claude-usage-tracker
Version:
Advanced analytics for Claude Code usage with cost optimization, conversation length analysis, and rate limit tracking
194 lines (192 loc) • 7.34 kB
JavaScript
import chalk from "chalk";
import Table from "cli-table3";
export function formatTokenCount(count) {
if (count >= 1_000_000) {
return `${(count / 1_000_000).toFixed(2)}M`;
}
else if (count >= 1_000) {
return `${(count / 1_000).toFixed(1)}K`;
}
return count.toString();
}
export function formatCost(cost) {
return `$${cost.toFixed(4)}`;
}
export function formatHours(hours) {
return hours.toFixed(1);
}
export function formatPercentage(percent) {
return `${percent.toFixed(1)}%`;
}
export function formatDailyTable(dailyUsage) {
const table = new Table({
head: [
chalk.cyan("Date"),
chalk.cyan("Total Tokens"),
chalk.cyan("Cost"),
chalk.cyan("Conversations"),
chalk.cyan("Models"),
],
style: { head: [], border: [] },
});
const sortedDays = Array.from(dailyUsage.keys()).sort().reverse();
for (const date of sortedDays) {
const usage = dailyUsage.get(date);
if (usage) {
table.push([
date,
formatTokenCount(usage.totalTokens),
chalk.green(formatCost(usage.cost)),
usage.conversationCount.toString(),
Array.from(usage.models).join(", "),
]);
}
}
return table.toString();
}
export function formatWeeklySummary(weeklyUsage) {
const table = new Table({
style: { head: [], border: [] },
});
table.push([chalk.cyan("Week"), `${weeklyUsage.startDate} to ${weeklyUsage.endDate}`], [chalk.cyan("Total Tokens"), formatTokenCount(weeklyUsage.totalTokens)], [chalk.cyan("Prompt Tokens"), formatTokenCount(weeklyUsage.promptTokens)], [
chalk.cyan("Completion Tokens"),
formatTokenCount(weeklyUsage.completionTokens),
], [
chalk.cyan("Cache Tokens"),
`${formatTokenCount(weeklyUsage.cacheCreationTokens + weeklyUsage.cacheReadTokens)}`,
], [chalk.cyan("Total Cost"), chalk.green(formatCost(weeklyUsage.cost))], [chalk.cyan("Conversations"), weeklyUsage.conversationCount.toString()], [chalk.cyan("Models Used"), Array.from(weeklyUsage.models).join(", ")]);
return table.toString();
}
export function formatRateLimitStatus(rateLimitInfo) {
const table = new Table({
head: [
chalk.cyan("Model"),
chalk.cyan("Estimated Usage"),
chalk.cyan("Weekly Limit"),
chalk.cyan("% Used"),
chalk.cyan("Status"),
],
style: { head: [], border: [] },
});
// Sonnet 4 (using actual calculated usage)
const sonnet4Usage = `${formatHours(rateLimitInfo.currentUsage.estimatedHours.sonnet4.min)} hrs`;
const sonnet4Limit = `${rateLimitInfo.weeklyLimits.sonnet4.min}-${rateLimitInfo.weeklyLimits.sonnet4.max} hrs`;
const sonnet4Percent = `${formatPercentage(rateLimitInfo.percentUsed.sonnet4.min)}`;
const sonnet4Status = rateLimitInfo.percentUsed.sonnet4.min > 80
? chalk.red("⚠️ High")
: rateLimitInfo.percentUsed.sonnet4.min > 50
? chalk.yellow("⚡ Medium")
: chalk.green("✅ Low");
table.push([
"Sonnet 4",
sonnet4Usage,
sonnet4Limit,
sonnet4Percent,
sonnet4Status,
]);
// Opus 4 (using actual calculated usage)
const opus4Usage = `${formatHours(rateLimitInfo.currentUsage.estimatedHours.opus4.min)} hrs`;
const opus4Limit = `${rateLimitInfo.weeklyLimits.opus4.min}-${rateLimitInfo.weeklyLimits.opus4.max} hrs`;
const opus4Percent = `${formatPercentage(rateLimitInfo.percentUsed.opus4.min)}`;
const opus4Status = rateLimitInfo.percentUsed.opus4.min > 80
? chalk.red("⚠️ High")
: rateLimitInfo.percentUsed.opus4.min > 50
? chalk.yellow("⚡ Medium")
: chalk.green("✅ Low");
table.push(["Opus 4", opus4Usage, opus4Limit, opus4Percent, opus4Status]);
return table.toString();
}
export function formatHeader(title) {
const line = "═".repeat(title.length + 4);
return chalk.bold.blue(`
╔${line}╗
║ ${title} ║
╚${line}╝
`);
}
export function formatHourlyUsage(hourlyUsage) {
const table = new Table({
head: ["Hour", "Tokens", "Cost", "Conversations", "Sonnet %", "Opus %"],
style: { head: [], border: [] },
});
// Sort by hour and only show hours with usage
const activeHours = hourlyUsage
.filter((h) => h.totalTokens > 0)
.sort((a, b) => a.hour - b.hour);
for (const hour of activeHours) {
const timeStr = `${hour.hour.toString().padStart(2, "0")}:00`;
const sonnetPercent = hour.totalTokens > 0
? `${((hour.sonnetTokens / hour.totalTokens) * 100).toFixed(0)}%`
: "0%";
const opusPercent = hour.totalTokens > 0
? `${((hour.opusTokens / hour.totalTokens) * 100).toFixed(0)}%`
: "0%";
table.push([
timeStr,
formatTokenCount(hour.totalTokens),
formatCost(hour.cost),
hour.conversationCount.toString(),
sonnetPercent,
opusPercent,
]);
}
return table.toString();
}
export function formatModelEfficiency(modelEfficiency) {
const table = new Table({
head: [
"Model",
"Conversations",
"Avg Tokens/Conv",
"Avg Cost/Conv",
"Cost/Token",
],
style: { head: [], border: [] },
});
// Sort by total cost (highest first)
const sortedModels = modelEfficiency.sort((a, b) => b.totalCost - a.totalCost);
for (const model of sortedModels) {
const modelName = model.model.includes("sonnet")
? "Sonnet 4"
: model.model.includes("opus")
? "Opus 4"
: model.model;
table.push([
modelName,
model.totalConversations.toString(),
formatTokenCount(model.avgTokensPerConversation),
formatCost(model.avgCostPerConversation),
`${formatCost(model.costPerToken * 1000)}/1K`,
]);
}
return table.toString();
}
export function formatEfficiencyInsights(insights) {
let output = "";
// Peak hours analysis
output += formatHeader("Peak Usage Hours");
const peakHoursStr = insights.peakHours
.map((h) => `${h.toString().padStart(2, "0")}:00`)
.join(", ");
output += `Your heaviest usage hours: ${chalk.yellow(peakHoursStr)}
`;
output += `💡 Consider scheduling intensive work during off-peak hours to avoid rate limits.
`;
// Model efficiency
output += formatHeader("Model Efficiency Analysis");
output += formatModelEfficiency(insights.modelEfficiency);
output += "\n";
// Cost savings opportunity
if (insights.costSavingsOpportunity.potentialSavings > 100) {
output += formatHeader("💰 Cost Optimization Opportunity");
output += chalk.green(`Potential monthly savings: $${insights.costSavingsOpportunity.potentialSavings.toFixed(0)}
`);
output += `${insights.costSavingsOpportunity.recommendation}
`;
}
// Hourly breakdown
output += formatHeader("Hourly Usage Pattern");
output += formatHourlyUsage(insights.hourlyUsage);
return output;
}
//# sourceMappingURL=formatters.js.map