cctrackr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/test-helpers.ts","../src/core/pricing.ts","../src/index.ts","../src/commands/daily.ts","../src/utils/fs.ts","../src/core/parser.ts","../src/core/types.ts","../src/core/dedup.ts","../src/core/calculator.ts","../src/utils/date.ts","../src/core/aggregator.ts","../src/utils/format.ts","../src/core/budget.ts","../src/core/burnrate.ts","../src/commands/monthly.ts","../src/commands/session.ts","../src/commands/dashboard.ts","../src/commands/export.ts","../src/commands/roi.ts","../src/commands/live.ts","../src/commands/pricing.ts","../src/commands/config.ts","../src/commands/blocks.ts","../src/commands/statusline.ts","../src/commands/limits.ts","../src/core/rate-model.ts"],"sourcesContent":["import type { UsageEntry } from './types.js';\n\n/**\n * Shared test helper: create a UsageEntry with sensible defaults.\n * Used across all in-source test files.\n */\nexport function makeEntry(overrides: Partial<UsageEntry> = {}): UsageEntry {\n return {\n timestamp: '2025-03-25T10:00:00Z',\n message: {\n model: 'claude-sonnet-4-20250514',\n usage: {\n input_tokens: 1000,\n output_tokens: 500,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n },\n },\n ...overrides,\n };\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { homedir } from 'node:os';\nimport type { PricingData, ModelPricing } from './types.js';\n\nlet pricingData: PricingData | null = null;\n\nconst CACHE_DIR = join(homedir(), '.cctrack');\nconst CACHE_FILE = join(CACHE_DIR, 'pricing.json');\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst PRICING_URL = 'https://platform.claude.com/docs/en/about-claude/pricing';\n\n// === Fetching & Caching ===\n\ninterface CachedPricing {\n fetched_at: string;\n data: PricingData;\n}\n\nfunction getCacheAge(): number {\n try {\n if (!existsSync(CACHE_FILE)) return Infinity;\n const cached: CachedPricing = JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));\n return Date.now() - new Date(cached.fetched_at).getTime();\n } catch {\n return Infinity;\n }\n}\n\nfunction readCache(): PricingData | null {\n try {\n if (!existsSync(CACHE_FILE)) return null;\n const cached: CachedPricing = JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));\n return cached.data;\n } catch {\n return null;\n }\n}\n\nfunction writeCache(data: PricingData): void {\n try {\n mkdirSync(CACHE_DIR, { recursive: true });\n const cached: CachedPricing = { fetched_at: new Date().toISOString(), data };\n writeFileSync(CACHE_FILE, JSON.stringify(cached, null, 2), 'utf-8');\n } catch {\n // Non-fatal: pricing still works, just not cached\n }\n}\n\nfunction loadBundled(): PricingData {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pricingPath = join(__dirname, '..', '..', 'pricing', 'models.json');\n try {\n return JSON.parse(readFileSync(pricingPath, 'utf-8')) as PricingData;\n } catch {\n const fallbackPath = join(process.cwd(), 'pricing', 'models.json');\n return JSON.parse(readFileSync(fallbackPath, 'utf-8')) as PricingData;\n }\n}\n\n/**\n * Parse pricing from the Anthropic pricing HTML page.\n * Extracts model names, input/output/cache costs from the pricing tables.\n */\nfunction parsePricingHtml(html: string): PricingData | null {\n try {\n const models: Record<string, ModelPricing> = {};\n const aliases: Record<string, string> = {};\n\n // Extract model blocks - look for model IDs in the HTML\n const modelIdPattern = /claude-(?:opus|sonnet|haiku)-[\\d.-]+(?:-\\d{8})?/g;\n const foundModelIds = new Set<string>();\n let match;\n while ((match = modelIdPattern.exec(html)) !== null) {\n foundModelIds.add(match[0]);\n }\n\n // Try to extract structured data from table rows\n // Pattern: model name followed by pricing values in same row/section\n const rowPattern = /<tr[^>]*>[\\s\\S]*?<\\/tr>/gi;\n const rows = html.match(rowPattern) || [];\n\n for (const row of rows) {\n // Find model ID in this row\n const modelMatch = row.match(/claude-(?:opus|sonnet|haiku)-[\\w.-]+/);\n if (!modelMatch) continue;\n\n const modelId = modelMatch[0];\n\n // Extract all dollar amounts from this row\n const prices: number[] = [];\n let priceMatch;\n const rowPricePattern = /\\$(\\d{1,3}(?:,\\d{3})*(?:\\.\\d+)?)/g;\n while ((priceMatch = rowPricePattern.exec(row)) !== null) {\n prices.push(parseFloat(priceMatch[1].replace(/,/g, '')));\n }\n\n // Typical table: Input, Output (minimum 2 prices)\n if (prices.length >= 2) {\n const pricing: ModelPricing = {\n input_cost_per_million: prices[0],\n output_cost_per_million: prices[1],\n cache_creation_cost_per_million: prices[0] * 1.25, // 1.25x input\n cache_read_cost_per_million: prices[0] * 0.1, // 0.1x input\n context_window: 200000,\n };\n\n // If more prices, they may be cache or tiered\n if (prices.length >= 4) {\n pricing.cache_creation_cost_per_million = prices[2];\n pricing.cache_read_cost_per_million = prices[3];\n }\n\n models[modelId] = pricing;\n }\n }\n\n if (Object.keys(models).length === 0) return null;\n\n // Generate common aliases\n for (const modelId of Object.keys(models)) {\n // claude-opus-4-6-20260205 -> claude-opus-4-6\n const shortMatch = modelId.match(/^(claude-(?:opus|sonnet|haiku)-[\\d.-]+)-\\d{8}$/);\n if (shortMatch) {\n aliases[shortMatch[1]] = modelId;\n aliases[shortMatch[1] + '-latest'] = modelId;\n }\n }\n\n return {\n version: new Date().toISOString().slice(0, 10),\n models,\n aliases,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch latest pricing from Anthropic's docs.\n * Returns parsed pricing data or null on failure.\n */\nexport async function fetchPricing(): Promise<PricingData | null> {\n try {\n const response = await fetch(PRICING_URL, {\n headers: { 'User-Agent': 'cctrack/0.1.0' },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) return null;\n\n const html = await response.text();\n return parsePricingHtml(html);\n } catch {\n return null;\n }\n}\n\n/**\n * Update pricing: fetch from Anthropic, merge with bundled, and cache.\n * Returns the merged pricing data and whether any new models were found.\n */\nexport async function updatePricing(): Promise<{ data: PricingData; newModels: string[]; source: string }> {\n const bundled = loadBundled();\n const fetched = await fetchPricing();\n\n if (!fetched || Object.keys(fetched.models).length === 0) {\n if (process.env.DEBUG) console.warn('cctrack: pricing fetch returned 0 models, using bundled pricing');\n return { data: bundled, newModels: [], source: 'bundled (fetch failed)' };\n }\n\n // Merge: fetched models override bundled, bundled fills gaps\n const merged: PricingData = {\n version: new Date().toISOString().slice(0, 10),\n models: { ...bundled.models },\n aliases: { ...bundled.aliases },\n };\n\n const newModels: string[] = [];\n for (const [id, pricing] of Object.entries(fetched.models)) {\n if (!merged.models[id]) {\n newModels.push(id);\n }\n merged.models[id] = pricing;\n }\n\n // Merge aliases\n for (const [alias, target] of Object.entries(fetched.aliases)) {\n merged.aliases[alias] = target;\n }\n\n writeCache(merged);\n return { data: merged, newModels, source: 'fetched + bundled' };\n}\n\n// === Loading (with cache priority) ===\n\n/**\n * Load pricing with priority:\n * 1. In-memory cache (for current process)\n * 2. Disk cache (~/.cctrack/pricing.json) if fresh\n * 3. Bundled pricing/models.json\n */\n/**\n * Load pricing synchronously. Uses in-memory cache after first call.\n * Sync I/O only happens once (first call) — subsequent calls return cached data.\n * This is safe for live mode since initPricing() pre-loads at startup.\n */\nfunction loadPricing(): PricingData {\n if (pricingData) return pricingData;\n\n // Try disk cache first\n const cacheAge = getCacheAge();\n if (cacheAge < CACHE_TTL_MS) {\n const cached = readCache();\n if (cached) {\n pricingData = cached;\n return pricingData;\n }\n }\n\n // Fall back to bundled\n pricingData = loadBundled();\n return pricingData;\n}\n\n/**\n * Initialize pricing with a background fetch if cache is stale.\n * Call this at startup — it returns immediately with cached/bundled data\n * and kicks off an async refresh if needed.\n */\nexport async function initPricing(): Promise<void> {\n const cacheAge = getCacheAge();\n\n if (cacheAge >= CACHE_TTL_MS) {\n // Cache is stale — try to refresh\n const fetched = await fetchPricing();\n if (fetched && Object.keys(fetched.models).length > 0) {\n const bundled = loadBundled();\n const merged: PricingData = {\n version: new Date().toISOString().slice(0, 10),\n models: { ...bundled.models, ...fetched.models },\n aliases: { ...bundled.aliases, ...fetched.aliases },\n };\n writeCache(merged);\n pricingData = merged;\n }\n }\n}\n\n/** For testing: inject pricing data directly */\nexport function setPricingData(data: PricingData): void {\n pricingData = data;\n}\n\n/** For testing: reset cached pricing */\nexport function resetPricing(): void {\n pricingData = null;\n}\n\n/** Get the current pricing source info */\nexport function getPricingInfo(): { source: string; modelCount: number; version: string; cacheAge: string } {\n const data = loadPricing();\n const age = getCacheAge();\n let cacheAge: string;\n if (age === Infinity) {\n cacheAge = 'no cache';\n } else {\n const hours = Math.floor(age / (60 * 60 * 1000));\n const mins = Math.floor((age % (60 * 60 * 1000)) / (60 * 1000));\n cacheAge = `${hours}h ${mins}m ago`;\n }\n\n const source = age < CACHE_TTL_MS ? 'cached (fetched)' : 'bundled';\n\n return {\n source,\n modelCount: Object.keys(data.models).length,\n version: data.version,\n cacheAge,\n };\n}\n\n/** Get all loaded pricing data */\nexport function getAllPricing(): PricingData {\n return loadPricing();\n}\n\n/**\n * Resolve a model name to its pricing.\n * 1. Exact match\n * 2. Alias lookup\n * 3. null (no match — caller should fall back to costUSD or 0)\n *\n * NEVER does substring/fuzzy matching.\n */\nexport function getModelPricing(modelName: string): ModelPricing | null {\n const data = loadPricing();\n\n // Exact match\n if (data.models[modelName]) return data.models[modelName];\n\n // Alias lookup\n const resolved = data.aliases[modelName];\n if (resolved && data.models[resolved]) return data.models[resolved];\n\n return null;\n}\n\n/**\n * Calculate cost for a single token type with tiered pricing.\n * Threshold is applied per-request, matching Anthropic's billing.\n */\nexport function calculateTieredCost(\n tokens: number,\n basePricePerMillion: number,\n tieredPricePerMillion?: number,\n threshold: number = 200_000,\n): number {\n if (tokens <= 0) return 0;\n\n const baseRate = basePricePerMillion / 1_000_000;\n\n if (tieredPricePerMillion !== undefined && tokens > threshold) {\n const tieredRate = tieredPricePerMillion / 1_000_000;\n return threshold * baseRate + (tokens - threshold) * tieredRate;\n }\n\n return tokens * baseRate;\n}\n\n/**\n * Calculate cost breakdown for a single usage entry.\n */\nexport function calculateEntryCost(\n modelName: string,\n inputTokens: number,\n outputTokens: number,\n cacheWriteTokens: number,\n cacheReadTokens: number,\n embeddedCostUSD?: number,\n): { input: number; output: number; cacheWrite: number; cacheRead: number; total: number } {\n const pricing = getModelPricing(modelName);\n\n if (!pricing) {\n // Fall back to embedded costUSD\n if (embeddedCostUSD !== undefined) {\n return { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, total: embeddedCostUSD };\n }\n // No pricing info at all\n return { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, total: 0 };\n }\n\n const input = calculateTieredCost(\n inputTokens,\n pricing.input_cost_per_million,\n pricing.input_cost_per_million_above_200k,\n );\n\n const output = calculateTieredCost(\n outputTokens,\n pricing.output_cost_per_million,\n pricing.output_cost_per_million_above_200k,\n );\n\n const cacheWrite = calculateTieredCost(\n cacheWriteTokens,\n pricing.cache_creation_cost_per_million,\n pricing.cache_creation_cost_per_million_above_200k,\n );\n\n const cacheRead = calculateTieredCost(\n cacheReadTokens,\n pricing.cache_read_cost_per_million,\n pricing.cache_read_cost_per_million_above_200k,\n );\n\n return {\n input,\n output,\n cacheWrite,\n cacheRead,\n total: input + output + cacheWrite + cacheRead,\n };\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect, beforeEach } = import.meta.vitest;\n\n const testPricing: PricingData = {\n version: 'test',\n models: {\n 'claude-sonnet-4-20250514': {\n input_cost_per_million: 3.0,\n output_cost_per_million: 15.0,\n cache_creation_cost_per_million: 3.75,\n cache_read_cost_per_million: 0.30,\n input_cost_per_million_above_200k: 6.0,\n output_cost_per_million_above_200k: 30.0,\n cache_creation_cost_per_million_above_200k: 7.50,\n cache_read_cost_per_million_above_200k: 0.60,\n context_window: 200000,\n },\n 'claude-opus-4-20250514': {\n input_cost_per_million: 15.0,\n output_cost_per_million: 75.0,\n cache_creation_cost_per_million: 18.75,\n cache_read_cost_per_million: 1.50,\n input_cost_per_million_above_200k: 30.0,\n output_cost_per_million_above_200k: 150.0,\n cache_creation_cost_per_million_above_200k: 37.50,\n cache_read_cost_per_million_above_200k: 3.00,\n context_window: 200000,\n },\n 'claude-haiku-3-5-20241022': {\n input_cost_per_million: 0.80,\n output_cost_per_million: 4.0,\n cache_creation_cost_per_million: 1.0,\n cache_read_cost_per_million: 0.08,\n context_window: 200000,\n },\n },\n aliases: {\n 'claude-sonnet-4-6': 'claude-sonnet-4-20250514',\n 'claude-opus-4-6': 'claude-opus-4-20250514',\n },\n };\n\n beforeEach(() => {\n setPricingData(testPricing);\n });\n\n describe('getModelPricing', () => {\n it('returns pricing for exact model match', () => {\n const pricing = getModelPricing('claude-sonnet-4-20250514');\n expect(pricing).not.toBeNull();\n expect(pricing!.input_cost_per_million).toBe(3.0);\n });\n\n it('resolves alias to pricing', () => {\n const pricing = getModelPricing('claude-sonnet-4-6');\n expect(pricing).not.toBeNull();\n expect(pricing!.input_cost_per_million).toBe(3.0);\n });\n\n it('returns null for unknown model (no fuzzy matching)', () => {\n const pricing = getModelPricing('claude-sonnet');\n expect(pricing).toBeNull();\n });\n\n it('returns null for empty string', () => {\n expect(getModelPricing('')).toBeNull();\n });\n\n it('returns null for partial match (no substring matching)', () => {\n expect(getModelPricing('sonnet-4-20250514')).toBeNull();\n });\n });\n\n describe('calculateTieredCost', () => {\n it('returns 0 for 0 tokens', () => {\n expect(calculateTieredCost(0, 3.0, 6.0)).toBe(0);\n });\n\n it('returns 0 for negative tokens', () => {\n expect(calculateTieredCost(-100, 3.0, 6.0)).toBe(0);\n });\n\n it('calculates base rate for tokens under threshold', () => {\n const cost = calculateTieredCost(100_000, 3.0, 6.0);\n expect(cost).toBeCloseTo(0.30, 6);\n });\n\n it('calculates base rate at exactly 200k threshold', () => {\n const cost = calculateTieredCost(200_000, 3.0, 6.0);\n expect(cost).toBeCloseTo(0.60, 6);\n });\n\n it('applies tiered pricing above 200k', () => {\n const cost = calculateTieredCost(200_001, 3.0, 6.0);\n const expected = 200_000 * (3.0 / 1_000_000) + 1 * (6.0 / 1_000_000);\n expect(cost).toBeCloseTo(expected, 10);\n });\n\n it('calculates large tiered cost correctly', () => {\n const cost = calculateTieredCost(1_000_000, 3.0, 6.0);\n const expected = 200_000 * (3.0 / 1_000_000) + 800_000 * (6.0 / 1_000_000);\n expect(cost).toBeCloseTo(expected, 6);\n expect(cost).toBeCloseTo(0.60 + 4.80, 6);\n });\n\n it('uses base rate when no tiered price provided', () => {\n const cost = calculateTieredCost(300_000, 0.80);\n expect(cost).toBeCloseTo(300_000 * (0.80 / 1_000_000), 6);\n });\n });\n\n describe('calculateEntryCost', () => {\n it('calculates cost for known model', () => {\n const result = calculateEntryCost('claude-sonnet-4-20250514', 1000, 500, 200, 300);\n expect(result.input).toBeCloseTo(1000 * (3.0 / 1_000_000), 10);\n expect(result.output).toBeCloseTo(500 * (15.0 / 1_000_000), 10);\n expect(result.cacheWrite).toBeCloseTo(200 * (3.75 / 1_000_000), 10);\n expect(result.cacheRead).toBeCloseTo(300 * (0.30 / 1_000_000), 10);\n expect(result.total).toBeCloseTo(\n result.input + result.output + result.cacheWrite + result.cacheRead,\n 10,\n );\n });\n\n it('falls back to costUSD for unknown model', () => {\n const result = calculateEntryCost('unknown-model', 1000, 500, 0, 0, 0.42);\n expect(result.total).toBe(0.42);\n expect(result.input).toBe(0);\n });\n\n it('returns zero cost when no pricing and no embedded cost', () => {\n const result = calculateEntryCost('unknown-model', 1000, 500, 0, 0);\n expect(result.total).toBe(0);\n });\n\n it('works with alias model names', () => {\n const result = calculateEntryCost('claude-opus-4-6', 1000, 500, 0, 0);\n expect(result.input).toBeCloseTo(1000 * (15.0 / 1_000_000), 10);\n expect(result.output).toBeCloseTo(500 * (75.0 / 1_000_000), 10);\n });\n });\n\n describe('parsePricingHtml', () => {\n it('returns null for empty HTML', () => {\n expect(parsePricingHtml('')).toBeNull();\n });\n\n it('returns null for HTML with no pricing tables', () => {\n expect(parsePricingHtml('<html><body>no data</body></html>')).toBeNull();\n });\n\n it('extracts pricing from table rows with 2 prices', () => {\n const html = `\n <tr><td>claude-sonnet-4-20250514</td><td>$3.00 / MTok</td><td>$15.00 / MTok</td></tr>\n `;\n const result = parsePricingHtml(html);\n expect(result).not.toBeNull();\n expect(result!.models['claude-sonnet-4-20250514']).toBeDefined();\n expect(result!.models['claude-sonnet-4-20250514'].input_cost_per_million).toBe(3.0);\n expect(result!.models['claude-sonnet-4-20250514'].output_cost_per_million).toBe(15.0);\n });\n\n it('extracts pricing with 4 prices (including cache)', () => {\n const html = `\n <tr><td>claude-opus-4-20250514</td><td>$15.00</td><td>$75.00</td><td>$18.75</td><td>$1.50</td></tr>\n `;\n const result = parsePricingHtml(html);\n expect(result).not.toBeNull();\n const p = result!.models['claude-opus-4-20250514'];\n expect(p.input_cost_per_million).toBe(15.0);\n expect(p.output_cost_per_million).toBe(75.0);\n expect(p.cache_creation_cost_per_million).toBe(18.75);\n expect(p.cache_read_cost_per_million).toBe(1.50);\n });\n\n it('extracts multiple models from HTML', () => {\n const html = `\n <table>\n <tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>\n <tr><td>claude-opus-4-20250514</td><td>$15.00</td><td>$75.00</td></tr>\n </table>\n `;\n const result = parsePricingHtml(html);\n expect(result).not.toBeNull();\n expect(Object.keys(result!.models)).toHaveLength(2);\n });\n\n it('generates aliases for dated model IDs', () => {\n const html = `\n <tr><td>claude-sonnet-4-6-20260217</td><td>$3.00</td><td>$15.00</td></tr>\n `;\n const result = parsePricingHtml(html);\n expect(result).not.toBeNull();\n expect(result!.aliases['claude-sonnet-4-6']).toBe('claude-sonnet-4-6-20260217');\n expect(result!.aliases['claude-sonnet-4-6-latest']).toBe('claude-sonnet-4-6-20260217');\n });\n\n it('skips rows without model IDs', () => {\n const html = `\n <tr><td>Some header</td><td>Input</td><td>Output</td></tr>\n <tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>\n `;\n const result = parsePricingHtml(html);\n expect(result).not.toBeNull();\n expect(Object.keys(result!.models)).toHaveLength(1);\n });\n\n it('skips rows with only 1 price', () => {\n const html = `\n <tr><td>claude-sonnet-4-20250514</td><td>$3.00</td></tr>\n `;\n const result = parsePricingHtml(html);\n expect(result).toBeNull(); // Not enough prices for a valid entry\n });\n\n it('handles invalid JSON-LD gracefully', () => {\n const html = `\n <script type=\"application/ld+json\">not json</script>\n <tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>\n `;\n const result = parsePricingHtml(html);\n expect(result).not.toBeNull();\n });\n\n it('sets version to current date', () => {\n const html = `<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>`;\n const result = parsePricingHtml(html);\n expect(result!.version).toMatch(/^\\d{4}-\\d{2}-\\d{2}$/);\n });\n\n it('defaults context_window to 200000', () => {\n const html = `<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>`;\n const result = parsePricingHtml(html);\n expect(result!.models['claude-sonnet-4-20250514'].context_window).toBe(200000);\n });\n\n it('derives cache costs from input when only 2 prices', () => {\n const html = `<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>`;\n const result = parsePricingHtml(html);\n const p = result!.models['claude-sonnet-4-20250514'];\n expect(p.cache_creation_cost_per_million).toBeCloseTo(3.0 * 1.25, 6); // 1.25x input\n expect(p.cache_read_cost_per_million).toBeCloseTo(3.0 * 0.1, 6); // 0.1x input\n });\n });\n\n describe('getPricingInfo', () => {\n it('returns correct model count', () => {\n const info = getPricingInfo();\n expect(info.modelCount).toBe(3); // testPricing has 3 models\n });\n\n it('returns version', () => {\n const info = getPricingInfo();\n expect(info.version).toBe('test');\n });\n });\n\n describe('getAllPricing', () => {\n it('returns the full pricing data', () => {\n const data = getAllPricing();\n expect(data.models).toBeDefined();\n expect(data.aliases).toBeDefined();\n expect(Object.keys(data.models)).toHaveLength(3);\n });\n });\n\n describe('cache functions', () => {\n it('resetPricing clears in-memory cache', () => {\n setPricingData(testPricing);\n expect(getModelPricing('claude-sonnet-4-20250514')).not.toBeNull();\n\n // Reset and re-inject to verify it was cleared\n resetPricing();\n setPricingData({ version: 'empty', models: {}, aliases: {} });\n expect(getModelPricing('claude-sonnet-4-20250514')).toBeNull();\n\n // Restore for other tests\n setPricingData(testPricing);\n });\n\n it('setPricingData overrides all lookups', () => {\n const custom: PricingData = {\n version: 'custom',\n models: {\n 'my-custom-model': {\n input_cost_per_million: 99.0,\n output_cost_per_million: 199.0,\n cache_creation_cost_per_million: 10.0,\n cache_read_cost_per_million: 1.0,\n context_window: 100000,\n },\n },\n aliases: { 'my-alias': 'my-custom-model' },\n };\n setPricingData(custom);\n\n expect(getModelPricing('my-custom-model')).not.toBeNull();\n expect(getModelPricing('my-custom-model')!.input_cost_per_million).toBe(99.0);\n expect(getModelPricing('my-alias')!.input_cost_per_million).toBe(99.0);\n expect(getModelPricing('claude-sonnet-4-20250514')).toBeNull();\n\n // Restore\n setPricingData(testPricing);\n });\n });\n\n describe('calculateEntryCost edge cases', () => {\n it('calculates with all four token types using tiered pricing', () => {\n // Use opus which has tiered pricing, with tokens above threshold\n const result = calculateEntryCost(\n 'claude-opus-4-20250514',\n 300_000, // above 200k threshold\n 100_000,\n 50_000,\n 10_000,\n );\n // Input: 200k * $15/M + 100k * $30/M = $3 + $3 = $6\n expect(result.input).toBeCloseTo(200_000 * (15 / 1e6) + 100_000 * (30 / 1e6), 6);\n expect(result.output).toBeCloseTo(100_000 * (75 / 1e6), 6);\n expect(result.total).toBeGreaterThan(0);\n });\n\n it('handles zero tokens for all types', () => {\n const result = calculateEntryCost('claude-sonnet-4-20250514', 0, 0, 0, 0);\n expect(result.total).toBe(0);\n expect(result.input).toBe(0);\n expect(result.output).toBe(0);\n });\n });\n}\n","import { Command } from 'commander';\nimport { registerDailyCommand } from './commands/daily.js';\nimport { registerMonthlyCommand } from './commands/monthly.js';\nimport { registerSessionCommand } from './commands/session.js';\nimport { registerDashboardCommand, dashboardAction } from './commands/dashboard.js';\nimport { registerExportCommand } from './commands/export.js';\nimport { registerRoiCommand } from './commands/roi.js';\nimport { registerLiveCommand } from './commands/live.js';\nimport { registerPricingCommand } from './commands/pricing.js';\nimport { registerConfigCommand } from './commands/config.js';\nimport { registerBlocksCommand } from './commands/blocks.js';\nimport { registerStatuslineCommand } from './commands/statusline.js';\nimport { registerLimitsCommand } from './commands/limits.js';\nimport { initPricing } from './core/pricing.js';\n\n// Kick off background pricing refresh (non-blocking)\ninitPricing().catch(() => {});\n\nconst program = new Command();\n\nprogram\n .name('cctrack')\n .description('Claude Code usage analytics — accurate metrics and a beautiful HTML dashboard')\n .version('0.1.0')\n .addHelpText('after', `\nExamples:\n cctrack Open interactive HTML dashboard\n cctrack daily --since YYYY-MM-DD Daily cost breakdown from a date\n cctrack blocks 5-hour rolling window usage\n cctrack roi --plan max20 ROI analysis vs Max 20 plan\n cctrack live Real-time terminal monitor\n cctrack statusline Compact status for tmux/editors\n cctrack config set budget.daily 50 Set daily budget alert at $50\n`);\n\nregisterDailyCommand(program);\nregisterMonthlyCommand(program);\nregisterSessionCommand(program);\nregisterDashboardCommand(program);\nregisterExportCommand(program);\nregisterRoiCommand(program);\nregisterLiveCommand(program);\nregisterPricingCommand(program);\nregisterConfigCommand(program);\nregisterBlocksCommand(program);\nregisterStatuslineCommand(program);\nregisterLimitsCommand(program);\n\n// Default action: run dashboard\nprogram.action(async () => {\n await dashboardAction({});\n});\n\nprogram.parse();\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport Table from 'cli-table3';\nimport type { CostMode, DailyAggregate } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries, aggregateDaily } from '../core/aggregator.js';\nimport { formatCost, formatTokens, parseCostMode } from '../utils/format.js';\nimport { loadBudgetConfig, calculateBudgetStatus, formatBudgetBar } from '../core/budget.js';\nimport { calculateBurnRate } from '../core/burnrate.js';\n\nfunction dailyToCsv(data: DailyAggregate[], breakdown: boolean): string {\n const lines: string[] = [];\n\n if (breakdown) {\n lines.push('date,model,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost');\n for (const day of data) {\n for (const [model, agg] of Object.entries(day.models)) {\n lines.push(\n [\n day.date,\n model,\n agg.tokens.input_tokens,\n agg.tokens.output_tokens,\n agg.tokens.cache_write_tokens,\n agg.tokens.cache_read_tokens,\n agg.tokens.total_tokens,\n agg.cost.total_cost.toFixed(6),\n ].join(','),\n );\n }\n }\n } else {\n lines.push('date,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost');\n for (const day of data) {\n lines.push(\n [\n day.date,\n day.tokens.input_tokens,\n day.tokens.output_tokens,\n day.tokens.cache_write_tokens,\n day.tokens.cache_read_tokens,\n day.tokens.total_tokens,\n day.cost.total_cost.toFixed(6),\n ].join(','),\n );\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction dailyToTable(data: DailyAggregate[], breakdown: boolean): void {\n if (data.length === 0) {\n console.log(chalk.yellow('No data found for the specified range.'));\n return;\n }\n\n if (breakdown) {\n const table = new Table({\n head: ['Date', 'Model', 'Input', 'Output', 'Cache Write', 'Cache Read', 'Total', 'Cost'].map((h) =>\n chalk.cyan(h),\n ),\n colAligns: ['left', 'left', 'right', 'right', 'right', 'right', 'right', 'right'],\n style: { head: [], border: [] },\n });\n\n for (const day of data) {\n for (const [model, agg] of Object.entries(day.models)) {\n table.push([\n day.date,\n model,\n formatTokens(agg.tokens.input_tokens),\n formatTokens(agg.tokens.output_tokens),\n formatTokens(agg.tokens.cache_write_tokens),\n formatTokens(agg.tokens.cache_read_tokens),\n formatTokens(agg.tokens.total_tokens),\n formatCost(agg.cost.total_cost),\n ]);\n }\n }\n\n console.log(table.toString());\n } else {\n const table = new Table({\n head: ['Date', 'Input', 'Output', 'Cache Write', 'Cache Read', 'Total', 'Cost'].map((h) => chalk.cyan(h)),\n colAligns: ['left', 'right', 'right', 'right', 'right', 'right', 'right'],\n style: { head: [], border: [] },\n });\n\n const maxCost = Math.max(...data.map((d) => d.cost.total_cost), 1);\n for (const day of data) {\n const barLen = Math.round((day.cost.total_cost / maxCost) * 8);\n const bar = chalk.dim('\\u2588'.repeat(barLen) + '\\u2591'.repeat(8 - barLen));\n table.push([\n day.date,\n formatTokens(day.tokens.input_tokens),\n formatTokens(day.tokens.output_tokens),\n formatTokens(day.tokens.cache_write_tokens),\n formatTokens(day.tokens.cache_read_tokens),\n formatTokens(day.tokens.total_tokens),\n formatCost(day.cost.total_cost) + ' ' + bar,\n ]);\n }\n\n console.log(table.toString());\n }\n\n // Budget indicator (today only)\n const budgetConfig = loadBudgetConfig();\n if (budgetConfig.daily != null) {\n const today = new Date().toISOString().slice(0, 10);\n const todayEntry = data.find((d) => d.date === today);\n const todayCost = todayEntry?.cost.total_cost ?? 0;\n const status = calculateBudgetStatus(todayCost, budgetConfig.daily);\n console.log();\n console.log(`Daily Budget: ${formatBudgetBar(status.percentage)} (${formatCost(status.spent)} / ${formatCost(status.budget)})`);\n }\n\n // Summary row\n const totalCost = data.reduce((sum, d) => sum + d.cost.total_cost, 0);\n const totalTokens = data.reduce((sum, d) => sum + d.tokens.total_tokens, 0);\n console.log(chalk.dim('─'.repeat(60)));\n console.log(chalk.bold(`Total: ${formatTokens(totalTokens)} tokens, ${formatCost(totalCost)}`));\n}\n\nfunction showBurnRate(entries: import('../core/types.js').UsageEntry[], mode: CostMode): void {\n const rate = calculateBurnRate(entries, mode);\n if (rate.insufficient_data) return;\n console.log(\n chalk.dim(`Burn rate: ${formatCost(rate.hourly_cost)}/hr, ${formatCost(rate.daily_cost)}/day → projected ${formatCost(rate.projected_monthly)}/month`),\n );\n}\n\nexport function registerDailyCommand(program: Command): void {\n program\n .command('daily')\n .description('Show daily usage breakdown')\n .option('--json', 'Output as JSON')\n .option('--csv', 'Output as CSV')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--project <name>', 'Filter by project name')\n .option('--breakdown', 'Show per-model breakdown')\n .option('--mode <mode>', 'Cost mode: calculate|display|compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for date grouping (e.g. America/New_York)')\n .action(async (opts) => {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n timezone: opts.timezone,\n });\n\n const mode = parseCostMode(opts.mode);\n const data = aggregateDaily(filtered, mode, opts.timezone);\n\n if (opts.json) {\n console.log(JSON.stringify(data, null, 2));\n } else if (opts.csv) {\n console.log(dailyToCsv(data, opts.breakdown));\n } else {\n dailyToTable(data, opts.breakdown);\n showBurnRate(filtered, mode);\n }\n });\n}\n","import { readdirSync, statSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\nexport function getProjectDirs(): string[] {\n const dirs: string[] = [];\n\n // Support CLAUDE_CONFIG_DIR env var\n const customDir = process.env.CLAUDE_CONFIG_DIR;\n if (customDir) {\n const projectsDir = join(customDir, 'projects');\n if (existsSync(projectsDir)) dirs.push(projectsDir);\n }\n\n // Default paths\n const home = homedir();\n const defaultPaths = [join(home, '.claude', 'projects'), join(home, '.config', 'claude', 'projects')];\n\n for (const p of defaultPaths) {\n if (existsSync(p)) dirs.push(p);\n }\n\n return [...new Set(dirs)];\n}\n\n/**\n * Build a map of JSONL file path -> project name.\n * The project name comes from the Claude project directory structure:\n * ~/.claude/projects/<encoded-project-dir>/<session-id>/<file>.jsonl\n *\n * The <encoded-project-dir> uses `-` to encode `/` in the absolute path.\n * e.g. `-Users-john-Sites-myproject` -> `myproject`\n *\n * This is the authoritative project identity. The `cwd` field in JSONL entries\n * can differ for subagents (e.g. `tradeforge/backend`) but they all live under\n * the same project directory.\n */\nconst fileProjectMap = new Map<string, string>();\n\nexport function findJsonlFiles(dirs: string[]): string[] {\n fileProjectMap.clear(); // Reset stale mappings from previous calls\n const files: string[] = [];\n\n for (const dir of dirs) {\n try {\n // Each child of the projects dir is an encoded project path\n const projectDirs = readdirSync(dir, { withFileTypes: true });\n for (const pdir of projectDirs) {\n if (!pdir.isDirectory()) continue;\n const projectName = decodeProjectDir(pdir.name);\n const projectPath = join(dir, pdir.name);\n walkCollect(projectPath, files, projectName);\n }\n } catch {\n // Skip dirs we can't read\n }\n }\n\n return files;\n}\n\nfunction walkCollect(dir: string, files: string[], projectName: string): void {\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n walkCollect(fullPath, files, projectName);\n } else if (entry.name.endsWith('.jsonl')) {\n files.push(fullPath);\n fileProjectMap.set(fullPath, projectName);\n }\n }\n } catch {\n // Skip dirs we can't read\n }\n}\n\n/**\n * Decode a Claude project directory name to the real project path.\n * e.g. `-Users-john-Sites-myproject` -> `/Users/john/Sites/myproject`\n *\n * Claude encodes `/` as `-`, but directory names can also contain `-`.\n * So we reconstruct the path by checking which segments exist on disk.\n */\nconst statCache = new Map<string, boolean>();\nfunction dirExists(path: string): boolean {\n if (statCache.has(path)) return statCache.get(path)!;\n try { const r = statSync(path).isDirectory(); statCache.set(path, r); return r; }\n catch { statCache.set(path, false); return false; }\n}\n\nfunction decodeProjectDir(encoded: string): string {\n const segments = encoded.replace(/^-/, '').split('-');\n\n let currentPath = '/';\n let i = 0;\n while (i < segments.length) {\n let matched = false;\n // Try longest possible segment first (greedy match for hyphenated dir names)\n for (let len = segments.length - i; len >= 1; len--) {\n const candidate = segments.slice(i, i + len).join('-');\n // Try the candidate as-is, then with underscores replacing hyphens\n const variants = [candidate, candidate.replace(/-/g, '_')];\n let found = false;\n for (const variant of variants) {\n const testPath = join(currentPath, variant);\n if (dirExists(testPath)) {\n currentPath = testPath;\n i += len;\n matched = true;\n found = true;\n break;\n } else {\n // doesn't exist\n }\n }\n if (found) break;\n }\n if (!matched) {\n // Can't resolve on disk — use last segment as best guess\n // This handles test environments and deleted directories\n return segments[segments.length - 1] || encoded;\n }\n }\n\n // Return just the basename for clean display\n const parts = currentPath.split('/').filter(Boolean);\n return parts[parts.length - 1] || encoded;\n}\n\n/**\n * Get the project name for a JSONL file path.\n * Uses the file -> project mapping built during findJsonlFiles.\n */\nexport function getProjectForFile(filePath: string): string {\n return fileProjectMap.get(filePath) ?? 'unknown';\n}\n\n/**\n * Get the project name for a usage entry.\n * After parsing, cwd is already normalized to the project name\n * (e.g. \"tradeforge\", \"cctrack\", \"astral\").\n */\nexport function extractProjectName(cwd: string): string {\n if (!cwd) return 'unknown';\n return cwd;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect, beforeEach, afterEach } = import.meta.vitest;\n const { mkdirSync, writeFileSync, rmSync } = await import('node:fs');\n const { join: joinPath } = await import('node:path');\n const { tmpdir } = await import('node:os');\n\n const tmpBase = joinPath(tmpdir(), 'cctrack-test-fs');\n\n beforeEach(() => {\n mkdirSync(tmpBase, { recursive: true });\n });\n\n afterEach(() => {\n rmSync(tmpBase, { recursive: true, force: true });\n });\n\n describe('findJsonlFiles', () => {\n it('finds .jsonl files inside project directories', () => {\n // Simulate: tmpBase/projects/-Users-me-myproject/session1/usage.jsonl\n const projDir = joinPath(tmpBase, '-Users-me-myproject', 'session1');\n mkdirSync(projDir, { recursive: true });\n writeFileSync(joinPath(projDir, 'usage.jsonl'), '{}');\n\n const result = findJsonlFiles([tmpBase]);\n expect(result).toHaveLength(1);\n expect(result[0]).toContain('usage.jsonl');\n });\n\n it('maps files to project names via getProjectForFile', () => {\n // Use a dir name that can't resolve on filesystem, so it falls back to last segment\n const projDir = joinPath(tmpBase, '-xtest-zfake-myproject', 'sess');\n mkdirSync(projDir, { recursive: true });\n writeFileSync(joinPath(projDir, 'data.jsonl'), '{}');\n\n const result = findJsonlFiles([tmpBase]);\n expect(result).toHaveLength(1);\n expect(getProjectForFile(result[0])).toBe('myproject');\n });\n\n it('returns empty for empty directory', () => {\n expect(findJsonlFiles([tmpBase])).toHaveLength(0);\n });\n\n it('returns empty for non-existent directory', () => {\n expect(findJsonlFiles([joinPath(tmpBase, 'nope')])).toHaveLength(0);\n });\n });\n\n describe('getProjectDirs', () => {\n it('includes CLAUDE_CONFIG_DIR when set', () => {\n const customDir = joinPath(tmpBase, 'custom');\n mkdirSync(joinPath(customDir, 'projects'), { recursive: true });\n\n const original = process.env.CLAUDE_CONFIG_DIR;\n process.env.CLAUDE_CONFIG_DIR = customDir;\n try {\n const dirs = getProjectDirs();\n expect(dirs.some((d) => d.includes('custom'))).toBe(true);\n } finally {\n if (original !== undefined) process.env.CLAUDE_CONFIG_DIR = original;\n else delete process.env.CLAUDE_CONFIG_DIR;\n }\n });\n\n it('deduplicates paths', () => {\n const dirs = getProjectDirs();\n expect(dirs.length).toBe(new Set(dirs).size);\n });\n });\n\n describe('extractProjectName', () => {\n it('returns cwd as-is (already normalized by parser)', () => {\n expect(extractProjectName('tradeforge')).toBe('tradeforge');\n });\n\n it('returns unknown for empty string', () => {\n expect(extractProjectName('')).toBe('unknown');\n });\n\n it('returns the normalized project name', () => {\n expect(extractProjectName('cctrack')).toBe('cctrack');\n });\n });\n\n describe('decodeProjectDir', () => {\n it('decodes encoded project directory to last component', () => {\n const projDir = joinPath(tmpBase, '-xtest-zfake-tradeforge', 'sess');\n mkdirSync(projDir, { recursive: true });\n writeFileSync(joinPath(projDir, 'test.jsonl'), '{}');\n const files = findJsonlFiles([tmpBase]);\n expect(getProjectForFile(files[0])).toBe('tradeforge');\n });\n });\n}\n","import { createReadStream, appendFileSync, mkdirSync } from 'node:fs';\nimport { createInterface } from 'node:readline';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { UsageEntrySchema, type UsageEntry } from './types.js';\nimport { getProjectForFile } from '../utils/fs.js';\n\nexport interface ParseResult {\n entries: UsageEntry[];\n errors: number;\n skipped: { apiErrors: number; synthetic: number };\n}\n\n/**\n * Parse a single JSONL file, streaming line-by-line.\n * Validates each entry against the Zod schema.\n * Filters out API errors and synthetic model entries.\n * Stamps each entry's cwd with the project name derived from the file path.\n */\nexport async function parseJsonlFile(filePath: string): Promise<ParseResult> {\n const entries: UsageEntry[] = [];\n let errors = 0;\n const skipped = { apiErrors: 0, synthetic: 0 };\n\n // Get project name from the file's location in the project directory tree\n const projectName = getProjectForFile(filePath);\n\n const rl = createInterface({\n input: createReadStream(filePath, 'utf-8'),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n try {\n const raw = JSON.parse(trimmed);\n\n // Skip non-usage entries (user messages, progress, file snapshots, etc.)\n // These are valid JSONL but don't contain token usage data\n if (!raw.message?.usage) continue;\n\n const parsed = UsageEntrySchema.safeParse(raw);\n\n if (!parsed.success) {\n errors++; // Actual schema validation failure on a usage entry\n continue;\n }\n\n const entry = parsed.data;\n\n // Filter API error entries — but save rate limit events for analysis\n if (entry.isApiErrorMessage === true) {\n skipped.apiErrors++;\n // Save rate limit events to a separate file for predictive modeling\n try {\n const content = entry.message.content;\n const hasRateLimit = content?.some((c: { text?: string }) => c.text?.includes('rate limit') || c.text?.includes('hit your limit'));\n if (hasRateLimit) {\n const dir = join(homedir(), '.cctrack');\n mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, 'rate-events.jsonl'),\n JSON.stringify({ timestamp: entry.timestamp, model: entry.message.model, content: content?.map((c: { text?: string }) => c.text).join(' ') }) + '\\n');\n }\n } catch {}\n continue;\n }\n\n // Filter synthetic model entries\n if (entry.message.model === '<synthetic>') {\n skipped.synthetic++;\n continue;\n }\n\n // Override cwd with the authoritative project name from file path\n // This ensures subagent entries (cwd=/tradeforge/backend) are grouped\n // under the parent project (tradeforge), not treated as separate projects\n if (projectName !== 'unknown') {\n entry.cwd = projectName;\n }\n\n entries.push(entry);\n } catch {\n errors++;\n }\n }\n\n return { entries, errors, skipped };\n}\n\n/**\n * Parse multiple JSONL files and combine results.\n */\nexport async function parseAllFiles(filePaths: string[]): Promise<ParseResult> {\n const combined: ParseResult = {\n entries: [],\n errors: 0,\n skipped: { apiErrors: 0, synthetic: 0 },\n };\n\n const BATCH_SIZE = 20;\n for (let i = 0; i < filePaths.length; i += BATCH_SIZE) {\n const batch = filePaths.slice(i, i + BATCH_SIZE);\n const results = await Promise.all(batch.map(parseJsonlFile));\n for (const result of results) {\n // Use a loop instead of push(...spread) to avoid stack overflow with large arrays\n for (const entry of result.entries) {\n combined.entries.push(entry);\n }\n combined.errors += result.errors;\n combined.skipped.apiErrors += result.skipped.apiErrors;\n combined.skipped.synthetic += result.skipped.synthetic;\n }\n }\n\n return combined;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect, afterAll } = import.meta.vitest;\n const { writeFileSync, unlinkSync, mkdirSync, rmSync } = await import('node:fs');\n const { join } = await import('node:path');\n const { tmpdir } = await import('node:os');\n\n const tmpDir = join(tmpdir(), 'cctrack-test-parser');\n afterAll(() => { try { rmSync(tmpDir, { recursive: true, force: true }); } catch {} });\n\n function writeTempJsonl(name: string, lines: string[]): string {\n mkdirSync(tmpDir, { recursive: true });\n const path = join(tmpDir, name);\n writeFileSync(path, lines.join('\\n'));\n return path;\n }\n\n const validLine = JSON.stringify({\n timestamp: '2025-03-25T10:00:00Z',\n message: {\n id: 'msg_1',\n model: 'claude-sonnet-4-20250514',\n usage: { input_tokens: 100, output_tokens: 50 },\n },\n requestId: 'req_1',\n });\n\n const apiErrorLine = JSON.stringify({\n timestamp: '2025-03-25T10:00:00Z',\n message: {\n model: 'claude-sonnet-4-20250514',\n usage: { input_tokens: 0, output_tokens: 0 },\n },\n isApiErrorMessage: true,\n });\n\n const syntheticLine = JSON.stringify({\n timestamp: '2025-03-25T10:00:00Z',\n message: {\n model: '<synthetic>',\n usage: { input_tokens: 50, output_tokens: 20 },\n },\n });\n\n describe('parseJsonlFile', () => {\n it('parses valid entries', async () => {\n const path = writeTempJsonl('valid.jsonl', [validLine]);\n const result = await parseJsonlFile(path);\n expect(result.entries).toHaveLength(1);\n expect(result.entries[0].message.usage.input_tokens).toBe(100);\n expect(result.errors).toBe(0);\n unlinkSync(path);\n });\n\n it('filters API error entries', async () => {\n const path = writeTempJsonl('api-err.jsonl', [validLine, apiErrorLine]);\n const result = await parseJsonlFile(path);\n expect(result.entries).toHaveLength(1);\n expect(result.skipped.apiErrors).toBe(1);\n unlinkSync(path);\n });\n\n it('filters synthetic model entries', async () => {\n const path = writeTempJsonl('synthetic.jsonl', [validLine, syntheticLine]);\n const result = await parseJsonlFile(path);\n expect(result.entries).toHaveLength(1);\n expect(result.skipped.synthetic).toBe(1);\n unlinkSync(path);\n });\n\n it('counts invalid JSON as errors', async () => {\n const path = writeTempJsonl('invalid.jsonl', [validLine, 'not json at all', '{\"broken']);\n const result = await parseJsonlFile(path);\n expect(result.entries).toHaveLength(1);\n expect(result.errors).toBe(2);\n unlinkSync(path);\n });\n\n it('counts schema validation failures as errors', async () => {\n // Has message.usage (so it's not skipped) but timestamp is invalid\n const badSchema = JSON.stringify({ timestamp: 'not-a-date', message: { usage: { input_tokens: 1, output_tokens: 1 } } });\n const path = writeTempJsonl('bad-schema.jsonl', [validLine, badSchema]);\n const result = await parseJsonlFile(path);\n expect(result.entries).toHaveLength(1);\n expect(result.errors).toBe(1);\n unlinkSync(path);\n });\n\n it('defaults cache tokens to 0', async () => {\n const path = writeTempJsonl('no-cache.jsonl', [validLine]);\n const result = await parseJsonlFile(path);\n expect(result.entries[0].message.usage.cache_creation_input_tokens).toBe(0);\n expect(result.entries[0].message.usage.cache_read_input_tokens).toBe(0);\n unlinkSync(path);\n });\n\n it('handles empty files', async () => {\n const path = writeTempJsonl('empty.jsonl', ['']);\n const result = await parseJsonlFile(path);\n expect(result.entries).toHaveLength(0);\n unlinkSync(path);\n });\n });\n\n describe('parseAllFiles', () => {\n it('combines entries from multiple files', async () => {\n const path1 = writeTempJsonl('multi1.jsonl', [validLine]);\n const path2 = writeTempJsonl('multi2.jsonl', [validLine]);\n const result = await parseAllFiles([path1, path2]);\n expect(result.entries).toHaveLength(2);\n expect(result.errors).toBe(0);\n unlinkSync(path1);\n unlinkSync(path2);\n });\n\n it('combines errors and skipped counts', async () => {\n const path1 = writeTempJsonl('comb1.jsonl', [validLine, apiErrorLine]);\n const path2 = writeTempJsonl('comb2.jsonl', [syntheticLine, 'bad json']);\n const result = await parseAllFiles([path1, path2]);\n expect(result.entries).toHaveLength(1);\n expect(result.skipped.apiErrors).toBe(1);\n expect(result.skipped.synthetic).toBe(1);\n expect(result.errors).toBe(1);\n unlinkSync(path1);\n unlinkSync(path2);\n });\n\n it('handles empty file list', async () => {\n const result = await parseAllFiles([]);\n expect(result.entries).toHaveLength(0);\n expect(result.errors).toBe(0);\n });\n\n it('handles more than BATCH_SIZE (20) files', async () => {\n const paths: string[] = [];\n for (let i = 0; i < 25; i++) {\n paths.push(writeTempJsonl(`batch-${i}.jsonl`, [validLine]));\n }\n const result = await parseAllFiles(paths);\n expect(result.entries).toHaveLength(25);\n expect(result.errors).toBe(0);\n for (const p of paths) unlinkSync(p);\n });\n });\n}\n","import { z } from 'zod/v4';\n\n// === JSONL Entry Schema ===\n\nexport const UsageEntrySchema = z.object({\n timestamp: z.string().datetime(),\n sessionId: z.string().optional(),\n version: z.string().optional(),\n cwd: z.string().optional(),\n message: z.object({\n id: z.string().optional(),\n model: z.string().optional(),\n usage: z.object({\n input_tokens: z.number(),\n output_tokens: z.number(),\n cache_creation_input_tokens: z.number().optional().default(0),\n cache_read_input_tokens: z.number().optional().default(0),\n speed: z.enum(['standard', 'fast']).optional(),\n }),\n content: z.array(z.object({ text: z.string().optional() })).optional(),\n }),\n costUSD: z.number().optional(),\n requestId: z.string().optional(),\n isApiErrorMessage: z.boolean().optional(),\n});\n\nexport type UsageEntry = z.infer<typeof UsageEntrySchema>;\n\n// === Pricing Types ===\n\nexport interface ModelPricing {\n input_cost_per_million: number;\n output_cost_per_million: number;\n cache_creation_cost_per_million: number;\n cache_read_cost_per_million: number;\n input_cost_per_million_above_200k?: number;\n output_cost_per_million_above_200k?: number;\n cache_creation_cost_per_million_above_200k?: number;\n cache_read_cost_per_million_above_200k?: number;\n context_window: number;\n max_output?: number;\n}\n\nexport interface PricingData {\n version: string;\n models: Record<string, ModelPricing>;\n aliases: Record<string, string>;\n}\n\n// === Aggregation Types ===\n\nexport interface TokenBreakdown {\n input_tokens: number;\n output_tokens: number;\n cache_write_tokens: number;\n cache_read_tokens: number;\n total_tokens: number;\n}\n\nexport interface CostBreakdown {\n input_cost: number;\n output_cost: number;\n cache_write_cost: number;\n cache_read_cost: number;\n total_cost: number;\n}\n\nexport interface AggregatedEntry {\n tokens: TokenBreakdown;\n cost: CostBreakdown;\n request_count: number;\n}\n\nexport interface DailyAggregate extends AggregatedEntry {\n date: string; // YYYY-MM-DD\n models: Record<string, AggregatedEntry>;\n projects: Record<string, AggregatedEntry>;\n}\n\nexport interface MonthlyAggregate extends AggregatedEntry {\n month: string; // YYYY-MM\n models: Record<string, AggregatedEntry>;\n}\n\nexport interface SessionAggregate extends AggregatedEntry {\n sessionId: string;\n project: string;\n startTime: string;\n endTime: string;\n primaryModel: string;\n models: Record<string, AggregatedEntry>;\n}\n\nexport interface ProjectAggregate extends AggregatedEntry {\n project: string;\n models: Record<string, AggregatedEntry>;\n}\n\n// === CLI Types ===\n\nexport type CostMode = 'calculate' | 'display' | 'compare';\n// OutputFormat removed — unused\nexport type SubscriptionPlan = 'pro' | 'max5' | 'max20';\n\nexport const PLAN_COSTS: Record<SubscriptionPlan, number> = {\n pro: 20,\n max5: 100,\n max20: 200,\n};\n\nexport interface FilterOptions {\n since?: string;\n until?: string;\n project?: string;\n timezone?: string;\n}\n\n// === Block Types (5-hour rolling windows) ===\n\nexport const BLOCK_DURATION_MS = 5 * 60 * 60 * 1000; // 5 hours in ms\n\nexport interface BlockAggregate extends AggregatedEntry {\n block_start: string;\n block_end: string;\n block_index: number;\n is_current: boolean;\n time_remaining_ms: number;\n models: Record<string, AggregatedEntry>;\n}\n\n// === Burn Rate Types ===\n\nexport interface BurnRate {\n hourly_cost: number;\n daily_cost: number;\n projected_monthly: number;\n hours_analyzed: number;\n insufficient_data: boolean; // true when span < 1 hour (projections unreliable)\n time_until_budget_exhausted_ms?: number; // undefined if no budget set\n}\n\n// === Budget Types ===\n\nexport type BudgetLevel = 'safe' | 'warning' | 'critical' | 'exceeded';\n\nexport interface BudgetConfig {\n daily?: number;\n monthly?: number;\n block?: number; // per 5-hour block\n}\n\nexport interface BudgetStatus {\n level: BudgetLevel;\n budget: number;\n spent: number;\n remaining: number;\n percentage: number;\n}\n\nexport const BUDGET_THRESHOLDS: Record<BudgetLevel, number> = {\n safe: 0,\n warning: 50,\n critical: 80,\n exceeded: 100,\n};\n\n// === Rate Limit Types ===\n\nexport interface RateLimitWindow {\n used_percentage: number; // 0-100 from Anthropic\n resets_at: number; // Unix timestamp\n}\n\nexport interface ExtraUsage {\n is_enabled: boolean;\n spent: number; // dollars spent\n limit: number; // dollar limit\n utilization: number; // 0-100\n resets_at: number;\n}\n\nexport interface RateLimitData {\n five_hour?: RateLimitWindow;\n seven_day?: RateLimitWindow;\n seven_day_sonnet?: RateLimitWindow;\n seven_day_opus?: RateLimitWindow;\n extra_usage?: ExtraUsage;\n source: 'statusline' | 'oauth' | 'estimated';\n captured_at: string;\n}\n\n// === Statusline Types ===\n\nexport interface StatuslineData {\n today_cost: number;\n session_cost: number;\n model: string;\n total_tokens: number;\n block_percentage: number;\n block_remaining: string;\n budget_level: BudgetLevel;\n rate_limits?: RateLimitData;\n updated_at: string;\n}\n\n// === Dashboard Types ===\n\nexport interface DashboardData {\n generated_at: string;\n date_range: { start: string; end: string };\n totals: AggregatedEntry;\n daily: DailyAggregate[];\n monthly: MonthlyAggregate[];\n sessions: SessionAggregate[];\n projects: ProjectAggregate[];\n models: Record<string, AggregatedEntry>;\n heatmap: number[][]; // 7×24 grid: day_of_week × hour_of_day\n project_heatmaps?: Record<string, number[][]>;\n burn_rate?: BurnRate;\n blocks?: BlockAggregate[];\n budget?: BudgetStatus;\n}\n","import { createHash } from 'node:crypto';\nimport type { UsageEntry } from './types.js';\n\n/**\n * Create a dedup key for a usage entry.\n * Priority: requestId > message.id > content hash\n * Every entry gets a key — no silent null-returns.\n */\nexport function createDedupKey(entry: UsageEntry): string {\n // Primary: requestId (globally unique per API call)\n if (entry.requestId) return `req:${entry.requestId}`;\n\n // Secondary: messageId (unique per message)\n if (entry.message.id) return `msg:${entry.message.id}`;\n\n // Tertiary: content hash for entries without IDs\n const hash = createHash('sha256')\n .update(\n `${entry.timestamp}|${entry.message.model}|${entry.message.usage.input_tokens}|${entry.message.usage.output_tokens}`,\n )\n .digest('hex')\n .slice(0, 16);\n return `hash:${hash}`;\n}\n\n/**\n * Deduplicate an array of usage entries.\n * Returns unique entries preserving insertion order.\n */\nexport function deduplicateEntries(entries: UsageEntry[]): UsageEntry[] {\n const seen = new Set<string>();\n const result: UsageEntry[] = [];\n\n for (const entry of entries) {\n const key = createDedupKey(entry);\n if (!seen.has(key)) {\n seen.add(key);\n result.push(entry);\n }\n }\n\n return result;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect } = import.meta.vitest;\n\n const { makeEntry } = await import('./test-helpers.js');\n\n describe('createDedupKey', () => {\n it('uses requestId when available (highest priority)', () => {\n const entry = makeEntry({ requestId: 'req_123', message: { ...makeEntry().message, id: 'msg_456' } });\n expect(createDedupKey(entry)).toBe('req:req_123');\n });\n\n it('falls back to message.id when no requestId', () => {\n const entry = makeEntry({ message: { ...makeEntry().message, id: 'msg_456' } });\n expect(createDedupKey(entry)).toBe('msg:msg_456');\n });\n\n it('falls back to hash when no requestId or message.id', () => {\n const entry = makeEntry();\n const key = createDedupKey(entry);\n expect(key).toMatch(/^hash:[a-f0-9]{16}$/);\n });\n\n it('produces same hash for identical entries', () => {\n const a = makeEntry();\n const b = makeEntry();\n expect(createDedupKey(a)).toBe(createDedupKey(b));\n });\n\n it('produces different hash for different token counts', () => {\n const a = makeEntry();\n const b = makeEntry({ message: { ...makeEntry().message, usage: { ...makeEntry().message.usage, input_tokens: 999 } } });\n expect(createDedupKey(a)).not.toBe(createDedupKey(b));\n });\n\n it('treats empty-string requestId as falsy (falls through to msg or hash)', () => {\n const entry = makeEntry({ requestId: '' });\n const key = createDedupKey(entry);\n expect(key).not.toBe('req:');\n expect(key).toMatch(/^(msg:|hash:)/);\n });\n });\n\n describe('deduplicateEntries', () => {\n it('removes duplicates by requestId', () => {\n const entries = [makeEntry({ requestId: 'r1' }), makeEntry({ requestId: 'r1' }), makeEntry({ requestId: 'r2' })];\n expect(deduplicateEntries(entries)).toHaveLength(2);\n });\n\n it('removes duplicates by message.id', () => {\n const entries = [\n makeEntry({ message: { ...makeEntry().message, id: 'm1' } }),\n makeEntry({ message: { ...makeEntry().message, id: 'm1' } }),\n ];\n expect(deduplicateEntries(entries)).toHaveLength(1);\n });\n\n it('removes duplicates by hash fallback', () => {\n const entries = [makeEntry(), makeEntry()];\n expect(deduplicateEntries(entries)).toHaveLength(1);\n });\n\n it('preserves insertion order', () => {\n const entries = [makeEntry({ requestId: 'r1' }), makeEntry({ requestId: 'r2' }), makeEntry({ requestId: 'r1' })];\n const result = deduplicateEntries(entries);\n expect(result[0].requestId).toBe('r1');\n expect(result[1].requestId).toBe('r2');\n });\n\n it('handles cross-file dedup (same requestId different entries)', () => {\n // Simulates entries from different JSONL files with same requestId\n const a = makeEntry({ requestId: 'r1', cwd: '/project-a' });\n const b = makeEntry({ requestId: 'r1', cwd: '/project-b' });\n expect(deduplicateEntries([a, b])).toHaveLength(1);\n });\n\n it('returns empty array for empty input', () => {\n expect(deduplicateEntries([])).toHaveLength(0);\n });\n });\n}\n","import type { UsageEntry, CostBreakdown, TokenBreakdown, CostMode } from './types.js';\nimport { calculateEntryCost } from './pricing.js';\n\nexport interface EntryResult {\n tokens: TokenBreakdown;\n cost: CostBreakdown;\n calculatedCost: CostBreakdown;\n displayCost: number | undefined;\n}\n\n/**\n * Process a single entry: extract token breakdown and calculate costs.\n */\nexport function processEntry(entry: UsageEntry, mode: CostMode = 'calculate'): EntryResult {\n const usage = entry.message.usage;\n const model = entry.message.model ?? 'unknown';\n\n const tokens: TokenBreakdown = {\n input_tokens: usage.input_tokens,\n output_tokens: usage.output_tokens,\n cache_write_tokens: usage.cache_creation_input_tokens ?? 0,\n cache_read_tokens: usage.cache_read_input_tokens ?? 0,\n total_tokens:\n usage.input_tokens +\n usage.output_tokens +\n (usage.cache_creation_input_tokens ?? 0) +\n (usage.cache_read_input_tokens ?? 0),\n };\n\n // Always calculate for compare mode\n const calc = calculateEntryCost(\n model,\n tokens.input_tokens,\n tokens.output_tokens,\n tokens.cache_write_tokens,\n tokens.cache_read_tokens,\n entry.costUSD,\n );\n\n const calculatedCost: CostBreakdown = {\n input_cost: calc.input,\n output_cost: calc.output,\n cache_write_cost: calc.cacheWrite,\n cache_read_cost: calc.cacheRead,\n total_cost: calc.total,\n };\n\n let cost: CostBreakdown;\n\n switch (mode) {\n case 'display':\n cost = {\n input_cost: 0,\n output_cost: 0,\n cache_write_cost: 0,\n cache_read_cost: 0,\n total_cost: entry.costUSD ?? 0,\n };\n break;\n\n case 'compare':\n case 'calculate':\n default:\n cost = calculatedCost;\n break;\n }\n\n return {\n tokens,\n cost,\n calculatedCost,\n displayCost: entry.costUSD,\n };\n}\n\n/**\n * Create an empty token breakdown.\n */\nexport function emptyTokens(): TokenBreakdown {\n return { input_tokens: 0, output_tokens: 0, cache_write_tokens: 0, cache_read_tokens: 0, total_tokens: 0 };\n}\n\n/**\n * Create an empty cost breakdown.\n */\nexport function emptyCost(): CostBreakdown {\n return { input_cost: 0, output_cost: 0, cache_write_cost: 0, cache_read_cost: 0, total_cost: 0 };\n}\n\n/**\n * Accumulate tokens into a running total.\n */\nexport function addTokens(a: TokenBreakdown, b: TokenBreakdown): TokenBreakdown {\n return {\n input_tokens: a.input_tokens + b.input_tokens,\n output_tokens: a.output_tokens + b.output_tokens,\n cache_write_tokens: a.cache_write_tokens + b.cache_write_tokens,\n cache_read_tokens: a.cache_read_tokens + b.cache_read_tokens,\n total_tokens: a.total_tokens + b.total_tokens,\n };\n}\n\n/**\n * Accumulate costs into a running total.\n */\nexport function addCosts(a: CostBreakdown, b: CostBreakdown): CostBreakdown {\n return {\n input_cost: a.input_cost + b.input_cost,\n output_cost: a.output_cost + b.output_cost,\n cache_write_cost: a.cache_write_cost + b.cache_write_cost,\n cache_read_cost: a.cache_read_cost + b.cache_read_cost,\n total_cost: a.total_cost + b.total_cost,\n };\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect, beforeEach } = import.meta.vitest;\n const { setPricingData } = await import('./pricing.js');\n\n const testPricing = {\n version: 'test',\n models: {\n 'claude-sonnet-4-20250514': {\n input_cost_per_million: 3.0,\n output_cost_per_million: 15.0,\n cache_creation_cost_per_million: 3.75,\n cache_read_cost_per_million: 0.30,\n context_window: 200000,\n },\n },\n aliases: {},\n };\n\n beforeEach(() => {\n setPricingData(testPricing);\n });\n\n const makeEntry = (overrides: Partial<UsageEntry> = {}): UsageEntry => ({\n timestamp: '2025-03-25T10:00:00Z',\n message: {\n model: 'claude-sonnet-4-20250514',\n usage: {\n input_tokens: 1000,\n output_tokens: 500,\n cache_creation_input_tokens: 200,\n cache_read_input_tokens: 300,\n },\n },\n ...overrides,\n });\n\n describe('processEntry', () => {\n it('extracts correct token breakdown', () => {\n const result = processEntry(makeEntry());\n expect(result.tokens.input_tokens).toBe(1000);\n expect(result.tokens.output_tokens).toBe(500);\n expect(result.tokens.cache_write_tokens).toBe(200);\n expect(result.tokens.cache_read_tokens).toBe(300);\n expect(result.tokens.total_tokens).toBe(2000);\n });\n\n it('calculates cost in calculate mode', () => {\n const result = processEntry(makeEntry(), 'calculate');\n expect(result.cost.input_cost).toBeCloseTo(1000 * (3.0 / 1_000_000), 10);\n expect(result.cost.output_cost).toBeCloseTo(500 * (15.0 / 1_000_000), 10);\n expect(result.cost.cache_write_cost).toBeCloseTo(200 * (3.75 / 1_000_000), 10);\n expect(result.cost.cache_read_cost).toBeCloseTo(300 * (0.30 / 1_000_000), 10);\n });\n\n it('uses embedded cost in display mode', () => {\n const result = processEntry(makeEntry({ costUSD: 0.42 }), 'display');\n expect(result.cost.total_cost).toBe(0.42);\n expect(result.cost.input_cost).toBe(0);\n });\n\n it('uses 0 in display mode when no embedded cost', () => {\n const result = processEntry(makeEntry(), 'display');\n expect(result.cost.total_cost).toBe(0);\n });\n\n it('provides both calculated and display costs in compare mode data', () => {\n const entry = makeEntry({ costUSD: 0.42 });\n const result = processEntry(entry, 'compare');\n expect(result.calculatedCost.total_cost).toBeGreaterThan(0);\n expect(result.displayCost).toBe(0.42);\n });\n });\n\n describe('addTokens', () => {\n it('sums token breakdowns', () => {\n const a = { input_tokens: 10, output_tokens: 5, cache_write_tokens: 2, cache_read_tokens: 3, total_tokens: 20 };\n const b = { input_tokens: 20, output_tokens: 10, cache_write_tokens: 4, cache_read_tokens: 6, total_tokens: 40 };\n const result = addTokens(a, b);\n expect(result.input_tokens).toBe(30);\n expect(result.output_tokens).toBe(15);\n expect(result.total_tokens).toBe(60);\n });\n });\n\n describe('addCosts', () => {\n it('sums cost breakdowns', () => {\n const a = { input_cost: 1, output_cost: 2, cache_write_cost: 0.5, cache_read_cost: 0.1, total_cost: 3.6 };\n const b = { input_cost: 3, output_cost: 4, cache_write_cost: 1.5, cache_read_cost: 0.2, total_cost: 8.7 };\n const result = addCosts(a, b);\n expect(result.input_cost).toBe(4);\n expect(result.total_cost).toBeCloseTo(12.3, 6);\n });\n });\n\n describe('processEntry with missing model', () => {\n it('returns zero cost when model is undefined', () => {\n const entry = makeEntry({\n message: {\n usage: { input_tokens: 100, output_tokens: 50, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },\n },\n });\n const result = processEntry(entry);\n expect(result.cost.total_cost).toBe(0);\n expect(result.tokens.input_tokens).toBe(100);\n });\n });\n\n describe('emptyTokens and emptyCost', () => {\n it('emptyTokens returns all zeros', () => {\n const t = emptyTokens();\n expect(t.input_tokens).toBe(0);\n expect(t.output_tokens).toBe(0);\n expect(t.cache_write_tokens).toBe(0);\n expect(t.cache_read_tokens).toBe(0);\n expect(t.total_tokens).toBe(0);\n });\n\n it('emptyCost returns all zeros', () => {\n const c = emptyCost();\n expect(c.input_cost).toBe(0);\n expect(c.output_cost).toBe(0);\n expect(c.cache_write_cost).toBe(0);\n expect(c.cache_read_cost).toBe(0);\n expect(c.total_cost).toBe(0);\n });\n });\n}\n","export function toDateString(timestamp: string, timezone?: string): string {\n const date = new Date(timestamp);\n if (timezone) {\n return date.toLocaleDateString('en-CA', { timeZone: timezone }); // en-CA gives YYYY-MM-DD\n }\n return date.toISOString().slice(0, 10);\n}\n\nexport function toMonthString(timestamp: string, timezone?: string): string {\n return toDateString(timestamp, timezone).slice(0, 7);\n}\n\nexport function getHourAndDay(timestamp: string, timezone?: string): { hour: number; day: number } {\n const date = new Date(timestamp);\n if (timezone) {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n hour: 'numeric',\n hour12: false,\n weekday: 'short',\n }).formatToParts(date);\n const hourPart = parts.find((p) => p.type === 'hour');\n const dayPart = parts.find((p) => p.type === 'weekday');\n const dayMap: Record<string, number> = {\n Sun: 0,\n Mon: 1,\n Tue: 2,\n Wed: 3,\n Thu: 4,\n Fri: 5,\n Sat: 6,\n };\n return {\n hour: parseInt(hourPart?.value ?? '0', 10),\n day: dayMap[dayPart?.value ?? 'Sun'] ?? 0,\n };\n }\n return { hour: date.getUTCHours(), day: date.getUTCDay() };\n}\n\nexport function isInRange(timestamp: string, since?: string, until?: string): boolean {\n const date = timestamp.slice(0, 10);\n if (since && date < since) return false;\n if (until && date > until) return false;\n return true;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect } = import.meta.vitest;\n\n describe('toDateString', () => {\n it('returns YYYY-MM-DD for UTC', () => {\n expect(toDateString('2025-03-25T10:00:00Z')).toBe('2025-03-25');\n });\n\n it('respects timezone', () => {\n // 23:00 UTC on Mar 25 = Mar 26 in IST (UTC+5:30)\n expect(toDateString('2025-03-25T23:00:00Z', 'Asia/Kolkata')).toBe('2025-03-26');\n });\n\n it('handles midnight boundary', () => {\n expect(toDateString('2025-03-25T00:00:00Z')).toBe('2025-03-25');\n });\n });\n\n describe('toMonthString', () => {\n it('returns YYYY-MM', () => {\n expect(toMonthString('2025-03-25T10:00:00Z')).toBe('2025-03');\n });\n\n it('respects timezone for month boundary', () => {\n // Mar 31 23:00 UTC = Apr 1 in IST\n expect(toMonthString('2025-03-31T23:00:00Z', 'Asia/Kolkata')).toBe('2025-04');\n });\n });\n\n describe('getHourAndDay', () => {\n it('returns UTC hour and day by default', () => {\n // 2025-03-25 is Tuesday (day=2)\n const result = getHourAndDay('2025-03-25T14:30:00Z');\n expect(result.hour).toBe(14);\n expect(result.day).toBe(2);\n });\n\n it('respects timezone', () => {\n // 14:00 UTC = 19:30 IST, still Tuesday\n const result = getHourAndDay('2025-03-25T14:00:00Z', 'Asia/Kolkata');\n expect(result.hour).toBe(19);\n expect(result.day).toBe(2);\n });\n\n it('handles day rollover with timezone', () => {\n // 20:00 UTC on Tuesday = 01:30 Wed IST\n const result = getHourAndDay('2025-03-25T20:00:00Z', 'Asia/Kolkata');\n expect(result.hour).toBe(1);\n expect(result.day).toBe(3); // Wednesday\n });\n\n it('handles Sunday correctly', () => {\n // 2025-03-23 is a Sunday\n const result = getHourAndDay('2025-03-23T10:00:00Z');\n expect(result.day).toBe(0);\n });\n });\n\n describe('isInRange', () => {\n it('returns true when no filters', () => {\n expect(isInRange('2025-03-25T10:00:00Z')).toBe(true);\n });\n\n it('filters by since', () => {\n expect(isInRange('2025-03-24T10:00:00Z', '2025-03-25')).toBe(false);\n expect(isInRange('2025-03-25T10:00:00Z', '2025-03-25')).toBe(true);\n expect(isInRange('2025-03-26T10:00:00Z', '2025-03-25')).toBe(true);\n });\n\n it('filters by until', () => {\n expect(isInRange('2025-03-26T10:00:00Z', undefined, '2025-03-25')).toBe(false);\n expect(isInRange('2025-03-25T10:00:00Z', undefined, '2025-03-25')).toBe(true);\n });\n\n it('filters by both since and until', () => {\n expect(isInRange('2025-03-25T10:00:00Z', '2025-03-25', '2025-03-25')).toBe(true);\n expect(isInRange('2025-03-24T10:00:00Z', '2025-03-25', '2025-03-26')).toBe(false);\n expect(isInRange('2025-03-27T10:00:00Z', '2025-03-25', '2025-03-26')).toBe(false);\n });\n\n it('includes entry at 23:59 on the same day', () => {\n expect(isInRange('2025-03-25T23:59:59Z', '2025-03-25', '2025-03-25')).toBe(true);\n });\n\n it('returns false when since > until (impossible range)', () => {\n expect(isInRange('2025-03-25T10:00:00Z', '2025-03-26', '2025-03-24')).toBe(false);\n });\n });\n}\n","import type {\n UsageEntry,\n CostMode,\n FilterOptions,\n DailyAggregate,\n MonthlyAggregate,\n SessionAggregate,\n ProjectAggregate,\n AggregatedEntry,\n DashboardData,\n} from './types.js';\nimport { processEntry, emptyTokens, emptyCost, addTokens, addCosts } from './calculator.js';\nimport { toDateString, toMonthString, getHourAndDay, isInRange } from '../utils/date.js';\nimport { extractProjectName } from '../utils/fs.js';\n\nexport function emptyAggregate(): AggregatedEntry {\n return { tokens: emptyTokens(), cost: emptyCost(), request_count: 0 };\n}\n\nexport function accumulate(agg: AggregatedEntry, result: ReturnType<typeof processEntry>): void {\n agg.tokens = addTokens(agg.tokens, result.tokens);\n agg.cost = addCosts(agg.cost, result.cost);\n agg.request_count++;\n}\n\n/**\n * Filter entries by date range and project.\n */\nexport function filterEntries(entries: UsageEntry[], options: FilterOptions): UsageEntry[] {\n return entries.filter((e) => {\n if (!isInRange(e.timestamp, options.since, options.until)) return false;\n if (options.project) {\n if (!e.cwd) return false;\n const project = extractProjectName(e.cwd);\n if (!project.toLowerCase().includes(options.project.toLowerCase())) return false;\n }\n return true;\n });\n}\n\n/**\n * Aggregate entries by day.\n */\nexport function aggregateDaily(\n entries: UsageEntry[],\n mode: CostMode = 'calculate',\n timezone?: string,\n): DailyAggregate[] {\n const map = new Map<string, DailyAggregate>();\n\n for (const entry of entries) {\n const date = toDateString(entry.timestamp, timezone);\n const model = entry.message.model ?? 'unknown';\n const project = entry.cwd ? extractProjectName(entry.cwd) : 'unknown';\n\n if (!map.has(date)) {\n map.set(date, { date, ...emptyAggregate(), models: {}, projects: {} });\n }\n const day = map.get(date)!;\n const result = processEntry(entry, mode);\n\n accumulate(day, result);\n\n if (!day.models[model]) day.models[model] = emptyAggregate();\n accumulate(day.models[model], result);\n\n if (!day.projects[project]) day.projects[project] = emptyAggregate();\n accumulate(day.projects[project], result);\n }\n\n return [...map.values()].sort((a, b) => a.date.localeCompare(b.date));\n}\n\n/**\n * Aggregate entries by month.\n */\nexport function aggregateMonthly(\n entries: UsageEntry[],\n mode: CostMode = 'calculate',\n timezone?: string,\n): MonthlyAggregate[] {\n const map = new Map<string, MonthlyAggregate>();\n\n for (const entry of entries) {\n const month = toMonthString(entry.timestamp, timezone);\n const model = entry.message.model ?? 'unknown';\n\n if (!map.has(month)) {\n map.set(month, { month, ...emptyAggregate(), models: {} });\n }\n const m = map.get(month)!;\n const result = processEntry(entry, mode);\n\n accumulate(m, result);\n\n if (!m.models[model]) m.models[model] = emptyAggregate();\n accumulate(m.models[model], result);\n }\n\n return [...map.values()].sort((a, b) => a.month.localeCompare(b.month));\n}\n\n/**\n * Aggregate entries by session.\n */\nexport function aggregateSessions(\n entries: UsageEntry[],\n mode: CostMode = 'calculate',\n): SessionAggregate[] {\n const map = new Map<string, SessionAggregate>();\n\n for (const entry of entries) {\n const sid = entry.sessionId ?? 'unknown';\n const model = entry.message.model ?? 'unknown';\n const project = entry.cwd ? extractProjectName(entry.cwd) : 'unknown';\n\n if (!map.has(sid)) {\n map.set(sid, {\n sessionId: sid,\n project,\n startTime: entry.timestamp,\n endTime: entry.timestamp,\n primaryModel: model,\n ...emptyAggregate(),\n models: {},\n });\n }\n const session = map.get(sid)!;\n const result = processEntry(entry, mode);\n\n accumulate(session, result);\n\n // Update time range\n if (entry.timestamp < session.startTime) session.startTime = entry.timestamp;\n if (entry.timestamp > session.endTime) session.endTime = entry.timestamp;\n\n // Track model usage for primary model detection\n if (!session.models[model]) session.models[model] = emptyAggregate();\n accumulate(session.models[model], result);\n\n // Primary model = most requests (incremental: compare current model against stored primary)\n const currentModelCount = session.models[model].request_count;\n const primaryCount = session.models[session.primaryModel]?.request_count ?? 0;\n if (currentModelCount >= primaryCount) {\n session.primaryModel = model;\n }\n }\n\n return [...map.values()].sort((a, b) => b.startTime.localeCompare(a.startTime));\n}\n\n/**\n * Aggregate entries by project.\n */\nexport function aggregateProjects(\n entries: UsageEntry[],\n mode: CostMode = 'calculate',\n): ProjectAggregate[] {\n const map = new Map<string, ProjectAggregate>();\n\n for (const entry of entries) {\n const project = entry.cwd ? extractProjectName(entry.cwd) : 'unknown';\n const model = entry.message.model ?? 'unknown';\n\n if (!map.has(project)) {\n map.set(project, { project, ...emptyAggregate(), models: {} });\n }\n const p = map.get(project)!;\n const result = processEntry(entry, mode);\n\n accumulate(p, result);\n\n if (!p.models[model]) p.models[model] = emptyAggregate();\n accumulate(p.models[model], result);\n }\n\n return [...map.values()].sort((a, b) => b.cost.total_cost - a.cost.total_cost);\n}\n\n/**\n * Aggregate entries by model.\n */\nexport function aggregateModels(\n entries: UsageEntry[],\n mode: CostMode = 'calculate',\n): Record<string, AggregatedEntry> {\n const map: Record<string, AggregatedEntry> = {};\n\n for (const entry of entries) {\n const model = entry.message.model ?? 'unknown';\n if (!map[model]) map[model] = emptyAggregate();\n accumulate(map[model], processEntry(entry, mode));\n }\n\n return map;\n}\n\n/**\n * Build the usage heatmap (7 days × 24 hours).\n */\nexport function buildHeatmap(entries: UsageEntry[], timezone?: string): number[][] {\n const grid: number[][] = Array.from({ length: 7 }, () => Array(24).fill(0) as number[]);\n\n for (const entry of entries) {\n const { hour, day } = getHourAndDay(entry.timestamp, timezone);\n grid[day][hour] += entry.message.usage.input_tokens + entry.message.usage.output_tokens;\n }\n\n return grid;\n}\n\n/**\n * Build complete dashboard data in a SINGLE PASS over all entries.\n * Previously this was 8+ separate passes each calling processEntry.\n */\nexport function buildDashboardData(\n entries: UsageEntry[],\n mode: CostMode = 'calculate',\n timezone?: string,\n): DashboardData {\n const sorted = [...entries].sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n // All aggregation buckets\n const totals = emptyAggregate();\n const dailyMap = new Map<string, DailyAggregate>();\n const monthlyMap = new Map<string, MonthlyAggregate>();\n const sessionMap = new Map<string, SessionAggregate>();\n const projectMap = new Map<string, ProjectAggregate>();\n const modelMap: Record<string, AggregatedEntry> = {};\n const heatmap: number[][] = Array.from({ length: 7 }, () => Array(24).fill(0) as number[]);\n const projectHeatmaps: Record<string, number[][]> = {};\n\n // Single pass\n for (const entry of sorted) {\n const result = processEntry(entry, mode); // Called ONCE per entry\n const date = toDateString(entry.timestamp, timezone);\n const month = date.slice(0, 7);\n const model = entry.message.model ?? 'unknown';\n const sid = entry.sessionId ?? 'unknown';\n const project = entry.cwd ? extractProjectName(entry.cwd) : 'unknown';\n const { hour, day } = getHourAndDay(entry.timestamp, timezone);\n const tokens = entry.message.usage.input_tokens + entry.message.usage.output_tokens;\n\n // Totals\n accumulate(totals, result);\n\n // Daily\n if (!dailyMap.has(date)) dailyMap.set(date, { date, ...emptyAggregate(), models: {}, projects: {} });\n const d = dailyMap.get(date)!;\n accumulate(d, result);\n if (!d.models[model]) d.models[model] = emptyAggregate();\n accumulate(d.models[model], result);\n if (!d.projects[project]) d.projects[project] = emptyAggregate();\n accumulate(d.projects[project], result);\n\n // Monthly\n if (!monthlyMap.has(month)) monthlyMap.set(month, { month, ...emptyAggregate(), models: {} });\n const m = monthlyMap.get(month)!;\n accumulate(m, result);\n if (!m.models[model]) m.models[model] = emptyAggregate();\n accumulate(m.models[model], result);\n\n // Session\n if (!sessionMap.has(sid)) {\n sessionMap.set(sid, { sessionId: sid, project, startTime: entry.timestamp, endTime: entry.timestamp, primaryModel: model, ...emptyAggregate(), models: {} });\n }\n const sess = sessionMap.get(sid)!;\n accumulate(sess, result);\n if (entry.timestamp < sess.startTime) sess.startTime = entry.timestamp;\n if (entry.timestamp > sess.endTime) sess.endTime = entry.timestamp;\n if (!sess.models[model]) sess.models[model] = emptyAggregate();\n accumulate(sess.models[model], result);\n const currentCount = sess.models[model].request_count;\n const primaryCount = sess.models[sess.primaryModel]?.request_count ?? 0;\n if (currentCount >= primaryCount) sess.primaryModel = model;\n\n // Project\n if (!projectMap.has(project)) projectMap.set(project, { project, ...emptyAggregate(), models: {} });\n const p = projectMap.get(project)!;\n accumulate(p, result);\n if (!p.models[model]) p.models[model] = emptyAggregate();\n accumulate(p.models[model], result);\n\n // Model\n if (!modelMap[model]) modelMap[model] = emptyAggregate();\n accumulate(modelMap[model], result);\n\n // Heatmap\n heatmap[day][hour] += tokens;\n\n // Project heatmap\n if (!projectHeatmaps[project]) {\n projectHeatmaps[project] = Array.from({ length: 7 }, () => Array(24).fill(0) as number[]);\n }\n projectHeatmaps[project][day][hour] += tokens;\n }\n\n return {\n generated_at: new Date().toISOString(),\n date_range: {\n start: sorted[0]?.timestamp ?? '',\n end: sorted[sorted.length - 1]?.timestamp ?? '',\n },\n totals,\n daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),\n monthly: [...monthlyMap.values()].sort((a, b) => a.month.localeCompare(b.month)),\n sessions: [...sessionMap.values()].sort((a, b) => b.startTime.localeCompare(a.startTime)),\n projects: [...projectMap.values()].sort((a, b) => b.cost.total_cost - a.cost.total_cost),\n models: modelMap,\n heatmap,\n project_heatmaps: projectHeatmaps,\n };\n}\n\n/**\n * Build per-project heatmaps.\n */\nexport function buildProjectHeatmaps(\n entries: UsageEntry[],\n timezone?: string,\n): Record<string, number[][]> {\n const maps: Record<string, number[][]> = {};\n\n for (const entry of entries) {\n const project = entry.cwd ? extractProjectName(entry.cwd) : 'unknown';\n if (!maps[project]) {\n maps[project] = Array.from({ length: 7 }, () => Array(24).fill(0) as number[]);\n }\n const { hour, day } = getHourAndDay(entry.timestamp, timezone);\n maps[project][day][hour] += entry.message.usage.input_tokens + entry.message.usage.output_tokens;\n }\n\n return maps;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect, beforeEach } = import.meta.vitest;\n const { setPricingData } = await import('./pricing.js');\n\n const testPricing = {\n version: 'test',\n models: {\n 'claude-sonnet-4-20250514': {\n input_cost_per_million: 3.0,\n output_cost_per_million: 15.0,\n cache_creation_cost_per_million: 3.75,\n cache_read_cost_per_million: 0.30,\n context_window: 200000,\n },\n },\n aliases: {},\n };\n\n beforeEach(() => {\n setPricingData(testPricing);\n });\n\n const { makeEntry } = await import('./test-helpers.js');\n\n describe('aggregateDaily', () => {\n it('groups entries by date', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T14:00:00Z' }),\n makeEntry({ timestamp: '2025-03-26T10:00:00Z' }),\n ];\n const result = aggregateDaily(entries);\n expect(result).toHaveLength(2);\n expect(result[0].date).toBe('2025-03-25');\n expect(result[0].request_count).toBe(2);\n expect(result[1].date).toBe('2025-03-26');\n });\n\n it('tracks per-model breakdown', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({\n timestamp: '2025-03-25T11:00:00Z',\n message: {\n model: 'other-model',\n usage: { input_tokens: 500, output_tokens: 200, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },\n },\n }),\n ];\n const result = aggregateDaily(entries);\n expect(Object.keys(result[0].models)).toHaveLength(2);\n });\n\n it('respects timezone for date grouping', () => {\n // 2025-03-25T23:00:00Z = March 26 in UTC+5\n const entries = [makeEntry({ timestamp: '2025-03-25T23:00:00Z' })];\n const result = aggregateDaily(entries, 'calculate', 'Asia/Kolkata');\n expect(result[0].date).toBe('2025-03-26');\n });\n });\n\n describe('aggregateSessions', () => {\n it('groups entries by sessionId', () => {\n const entries = [\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T10:05:00Z' }),\n makeEntry({ sessionId: 's2', timestamp: '2025-03-25T11:00:00Z' }),\n ];\n const result = aggregateSessions(entries);\n expect(result).toHaveLength(2);\n });\n\n it('tracks session time range', () => {\n const entries = [\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T10:30:00Z' }),\n ];\n const result = aggregateSessions(entries);\n expect(result[0].startTime).toBe('2025-03-25T10:00:00Z');\n expect(result[0].endTime).toBe('2025-03-25T10:30:00Z');\n });\n });\n\n describe('buildHeatmap', () => {\n it('creates 7×24 grid', () => {\n const heatmap = buildHeatmap([]);\n expect(heatmap).toHaveLength(7);\n expect(heatmap[0]).toHaveLength(24);\n });\n\n it('accumulates tokens in correct cell', () => {\n // 2025-03-25 is a Tuesday (day=2), 10:00 UTC\n const entries = [makeEntry({ timestamp: '2025-03-25T10:00:00Z' })];\n const heatmap = buildHeatmap(entries);\n expect(heatmap[2][10]).toBe(1500); // 1000 input + 500 output\n });\n });\n\n describe('aggregateMonthly', () => {\n it('groups entries by month', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-26T10:00:00Z' }),\n makeEntry({ timestamp: '2025-04-01T10:00:00Z' }),\n ];\n const result = aggregateMonthly(entries);\n expect(result).toHaveLength(2);\n expect(result[0].month).toBe('2025-03');\n expect(result[0].request_count).toBe(2);\n expect(result[1].month).toBe('2025-04');\n expect(result[1].request_count).toBe(1);\n });\n\n it('tracks per-model breakdown in monthly', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({\n timestamp: '2025-03-25T11:00:00Z',\n message: {\n model: 'other-model',\n usage: { input_tokens: 500, output_tokens: 200, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },\n },\n }),\n ];\n const result = aggregateMonthly(entries);\n expect(Object.keys(result[0].models)).toHaveLength(2);\n });\n });\n\n describe('aggregateProjects', () => {\n it('groups entries by project (cwd)', () => {\n const entries = [\n makeEntry({ cwd: '/home/.claude/projects/-Users-me-proj1/session.jsonl' }),\n makeEntry({ cwd: '/home/.claude/projects/-Users-me-proj1/session.jsonl' }),\n makeEntry({ cwd: '/home/.claude/projects/-Users-me-proj2/session.jsonl' }),\n ];\n const result = aggregateProjects(entries);\n expect(result).toHaveLength(2);\n });\n\n it('sorts by cost descending', () => {\n const entries = [\n makeEntry({ cwd: '/home/.claude/projects/cheap/f.jsonl', message: { model: 'claude-sonnet-4-20250514', usage: { input_tokens: 100, output_tokens: 50, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 } } }),\n makeEntry({ cwd: '/home/.claude/projects/expensive/f.jsonl', message: { model: 'claude-sonnet-4-20250514', usage: { input_tokens: 100000, output_tokens: 50000, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 } } }),\n ];\n const result = aggregateProjects(entries);\n expect(result[0].cost.total_cost).toBeGreaterThan(result[1].cost.total_cost);\n });\n\n it('uses unknown for entries without cwd', () => {\n const entries = [makeEntry()];\n const result = aggregateProjects(entries);\n expect(result[0].project).toBe('unknown');\n });\n });\n\n describe('aggregateModels', () => {\n it('groups entries by model name', () => {\n const entries = [\n makeEntry(),\n makeEntry({\n message: {\n model: 'other-model',\n usage: { input_tokens: 100, output_tokens: 50, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },\n },\n }),\n ];\n const result = aggregateModels(entries);\n expect(Object.keys(result)).toHaveLength(2);\n expect(result['claude-sonnet-4-20250514']).toBeDefined();\n expect(result['other-model']).toBeDefined();\n });\n\n it('uses unknown for entries without model', () => {\n const entries = [\n makeEntry({\n message: {\n usage: { input_tokens: 100, output_tokens: 50, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },\n },\n }),\n ];\n const result = aggregateModels(entries);\n expect(result['unknown']).toBeDefined();\n });\n });\n\n describe('aggregateSessions (extended)', () => {\n it('detects primary model by request count', () => {\n const entries = [\n makeEntry({ sessionId: 's1' }),\n makeEntry({ sessionId: 's1' }),\n makeEntry({\n sessionId: 's1',\n message: {\n model: 'other-model',\n usage: { input_tokens: 100, output_tokens: 50, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },\n },\n }),\n ];\n const result = aggregateSessions(entries);\n expect(result[0].primaryModel).toBe('claude-sonnet-4-20250514');\n });\n\n it('uses unknown sessionId when missing', () => {\n const entries = [makeEntry()];\n const result = aggregateSessions(entries);\n expect(result[0].sessionId).toBe('unknown');\n });\n\n it('sorts sessions by startTime descending (newest first)', () => {\n const entries = [\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T08:00:00Z' }),\n makeEntry({ sessionId: 's2', timestamp: '2025-03-25T12:00:00Z' }),\n ];\n const result = aggregateSessions(entries);\n expect(result[0].sessionId).toBe('s2');\n expect(result[1].sessionId).toBe('s1');\n });\n });\n\n describe('buildDashboardData', () => {\n it('returns all required fields', () => {\n const entries = [\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T11:00:00Z' }),\n ];\n const data = buildDashboardData(entries);\n expect(data.generated_at).toBeTruthy();\n expect(data.date_range.start).toBe('2025-03-25T10:00:00Z');\n expect(data.date_range.end).toBe('2025-03-25T11:00:00Z');\n expect(data.totals.request_count).toBe(2);\n expect(data.daily).toHaveLength(1);\n expect(data.monthly).toHaveLength(1);\n expect(data.sessions).toHaveLength(1);\n expect(data.heatmap).toHaveLength(7);\n expect(Object.keys(data.models)).toHaveLength(1);\n });\n\n it('handles empty entries', () => {\n const data = buildDashboardData([]);\n expect(data.totals.request_count).toBe(0);\n expect(data.daily).toHaveLength(0);\n expect(data.date_range.start).toBe('');\n });\n });\n\n describe('filterEntries', () => {\n it('filters by date range', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-24T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-26T10:00:00Z' }),\n ];\n const result = filterEntries(entries, { since: '2025-03-25', until: '2025-03-25' });\n expect(result).toHaveLength(1);\n });\n\n it('returns all entries with no filters', () => {\n const entries = [makeEntry(), makeEntry()];\n expect(filterEntries(entries, {})).toHaveLength(2);\n });\n\n it('filters by since only', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-24T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n ];\n expect(filterEntries(entries, { since: '2025-03-25' })).toHaveLength(1);\n });\n\n it('filters by until only', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-24T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n ];\n expect(filterEntries(entries, { until: '2025-03-24' })).toHaveLength(1);\n });\n\n it('filters by project name (case-insensitive substring)', () => {\n const entries = [\n makeEntry({ cwd: 'tradeforge' }),\n makeEntry({ cwd: 'cctrack' }),\n makeEntry({ cwd: 'TradeForge' }),\n ];\n const result = filterEntries(entries, { project: 'trade' });\n expect(result).toHaveLength(2);\n });\n\n it('excludes entries without cwd when project filter is set', () => {\n const entries = [makeEntry(), makeEntry({ cwd: 'cctrack' })];\n expect(filterEntries(entries, { project: 'cctrack' })).toHaveLength(1);\n });\n });\n\n describe('aggregateDaily (project breakdown)', () => {\n it('tracks per-project breakdown in daily', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z', cwd: 'proj-a' }),\n makeEntry({ timestamp: '2025-03-25T11:00:00Z', cwd: 'proj-b' }),\n ];\n const result = aggregateDaily(entries);\n expect(Object.keys(result[0].projects)).toHaveLength(2);\n expect(result[0].projects['proj-a'].request_count).toBe(1);\n expect(result[0].projects['proj-b'].request_count).toBe(1);\n });\n\n it('per-project daily data has complete cost and token fields', () => {\n const entries = [makeEntry({ timestamp: '2025-03-25T10:00:00Z', cwd: 'proj-a' })];\n const result = aggregateDaily(entries);\n const projData = result[0].projects['proj-a'];\n\n // Must have request_count (needed for Cost Per Request ROI metric)\n expect(projData.request_count).toBe(1);\n\n // Must have all cost fields (needed for Cache Savings ROI metric)\n expect(projData.cost.input_cost).toBeDefined();\n expect(projData.cost.output_cost).toBeDefined();\n expect(projData.cost.cache_write_cost).toBeDefined();\n expect(projData.cost.cache_read_cost).toBeDefined();\n expect(projData.cost.total_cost).toBeDefined();\n expect(typeof projData.cost.cache_read_cost).toBe('number');\n\n // Must have all token fields\n expect(projData.tokens.input_tokens).toBeDefined();\n expect(projData.tokens.output_tokens).toBeDefined();\n expect(projData.tokens.cache_write_tokens).toBeDefined();\n expect(projData.tokens.cache_read_tokens).toBeDefined();\n expect(projData.tokens.total_tokens).toBeDefined();\n });\n\n it('per-project data produces non-zero ROI when summed (catches the $0.00 bug)', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z', cwd: 'proj-a' }),\n makeEntry({ timestamp: '2025-03-25T11:00:00Z', cwd: 'proj-a' }),\n makeEntry({ timestamp: '2025-03-25T12:00:00Z', cwd: 'proj-b' }),\n ];\n const result = aggregateDaily(entries);\n const projA = result[0].projects['proj-a'];\n\n // Simulate what the dashboard does: sum per-project daily data for ROI\n const totalCost = projA.cost.total_cost;\n const totalReqs = projA.request_count;\n const cacheReadCost = projA.cost.cache_read_cost;\n\n const costPerReq = totalReqs > 0 ? totalCost / totalReqs : 0;\n const cacheSavings = cacheReadCost * 9;\n\n // These must NOT be zero (the bug that was caught)\n expect(totalReqs).toBe(2);\n expect(costPerReq).toBeGreaterThan(0);\n // cache_read_cost is 0 for test data with 0 cache tokens, but the field must exist\n expect(typeof cacheReadCost).toBe('number');\n });\n });\n\n describe('buildProjectHeatmaps', () => {\n it('creates separate heatmaps per project', () => {\n const entries = [\n makeEntry({ cwd: 'proj-a', timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ cwd: 'proj-b', timestamp: '2025-03-25T14:00:00Z' }),\n ];\n const maps = buildProjectHeatmaps(entries);\n expect(Object.keys(maps)).toHaveLength(2);\n expect(maps['proj-a']).toHaveLength(7);\n expect(maps['proj-a'][2][10]).toBe(1500); // Tuesday 10:00, 1000+500 tokens\n });\n\n it('returns empty object for no entries', () => {\n expect(buildProjectHeatmaps([])).toEqual({});\n });\n });\n\n describe('buildDashboardData (single-pass)', () => {\n it('includes project_heatmaps', () => {\n const entries = [makeEntry({ cwd: 'proj-a', sessionId: 's1', timestamp: '2025-03-25T10:00:00Z' })];\n const data = buildDashboardData(entries);\n expect(data.project_heatmaps).toBeDefined();\n expect(data.project_heatmaps!['proj-a']).toHaveLength(7);\n });\n\n it('produces consistent totals across aggregations', () => {\n const entries = [\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ sessionId: 's1', timestamp: '2025-03-25T11:00:00Z' }),\n makeEntry({ sessionId: 's2', timestamp: '2025-03-26T10:00:00Z' }),\n ];\n const data = buildDashboardData(entries);\n expect(data.totals.request_count).toBe(3);\n expect(data.daily.reduce((s, d) => s + d.request_count, 0)).toBe(3);\n expect(data.sessions.reduce((s, d) => s + d.request_count, 0)).toBe(3);\n });\n });\n}\n","export function formatCost(cost: number): string {\n if (cost < 0) return `-$${Math.abs(cost).toFixed(2)}`;\n if (cost < 0.01 && cost > 0) return `$${cost.toFixed(4)}`;\n return `$${cost.toFixed(2)}`;\n}\n\nexport function formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;\n if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`;\n return tokens.toString();\n}\n\nexport function formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ${seconds % 60}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m`;\n}\n\n/** Validate CostMode from CLI input. Returns valid mode or exits with error. */\nexport function parseCostMode(input: string | undefined): 'calculate' | 'display' | 'compare' {\n const mode = input ?? 'calculate';\n if (mode === 'calculate' || mode === 'display' || mode === 'compare') return mode;\n console.error(`Invalid mode: \"${mode}\". Choose from: calculate, display, compare`);\n process.exit(1);\n}\n\n/** Escape a value for CSV: wrap in quotes if it contains comma, quote, or newline */\nexport function csvEscape(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\n return '\"' + value.replace(/\"/g, '\"\"') + '\"';\n }\n return value;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect } = import.meta.vitest;\n\n describe('formatCost', () => {\n it('formats zero as $0.00', () => expect(formatCost(0)).toBe('$0.00'));\n it('formats sub-cent with 4 decimals', () => expect(formatCost(0.0012)).toBe('$0.0012'));\n it('formats $0.0099 with 4 decimals', () => expect(formatCost(0.0099)).toBe('$0.0099'));\n it('formats $0.01 with 2 decimals', () => expect(formatCost(0.01)).toBe('$0.01'));\n it('formats $1.50 with 2 decimals', () => expect(formatCost(1.5)).toBe('$1.50'));\n it('formats large cost', () => expect(formatCost(1234.56)).toBe('$1234.56'));\n it('formats negative cost with minus before $', () => expect(formatCost(-5)).toBe('-$5.00'));\n });\n\n describe('formatTokens', () => {\n it('formats millions', () => expect(formatTokens(1_500_000)).toBe('1.5M'));\n it('formats thousands', () => expect(formatTokens(1_500)).toBe('1.5K'));\n it('formats small numbers as-is', () => expect(formatTokens(999)).toBe('999'));\n it('formats zero', () => expect(formatTokens(0)).toBe('0'));\n });\n\n describe('formatDuration', () => {\n it('formats seconds', () => expect(formatDuration(5000)).toBe('5s'));\n it('formats minutes', () => expect(formatDuration(125_000)).toBe('2m 5s'));\n it('formats hours', () => expect(formatDuration(3_725_000)).toBe('1h 2m'));\n it('formats zero', () => expect(formatDuration(0)).toBe('0s'));\n });\n\n describe('csvEscape', () => {\n it('passes plain text through', () => expect(csvEscape('hello')).toBe('hello'));\n it('wraps text with comma', () => expect(csvEscape('a,b')).toBe('\"a,b\"'));\n it('escapes double quotes', () => expect(csvEscape('say \"hi\"')).toBe('\"say \"\"hi\"\"\"'));\n it('wraps text with newline', () => expect(csvEscape('a\\nb')).toBe('\"a\\nb\"'));\n });\n\n describe('shortenModelName', () => {\n it('shortens opus-4.6', () => expect(shortenModelName('claude-opus-4-6-20260205')).toBe('opus-4.6'));\n it('shortens sonnet-4.6', () => expect(shortenModelName('claude-sonnet-4-6-20260217')).toBe('sonnet-4.6'));\n it('shortens opus-4', () => expect(shortenModelName('claude-opus-4-20250514')).toBe('opus-4'));\n it('shortens haiku-4.5', () => expect(shortenModelName('claude-haiku-4-5-20251001')).toBe('haiku-4.5'));\n it('shortens legacy sonnet-3.5', () => expect(shortenModelName('claude-3-5-sonnet-20241022')).toBe('sonnet-3.5'));\n it('strips claude- prefix for unknown', () => expect(shortenModelName('claude-custom-model')).toBe('custom-model'));\n it('returns non-claude model as-is', () => expect(shortenModelName('gpt-4')).toBe('gpt-4'));\n });\n\n describe('parseCostMode', () => {\n it('accepts calculate', () => expect(parseCostMode('calculate')).toBe('calculate'));\n it('accepts display', () => expect(parseCostMode('display')).toBe('display'));\n it('accepts compare', () => expect(parseCostMode('compare')).toBe('compare'));\n it('defaults to calculate', () => expect(parseCostMode(undefined)).toBe('calculate'));\n });\n\n}\n\n/** Shorten Claude model names for display: claude-opus-4-6-20260205 → opus-4.6 */\nexport function shortenModelName(model: string): string {\n const map: Record<string, string> = {\n 'claude-opus-4-6': 'opus-4.6',\n 'claude-sonnet-4-6': 'sonnet-4.6',\n 'claude-opus-4-5': 'opus-4.5',\n 'claude-sonnet-4-5': 'sonnet-4.5',\n 'claude-haiku-4-5': 'haiku-4.5',\n 'claude-opus-4': 'opus-4',\n 'claude-sonnet-4': 'sonnet-4',\n 'claude-3-7-sonnet': 'sonnet-3.7',\n 'claude-3-5-sonnet': 'sonnet-3.5',\n 'claude-3-5-haiku': 'haiku-3.5',\n 'claude-3-opus': 'opus-3',\n 'claude-3-sonnet': 'sonnet-3',\n 'claude-3-haiku': 'haiku-3',\n };\n for (const [prefix, short] of Object.entries(map)) {\n if (model.startsWith(prefix)) return short;\n }\n return model.replace(/^claude-/, '').replace(/-\\d{8}$/, '');\n}\n","import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport chalk from 'chalk';\nimport type { BudgetConfig, BudgetStatus, BudgetLevel } from './types.js';\nimport { BUDGET_THRESHOLDS } from './types.js';\n\nconst CONFIG_DIR = join(homedir(), '.cctrack');\nconst CONFIG_PATH = join(CONFIG_DIR, 'config.json');\n\ninterface ConfigFile {\n budget?: BudgetConfig;\n}\n\nexport function loadBudgetConfig(): BudgetConfig {\n try {\n const raw = readFileSync(CONFIG_PATH, 'utf-8');\n const config: ConfigFile = JSON.parse(raw);\n return config.budget ?? {};\n } catch {\n return {};\n }\n}\n\nexport function saveBudgetConfig(config: BudgetConfig): void {\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n let existing: ConfigFile = {};\n try {\n existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));\n } catch {\n // No existing config, start fresh\n }\n\n existing.budget = config;\n writeFileSync(CONFIG_PATH, JSON.stringify(existing, null, 2) + '\\n', 'utf-8');\n}\n\nexport function loadFullConfig(): ConfigFile {\n try {\n const raw = readFileSync(CONFIG_PATH, 'utf-8');\n return JSON.parse(raw);\n } catch {\n return {};\n }\n}\n\nexport function saveFullConfig(config: ConfigFile): void {\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\nexport function resetConfig(): void {\n if (existsSync(CONFIG_PATH)) {\n unlinkSync(CONFIG_PATH);\n }\n}\n\nexport function getConfigPath(): string {\n return CONFIG_PATH;\n}\n\nexport function getBudgetLevel(percentage: number): BudgetLevel {\n if (percentage >= BUDGET_THRESHOLDS.exceeded) return 'exceeded';\n if (percentage >= BUDGET_THRESHOLDS.critical) return 'critical';\n if (percentage >= BUDGET_THRESHOLDS.warning) return 'warning';\n return 'safe';\n}\n\nexport function calculateBudgetStatus(spent: number, budget: number): BudgetStatus {\n const percentage = budget === 0 ? (spent > 0 ? 100 : 0) : (spent / budget) * 100;\n const level = getBudgetLevel(percentage);\n const remaining = Math.max(0, budget - spent);\n return { level, budget, spent, remaining, percentage };\n}\n\nexport function budgetColor(level: BudgetLevel): (text: string) => string {\n switch (level) {\n case 'safe':\n return chalk.green;\n case 'warning':\n return chalk.yellow;\n case 'critical':\n return chalk.red;\n case 'exceeded':\n return chalk.bgRed.white;\n }\n}\n\nexport function formatBudgetBar(percentage: number, width: number = 20): string {\n const clamped = Math.min(Math.max(percentage, 0), 100);\n const filled = Math.round((clamped / 100) * width);\n const empty = width - filled;\n const bar = '\\u2588'.repeat(filled) + '\\u2591'.repeat(empty);\n\n const level = getBudgetLevel(percentage);\n const color = budgetColor(level);\n const pctStr = `${Math.round(percentage)}%`;\n\n return `${color(bar)} ${pctStr}`;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect, beforeEach, afterEach } = import.meta.vitest;\n const { mkdtempSync, writeFileSync: writeTmp, readFileSync: readTmp, rmSync } = await import('node:fs');\n const { tmpdir } = await import('node:os');\n const { join: joinPath } = await import('node:path');\n\n describe('loadBudgetConfig', () => {\n it('returns empty object when no config file exists', () => {\n // loadBudgetConfig reads from a fixed path; we test the function's\n // behavior by calling it and verifying it returns a valid BudgetConfig.\n // Since the actual home dir config may or may not exist, we just verify\n // the return type shape.\n const config = loadBudgetConfig();\n expect(typeof config).toBe('object');\n expect(config).toBeDefined();\n });\n });\n\n describe('getBudgetLevel', () => {\n it('returns safe for 0%', () => {\n expect(getBudgetLevel(0)).toBe('safe');\n });\n\n it('returns safe for 25%', () => {\n expect(getBudgetLevel(25)).toBe('safe');\n });\n\n it('returns safe for 49.9%', () => {\n expect(getBudgetLevel(49.9)).toBe('safe');\n });\n\n it('returns warning at exactly 50%', () => {\n expect(getBudgetLevel(50)).toBe('warning');\n });\n\n it('returns warning for 75%', () => {\n expect(getBudgetLevel(75)).toBe('warning');\n });\n\n it('returns critical at exactly 80%', () => {\n expect(getBudgetLevel(80)).toBe('critical');\n });\n\n it('returns critical for 99%', () => {\n expect(getBudgetLevel(99)).toBe('critical');\n });\n\n it('returns exceeded at exactly 100%', () => {\n expect(getBudgetLevel(100)).toBe('exceeded');\n });\n\n it('returns exceeded for 150%', () => {\n expect(getBudgetLevel(150)).toBe('exceeded');\n });\n });\n\n describe('calculateBudgetStatus', () => {\n it('calculates 0% when nothing spent', () => {\n const status = calculateBudgetStatus(0, 100);\n expect(status.percentage).toBe(0);\n expect(status.level).toBe('safe');\n expect(status.remaining).toBe(100);\n expect(status.spent).toBe(0);\n expect(status.budget).toBe(100);\n });\n\n it('calculates 25% spent', () => {\n const status = calculateBudgetStatus(25, 100);\n expect(status.percentage).toBe(25);\n expect(status.level).toBe('safe');\n expect(status.remaining).toBe(75);\n });\n\n it('calculates 50% (warning threshold)', () => {\n const status = calculateBudgetStatus(50, 100);\n expect(status.percentage).toBe(50);\n expect(status.level).toBe('warning');\n expect(status.remaining).toBe(50);\n });\n\n it('calculates 75% (still warning)', () => {\n const status = calculateBudgetStatus(75, 100);\n expect(status.percentage).toBe(75);\n expect(status.level).toBe('warning');\n expect(status.remaining).toBe(25);\n });\n\n it('calculates 99% (critical)', () => {\n const status = calculateBudgetStatus(99, 100);\n expect(status.percentage).toBe(99);\n expect(status.level).toBe('critical');\n expect(status.remaining).toBe(1);\n });\n\n it('calculates 100% (exceeded)', () => {\n const status = calculateBudgetStatus(100, 100);\n expect(status.percentage).toBe(100);\n expect(status.level).toBe('exceeded');\n expect(status.remaining).toBe(0);\n });\n\n it('calculates 150% (exceeded, remaining clamped to 0)', () => {\n const status = calculateBudgetStatus(150, 100);\n expect(status.percentage).toBe(150);\n expect(status.level).toBe('exceeded');\n expect(status.remaining).toBe(0);\n });\n });\n\n describe('formatBudgetBar', () => {\n // Strip ANSI codes for testing\n const stripAnsi = (s: string) => s.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n it('shows empty bar at 0%', () => {\n const bar = stripAnsi(formatBudgetBar(0, 20));\n expect(bar).toContain('\\u2591'.repeat(20));\n expect(bar).toContain('0%');\n });\n\n it('shows half-filled bar at 50%', () => {\n const bar = stripAnsi(formatBudgetBar(50, 20));\n expect(bar).toContain('\\u2588'.repeat(10));\n expect(bar).toContain('\\u2591'.repeat(10));\n expect(bar).toContain('50%');\n });\n\n it('shows full bar at 100%', () => {\n const bar = stripAnsi(formatBudgetBar(100, 20));\n expect(bar).toContain('\\u2588'.repeat(20));\n expect(bar).toContain('100%');\n });\n\n it('clamps bar fill at 100% for values over 100%', () => {\n const bar = stripAnsi(formatBudgetBar(150, 20));\n expect(bar).toContain('\\u2588'.repeat(20));\n expect(bar).toContain('150%');\n });\n\n it('uses default width of 20', () => {\n const bar = stripAnsi(formatBudgetBar(50));\n // 10 filled + 10 empty = 20\n const barPart = bar.split(' ')[0];\n expect(barPart).toHaveLength(20);\n });\n });\n\n describe('budgetColor', () => {\n it('returns green for safe', () => {\n const fn = budgetColor('safe');\n expect(fn('test')).toContain('test');\n });\n\n it('returns yellow for warning', () => {\n const fn = budgetColor('warning');\n expect(fn('test')).toContain('test');\n });\n\n it('returns red for critical', () => {\n const fn = budgetColor('critical');\n expect(fn('test')).toContain('test');\n });\n\n it('returns bgRed.white for exceeded', () => {\n const fn = budgetColor('exceeded');\n expect(fn('test')).toContain('test');\n });\n });\n\n // --- New edge-case tests ---\n\n describe('calculateBudgetStatus edge cases', () => {\n it('handles zero budget with zero spending', () => {\n const status = calculateBudgetStatus(0, 0);\n expect(status.percentage).toBe(0);\n expect(status.level).toBe('safe');\n expect(status.remaining).toBe(0);\n });\n\n it('handles spending against zero budget', () => {\n const status = calculateBudgetStatus(10, 0);\n expect(status.percentage).toBe(100);\n expect(status.level).toBe('exceeded');\n expect(status.remaining).toBe(0);\n });\n\n it('handles fractional dollar amounts without precision errors', () => {\n // 0.1 + 0.2 !== 0.30 in IEEE 754\n const status = calculateBudgetStatus(0.1 + 0.2, 1.0);\n expect(status.percentage).toBeCloseTo(30, 10);\n expect(status.remaining).toBeCloseTo(0.7, 10);\n expect(status.level).toBe('safe');\n });\n });\n\n describe('getBudgetLevel edge cases', () => {\n it('returns safe for negative percentage', () => {\n expect(getBudgetLevel(-10)).toBe('safe');\n });\n\n it('returns safe for NaN (documents behavior)', () => {\n // NaN >= any_number is always false, so all checks fail => 'safe'\n expect(getBudgetLevel(NaN)).toBe('safe');\n });\n });\n\n describe('formatBudgetBar edge cases', () => {\n it('handles negative percentage without throwing', () => {\n // formatBudgetBar only clamps to max 100, not min 0\n // This will throw RangeError if repeat gets a negative number\n // Math.round((-50 / 100) * 20) = -10, repeat(-10) throws\n // If it does throw, this test documents the bug\n try {\n const bar = formatBudgetBar(-50, 20);\n // If no throw, verify it produced something reasonable\n expect(typeof bar).toBe('string');\n } catch (e) {\n // Documents that negative percentage causes a crash\n expect(e).toBeInstanceOf(RangeError);\n }\n });\n\n it('bar at exactly 80% shows 16 filled blocks', () => {\n const stripAnsi = (s: string) => s.replace(/\\x1B\\[[0-9;]*m/g, '');\n const bar = stripAnsi(formatBudgetBar(80, 20));\n expect(bar).toContain('\\u2588'.repeat(16));\n expect(bar).toContain('80%');\n });\n });\n}\n","import type { UsageEntry, CostMode, BurnRate, BudgetConfig } from './types.js';\nimport { processEntry } from './calculator.js';\n\n/**\n * Calculate burn rates from usage entries.\n * Derives hourly, daily, and projected monthly costs from the time span of entries.\n */\nexport function calculateBurnRate(\n entries: UsageEntry[],\n mode: CostMode,\n budget?: BudgetConfig,\n): BurnRate {\n if (entries.length === 0) {\n return {\n hourly_cost: 0,\n daily_cost: 0,\n projected_monthly: 0,\n hours_analyzed: 0,\n insufficient_data: true,\n };\n }\n\n // Sort by timestamp to find first and last\n const sorted = [...entries].sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n const firstTime = new Date(sorted[0].timestamp).getTime();\n const lastTime = new Date(sorted[sorted.length - 1].timestamp).getTime();\n\n // Calculate total cost\n let totalCost = 0;\n for (const entry of sorted) {\n const result = processEntry(entry, mode);\n totalCost += result.cost.total_cost;\n }\n\n const spanMs = lastTime - firstTime;\n const spanHours = Math.max(spanMs / (1000 * 60 * 60), 1); // Minimum 1 hour to avoid division spikes\n\n const hourly_cost = totalCost / spanHours;\n\n // If more than 24 hours of data, use actual daily average\n const spanDays = spanHours / 24;\n const daily_cost = spanDays >= 1 ? totalCost / spanDays : hourly_cost * 24;\n\n const projected_monthly = daily_cost * 30;\n\n const result: BurnRate = {\n hourly_cost,\n daily_cost,\n projected_monthly,\n hours_analyzed: spanHours,\n insufficient_data: spanMs < 60 * 60 * 1000, // less than 1 hour of data\n };\n\n // Calculate time until budget exhausted if budget provided\n if (budget?.monthly !== undefined && hourly_cost > 0) {\n const remaining = budget.monthly - totalCost;\n if (remaining <= 0) {\n result.time_until_budget_exhausted_ms = 0;\n } else {\n result.time_until_budget_exhausted_ms = (remaining / hourly_cost) * 60 * 60 * 1000;\n }\n }\n\n return result;\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect, beforeEach } = import.meta.vitest;\n const { setPricingData } = await import('./pricing.js');\n\n const testPricing = {\n version: 'test',\n models: {\n 'claude-sonnet-4-20250514': {\n input_cost_per_million: 3.0,\n output_cost_per_million: 15.0,\n cache_creation_cost_per_million: 3.75,\n cache_read_cost_per_million: 0.3,\n context_window: 200000,\n },\n },\n aliases: {},\n };\n\n beforeEach(() => {\n setPricingData(testPricing);\n });\n\n const { makeEntry } = await import('./test-helpers.js');\n\n describe('calculateBurnRate', () => {\n it('returns zero rates for zero entries', () => {\n const result = calculateBurnRate([], 'calculate');\n expect(result.hourly_cost).toBe(0);\n expect(result.daily_cost).toBe(0);\n expect(result.projected_monthly).toBe(0);\n expect(result.hours_analyzed).toBe(0);\n expect(result.time_until_budget_exhausted_ms).toBeUndefined();\n });\n\n it('calculates burn rate with 1 hour of data', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T11:00:00Z' }),\n ];\n const result = calculateBurnRate(entries, 'calculate');\n\n expect(result.hours_analyzed).toBeCloseTo(1, 2);\n expect(result.hourly_cost).toBeGreaterThan(0);\n // With 1 hour of data (< 1 day), daily = hourly * 24\n expect(result.daily_cost).toBeCloseTo(result.hourly_cost * 24, 6);\n expect(result.projected_monthly).toBeCloseTo(result.daily_cost * 30, 6);\n });\n\n it('calculates burn rate with multiple days of data', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-26T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-27T10:00:00Z' }),\n ];\n const result = calculateBurnRate(entries, 'calculate');\n\n // Span is 48 hours = 2 days\n expect(result.hours_analyzed).toBeCloseTo(48, 2);\n // With > 1 day, daily = total / days\n const spanDays = result.hours_analyzed / 24;\n expect(spanDays).toBeGreaterThanOrEqual(1);\n\n // Verify projected monthly\n expect(result.projected_monthly).toBeCloseTo(result.daily_cost * 30, 6);\n });\n\n it('projected monthly calculation is accurate', () => {\n // Create exactly 24 hours of data with known cost\n const entries = [\n makeEntry({ timestamp: '2025-03-25T00:00:00Z' }),\n makeEntry({ timestamp: '2025-03-26T00:00:00Z' }),\n ];\n const result = calculateBurnRate(entries, 'calculate');\n\n // With exactly 1 day of data, daily_cost should be total cost / 1 day\n // projected_monthly should be daily * 30\n expect(result.projected_monthly).toBeCloseTo(result.daily_cost * 30, 6);\n expect(result.projected_monthly).toBeGreaterThan(0);\n });\n\n it('calculates time_until_budget_exhausted with budget config', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T11:00:00Z' }),\n ];\n const result = calculateBurnRate(entries, 'calculate', { monthly: 100 });\n\n expect(result.time_until_budget_exhausted_ms).toBeDefined();\n expect(result.time_until_budget_exhausted_ms!).toBeGreaterThan(0);\n });\n\n it('returns 0 exhaustion time when budget already exceeded', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T11:00:00Z' }),\n ];\n // Set budget to effectively 0\n const result = calculateBurnRate(entries, 'calculate', { monthly: 0 });\n\n expect(result.time_until_budget_exhausted_ms).toBe(0);\n });\n\n it('does not include exhaustion time without budget config', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T11:00:00Z' }),\n ];\n const result = calculateBurnRate(entries, 'calculate');\n\n expect(result.time_until_budget_exhausted_ms).toBeUndefined();\n });\n\n // --- New edge-case tests ---\n\n it('clamps to minimum 1 hour for a single entry (avoids infinite rates)', () => {\n const entries = [makeEntry({ timestamp: '2025-03-25T10:00:00Z' })];\n const result = calculateBurnRate(entries, 'calculate');\n\n expect(result.hours_analyzed).toBe(1);\n expect(Number.isFinite(result.hourly_cost)).toBe(true);\n expect(result.hourly_cost).toBeGreaterThan(0);\n // daily = hourly * 24 since span < 1 day\n expect(result.daily_cost).toBeCloseTo(result.hourly_cost * 24, 6);\n });\n\n it('hourly cost matches hand-calculated value', () => {\n // 2 entries over 2 hours, each: 1000 input + 500 output, no cache\n // Cost per entry: 1000*(3/1e6) + 500*(15/1e6) = 0.003 + 0.0075 = 0.0105\n // Total cost: 0.0105 * 2 = 0.021\n // Span: 2 hours => hourly = 0.021 / 2 = 0.0105\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T12:00:00Z' }),\n ];\n const result = calculateBurnRate(entries, 'calculate');\n\n expect(result.hourly_cost).toBeCloseTo(0.0105, 6);\n expect(result.hours_analyzed).toBeCloseTo(2, 2);\n });\n\n it('time_until_budget_exhausted_ms matches expected value', () => {\n // Hourly cost = 0.0105, total spent = 0.021, budget = $1.00\n // remaining = $0.979, hours = 0.979 / 0.0105 = 93.238...\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T12:00:00Z' }),\n ];\n const result = calculateBurnRate(entries, 'calculate', { monthly: 1.0 });\n\n const expectedHourly = 0.0105;\n const totalCost = 0.021;\n const remaining = 1.0 - totalCost;\n const expectedMs = (remaining / expectedHourly) * 60 * 60 * 1000;\n\n expect(result.time_until_budget_exhausted_ms).toBeDefined();\n expect(result.time_until_budget_exhausted_ms!).toBeCloseTo(expectedMs, -1);\n expect(result.time_until_budget_exhausted_ms!).toBeGreaterThan(0);\n });\n\n it('returns 0 exhaustion time when spending exceeds budget (realistic overspend)', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }),\n makeEntry({ timestamp: '2025-03-25T12:00:00Z' }),\n ];\n // Total cost ~$0.021, budget $0.01 => already exceeded\n const result = calculateBurnRate(entries, 'calculate', { monthly: 0.01 });\n expect(result.time_until_budget_exhausted_ms).toBe(0);\n });\n\n it('handles unsorted entries correctly', () => {\n const entries = [\n makeEntry({ timestamp: '2025-03-25T12:00:00Z' }), // later first\n makeEntry({ timestamp: '2025-03-25T10:00:00Z' }), // earlier second\n ];\n const result = calculateBurnRate(entries, 'calculate');\n\n expect(result.hours_analyzed).toBeCloseTo(2, 2);\n expect(result.hourly_cost).toBeCloseTo(0.0105, 6);\n });\n\n });\n}\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport Table from 'cli-table3';\nimport type { CostMode, MonthlyAggregate } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries, aggregateMonthly } from '../core/aggregator.js';\nimport { formatCost, formatTokens, parseCostMode } from '../utils/format.js';\nimport { loadBudgetConfig, calculateBudgetStatus, formatBudgetBar } from '../core/budget.js';\n\nfunction monthlyToCsv(data: MonthlyAggregate[], breakdown: boolean): string {\n const lines: string[] = [];\n\n if (breakdown) {\n lines.push('month,model,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost');\n for (const m of data) {\n for (const [model, agg] of Object.entries(m.models)) {\n lines.push(\n [\n m.month,\n model,\n agg.tokens.input_tokens,\n agg.tokens.output_tokens,\n agg.tokens.cache_write_tokens,\n agg.tokens.cache_read_tokens,\n agg.tokens.total_tokens,\n agg.cost.total_cost.toFixed(6),\n ].join(','),\n );\n }\n }\n } else {\n lines.push('month,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost');\n for (const m of data) {\n lines.push(\n [\n m.month,\n m.tokens.input_tokens,\n m.tokens.output_tokens,\n m.tokens.cache_write_tokens,\n m.tokens.cache_read_tokens,\n m.tokens.total_tokens,\n m.cost.total_cost.toFixed(6),\n ].join(','),\n );\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction monthlyToTable(data: MonthlyAggregate[], breakdown: boolean): void {\n if (data.length === 0) {\n console.log(chalk.yellow('No data found for the specified range.'));\n return;\n }\n\n if (breakdown) {\n const table = new Table({\n head: ['Month', 'Model', 'Input', 'Output', 'Cache Write', 'Cache Read', 'Total', 'Cost'].map((h) =>\n chalk.cyan(h),\n ),\n colAligns: ['left', 'left', 'right', 'right', 'right', 'right', 'right', 'right'],\n style: { head: [], border: [] },\n });\n\n for (const m of data) {\n for (const [model, agg] of Object.entries(m.models)) {\n table.push([\n m.month,\n model,\n formatTokens(agg.tokens.input_tokens),\n formatTokens(agg.tokens.output_tokens),\n formatTokens(agg.tokens.cache_write_tokens),\n formatTokens(agg.tokens.cache_read_tokens),\n formatTokens(agg.tokens.total_tokens),\n formatCost(agg.cost.total_cost),\n ]);\n }\n }\n\n console.log(table.toString());\n } else {\n const table = new Table({\n head: ['Month', 'Input', 'Output', 'Cache Write', 'Cache Read', 'Total', 'Cost'].map((h) => chalk.cyan(h)),\n colAligns: ['left', 'right', 'right', 'right', 'right', 'right', 'right'],\n style: { head: [], border: [] },\n });\n\n for (const m of data) {\n table.push([\n m.month,\n formatTokens(m.tokens.input_tokens),\n formatTokens(m.tokens.output_tokens),\n formatTokens(m.tokens.cache_write_tokens),\n formatTokens(m.tokens.cache_read_tokens),\n formatTokens(m.tokens.total_tokens),\n formatCost(m.cost.total_cost),\n ]);\n }\n\n console.log(table.toString());\n }\n\n // Budget indicator (current month only)\n const budgetConfig = loadBudgetConfig();\n if (budgetConfig.monthly != null) {\n const currentMonth = new Date().toISOString().slice(0, 7);\n const monthEntry = data.find((m) => m.month === currentMonth);\n const totalSpent = monthEntry?.cost.total_cost ?? 0;\n const status = calculateBudgetStatus(totalSpent, budgetConfig.monthly);\n console.log();\n console.log(`Monthly Budget: ${formatBudgetBar(status.percentage)} (${formatCost(status.spent)} / ${formatCost(status.budget)})`);\n }\n\n const totalCost = data.reduce((sum, m) => sum + m.cost.total_cost, 0);\n const totalTokens = data.reduce((sum, m) => sum + m.tokens.total_tokens, 0);\n console.log(chalk.dim('─'.repeat(60)));\n console.log(chalk.bold(`Total: ${formatTokens(totalTokens)} tokens, ${formatCost(totalCost)}`));\n}\n\nexport function registerMonthlyCommand(program: Command): void {\n program\n .command('monthly')\n .description('Show monthly usage breakdown')\n .option('--json', 'Output as JSON')\n .option('--csv', 'Output as CSV')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--project <name>', 'Filter by project name')\n .option('--breakdown', 'Show per-model breakdown')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for date grouping (e.g. America/New_York)')\n .action(async (opts) => {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n timezone: opts.timezone,\n });\n\n const mode = parseCostMode(opts.mode);\n const data = aggregateMonthly(filtered, mode, opts.timezone);\n\n if (opts.json) {\n console.log(JSON.stringify(data, null, 2));\n } else if (opts.csv) {\n console.log(monthlyToCsv(data, opts.breakdown));\n } else {\n monthlyToTable(data, opts.breakdown);\n }\n });\n}\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport Table from 'cli-table3';\nimport type { CostMode, SessionAggregate } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries, aggregateSessions } from '../core/aggregator.js';\nimport { formatCost, formatTokens, formatDuration, shortenModelName, csvEscape, parseCostMode } from '../utils/format.js';\n\nfunction sessionDuration(session: SessionAggregate): number {\n return new Date(session.endTime).getTime() - new Date(session.startTime).getTime();\n}\n\nfunction truncateId(id: string, length: number = 12): string {\n if (id.length <= length) return id;\n return id.slice(0, length) + '...';\n}\n\nfunction sessionToCsv(data: SessionAggregate[]): string {\n const lines: string[] = [];\n lines.push('session_id,project,model,duration_ms,requests,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost');\n\n for (const s of data) {\n const duration = sessionDuration(s);\n lines.push(\n [\n csvEscape(s.sessionId),\n csvEscape(s.project),\n csvEscape(s.primaryModel),\n duration,\n s.request_count,\n s.tokens.input_tokens,\n s.tokens.output_tokens,\n s.tokens.cache_write_tokens,\n s.tokens.cache_read_tokens,\n s.tokens.total_tokens,\n s.cost.total_cost.toFixed(6),\n ].join(','),\n );\n }\n\n return lines.join('\\n');\n}\n\nfunction sessionToTable(data: SessionAggregate[], full: boolean = false): void {\n if (data.length === 0) {\n console.log(chalk.yellow('No sessions found for the specified range.'));\n return;\n }\n\n const table = new Table({\n head: ['Session ID', 'Project', 'Model', 'Duration', 'Requests', 'Tokens', 'Cost'].map((h) => chalk.cyan(h)),\n colAligns: ['left', 'left', 'left', 'right', 'right', 'right', 'right'],\n style: { head: [], border: [] },\n });\n\n for (const s of data) {\n const duration = sessionDuration(s);\n table.push([\n full ? s.sessionId : truncateId(s.sessionId),\n full ? s.project : truncateId(s.project, 20),\n shortenModelName(s.primaryModel) + (Object.keys(s.models || {}).length > 1 ? chalk.dim(` +${Object.keys(s.models).length - 1}`) : ''),\n formatDuration(duration),\n s.request_count.toString(),\n formatTokens(s.tokens.total_tokens),\n formatCost(s.cost.total_cost),\n ]);\n }\n\n console.log(table.toString());\n\n const totalCost = data.reduce((sum, s) => sum + s.cost.total_cost, 0);\n const totalTokens = data.reduce((sum, s) => sum + s.tokens.total_tokens, 0);\n const totalRequests = data.reduce((sum, s) => sum + s.request_count, 0);\n console.log(\n chalk.bold(`\\n${data.length} sessions, ${totalRequests} requests, ${formatTokens(totalTokens)} tokens, ${formatCost(totalCost)}`),\n );\n}\n\nexport function registerSessionCommand(program: Command): void {\n program\n .command('session')\n .description('Show session-level usage')\n .option('--json', 'Output as JSON')\n .option('--csv', 'Output as CSV')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--project <name>', 'Filter by project name')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for filtering')\n .option('--full', 'Show full session IDs and project names (no truncation)')\n .action(async (opts) => {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n timezone: opts.timezone,\n });\n\n const mode = parseCostMode(opts.mode);\n const data = aggregateSessions(filtered, mode);\n\n if (opts.json) {\n console.log(JSON.stringify(data, null, 2));\n } else if (opts.csv) {\n console.log(sessionToCsv(data));\n } else {\n sessionToTable(data, !!opts.full);\n }\n });\n}\n","import type { Command } from 'commander';\nimport { writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport chalk from 'chalk';\nimport type { CostMode, DashboardData } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries, buildDashboardData } from '../core/aggregator.js';\nimport { parseCostMode } from '../utils/format.js';\n\nfunction generateHtml(data: DashboardData): string {\n // Double-encode: JSON.stringify produces the JSON string, then stringify THAT\n // to produce a valid JS string literal. This is XSS-safe because all special\n // characters (<, >, ', \\) are properly escaped by the outer stringify.\n const rawJson = JSON.stringify(data);\n const safeJson = JSON.stringify(rawJson);\n\n const esc = (s: string) =>\n s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n const projectOptions = data.projects\n .map((p) => `<option value=\"${esc(p.project)}\">${esc(p.project)}</option>`)\n .join('\\n');\n const dateStart = data.date_range.start ? data.date_range.start.slice(0, 10) : '';\n const dateEnd = data.date_range.end ? data.date_range.end.slice(0, 10) : '';\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>CCTrack Dashboard</title>\n<!-- Apache ECharts: Apache License 2.0 - https://echarts.apache.org -->\n<script src=\"https://cdn.jsdelivr.net/npm/echarts@5.6.0/dist/echarts.min.js\"><\\/script>\n<script>var DATA = JSON.parse(${safeJson});<\\/script>\n<style>\n:root{--bg:#0f172a;--card:#1e293b;--border:#334155;--text:#e2e8f0;--muted:#94a3b8;--accent:#6366f1;--green:#22c55e;--red:#ef4444;--yellow:#eab308;--cyan:#06b6d4;--blue:#3b82f6;--hm0:#1e293b;--hm1:#2d3a4a;--hm2:#365314;--hm3:#4d7c0f;--hm4:#ca8a04;--hm5:#dc2626}\n.light{--bg:#f8fafc;--card:#fff;--border:#e2e8f0;--text:#1e293b;--muted:#64748b;--hm0:#f1f5f9;--hm1:#d9f99d;--hm2:#84cc16;--hm3:#ca8a04;--hm4:#ea580c;--hm5:#dc2626}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);padding:24px;min-height:100vh;max-width:100vw;overflow-x:hidden}\n.header{margin-bottom:20px;padding-right:50px}\n.header h1{font-size:1.4rem;font-weight:700}.header .sub{color:var(--muted);font-size:.8rem;margin-top:2px}\n.toggle{position:fixed;top:24px;right:24px;z-index:200;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:6px 10px;cursor:pointer;color:var(--text);font-size:1rem;box-shadow:0 2px 8px rgba(0,0,0,.2)}\n.filters{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px 20px;display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:20px}\n.filter-group{display:flex;align-items:center;gap:6px}\n.filters label{color:var(--muted);font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;font-weight:600;white-space:nowrap}\n.filters input,.filters select{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text);font-size:.85rem;max-width:100%}\n.filters select{min-width:140px}\n.filter-actions{display:flex;gap:8px}\n.btn{padding:6px 16px;border-radius:6px;border:none;cursor:pointer;font-size:.85rem;font-weight:600}\n.btn-apply{background:var(--accent);color:#fff}.btn-reset{background:var(--card);color:var(--text);border:1px solid var(--border)}\n.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}\n.stat{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 20px;min-width:0;overflow:hidden}\n.stat-label{color:var(--muted);font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;font-weight:600}\n.stat-value{font-size:1.6rem;font-weight:700;margin-top:4px;font-variant-numeric:tabular-nums}\n.stat-value.green{color:var(--green)}\n.grid{display:grid;gap:16px;margin-bottom:16px}\n.grid-1{grid-template-columns:1fr}.grid-2{grid-template-columns:1fr 1fr}\n.grid-2-1{grid-template-columns:2fr 1fr}\n.panel{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px;position:relative;min-width:0}\n.panel-title{font-size:.85rem;font-weight:700;margin-bottom:12px;display:flex;align-items:center;gap:8px}\n.panel-title::before{content:'';width:8px;height:8px;border-radius:50%;display:inline-block}\n.pt-blue::before{background:var(--blue)}.pt-yellow::before{background:var(--yellow)}.pt-green::before{background:var(--green)}.pt-accent::before{background:var(--accent)}.pt-cyan::before{background:var(--cyan)}.pt-red::before{background:var(--red)}\n.chart-container{height:320px;width:100%}\n.chart-sm{height:260px}\n.chart-tall{min-height:300px}\n.heatmap-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}\n.heatmap{display:grid;grid-template-columns:40px repeat(24,1fr);gap:3px;font-size:.7rem;padding:8px 0;min-width:600px}\n.hm-label{color:var(--muted);display:flex;align-items:center;justify-content:flex-end;padding-right:8px;font-weight:600}\n.hm-cell{aspect-ratio:1;border-radius:3px;min-width:16px;min-height:16px;position:relative;cursor:default}\n.hm-cell:hover .hm-tip{display:block}\n.hm-tip{display:none;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:var(--card);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:.7rem;white-space:nowrap;z-index:100;box-shadow:0 4px 12px rgba(0,0,0,.3)}\n.tbl-wrap{max-height:400px;overflow:auto;border:1px solid var(--border);border-radius:8px}\ntable{width:100%;border-collapse:collapse}\nth{position:sticky;top:0;background:var(--card);text-align:left;padding:10px 12px;font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);border-bottom:1px solid var(--border);cursor:pointer;user-select:none;font-weight:600}\nth:hover{color:var(--text)}\ntd{padding:8px 12px;border-bottom:1px solid var(--border);font-size:.8rem;font-variant-numeric:tabular-nums}\n.text-right{text-align:right}.text-mono{font-family:ui-monospace,monospace;font-size:.75rem}\n.roi-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}\n.roi-card{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;text-align:center;min-width:0}\n.roi-label{color:var(--muted);font-size:.65rem;text-transform:uppercase;letter-spacing:.04em;font-weight:600}\n.roi-value{font-size:1.3rem;font-weight:700;margin-top:4px}.roi-sub{color:var(--muted);font-size:.7rem;margin-top:2px}\n.footer{text-align:center;color:var(--muted);font-size:.7rem;margin-top:24px;padding-top:16px;border-top:1px solid var(--border)}\n@media(max-width:1024px){.grid-2-1{grid-template-columns:1fr 1fr}}\n@media(max-width:768px){.stats{grid-template-columns:repeat(2,1fr)}.roi-grid{grid-template-columns:repeat(2,1fr)}.grid-2,.grid-2-1{grid-template-columns:1fr}.filters{flex-direction:column;align-items:stretch}.filter-group{flex-direction:column;align-items:stretch}.filter-group label{margin-bottom:2px}.filters select,.filters input{min-width:0;width:100%}.filter-actions{justify-content:stretch}.filter-actions .btn{flex:1}.header h1{font-size:1.2rem}.toggle{top:16px;right:16px}.stat-value{font-size:1.3rem}.chart-container{height:280px}.chart-sm{height:240px}body{padding:16px}}\n@media(max-width:480px){.stats{grid-template-columns:1fr}.roi-grid{grid-template-columns:repeat(2,1fr)}.stat{padding:12px 16px}.stat-value{font-size:1.1rem}.roi-value{font-size:1rem}.chart-container{height:240px}.chart-sm{height:200px}.toggle{top:10px;right:10px}body{padding:10px}.grid{gap:10px}.panel{padding:10px}.panel-title{font-size:.8rem;margin-bottom:8px}.filters label{font-size:.7rem}.filters input,.filters select{font-size:.8rem;padding:5px 8px}.footer{font-size:.65rem}}\n@media print{body{background:#fff;color:#000}.panel,.stat,.roi-card{border-color:#ddd;break-inside:avoid}.filters,.toggle{display:none!important}.chart-print-img{width:100%;height:auto}}\n</style>\n</head>\n<body>\n<div class=\"header\">\n <div><h1>CCTrack Dashboard</h1><div class=\"sub\">${esc(dateStart)} — ${esc(dateEnd)} &middot; Generated ${new Date().toLocaleString()}</div></div>\n <button class=\"toggle\" id=\"themeToggle\" title=\"Toggle theme\">☀</button>\n</div>\n\n<div class=\"filters\">\n <div class=\"filter-group\"><label>From</label><input type=\"date\" id=\"dateStart\" value=\"${esc(dateStart)}\"></div>\n <div class=\"filter-group\"><label>To</label><input type=\"date\" id=\"dateEnd\" value=\"${esc(dateEnd)}\"></div>\n <div class=\"filter-group\"><label>Project</label><select id=\"projectFilter\"><option value=\"\">All Projects</option>${projectOptions}</select></div>\n <div class=\"filter-actions\"><button class=\"btn btn-apply\" id=\"btnApply\">Apply</button><button class=\"btn btn-reset\" id=\"btnReset\">Reset</button></div>\n</div>\n\n<div class=\"stats\">\n <div class=\"stat\"><div class=\"stat-label\">Total Cost</div><div class=\"stat-value\" id=\"statCost\" style=\"color:var(--accent)\"></div></div>\n <div class=\"stat\"><div class=\"stat-label\">Total Tokens</div><div class=\"stat-value\" id=\"statTokens\"></div></div>\n <div class=\"stat\"><div class=\"stat-label\">Total Requests</div><div class=\"stat-value\" id=\"statReqs\"></div></div>\n <div class=\"stat\"><div class=\"stat-label\">Active Sessions</div><div class=\"stat-value\" id=\"statSessions\"></div></div>\n</div>\n\n<div class=\"grid grid-1\"><div class=\"panel\"><div class=\"panel-title pt-blue\">Cost Over Time</div><div class=\"chart-container\" id=\"chartCost\" role=\"img\" aria-label=\"Bar chart showing daily cost over time with cumulative line\"></div></div></div>\n<div class=\"grid grid-2\">\n <div class=\"panel\"><div class=\"panel-title pt-cyan\">Input / Output Tokens</div><div class=\"chart-container\" id=\"chartIO\" role=\"img\" aria-label=\"Stacked bar chart of input and output tokens per day\"></div></div>\n <div class=\"panel\"><div class=\"panel-title pt-yellow\">Cache Tokens</div><div class=\"chart-container\" id=\"chartCache\" role=\"img\" aria-label=\"Stacked bar chart of cache write and read tokens per day\"></div></div>\n</div>\n<div class=\"grid grid-2-1\">\n <div class=\"panel\" id=\"projectPanel\"><div class=\"panel-title pt-accent\">Project Breakdown</div><div class=\"chart-container chart-tall\" id=\"chartProject\" style=\"height:${Math.max(280, data.projects.length * 44)}px\"></div></div>\n <div class=\"panel\"><div class=\"panel-title pt-green\">Model Distribution</div><div class=\"chart-container\" id=\"chartModel\" style=\"min-height:320px\"></div></div>\n</div>\n<div class=\"grid grid-1\"><div class=\"panel\"><div class=\"panel-title pt-green\">Cache Reuse Efficiency</div><div class=\"chart-container chart-sm\" id=\"chartCacheEff\"></div></div></div>\n\n<div class=\"grid grid-1\"><div class=\"panel\">\n <div class=\"panel-title pt-blue\">Usage Heatmap</div>\n <div style=\"color:var(--muted);font-size:.75rem;margin-bottom:10px\" id=\"heatmapDesc\">When do you use Claude the most? Each cell shows total tokens processed at that day-of-week + hour, aggregated across all dates in the range.</div>\n <div class=\"heatmap-wrap\"><div id=\"heatmap\" class=\"heatmap\"></div></div>\n <div style=\"display:flex;align-items:center;gap:8px;margin-top:10px;font-size:.7rem;color:var(--muted)\">\n <span>Less</span>\n <span style=\"width:14px;height:14px;border-radius:2px;background:var(--hm0)\"></span>\n <span style=\"width:14px;height:14px;border-radius:2px;background:var(--hm1)\"></span>\n <span style=\"width:14px;height:14px;border-radius:2px;background:var(--hm2)\"></span>\n <span style=\"width:14px;height:14px;border-radius:2px;background:var(--hm3)\"></span>\n <span style=\"width:14px;height:14px;border-radius:2px;background:var(--hm4)\"></span>\n <span style=\"width:14px;height:14px;border-radius:2px;background:var(--hm5)\"></span>\n <span>More</span>\n </div>\n</div></div>\n\n<div class=\"grid grid-1\"><div class=\"panel\">\n <div class=\"panel-title pt-accent\">Sessions <span id=\"sessCount\" style=\"color:var(--muted);font-weight:400;font-size:.75rem\"></span></div>\n <div class=\"tbl-wrap\">\n <table id=\"sessTable\">\n <thead><tr><th data-col=\"session\">Session</th><th data-col=\"project\">Project</th><th data-col=\"model\">Model</th><th data-col=\"duration\" class=\"text-right\">Duration</th><th data-col=\"requests\" class=\"text-right\">Requests</th><th data-col=\"tokens\" class=\"text-right\">Tokens</th><th data-col=\"cost\" class=\"text-right\">Cost</th></tr></thead>\n <tbody id=\"sessBody\"></tbody>\n </table>\n </div>\n</div></div>\n\n<div class=\"grid grid-1\"><div class=\"panel\">\n <div class=\"panel-title pt-red\">ROI Analysis</div>\n <div class=\"roi-grid\" id=\"roiCards\"></div>\n <div class=\"chart-container chart-sm\" id=\"chartROI\"></div>\n</div></div>\n\n<div class=\"footer\">Generated by cctrack &middot; <a href=\"https://github.com/azharuddinkhan3005/cctrack\" style=\"color:var(--accent)\">github</a></div>\n\n<script>\n(function(){\n // ── Helpers ──\n function fmt(n){if(n>=1e6)return(n/1e6).toFixed(1)+'M';if(n>=1e3)return(n/1e3).toFixed(1)+'K';return n.toLocaleString();}\n function fmtCost(n){return n<0.01&&n>0?'$'+n.toFixed(4):'$'+n.toFixed(2);}\n function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML;}\n function cumul(arr){var r=[],s=0;arr.forEach(function(v){s+=v;r.push(s);});return r;}\n function isDark(){return!document.documentElement.classList.contains('light');}\n function duration(start,end){var ms=new Date(end)-new Date(start);var s=Math.floor(ms/1000);if(s<60)return s+'s';var m=Math.floor(s/60);if(m<60)return m+'m '+s%60+'s';var h=Math.floor(m/60);return h+'h '+m%60+'m';}\n\n var allData=DATA;\n var charts={};\n var COLORS=['#6366f1','#06b6d4','#22c55e','#eab308','#f97316','#ef4444','#ec4899','#8b5cf6','#14b8a6','#84cc16'];\n\n // ── Theme ──\n function textColor(){return isDark()?'#94a3b8':'#64748b';}\n function gridColor(){return isDark()?'rgba(148,163,184,0.08)':'rgba(148,163,184,0.15)';}\n function tooltipBg(){return isDark()?'rgba(15,23,42,0.95)':'rgba(255,255,255,0.95)';}\n function tooltipBorder(){return isDark()?'#334155':'#e2e8f0';}\n function tooltipText(){return isDark()?'#e2e8f0':'#1e293b';}\n\n function baseTooltip(){\n return{trigger:'axis',confine:true,appendToBody:true,backgroundColor:tooltipBg(),borderColor:tooltipBorder(),textStyle:{color:tooltipText(),fontSize:12}};\n }\n function itemTooltip(){\n return{trigger:'item',confine:true,appendToBody:true,backgroundColor:tooltipBg(),borderColor:tooltipBorder(),textStyle:{color:tooltipText(),fontSize:12}};\n }\n\n // ── Chart Init ──\n function initChart(id,option){\n var dom=document.getElementById(id);\n if(!dom)return null;\n var c=echarts.init(dom,isDark()?'dark':null);\n // Enable ARIA for screen readers\n option.aria={enabled:true,decal:{show:false}};\n c.setOption(option);\n charts[id]=c;\n return c;\n }\n\n // ── Stats ──\n function updateStats(totals,sessionCount){\n document.getElementById('statCost').textContent=fmtCost(totals.cost.total_cost);\n document.getElementById('statTokens').textContent=fmt(totals.tokens.total_tokens);\n document.getElementById('statReqs').textContent=totals.request_count.toLocaleString();\n document.getElementById('statSessions').textContent=sessionCount.toLocaleString();\n }\n\n // ── Chart Option Builders ──\n function costOption(daily){\n var labels=daily.map(function(d){return d.date;});\n var costs=daily.map(function(d){return d.cost?d.cost.total_cost:d.cost_val||0;});\n var cum=cumul(costs);\n return{\n tooltip:Object.assign(baseTooltip(),{formatter:function(p){\n var h='<b>'+p[0].axisValueLabel+'</b><br>';\n p.forEach(function(i){h+='<span style=\"display:inline-block;width:8px;height:8px;border-radius:50%;background:'+i.color+';margin-right:6px\"></span>'+i.seriesName+': <b>'+fmtCost(i.value)+'</b><br>';});\n return h;\n }}),\n grid:{left:'3%',right:'3%',top:35,bottom:15,containLabel:true},\n xAxis:{type:'category',data:labels,axisLabel:{color:textColor(),rotate:labels.length>7?45:0,hideOverlap:true},axisLine:{lineStyle:{color:gridColor()}}},\n yAxis:[\n {type:'value',name:'Daily ($)',nameTextStyle:{color:textColor(),padding:[0,0,0,40]},axisLabel:{formatter:function(v){return fmtCost(v);},color:textColor()},splitLine:{lineStyle:{color:gridColor()}}},\n {type:'value',name:'Cumulative ($)',nameTextStyle:{color:textColor(),padding:[0,40,0,0]},axisLabel:{formatter:function(v){return fmtCost(v);},color:textColor()},splitLine:{show:false}}\n ],\n series:[\n {name:'Daily Cost',type:'bar',data:costs,barMaxWidth:50,itemStyle:{color:'#6366f1',borderRadius:[4,4,0,0]},emphasis:{itemStyle:{color:'#818cf8'}}},\n {name:'Cumulative',type:'line',yAxisIndex:1,data:cum,smooth:true,symbol:costs.length<=2?'circle':'none',symbolSize:8,lineStyle:{color:'#06b6d4',type:'dashed',width:2},itemStyle:{color:'#06b6d4'}}\n ]\n };\n }\n\n function ioOption(daily){\n var labels=daily.map(function(d){return d.date;});\n return{\n tooltip:Object.assign(baseTooltip(),{formatter:function(p){\n var h='<b>'+p[0].axisValueLabel+'</b><br>';\n p.forEach(function(i){h+='<span style=\"display:inline-block;width:8px;height:8px;border-radius:50%;background:'+i.color+';margin-right:6px\"></span>'+i.seriesName+': <b>'+fmt(i.value)+'</b><br>';});\n return h;\n }}),\n grid:{left:'3%',right:'3%',top:35,bottom:15,containLabel:true},\n xAxis:{type:'category',data:labels,axisLabel:{color:textColor(),rotate:labels.length>7?45:0,hideOverlap:true},axisLine:{lineStyle:{color:gridColor()}}},\n yAxis:{type:'value',name:'Tokens',nameTextStyle:{color:textColor(),padding:[0,0,0,30]},axisLabel:{formatter:function(v){return fmt(v);},color:textColor()},splitLine:{lineStyle:{color:gridColor()}}},\n series:[\n {name:'Input',type:'bar',stack:'io',data:daily.map(function(d){return d.tokens?d.tokens.input_tokens:d.input||0;}),barMaxWidth:50,itemStyle:{color:'#3b82f6'}},\n {name:'Output',type:'bar',stack:'io',data:daily.map(function(d){return d.tokens?d.tokens.output_tokens:d.output||0;}),barMaxWidth:50,itemStyle:{color:'#06b6d4'}}\n ]\n };\n }\n\n function cacheOption(daily){\n var labels=daily.map(function(d){return d.date;});\n return{\n tooltip:Object.assign(baseTooltip(),{formatter:function(p){\n var h='<b>'+p[0].axisValueLabel+'</b><br>';\n p.forEach(function(i){h+='<span style=\"display:inline-block;width:8px;height:8px;border-radius:50%;background:'+i.color+';margin-right:6px\"></span>'+i.seriesName+': <b>'+fmt(i.value)+'</b><br>';});\n return h;\n }}),\n grid:{left:'3%',right:'3%',top:35,bottom:15,containLabel:true},\n xAxis:{type:'category',data:labels,axisLabel:{color:textColor(),rotate:labels.length>7?45:0,hideOverlap:true},axisLine:{lineStyle:{color:gridColor()}}},\n yAxis:{type:'value',name:'Cache Tokens',nameTextStyle:{color:textColor(),padding:[0,0,0,30]},axisLabel:{formatter:function(v){return fmt(v);},color:textColor()},splitLine:{lineStyle:{color:gridColor()}}},\n series:[\n {name:'Cache Write',type:'bar',stack:'cache',data:daily.map(function(d){return d.tokens?d.tokens.cache_write_tokens:d.cw||0;}),barMaxWidth:50,itemStyle:{color:'#eab308'}},\n {name:'Cache Read',type:'bar',stack:'cache',data:daily.map(function(d){return d.tokens?d.tokens.cache_read_tokens:d.cr||0;}),barMaxWidth:50,itemStyle:{color:'#22c55e'}}\n ]\n };\n }\n\n function projectOption(projects){\n var sorted=projects.slice().sort(function(a,b){return a.cost.total_cost-b.cost.total_cost;});\n var maxLabelLen=Math.max.apply(null,sorted.map(function(p){return p.project.length;}));\n return{\n tooltip:Object.assign(itemTooltip(),{formatter:function(p){return'<b>'+esc(p.name)+'</b><br>Cost: <b>'+fmtCost(p.value)+'</b>';}}),\n grid:{left:'3%',right:'8%',top:20,bottom:20,containLabel:true},\n xAxis:{type:'value',axisLabel:{formatter:function(v){return fmtCost(v);},color:textColor()},splitLine:{lineStyle:{color:gridColor()}}},\n yAxis:{type:'category',data:sorted.map(function(p){return p.project;}),axisLabel:{color:textColor(),width:150,overflow:'truncate'},axisLine:{lineStyle:{color:gridColor()}}},\n series:[{type:'bar',data:sorted.map(function(p,i){return{value:p.cost.total_cost,itemStyle:{color:COLORS[i%COLORS.length],borderRadius:[0,4,4,0]}};}),barMaxWidth:28,\n label:{show:true,position:'right',formatter:function(p){return fmtCost(p.value);},color:textColor(),fontSize:11},\n emphasis:{itemStyle:{shadowBlur:4}}}]\n };\n }\n\n function modelOption(models){\n var entries=Object.entries(models).sort(function(a,b){return b[1].cost.total_cost-a[1].cost.total_cost;});\n return{\n tooltip:Object.assign(itemTooltip(),{formatter:function(p){return'<b>'+esc(p.name)+'</b><br>'+fmtCost(p.value)+' ('+p.percent.toFixed(1)+'%)';}}),\n legend:{orient:'horizontal',bottom:0,textStyle:{color:textColor(),fontSize:11},itemWidth:12,itemHeight:12},\n series:[{type:'pie',radius:['40%','70%'],center:['50%','45%'],\n label:{show:true,formatter:function(p){return p.percent.toFixed(1)+'%';},color:textColor(),fontSize:11},\n labelLine:{show:true,lineStyle:{color:textColor()}},\n emphasis:{label:{show:true,fontSize:14,fontWeight:'bold'}},\n data:entries.map(function(e,i){return{name:e[0],value:e[1].cost.total_cost,itemStyle:{color:COLORS[i%COLORS.length]}};})}]\n };\n }\n\n function cacheEffOption(daily){\n var labels=daily.map(function(d){return d.date;});\n var data=daily.map(function(d){\n var cr=d.tokens?d.tokens.cache_read_tokens:(d.cr||0);\n var cw=d.tokens?d.tokens.cache_write_tokens:(d.cw||0);\n var denom=cr+cw;return denom>0?(cr/denom)*100:0;\n });\n return{\n tooltip:Object.assign(baseTooltip(),{formatter:function(p){return'<b>'+p[0].axisValueLabel+'</b><br>Cache Reuse: <b>'+p[0].value.toFixed(1)+'%</b>';}}),\n grid:{left:'3%',right:'3%',top:20,bottom:15,containLabel:true},\n xAxis:{type:'category',data:labels,axisLabel:{color:textColor(),rotate:labels.length>7?45:0,hideOverlap:true},axisLine:{lineStyle:{color:gridColor()}}},\n yAxis:{type:'value',min:0,max:100,axisLabel:{formatter:function(v){return v+'%';},color:textColor()},splitLine:{lineStyle:{color:gridColor()}}},\n series:[{type:'line',data:data,smooth:true,symbol:data.length<=2?'circle':'none',symbolSize:8,areaStyle:{color:{type:'linear',x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:'rgba(34,197,94,0.3)'},{offset:1,color:'rgba(34,197,94,0.02)'}]}},lineStyle:{color:'#22c55e',width:2},itemStyle:{color:'#22c55e'}}]\n };\n }\n\n function roiOption(totalCost,days){\n var monthly=days>0?(totalCost/days)*30:0;\n return{\n tooltip:Object.assign(baseTooltip(),{trigger:'axis'}),\n grid:{left:'3%',right:'3%',top:25,bottom:15,containLabel:true},\n xAxis:{type:'category',data:['Projected\\\\nMonthly','Pro\\\\n$20/mo','Max 5x\\\\n$100/mo','Max 20x\\\\n$200/mo'],axisLabel:{color:textColor(),interval:0,rotate:0},axisLine:{lineStyle:{color:gridColor()}}},\n yAxis:{type:'value',axisLabel:{formatter:function(v){return fmtCost(v);},color:textColor()},splitLine:{lineStyle:{color:gridColor()}}},\n series:[{type:'bar',barMaxWidth:60,data:[\n {value:monthly,itemStyle:{color:'#6366f1'}},\n {value:20,itemStyle:{color:'#22c55e'}},\n {value:100,itemStyle:{color:'#eab308'}},\n {value:200,itemStyle:{color:'#ef4444'}}\n ],label:{show:true,position:'top',formatter:function(p){return fmtCost(p.value);},color:textColor(),fontSize:11}}]\n };\n }\n\n // ── Heatmap ──\n function renderHeatmap(hm){\n var el=document.getElementById('heatmap');el.innerHTML='';\n var days=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];\n var maxVal=Math.max(1,Math.max.apply(null,hm.map(function(r){return Math.max.apply(null,r);})));\n var levels=['var(--hm0)','var(--hm1)','var(--hm2)','var(--hm3)','var(--hm4)','var(--hm5)'];\n function cellBg(v){if(v===0)return levels[0];var p=v/maxVal;if(p<0.15)return levels[1];if(p<0.35)return levels[2];if(p<0.55)return levels[3];if(p<0.75)return levels[4];return levels[5];}\n // Header\n var corner=document.createElement('div');el.appendChild(corner);\n for(var h=0;h<24;h++){var hd=document.createElement('div');hd.className='hm-label';hd.style.justifyContent='center';hd.textContent=h;el.appendChild(hd);}\n for(var d=0;d<7;d++){\n var lbl=document.createElement('div');lbl.className='hm-label';lbl.textContent=days[d];el.appendChild(lbl);\n for(var h2=0;h2<24;h2++){\n var cell=document.createElement('div');cell.className='hm-cell';cell.style.background=cellBg(hm[d][h2]);\n var tip=document.createElement('div');tip.className='hm-tip';tip.textContent=days[d]+' '+h2+':00 — '+fmt(hm[d][h2])+' tokens';\n cell.appendChild(tip);el.appendChild(cell);\n }\n }\n }\n\n // ── Session Table ──\n var sortCol='cost',sortDir=-1;\n function renderSessions(sessions){\n var tbody=document.getElementById('sessBody');tbody.innerHTML='';\n var showing=Math.min(sessions.length,100);\n document.getElementById('sessCount').textContent='(showing '+showing+' of '+sessions.length+')';\n var sorted=sessions.slice().sort(function(a,b){\n var va,vb;\n switch(sortCol){\n case'session':va=a.sessionId;vb=b.sessionId;break;\n case'project':va=a.project;vb=b.project;break;\n case'model':va=a.primaryModel;vb=b.primaryModel;break;\n case'duration':va=new Date(a.endTime)-new Date(a.startTime);vb=new Date(b.endTime)-new Date(b.startTime);break;\n case'requests':va=a.request_count;vb=b.request_count;break;\n case'tokens':va=a.tokens.total_tokens;vb=b.tokens.total_tokens;break;\n case'cost':va=a.cost.total_cost;vb=b.cost.total_cost;break;\n default:va=0;vb=0;\n }\n if(typeof va==='string')return sortDir*va.localeCompare(vb);return sortDir*(va-vb);\n }).slice(0,100);\n sorted.forEach(function(s){\n var tr=document.createElement('tr');\n tr.innerHTML='<td class=\"text-mono\">'+esc(s.sessionId.slice(0,14))+'...</td>'\n +'<td>'+esc(s.project)+'</td>'\n +'<td class=\"text-mono\">'+esc(s.primaryModel)+(Object.keys(s.models||{}).length>1?' <span style=\"color:var(--muted);font-size:.65rem\">+'+String(Object.keys(s.models).length-1)+'</span>':'')+'</td>'\n +'<td class=\"text-right text-mono\">'+duration(s.startTime,s.endTime)+'</td>'\n +'<td class=\"text-right\">'+s.request_count+'</td>'\n +'<td class=\"text-right\">'+fmt(s.tokens.total_tokens)+'</td>'\n +'<td class=\"text-right\" style=\"font-weight:600\">'+fmtCost(s.cost.total_cost)+'</td>';\n tbody.appendChild(tr);\n });\n }\n\n // ── ROI ──\n function renderROI(totals,days){\n var tc=totals.cost.total_cost;var avgD=days>0?tc/days:0;var projM=avgD*30;\n var cpr=totals.request_count>0?tc/totals.request_count:0;\n var cp1k=totals.tokens.total_tokens>0?(tc/totals.tokens.total_tokens)*1000:0;\n var cacheSave=totals.cost.cache_read_cost*9;\n var cards=[\n {label:'Avg Daily Cost',value:fmtCost(avgD),sub:'Projected monthly: '+fmtCost(projM),cls:'green'},\n {label:'Cost Per Request',value:fmtCost(cpr),sub:totals.request_count.toLocaleString()+' total requests',cls:''},\n {label:'Cache Savings',value:'~'+fmtCost(cacheSave),sub:((totals.tokens.cache_read_tokens/(totals.tokens.cache_read_tokens+totals.tokens.input_tokens||1))*100).toFixed(1)+'% cache hit',cls:'green'},\n {label:'Cost Per 1K Tokens',value:fmtCost(cp1k),sub:fmt(totals.tokens.total_tokens)+' total tokens',cls:''}\n ];\n var el=document.getElementById('roiCards');el.innerHTML='';\n cards.forEach(function(c){\n el.innerHTML+='<div class=\"roi-card\"><div class=\"roi-label\">'+c.label+'</div><div class=\"roi-value'+(c.cls?' '+c.cls:'')+'\">'+c.value+'</div><div class=\"roi-sub\">'+c.sub+'</div></div>';\n });\n if(charts['chartROI'])charts['chartROI'].setOption(roiOption(tc,days),{notMerge:true});\n }\n\n // ── Initialize Everything ──\n updateStats(allData.totals,allData.sessions.length);\n initChart('chartCost',costOption(allData.daily));\n initChart('chartIO',ioOption(allData.daily));\n initChart('chartCache',cacheOption(allData.daily));\n initChart('chartProject',projectOption(allData.projects));\n initChart('chartModel',modelOption(allData.models));\n initChart('chartCacheEff',cacheEffOption(allData.daily));\n initChart('chartROI',roiOption(allData.totals.cost.total_cost,allData.daily.length));\n renderHeatmap(allData.heatmap);\n // Show timezone in heatmap description\n try{var tz=Intl.DateTimeFormat().resolvedOptions().timeZone;document.getElementById('heatmapDesc').textContent+=' Hours shown in UTC (your timezone: '+tz+').'}catch(e){}\n renderSessions(allData.sessions);\n renderROI(allData.totals,allData.daily.length);\n\n // Sort headers\n document.querySelectorAll('#sessTable th').forEach(function(th){\n th.addEventListener('click',function(){\n var col=th.dataset.col;if(!col)return;\n sortDir=sortCol===col?sortDir*-1:-1;sortCol=col;\n renderSessions(currentSessions);\n });\n });\n\n // ── Filtering ──\n var currentSessions=allData.sessions;\n function applyFilters(){\n var s=document.getElementById('dateStart').value;\n var e=document.getElementById('dateEnd').value;\n var p=document.getElementById('projectFilter').value;\n\n // Filter daily by date\n var fDaily=allData.daily.filter(function(d){\n if(s&&d.date<s)return false;if(e&&d.date>e)return false;return true;\n });\n\n // Filter sessions\n var fSessions=allData.sessions.filter(function(ss){\n if(p&&ss.project!==p)return false;\n var d=ss.startTime.slice(0,10);if(s&&d<s)return false;if(e&&d>e)return false;return true;\n });\n currentSessions=fSessions;\n\n // Build chart daily data: use per-project breakdown if project filter active\n var chartDaily;\n if(p){\n chartDaily=fDaily.filter(function(d){return d.projects&&d.projects[p];}).map(function(d){var pp=d.projects[p];return{date:d.date,cost:pp.cost,tokens:pp.tokens,request_count:pp.request_count};});\n } else {\n chartDaily=fDaily;\n }\n\n // Compute totals\n var totals={tokens:{input_tokens:0,output_tokens:0,cache_write_tokens:0,cache_read_tokens:0,total_tokens:0},cost:{input_cost:0,output_cost:0,cache_write_cost:0,cache_read_cost:0,total_cost:0},request_count:0};\n chartDaily.forEach(function(d){\n var t=d.tokens,c=d.cost;\n totals.tokens.input_tokens+=t.input_tokens;totals.tokens.output_tokens+=t.output_tokens;\n totals.tokens.cache_write_tokens+=t.cache_write_tokens;totals.tokens.cache_read_tokens+=t.cache_read_tokens;\n totals.tokens.total_tokens+=t.total_tokens;\n totals.cost.input_cost+=c.input_cost||0;totals.cost.output_cost+=c.output_cost||0;\n totals.cost.cache_write_cost+=c.cache_write_cost||0;totals.cost.cache_read_cost+=c.cache_read_cost||0;\n totals.cost.total_cost+=c.total_cost;totals.request_count+=(d.request_count||0);\n });\n\n updateStats(totals,fSessions.length);\n\n // Update all charts via setOption (no destroy!)\n charts['chartCost'].setOption(costOption(chartDaily),{notMerge:true});\n charts['chartIO'].setOption(ioOption(chartDaily),{notMerge:true});\n charts['chartCache'].setOption(cacheOption(chartDaily),{notMerge:true});\n charts['chartCacheEff'].setOption(cacheEffOption(chartDaily),{notMerge:true});\n\n // Model: always aggregate from filtered sessions so date+project filters both apply\n var fm={};fSessions.forEach(function(ss){if(ss.models)Object.entries(ss.models).forEach(function(en){if(!fm[en[0]])fm[en[0]]={cost:{total_cost:0}};fm[en[0]].cost.total_cost+=en[1].cost.total_cost;});});\n charts['chartModel'].setOption(modelOption(fm),{notMerge:true});\n\n // Project: hide when single project selected, show otherwise\n var projPanel=document.getElementById('projectPanel');\n if(p){\n projPanel.style.display='none';\n } else {\n projPanel.style.display='';\n var fp=allData.projects.filter(function(pp){if(s||e){var hasDays=fDaily.some(function(d){return d.projects&&d.projects[pp.project];});return hasDays;}return true;});\n charts['chartProject'].setOption(projectOption(fp),{notMerge:true});\n }\n\n // Heatmap: rebuild from filtered sessions so date+project filters both apply\n var hasFilter=s||e||p;\n if(hasFilter){\n var hm=[];for(var di=0;di<7;di++){hm[di]=[];for(var hi=0;hi<24;hi++)hm[di][hi]=0;}\n fSessions.forEach(function(ss){\n var dt=new Date(ss.startTime);var day=dt.getDay();var hr=dt.getHours();\n hm[day][hr]+=(ss.tokens?ss.tokens.total_tokens:0);\n });\n renderHeatmap(hm);\n } else {\n renderHeatmap(allData.heatmap);\n }\n\n renderSessions(fSessions);\n renderROI(totals,chartDaily.length);\n }\n\n document.getElementById('btnApply').addEventListener('click',applyFilters);\n document.getElementById('dateStart').addEventListener('change',applyFilters);\n document.getElementById('dateEnd').addEventListener('change',applyFilters);\n document.getElementById('projectFilter').addEventListener('change',applyFilters);\n document.getElementById('btnReset').addEventListener('click',function(){\n document.getElementById('dateStart').value='${esc(dateStart)}';\n document.getElementById('dateEnd').value='${esc(dateEnd)}';\n document.getElementById('projectFilter').value='';\n applyFilters();\n });\n\n // ── Theme Toggle ──\n document.getElementById('themeToggle').addEventListener('click',function(){\n document.documentElement.classList.toggle('light');\n this.textContent=isDark()?'☀':'🌙';\n // Rebuild all charts from scratch with correct theme (getOption carries stale colors)\n Object.keys(charts).forEach(function(id){\n charts[id].dispose();\n charts[id]=echarts.init(document.getElementById(id),isDark()?'dark':null);\n });\n // Re-trigger current filter state to rebuild all chart options with fresh colors\n applyFilters();\n });\n\n // ── Resize ──\n window.addEventListener('resize',function(){Object.values(charts).forEach(function(c){c.resize();});});\n\n // Convert charts to static images before printing so they don't go blank\n window.addEventListener('beforeprint',function(){\n Object.keys(charts).forEach(function(id){\n var c=charts[id];if(!c)return;\n var url=c.getDataURL({type:'png',pixelRatio:2,backgroundColor:isDark()?'#1e293b':'#ffffff'});\n var dom=document.getElementById(id);if(!dom)return;\n var img=document.createElement('img');img.src=url;img.className='chart-print-img';img.style.width='100%';\n dom.style.display='none';dom.parentNode.insertBefore(img,dom);\n });\n });\n window.addEventListener('afterprint',function(){\n document.querySelectorAll('.chart-print-img').forEach(function(img){\n var dom=img.nextElementSibling;if(dom)dom.style.display='';\n img.remove();\n });\n });\n})();\n<\\/script>\n</body>\n</html>`;\n}\n\nfunction openInBrowser(filePath: string): void {\n const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';\n import('node:child_process').then(({ execFile }) => {\n execFile(cmd, [filePath], () => {});\n });\n}\n\nexport async function dashboardAction(opts: {\n save?: string;\n json?: boolean;\n since?: string;\n until?: string;\n project?: string;\n mode?: string;\n timezone?: string;\n}): Promise<void> {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n timezone: opts.timezone,\n });\n\n if (filtered.length === 0) {\n console.log(chalk.yellow('No data found.'));\n return;\n }\n\n const mode = parseCostMode(opts.mode);\n const data = buildDashboardData(filtered, mode, opts.timezone);\n\n if (opts.json) {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n const html = generateHtml(data);\n\n if (opts.save) {\n const { resolve, normalize } = await import('node:path');\n const { realpathSync } = await import('node:fs');\n const savePath = resolve(opts.save);\n const home = homedir();\n const cwd = process.cwd();\n // Prevent writes to root or system directories\n if (cwd === '/' || savePath === '/') {\n console.log(chalk.red('Refusing to write to root directory.'));\n return;\n }\n // Resolve symlinks to catch symlink escapes\n let realSavePath = savePath;\n try {\n const parentDir = dirname(savePath);\n if (existsSync(parentDir)) realSavePath = resolve(realpathSync(parentDir), savePath.split('/').pop()!);\n } catch {}\n if (!realSavePath.startsWith(home) && !realSavePath.startsWith(cwd) && !realSavePath.startsWith('/tmp') && !realSavePath.startsWith('/private/tmp')) {\n console.log(chalk.red(`Refusing to write outside home directory, cwd, or /tmp: ${realSavePath}`));\n return;\n }\n mkdirSync(dirname(savePath), { recursive: true });\n writeFileSync(savePath, html, 'utf-8');\n console.log(chalk.green(`Dashboard saved to: ${savePath}`));\n return;\n }\n\n const defaultDir = join(homedir(), '.cctrack');\n const defaultPath = join(defaultDir, 'dashboard.html');\n mkdirSync(defaultDir, { recursive: true });\n writeFileSync(defaultPath, html, 'utf-8');\n console.log(chalk.green(`Dashboard saved to: ${defaultPath}`));\n openInBrowser(defaultPath);\n}\n\nexport function registerDashboardCommand(program: Command): void {\n program\n .command('dashboard')\n .description('Generate and open interactive HTML dashboard')\n .option('--save <path>', 'Save to custom path (does not auto-open)')\n .option('--json', 'Output dashboard data as JSON instead of HTML')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--project <name>', 'Filter by project name')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for date grouping')\n .action(dashboardAction);\n}\n","import type { Command } from 'commander';\nimport type { CostMode, UsageEntry } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles, extractProjectName } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries, buildDashboardData } from '../core/aggregator.js';\nimport { processEntry } from '../core/calculator.js';\nimport { csvEscape, parseCostMode } from '../utils/format.js';\n\nfunction entriesToCsv(entries: UsageEntry[], mode: CostMode): string {\n const lines: string[] = [];\n lines.push(\n 'date,session_id,project,model,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,cost_calculated,cost_embedded,request_id',\n );\n\n for (const entry of entries) {\n const date = entry.timestamp.slice(0, 10);\n const sessionId = entry.sessionId ?? '';\n const project = entry.cwd ? extractProjectName(entry.cwd) : '';\n const model = entry.message.model ?? 'unknown';\n const usage = entry.message.usage;\n const result = processEntry(entry, mode);\n\n lines.push(\n [\n date,\n csvEscape(sessionId),\n csvEscape(project),\n csvEscape(model),\n usage.input_tokens,\n usage.output_tokens,\n usage.cache_creation_input_tokens ?? 0,\n usage.cache_read_input_tokens ?? 0,\n result.calculatedCost.total_cost.toFixed(6),\n entry.costUSD?.toFixed(6) ?? '',\n entry.requestId ?? '',\n ].join(','),\n );\n }\n\n return lines.join('\\n');\n}\n\nexport function registerExportCommand(program: Command): void {\n const exportCmd = program.command('export').description('Export usage data');\n\n exportCmd\n .command('csv')\n .description('Export flat CSV with one row per request')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--project <name>', 'Filter by project name')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for filtering')\n .action(async (opts) => {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n timezone: opts.timezone,\n });\n\n // Sort chronologically for export\n const sorted = [...filtered].sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n const mode = parseCostMode(opts.mode);\n console.log(entriesToCsv(sorted, mode));\n });\n\n exportCmd\n .command('json')\n .description('Export structured JSON matching dashboard data format')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--project <name>', 'Filter by project name')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for date grouping')\n .action(async (opts) => {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n timezone: opts.timezone,\n });\n\n const mode = parseCostMode(opts.mode);\n const data = buildDashboardData(filtered, mode, opts.timezone);\n console.log(JSON.stringify(data, null, 2));\n });\n}\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport type { CostMode, SubscriptionPlan } from '../core/types.js';\nimport { PLAN_COSTS } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries } from '../core/aggregator.js';\nimport { processEntry } from '../core/calculator.js';\nimport { formatCost, parseCostMode } from '../utils/format.js';\n\nexport function registerRoiCommand(program: Command): void {\n program\n .command('roi')\n .description('Calculate ROI vs API-equivalent cost')\n .option('--plan <plan>', 'Subscription plan: pro ($20), max5 ($100), max20 ($200)', 'max5')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--project <name>', 'Filter by project name')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for filtering')\n .option('--json', 'Output as JSON')\n .action(async (opts) => {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n timezone: opts.timezone,\n });\n\n if (filtered.length === 0) {\n console.log(chalk.yellow('No data found for the specified range.'));\n return;\n }\n\n // Accept fuzzy plan names\n const planAliases: Record<string, SubscriptionPlan> = {\n pro: 'pro', '20': 'pro',\n max5: 'max5', 'max-5x': 'max5', '100': 'max5', max: 'max5',\n max20: 'max20', 'max-20x': 'max20', '200': 'max20',\n };\n const plan = planAliases[opts.plan?.toLowerCase() ?? 'max5'] as SubscriptionPlan | undefined;\n if (!plan) {\n console.error(chalk.red(`Unknown plan: ${opts.plan}. Choose from: pro ($20), max5 ($100), max20 ($200)`));\n process.exit(1);\n }\n\n const mode = parseCostMode(opts.mode);\n const subscriptionCost = PLAN_COSTS[plan];\n\n // Calculate totals\n let totalApiCost = 0;\n let totalInputTokens = 0;\n let totalOutputTokens = 0;\n let totalCacheWriteTokens = 0;\n let totalCacheReadTokens = 0;\n let totalTokens = 0;\n\n for (const entry of filtered) {\n const result = processEntry(entry, mode);\n totalApiCost += result.calculatedCost.total_cost;\n totalInputTokens += result.tokens.input_tokens;\n totalOutputTokens += result.tokens.output_tokens;\n totalCacheWriteTokens += result.tokens.cache_write_tokens;\n totalCacheReadTokens += result.tokens.cache_read_tokens;\n totalTokens += result.tokens.total_tokens;\n }\n\n // Calculate date range for projections\n const sorted = [...filtered].sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n const firstDate = new Date(sorted[0].timestamp);\n const lastDate = new Date(sorted[sorted.length - 1].timestamp);\n const daysSpan = Math.max(1, Math.ceil((lastDate.getTime() - firstDate.getTime()) / (1000 * 60 * 60 * 24)) + 1);\n\n const avgDailyCost = totalApiCost / daysSpan;\n const projectedMonthlyCost = avgDailyCost * 30;\n const savings = totalApiCost - subscriptionCost;\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n {\n plan,\n subscription_cost: subscriptionCost,\n api_equivalent_cost: totalApiCost,\n savings,\n savings_percentage: totalApiCost > 0 ? ((savings / totalApiCost) * 100) : 0,\n days_analyzed: daysSpan,\n avg_daily_cost: avgDailyCost,\n projected_monthly_cost: projectedMonthlyCost,\n total_tokens: totalTokens,\n token_breakdown: {\n input: totalInputTokens,\n output: totalOutputTokens,\n cache_write: totalCacheWriteTokens,\n cache_read: totalCacheReadTokens,\n },\n request_count: filtered.length,\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n // Terminal output\n console.log(chalk.bold('\\n ROI Analysis\\n'));\n console.log(chalk.dim(` Plan: ${plan} (${formatCost(subscriptionCost)}/mo)`));\n console.log(chalk.dim(` Period: ${sorted[0].timestamp.slice(0, 10)} to ${sorted[sorted.length - 1].timestamp.slice(0, 10)} (${daysSpan} days)`));\n console.log(chalk.dim(` Requests: ${filtered.length.toLocaleString()}\\n`));\n\n const w = 24;\n console.log(` ${'Total tokens'.padEnd(w)} ${chalk.white(totalTokens.toLocaleString())}`);\n console.log(` ${'API-equivalent cost'.padEnd(w)} ${chalk.white(formatCost(totalApiCost))}`);\n console.log(` ${'Subscription cost'.padEnd(w)} ${chalk.white(formatCost(subscriptionCost))}`);\n\n if (savings > 0) {\n console.log(` ${'Savings'.padEnd(w)} ${chalk.green(formatCost(savings))} ${chalk.green(`(${((savings / totalApiCost) * 100).toFixed(0)}%)`)}`);\n } else {\n const loss = Math.abs(savings);\n console.log(` ${'Loss'.padEnd(w)} ${chalk.red(formatCost(loss))} ${chalk.red('(subscription > API cost)')}`);\n }\n\n console.log('');\n console.log(` ${'Avg daily API cost'.padEnd(w)} ${chalk.white(formatCost(avgDailyCost))}`);\n console.log(` ${'Projected monthly'.padEnd(w)} ${chalk.white(formatCost(projectedMonthlyCost))}`);\n\n // Break-even indicator\n if (projectedMonthlyCost > subscriptionCost) {\n console.log(chalk.green(`\\n Subscription is worth it at projected usage.`));\n } else {\n console.log(chalk.yellow(`\\n API costs are lower than your subscription at current usage.`));\n }\n\n console.log('');\n });\n}\n","import type { Command } from 'commander';\nimport { statSync } from 'node:fs';\nimport chalk from 'chalk';\nimport type { CostMode } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries, aggregateDaily, aggregateSessions } from '../core/aggregator.js';\nimport { formatCost, formatTokens, formatDuration, parseCostMode } from '../utils/format.js';\nimport { calculateBurnRate } from '../core/burnrate.js';\nimport { loadBudgetConfig, calculateBudgetStatus, formatBudgetBar } from '../core/budget.js';\n\nfunction clearScreen(): void {\n process.stdout.write('\\x1b[2J\\x1b[H');\n}\n\nasync function loadAndDisplay(mode: CostMode, project?: string, timezone?: string): Promise<void> {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n\n if (files.length === 0) {\n console.log(chalk.yellow('No JSONL files found. Waiting for data...'));\n return;\n }\n\n const { entries, errors } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n\n const today = new Date().toISOString().slice(0, 10);\n const filtered = filterEntries(unique, { since: today, project, timezone });\n\n clearScreen();\n\n console.log(chalk.bold.cyan(' CCTrack Live Monitor'));\n console.log(chalk.dim(` ${new Date().toLocaleTimeString()} | ${unique.length} usage entries | ${files.length} files\\n`));\n\n // Today's summary\n const dailyData = aggregateDaily(filtered, mode, timezone);\n const todayData = dailyData.find((d) => d.date === today);\n\n if (todayData) {\n console.log(chalk.bold(' Today'));\n console.log(` Requests: ${chalk.white(todayData.request_count.toLocaleString())}`);\n console.log(` Input: ${chalk.white(formatTokens(todayData.tokens.input_tokens))}`);\n console.log(` Output: ${chalk.white(formatTokens(todayData.tokens.output_tokens))}`);\n console.log(` Cache Write: ${chalk.white(formatTokens(todayData.tokens.cache_write_tokens))}`);\n console.log(` Cache Read: ${chalk.white(formatTokens(todayData.tokens.cache_read_tokens))}`);\n console.log(` Total: ${chalk.white(formatTokens(todayData.tokens.total_tokens))}`);\n console.log(` Cost: ${chalk.white(formatCost(todayData.cost.total_cost))}`);\n\n // Burn rate\n const rate = calculateBurnRate(filtered, mode);\n if (!rate.insufficient_data) {\n console.log(` Burn rate: ${chalk.dim(formatCost(rate.hourly_cost) + '/hr → ' + formatCost(rate.projected_monthly) + '/month projected')}`);\n }\n } else {\n console.log(chalk.dim(' No activity today yet.'));\n }\n\n // Active sessions (from today's data)\n const sessions = aggregateSessions(filtered, mode);\n if (sessions.length > 0) {\n console.log(chalk.bold('\\n Recent Sessions'));\n const recent = sessions.slice(0, 5);\n for (const s of recent) {\n const duration = new Date(s.endTime).getTime() - new Date(s.startTime).getTime();\n const id = s.sessionId.length > 12 ? s.sessionId.slice(0, 12) + '...' : s.sessionId;\n console.log(\n ` ${chalk.dim(id)} ${chalk.white(s.primaryModel)} ${chalk.dim(formatDuration(duration))} ${formatTokens(s.tokens.total_tokens)} ${chalk.white(formatCost(s.cost.total_cost))}`,\n );\n }\n }\n\n // Model breakdown for today\n if (todayData && Object.keys(todayData.models).length > 0) {\n console.log(chalk.bold('\\n Models Today'));\n for (const [model, agg] of Object.entries(todayData.models)) {\n console.log(\n ` ${chalk.white(model)} ${chalk.dim('|')} ${agg.request_count} reqs ${chalk.dim('|')} ${formatTokens(agg.tokens.total_tokens)} ${chalk.dim('|')} ${chalk.white(formatCost(agg.cost.total_cost))}`,\n );\n }\n }\n\n // File watch stats\n const newestFile = files.reduce((newest, f) => {\n try {\n const mtime = statSync(f).mtimeMs;\n return mtime > newest.time ? { path: f, time: mtime } : newest;\n } catch {\n return newest;\n }\n }, { path: '', time: 0 });\n\n if (newestFile.path) {\n const ago = Date.now() - newestFile.time;\n console.log(chalk.dim(`\\n Last file update: ${formatDuration(ago)} ago`));\n }\n\n // Budget status\n const budgetConfig = loadBudgetConfig();\n if (budgetConfig.daily && todayData) {\n const status = calculateBudgetStatus(todayData.cost.total_cost, budgetConfig.daily);\n console.log(chalk.bold('\\n Budget'));\n console.log(` Daily: ${formatBudgetBar(status.percentage)} (${formatCost(status.spent)} / ${formatCost(status.budget)})`);\n }\n\n console.log(chalk.dim('\\n Press Ctrl+C to exit'));\n}\n\nexport function registerLiveCommand(program: Command): void {\n program\n .command('live')\n .description('Real-time terminal monitor')\n .option('--interval <seconds>', 'Refresh interval in seconds', '5')\n .option('--project <name>', 'Filter by project name')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--timezone <tz>', 'Timezone for date grouping')\n .action(async (opts) => {\n const intervalMs = Math.max(1, parseInt(opts.interval ?? '5', 10)) * 1000;\n const mode = parseCostMode(opts.mode);\n\n // Initial render\n await loadAndDisplay(mode, opts.project, opts.timezone);\n\n // Periodic refresh using setTimeout to prevent overlap\n let stopped = false;\n async function scheduleNext() {\n if (stopped) return;\n await new Promise((r) => setTimeout(r, intervalMs));\n if (stopped) return;\n try {\n await loadAndDisplay(mode, opts.project, opts.timezone);\n } catch {\n // Silently continue on transient errors\n }\n scheduleNext();\n }\n scheduleNext();\n\n // Graceful shutdown\n process.on('SIGINT', () => {\n stopped = true;\n clearScreen();\n console.log(chalk.dim('Live monitor stopped.'));\n process.exit(0);\n });\n });\n}\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport Table from 'cli-table3';\nimport { getAllPricing, getPricingInfo, updatePricing } from '../core/pricing.js';\nimport { formatCost } from '../utils/format.js';\n\nexport function registerPricingCommand(program: Command): void {\n const cmd = program.command('pricing').description('View and update model pricing data');\n\n cmd\n .command('list')\n .description('List all known model prices')\n .option('--json', 'Output as JSON')\n .action(async (options) => {\n const info = getPricingInfo();\n const data = getAllPricing();\n\n if (options.json) {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n console.log(chalk.bold('Model Pricing'));\n console.log(chalk.dim(`Source: ${info.source} | ${info.modelCount} models | version: ${info.version} | cache: ${info.cacheAge}\\n`));\n\n const sorted = Object.entries(data.models).sort(([a], [b]) => a.localeCompare(b));\n\n const table = new Table({\n head: ['Model', 'Input/M', 'Output/M', 'Cache W/M', 'Cache R/M', 'Context'].map((h) => chalk.cyan(h)),\n colAligns: ['left', 'right', 'right', 'right', 'right', 'right'],\n style: { head: [], border: [] },\n });\n\n for (const [id, p] of sorted) {\n const tiered = p.input_cost_per_million_above_200k ? chalk.yellow(' *') : '';\n table.push([\n id + tiered,\n formatCost(p.input_cost_per_million),\n formatCost(p.output_cost_per_million),\n formatCost(p.cache_creation_cost_per_million),\n formatCost(p.cache_read_cost_per_million),\n `${(p.context_window / 1000).toFixed(0)}K`,\n ]);\n }\n\n console.log(table.toString());\n console.log(chalk.dim('\\n* = tiered pricing above 200K context'));\n\n // Show aliases\n console.log(chalk.bold('\\nAliases'));\n const aliasSorted = Object.entries(data.aliases).sort(([a], [b]) => a.localeCompare(b));\n for (const [alias, target] of aliasSorted) {\n console.log(` ${chalk.white(alias)} ${chalk.dim('→')} ${target}`);\n }\n });\n\n cmd\n .command('update')\n .description('Fetch latest pricing from Anthropic and update cache')\n .action(async () => {\n console.log(chalk.dim('Fetching pricing from Anthropic...'));\n const { data, newModels, source } = await updatePricing();\n\n console.log(chalk.green(`Pricing updated: ${Object.keys(data.models).length} models (${source})`));\n\n if (newModels.length > 0) {\n console.log(chalk.yellow(`\\nNew models discovered:`));\n for (const m of newModels) {\n console.log(` + ${chalk.cyan(m)}`);\n }\n }\n\n const info = getPricingInfo();\n console.log(chalk.dim(`\\nCache: ${info.cacheAge}`));\n });\n\n cmd\n .command('status')\n .description('Show pricing source and cache status')\n .action(async () => {\n const info = getPricingInfo();\n console.log(`Source: ${chalk.white(info.source)}`);\n console.log(`Models: ${chalk.white(info.modelCount.toString())}`);\n console.log(`Version: ${chalk.white(info.version)}`);\n console.log(`Cache: ${chalk.white(info.cacheAge)}`);\n });\n\n // Default: show status\n cmd.action(async () => {\n await cmd.commands.find((c) => c.name() === 'status')?.parseAsync(['node', 'cctrack', 'pricing', 'status']);\n });\n}\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport {\n loadBudgetConfig,\n saveBudgetConfig,\n loadFullConfig,\n saveFullConfig,\n resetConfig,\n getConfigPath,\n} from '../core/budget.js';\nimport { formatCost } from '../utils/format.js';\n\nexport function registerConfigCommand(program: Command): void {\n const configCmd = program\n .command('config')\n .description('Manage cctrack configuration (budgets, etc.)');\n\n configCmd\n .command('set <key> <value>')\n .description('Set a configuration value (e.g. budget.daily 50)')\n .action((key: string, value: string) => {\n const numValue = Number(value);\n if (isNaN(numValue) || numValue < 0) {\n console.error(chalk.red(`Invalid value: ${value}. Must be a non-negative number.`));\n process.exit(1);\n }\n\n const parts = key.split('.');\n if (parts[0] === 'budget' && parts.length === 2) {\n const budgetKey = parts[1] as 'daily' | 'monthly' | 'block';\n if (!['daily', 'monthly', 'block'].includes(budgetKey)) {\n console.error(chalk.red(`Unknown budget key: ${budgetKey}. Valid keys: daily, monthly, block`));\n process.exit(1);\n }\n const config = loadBudgetConfig();\n config[budgetKey] = numValue;\n saveBudgetConfig(config);\n console.log(chalk.green(`Set ${key} = ${formatCost(numValue)}`));\n } else {\n console.error(chalk.red(`Unknown config key: ${key}`));\n console.error(chalk.dim('Valid keys: budget.daily, budget.monthly, budget.block'));\n process.exit(1);\n }\n });\n\n configCmd\n .command('get [key]')\n .description('Show current configuration')\n .action((key?: string) => {\n const config = loadFullConfig();\n const configPath = getConfigPath();\n\n if (key && key !== 'budget') {\n console.error(chalk.red(`Unknown config section: ${key}`));\n process.exit(1);\n }\n\n console.log(chalk.bold(`CCTrack Configuration`) + chalk.dim(` (${configPath})`));\n console.log();\n\n const budget = config.budget ?? {};\n console.log(chalk.bold(' Budget'));\n console.log(` Daily: ${budget.daily != null ? formatCost(budget.daily) + chalk.dim(' (alerts at 50%/80%/100%)') : chalk.dim('not set')}`);\n console.log(` Monthly: ${budget.monthly != null ? formatCost(budget.monthly) + chalk.dim(' (alerts at 50%/80%/100%)') : chalk.dim('not set')}`);\n console.log(` Block: ${budget.block != null ? formatCost(budget.block) : chalk.dim('not set')}`);\n console.log(chalk.dim('\\n Set with: cctrack config set budget.daily <dollars>'));\n });\n\n configCmd\n .command('reset')\n .description('Reset configuration to defaults')\n .action(() => {\n resetConfig();\n console.log(chalk.green('Configuration reset to defaults.'));\n });\n}\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport Table from 'cli-table3';\nimport type { CostMode, UsageEntry, BlockAggregate, AggregatedEntry } from '../core/types.js';\nimport { BLOCK_DURATION_MS } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries, emptyAggregate, accumulate } from '../core/aggregator.js';\nimport { processEntry, addTokens, addCosts } from '../core/calculator.js';\nimport { formatCost, formatTokens, formatDuration, parseCostMode } from '../utils/format.js';\nimport { getRateLimits } from './statusline.js';\n\n/**\n * Group entries into 5-hour rolling blocks.\n * Current block: [now - 5h, now]\n * Previous blocks: 5-hour windows going backwards.\n */\nfunction aggregateBlocks(entries: UsageEntry[], mode: CostMode, numBlocks: number = 10): BlockAggregate[] {\n const now = Date.now();\n const oldestBlock = now - numBlocks * BLOCK_DURATION_MS;\n\n // Initialize all blocks\n const blockMap = new Map<number, BlockAggregate>();\n for (let i = 0; i < numBlocks; i++) {\n const blockEnd = now - i * BLOCK_DURATION_MS;\n const blockStart = blockEnd - BLOCK_DURATION_MS;\n blockMap.set(i, {\n block_start: new Date(blockStart).toISOString(),\n block_end: new Date(blockEnd).toISOString(),\n block_index: i,\n is_current: i === 0,\n time_remaining_ms: i === 0 ? blockEnd - now : 0,\n ...emptyAggregate(),\n models: {},\n });\n }\n\n // Single pass: assign each entry to its block by index\n for (const entry of entries) {\n const entryTime = new Date(entry.timestamp).getTime();\n if (entryTime < oldestBlock || entryTime > now) continue;\n\n const blockIndex = Math.floor((now - entryTime) / BLOCK_DURATION_MS);\n if (blockIndex < 0 || blockIndex >= numBlocks) continue;\n\n const block = blockMap.get(blockIndex)!;\n const model = entry.message.model ?? 'unknown';\n const result = processEntry(entry, mode);\n accumulate(block, result);\n\n if (!block.models[model]) block.models[model] = emptyAggregate();\n const modelAgg = block.models[model];\n modelAgg.tokens = addTokens(modelAgg.tokens, result.tokens);\n modelAgg.cost = addCosts(modelAgg.cost, result.cost);\n modelAgg.request_count++;\n }\n\n return Array.from(blockMap.values());\n}\n\n/**\n * Build a progress bar using Unicode block characters.\n * Green <50%, Yellow 50-80%, Red >80%.\n */\nfunction buildProgressBar(percentage: number, width: number = 20): string {\n const pct = Math.min(Math.max(percentage, 0), 100);\n const filled = Math.round((pct / 100) * width);\n const empty = width - filled;\n\n const bar = '\\u2588'.repeat(filled) + '\\u2591'.repeat(empty);\n\n if (pct > 80) return chalk.red(bar);\n if (pct >= 50) return chalk.yellow(bar);\n return chalk.green(bar);\n}\n\nfunction clearScreen(): void {\n process.stdout.write('\\x1b[2J\\x1b[H');\n}\n\nfunction formatBlockTime(isoString: string): string {\n const date = new Date(isoString);\n const y = date.getFullYear();\n const mo = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n const h = String(date.getHours()).padStart(2, '0');\n const mi = String(date.getMinutes()).padStart(2, '0');\n return `${y}-${mo}-${d} ${h}:${mi}`;\n}\n\nfunction displayBlocks(blocks: BlockAggregate[]): void {\n // Show real rate limits if available (from statusline capture)\n const rateLimits = getRateLimits();\n if (rateLimits) {\n console.log(chalk.bold('Anthropic Rate Limits') + chalk.dim(` (via ${rateLimits.source}, ${rateLimits.captured_at.slice(11, 19)} UTC)`));\n const showWindow = (label: string, w: { used_percentage: number; resets_at: number } | undefined) => {\n if (!w) return;\n const pct = w.used_percentage;\n const resetMs = w.resets_at * 1000 - Date.now();\n const resetStr = resetMs > 0 ? formatDuration(resetMs) : 'now';\n const color = pct >= 80 ? chalk.red : pct >= 50 ? chalk.yellow : chalk.green;\n const bar = '\\u2588'.repeat(Math.round(pct / 5)) + '\\u2591'.repeat(20 - Math.round(pct / 5));\n console.log(` ${label.padEnd(16)} ${color(bar)} ${color(pct.toFixed(1).padStart(5) + '%')} | resets in ${resetStr}`);\n };\n showWindow('Session (5h)', rateLimits.five_hour);\n showWindow('Weekly (all)', rateLimits.seven_day);\n showWindow('Weekly (Sonnet)', rateLimits.seven_day_sonnet);\n showWindow('Weekly (Opus)', rateLimits.seven_day_opus);\n if (rateLimits.extra_usage) {\n const eu = rateLimits.extra_usage;\n const pct = eu.utilization;\n const color = pct >= 80 ? chalk.red : pct >= 50 ? chalk.yellow : chalk.green;\n const resetMs = eu.resets_at * 1000 - Date.now();\n const resetStr = resetMs > 0 ? formatDuration(resetMs) : 'now';\n console.log(` ${'Extra usage'.padEnd(16)} ${color('$' + eu.spent.toFixed(2) + ' / $' + eu.limit.toFixed(0))} (${pct.toFixed(0)}%) | resets in ${resetStr}`);\n }\n console.log('');\n }\n\n if (blocks.length === 0) {\n console.log(chalk.yellow('No usage recorded in the last 50 hours.'));\n console.log(chalk.dim('Start a Claude Code session and data will appear here.'));\n return;\n }\n\n const current = blocks[0];\n\n // Last 5 Hours summary\n console.log(chalk.bold('Last 5 Hours'));\n if (current.request_count === 0) {\n console.log(chalk.dim(' No requests in this window.\\n'));\n } else {\n console.log(` ${chalk.white(formatCost(current.cost.total_cost))} spent | ${chalk.white(current.request_count.toString())} requests`);\n console.log(\n ` Input: ${chalk.white(formatTokens(current.tokens.input_tokens))} | ` +\n `Output: ${chalk.white(formatTokens(current.tokens.output_tokens))} | ` +\n `Cache: ${chalk.white(formatTokens(current.tokens.cache_read_tokens))}`,\n );\n }\n\n // Recent blocks table\n const pastBlocks = blocks.filter((b) => !b.is_current && b.request_count > 0);\n if (pastBlocks.length > 0) {\n console.log(chalk.bold('\\nPrevious 5-Hour Windows'));\n\n const table = new Table({\n head: ['Window Start', 'Requests', 'Tokens', 'Cost'].map((h) => chalk.cyan(h)),\n colAligns: ['left', 'right', 'right', 'right'],\n style: { head: [], border: [] },\n });\n\n for (const block of pastBlocks) {\n table.push([\n formatBlockTime(block.block_start),\n block.request_count.toString(),\n formatTokens(block.tokens.total_tokens),\n formatCost(block.cost.total_cost),\n ]);\n }\n\n console.log(table.toString());\n }\n\n console.log(chalk.dim('\\nNote: These are your usage patterns grouped by 5-hour windows.'));\n console.log(chalk.dim('They do not reflect Anthropic\\'s actual rate limit calculations.'));\n}\n\nasync function loadAndDisplay(mode: CostMode, opts: { since?: string; until?: string; project?: string }): Promise<void> {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n\n if (files.length === 0) {\n console.log(chalk.yellow('No JSONL files found. Waiting for data...'));\n return;\n }\n\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n project: opts.project,\n });\n\n const blocks = aggregateBlocks(filtered, mode);\n\n displayBlocks(blocks);\n}\n\nexport function registerBlocksCommand(program: Command): void {\n program\n .command('blocks')\n .description('Show usage grouped by 5-hour windows')\n .option('--json', 'Output as JSON')\n .option('--since <date>', 'Start date (YYYY-MM-DD)')\n .option('--until <date>', 'End date (YYYY-MM-DD)')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .option('--live', 'Auto-refresh every 5 seconds')\n .action(async (opts) => {\n const mode = parseCostMode(opts.mode);\n\n if (opts.json) {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n const filtered = filterEntries(unique, {\n since: opts.since,\n until: opts.until,\n });\n const blocks = aggregateBlocks(filtered, mode);\n console.log(JSON.stringify(blocks, null, 2));\n return;\n }\n\n if (opts.live) {\n // Initial render\n clearScreen();\n await loadAndDisplay(mode, opts);\n\n const timer = setInterval(async () => {\n try {\n clearScreen();\n await loadAndDisplay(mode, opts);\n console.log(chalk.dim('\\nPress Ctrl+C to exit'));\n } catch {\n // Silently continue on transient errors\n }\n }, 5000);\n\n process.on('SIGINT', () => {\n clearInterval(timer);\n clearScreen();\n console.log(chalk.dim('Block monitor stopped.'));\n process.exit(0);\n });\n } else {\n await loadAndDisplay(mode, opts);\n }\n });\n}\n","import type { Command } from 'commander';\nimport { readFileSync, writeFileSync, mkdirSync, statSync, existsSync, readdirSync, openSync, readSync, closeSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { CostMode, UsageEntry, StatuslineData, RateLimitData } from '../core/types.js';\nimport { BLOCK_DURATION_MS } from '../core/types.js';\nimport { UsageEntrySchema } from '../core/types.js';\nimport { processEntry } from '../core/calculator.js';\nimport { formatCost, formatTokens, formatDuration, shortenModelName, parseCostMode } from '../utils/format.js';\n\nconst CACHE_DIR = join(homedir(), '.cctrack');\nconst CACHE_FILE = join(CACHE_DIR, 'statusline.cache');\nconst RATELIMIT_FILE = join(CACHE_DIR, 'ratelimits.json');\nconst CACHE_MAX_AGE_MS = 30_000; // 30 seconds\n\n/**\n * Try to read rate limit data from Claude Code's statusline stdin.\n * Returns null if not running as a statusline hook (no stdin or no rate_limits).\n */\nfunction readStdinRateLimits(): RateLimitData | null {\n try {\n // Check if stdin has data (non-TTY means piped input from Claude Code)\n if (process.stdin.isTTY) return null;\n\n // Read stdin synchronously via fd 0\n const stdinContent = readFileSync(0, 'utf-8').trim();\n if (!stdinContent) return null;\n\n const input = JSON.parse(stdinContent);\n if (!input.rate_limits) return null;\n\n const rl: RateLimitData = {\n source: 'statusline',\n captured_at: new Date().toISOString(),\n };\n\n const limits = input.rate_limits;\n if (limits.five_hour) {\n rl.five_hour = { used_percentage: limits.five_hour.used_percentage, resets_at: limits.five_hour.resets_at };\n }\n if (limits.seven_day) {\n rl.seven_day = { used_percentage: limits.seven_day.used_percentage, resets_at: limits.seven_day.resets_at };\n }\n if (limits.seven_day_sonnet) {\n rl.seven_day_sonnet = { used_percentage: limits.seven_day_sonnet.used_percentage, resets_at: limits.seven_day_sonnet.resets_at };\n }\n if (limits.seven_day_opus) {\n rl.seven_day_opus = { used_percentage: limits.seven_day_opus.used_percentage, resets_at: limits.seven_day_opus.resets_at };\n }\n if (limits.extra_usage?.is_enabled) {\n rl.extra_usage = {\n is_enabled: true,\n spent: limits.extra_usage.used_credits ?? 0,\n limit: limits.extra_usage.monthly_limit ?? 0,\n utilization: limits.extra_usage.utilization ?? 0,\n resets_at: limits.extra_usage.resets_at ?? 0,\n };\n }\n\n // Persist to disk so other commands (blocks, live, dashboard) can read it\n try {\n mkdirSync(CACHE_DIR, { recursive: true });\n writeFileSync(RATELIMIT_FILE, JSON.stringify(rl, null, 2), 'utf-8');\n } catch {}\n\n return rl;\n } catch {\n return null;\n }\n}\n\n/**\n * Read cached rate limit data from disk (written by statusline hook or OAuth).\n */\nfunction readCachedRateLimits(): RateLimitData | null {\n try {\n if (!existsSync(RATELIMIT_FILE)) return null;\n const raw = readFileSync(RATELIMIT_FILE, 'utf-8');\n const data: RateLimitData = JSON.parse(raw);\n // Stale after 10 minutes\n const age = Date.now() - new Date(data.captured_at).getTime();\n if (age > 10 * 60 * 1000) return null;\n return data;\n } catch {\n return null;\n }\n}\n\n/**\n * Export for other commands to use.\n */\nexport function getRateLimits(): RateLimitData | null {\n return readCachedRateLimits();\n}\n\ninterface CacheFile {\n updated_at: string;\n data: StatuslineData;\n}\n\n// shortenModelName imported from ../utils/format.js\n\nfunction readCache(): CacheFile | null {\n try {\n if (!existsSync(CACHE_FILE)) return null;\n const raw = readFileSync(CACHE_FILE, 'utf-8');\n return JSON.parse(raw) as CacheFile;\n } catch {\n return null;\n }\n}\n\nfunction writeCache(data: StatuslineData): void {\n try {\n mkdirSync(CACHE_DIR, { recursive: true });\n const cache: CacheFile = { updated_at: new Date().toISOString(), data };\n writeFileSync(CACHE_FILE, JSON.stringify(cache), 'utf-8');\n } catch {\n // Non-fatal\n }\n}\n\nfunction isCacheFresh(): boolean {\n try {\n if (!existsSync(CACHE_FILE)) return false;\n const stat = statSync(CACHE_FILE);\n return Date.now() - stat.mtimeMs < CACHE_MAX_AGE_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Find all .jsonl files under the given directories, recursively.\n */\nfunction findJsonlFilesSync(dirs: string[]): string[] {\n const files: string[] = [];\n\n function walk(dir: string): void {\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.name.endsWith('.jsonl')) {\n files.push(fullPath);\n }\n }\n } catch {\n // Skip dirs we can't read\n }\n }\n\n for (const dir of dirs) {\n walk(dir);\n }\n\n return files;\n}\n\n/**\n * Get project directories (sync version for speed).\n */\nfunction getProjectDirsSync(): string[] {\n const dirs: string[] = [];\n\n const customDir = process.env.CLAUDE_CONFIG_DIR;\n if (customDir) {\n const projectsDir = join(customDir, 'projects');\n if (existsSync(projectsDir)) dirs.push(projectsDir);\n }\n\n const home = homedir();\n const defaultPaths = [join(home, '.claude', 'projects'), join(home, '.config', 'claude', 'projects')];\n\n for (const p of defaultPaths) {\n if (existsSync(p)) dirs.push(p);\n }\n\n return [...new Set(dirs)];\n}\n\n/**\n * Parse JSONL files synchronously, only reading files modified in the last 24 hours.\n * Filters to only today's entries for speed.\n */\nfunction parseRecentEntriesSync(files: string[], mode: CostMode): StatuslineData {\n const now = Date.now();\n const oneDayAgo = now - 24 * 60 * 60 * 1000;\n const todayStr = new Date().toISOString().slice(0, 10);\n const blockStart = now - BLOCK_DURATION_MS;\n\n let todayCost = 0;\n let totalTokens = 0;\n let blockTokens = 0;\n let blockRequests = 0;\n let latestModel = 'unknown';\n let latestTimestamp = '';\n let latestSessionId = '';\n const sessionCosts = new Map<string, number>();\n\n for (const file of files) {\n // Only parse files modified in the last 24 hours\n try {\n const stat = statSync(file);\n if (stat.mtimeMs < oneDayAgo) continue;\n } catch {\n continue;\n }\n\n // Read only the last 256KB of the file (today's entries are at the end)\n let content: string;\n try {\n const stat2 = statSync(file);\n const maxBytes = 256 * 1024;\n if (stat2.size > maxBytes) {\n const fd = openSync(file, 'r');\n const buf = Buffer.alloc(maxBytes);\n readSync(fd, buf, 0, maxBytes, stat2.size - maxBytes);\n closeSync(fd);\n content = buf.toString('utf-8');\n // Skip first partial line\n const firstNewline = content.indexOf('\\n');\n if (firstNewline >= 0) content = content.slice(firstNewline + 1);\n } else {\n content = readFileSync(file, 'utf-8');\n }\n } catch {\n continue;\n }\n\n const lines = content.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n try {\n const raw = JSON.parse(trimmed);\n const parsed = UsageEntrySchema.safeParse(raw);\n if (!parsed.success) continue;\n\n const entry = parsed.data;\n if (entry.isApiErrorMessage === true) continue;\n if (entry.message.model === '<synthetic>') continue;\n\n // Only process today's entries\n const entryDate = entry.timestamp.slice(0, 10);\n if (entryDate !== todayStr) continue;\n\n const result = processEntry(entry, mode);\n todayCost += result.cost.total_cost;\n totalTokens += result.tokens.total_tokens;\n\n // Track latest model\n if (entry.timestamp > latestTimestamp) {\n latestTimestamp = entry.timestamp;\n latestModel = entry.message.model ?? 'unknown';\n if (entry.sessionId) latestSessionId = entry.sessionId;\n }\n\n // Accumulate per-session costs in a map\n if (entry.sessionId) {\n sessionCosts.set(entry.sessionId, (sessionCosts.get(entry.sessionId) ?? 0) + result.cost.total_cost);\n }\n\n // Block tracking (current 5-hour window)\n const entryTime = new Date(entry.timestamp).getTime();\n if (entryTime >= blockStart) {\n blockTokens += result.tokens.total_tokens;\n blockRequests++;\n }\n } catch {\n continue;\n }\n }\n }\n\n // Look up session cost for the latest session from the map\n const sessionCost = latestSessionId ? (sessionCosts.get(latestSessionId) ?? 0) : 0;\n\n // Calculate block percentage based on token/request density\n // Since blocks are rolling windows, we show how \"full\" the current block is\n // relative to the average block capacity\n const blockPct = blockRequests > 0 ? Math.min(blockRequests, 100) : 0;\n const blockElapsed = now - blockStart;\n const blockRemaining = Math.max(BLOCK_DURATION_MS - blockElapsed, 0);\n\n // Compute budget level from config\n // Inline the budget logic to avoid importing budget.ts (which has top-level await in tests)\n let budgetLevel: import('../core/types.js').BudgetLevel = 'safe';\n try {\n const configPath = join(homedir(), '.cctrack', 'config.json');\n if (existsSync(configPath)) {\n const config = JSON.parse(readFileSync(configPath, 'utf-8'));\n const dailyBudget = config?.budget?.daily;\n if (dailyBudget && dailyBudget > 0) {\n const pct = (todayCost / dailyBudget) * 100;\n if (pct >= 100) budgetLevel = 'exceeded';\n else if (pct >= 80) budgetLevel = 'critical';\n else if (pct >= 50) budgetLevel = 'warning';\n }\n }\n } catch {\n // Config not available, stay safe\n }\n\n return {\n today_cost: todayCost,\n session_cost: sessionCost,\n model: latestModel,\n total_tokens: totalTokens,\n block_percentage: Math.round(blockPct),\n block_remaining: formatDuration(blockRemaining),\n budget_level: budgetLevel,\n updated_at: new Date().toISOString(),\n };\n}\n\nfunction buildProgressBarCompact(percentage: number, width: number = 8): string {\n const pct = Math.min(Math.max(percentage, 0), 100);\n const filled = Math.round((pct / 100) * width);\n const empty = width - filled;\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(empty);\n}\n\nfunction formatOutput(data: StatuslineData, template?: string): string {\n if (template) {\n return template\n .replace('{cost}', formatCost(data.today_cost))\n .replace('{model}', shortenModelName(data.model))\n .replace('{tokens}', formatTokens(data.total_tokens))\n .replace('{block_pct}', `${data.block_percentage}%`)\n .replace('{block_remaining}', data.block_remaining);\n }\n\n const parts = [\n formatCost(data.today_cost) + ' today',\n shortenModelName(data.model),\n formatTokens(data.total_tokens) + ' tok',\n ];\n\n // Show real rate limits if available, otherwise show estimated\n if (data.rate_limits?.five_hour) {\n const pct = Math.round(data.rate_limits.five_hour.used_percentage);\n const bar = buildProgressBarCompact(pct);\n const resetMs = data.rate_limits.five_hour.resets_at * 1000 - Date.now();\n const resetStr = resetMs > 0 ? formatDuration(resetMs) : 'now';\n parts.push(`${bar} ${pct}% 5h (${resetStr})`);\n }\n if (data.rate_limits?.seven_day) {\n parts.push(`7d: ${Math.round(data.rate_limits.seven_day.used_percentage)}%`);\n }\n if (data.rate_limits?.extra_usage) {\n const eu = data.rate_limits.extra_usage;\n parts.push(`extra: $${eu.spent.toFixed(2)}/$${eu.limit.toFixed(0)}`);\n }\n if (!data.rate_limits) {\n parts.push(`${buildProgressBarCompact(data.block_percentage)} ~${data.block_percentage}%`);\n }\n\n return parts.join(' \\u2502 ');\n}\n\nexport function registerStatuslineCommand(program: Command): void {\n program\n .command('statusline')\n .description('Ultra-lightweight cached output for tmux/neovim/hooks')\n .option('--format <template>', 'Custom format with placeholders: {cost}, {model}, {tokens}, {block_pct}, {block_remaining}')\n .option('--no-cache', 'Force fresh parse (skip cache)')\n .option('--json', 'Output as JSON')\n .option('--mode <mode>', 'Cost mode: calculate, display, compare', 'calculate')\n .action((opts) => {\n const mode = parseCostMode(opts.mode);\n const useCache = opts.cache !== false;\n\n // Try to capture rate limits from Claude Code's stdin (when used as statusline hook)\n const stdinLimits = readStdinRateLimits();\n\n let data: StatuslineData;\n\n // Fast path: try cache first\n if (useCache && isCacheFresh()) {\n const cached = readCache();\n if (cached) {\n data = cached.data;\n } else {\n const dirs = getProjectDirsSync();\n const files = findJsonlFilesSync(dirs);\n data = parseRecentEntriesSync(files, mode);\n writeCache(data);\n }\n } else {\n const dirs = getProjectDirsSync();\n const files = findJsonlFilesSync(dirs);\n data = parseRecentEntriesSync(files, mode);\n if (useCache) writeCache(data);\n }\n\n // Attach rate limits (from stdin capture or disk cache)\n data.rate_limits = stdinLimits ?? readCachedRateLimits() ?? undefined;\n\n // Update block percentage from real data if available\n if (data.rate_limits?.five_hour) {\n data.block_percentage = Math.round(data.rate_limits.five_hour.used_percentage);\n const resetMs = data.rate_limits.five_hour.resets_at * 1000 - Date.now();\n data.block_remaining = formatDuration(Math.max(resetMs, 0));\n }\n\n if (opts.json) {\n process.stdout.write(JSON.stringify(data));\n } else {\n process.stdout.write(formatOutput(data, opts.format));\n }\n });\n}\n","import type { Command } from 'commander';\nimport chalk from 'chalk';\nimport type { CostMode } from '../core/types.js';\nimport { getProjectDirs, findJsonlFiles } from '../utils/fs.js';\nimport { parseAllFiles } from '../core/parser.js';\nimport { deduplicateEntries } from '../core/dedup.js';\nimport { filterEntries } from '../core/aggregator.js';\nimport { predictUtilization, loadModel, loadEvents, currentWindowConsumption } from '../core/rate-model.js';\nimport { formatTokens, formatDuration, parseCostMode } from '../utils/format.js';\n\nfunction buildBar(pct: number, width: number = 30): string {\n const clamped = Math.min(Math.max(pct, 0), 100);\n const filled = Math.round((clamped / 100) * width);\n const bar = '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n if (pct >= 80) return chalk.red(bar);\n if (pct >= 50) return chalk.yellow(bar);\n return chalk.green(bar);\n}\n\nexport function registerLimitsCommand(program: Command): void {\n program\n .command('limits')\n .description('Predict rate limit utilization from your usage patterns')\n .option('--json', 'Output as JSON')\n .option('--mode <mode>', 'Cost mode', 'calculate')\n .action(async (opts) => {\n const dirs = getProjectDirs();\n const files = findJsonlFiles(dirs);\n const { entries } = await parseAllFiles(files);\n const unique = deduplicateEntries(entries);\n\n const mode = parseCostMode(opts.mode);\n\n // Determine primary model (most recent)\n const sorted = [...unique].sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n const primaryModel = sorted[0]?.message.model ?? 'claude-opus-4-6';\n\n // Get prediction\n const prediction = predictUtilization(unique, primaryModel);\n const consumption = currentWindowConsumption(unique);\n const model = loadModel();\n const events = loadEvents();\n\n if (opts.json) {\n console.log(JSON.stringify({ prediction, consumption, model, events_count: events.length }, null, 2));\n return;\n }\n\n console.log(chalk.bold('Rate Limit Analysis'));\n console.log(chalk.dim(`Model: ${primaryModel} (${prediction.model_family})`));\n console.log('');\n\n // Current 5-hour window consumption\n console.log(chalk.bold('Current 5-Hour Window'));\n console.log(` Billable tokens: ${chalk.white(formatTokens(consumption.billable_tokens))} ${chalk.dim('(input + cache_creation, excludes cache_read)')}`);\n console.log(` Total tokens: ${chalk.white(formatTokens(consumption.total_tokens))}`);\n console.log(` Requests: ${chalk.white(consumption.requests.toString())}`);\n console.log('');\n\n if (prediction.source === 'calibrated') {\n console.log(chalk.bold('Estimated Utilization') + chalk.dim(` (${prediction.calibration_events} calibration events, ${Math.round(prediction.confidence * 100)}% confidence)`));\n console.log(` ${buildBar(prediction.estimated_utilization)} ${chalk.white(prediction.estimated_utilization.toFixed(1) + '%')}`);\n console.log(` Estimated limit: ${chalk.white(formatTokens(prediction.estimated_limit))} billable tokens / 5h`);\n\n if (prediction.minutes_to_limit !== null) {\n if (prediction.minutes_to_limit === 0) {\n console.log(` Time to limit: ${chalk.red('NOW — you may be rate limited')}`);\n } else {\n const color = prediction.minutes_to_limit < 30 ? chalk.red : prediction.minutes_to_limit < 60 ? chalk.yellow : chalk.green;\n console.log(` Time to limit: ${color(formatDuration(prediction.minutes_to_limit * 60 * 1000))}`);\n }\n }\n } else {\n console.log(chalk.bold('Estimated Utilization'));\n console.log(chalk.dim(' No calibration data yet. The model learns when you hit rate limits.'));\n console.log(chalk.dim(' Keep using Claude Code normally — the first time you hit a limit,'));\n console.log(chalk.dim(' cctrack will record it and start predicting.'));\n console.log('');\n console.log(chalk.dim(' To see your actual limits right now, use:'));\n console.log(chalk.dim(' /usage in Claude Code'));\n }\n\n console.log('');\n\n // Historical calibration events\n if (events.length > 0) {\n console.log(chalk.bold('Rate Limit History'));\n const recent = events.slice(-5).reverse();\n for (const e of recent) {\n const date = e.timestamp.slice(0, 16).replace('T', ' ');\n console.log(` ${chalk.dim(date)} ${e.model} — ${formatTokens(e.input_tokens_in_window)} billable tokens`);\n if (e.reset_time) console.log(` ${chalk.dim('Reset: ' + e.reset_time)}`);\n }\n } else {\n console.log(chalk.dim('No rate limit events recorded yet.'));\n }\n });\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { UsageEntry } from './types.js';\n\nconst DATA_DIR = join(homedir(), '.cctrack');\nconst EVENTS_FILE = join(DATA_DIR, 'rate-events.jsonl');\nconst MODEL_FILE = join(DATA_DIR, 'rate-model.json');\n\n// 5-hour window in ms\nconst WINDOW_MS = 5 * 60 * 60 * 1000;\n\n// === Rate Limit Event (calibration point) ===\n\nexport interface RateLimitEvent {\n timestamp: string;\n model: string;\n tokens_in_window: number; // total tokens consumed in the 5h window before hitting the limit\n input_tokens_in_window: number; // input tokens only (cache_read doesn't count toward limits)\n reset_time?: string; // parsed from error message\n content?: string; // raw error text\n}\n\n// === Learned Model ===\n\nexport interface LearnedLimits {\n /** Estimated 5-hour token limit (input + cache_creation, excluding cache_read) */\n estimated_limit: number;\n /** How confident we are (0-1), based on number of calibration events */\n confidence: number;\n /** Number of calibration events used */\n sample_count: number;\n /** Last calibration timestamp */\n last_calibration: string;\n /** EMA alpha (higher = more weight on recent events) */\n alpha: number;\n}\n\nexport interface RateModelState {\n limits: Record<string, LearnedLimits>; // keyed by model family (opus, sonnet, etc.)\n updated_at: string;\n}\n\n// === Core Functions ===\n\n/**\n * Load the learned rate model from disk.\n */\nexport function loadModel(): RateModelState {\n try {\n if (existsSync(MODEL_FILE)) {\n return JSON.parse(readFileSync(MODEL_FILE, 'utf-8'));\n }\n } catch {}\n return { limits: {}, updated_at: new Date().toISOString() };\n}\n\n/**\n * Save the learned rate model to disk.\n */\nfunction saveModel(model: RateModelState): void {\n mkdirSync(DATA_DIR, { recursive: true });\n model.updated_at = new Date().toISOString();\n writeFileSync(MODEL_FILE, JSON.stringify(model, null, 2), 'utf-8');\n}\n\n/**\n * Extract model family from a full model name.\n * claude-opus-4-6-20260205 → opus\n * claude-sonnet-4-20250514 → sonnet\n */\nfunction modelFamily(model: string): string {\n if (model.includes('opus')) return 'opus';\n if (model.includes('sonnet')) return 'sonnet';\n if (model.includes('haiku')) return 'haiku';\n return 'unknown';\n}\n\n/**\n * Record a calibration event: we hit the rate limit.\n * This is called by the parser when it finds a rate limit error in JSONL.\n */\nexport function recordCalibration(event: RateLimitEvent): void {\n mkdirSync(DATA_DIR, { recursive: true });\n appendFileSync(EVENTS_FILE, JSON.stringify(event) + '\\n', 'utf-8');\n\n // Update the EMA model\n const model = loadModel();\n const family = modelFamily(event.model);\n const alpha = 0.3; // EMA weight: 30% new, 70% historical\n\n if (!model.limits[family]) {\n model.limits[family] = {\n estimated_limit: event.input_tokens_in_window,\n confidence: 0.3,\n sample_count: 1,\n last_calibration: event.timestamp,\n alpha,\n };\n } else {\n const prev = model.limits[family];\n // Exponential Moving Average: new_estimate = alpha * new_value + (1 - alpha) * old_estimate\n prev.estimated_limit = alpha * event.input_tokens_in_window + (1 - alpha) * prev.estimated_limit;\n prev.sample_count++;\n prev.confidence = Math.min(1 - (1 / (prev.sample_count + 1)), 0.95); // approaches 0.95 asymptotically\n prev.last_calibration = event.timestamp;\n }\n\n saveModel(model);\n}\n\n/**\n * Load all calibration events from disk.\n */\nexport function loadEvents(): RateLimitEvent[] {\n try {\n if (!existsSync(EVENTS_FILE)) return [];\n return readFileSync(EVENTS_FILE, 'utf-8')\n .split('\\n')\n .filter(Boolean)\n .map((line) => JSON.parse(line));\n } catch {\n return [];\n }\n}\n\n/**\n * Calculate current consumption in the rolling 5-hour window.\n * Only counts input_tokens + cache_creation_input_tokens (not cache_read — those don't count toward limits).\n */\nexport function currentWindowConsumption(entries: UsageEntry[]): {\n billable_tokens: number;\n total_tokens: number;\n requests: number;\n window_start: Date;\n} {\n const now = Date.now();\n const windowStart = new Date(now - WINDOW_MS);\n\n let billable = 0;\n let total = 0;\n let requests = 0;\n\n for (const entry of entries) {\n const entryTime = new Date(entry.timestamp).getTime();\n if (entryTime < windowStart.getTime()) continue;\n if (entryTime > now) continue;\n\n const usage = entry.message.usage;\n // Billable = input + cache_creation (cache_read is free toward limits)\n billable += usage.input_tokens + (usage.cache_creation_input_tokens ?? 0);\n total += usage.input_tokens + usage.output_tokens + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);\n requests++;\n }\n\n return { billable_tokens: billable, total_tokens: total, requests, window_start: windowStart };\n}\n\n/**\n * Predict current utilization and time-to-limit.\n */\nexport interface RatePrediction {\n /** Current model family being predicted for */\n model_family: string;\n /** Estimated utilization percentage (0-100) */\n estimated_utilization: number;\n /** How confident we are in this estimate */\n confidence: number;\n /** Estimated limit (billable tokens in 5h window) */\n estimated_limit: number;\n /** Current billable tokens consumed in window */\n current_consumption: number;\n /** Minutes until estimated limit at current burn rate, null if not enough data */\n minutes_to_limit: number | null;\n /** Number of calibration events this estimate is based on */\n calibration_events: number;\n /** Source of estimate */\n source: 'calibrated' | 'uncalibrated';\n}\n\nexport function predictUtilization(entries: UsageEntry[], primaryModel: string): RatePrediction {\n const family = modelFamily(primaryModel);\n const model = loadModel();\n const learned = model.limits[family];\n\n const consumption = currentWindowConsumption(entries);\n\n // If we have calibration data, use it\n if (learned && learned.sample_count > 0) {\n const utilization = (consumption.billable_tokens / learned.estimated_limit) * 100;\n\n // Calculate burn rate and time to limit\n const elapsedMs = Date.now() - consumption.window_start.getTime();\n const elapsedHours = elapsedMs / (1000 * 60 * 60);\n let minutesToLimit: number | null = null;\n\n if (elapsedHours > 0.1 && consumption.billable_tokens > 0) {\n const hourlyRate = consumption.billable_tokens / elapsedHours;\n const remaining = learned.estimated_limit - consumption.billable_tokens;\n if (remaining > 0 && hourlyRate > 0) {\n minutesToLimit = Math.round((remaining / hourlyRate) * 60);\n } else {\n minutesToLimit = 0;\n }\n }\n\n return {\n model_family: family,\n estimated_utilization: Math.min(Math.round(utilization * 10) / 10, 100),\n confidence: learned.confidence,\n estimated_limit: Math.round(learned.estimated_limit),\n current_consumption: consumption.billable_tokens,\n minutes_to_limit: minutesToLimit,\n calibration_events: learned.sample_count,\n source: 'calibrated',\n };\n }\n\n // No calibration data — return what we know\n return {\n model_family: family,\n estimated_utilization: 0,\n confidence: 0,\n estimated_limit: 0,\n current_consumption: consumption.billable_tokens,\n minutes_to_limit: null,\n calibration_events: 0,\n source: 'uncalibrated',\n };\n}\n\n// === In-source Tests ===\n\nif (import.meta.vitest) {\n const { describe, it, expect } = import.meta.vitest;\n const { makeEntry } = await import('./test-helpers.js');\n\n describe('modelFamily', () => {\n it('extracts opus', () => expect(modelFamily('claude-opus-4-6-20260205')).toBe('opus'));\n it('extracts sonnet', () => expect(modelFamily('claude-sonnet-4-20250514')).toBe('sonnet'));\n it('extracts haiku', () => expect(modelFamily('claude-haiku-4-5-20251001')).toBe('haiku'));\n it('returns unknown for unrecognized', () => expect(modelFamily('gpt-4')).toBe('unknown'));\n });\n\n describe('currentWindowConsumption', () => {\n it('counts only entries in last 5 hours', () => {\n const now = new Date();\n const recent = new Date(now.getTime() - 60000).toISOString(); // 1 min ago\n const old = new Date(now.getTime() - 6 * 60 * 60 * 1000).toISOString(); // 6h ago\n\n const entries = [\n makeEntry({ timestamp: recent }),\n makeEntry({ timestamp: old }),\n ];\n const result = currentWindowConsumption(entries);\n expect(result.requests).toBe(1); // only the recent one\n });\n\n it('billable tokens exclude cache_read', () => {\n const now = new Date();\n const ts = new Date(now.getTime() - 60000).toISOString();\n const entries = [\n makeEntry({\n timestamp: ts,\n message: {\n model: 'claude-opus-4-6',\n usage: { input_tokens: 100, output_tokens: 50, cache_creation_input_tokens: 200, cache_read_input_tokens: 5000 },\n },\n }),\n ];\n const result = currentWindowConsumption(entries);\n // billable = input(100) + cache_creation(200) = 300 (NOT cache_read 5000)\n expect(result.billable_tokens).toBe(300);\n });\n\n it('returns zero for empty entries', () => {\n const result = currentWindowConsumption([]);\n expect(result.billable_tokens).toBe(0);\n expect(result.requests).toBe(0);\n });\n });\n\n describe('predictUtilization', () => {\n it('returns uncalibrated when no model data', () => {\n const result = predictUtilization([], 'claude-opus-4-6');\n expect(result.source).toBe('uncalibrated');\n expect(result.confidence).toBe(0);\n expect(result.calibration_events).toBe(0);\n });\n\n it('returns calibrated when model has learned limits', async () => {\n const { writeFileSync: wf, mkdirSync: md, readFileSync: rf, existsSync: ex, unlinkSync } = await import('node:fs');\n const { join: jn } = await import('node:path');\n const { homedir: hd } = await import('node:os');\n const dir = jn(hd(), '.cctrack');\n md(dir, { recursive: true });\n const modelPath = jn(dir, 'rate-model.json');\n const backup = ex(modelPath) ? rf(modelPath, 'utf-8') : null;\n\n wf(modelPath, JSON.stringify({\n limits: { opus: { estimated_limit: 1000000, confidence: 0.8, sample_count: 5, last_calibration: '2026-03-25T00:00:00Z', alpha: 0.3 } },\n updated_at: '2026-03-25T00:00:00Z',\n }));\n\n const now = new Date();\n const entries = [\n makeEntry({ timestamp: new Date(now.getTime() - 60000).toISOString(), message: { model: 'claude-opus-4-6', usage: { input_tokens: 500, output_tokens: 200, cache_creation_input_tokens: 100, cache_read_input_tokens: 0 } } }),\n ];\n\n const result = predictUtilization(entries, 'claude-opus-4-6');\n expect(result.source).toBe('calibrated');\n expect(result.confidence).toBe(0.8);\n expect(result.estimated_limit).toBe(1000000);\n expect(result.current_consumption).toBe(600);\n\n // Restore\n if (backup) wf(modelPath, backup);\n else { try { unlinkSync(modelPath); } catch {} }\n });\n });\n}\n"],"mappings":"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","names":["test_helpers_exports","__export","makeEntry","overrides","init_test_helpers","__esmMin","pricing_exports","__export","calculateEntryCost","calculateTieredCost","fetchPricing","getAllPricing","getModelPricing","getPricingInfo","initPricing","resetPricing","setPricingData","updatePricing","readFileSync","writeFileSync","existsSync","mkdirSync","join","dirname","fileURLToPath","homedir","getCacheAge","CACHE_FILE","cached","readCache","writeCache","data","CACHE_DIR","loadBundled","__dirname","pricingPath","fallbackPath","parsePricingHtml","html","models","aliases","modelIdPattern","foundModelIds","match","rowPattern","rows","row","modelMatch","modelId","prices","priceMatch","rowPricePattern","pricing","shortMatch","response","PRICING_URL","bundled","fetched","merged","newModels","id","alias","target","loadPricing","pricingData","CACHE_TTL_MS","age","cacheAge","hours","mins","modelName","resolved","tokens","basePricePerMillion","tieredPricePerMillion","threshold","baseRate","tieredRate","inputTokens","outputTokens","cacheWriteTokens","cacheReadTokens","embeddedCostUSD","input","output","cacheWrite","cacheRead","init_pricing","__esmMin","describe","it","expect","beforeEach","testPricing","cost","expected","result","p","info","Command","chalk","Table","readdirSync","statSync","existsSync","join","homedir","getProjectDirs","dirs","customDir","projectsDir","home","defaultPaths","p","fileProjectMap","findJsonlFiles","files","dir","projectDirs","pdir","projectName","decodeProjectDir","projectPath","walkCollect","entries","entry","fullPath","statCache","dirExists","path","r","encoded","segments","currentPath","i","matched","len","candidate","variants","found","variant","testPath","parts","getProjectForFile","filePath","extractProjectName","cwd","describe","it","expect","beforeEach","afterEach","mkdirSync","writeFileSync","rmSync","joinPath","tmpdir","tmpBase","projDir","result","original","d","createReadStream","appendFileSync","mkdirSync","createInterface","join","homedir","z","UsageEntrySchema","PLAN_COSTS","BLOCK_DURATION_MS","BUDGET_THRESHOLDS","parseJsonlFile","filePath","entries","errors","skipped","projectName","getProjectForFile","rl","createInterface","createReadStream","line","trimmed","raw","parsed","UsageEntrySchema","entry","content","c","dir","join","homedir","mkdirSync","appendFileSync","parseAllFiles","filePaths","combined","BATCH_SIZE","i","batch","results","result","writeTempJsonl","name","lines","tmpDir","path","writeFileSync","describe","it","expect","afterAll","unlinkSync","rmSync","tmpdir","validLine","apiErrorLine","syntheticLine","badSchema","path1","path2","paths","p","createHash","createDedupKey","entry","deduplicateEntries","entries","seen","result","key","describe","it","expect","makeEntry","a","b","init_pricing","processEntry","entry","mode","usage","model","tokens","calc","calculateEntryCost","calculatedCost","cost","emptyTokens","emptyCost","addTokens","a","b","addCosts","describe","it","expect","beforeEach","setPricingData","testPricing","makeEntry","overrides","result","t","c","toDateString","timestamp","timezone","date","toMonthString","getHourAndDay","parts","hourPart","p","dayPart","dayMap","isInRange","since","until","describe","it","expect","result","emptyAggregate","emptyTokens","emptyCost","accumulate","agg","result","addTokens","addCosts","filterEntries","entries","options","e","isInRange","extractProjectName","aggregateDaily","mode","timezone","map","entry","date","toDateString","model","project","day","processEntry","a","b","aggregateMonthly","month","toMonthString","m","aggregateSessions","sid","session","currentModelCount","primaryCount","aggregateProjects","p","aggregateModels","buildHeatmap","grid","hour","getHourAndDay","buildDashboardData","sorted","totals","dailyMap","monthlyMap","sessionMap","projectMap","modelMap","heatmap","projectHeatmaps","tokens","d","sess","currentCount","buildProjectHeatmaps","maps","describe","it","expect","beforeEach","setPricingData","testPricing","makeEntry","data","projData","projA","totalCost","totalReqs","cacheReadCost","costPerReq","cacheSavings","s","formatCost","cost","formatTokens","tokens","formatDuration","ms","seconds","minutes","parseCostMode","input","mode","csvEscape","value","describe","it","expect","shortenModelName","model","map","prefix","short","readFileSync","writeFileSync","mkdirSync","unlinkSync","existsSync","join","homedir","chalk","CONFIG_DIR","join","homedir","CONFIG_PATH","loadBudgetConfig","raw","readFileSync","saveBudgetConfig","config","mkdirSync","existing","writeFileSync","loadFullConfig","resetConfig","existsSync","CONFIG_PATH","unlinkSync","getConfigPath","getBudgetLevel","percentage","BUDGET_THRESHOLDS","calculateBudgetStatus","spent","budget","level","remaining","budgetColor","chalk","formatBudgetBar","width","clamped","filled","empty","bar","color","pctStr","describe","it","expect","beforeEach","afterEach","mkdtempSync","writeTmp","readTmp","rmSync","tmpdir","joinPath","config","loadBudgetConfig","status","stripAnsi","s","barPart","fn","e","calculateBurnRate","entries","mode","budget","sorted","a","b","firstTime","lastTime","totalCost","entry","result","processEntry","spanMs","spanHours","hourly_cost","spanDays","daily_cost","projected_monthly","remaining","describe","it","expect","beforeEach","setPricingData","testPricing","makeEntry","expectedMs","dailyToCsv","data","breakdown","lines","day","model","agg","dailyToTable","chalk","table","Table","h","formatTokens","formatCost","maxCost","d","barLen","bar","budgetConfig","loadBudgetConfig","today","todayCost","status","calculateBudgetStatus","formatBudgetBar","totalCost","sum","totalTokens","showBurnRate","entries","mode","rate","calculateBurnRate","registerDailyCommand","program","opts","dirs","getProjectDirs","files","findJsonlFiles","parseAllFiles","unique","deduplicateEntries","filtered","filterEntries","parseCostMode","aggregateDaily","chalk","Table","monthlyToCsv","data","breakdown","lines","m","model","agg","monthlyToTable","chalk","table","Table","h","formatTokens","formatCost","budgetConfig","loadBudgetConfig","currentMonth","totalSpent","status","calculateBudgetStatus","formatBudgetBar","totalCost","sum","totalTokens","registerMonthlyCommand","program","opts","dirs","getProjectDirs","files","findJsonlFiles","entries","parseAllFiles","unique","deduplicateEntries","filtered","filterEntries","mode","parseCostMode","aggregateMonthly","chalk","Table","sessionDuration","session","truncateId","id","length","sessionToCsv","data","lines","s","duration","csvEscape","sessionToTable","full","chalk","table","Table","h","shortenModelName","formatDuration","formatTokens","formatCost","totalCost","sum","totalTokens","totalRequests","registerSessionCommand","program","opts","dirs","getProjectDirs","files","findJsonlFiles","entries","parseAllFiles","unique","deduplicateEntries","filtered","filterEntries","mode","parseCostMode","aggregateSessions","writeFileSync","mkdirSync","existsSync","join","dirname","homedir","chalk","generateHtml","data","rawJson","safeJson","esc","s","projectOptions","p","dateStart","dateEnd","openInBrowser","filePath","cmd","execFile","dashboardAction","opts","dirs","getProjectDirs","files","findJsonlFiles","entries","parseAllFiles","unique","deduplicateEntries","filtered","filterEntries","chalk","mode","parseCostMode","buildDashboardData","html","resolve","normalize","realpathSync","savePath","home","homedir","cwd","realSavePath","parentDir","dirname","existsSync","mkdirSync","writeFileSync","defaultDir","join","defaultPath","registerDashboardCommand","program","entriesToCsv","entries","mode","lines","entry","date","sessionId","project","extractProjectName","model","usage","result","processEntry","csvEscape","registerExportCommand","program","exportCmd","opts","dirs","getProjectDirs","files","findJsonlFiles","parseAllFiles","unique","deduplicateEntries","sorted","filterEntries","a","b","parseCostMode","filtered","data","buildDashboardData","chalk","registerRoiCommand","program","opts","dirs","getProjectDirs","files","findJsonlFiles","entries","parseAllFiles","unique","deduplicateEntries","filtered","filterEntries","chalk","plan","mode","parseCostMode","subscriptionCost","PLAN_COSTS","totalApiCost","totalInputTokens","totalOutputTokens","totalCacheWriteTokens","totalCacheReadTokens","totalTokens","entry","result","processEntry","sorted","a","b","firstDate","lastDate","daysSpan","avgDailyCost","projectedMonthlyCost","savings","formatCost","w","loss","statSync","chalk","clearScreen","loadAndDisplay","mode","project","timezone","dirs","getProjectDirs","files","findJsonlFiles","chalk","entries","errors","parseAllFiles","unique","deduplicateEntries","today","filtered","filterEntries","todayData","aggregateDaily","d","formatTokens","formatCost","rate","calculateBurnRate","sessions","aggregateSessions","recent","s","duration","id","formatDuration","model","agg","newestFile","newest","f","mtime","statSync","ago","budgetConfig","loadBudgetConfig","status","calculateBudgetStatus","formatBudgetBar","registerLiveCommand","program","opts","intervalMs","parseCostMode","stopped","scheduleNext","r","init_pricing","chalk","Table","registerPricingCommand","program","cmd","options","info","getPricingInfo","data","getAllPricing","chalk","sorted","a","b","table","Table","h","id","p","tiered","formatCost","aliasSorted","alias","target","newModels","source","updatePricing","m","c","chalk","registerConfigCommand","program","configCmd","key","value","numValue","chalk","parts","budgetKey","config","loadBudgetConfig","saveBudgetConfig","formatCost","loadFullConfig","configPath","getConfigPath","budget","resetConfig","chalk","Table","readFileSync","writeFileSync","mkdirSync","statSync","existsSync","readdirSync","openSync","readSync","closeSync","join","homedir","CACHE_DIR","join","homedir","CACHE_FILE","RATELIMIT_FILE","CACHE_MAX_AGE_MS","readStdinRateLimits","stdinContent","readFileSync","input","rl","limits","mkdirSync","writeFileSync","readCachedRateLimits","existsSync","raw","data","getRateLimits","readCache","writeCache","cache","isCacheFresh","stat","statSync","findJsonlFilesSync","dirs","files","walk","dir","entries","readdirSync","entry","fullPath","getProjectDirsSync","customDir","projectsDir","home","defaultPaths","p","parseRecentEntriesSync","mode","now","oneDayAgo","todayStr","blockStart","BLOCK_DURATION_MS","todayCost","totalTokens","blockTokens","blockRequests","latestModel","latestTimestamp","latestSessionId","sessionCosts","file","content","stat2","maxBytes","fd","openSync","buf","readSync","closeSync","firstNewline","lines","line","trimmed","parsed","UsageEntrySchema","result","processEntry","sessionCost","blockPct","blockElapsed","blockRemaining","budgetLevel","configPath","dailyBudget","pct","formatDuration","buildProgressBarCompact","percentage","width","filled","empty","formatOutput","template","formatCost","shortenModelName","formatTokens","parts","bar","resetMs","resetStr","eu","registerStatuslineCommand","program","opts","parseCostMode","useCache","stdinLimits","cached","aggregateBlocks","entries","mode","numBlocks","now","oldestBlock","BLOCK_DURATION_MS","blockMap","i","blockEnd","blockStart","emptyAggregate","entry","entryTime","blockIndex","block","model","result","processEntry","accumulate","modelAgg","addTokens","addCosts","clearScreen","formatBlockTime","isoString","date","y","mo","d","h","mi","displayBlocks","blocks","rateLimits","getRateLimits","chalk","showWindow","label","w","pct","resetMs","resetStr","formatDuration","color","bar","eu","current","formatCost","formatTokens","pastBlocks","b","table","Table","block","loadAndDisplay","mode","opts","dirs","getProjectDirs","files","findJsonlFiles","entries","parseAllFiles","unique","deduplicateEntries","filtered","filterEntries","aggregateBlocks","registerBlocksCommand","program","parseCostMode","timer","chalk","readFileSync","writeFileSync","existsSync","mkdirSync","appendFileSync","join","homedir","DATA_DIR","EVENTS_FILE","MODEL_FILE","WINDOW_MS","loadModel","modelFamily","model","loadEvents","existsSync","EVENTS_FILE","readFileSync","line","currentWindowConsumption","entries","now","windowStart","WINDOW_MS","billable","total","requests","entry","entryTime","usage","predictUtilization","primaryModel","family","modelFamily","learned","loadModel","consumption","utilization","elapsedHours","minutesToLimit","hourlyRate","remaining","describe","it","expect","makeEntry","recent","old","result","ts","wf","md","rf","ex","unlinkSync","jn","hd","dir","modelPath","backup","buildBar","pct","width","clamped","filled","bar","chalk","registerLimitsCommand","program","opts","dirs","getProjectDirs","files","findJsonlFiles","entries","parseAllFiles","unique","deduplicateEntries","mode","parseCostMode","primaryModel","a","b","prediction","predictUtilization","consumption","currentWindowConsumption","model","loadModel","events","loadEvents","formatTokens","color","formatDuration","recent","e","date","init_pricing","initPricing","program","Command","registerDailyCommand","registerMonthlyCommand","registerSessionCommand","registerDashboardCommand","registerExportCommand","registerRoiCommand","registerLiveCommand","registerPricingCommand","registerConfigCommand","registerBlocksCommand","registerStatuslineCommand","registerLimitsCommand","dashboardAction"]}