New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

codeledger

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

codeledger - npm Package Compare versions

Comparing version
0.1.0
to
0.2.0
+143
dist/hooks/on-stop.js
import {
deduplicateMessages,
extractAssistantData,
readJsonlLines
} from "../chunk-LZZNYR5Q.js";
import {
insertSession,
insertTokenUsage,
upsertProject
} from "../chunk-DAM47DIR.js";
import {
initHookDb
} from "../chunk-MVMBCMM6.js";
import {
calculateCost
} from "../chunk-O5NAJLY3.js";
// src/hooks/on-stop.ts
import { dirname, basename } from "path";
import { fileURLToPath } from "url";
async function handleStop(payload, dbPath) {
if (payload.agent_id) return;
const db = initHookDb(dbPath);
try {
const lines = await readJsonlLines(payload.transcript_path);
const firstUser = lines.find((l) => l.type === "user");
const sessionId = firstUser?.sessionId ?? basename(payload.transcript_path, ".jsonl");
const claudeVersion = firstUser?.version ?? null;
const gitBranch = firstUser?.gitBranch ?? null;
const actualCwd = firstUser?.cwd ?? payload.cwd;
const firstLineTimestamp = lines[0]?.timestamp ?? null;
const rawAssistant = lines.filter((l) => l.type === "assistant").map(extractAssistantData).filter((d) => d !== null);
const messages = deduplicateMessages(rawAssistant);
if (messages.length === 0) return;
const dir = dirname(payload.transcript_path);
const projectPath = basename(dir);
const displayName = actualCwd.split("/").filter(Boolean).pop() ?? projectPath;
db.transaction(() => {
const projectId = upsertProject(
db,
projectPath,
displayName,
actualCwd,
messages[0]?.timestamp ?? firstLineTimestamp ?? (/* @__PURE__ */ new Date()).toISOString()
);
const modelTokens = /* @__PURE__ */ new Map();
let totalInput = 0;
let totalOutput = 0;
let totalCacheCreate = 0;
let totalCacheRead = 0;
let totalCost = 0;
let toolUseCount = 0;
const messageCosts = [];
for (const msg of messages) {
const cost = calculateCost(db, msg.model, {
input_tokens: msg.inputTokens,
output_tokens: msg.outputTokens,
cache_create_tokens: msg.cacheCreateTokens,
cache_read_tokens: msg.cacheReadTokens
});
messageCosts.push(cost);
totalInput += msg.inputTokens;
totalOutput += msg.outputTokens;
totalCacheCreate += msg.cacheCreateTokens;
totalCacheRead += msg.cacheReadTokens;
totalCost += cost;
const mt = modelTokens.get(msg.model) ?? 0;
modelTokens.set(msg.model, mt + msg.outputTokens);
toolUseCount += msg.toolUses.length;
}
let primaryModel = null;
let maxOutput = 0;
for (const [model, output] of modelTokens) {
if (output > maxOutput) {
maxOutput = output;
primaryModel = model;
}
}
const agentCount = messages.reduce(
(sum, m) => sum + m.toolUses.filter((t) => t.name === "Agent").length,
0
);
const startedAt = messages[0]?.timestamp ?? firstLineTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
const endedAt = messages[messages.length - 1]?.timestamp ?? null;
insertSession(db, {
id: sessionId,
projectId,
startedAt,
endedAt,
endReason: null,
// Only SessionEnd sets this
primaryModel,
claudeVersion,
gitBranch,
totalInputTokens: totalInput,
totalOutputTokens: totalOutput,
totalCacheCreateTokens: totalCacheCreate,
totalCacheReadTokens: totalCacheRead,
totalCostUsd: totalCost,
messageCount: messages.length,
toolUseCount,
agentCount
});
for (let i = 0; i < messages.length; i++) {
const msg = messages[i];
insertTokenUsage(db, {
sessionId,
messageId: msg.messageId,
model: msg.model,
inputTokens: msg.inputTokens,
outputTokens: msg.outputTokens,
cacheCreateTokens: msg.cacheCreateTokens,
cacheReadTokens: msg.cacheReadTokens,
costUsd: messageCosts[i],
timestamp: msg.timestamp
});
}
})();
} finally {
db.close();
}
}
async function main() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
const payload = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
try {
await handleStop(payload);
} catch (err) {
process.stderr.write(`[codeledger] Stop hook error: ${err}
`);
}
process.exit(0);
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main();
}
export {
handleStop
};
//# sourceMappingURL=on-stop.js.map
{"version":3,"sources":["../../src/hooks/on-stop.ts"],"sourcesContent":["import { initHookDb } from \"./init-db.js\";\nimport { readJsonlLines } from \"../parser/jsonl-reader.js\";\nimport {\n extractAssistantData,\n deduplicateMessages,\n} from \"../parser/message-extractor.js\";\nimport { calculateCost } from \"../db/pricing.js\";\nimport { upsertProject, insertSession, insertTokenUsage } from \"../db/queries.js\";\nimport { dirname, basename } from \"path\";\n\n/**\n * Lightweight real-time session tracking.\n *\n * Called on every Claude response (Stop event) DURING the session.\n * Only writes to dedup-safe tables:\n * - token_usage: INSERT OR IGNORE (UNIQUE constraint handles dedup)\n * - sessions: INSERT OR REPLACE (latest totals overwrite)\n * - projects: ON CONFLICT upsert (idempotent)\n *\n * Does NOT touch accumulation-based tables (daily_summaries, tool_calls)\n * or run classification — SessionEnd handles those at session completion.\n */\n\ninterface StopPayload {\n session_id: string;\n transcript_path: string;\n cwd: string;\n permission_mode: string;\n hook_event_name: string;\n stop_hook_active?: boolean;\n last_assistant_message?: string;\n agent_id?: string;\n agent_type?: string;\n}\n\nexport async function handleStop(\n payload: StopPayload,\n dbPath?: string,\n): Promise<void> {\n // Skip if this is a subagent Stop — SubagentStop hook handles those\n if (payload.agent_id) return;\n\n const db = initHookDb(dbPath);\n\n try {\n const lines = await readJsonlLines(payload.transcript_path);\n\n // Extract session metadata from first user line\n const firstUser = lines.find((l) => l.type === \"user\") as any;\n const sessionId = firstUser?.sessionId ?? basename(payload.transcript_path, \".jsonl\");\n const claudeVersion = firstUser?.version ?? null;\n const gitBranch = firstUser?.gitBranch ?? null;\n const actualCwd = firstUser?.cwd ?? payload.cwd;\n\n const firstLineTimestamp = (lines[0] as any)?.timestamp ?? null;\n\n // Extract and deduplicate assistant messages\n const rawAssistant = lines\n .filter((l) => l.type === \"assistant\")\n .map(extractAssistantData)\n .filter((d): d is NonNullable<typeof d> => d !== null);\n\n const messages = deduplicateMessages(rawAssistant);\n\n if (messages.length === 0) return;\n\n // Derive project path and display name\n const dir = dirname(payload.transcript_path);\n const projectPath = basename(dir);\n const displayName = actualCwd.split(\"/\").filter(Boolean).pop() ?? projectPath;\n\n db.transaction(() => {\n const projectId = upsertProject(\n db,\n projectPath,\n displayName,\n actualCwd,\n messages[0]?.timestamp ?? firstLineTimestamp ?? new Date().toISOString(),\n );\n\n // Compute aggregates\n const modelTokens = new Map<string, number>();\n let totalInput = 0;\n let totalOutput = 0;\n let totalCacheCreate = 0;\n let totalCacheRead = 0;\n let totalCost = 0;\n let toolUseCount = 0;\n const messageCosts: number[] = [];\n\n for (const msg of messages) {\n const cost = calculateCost(db, msg.model, {\n input_tokens: msg.inputTokens,\n output_tokens: msg.outputTokens,\n cache_create_tokens: msg.cacheCreateTokens,\n cache_read_tokens: msg.cacheReadTokens,\n });\n messageCosts.push(cost);\n totalInput += msg.inputTokens;\n totalOutput += msg.outputTokens;\n totalCacheCreate += msg.cacheCreateTokens;\n totalCacheRead += msg.cacheReadTokens;\n totalCost += cost;\n\n const mt = modelTokens.get(msg.model) ?? 0;\n modelTokens.set(msg.model, mt + msg.outputTokens);\n\n toolUseCount += msg.toolUses.length;\n }\n\n // Primary model\n let primaryModel: string | null = null;\n let maxOutput = 0;\n for (const [model, output] of modelTokens) {\n if (output > maxOutput) {\n maxOutput = output;\n primaryModel = model;\n }\n }\n\n const agentCount = messages.reduce(\n (sum, m) => sum + m.toolUses.filter((t) => t.name === \"Agent\").length,\n 0,\n );\n\n const startedAt = messages[0]?.timestamp ?? firstLineTimestamp ?? new Date().toISOString();\n const endedAt = messages[messages.length - 1]?.timestamp ?? null;\n\n // Upsert session — INSERT OR REPLACE ensures latest totals win\n insertSession(db, {\n id: sessionId,\n projectId,\n startedAt,\n endedAt,\n endReason: null, // Only SessionEnd sets this\n primaryModel,\n claudeVersion,\n gitBranch,\n totalInputTokens: totalInput,\n totalOutputTokens: totalOutput,\n totalCacheCreateTokens: totalCacheCreate,\n totalCacheReadTokens: totalCacheRead,\n totalCostUsd: totalCost,\n messageCount: messages.length,\n toolUseCount,\n agentCount,\n });\n\n // Insert token_usage — INSERT OR IGNORE handles dedup via UNIQUE(session_id, message_id)\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n insertTokenUsage(db, {\n sessionId,\n messageId: msg.messageId,\n model: msg.model,\n inputTokens: msg.inputTokens,\n outputTokens: msg.outputTokens,\n cacheCreateTokens: msg.cacheCreateTokens,\n cacheReadTokens: msg.cacheReadTokens,\n costUsd: messageCosts[i],\n timestamp: msg.timestamp,\n });\n }\n })();\n } finally {\n db.close();\n }\n}\n\n// When run as hook handler: read JSON from stdin\nasync function main() {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n const payload: StopPayload = JSON.parse(Buffer.concat(chunks).toString(\"utf-8\"));\n\n try {\n await handleStop(payload);\n } catch (err) {\n process.stderr.write(`[codeledger] Stop hook error: ${err}\\n`);\n }\n process.exit(0);\n}\n\nimport { fileURLToPath } from \"url\";\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n main();\n}\n"],"mappings":"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","names":[]}
+1
-1
{
"name": "codeledger",
"version": "0.1.0",
"version": "0.2.0",
"description": "Token-level cost intelligence for Claude Code",

@@ -5,0 +5,0 @@ "author": {

@@ -350,3 +350,3 @@ import {

name: "codeledger",
version: "0.1.0"
version: "0.2.0"
});

@@ -353,0 +353,0 @@ registerUsageSummary(server, db);

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/index.ts","../src/tools/usage-summary.ts","../src/tools/project-usage.ts","../src/tools/model-stats.ts","../src/tools/agent-usage.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createConnection, getDefaultDbPath } from \"./db/connection.js\";\nimport { runMigrations } from \"./db/migrate.js\";\nimport { seedPricing } from \"./db/pricing.js\";\nimport { scanForNewSessions } from \"./sync/scanner.js\";\nimport { classifyAllSessions } from \"./classifier/categorize-session.js\";\nimport { registerUsageSummary } from \"./tools/usage-summary.js\";\nimport { registerProjectUsage } from \"./tools/project-usage.js\";\nimport { registerModelStats } from \"./tools/model-stats.js\";\nimport { registerAgentUsage } from \"./tools/agent-usage.js\";\nimport { registerSkillUsage } from \"./tools/skill-usage.js\";\nimport { registerCostOptimize } from \"./tools/cost-optimize.js\";\n\nconst SCAN_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n\nasync function main() {\n const dbPath = getDefaultDbPath();\n const claudeDir = process.env.CLAUDE_DATA_DIR ||\n `${process.env.HOME || process.env.USERPROFILE}/.claude`;\n\n const db = createConnection(dbPath);\n runMigrations(db);\n seedPricing(db);\n\n // Retroactively classify existing sessions\n classifyAllSessions(db);\n\n // Initial scan\n await scanForNewSessions(db, claudeDir);\n\n // Periodic scan\n setInterval(async () => {\n try {\n await scanForNewSessions(db, claudeDir);\n } catch (err) {\n process.stderr.write(`[codeledger] Scanner error: ${err}\\n`);\n }\n }, SCAN_INTERVAL_MS);\n\n // Create MCP server\n const server = new McpServer({\n name: \"codeledger\",\n version: \"0.1.0\",\n });\n\n // Register Phase A tools\n registerUsageSummary(server, db);\n registerProjectUsage(server, db);\n registerModelStats(server, db);\n\n // Register Phase B tools\n registerAgentUsage(server, db);\n registerSkillUsage(server, db);\n\n // Register Phase C tools\n registerCostOptimize(server, db);\n\n // Connect via stdio\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n process.stderr.write(`[codeledger] Fatal: ${err}\\n`);\n process.exit(1);\n});\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { periodToStart } from \"../utils/period.js\";\n\ninterface UsageSummary {\n totalCostUsd: number;\n totalInputTokens: number;\n totalOutputTokens: number;\n sessionCount: number;\n topProject: string | null;\n modelDistribution: { model: string; cost: number; pct: number }[];\n overheadCostUsd: number;\n}\n\nexport function queryUsageSummary(\n db: Database.Database,\n period: string,\n project?: string\n): UsageSummary {\n const start = periodToStart(period);\n\n let sessionFilter = \"WHERE s.started_at >= ?\";\n const params: (string | number)[] = [start];\n\n if (project) {\n sessionFilter += \" AND p.display_name = ?\";\n params.push(project);\n }\n\n const totals = db\n .prepare(\n `\n SELECT\n COALESCE(SUM(s.total_cost_usd), 0) as cost,\n COALESCE(SUM(s.total_input_tokens), 0) as input_tok,\n COALESCE(SUM(s.total_output_tokens), 0) as output_tok,\n COUNT(*) as session_count\n FROM sessions s\n LEFT JOIN projects p ON s.project_id = p.id\n ${sessionFilter}\n `\n )\n .get(...params) as {\n cost: number;\n input_tok: number;\n output_tok: number;\n session_count: number;\n } | undefined;\n\n const topProj = db\n .prepare(\n `\n SELECT p.display_name, SUM(s.total_cost_usd) as cost\n FROM sessions s\n JOIN projects p ON s.project_id = p.id\n ${sessionFilter}\n GROUP BY p.id ORDER BY cost DESC LIMIT 1\n `\n )\n .get(...params) as { display_name: string; cost: number } | undefined;\n\n const models = db\n .prepare(\n `\n SELECT s.primary_model as model, SUM(s.total_cost_usd) as cost\n FROM sessions s\n LEFT JOIN projects p ON s.project_id = p.id\n ${sessionFilter}\n GROUP BY s.primary_model ORDER BY cost DESC\n `\n )\n .all(...params) as { model: string | null; cost: number }[];\n\n let agentFilter = \"WHERE a.started_at >= ?\";\n const agentParams: (string | number)[] = [start];\n if (project) {\n agentFilter += \" AND p.display_name = ?\";\n agentParams.push(project);\n }\n\n const overheadRow = db\n .prepare(\n `\n SELECT COALESCE(SUM(a.total_cost_usd), 0) as overhead_cost\n FROM agents a\n JOIN sessions s ON a.session_id = s.id\n LEFT JOIN projects p ON s.project_id = p.id\n ${agentFilter}\n AND a.source_category = 'overhead'\n `\n )\n .get(...agentParams) as { overhead_cost: number } | undefined;\n\n const totalCost = totals?.cost ?? 0;\n const modelDistribution = models.map((m) => ({\n model: m.model ?? \"unknown\",\n cost: m.cost,\n pct: totalCost > 0 ? Math.round((m.cost / totalCost) * 100) : 0,\n }));\n\n return {\n totalCostUsd: totalCost,\n totalInputTokens: totals?.input_tok ?? 0,\n totalOutputTokens: totals?.output_tok ?? 0,\n sessionCount: totals?.session_count ?? 0,\n topProject: topProj?.display_name ?? null,\n modelDistribution,\n overheadCostUsd: overheadRow?.overhead_cost ?? 0,\n };\n}\n\nexport function registerUsageSummary(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"usage_summary\",\n \"Get a smart summary of Claude Code usage and costs\",\n {\n period: z\n .enum([\"today\", \"week\", \"month\", \"all\"])\n .default(\"today\"),\n project: z.string().optional().describe(\"Filter by project name\"),\n },\n async ({ period, project }) => {\n const s = queryUsageSummary(db, period, project);\n\n const lines = [\n `## Usage Summary (${period})`,\n `**Total Cost:** $${s.totalCostUsd.toFixed(2)}`,\n `**Sessions:** ${s.sessionCount}`,\n `**Tokens:** ${s.totalInputTokens.toLocaleString()} input, ${s.totalOutputTokens.toLocaleString()} output`,\n s.topProject ? `**Top Project:** ${s.topProject}` : \"\",\n s.overheadCostUsd > 0 ? `**Background overhead:** $${s.overheadCostUsd.toFixed(2)} (plugin observers, system agents)` : \"\",\n \"\",\n \"**Model Distribution:**\",\n ...s.modelDistribution.map(\n (m) => `- ${m.model}: $${m.cost.toFixed(2)} (${m.pct}%)`\n ),\n ];\n\n return {\n content: [{ type: \"text\" as const, text: lines.filter(Boolean).join(\"\\n\") }],\n };\n }\n );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { periodToStart } from \"../utils/period.js\";\n\nexport function queryProjectUsage(\n db: Database.Database,\n period: string,\n sortBy: string,\n limit: number\n): any[] {\n const start = periodToStart(period);\n\n const orderCol =\n sortBy === \"tokens\"\n ? \"total_tokens\"\n : sortBy === \"sessions\"\n ? \"session_count\"\n : \"total_cost\";\n\n return db\n .prepare(\n `\n SELECT\n p.display_name as project,\n SUM(s.total_input_tokens + s.total_output_tokens) as total_tokens,\n SUM(s.total_cost_usd) as total_cost,\n COUNT(*) as session_count,\n MAX(s.started_at) as last_active\n FROM sessions s\n JOIN projects p ON s.project_id = p.id\n WHERE s.started_at >= ?\n GROUP BY p.id\n ORDER BY ${orderCol} DESC\n LIMIT ?\n `\n )\n .all(start, limit) as any[];\n}\n\nexport function registerProjectUsage(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"project_usage\",\n \"Get token usage and costs broken down by project\",\n {\n period: z\n .enum([\"today\", \"week\", \"month\", \"all\"])\n .default(\"week\"),\n sort_by: z\n .enum([\"cost\", \"tokens\", \"sessions\"])\n .default(\"cost\"),\n limit: z.number().default(10),\n },\n async ({ period, sort_by, limit }) => {\n const projects = queryProjectUsage(db, period, sort_by, limit);\n const lines = [\n `## Project Usage (${period}, top ${limit} by ${sort_by})`,\n \"\",\n \"| Project | Cost | Tokens | Sessions | Last Active |\",\n \"|---------|------|--------|----------|-------------|\",\n ...projects.map(\n (p: any) =>\n `| ${p.project} | $${p.total_cost.toFixed(2)} | ${Number(p.total_tokens).toLocaleString()} | ${p.session_count} | ${p.last_active?.split(\"T\")[0] ?? \"\\u2014\"} |`\n ),\n ];\n if (projects.length === 0) lines.push(\"No project data for this period.\");\n return { content: [{ type: \"text\" as const, text: lines.join(\"\\n\") }] };\n }\n );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { lookupPricing } from \"../db/pricing.js\";\nimport { periodToStart } from \"../utils/period.js\";\n\nexport interface ModelStatsRow {\n model: string;\n totalCost: number;\n totalInput: number;\n totalOutput: number;\n messageCount: number;\n pct: number;\n hypotheticalSonnetCost: number;\n potentialSavings: number;\n}\n\nexport function queryModelStats(\n db: Database.Database,\n period: string\n): ModelStatsRow[] {\n const start = periodToStart(period);\n\n const rows = db\n .prepare(\n `\n SELECT\n model,\n SUM(input_tokens) as total_input,\n SUM(output_tokens) as total_output,\n SUM(cost_usd) as total_cost,\n COUNT(*) as message_count\n FROM token_usage\n WHERE timestamp >= ?\n GROUP BY model\n ORDER BY total_cost DESC\n `\n )\n .all(start) as Array<{\n model: string;\n total_input: number;\n total_output: number;\n total_cost: number;\n message_count: number;\n }>;\n\n const totalCost = rows.reduce((sum, r) => sum + r.total_cost, 0);\n\n const sonnetPricing = lookupPricing(db, \"claude-sonnet-4-5\");\n\n return rows.map((r) => {\n let hypotheticalCost = r.total_cost;\n if (\n sonnetPricing &&\n !r.model.includes(\"sonnet\") &&\n !r.model.includes(\"haiku\")\n ) {\n hypotheticalCost =\n (r.total_input * sonnetPricing.input_per_mtok) / 1_000_000 +\n (r.total_output * sonnetPricing.output_per_mtok) / 1_000_000;\n }\n return {\n model: r.model,\n totalCost: r.total_cost,\n totalInput: r.total_input,\n totalOutput: r.total_output,\n messageCount: r.message_count,\n pct: totalCost > 0 ? Math.round((r.total_cost / totalCost) * 100) : 0,\n hypotheticalSonnetCost: hypotheticalCost,\n potentialSavings: Math.max(0, r.total_cost - hypotheticalCost),\n };\n });\n}\n\nexport function registerModelStats(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"model_stats\",\n \"Analyze which models are being used and potential cost savings\",\n {\n period: z.enum([\"today\", \"week\", \"month\", \"all\"]).default(\"month\"),\n },\n async ({ period }) => {\n const stats = queryModelStats(db, period);\n const totalSavings = stats.reduce((s, m) => s + m.potentialSavings, 0);\n\n const lines = [\n `## Model Usage (${period})`,\n \"\",\n \"| Model | Cost | % | Messages | If Sonnet | Savings |\",\n \"|-------|------|---|----------|-----------|---------|\",\n ...stats.map(\n (m) =>\n `| ${m.model} | $${m.totalCost.toFixed(2)} | ${m.pct}% | ${m.messageCount} | $${m.hypotheticalSonnetCost.toFixed(2)} | $${m.potentialSavings.toFixed(2)} |`\n ),\n \"\",\n totalSavings > 0\n ? `**Potential savings if non-Opus tasks used Sonnet: $${totalSavings.toFixed(2)}**`\n : \"Model usage looks optimized.\",\n ];\n return { content: [{ type: \"text\" as const, text: lines.join(\"\\n\") }] };\n }\n );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { periodToStart } from \"../utils/period.js\";\n\ninterface AgentUsageRow {\n agent_id: string;\n session_id: string;\n agent_type: string | null;\n description: string | null;\n model: string | null;\n total_input_tokens: number;\n total_output_tokens: number;\n total_cache_create_tokens: number;\n total_cache_read_tokens: number;\n total_cost_usd: number;\n started_at: string | null;\n ended_at: string | null;\n message_count: number;\n source_category: string;\n project: string;\n}\n\nexport function queryAgentUsage(\n db: Database.Database,\n period: string,\n filters?: { sessionId?: string; project?: string; sourceCategory?: string }\n): AgentUsageRow[] {\n const start = periodToStart(period);\n\n let whereClause = \"WHERE a.started_at >= ?\";\n const params: (string | number)[] = [start];\n\n if (filters?.sessionId) {\n whereClause += \" AND a.session_id = ?\";\n params.push(filters.sessionId);\n }\n\n if (filters?.project) {\n whereClause += \" AND p.display_name = ?\";\n params.push(filters.project);\n }\n\n if (filters?.sourceCategory) {\n whereClause += \" AND a.source_category = ?\";\n params.push(filters.sourceCategory);\n }\n\n return db\n .prepare(\n `\n SELECT\n a.id as agent_id,\n a.session_id,\n a.agent_type,\n a.description,\n a.model,\n a.total_input_tokens,\n a.total_output_tokens,\n a.total_cache_create_tokens,\n a.total_cache_read_tokens,\n a.total_cost_usd,\n a.started_at,\n a.ended_at,\n a.message_count,\n a.source_category,\n p.display_name as project\n FROM agents a\n JOIN sessions s ON a.session_id = s.id\n JOIN projects p ON s.project_id = p.id\n ${whereClause}\n ORDER BY a.total_cost_usd DESC\n `\n )\n .all(...params) as AgentUsageRow[];\n}\n\nexport function registerAgentUsage(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"agent_usage\",\n \"Get token usage broken down by subagent\",\n {\n session_id: z.string().optional().describe(\"Filter by session\"),\n project: z.string().optional().describe(\"Filter by project\"),\n period: z\n .enum([\"today\", \"week\", \"month\", \"all\"])\n .default(\"week\"),\n source_category: z\n .enum([\"user\", \"overhead\", \"all\"])\n .default(\"all\")\n .describe(\"Filter: 'user' for coding work, 'overhead' for background agents\"),\n },\n async ({ session_id, project, period, source_category }) => {\n const agents = queryAgentUsage(db, period, {\n sessionId: session_id,\n project,\n sourceCategory: source_category === \"all\" ? undefined : source_category,\n });\n\n if (agents.length === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `No agent usage data found for period: ${period}`,\n },\n ],\n };\n }\n\n const totalCost = agents.reduce((sum, a) => sum + a.total_cost_usd, 0);\n const userCost = agents.filter(a => a.source_category === \"user\").reduce((s, a) => s + a.total_cost_usd, 0);\n const overheadCost = agents.filter(a => a.source_category === \"overhead\").reduce((s, a) => s + a.total_cost_usd, 0);\n\n const lines = [\n `## Agent Usage (${period})`,\n `**Total agents:** ${agents.length} | **Total cost:** $${totalCost.toFixed(2)}`,\n `**Your coding agents:** $${userCost.toFixed(2)} | **Background overhead:** $${overheadCost.toFixed(2)}`,\n \"\",\n \"| Agent | Type | Model | Tokens (in/out) | Cost | Messages | Session | Project |\",\n \"|-------|------|-------|-----------------|------|----------|---------|---------|\",\n ...agents.map((a) => {\n const tokens = `${Number(a.total_input_tokens).toLocaleString()}/${Number(a.total_output_tokens).toLocaleString()}`;\n return `| ${a.agent_id} | ${a.agent_type ?? \"\\u2014\"} | ${a.model ?? \"\\u2014\"} | ${tokens} | $${a.total_cost_usd.toFixed(2)} | ${a.message_count} | ${a.session_id.slice(0, 8)}... | ${a.project} |`;\n }),\n ];\n\n return {\n content: [{ type: \"text\" as const, text: lines.join(\"\\n\") }],\n };\n }\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;;;ACCrC,SAAS,SAAS;AAaX,SAAS,kBACd,IACA,QACA,SACc;AACd,QAAM,QAAQ,cAAc,MAAM;AAElC,MAAI,gBAAgB;AACpB,QAAM,SAA8B,CAAC,KAAK;AAE1C,MAAI,SAAS;AACX,qBAAiB;AACjB,WAAO,KAAK,OAAO;AAAA,EACrB;AAEA,QAAM,SAAS,GACZ;AAAA,IACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,aAAa;AAAA;AAAA,EAEf,EACC,IAAI,GAAG,MAAM;AAOhB,QAAM,UAAU,GACb;AAAA,IACC;AAAA;AAAA;AAAA;AAAA,MAIA,aAAa;AAAA;AAAA;AAAA,EAGf,EACC,IAAI,GAAG,MAAM;AAEhB,QAAM,SAAS,GACZ;AAAA,IACC;AAAA;AAAA;AAAA;AAAA,MAIA,aAAa;AAAA;AAAA;AAAA,EAGf,EACC,IAAI,GAAG,MAAM;AAEhB,MAAI,cAAc;AAClB,QAAM,cAAmC,CAAC,KAAK;AAC/C,MAAI,SAAS;AACX,mBAAe;AACf,gBAAY,KAAK,OAAO;AAAA,EAC1B;AAEA,QAAM,cAAc,GACjB;AAAA,IACC;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,WAAW;AAAA;AAAA;AAAA,EAGb,EACC,IAAI,GAAG,WAAW;AAErB,QAAM,YAAY,QAAQ,QAAQ;AAClC,QAAM,oBAAoB,OAAO,IAAI,CAAC,OAAO;AAAA,IAC3C,OAAO,EAAE,SAAS;AAAA,IAClB,MAAM,EAAE;AAAA,IACR,KAAK,YAAY,IAAI,KAAK,MAAO,EAAE,OAAO,YAAa,GAAG,IAAI;AAAA,EAChE,EAAE;AAEF,SAAO;AAAA,IACL,cAAc;AAAA,IACd,kBAAkB,QAAQ,aAAa;AAAA,IACvC,mBAAmB,QAAQ,cAAc;AAAA,IACzC,cAAc,QAAQ,iBAAiB;AAAA,IACvC,YAAY,SAAS,gBAAgB;AAAA,IACrC;AAAA,IACA,iBAAiB,aAAa,iBAAiB;AAAA,EACjD;AACF;AAEO,SAAS,qBACd,QACA,IACM;AACN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQ,EACL,KAAK,CAAC,SAAS,QAAQ,SAAS,KAAK,CAAC,EACtC,QAAQ,OAAO;AAAA,MAClB,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,wBAAwB;AAAA,IAClE;AAAA,IACA,OAAO,EAAE,QAAQ,QAAQ,MAAM;AAC7B,YAAM,IAAI,kBAAkB,IAAI,QAAQ,OAAO;AAE/C,YAAM,QAAQ;AAAA,QACZ,qBAAqB,MAAM;AAAA,QAC3B,oBAAoB,EAAE,aAAa,QAAQ,CAAC,CAAC;AAAA,QAC7C,iBAAiB,EAAE,YAAY;AAAA,QAC/B,eAAe,EAAE,iBAAiB,eAAe,CAAC,WAAW,EAAE,kBAAkB,eAAe,CAAC;AAAA,QACjG,EAAE,aAAa,oBAAoB,EAAE,UAAU,KAAK;AAAA,QACpD,EAAE,kBAAkB,IAAI,6BAA6B,EAAE,gBAAgB,QAAQ,CAAC,CAAC,uCAAuC;AAAA,QACxH;AAAA,QACA;AAAA,QACA,GAAG,EAAE,kBAAkB;AAAA,UACrB,CAAC,MAAM,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,CAAC,CAAC,KAAK,EAAE,GAAG;AAAA,QACtD;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,OAAO,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;AAAA,MAC7E;AAAA,IACF;AAAA,EACF;AACF;;;ACjJA,SAAS,KAAAA,UAAS;AAGX,SAAS,kBACd,IACA,QACA,QACA,OACO;AACP,QAAM,QAAQ,cAAc,MAAM;AAElC,QAAM,WACJ,WAAW,WACP,iBACA,WAAW,aACT,kBACA;AAER,SAAO,GACJ;AAAA,IACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAWS,QAAQ;AAAA;AAAA;AAAA,EAGnB,EACC,IAAI,OAAO,KAAK;AACrB;AAEO,SAAS,qBACd,QACA,IACM;AACN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQC,GACL,KAAK,CAAC,SAAS,QAAQ,SAAS,KAAK,CAAC,EACtC,QAAQ,MAAM;AAAA,MACjB,SAASA,GACN,KAAK,CAAC,QAAQ,UAAU,UAAU,CAAC,EACnC,QAAQ,MAAM;AAAA,MACjB,OAAOA,GAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,IAC9B;AAAA,IACA,OAAO,EAAE,QAAQ,SAAS,MAAM,MAAM;AACpC,YAAM,WAAW,kBAAkB,IAAI,QAAQ,SAAS,KAAK;AAC7D,YAAM,QAAQ;AAAA,QACZ,qBAAqB,MAAM,SAAS,KAAK,OAAO,OAAO;AAAA,QACvD;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,SAAS;AAAA,UACV,CAAC,MACC,KAAK,EAAE,OAAO,OAAO,EAAE,WAAW,QAAQ,CAAC,CAAC,MAAM,OAAO,EAAE,YAAY,EAAE,eAAe,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE,aAAa,MAAM,GAAG,EAAE,CAAC,KAAK,QAAQ;AAAA,QAChK;AAAA,MACF;AACA,UAAI,SAAS,WAAW,EAAG,OAAM,KAAK,kCAAkC;AACxE,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,KAAK,IAAI,EAAE,CAAC,EAAE;AAAA,IACxE;AAAA,EACF;AACF;;;ACtEA,SAAS,KAAAC,UAAS;AAeX,SAAS,gBACd,IACA,QACiB;AACjB,QAAM,QAAQ,cAAc,MAAM;AAElC,QAAM,OAAO,GACV;AAAA,IACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYF,EACC,IAAI,KAAK;AAQZ,QAAM,YAAY,KAAK,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AAE/D,QAAM,gBAAgB,cAAc,IAAI,mBAAmB;AAE3D,SAAO,KAAK,IAAI,CAAC,MAAM;AACrB,QAAI,mBAAmB,EAAE;AACzB,QACE,iBACA,CAAC,EAAE,MAAM,SAAS,QAAQ,KAC1B,CAAC,EAAE,MAAM,SAAS,OAAO,GACzB;AACA,yBACG,EAAE,cAAc,cAAc,iBAAkB,MAChD,EAAE,eAAe,cAAc,kBAAmB;AAAA,IACvD;AACA,WAAO;AAAA,MACL,OAAO,EAAE;AAAA,MACT,WAAW,EAAE;AAAA,MACb,YAAY,EAAE;AAAA,MACd,aAAa,EAAE;AAAA,MACf,cAAc,EAAE;AAAA,MAChB,KAAK,YAAY,IAAI,KAAK,MAAO,EAAE,aAAa,YAAa,GAAG,IAAI;AAAA,MACpE,wBAAwB;AAAA,MACxB,kBAAkB,KAAK,IAAI,GAAG,EAAE,aAAa,gBAAgB;AAAA,IAC/D;AAAA,EACF,CAAC;AACH;AAEO,SAAS,mBACd,QACA,IACM;AACN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQC,GAAE,KAAK,CAAC,SAAS,QAAQ,SAAS,KAAK,CAAC,EAAE,QAAQ,OAAO;AAAA,IACnE;AAAA,IACA,OAAO,EAAE,OAAO,MAAM;AACpB,YAAM,QAAQ,gBAAgB,IAAI,MAAM;AACxC,YAAM,eAAe,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,kBAAkB,CAAC;AAErE,YAAM,QAAQ;AAAA,QACZ,mBAAmB,MAAM;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,MAAM;AAAA,UACP,CAAC,MACC,KAAK,EAAE,KAAK,OAAO,EAAE,UAAU,QAAQ,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,EAAE,YAAY,OAAO,EAAE,uBAAuB,QAAQ,CAAC,CAAC,OAAO,EAAE,iBAAiB,QAAQ,CAAC,CAAC;AAAA,QAC3J;AAAA,QACA;AAAA,QACA,eAAe,IACX,uDAAuD,aAAa,QAAQ,CAAC,CAAC,OAC9E;AAAA,MACN;AACA,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,KAAK,IAAI,EAAE,CAAC,EAAE;AAAA,IACxE;AAAA,EACF;AACF;;;ACvGA,SAAS,KAAAC,UAAS;AAqBX,SAAS,gBACd,IACA,QACA,SACiB;AACjB,QAAM,QAAQ,cAAc,MAAM;AAElC,MAAI,cAAc;AAClB,QAAM,SAA8B,CAAC,KAAK;AAE1C,MAAI,SAAS,WAAW;AACtB,mBAAe;AACf,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAEA,MAAI,SAAS,SAAS;AACpB,mBAAe;AACf,WAAO,KAAK,QAAQ,OAAO;AAAA,EAC7B;AAEA,MAAI,SAAS,gBAAgB;AAC3B,mBAAe;AACf,WAAO,KAAK,QAAQ,cAAc;AAAA,EACpC;AAEA,SAAO,GACJ;AAAA,IACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAoBA,WAAW;AAAA;AAAA;AAAA,EAGb,EACC,IAAI,GAAG,MAAM;AAClB;AAEO,SAAS,mBACd,QACA,IACM;AACN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,YAAYC,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mBAAmB;AAAA,MAC9D,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mBAAmB;AAAA,MAC3D,QAAQA,GACL,KAAK,CAAC,SAAS,QAAQ,SAAS,KAAK,CAAC,EACtC,QAAQ,MAAM;AAAA,MACjB,iBAAiBA,GACd,KAAK,CAAC,QAAQ,YAAY,KAAK,CAAC,EAChC,QAAQ,KAAK,EACb,SAAS,kEAAkE;AAAA,IAChF;AAAA,IACA,OAAO,EAAE,YAAY,SAAS,QAAQ,gBAAgB,MAAM;AAC1D,YAAM,SAAS,gBAAgB,IAAI,QAAQ;AAAA,QACzC,WAAW;AAAA,QACX;AAAA,QACA,gBAAgB,oBAAoB,QAAQ,SAAY;AAAA,MAC1D,CAAC;AAED,UAAI,OAAO,WAAW,GAAG;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,yCAAyC,MAAM;AAAA,YACvD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,YAAY,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,gBAAgB,CAAC;AACrE,YAAM,WAAW,OAAO,OAAO,OAAK,EAAE,oBAAoB,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,gBAAgB,CAAC;AAC1G,YAAM,eAAe,OAAO,OAAO,OAAK,EAAE,oBAAoB,UAAU,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,gBAAgB,CAAC;AAElH,YAAM,QAAQ;AAAA,QACZ,mBAAmB,MAAM;AAAA,QACzB,qBAAqB,OAAO,MAAM,uBAAuB,UAAU,QAAQ,CAAC,CAAC;AAAA,QAC7E,4BAA4B,SAAS,QAAQ,CAAC,CAAC,gCAAgC,aAAa,QAAQ,CAAC,CAAC;AAAA,QACtG;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,OAAO,IAAI,CAAC,MAAM;AACnB,gBAAM,SAAS,GAAG,OAAO,EAAE,kBAAkB,EAAE,eAAe,CAAC,IAAI,OAAO,EAAE,mBAAmB,EAAE,eAAe,CAAC;AACjH,iBAAO,KAAK,EAAE,QAAQ,MAAM,EAAE,cAAc,QAAQ,MAAM,EAAE,SAAS,QAAQ,MAAM,MAAM,OAAO,EAAE,eAAe,QAAQ,CAAC,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE,WAAW,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,OAAO;AAAA,QAClM,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,KAAK,IAAI,EAAE,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACF;;;AJzHA,IAAM,mBAAmB,IAAI,KAAK;AAElC,eAAe,OAAO;AACpB,QAAM,SAAS,iBAAiB;AAChC,QAAM,YAAY,QAAQ,IAAI,mBAC5B,GAAG,QAAQ,IAAI,QAAQ,QAAQ,IAAI,WAAW;AAEhD,QAAM,KAAK,iBAAiB,MAAM;AAClC,gBAAc,EAAE;AAChB,cAAY,EAAE;AAGd,sBAAoB,EAAE;AAGtB,QAAM,mBAAmB,IAAI,SAAS;AAGtC,cAAY,YAAY;AACtB,QAAI;AACF,YAAM,mBAAmB,IAAI,SAAS;AAAA,IACxC,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,+BAA+B,GAAG;AAAA,CAAI;AAAA,IAC7D;AAAA,EACF,GAAG,gBAAgB;AAGnB,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AAGD,uBAAqB,QAAQ,EAAE;AAC/B,uBAAqB,QAAQ,EAAE;AAC/B,qBAAmB,QAAQ,EAAE;AAG7B,qBAAmB,QAAQ,EAAE;AAC7B,qBAAmB,QAAQ,EAAE;AAG7B,uBAAqB,QAAQ,EAAE;AAG/B,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAChC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,uBAAuB,GAAG;AAAA,CAAI;AACnD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["z","z","z","z","z","z"]}
{"version":3,"sources":["../src/index.ts","../src/tools/usage-summary.ts","../src/tools/project-usage.ts","../src/tools/model-stats.ts","../src/tools/agent-usage.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createConnection, getDefaultDbPath } from \"./db/connection.js\";\nimport { runMigrations } from \"./db/migrate.js\";\nimport { seedPricing } from \"./db/pricing.js\";\nimport { scanForNewSessions } from \"./sync/scanner.js\";\nimport { classifyAllSessions } from \"./classifier/categorize-session.js\";\nimport { registerUsageSummary } from \"./tools/usage-summary.js\";\nimport { registerProjectUsage } from \"./tools/project-usage.js\";\nimport { registerModelStats } from \"./tools/model-stats.js\";\nimport { registerAgentUsage } from \"./tools/agent-usage.js\";\nimport { registerSkillUsage } from \"./tools/skill-usage.js\";\nimport { registerCostOptimize } from \"./tools/cost-optimize.js\";\n\nconst SCAN_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n\nasync function main() {\n const dbPath = getDefaultDbPath();\n const claudeDir = process.env.CLAUDE_DATA_DIR ||\n `${process.env.HOME || process.env.USERPROFILE}/.claude`;\n\n const db = createConnection(dbPath);\n runMigrations(db);\n seedPricing(db);\n\n // Retroactively classify existing sessions\n classifyAllSessions(db);\n\n // Initial scan\n await scanForNewSessions(db, claudeDir);\n\n // Periodic scan\n setInterval(async () => {\n try {\n await scanForNewSessions(db, claudeDir);\n } catch (err) {\n process.stderr.write(`[codeledger] Scanner error: ${err}\\n`);\n }\n }, SCAN_INTERVAL_MS);\n\n // Create MCP server\n const server = new McpServer({\n name: \"codeledger\",\n version: \"0.2.0\",\n });\n\n // Register Phase A tools\n registerUsageSummary(server, db);\n registerProjectUsage(server, db);\n registerModelStats(server, db);\n\n // Register Phase B tools\n registerAgentUsage(server, db);\n registerSkillUsage(server, db);\n\n // Register Phase C tools\n registerCostOptimize(server, db);\n\n // Connect via stdio\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n process.stderr.write(`[codeledger] Fatal: ${err}\\n`);\n process.exit(1);\n});\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { periodToStart } from \"../utils/period.js\";\n\ninterface UsageSummary {\n totalCostUsd: number;\n totalInputTokens: number;\n totalOutputTokens: number;\n sessionCount: number;\n topProject: string | null;\n modelDistribution: { model: string; cost: number; pct: number }[];\n overheadCostUsd: number;\n}\n\nexport function queryUsageSummary(\n db: Database.Database,\n period: string,\n project?: string\n): UsageSummary {\n const start = periodToStart(period);\n\n let sessionFilter = \"WHERE s.started_at >= ?\";\n const params: (string | number)[] = [start];\n\n if (project) {\n sessionFilter += \" AND p.display_name = ?\";\n params.push(project);\n }\n\n const totals = db\n .prepare(\n `\n SELECT\n COALESCE(SUM(s.total_cost_usd), 0) as cost,\n COALESCE(SUM(s.total_input_tokens), 0) as input_tok,\n COALESCE(SUM(s.total_output_tokens), 0) as output_tok,\n COUNT(*) as session_count\n FROM sessions s\n LEFT JOIN projects p ON s.project_id = p.id\n ${sessionFilter}\n `\n )\n .get(...params) as {\n cost: number;\n input_tok: number;\n output_tok: number;\n session_count: number;\n } | undefined;\n\n const topProj = db\n .prepare(\n `\n SELECT p.display_name, SUM(s.total_cost_usd) as cost\n FROM sessions s\n JOIN projects p ON s.project_id = p.id\n ${sessionFilter}\n GROUP BY p.id ORDER BY cost DESC LIMIT 1\n `\n )\n .get(...params) as { display_name: string; cost: number } | undefined;\n\n const models = db\n .prepare(\n `\n SELECT s.primary_model as model, SUM(s.total_cost_usd) as cost\n FROM sessions s\n LEFT JOIN projects p ON s.project_id = p.id\n ${sessionFilter}\n GROUP BY s.primary_model ORDER BY cost DESC\n `\n )\n .all(...params) as { model: string | null; cost: number }[];\n\n let agentFilter = \"WHERE a.started_at >= ?\";\n const agentParams: (string | number)[] = [start];\n if (project) {\n agentFilter += \" AND p.display_name = ?\";\n agentParams.push(project);\n }\n\n const overheadRow = db\n .prepare(\n `\n SELECT COALESCE(SUM(a.total_cost_usd), 0) as overhead_cost\n FROM agents a\n JOIN sessions s ON a.session_id = s.id\n LEFT JOIN projects p ON s.project_id = p.id\n ${agentFilter}\n AND a.source_category = 'overhead'\n `\n )\n .get(...agentParams) as { overhead_cost: number } | undefined;\n\n const totalCost = totals?.cost ?? 0;\n const modelDistribution = models.map((m) => ({\n model: m.model ?? \"unknown\",\n cost: m.cost,\n pct: totalCost > 0 ? Math.round((m.cost / totalCost) * 100) : 0,\n }));\n\n return {\n totalCostUsd: totalCost,\n totalInputTokens: totals?.input_tok ?? 0,\n totalOutputTokens: totals?.output_tok ?? 0,\n sessionCount: totals?.session_count ?? 0,\n topProject: topProj?.display_name ?? null,\n modelDistribution,\n overheadCostUsd: overheadRow?.overhead_cost ?? 0,\n };\n}\n\nexport function registerUsageSummary(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"usage_summary\",\n \"Get a smart summary of Claude Code usage and costs\",\n {\n period: z\n .enum([\"today\", \"week\", \"month\", \"all\"])\n .default(\"today\"),\n project: z.string().optional().describe(\"Filter by project name\"),\n },\n async ({ period, project }) => {\n const s = queryUsageSummary(db, period, project);\n\n const lines = [\n `## Usage Summary (${period})`,\n `**Total Cost:** $${s.totalCostUsd.toFixed(2)}`,\n `**Sessions:** ${s.sessionCount}`,\n `**Tokens:** ${s.totalInputTokens.toLocaleString()} input, ${s.totalOutputTokens.toLocaleString()} output`,\n s.topProject ? `**Top Project:** ${s.topProject}` : \"\",\n s.overheadCostUsd > 0 ? `**Background overhead:** $${s.overheadCostUsd.toFixed(2)} (plugin observers, system agents)` : \"\",\n \"\",\n \"**Model Distribution:**\",\n ...s.modelDistribution.map(\n (m) => `- ${m.model}: $${m.cost.toFixed(2)} (${m.pct}%)`\n ),\n ];\n\n return {\n content: [{ type: \"text\" as const, text: lines.filter(Boolean).join(\"\\n\") }],\n };\n }\n );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { periodToStart } from \"../utils/period.js\";\n\nexport function queryProjectUsage(\n db: Database.Database,\n period: string,\n sortBy: string,\n limit: number\n): any[] {\n const start = periodToStart(period);\n\n const orderCol =\n sortBy === \"tokens\"\n ? \"total_tokens\"\n : sortBy === \"sessions\"\n ? \"session_count\"\n : \"total_cost\";\n\n return db\n .prepare(\n `\n SELECT\n p.display_name as project,\n SUM(s.total_input_tokens + s.total_output_tokens) as total_tokens,\n SUM(s.total_cost_usd) as total_cost,\n COUNT(*) as session_count,\n MAX(s.started_at) as last_active\n FROM sessions s\n JOIN projects p ON s.project_id = p.id\n WHERE s.started_at >= ?\n GROUP BY p.id\n ORDER BY ${orderCol} DESC\n LIMIT ?\n `\n )\n .all(start, limit) as any[];\n}\n\nexport function registerProjectUsage(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"project_usage\",\n \"Get token usage and costs broken down by project\",\n {\n period: z\n .enum([\"today\", \"week\", \"month\", \"all\"])\n .default(\"week\"),\n sort_by: z\n .enum([\"cost\", \"tokens\", \"sessions\"])\n .default(\"cost\"),\n limit: z.number().default(10),\n },\n async ({ period, sort_by, limit }) => {\n const projects = queryProjectUsage(db, period, sort_by, limit);\n const lines = [\n `## Project Usage (${period}, top ${limit} by ${sort_by})`,\n \"\",\n \"| Project | Cost | Tokens | Sessions | Last Active |\",\n \"|---------|------|--------|----------|-------------|\",\n ...projects.map(\n (p: any) =>\n `| ${p.project} | $${p.total_cost.toFixed(2)} | ${Number(p.total_tokens).toLocaleString()} | ${p.session_count} | ${p.last_active?.split(\"T\")[0] ?? \"\\u2014\"} |`\n ),\n ];\n if (projects.length === 0) lines.push(\"No project data for this period.\");\n return { content: [{ type: \"text\" as const, text: lines.join(\"\\n\") }] };\n }\n );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { lookupPricing } from \"../db/pricing.js\";\nimport { periodToStart } from \"../utils/period.js\";\n\nexport interface ModelStatsRow {\n model: string;\n totalCost: number;\n totalInput: number;\n totalOutput: number;\n messageCount: number;\n pct: number;\n hypotheticalSonnetCost: number;\n potentialSavings: number;\n}\n\nexport function queryModelStats(\n db: Database.Database,\n period: string\n): ModelStatsRow[] {\n const start = periodToStart(period);\n\n const rows = db\n .prepare(\n `\n SELECT\n model,\n SUM(input_tokens) as total_input,\n SUM(output_tokens) as total_output,\n SUM(cost_usd) as total_cost,\n COUNT(*) as message_count\n FROM token_usage\n WHERE timestamp >= ?\n GROUP BY model\n ORDER BY total_cost DESC\n `\n )\n .all(start) as Array<{\n model: string;\n total_input: number;\n total_output: number;\n total_cost: number;\n message_count: number;\n }>;\n\n const totalCost = rows.reduce((sum, r) => sum + r.total_cost, 0);\n\n const sonnetPricing = lookupPricing(db, \"claude-sonnet-4-5\");\n\n return rows.map((r) => {\n let hypotheticalCost = r.total_cost;\n if (\n sonnetPricing &&\n !r.model.includes(\"sonnet\") &&\n !r.model.includes(\"haiku\")\n ) {\n hypotheticalCost =\n (r.total_input * sonnetPricing.input_per_mtok) / 1_000_000 +\n (r.total_output * sonnetPricing.output_per_mtok) / 1_000_000;\n }\n return {\n model: r.model,\n totalCost: r.total_cost,\n totalInput: r.total_input,\n totalOutput: r.total_output,\n messageCount: r.message_count,\n pct: totalCost > 0 ? Math.round((r.total_cost / totalCost) * 100) : 0,\n hypotheticalSonnetCost: hypotheticalCost,\n potentialSavings: Math.max(0, r.total_cost - hypotheticalCost),\n };\n });\n}\n\nexport function registerModelStats(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"model_stats\",\n \"Analyze which models are being used and potential cost savings\",\n {\n period: z.enum([\"today\", \"week\", \"month\", \"all\"]).default(\"month\"),\n },\n async ({ period }) => {\n const stats = queryModelStats(db, period);\n const totalSavings = stats.reduce((s, m) => s + m.potentialSavings, 0);\n\n const lines = [\n `## Model Usage (${period})`,\n \"\",\n \"| Model | Cost | % | Messages | If Sonnet | Savings |\",\n \"|-------|------|---|----------|-----------|---------|\",\n ...stats.map(\n (m) =>\n `| ${m.model} | $${m.totalCost.toFixed(2)} | ${m.pct}% | ${m.messageCount} | $${m.hypotheticalSonnetCost.toFixed(2)} | $${m.potentialSavings.toFixed(2)} |`\n ),\n \"\",\n totalSavings > 0\n ? `**Potential savings if non-Opus tasks used Sonnet: $${totalSavings.toFixed(2)}**`\n : \"Model usage looks optimized.\",\n ];\n return { content: [{ type: \"text\" as const, text: lines.join(\"\\n\") }] };\n }\n );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type Database from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { periodToStart } from \"../utils/period.js\";\n\ninterface AgentUsageRow {\n agent_id: string;\n session_id: string;\n agent_type: string | null;\n description: string | null;\n model: string | null;\n total_input_tokens: number;\n total_output_tokens: number;\n total_cache_create_tokens: number;\n total_cache_read_tokens: number;\n total_cost_usd: number;\n started_at: string | null;\n ended_at: string | null;\n message_count: number;\n source_category: string;\n project: string;\n}\n\nexport function queryAgentUsage(\n db: Database.Database,\n period: string,\n filters?: { sessionId?: string; project?: string; sourceCategory?: string }\n): AgentUsageRow[] {\n const start = periodToStart(period);\n\n let whereClause = \"WHERE a.started_at >= ?\";\n const params: (string | number)[] = [start];\n\n if (filters?.sessionId) {\n whereClause += \" AND a.session_id = ?\";\n params.push(filters.sessionId);\n }\n\n if (filters?.project) {\n whereClause += \" AND p.display_name = ?\";\n params.push(filters.project);\n }\n\n if (filters?.sourceCategory) {\n whereClause += \" AND a.source_category = ?\";\n params.push(filters.sourceCategory);\n }\n\n return db\n .prepare(\n `\n SELECT\n a.id as agent_id,\n a.session_id,\n a.agent_type,\n a.description,\n a.model,\n a.total_input_tokens,\n a.total_output_tokens,\n a.total_cache_create_tokens,\n a.total_cache_read_tokens,\n a.total_cost_usd,\n a.started_at,\n a.ended_at,\n a.message_count,\n a.source_category,\n p.display_name as project\n FROM agents a\n JOIN sessions s ON a.session_id = s.id\n JOIN projects p ON s.project_id = p.id\n ${whereClause}\n ORDER BY a.total_cost_usd DESC\n `\n )\n .all(...params) as AgentUsageRow[];\n}\n\nexport function registerAgentUsage(\n server: McpServer,\n db: Database.Database\n): void {\n server.tool(\n \"agent_usage\",\n \"Get token usage broken down by subagent\",\n {\n session_id: z.string().optional().describe(\"Filter by session\"),\n project: z.string().optional().describe(\"Filter by project\"),\n period: z\n .enum([\"today\", \"week\", \"month\", \"all\"])\n .default(\"week\"),\n source_category: z\n .enum([\"user\", \"overhead\", \"all\"])\n .default(\"all\")\n .describe(\"Filter: 'user' for coding work, 'overhead' for background agents\"),\n },\n async ({ session_id, project, period, source_category }) => {\n const agents = queryAgentUsage(db, period, {\n sessionId: session_id,\n project,\n sourceCategory: source_category === \"all\" ? undefined : source_category,\n });\n\n if (agents.length === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `No agent usage data found for period: ${period}`,\n },\n ],\n };\n }\n\n const totalCost = agents.reduce((sum, a) => sum + a.total_cost_usd, 0);\n const userCost = agents.filter(a => a.source_category === \"user\").reduce((s, a) => s + a.total_cost_usd, 0);\n const overheadCost = agents.filter(a => a.source_category === \"overhead\").reduce((s, a) => s + a.total_cost_usd, 0);\n\n const lines = [\n `## Agent Usage (${period})`,\n `**Total agents:** ${agents.length} | **Total cost:** $${totalCost.toFixed(2)}`,\n `**Your coding agents:** $${userCost.toFixed(2)} | **Background overhead:** $${overheadCost.toFixed(2)}`,\n \"\",\n \"| Agent | Type | Model | Tokens (in/out) | Cost | Messages | Session | Project |\",\n \"|-------|------|-------|-----------------|------|----------|---------|---------|\",\n ...agents.map((a) => {\n const tokens = `${Number(a.total_input_tokens).toLocaleString()}/${Number(a.total_output_tokens).toLocaleString()}`;\n return `| ${a.agent_id} | ${a.agent_type ?? \"\\u2014\"} | ${a.model ?? \"\\u2014\"} | ${tokens} | $${a.total_cost_usd.toFixed(2)} | ${a.message_count} | ${a.session_id.slice(0, 8)}... | ${a.project} |`;\n }),\n ];\n\n return {\n content: [{ type: \"text\" as const, text: lines.join(\"\\n\") }],\n };\n }\n );\n}\n"],"mappings":"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","names":["z","z","z","z","z","z"]}

@@ -27,2 +27,13 @@ {

],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/hooks/on-stop.js",
"timeout": 5
}
]
}
],
"SessionEnd": [

@@ -29,0 +40,0 @@ {

{
"name": "codeledger",
"version": "0.1.0",
"version": "0.2.0",
"description": "Token-level cost intelligence for Claude Code — per-project, per-agent, per-skill usage tracking with conversational MCP querying and local dashboard",

@@ -5,0 +5,0 @@ "type": "module",

@@ -39,14 +39,29 @@ # CodeLedger

### Install as Claude Code plugin
### Install from npm
```bash
# Add the MCP server
claude mcp add codeledger-server node -- $(npm root -g)/codeledger/dist/index.js
npm install -g codeledger
```
# Or install from source
git clone https://github.com/bhvbhushan/codeledger.git
cd codeledger && npm install && npm run build
claude mcp add codeledger-server node -- $(pwd)/dist/index.js
### Run Claude Code with the plugin
**macOS / Linux:**
```bash
claude --plugin-dir $(npm root -g)/codeledger
```
**Windows (PowerShell):**
```powershell
claude --plugin-dir "$(npm root -g)\codeledger"
```
**Windows (CMD):**
```cmd
for /f "delims=" %i in ('npm root -g') do claude --plugin-dir "%i\codeledger"
```
This loads **everything** — 6 MCP tools, 4 hooks (real-time tracking), and slash commands.
> **Plugin marketplace:** CodeLedger has been submitted to the official Claude Code plugin directory. Once approved, installation will be simply `/plugin install codeledger`.
### Start the dashboard

@@ -60,2 +75,10 @@

### Install from source (development)
```bash
git clone https://github.com/bhvbhushan/codeledger.git
cd codeledger && npm install && npm run build
claude --plugin-dir .
```
## Features

@@ -191,4 +214,14 @@

## Contributing
Contributions are welcome! Please read the [Contributing Guidelines](./CONTRIBUTING.md) before submitting a PR.
- **Bug reports** and **feature requests** — use [Issue Templates](https://github.com/bhvbhushan/codeledger/issues/new/choose)
- **Questions** — use [GitHub Discussions](https://github.com/bhvbhushan/codeledger/discussions)
- **Security vulnerabilities** — see [Security Policy](./SECURITY.md)
All contributors are expected to follow the [Code of Conduct](./CODE_OF_CONDUCT.md).
## License
[MIT](./LICENSE)
<claude-mem-context>
# Recent Activity
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
*No recent activity*
</claude-mem-context>
<claude-mem-context>
# Recent Activity
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
*No recent activity*
</claude-mem-context>
<claude-mem-context>
# Recent Activity
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
*No recent activity*
</claude-mem-context>