[FEATURE] Lifetime Usage

Resolved 💬 2 comments Opened Mar 20, 2026 by shaneclary Closed Apr 17, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

I, any many others, would love to know our lifetime usage.

Proposed Solution

A usage tracker. Could be as simple as a lifetime amount or it could be more like a stock market ticker that shows usages peaks and valleys as well as amounts within specific periods of time.

which codes do i need post for them, this?

[package.json]

{
"name": "claude-usage-tracker",
"version": "1.0.0",
"description": "A stock-market-style usage tracker for Claude Code",
"type": "module",
"main": "index.js",
"bin": {
"claude-usage": "./index.js"
},
"scripts": {
"start": "node index.js",
"demo": "node index.js --demo"
},
"dependencies": {},
"license": "MIT"
}

----------------------------------
[index.js]

#!/usr/bin/env node

// ============================================================================
// Claude Code Usage Tracker — Stock-Market-Style Terminal Dashboard
// ============================================================================
// A proposed feature for Claude Code that tracks and visualizes token usage
// over time with sparklines, period breakdowns, peaks/valleys, and streaks.
//
// Usage:
// node index.js # Show dashboard with real data
// node index.js --demo # Generate demo data and show dashboard
// node index.js --record # Record current session usage (hook this up)
// node index.js --export # Export data as CSV
// node index.js --period 7d # Show last 7 days (1d, 7d, 30d, 90d, 1y, all)
// ============================================================================

import fs from "fs";
import path from "path";
import os from "os";

// ---------------------------------------------------------------------------
// Config & Storage
// ---------------------------------------------------------------------------

const DATA_DIR = path.join(os.homedir(), ".claude", "usage-tracker");
const DATA_FILE = path.join(DATA_DIR, "usage-history.json");

function ensureDataDir() {
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
}

function loadHistory() {
ensureDataDir();
if (!fs.existsSync(DATA_FILE)) return [];
try {
return JSON.parse(fs.readFileSync(DATA_FILE, "utf-8"));
} catch {
return [];
}
}

function saveHistory(history) {
ensureDataDir();
fs.writeFileSync(DATA_FILE, JSON.stringify(history, null, 2));
}

// ---------------------------------------------------------------------------
// ANSI Color Helpers
// ---------------------------------------------------------------------------

const c = {
reset: "\x1b[0m",
bold: "\x1b[1m",
dim: "\x1b[2m",
red: "\x1b[31m",
green: "\x1b[32m",
yellow: "\x1b[33m",
blue: "\x1b[34m",
magenta: "\x1b[35m",
cyan: "\x1b[36m",
white: "\x1b[37m",
gray: "\x1b[90m",
bgRed: "\x1b[41m",
bgGreen: "\x1b[42m",
bgBlue: "\x1b[44m",
bgMagenta: "\x1b[45m",
bgCyan: "\x1b[46m",
};

// ---------------------------------------------------------------------------
// Number Formatting
// ---------------------------------------------------------------------------

function formatTokens(n) {
if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(2) + "B";
if (n >= 1_000_000) return (n / 1_000_000).toFixed(2) + "M";
if (n >= 1_000) return (n / 1_000).toFixed(1) + "K";
return n.toString();
}

function formatCost(cents) {
return "$" + (cents / 100).toFixed(2);
}

function padLeft(str, len) {
return str.toString().padStart(len);
}

function padRight(str, len) {
return str.toString().padEnd(len);
}

// ---------------------------------------------------------------------------
// Sparkline & Chart Rendering
// ---------------------------------------------------------------------------

const SPARK_CHARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];

function sparkline(values, width = 60) {
if (values.length === 0) return "";

// Bucket values into width bins
const bins = [];
const step = values.length / width;
for (let i = 0; i < width; i++) {
const start = Math.floor(i * step);
const end = Math.floor((i + 1) * step);
const slice = values.slice(start, Math.max(end, start + 1));
bins.push(slice.reduce((a, b) => a + b, 0) / slice.length);
}

const max = Math.max(...bins);
const min = Math.min(...bins);
const range = max - min || 1;

return bins
.map((v) => {
const idx = Math.round(((v - min) / range) * (SPARK_CHARS.length - 1));
return SPARK_CHARS[idx];
})
.join("");
}

function barChart(data, width = 40, colorFn = () => c.cyan) {
const max = Math.max(...data.map((d) => d.value));
const lines = [];
for (const d of data) {
const barLen = max > 0 ? Math.round((d.value / max) * width) : 0;
const bar = "█".repeat(barLen) + "░".repeat(width - barLen);
const color = colorFn(d);
lines.push(
${c.dim}${padRight(d.label, 12)}${c.reset} ${color}${bar}${c.reset} ${c.bold}${formatTokens(d.value)}${c.reset}
);
}
return lines.join("\n");
}

function areaChart(values, width = 70, height = 12) {
if (values.length === 0) return " No data";

// Bucket into width bins
const bins = [];
const step = values.length / width;
for (let i = 0; i < width; i++) {
const start = Math.floor(i * step);
const end = Math.floor((i + 1) * step);
const slice = values.slice(start, Math.max(end, start + 1));
bins.push(slice.reduce((a, b) => a + b, 0) / slice.length);
}

const max = Math.max(...bins);
const min = 0;
const range = max - min || 1;

const grid = Array.from({ length: height }, () =>
Array.from({ length: width }, () => " ")
);

for (let col = 0; col < width; col++) {
const h = Math.round(((bins[col] - min) / range) * (height - 1));
for (let row = 0; row <= h; row++) {
grid[height - 1 - row][col] = "█";
}
}

const lines = [];
for (let row = 0; row < height; row++) {
const yVal = max - (row / (height - 1)) * range;
const yLabel = padLeft(formatTokens(Math.round(yVal)), 7);
const rowChars = grid[row]
.map((ch, col) => {
if (ch === "█") {
// Gradient color based on height
const h = height - 1 - row;
if (h > height * 0.75) return ${c.red}█${c.reset};
if (h > height * 0.5) return ${c.yellow}█${c.reset};
if (h > height * 0.25) return ${c.green}█${c.reset};
return ${c.cyan}█${c.reset};
}
return ${c.dim}·${c.reset};
})
.join("");
lines.push( ${c.dim}${yLabel} │${c.reset}${rowChars});
}
lines.push( ${c.dim}${" ".repeat(7)} └${"─".repeat(width)}${c.reset});

return lines.join("\n");
}

// ---------------------------------------------------------------------------
// Statistics & Analysis
// ---------------------------------------------------------------------------

function calculateStats(entries) {
if (entries.length === 0) {
return {
totalInput: 0,
totalOutput: 0,
totalCacheRead: 0,
totalCacheWrite: 0,
totalTokens: 0,
totalCost: 0,
avgDaily: 0,
peakDay: null,
valleyDay: null,
currentStreak: 0,
longestStreak: 0,
sessionsCount: entries.length,
};
}

let totalInput = 0;
let totalOutput = 0;
let totalCacheRead = 0;
let totalCacheWrite = 0;
let totalCost = 0;

// Group by day
const daily = {};
for (const e of entries) {
const day = e.timestamp.slice(0, 10);
if (!daily[day]) daily[day] = { input: 0, output: 0, cost: 0, total: 0 };
daily[day].input += e.inputTokens || 0;
daily[day].output += e.outputTokens || 0;
daily[day].cost += e.costCents || 0;
daily[day].total +=
(e.inputTokens || 0) + (e.outputTokens || 0) + (e.cacheReadTokens || 0);

totalInput += e.inputTokens || 0;
totalOutput += e.outputTokens || 0;
totalCacheRead += e.cacheReadTokens || 0;
totalCacheWrite += e.cacheWriteTokens || 0;
totalCost += e.costCents || 0;
}

const days = Object.keys(daily).sort();
const dailyTotals = days.map((d) => daily[d].total);

// Peak & Valley
let peakIdx = 0;
let valleyIdx = 0;
for (let i = 0; i < dailyTotals.length; i++) {
if (dailyTotals[i] > dailyTotals[peakIdx]) peakIdx = i;
if (dailyTotals[i] < dailyTotals[valleyIdx]) valleyIdx = i;
}

// Streaks (consecutive days with usage)
let currentStreak = 0;
let longestStreak = 0;
let streak = 0;
const today = new Date().toISOString().slice(0, 10);

// Build set of active days
const daySet = new Set(days);
const sortedDays = [...days].sort().reverse();

// Current streak from today backwards
let checkDate = new Date();
while (daySet.has(checkDate.toISOString().slice(0, 10))) {
currentStreak++;
checkDate.setDate(checkDate.getDate() - 1);
}

// Longest streak
for (let i = 0; i < days.length; i++) {
if (
i === 0 ||
dateDiffDays(new Date(days[i - 1]), new Date(days[i])) === 1
) {
streak++;
} else {
streak = 1;
}
longestStreak = Math.max(longestStreak, streak);
}

const totalTokens = totalInput + totalOutput + totalCacheRead;
const avgDaily =
dailyTotals.length > 0
? totalTokens / dailyTotals.length
: 0;

return {
totalInput,
totalOutput,
totalCacheRead,
totalCacheWrite,
totalTokens,
totalCost,
avgDaily,
peakDay: days[peakIdx]
? { date: days[peakIdx], tokens: dailyTotals[peakIdx] }
: null,
valleyDay: days[valleyIdx]
? { date: days[valleyIdx], tokens: dailyTotals[valleyIdx] }
: null,
currentStreak,
longestStreak,
sessionsCount: entries.length,
daily,
days,
dailyTotals,
};
}

function dateDiffDays(a, b) {
return Math.round((b - a) / (1000 60 60 * 24));
}

// ---------------------------------------------------------------------------
// Period Filtering
// ---------------------------------------------------------------------------

function filterByPeriod(entries, period) {
if (period === "all") return entries;

const now = new Date();
let cutoff;
switch (period) {
case "1d":
cutoff = new Date(now - 24 60 60 * 1000);
break;
case "7d":
cutoff = new Date(now - 7 24 60 60 1000);
break;
case "30d":
cutoff = new Date(now - 30 24 60 60 1000);
break;
case "90d":
cutoff = new Date(now - 90 24 60 60 1000);
break;
case "1y":
cutoff = new Date(now - 365 24 60 60 1000);
break;
default:
return entries;
}

return entries.filter((e) => new Date(e.timestamp) >= cutoff);
}

// ---------------------------------------------------------------------------
// Trend Detection
// ---------------------------------------------------------------------------

function detectTrend(dailyTotals) {
if (dailyTotals.length < 3) return { direction: "→", label: "Not enough data", color: c.gray };

const recent = dailyTotals.slice(-7);
const prev = dailyTotals.slice(-14, -7);

if (prev.length === 0) return { direction: "→", label: "New", color: c.gray };

const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
const prevAvg = prev.reduce((a, b) => a + b, 0) / prev.length;

const change = prevAvg > 0 ? ((recentAvg - prevAvg) / prevAvg) * 100 : 0;

if (change > 20) return { direction: "↑↑", label: +${change.toFixed(0)}%, color: c.red };
if (change > 5) return { direction: "↑", label: +${change.toFixed(0)}%, color: c.yellow };
if (change < -20) return { direction: "↓↓", label: ${change.toFixed(0)}%, color: c.green };
if (change < -5) return { direction: "↓", label: ${change.toFixed(0)}%, color: c.cyan };
return { direction: "→", label: ${change > 0 ? "+" : ""}${change.toFixed(0)}%, color: c.gray };
}

// ---------------------------------------------------------------------------
// Heatmap (GitHub-style contribution grid)
// ---------------------------------------------------------------------------

function heatmap(daily, weeks = 20) {
const HEAT_CHARS = [" ", "░", "▒", "▓", "█"];
const DAY_LABELS = ["Mon", " ", "Wed", " ", "Fri", " ", "Sun"];

const today = new Date();
const totalDays = weeks * 7;

// Collect daily totals
const vals = [];
for (let i = totalDays - 1; i >= 0; i--) {
const d = new Date(today);
d.setDate(d.getDate() - i);
const key = d.toISOString().slice(0, 10);
vals.push({ date: key, value: daily[key]?.total || 0 });
}

const maxVal = Math.max(...vals.map((v) => v.value), 1);

// Build grid [7 rows x N weeks]
const grid = Array.from({ length: 7 }, () => []);
for (let i = 0; i < vals.length; i++) {
const row = i % 7;
const level = Math.round((vals[i].value / maxVal) * (HEAT_CHARS.length - 1));
grid[row].push({ char: HEAT_CHARS[level], value: vals[i].value, date: vals[i].date });
}

const lines = [];
for (let row = 0; row < 7; row++) {
const label = padRight(DAY_LABELS[row], 4);
const cells = grid[row]
.map((cell) => {
if (cell.value === 0) return ${c.dim}·${c.reset};
const ratio = cell.value / maxVal;
if (ratio > 0.75) return ${c.red}█${c.reset};
if (ratio > 0.5) return ${c.yellow}▓${c.reset};
if (ratio > 0.25) return ${c.green}▒${c.reset};
return ${c.cyan}░${c.reset};
})
.join("");
lines.push( ${c.dim}${label}${c.reset}${cells});
}

return lines.join("\n");
}

// ---------------------------------------------------------------------------
// Hourly Distribution
// ---------------------------------------------------------------------------

function hourlyDistribution(entries) {
const hours = Array(24).fill(0);
for (const e of entries) {
const h = new Date(e.timestamp).getHours();
hours[h] += (e.inputTokens || 0) + (e.outputTokens || 0);
}

const max = Math.max(...hours);
const barHeight = 6;
const lines = [];

for (let row = barHeight; row > 0; row--) {
const threshold = (row / barHeight) * max;
let line = " ";
for (let h = 0; h < 24; h++) {
if (hours[h] >= threshold) {
if (h >= 6 && h < 12) line += ${c.yellow}█${c.reset};
else if (h >= 12 && h < 18) line += ${c.green}█${c.reset};
else if (h >= 18 && h < 22) line += ${c.cyan}█${c.reset};
else line += ${c.magenta}█${c.reset};
} else {
line += ${c.dim}·${c.reset};
}
line += " ";
}
lines.push(line);
}
lines.push(
${c.dim}${Array.from({ length: 24 }, (_, i) => (i % 3 === 0 ? padLeft(i, 2) : " ")).join("")}${c.reset}
);
lines.push(
${c.magenta}■${c.reset}${c.dim} night${c.reset} ${c.yellow}■${c.reset}${c.dim} morning${c.reset} ${c.green}■${c.reset}${c.dim} afternoon${c.reset} ${c.cyan}■${c.reset}${c.dim} evening${c.reset}
);

return lines.join("\n");
}

// ---------------------------------------------------------------------------
// Demo Data Generator
// ---------------------------------------------------------------------------

function generateDemoData() {
const entries = [];
const now = new Date();

for (let daysAgo = 180; daysAgo >= 0; daysAgo--) {
const date = new Date(now);
date.setDate(date.getDate() - daysAgo);

// Skip some days randomly (weekends less likely)
const dow = date.getDay();
if (Math.random() < (dow === 0 || dow === 6 ? 0.4 : 0.1)) continue;

// Sessions per day: 1-8
const sessions = Math.floor(Math.random() * 8) + 1;

for (let s = 0; s < sessions; s++) {
const hour = Math.floor(Math.random() * 14) + 8; // 8am - 10pm
const sessionDate = new Date(date);
sessionDate.setHours(hour, Math.floor(Math.random() * 60));

// Simulate varying usage levels with some "big coding days"
const isBigDay = Math.random() < 0.15;
const multiplier = isBigDay ? 3 + Math.random() * 5 : 1;

const inputTokens = Math.floor(
(5000 + Math.random() 30000) multiplier
);
const outputTokens = Math.floor(
(2000 + Math.random() 15000) multiplier
);
const cacheReadTokens = Math.floor(Math.random() inputTokens 0.6);
const cacheWriteTokens = Math.floor(Math.random() inputTokens 0.1);

// Approximate cost (Sonnet pricing rough estimate)
const costCents =
(inputTokens 0.3 + outputTokens 1.5 + cacheReadTokens * 0.03) /
100;

entries.push({
timestamp: sessionDate.toISOString(),
inputTokens,
outputTokens,
cacheReadTokens,
cacheWriteTokens,
costCents: Math.round(costCents * 100) / 100,
model: Math.random() > 0.3 ? "opus-4" : "sonnet-4",
sessionDurationMinutes: Math.floor(5 + Math.random() * 120),
});
}
}

return entries;
}

// ---------------------------------------------------------------------------
// Record a Session (for hook integration)
// ---------------------------------------------------------------------------

function recordSession(data) {
const history = loadHistory();
history.push({
timestamp: new Date().toISOString(),
inputTokens: data.inputTokens || 0,
outputTokens: data.outputTokens || 0,
cacheReadTokens: data.cacheReadTokens || 0,
cacheWriteTokens: data.cacheWriteTokens || 0,
costCents: data.costCents || 0,
model: data.model || "unknown",
sessionDurationMinutes: data.durationMinutes || 0,
});
saveHistory(history);
console.log(${c.green}✓${c.reset} Session recorded.);
}

// ---------------------------------------------------------------------------
// CSV Export
// ---------------------------------------------------------------------------

function exportCSV(entries) {
const header =
"timestamp,input_tokens,output_tokens,cache_read,cache_write,cost_cents,model,duration_min";
const rows = entries.map(
(e) =>
${e.timestamp},${e.inputTokens},${e.outputTokens},${e.cacheReadTokens},${e.cacheWriteTokens},${e.costCents},${e.model},${e.sessionDurationMinutes}
);
return [header, ...rows].join("\n");
}

// ---------------------------------------------------------------------------
// Main Dashboard Render
// ---------------------------------------------------------------------------

function renderDashboard(entries, period = "all") {
const filtered = filterByPeriod(entries, period);
const stats = calculateStats(filtered);
const allStats = calculateStats(entries);
const trend = detectTrend(stats.dailyTotals || []);

const W = Math.min(process.stdout.columns || 80, 90);
const line = c.dim + "─".repeat(W) + c.reset;
const doubleLine = c.dim + "═".repeat(W) + c.reset;

console.log("");
console.log(doubleLine);
console.log(
${c.bold}${c.cyan}◆ CLAUDE CODE USAGE TRACKER${c.reset}${c.dim} · Period: ${period.toUpperCase()} · ${new Date().toLocaleDateString()}${c.reset}
);
console.log(doubleLine);

// ── Ticker Bar ──
console.log("");
const tickerItems = [
${c.bold}TOTAL${c.reset} ${formatTokens(stats.totalTokens)},
${trend.color}${trend.direction} ${trend.label}${c.reset},
${c.dim}IN${c.reset} ${c.green}${formatTokens(stats.totalInput)}${c.reset},
${c.dim}OUT${c.reset} ${c.yellow}${formatTokens(stats.totalOutput)}${c.reset},
${c.dim}CACHE${c.reset} ${c.cyan}${formatTokens(stats.totalCacheRead)}${c.reset},
${c.dim}COST${c.reset} ${c.magenta}${formatCost(stats.totalCost)}${c.reset},
${c.dim}SESSIONS${c.reset} ${c.bold}${stats.sessionsCount}${c.reset},
];
console.log( ${tickerItems.join(" ${c.dim}│${c.reset} ".replace("${c.dim}", c.dim).replace("${c.reset}", c.reset))});

// ── Lifetime Stats (always show) ──
if (period !== "all") {
console.log("");
console.log(
${c.dim}Lifetime: ${formatTokens(allStats.totalTokens)} tokens · ${formatCost(allStats.totalCost)} · ${allStats.sessionsCount} sessions${c.reset}
);
}

// ── Usage Chart ──
console.log("");
console.log( ${c.bold}📈 TOKEN USAGE OVER TIME${c.reset});
console.log(line);
if (stats.dailyTotals && stats.dailyTotals.length > 0) {
console.log(areaChart(stats.dailyTotals, Math.min(W - 12, 70), 10));
console.log(
${c.dim}${" ".repeat(7)} ${stats.days[0]}${" ".repeat(Math.max(0, Math.min(W - 12, 70) - 21))}${stats.days[stats.days.length - 1]}${c.reset}
);
} else {
console.log(" No data for this period");
}

// ── Sparklines ──
console.log("");
console.log( ${c.bold}⚡ SPARKLINES${c.reset});
console.log(line);
if (stats.dailyTotals && stats.dailyTotals.length > 0) {
const sparkW = Math.min(W - 20, 60);
console.log(
${c.dim}Total:${c.reset} ${c.cyan}${sparkline(stats.dailyTotals, sparkW)}${c.reset}
);

// Input sparkline
const inputDaily = (stats.days || []).map(
(d) => stats.daily[d]?.input || 0
);
console.log(
${c.dim}Input:${c.reset} ${c.green}${sparkline(inputDaily, sparkW)}${c.reset}
);

// Output sparkline
const outputDaily = (stats.days || []).map(
(d) => stats.daily[d]?.output || 0
);
console.log(
${c.dim}Output:${c.reset} ${c.yellow}${sparkline(outputDaily, sparkW)}${c.reset}
);
}

// ── Peaks & Valleys ──
console.log("");
console.log( ${c.bold}🏔️ PEAKS & VALLEYS${c.reset});
console.log(line);
if (stats.peakDay) {
console.log(
${c.red}▲ Peak:${c.reset} ${stats.peakDay.date} — ${c.bold}${formatTokens(stats.peakDay.tokens)}${c.reset} tokens
);
}
if (stats.valleyDay) {
console.log(
${c.green}▼ Valley:${c.reset} ${stats.valleyDay.date} — ${c.bold}${formatTokens(stats.valleyDay.tokens)}${c.reset} tokens
);
}
console.log(
${c.dim}Avg/day:${c.reset} ${c.bold}${formatTokens(Math.round(stats.avgDaily))}${c.reset} tokens
);

// ── Streaks ──
console.log("");
console.log( ${c.bold}🔥 STREAKS${c.reset});
console.log(line);
console.log(
Current: ${c.bold}${allStats.currentStreak}${c.reset} day${allStats.currentStreak !== 1 ? "s" : ""} Longest: ${c.bold}${allStats.longestStreak}${c.reset} day${allStats.longestStreak !== 1 ? "s" : ""}
);

// ── Heatmap ──
console.log("");
console.log( ${c.bold}📅 ACTIVITY HEATMAP${c.reset} ${c.dim}(last 20 weeks)${c.reset});
console.log(line);
console.log(heatmap(allStats.daily || {}, 20));
console.log(
${c.dim}·${c.reset} none ${c.cyan}░${c.reset} light ${c.green}▒${c.reset} medium ${c.yellow}▓${c.reset} heavy ${c.red}█${c.reset} extreme
);

// ── Hourly Distribution ──
console.log("");
console.log( ${c.bold}🕐 HOURLY DISTRIBUTION${c.reset});
console.log(line);
console.log(hourlyDistribution(filtered));

// ── Token Breakdown ──
console.log("");
console.log( ${c.bold}🧮 TOKEN BREAKDOWN${c.reset});
console.log(line);
console.log(
barChart(
[
{ label: "Input", value: stats.totalInput },
{ label: "Output", value: stats.totalOutput },
{ label: "Cache Read", value: stats.totalCacheRead },
{ label: "Cache Write", value: stats.totalCacheWrite },
],
Math.min(W - 30, 40),
(d) => {
if (d.label === "Input") return c.green;
if (d.label === "Output") return c.yellow;
if (d.label === "Cache Read") return c.cyan;
return c.magenta;
}
)
);

// ── Weekly Breakdown ──
if (stats.days && stats.days.length > 7) {
console.log("");
console.log( ${c.bold}📊 WEEKLY TOTALS${c.reset});
console.log(line);

const weeklyData = [];
let weekTotal = 0;
let weekStart = stats.days[0];

for (let i = 0; i < stats.days.length; i++) {
weekTotal += stats.dailyTotals[i];
if (
(i + 1) % 7 === 0 ||
i === stats.days.length - 1
) {
const label = weekStart.slice(5);
weeklyData.push({ label, value: weekTotal });
weekTotal = 0;
if (i + 1 < stats.days.length) weekStart = stats.days[i + 1];
}
}

// Show last 12 weeks max
const recentWeeks = weeklyData.slice(-12);
console.log(
barChart(recentWeeks, Math.min(W - 30, 40), (d) => {
const max = Math.max(...recentWeeks.map((w) => w.value));
const ratio = d.value / max;
if (ratio > 0.75) return c.red;
if (ratio > 0.5) return c.yellow;
if (ratio > 0.25) return c.green;
return c.cyan;
})
);
}

// ── Model Split ──
const models = {};
for (const e of filtered) {
const m = e.model || "unknown";
if (!models[m]) models[m] = 0;
models[m] += (e.inputTokens || 0) + (e.outputTokens || 0);
}
if (Object.keys(models).length > 0) {
console.log("");
console.log( ${c.bold}🤖 MODEL SPLIT${c.reset});
console.log(line);
const modelData = Object.entries(models)
.map(([label, value]) => ({ label, value }))
.sort((a, b) => b.value - a.value);
console.log(
barChart(modelData, Math.min(W - 30, 40), () => c.magenta)
);
}

console.log("");
console.log(doubleLine);
console.log(
${c.dim}Data: ${DATA_FILE}${c.reset}
);
console.log(
${c.dim}Record: claude-usage --record '{"inputTokens":1000,"outputTokens":500}'${c.reset}
);
console.log(
${c.dim}Filter: claude-usage --period 7d (1d, 7d, 30d, 90d, 1y, all)${c.reset}
);
console.log("");
}

// ---------------------------------------------------------------------------
// CLI Entry Point
// ---------------------------------------------------------------------------

const args = process.argv.slice(2);

if (args.includes("--demo")) {
console.log(${c.yellow}Generating demo data...${c.reset});
const demo = generateDemoData();
saveHistory(demo);
console.log(
${c.green}✓${c.reset} Generated ${demo.length} sessions over 180 days.
);
const periodIdx = args.indexOf("--period");
const period = periodIdx !== -1 ? args[periodIdx + 1] : "all";
renderDashboard(demo, period);
} else if (args.includes("--record")) {
const dataIdx = args.indexOf("--record");
try {
const data = JSON.parse(args[dataIdx + 1]);
recordSession(data);
} catch {
console.error("Usage: claude-usage --record '{\"inputTokens\":..., \"outputTokens\":...}'");
process.exit(1);
}
} else if (args.includes("--export")) {
const history = loadHistory();
const csv = exportCSV(history);
const outFile = path.join(DATA_DIR, "usage-export.csv");
fs.writeFileSync(outFile, csv);
console.log(${c.green}✓${c.reset} Exported ${history.length} entries to ${outFile});
} else {
const history = loadHistory();
if (history.length === 0) {
console.log("");
console.log( ${c.yellow}No usage data found.${c.reset});
console.log( Run with ${c.bold}--demo${c.reset} to generate sample data.);
console.log( Use ${c.bold}--record${c.reset} to start tracking.);
console.log("");
console.log( ${c.dim}Tip: Add a Claude Code hook to auto-record sessions:${c.reset});
console.log( ${c.dim} // .claude/hooks/on-session-end.sh${c.reset});
console.log( ${c.dim} claude-usage --record "\${USAGE_JSON}"${c.reset});
console.log("");
} else {
const periodIdx = args.indexOf("--period");
const period = periodIdx !== -1 ? args[periodIdx + 1] : "all";
renderDashboard(history, period);
}
}

Alternative Solutions

_No response_

Priority

Critical - Blocking my work

Feature Category

CLI commands and flags

Use Case Example

_No response_

Additional Context

_No response_

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗