ccusage-live
Version:
Enhanced Claude Code usage analysis tool with live team monitoring and collaboration features
305 lines (304 loc) • 9.83 kB
JavaScript
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 };