UNPKG

claude-analytics

Version:

Advanced Claude Code analytics with real-time token tracking, cost analysis, usage heatmaps, and productivity insights

179 lines (155 loc) 5.51 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const os = require('os'); const readline = require('readline'); // Parse JSONL files from ~/.claude/projects directories async function parseProjectJSONL(projectPath) { const results = []; try { const files = fs.readdirSync(projectPath).filter(f => f.endsWith('.jsonl')); for (const file of files) { const filePath = path.join(projectPath, file); const fileStream = fs.createReadStream(filePath); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); for await (const line of rl) { if (line.trim()) { try { const entry = JSON.parse(line); if (entry.type === 'assistant' && entry.message && entry.message.usage) { results.push({ timestamp: entry.timestamp, model: entry.message.model, costUSD: entry.costUSD || 0, durationMs: entry.durationMs || 0, usage: { input_tokens: entry.message.usage.input_tokens || 0, output_tokens: entry.message.usage.output_tokens || 0, cache_creation_input_tokens: entry.message.usage.cache_creation_input_tokens || 0, cache_read_input_tokens: entry.message.usage.cache_read_input_tokens || 0 }, sessionId: entry.sessionId, requestId: entry.requestId }); } } catch (e) { // Skip invalid JSON lines } } } } } catch (e) { console.error(`Error reading project ${projectPath}:`, e.message); } return results; } // Get all project stats from ~/.claude/projects async function getAllProjectStats() { const claudeProjectsDir = path.join(os.homedir(), '.claude', 'projects'); const allStats = []; if (!fs.existsSync(claudeProjectsDir)) { return allStats; } try { const projects = fs.readdirSync(claudeProjectsDir); for (const project of projects) { const projectPath = path.join(claudeProjectsDir, project); if (fs.statSync(projectPath).isDirectory()) { const projectStats = await parseProjectJSONL(projectPath); allStats.push({ projectName: project, stats: projectStats }); } } } catch (e) { console.error('Error reading projects directory:', e.message); } return allStats; } // Aggregate stats across all projects async function getAggregatedStats() { const allProjects = await getAllProjectStats(); let totalStats = { totalCost: 0, totalDuration: 0, totalRequests: 0, usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 }, byModel: {}, byProject: {} }; for (const project of allProjects) { let projectCost = 0; let projectDuration = 0; let projectUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 }; for (const stat of project.stats) { totalStats.totalCost += stat.costUSD; totalStats.totalDuration += stat.durationMs; totalStats.totalRequests++; projectCost += stat.costUSD; projectDuration += stat.durationMs; // Aggregate usage totalStats.usage.input_tokens += stat.usage.input_tokens; totalStats.usage.output_tokens += stat.usage.output_tokens; totalStats.usage.cache_creation_input_tokens += stat.usage.cache_creation_input_tokens; totalStats.usage.cache_read_input_tokens += stat.usage.cache_read_input_tokens; projectUsage.input_tokens += stat.usage.input_tokens; projectUsage.output_tokens += stat.usage.output_tokens; projectUsage.cache_creation_input_tokens += stat.usage.cache_creation_input_tokens; projectUsage.cache_read_input_tokens += stat.usage.cache_read_input_tokens; // Group by model if (!totalStats.byModel[stat.model]) { totalStats.byModel[stat.model] = { count: 0, cost: 0, duration: 0, usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 } }; } totalStats.byModel[stat.model].count++; totalStats.byModel[stat.model].cost += stat.costUSD; totalStats.byModel[stat.model].duration += stat.durationMs; totalStats.byModel[stat.model].usage.input_tokens += stat.usage.input_tokens; totalStats.byModel[stat.model].usage.output_tokens += stat.usage.output_tokens; totalStats.byModel[stat.model].usage.cache_creation_input_tokens += stat.usage.cache_creation_input_tokens; totalStats.byModel[stat.model].usage.cache_read_input_tokens += stat.usage.cache_read_input_tokens; } totalStats.byProject[project.projectName] = { cost: projectCost, duration: projectDuration, requests: project.stats.length, usage: projectUsage }; } return totalStats; } module.exports = { parseProjectJSONL, getAllProjectStats, getAggregatedStats }; // If run directly, display stats if (require.main === module) { (async () => { const stats = await getAggregatedStats(); console.log(JSON.stringify(stats, null, 2)); })(); }