UNPKG

claude-usage-tracker

Version:

Advanced analytics for Claude Code usage with cost optimization, conversation length analysis, and rate limit tracking

883 lines 42.6 kB
#!/usr/bin/env node import chalk from "chalk"; import { program } from "commander"; import { aggregateDailyUsage, getCurrentWeekUsage, getEfficiencyInsights, getRateLimitInfo, } from "./analyzer.js"; import { ConversationLengthAnalyzer } from "./conversation-length-analytics.js"; import { loadUsageData } from "./data-loader.js"; import { ExportManager } from "./export-manager.js"; import { formatDailyTable, formatEfficiencyInsights, formatHeader, formatRateLimitStatus, formatWeeklySummary, } from "./formatters.js"; import { ModelAdvisor } from "./model-advisor.js"; import { OptimizationAnalyzer } from "./optimization-analytics.js"; import { PatternAnalyzer } from "./pattern-analysis.js"; import { PredictiveAnalyzer } from "./predictive-analytics.js"; import { QueryEngine } from "./query-engine.js"; import { ResearchAnalyzer } from "./research-analytics.js"; import { UsageWatcher } from "./watch-monitor.js"; function handleError(error, isJsonMode = false) { if (isJsonMode) { console.log(JSON.stringify({ error: "Command failed", message: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString(), }, null, 2)); } else { console.error(chalk.red("❌ Error:")); if (error instanceof Error) { console.error(chalk.gray(error.message)); if (error.stack && process.env.NODE_ENV !== "production") { console.error(chalk.gray(error.stack)); } } else { console.error(chalk.gray(String(error))); } } process.exit(1); } program .name("claude-usage") .description("Track and analyze Claude Code usage with rate limit awareness") .version("1.0.0") .option("-c, --config <path>", "Path to configuration file (YAML format)"); program .command("status") .description("Show current week usage and rate limit status") .option("-p, --plan <plan>", "Your Claude plan (Pro, $100 Max, $200 Max)", "Pro") .option("-j, --json", "Output as JSON instead of formatted text") .action(async (options) => { try { const plan = options.plan; if (!["Pro", "$100 Max", "$200 Max"].includes(plan)) { console.error(chalk.red("Invalid plan. Must be one of: Pro, $100 Max, $200 Max")); process.exit(1); } if (!options.json) { console.log(chalk.blue("Loading usage data...")); } const entries = await loadUsageData(); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found", message: "Make sure Claude Code has been used and data is available.", }, null, 2)); } else { console.log(chalk.yellow("No usage data found. Make sure Claude Code has been used and data is available.")); } return; } const weeklyUsage = getCurrentWeekUsage(entries); const rateLimitInfo = getRateLimitInfo(weeklyUsage, plan); if (options.json) { const jsonOutput = { plan, weeklyUsage, rateLimitInfo, warnings: [], }; // Add warnings if (rateLimitInfo.percentUsed.sonnet4.max > 80 || rateLimitInfo.percentUsed.opus4.max > 80) { jsonOutput.warnings.push({ level: "warning", message: "You are approaching your weekly rate limits!", }); } else if (rateLimitInfo.percentUsed.sonnet4.max > 50 || rateLimitInfo.percentUsed.opus4.max > 50) { jsonOutput.warnings.push({ level: "notice", message: "You have used over 50% of your weekly limits.", }); } console.log(JSON.stringify(jsonOutput, null, 2)); } else { console.log(formatHeader(`Claude Code Usage Status (${plan} Plan)`)); console.log(formatWeeklySummary(weeklyUsage)); console.log(formatHeader("Rate Limit Status")); console.log(formatRateLimitStatus(rateLimitInfo)); // Warnings if (rateLimitInfo.percentUsed.sonnet4.max > 80 || rateLimitInfo.percentUsed.opus4.max > 80) { console.log(chalk.red.bold("\\n⚠️ WARNING: You are approaching your weekly rate limits!")); } else if (rateLimitInfo.percentUsed.sonnet4.max > 50 || rateLimitInfo.percentUsed.opus4.max > 50) { console.log(chalk.yellow.bold("\\n⚡ NOTICE: You have used over 50% of your weekly limits.")); } } } catch (error) { handleError(error, options.json); } }); program .command("daily") .description("Show daily usage breakdown") .option("-d, --days <days>", "Number of days to show", "7") .option("-j, --json", "Output as JSON instead of formatted text") .action(async (options) => { try { if (!options.json) { console.log(chalk.blue("Loading usage data...")); } const entries = await loadUsageData(); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found", }, null, 2)); } else { console.log(chalk.yellow("No usage data found.")); } return; } const dailyUsage = aggregateDailyUsage(entries); const days = parseInt(options.days); // Limit to requested number of days const recentDays = Array.from(dailyUsage.keys()) .sort() .reverse() .slice(0, days); const filteredUsage = new Map(); for (const day of recentDays) { const usage = dailyUsage.get(day); if (usage) { filteredUsage.set(day, usage); } } if (options.json) { const jsonOutput = { days: parseInt(options.days), dailyUsage: Object.fromEntries(filteredUsage), }; console.log(JSON.stringify(jsonOutput, null, 2)); } else { console.log(formatHeader(`Daily Usage (Last ${days} days)`)); console.log(formatDailyTable(filteredUsage)); } } catch (error) { console.error(chalk.red("Error loading usage data:"), error); process.exit(1); } }); program .command("week") .description("Show current week summary") .action(async () => { try { console.log(chalk.blue("Loading usage data...")); const entries = await loadUsageData(); if (entries.length === 0) { console.log(chalk.yellow("No usage data found.")); return; } const weeklyUsage = getCurrentWeekUsage(entries); console.log(formatHeader("Current Week Summary")); console.log(formatWeeklySummary(weeklyUsage)); } catch (error) { console.error(chalk.red("Error loading usage data:"), error); process.exit(1); } }); program .command("check-limits") .description("Check rate limit status for all plans") .action(async () => { try { console.log(chalk.blue("Loading usage data...")); const entries = await loadUsageData(); if (entries.length === 0) { console.log(chalk.yellow("No usage data found.")); return; } const weeklyUsage = getCurrentWeekUsage(entries); const plans = ["Pro", "$100 Max", "$200 Max"]; for (const plan of plans) { const rateLimitInfo = getRateLimitInfo(weeklyUsage, plan); console.log(formatHeader(`Rate Limits - ${plan} Plan`)); console.log(formatRateLimitStatus(rateLimitInfo)); } } catch (error) { console.error(chalk.red("Error loading usage data:"), error); process.exit(1); } }); program .command("insights") .description("Show detailed efficiency insights and optimization recommendations") .option("-d, --days <days>", "Number of days to analyze", "30") .action(async (options) => { try { console.log(chalk.dim("Loading usage data...")); const entries = await loadUsageData(); if (entries.length === 0) { console.log(chalk.yellow("No usage data found. Make sure Claude Code has been used and data is available.")); return; } // Filter to specified number of days const days = parseInt(options.days); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); const filteredEntries = entries.filter((entry) => new Date(entry.timestamp) >= cutoffDate); const insights = getEfficiencyInsights(filteredEntries); console.log(formatEfficiencyInsights(insights)); } catch (error) { console.error(chalk.red("Error loading usage data:"), error); process.exit(1); } }); program .command("recommend") .description("Get model recommendation for a specific task or prompt") .argument("[prompt]", "The task or prompt to analyze (optional - will prompt interactively)") .action(async (promptArg) => { const advisor = new ModelAdvisor(); let prompt = promptArg; if (!prompt) { // Interactive mode const readline = await import("node:readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); console.log(chalk.blue.bold("🤖 Model Advisor - Interactive Mode\n")); console.log(chalk.gray("Describe your task or paste your prompt below:")); console.log(chalk.gray("(Press Ctrl+C to exit)\n")); prompt = await new Promise((resolve) => { rl.question(chalk.cyan("Your task: "), (answer) => { rl.close(); resolve(answer); }); }); if (!prompt.trim()) { console.log(chalk.yellow("No prompt provided. Exiting.")); return; } } console.log(); // Add spacing const classification = advisor.classifyTask(prompt); const recommendation = advisor.getModelRecommendation(classification); console.log(advisor.formatRecommendation(classification, recommendation)); // Show cost savings potential if (recommendation.costSavings && recommendation.costSavings > 0) { console.log(chalk.green.bold(`💡 Daily Savings Potential:`)); console.log(chalk.green(`If you have 10 similar conversations: $${(recommendation.costSavings * 10).toFixed(2)}`)); console.log(chalk.green(`Monthly potential: $${(recommendation.costSavings * 10 * 30).toFixed(0)}\n`)); } }); program .command("watch") .description("Live monitoring of Claude usage with real-time cost tracking") .action(async () => { const watcher = new UsageWatcher(); // Handle graceful shutdown process.on("SIGINT", () => { watcher.stopWatching(); console.log(chalk.yellow("\n👋 Monitoring stopped. Goodbye!")); process.exit(0); }); process.on("SIGTERM", () => { watcher.stopWatching(); process.exit(0); }); try { await watcher.startWatching((stats, recentConversations) => { const display = watcher.formatLiveDisplay(stats, recentConversations); console.log(display); }); // Keep the process running await new Promise(() => { }); // Infinite promise } catch (error) { console.error(chalk.red("Error starting live monitor:"), error); process.exit(1); } }); program .command("predict") .description("Predictive analytics: budget burn, anomalies, model suggestions") .option("--json", "Output as JSON") .action(async (options) => { try { const entries = await loadUsageData(); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found" }, null, 2)); } else { console.log(chalk.yellow("No usage data found.")); } return; } const analyzer = new PredictiveAnalyzer(); const budgetPrediction = analyzer.predictBudgetBurn(entries); const anomalies = analyzer.detectUsageAnomalies(entries); const modelSuggestions = analyzer.generateModelSuggestions(entries); if (options.json) { console.log(JSON.stringify({ budgetPrediction, anomalies, modelSuggestions: modelSuggestions.slice(0, 5), }, null, 2)); } else { console.log(formatHeader("🔮 Predictive Analytics")); // Budget prediction console.log(chalk.blue.bold("💰 Budget Prediction")); console.log(`Current spend: ${chalk.green(`$${budgetPrediction.currentSpend.toFixed(2)}`)}`); console.log(`Projected monthly: ${chalk.yellow(`$${budgetPrediction.projectedMonthlySpend.toFixed(2)}`)}`); console.log(`Days until budget exhausted: ${budgetPrediction.daysUntilBudgetExhausted > 0 ? chalk.red(budgetPrediction.daysUntilBudgetExhausted) : chalk.green("N/A")}`); console.log(`Trend: ${budgetPrediction.trendDirection === "increasing" ? chalk.red("📈") : budgetPrediction.trendDirection === "decreasing" ? chalk.green("📉") : "📊"} ${budgetPrediction.trendDirection}`); console.log(`Confidence: ${chalk.cyan(`${(budgetPrediction.confidenceLevel * 100).toFixed(0)}%`)}\n`); // Recommendations if (budgetPrediction.recommendations.length > 0) { console.log(chalk.yellow.bold("💡 Recommendations:")); budgetPrediction.recommendations.forEach((rec) => console.log(` ${rec}`)); console.log(); } // Anomalies if (anomalies.length > 0) { console.log(chalk.red.bold("⚠️ Usage Anomalies")); anomalies.forEach((anomaly) => { const severityIcon = anomaly.severity === "high" ? "🔴" : anomaly.severity === "medium" ? "🟡" : "🟢"; console.log(`${severityIcon} ${anomaly.description}`); }); console.log(); } // Model suggestions if (modelSuggestions.length > 0) { console.log(chalk.blue.bold("🎯 Model Optimization Suggestions")); modelSuggestions.slice(0, 3).forEach((suggestion) => { const savingsText = suggestion.potentialSavings > 0 ? chalk.green(`+$${suggestion.potentialSavings.toFixed(3)}`) : chalk.red(`$${Math.abs(suggestion.potentialSavings).toFixed(3)}`); console.log(`${suggestion.conversationContext}: ${suggestion.reasoning} (${savingsText})`); }); } } } catch (error) { handleError(error, options.json); } }); program .command("optimize") .description("Cost optimization analytics: clustering, batch processing, model switching") .option("--json", "Output as JSON") .action(async (options) => { try { const entries = await loadUsageData(); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found" }, null, 2)); } else { console.log(chalk.yellow("No usage data found.")); } return; } const analyzer = new OptimizationAnalyzer(); const summary = analyzer.generateOptimizationSummary(entries); const clusters = analyzer.clusterConversations(entries); const batchOpportunities = analyzer.identifyBatchProcessingOpportunities(entries); if (options.json) { console.log(JSON.stringify({ summary, clusters: clusters.slice(0, 5), batchOpportunities: batchOpportunities.opportunities.slice(0, 5), }, null, 2)); } else { console.log(formatHeader("⚡ Optimization Analytics")); // Summary console.log(chalk.green.bold(`💰 Total Potential Savings: $${summary.totalPotentialSavings.toFixed(2)}`)); console.log(` • Batch Processing: ${chalk.cyan(`$${summary.batchProcessingSavings.toFixed(2)}`)}`); console.log(` • Model Switching: ${chalk.cyan(`$${summary.modelSwitchingSavings.toFixed(2)}`)}`); console.log(` • Efficiency Improvements: ${chalk.cyan(`$${summary.efficiencyImprovements.toFixed(2)}`)}\n`); // Top recommendations console.log(chalk.yellow.bold("🎯 Top Recommendations")); summary.recommendations.forEach((rec, i) => { const effortIcon = rec.effort === "low" ? "🟢" : rec.effort === "medium" ? "🟡" : "🔴"; console.log(`${i + 1}. ${rec.description} (${chalk.green(`$${rec.savings.toFixed(2)}`)} ${effortIcon})`); }); console.log(); // Conversation clusters console.log(chalk.blue.bold("📊 Conversation Clusters")); clusters.slice(0, 3).forEach((cluster) => { console.log(`${cluster.type}: ${cluster.conversations.length} conversations, avg $${cluster.avgCost.toFixed(2)}, potential savings: $${cluster.optimizationPotential.toFixed(2)}`); if (cluster.recommendations.length > 0) { console.log(` 💡 ${cluster.recommendations[0]}`); } }); } } catch (error) { handleError(error, options.json); } }); program .command("patterns") .description("Usage pattern analysis: conversation patterns, learning curves, task switching") .option("--json", "Output as JSON") .action(async (options) => { try { const entries = await loadUsageData(); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found" }, null, 2)); } else { console.log(chalk.yellow("No usage data found.")); } return; } const analyzer = new PatternAnalyzer(); const lengthPatterns = analyzer.analyzeConversationLengthPatterns(entries); const completionAnalysis = analyzer.analyzeTimeToCompletion(entries); const switchingPatterns = analyzer.analyzeTaskSwitchingPatterns(entries); const learningCurve = analyzer.analyzeLearningCurve(entries); const usagePatterns = analyzer.identifyUsagePatterns(entries); if (options.json) { console.log(JSON.stringify({ lengthPatterns, completionAnalysis, switchingPatterns, learningCurve, usagePatterns, }, null, 2)); } else { console.log(formatHeader("📈 Pattern Analysis")); // Conversation length patterns console.log(chalk.blue.bold("💬 Conversation Length Patterns")); console.log(`Quick Questions: ${lengthPatterns.conversationTypes.quickQuestions.count} conversations (avg ${lengthPatterns.avgLengthByType.quickQuestions} messages)`); console.log(`Detailed Discussions: ${lengthPatterns.conversationTypes.detailedDiscussions.count} conversations (avg ${lengthPatterns.avgLengthByType.detailedDiscussions} messages)`); console.log(`Deep Dives: ${lengthPatterns.conversationTypes.deepDives.count} conversations (avg ${lengthPatterns.avgLengthByType.deepDives} messages)`); console.log(`Most Efficient: ${chalk.green(lengthPatterns.efficiencyInsights.mostEfficientType)}`); if (lengthPatterns.recommendations?.length > 0) { console.log(chalk.yellow("💡 Recommendations:")); lengthPatterns.recommendations.forEach((rec) => { console.log(` • ${rec}`); }); } console.log(); // Learning curve console.log(chalk.green.bold("📚 Learning Progress")); console.log(`Skill Area: ${learningCurve.skillArea}`); console.log(`Learning Phase: ${chalk.cyan(learningCurve.learningPhase)}`); console.log(`Improvement Rate: ${learningCurve.improvementRate > 0 ? chalk.green(`+${learningCurve.improvementRate.toFixed(1)}%/week`) : chalk.red(`${learningCurve.improvementRate.toFixed(1)}%/week`)}`); console.log(`Current Efficiency: ${chalk.white(learningCurve.currentEfficiency.toFixed(0))} tokens/$`); if (learningCurve.plateauDetected) { console.log(chalk.yellow("⚠️ Learning plateau detected")); } console.log(`Next Milestone: ${chalk.cyan(learningCurve.nextMilestone)}\n`); // Task switching console.log(chalk.yellow.bold("🔄 Task Switching")); console.log(`Switch Frequency: ${switchingPatterns.switchFrequency.toFixed(1)} switches/day`); console.log(`Avg Time Between Switches: ${switchingPatterns.avgTimeBetweenSwitches.toFixed(0)} minutes`); console.log(`Switching Cost: $${switchingPatterns.costOfSwitching.toFixed(2)}`); if (switchingPatterns.recommendations.length > 0) { console.log(`💡 ${switchingPatterns.recommendations[0]}`); } console.log(); // Usage patterns if (usagePatterns.length > 0) { console.log(chalk.magenta.bold("🎯 Usage Patterns")); usagePatterns.slice(0, 3).forEach((pattern) => { const strengthBar = "█".repeat(Math.floor(pattern.strength * 10)); console.log(`${pattern.description} (${strengthBar} ${(pattern.strength * 100).toFixed(0)}%)`); if (pattern.recommendation) { console.log(` 💡 ${pattern.recommendation}`); } }); } } } catch (error) { handleError(error, options.json); } }); program .command("research") .description("Advanced research analytics: conversation success, project ROI, correlations") .option("--json", "Output as JSON") .action(async (options) => { try { const entries = await loadUsageData(); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found" }, null, 2)); } else { console.log(chalk.yellow("No usage data found.")); } return; } const analyzer = new ResearchAnalyzer(); const insights = analyzer.generateAdvancedInsights(entries); if (options.json) { console.log(JSON.stringify({ conversationSuccess: insights.conversationSuccess.slice(0, 10), projectAnalysis: insights.projectAnalysis.slice(0, 5), timeSeriesData: insights.timeSeriesData.slice(-30), // Last 30 days cacheOptimization: insights.cacheOptimization, promptingPatterns: insights.promptingPatterns, correlationInsights: insights.correlationInsights, }, null, 2)); } else { console.log(formatHeader("🔬 Research Analytics")); // Top performing conversations console.log(chalk.blue.bold("🏆 Top Performing Conversations")); insights.conversationSuccess.slice(0, 5).forEach((conv, i) => { console.log(`${i + 1}. Success: ${(conv.successScore * 100).toFixed(1)}% | Efficiency: ${conv.efficiency.toFixed(0)} tokens/$ | ${conv.messageCount} msgs | ${conv.duration.toFixed(0)}min`); }); console.log(); // Project ROI analysis if (insights.projectAnalysis.length > 0) { console.log(chalk.green.bold("📊 Project ROI Analysis")); insights.projectAnalysis.slice(0, 3).forEach((project) => { console.log(`${project.projectPath}: ROI ${project.roi.toFixed(2)} | $${project.totalCost.toFixed(2)} | ${project.conversationCount} conversations | ${project.timeSpent.toFixed(1)}hrs`); if (project.topics.length > 0) { console.log(` Topics: ${project.topics.join(", ")}`); } }); console.log(); } // Cache optimization console.log(chalk.cyan.bold("💾 Cache Optimization")); console.log(`Cache hit rate: ${(insights.cacheOptimization.cacheHitRate * 100).toFixed(1)}%`); console.log(`Cache savings: $${insights.cacheOptimization.cacheSavings.toFixed(2)}`); if (insights.cacheOptimization.underutilizedConversations.length > 0) { console.log(`Underutilized conversations: ${insights.cacheOptimization.underutilizedConversations.length}`); const topMissed = insights.cacheOptimization.underutilizedConversations[0]; console.log(` Top opportunity: $${topMissed.missedCachingOpportunity.toFixed(2)} potential savings`); } insights.cacheOptimization.recommendations.forEach((rec) => console.log(` 💡 ${rec}`)); console.log(); // Prompting patterns console.log(chalk.magenta.bold("📝 Prompting Insights")); console.log(`Average prompt length: ${insights.promptingPatterns.avgPromptLength.toFixed(0)} tokens`); if (insights.promptingPatterns.effectivePromptPatterns.length > 0) { const best = insights.promptingPatterns.effectivePromptPatterns[0]; console.log(`Most effective pattern: ${best.pattern} prompts (${(best.successRate * 100).toFixed(1)}% success rate)`); } insights.promptingPatterns.optimalPromptingGuidelines .slice(0, 3) .forEach((guideline) => { console.log(` 💡 ${guideline}`); }); console.log(); // Correlation insights if (insights.correlationInsights.length > 0) { console.log(chalk.yellow.bold("🔗 Key Correlations")); insights.correlationInsights.forEach((insight) => { const strength = Math.abs(insight.correlation); const strengthText = strength > 0.5 ? "Strong" : strength > 0.3 ? "Moderate" : "Weak"; console.log(`${strengthText} correlation (${insight.correlation.toFixed(2)}): ${insight.insight}`); }); } } } catch (error) { handleError(error, options.json); } }); program .command("length") .description("Analyze conversation length patterns and optimization opportunities") .option("--json", "Output as JSON") .option("-d, --days <days>", "Number of days to analyze (default: all data)", "0") .action(async (options) => { try { const rawEntries = await loadUsageData(); if (rawEntries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found" }, null, 2)); } else { console.log(chalk.yellow("No usage data found.")); } return; } // Filter entries by date if days option is provided let entries = rawEntries; const days = parseInt(options.days); if (days > 0) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); entries = rawEntries.filter((entry) => new Date(entry.timestamp) >= cutoffDate); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: `No usage data found in the last ${days} days` }, null, 2)); } else { console.log(chalk.yellow(`No usage data found in the last ${days} days.`)); } return; } } const analyzer = new ConversationLengthAnalyzer(); analyzer.loadConversations(entries); const analysis = analyzer.analyzeConversationLengths(); if (options.json) { console.log(JSON.stringify(analysis, null, 2)); } else { console.log(formatHeader("📏 Conversation Length Analysis")); // Overview console.log(chalk.blue.bold("📊 Overview")); if (days > 0) { console.log(`Analysis period: Last ${chalk.yellow(days)} days`); } console.log(`Total conversations: ${chalk.green(analysis.totalConversations)}`); console.log(`Optimal range: ${chalk.cyan(`${analysis.overallOptimalRange.minMessages}-${analysis.overallOptimalRange.maxMessages} messages`)}`); console.log(`${analysis.overallOptimalRange.explanation}\n`); // Length distribution console.log(chalk.blue.bold("📈 Length Distribution")); const dist = analysis.lengthDistribution; console.log(`Quick (1-5 msgs): ${(dist.quick * 100).toFixed(1)}% (${Math.round(dist.quick * analysis.totalConversations)} conversations)`); console.log(`Medium (6-20 msgs): ${(dist.medium * 100).toFixed(1)}% (${Math.round(dist.medium * analysis.totalConversations)} conversations)`); console.log(`Deep (21-100 msgs): ${(dist.deep * 100).toFixed(1)}% (${Math.round(dist.deep * analysis.totalConversations)} conversations)`); console.log(`Marathon (100+ msgs): ${(dist.marathon * 100).toFixed(1)}% (${Math.round(dist.marathon * analysis.totalConversations)} conversations)\n`); // Project profiles if (analysis.projectProfiles.length > 0) { console.log(chalk.blue.bold("🏗️ Project Profiles")); analysis.projectProfiles.slice(0, 5).forEach((project) => { console.log(chalk.green.bold(`\n${project.project}:`)); console.log(` Conversations: ${project.totalConversations}`); console.log(` Avg length: ${project.avgMessageCount.toFixed(1)} messages`); console.log(` Optimal range: ${project.optimalRange.minMessages}-${project.optimalRange.maxMessages} messages`); console.log(` ${project.optimalRange.explanation}`); // Efficiency by length const eff = project.efficiencyByLength; console.log(` Efficiency by length:`); if (eff.quick.count > 0) console.log(` Quick: ${eff.quick.successRate.toFixed(1)}% success, $${eff.quick.avgCost.toFixed(4)} avg cost`); if (eff.medium.count > 0) console.log(` Medium: ${eff.medium.successRate.toFixed(1)}% success, $${eff.medium.avgCost.toFixed(4)} avg cost`); if (eff.deep.count > 0) console.log(` Deep: ${eff.deep.successRate.toFixed(1)}% success, $${eff.deep.avgCost.toFixed(4)} avg cost`); if (eff.marathon.count > 0) console.log(` Marathon: ${eff.marathon.successRate.toFixed(1)}% success, $${eff.marathon.avgCost.toFixed(4)} avg cost`); // Project recommendations if (project.recommendations.length > 0) { console.log(` ${chalk.yellow("💡 Recommendations:")}`); project.recommendations.forEach((rec) => console.log(` • ${rec}`)); } }); } // Cost analysis if (analysis.costAnalysis && analysis.costAnalysis.totalCost > 0) { console.log(chalk.blue.bold("\n💰 Cost Analysis")); console.log(`Total cost: ${chalk.green(`$${analysis.costAnalysis.totalCost.toFixed(4)}`)}`); console.log(`Average per conversation: ${chalk.cyan(`$${analysis.costAnalysis.avgCostPerConversation.toFixed(4)}`)}`); console.log(`Most cost-efficient: ${chalk.yellow(analysis.costAnalysis.mostCostEfficient)} conversations`); console.log(chalk.blue.bold("\n💸 Cost by Length Category")); const costByLength = analysis.costAnalysis.costByLength; if (costByLength.quick.totalCost > 0) { console.log(`Quick: $${costByLength.quick.totalCost.toFixed(4)} total, $${costByLength.quick.avgCost.toFixed(4)} avg, ${Math.round(costByLength.quick.costEfficiency)} tokens/$`); } if (costByLength.medium.totalCost > 0) { console.log(`Medium: $${costByLength.medium.totalCost.toFixed(4)} total, $${costByLength.medium.avgCost.toFixed(4)} avg, ${Math.round(costByLength.medium.costEfficiency)} tokens/$`); } if (costByLength.deep.totalCost > 0) { console.log(`Deep: $${costByLength.deep.totalCost.toFixed(4)} total, $${costByLength.deep.avgCost.toFixed(4)} avg, ${Math.round(costByLength.deep.costEfficiency)} tokens/$`); } if (costByLength.marathon.totalCost > 0) { console.log(`Marathon: $${costByLength.marathon.totalCost.toFixed(4)} total, $${costByLength.marathon.avgCost.toFixed(4)} avg, ${Math.round(costByLength.marathon.costEfficiency)} tokens/$`); } } // Overall insights if (analysis.insights.length > 0) { console.log(chalk.blue.bold("\n🔍 Key Insights")); analysis.insights.forEach((insight) => console.log(`• ${insight}`)); } // Recommendations if (analysis.recommendations.length > 0) { console.log(chalk.yellow.bold("\n💡 Recommendations")); analysis.recommendations.forEach((rec) => console.log(`• ${rec}`)); } } } catch (error) { handleError(error, options.json); } }); program .command("export") .description("Export usage data in various formats") .option("-f, --format <format>", "Export format: csv, json, summary", "csv") .option("-o, --output <file>", "Output filename (auto-generated if not specified)") .option("-s, --start <date>", "Start date (YYYY-MM-DD)") .option("-e, --end <date>", "End date (YYYY-MM-DD)") .option("-p, --project <name>", "Filter by project name") .option("-t, --template <type>", "Template: billing, efficiency, analytics, raw", "raw") .option("--json", "Output export summary as JSON") .action(async (options) => { try { const entries = await loadUsageData(); if (entries.length === 0) { if (options.json) { console.log(JSON.stringify({ error: "No usage data found" }, null, 2)); } else { console.log(chalk.yellow("No usage data found.")); } return; } const exportOptions = { format: options.format, output: options.output, startDate: options.start, endDate: options.end, project: options.project, template: options.template, }; const exportManager = new ExportManager(entries); const summary = exportManager.export(exportOptions); if (options.json) { console.log(JSON.stringify(summary, null, 2)); } else { console.log(formatHeader("📤 Export Complete")); console.log(`${chalk.green("✓")} File: ${summary.exportedAt.split("T")[0]}`); console.log(`${chalk.green("✓")} Exported: ${chalk.cyan(summary.totalEntries.toLocaleString())} entries`); console.log(`${chalk.green("✓")} Date range: ${chalk.yellow(summary.dateRange.start)} to ${chalk.yellow(summary.dateRange.end)}`); console.log(`${chalk.green("✓")} Total cost: ${chalk.green(`$${summary.totalCost.toFixed(4)}`)}`); console.log(`${chalk.green("✓")} Total tokens: ${chalk.cyan(summary.totalTokens.toLocaleString())}`); console.log(`${chalk.green("✓")} Projects: ${summary.projects.length} (${summary.projects.slice(0, 3).join(", ")}${summary.projects.length > 3 ? "..." : ""})`); console.log(`${chalk.green("✓")} File size: ${chalk.gray(summary.fileSize)}`); // Show some helpful next steps console.log(chalk.blue.bold("\n💡 Next Steps:")); if (options.format === "csv") { console.log("• Open in Excel/Google Sheets for analysis"); console.log("• Import into accounting software for billing"); } else if (options.format === "json") { console.log("• Use for programmatic analysis or data pipeline"); console.log("• Import into business intelligence tools"); } else { console.log("• Review summary for insights and planning"); console.log("• Share with stakeholders for budget discussions"); } } } catch (error) { handleError(error, options.json); } }); program .command("query") .description("Query usage data using SQL-like syntax") .argument("<query>", "SQL-like query string") .option("-f, --format <format>", "output format (json, table, csv)", "json") .option("--pretty", "pretty print JSON output", false) .option("--no-metadata", "exclude execution metadata") .option("--explain", "show query execution plan instead of results") .action(async (queryString, options) => { try { const allEntries = await loadUsageData(); if (allEntries.length === 0) { console.log("❌ No usage data found. Make sure Claude has been used and log files exist."); process.exit(1); } const queryEngine = new QueryEngine(allEntries); if (options.explain) { const result = await queryEngine.execute(queryString, true); if (options.format === "json") { console.log(JSON.stringify(result.explanation, null, options.pretty ? 2 : 0)); } else { console.log(chalk.cyan.bold("🔍 Query Execution Plan")); console.log(chalk.gray(`Query: ${queryString}`)); console.log(chalk.gray(`Dataset: ${allEntries.length.toLocaleString()} entries\n`)); // Display execution plan console.log(chalk.blue.bold("📋 Execution Steps:")); result.explanation.plan.forEach((step, index) => { const costIndicator = step.estimatedCost > 5 ? "🔴" : step.estimatedCost > 2 ? "🟡" : "🟢"; console.log(`${index + 1}. ${costIndicator} ${chalk.bold(step.step)}`); console.log(` ${step.description}`); console.log(` ${chalk.gray(`Cost: ${step.estimatedCost} | Rows: ${step.rowsProcessed.toLocaleString()}`)}`); console.log(); }); // Show optimization hints if (result.explanation.optimizationHints.length > 0) { console.log(chalk.yellow.bold("💡 Optimization Hints:")); result.explanation.optimizationHints.forEach((hint) => { console.log(` • ${hint}`); }); console.log(); } console.log(chalk.green(`✅ Total steps: ${result.explanation.totalExecutionSteps}`)); } return; } const result = await queryEngine.execute(queryString); if (options.format === "json") { const output = options.metadata ? result : result.data; console.log(JSON.stringify(output, null, options.pretty ? 2 : 0)); } else if (options.format === "table") { // Pretty table output for humans if (result.data.length === 0) { console.log("📊 No results found"); return; } console.log(`\n📊 Query Results (${result.data.length} rows, ${result.metadata.executionTime}ms)\n`); console.table(result.data); } else if (options.format === "csv") { // CSV output if (result.data.length === 0) { console.log(""); // Empty CSV return; } const headers = Object.keys(result.data[0]); console.log(headers.join(",")); for (const row of result.data) { const values = headers.map((header) => { const value = row[header]; return typeof value === "string" && value.includes(",") ? `"${value}"` : String(value); }); console.log(values.join(",")); } } // Show metadata if requested and not in JSON mode if (options.metadata && options.format !== "json") { console.log(`\n⚡ Execution: ${result.metadata.executionTime}ms | Rows: ${result.metadata.totalRows}`); } } catch (error) { handleError(error, options.json); } }); // Default command if (process.argv.length === 2) { program.parse(["node", "cli.js", "status"]); } else { program.parse(process.argv); } //# sourceMappingURL=cli.js.map