UNPKG

ccusage-live

Version:

Enhanced Claude Code usage analysis tool with live team monitoring and collaboration features

305 lines (304 loc) 9.83 kB
import { __toESM, require_usingCtx } from "./pricing-fetcher-C4VsnO1-.js"; import { getTotalTokens } from "./_token-utils-WjkbrjKv.js"; import { filterDateSchema } from "./_types-Dbrgtaqy.js"; import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js"; import { getClaudePaths, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData } from "./data-loader-B7Yx3ZuE.js"; import { name, version } from "./logger-D3prNztu.js"; import { z } from "zod"; import { StreamableHTTPTransport } from "@hono/mcp"; import "@modelcontextprotocol/sdk/client/index.js"; import "@modelcontextprotocol/sdk/inMemory.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Hono } from "hono/tiny"; var import_usingCtx = __toESM(require_usingCtx(), 1); const modelBreakdownSchema = z.object({ modelName: z.string(), inputTokens: z.number(), outputTokens: z.number(), cacheCreationTokens: z.number().optional(), cacheReadTokens: z.number().optional(), cost: z.number() }); const dailyUsageSchema = z.object({ date: z.string(), inputTokens: z.number(), outputTokens: z.number(), cacheCreationTokens: z.number().optional(), cacheReadTokens: z.number().optional(), totalTokens: z.number(), totalCost: z.number(), modelsUsed: z.array(z.string()), modelBreakdowns: z.array(modelBreakdownSchema) }); const sessionUsageSchema = z.object({ sessionId: z.string(), inputTokens: z.number(), outputTokens: z.number(), cacheCreationTokens: z.number().optional(), cacheReadTokens: z.number().optional(), totalTokens: z.number(), totalCost: z.number(), lastActivity: z.string(), modelsUsed: z.array(z.string()), modelBreakdowns: z.array(modelBreakdownSchema) }); const monthlyUsageSchema = z.object({ month: z.string(), inputTokens: z.number(), outputTokens: z.number(), cacheCreationTokens: z.number().optional(), cacheReadTokens: z.number().optional(), totalTokens: z.number(), totalCost: z.number(), modelsUsed: z.array(z.string()), modelBreakdowns: z.array(modelBreakdownSchema) }); const blockUsageSchema = z.object({ id: z.string(), startTime: z.string(), endTime: z.string().optional(), actualEndTime: z.string().optional(), isActive: z.boolean(), isGap: z.boolean(), entries: z.number(), tokenCounts: z.object({ inputTokens: z.number(), outputTokens: z.number(), cacheCreationInputTokens: z.number(), cacheReadInputTokens: z.number() }), totalTokens: z.number(), costUSD: z.number(), models: z.array(z.string()), burnRate: z.number().nullable(), projection: z.unknown().nullable() }); const dailyResponseSchema = { daily: z.array(dailyUsageSchema), totals: z.object({ totalInputTokens: z.number().optional(), totalOutputTokens: z.number().optional(), totalCacheCreationTokens: z.number().optional(), totalCacheReadTokens: z.number().optional(), totalTokens: z.number().optional(), totalCost: z.number().optional(), modelsUsed: z.array(z.string()).optional() }) }; const sessionResponseSchema = { sessions: z.array(sessionUsageSchema), totals: z.object({ totalInputTokens: z.number().optional(), totalOutputTokens: z.number().optional(), totalCacheCreationTokens: z.number().optional(), totalCacheReadTokens: z.number().optional(), totalTokens: z.number().optional(), totalCost: z.number().optional(), modelsUsed: z.array(z.string()).optional() }) }; const monthlyResponseSchema = { monthly: z.array(monthlyUsageSchema), totals: z.object({ totalInputTokens: z.number().optional(), totalOutputTokens: z.number().optional(), totalCacheCreationTokens: z.number().optional(), totalCacheReadTokens: z.number().optional(), totalTokens: z.number().optional(), totalCost: z.number().optional(), modelsUsed: z.array(z.string()).optional() }) }; const blocksResponseSchema = { blocks: z.array(blockUsageSchema) }; /** * Helper function to transform usage data with totals into JSON output format */ function transformUsageDataWithTotals(data, totals, mapper, key) { return { [key]: data.map(mapper), totals: createTotalsObject(totals) }; } /** Default options for the MCP server */ const defaultOptions = { claudePath: (() => { const paths = getClaudePaths(); if (paths.length === 0) throw new Error("No valid Claude path found. Ensure getClaudePaths() returns at least one valid path."); return paths[0]; })() }; /** * Creates an MCP server with tools for showing usage reports. * Registers tools for daily, session, monthly, and blocks usage data. * * @param options - Configuration options for the MCP server * @param options.claudePath - Path to Claude's data directory * @returns Configured MCP server instance with registered tools */ function createMcpServer({ claudePath } = defaultOptions) { const server = new McpServer({ name, version }); const parametersZodSchema = { since: filterDateSchema.optional(), until: filterDateSchema.optional(), mode: z.enum([ "auto", "calculate", "display" ]).default("auto").optional() }; server.registerTool("daily", { description: "Show usage report grouped by date", inputSchema: parametersZodSchema, outputSchema: dailyResponseSchema }, async (args) => { const dailyData = await loadDailyUsageData({ ...args, claudePath }); const totals = calculateTotals(dailyData); const jsonOutput = transformUsageDataWithTotals(dailyData, totals, (data) => ({ date: data.date, inputTokens: data.inputTokens, outputTokens: data.outputTokens, cacheCreationTokens: data.cacheCreationTokens, cacheReadTokens: data.cacheReadTokens, totalTokens: getTotalTokens(data), totalCost: data.totalCost, modelsUsed: data.modelsUsed, modelBreakdowns: data.modelBreakdowns }), "daily"); return { content: [{ type: "text", text: JSON.stringify(jsonOutput, null, 2) }], structuredContent: jsonOutput }; }); server.registerTool("session", { description: "Show usage report grouped by conversation session", inputSchema: parametersZodSchema, outputSchema: sessionResponseSchema }, async (args) => { const sessionData = await loadSessionData({ ...args, claudePath }); const totals = calculateTotals(sessionData); const jsonOutput = transformUsageDataWithTotals(sessionData, totals, (data) => ({ sessionId: data.sessionId, inputTokens: data.inputTokens, outputTokens: data.outputTokens, cacheCreationTokens: data.cacheCreationTokens, cacheReadTokens: data.cacheReadTokens, totalTokens: getTotalTokens(data), totalCost: data.totalCost, lastActivity: data.lastActivity, modelsUsed: data.modelsUsed, modelBreakdowns: data.modelBreakdowns }), "sessions"); return { content: [{ type: "text", text: JSON.stringify(jsonOutput, null, 2) }], structuredContent: jsonOutput }; }); server.registerTool("monthly", { description: "Show usage report grouped by month", inputSchema: parametersZodSchema, outputSchema: monthlyResponseSchema }, async (args) => { const monthlyData = await loadMonthlyUsageData({ ...args, claudePath }); const totals = calculateTotals(monthlyData); const jsonOutput = transformUsageDataWithTotals(monthlyData, totals, (data) => ({ month: data.month, inputTokens: data.inputTokens, outputTokens: data.outputTokens, cacheCreationTokens: data.cacheCreationTokens, cacheReadTokens: data.cacheReadTokens, totalTokens: getTotalTokens(data), totalCost: data.totalCost, modelsUsed: data.modelsUsed, modelBreakdowns: data.modelBreakdowns }), "monthly"); return { content: [{ type: "text", text: JSON.stringify(jsonOutput, null, 2) }], structuredContent: jsonOutput }; }); server.registerTool("blocks", { description: "Show usage report grouped by session billing blocks", inputSchema: parametersZodSchema, outputSchema: blocksResponseSchema }, async (args) => { const blocks = await loadSessionBlockData({ ...args, claudePath }); const jsonOutput = { blocks: blocks.map((block) => { return { id: block.id, startTime: block.startTime.toISOString(), endTime: block.endTime.toISOString(), actualEndTime: block.actualEndTime?.toISOString() ?? null, isActive: block.isActive, isGap: block.isGap ?? false, entries: block.entries.length, tokenCounts: block.tokenCounts, totalTokens: block.tokenCounts.inputTokens + block.tokenCounts.outputTokens, costUSD: block.costUSD, models: block.models, burnRate: null, projection: null }; }) }; return { content: [{ type: "text", text: JSON.stringify(jsonOutput, null, 2) }], structuredContent: jsonOutput }; }); return server; } /** * Start the MCP server with stdio transport. * Used for traditional MCP client connections via standard input/output. * * @param server - The MCP server instance to start */ async function startMcpServerStdio(server) { const transport = new StdioServerTransport(); await server.connect(transport); } /** * Create Hono app for MCP HTTP server. * Provides HTTP transport support for MCP protocol using Hono framework. * Handles POST requests for MCP communication and returns appropriate errors for other methods. * * @param options - Configuration options for the MCP server * @param options.claudePath - Path to Claude's data directory * @returns Configured Hono application for HTTP MCP transport */ function createMcpHttpApp(options = defaultOptions) { const app = new Hono(); const mcpServer = createMcpServer(options); app.all("/", async (c) => { const transport = new StreamableHTTPTransport(); await mcpServer.connect(transport); return transport.handleRequest(c); }); return app; } export { createMcpHttpApp, createMcpServer, startMcpServerStdio };