@abnersajr/claude-timeline 1.0.0 → 1.0.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"db-reader.d.ts","sourceRoot":"","sources":["../src/db-reader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAc,IAAI,EAAE,MAAM,YAAY,CAAA;AAsBnE,4CAA4C;AAC5C,qBAAa,WAAY,SAAQ,KAAK;IACpC,IAAI,SAAI;gBACI,OAAO,EAAE,MAAM;CAI5B;AAED,4CAA4C;AAC5C,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,SAAI;gBACI,SAAS,EAAE,MAAM;CAI9B;AA4BD;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAgD7E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,CAiClE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiB5E;AAED,2BAA2B;AAC3B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAWD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CA8BjE;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,cAAc,EAAE,CA2EzE;AAqLD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,SAAM,GACV,cAAc,EAAE,CAkDlB"}
1
+ {"version":3,"file":"db-reader.d.ts","sourceRoot":"","sources":["../src/db-reader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAc,IAAI,EAAE,MAAM,YAAY,CAAA;AAsBnE,4CAA4C;AAC5C,qBAAa,WAAY,SAAQ,KAAK;IACpC,IAAI,SAAI;gBACI,OAAO,EAAE,MAAM;CAI5B;AAED,4CAA4C;AAC5C,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,SAAI;gBACI,SAAS,EAAE,MAAM;CAI9B;AA4BD;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAiD7E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,CAiClE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiB5E;AAED,2BAA2B;AAC3B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAWD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CA8BjE;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,cAAc,EAAE,CA2EzE;AAqLD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,SAAM,GACV,cAAc,EAAE,CAkDlB"}
@@ -270,4 +270,4 @@ function calculateSessionCost(session, turns) {
270
270
  //#endregion
271
271
  export { refreshPricing as a, getPricing as i, calculateTurnCost as n, savePricingFile as o, fetchFromOpenRouter as r, normalizeModelName as s, calculateSessionCost as t };
272
272
 
273
- //# sourceMappingURL=pricing-DTmya3JY.mjs.map
273
+ //# sourceMappingURL=pricing-5MZ5_SQc.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pricing-DTmya3JY.mjs","names":[],"sources":["../packages/extractor/src/model-parser.ts","../packages/extractor/src/pricing.ts"],"sourcesContent":["/**\n * Model name parser for multi-model sessions.\n *\n * Strips provider prefixes and date suffixes from raw model strings\n * to produce a normalized key suitable for pricing lookups.\n *\n * Examples:\n * \"anthropic/claude-sonnet-4-20250514\" → \"claude-sonnet-4\"\n * \"claude-opus-4-20250514\" → \"claude-opus-4\"\n * \"claude-sonnet-4-6\" → \"claude-sonnet-4-6\"\n */\n\n/** Date suffix pattern: 8-digit date at the end of the string */\nconst DATE_SUFFIX_RE = /^(.+)-\\d{8}$/\n\n/**\n * Parse a raw model string into a normalized form for pricing lookups.\n *\n * - Strips provider prefix (e.g. \"anthropic/\")\n * - Strips date suffix (e.g. \"-20250514\")\n * - Lowercases the result\n * - Returns \"unknown\" for null/undefined/empty/whitespace\n */\nexport function parseModelName(raw: string | null | undefined): string {\n if (!raw) return \"unknown\"\n\n const trimmed = raw.trim()\n if (trimmed === \"\") return \"unknown\"\n\n // Strip provider prefix (e.g. \"anthropic/\")\n const slashIndex = trimmed.indexOf(\"/\")\n const withoutPrefix = slashIndex >= 0 ? trimmed.slice(slashIndex + 1) : trimmed\n\n // Strip date suffix (e.g. \"-20250514\")\n const dateMatch = DATE_SUFFIX_RE.exec(withoutPrefix)\n const withoutDate = dateMatch ? dateMatch[1] : withoutPrefix\n\n return withoutDate.toLowerCase()\n}\n\n/**\n * Alias for parseModelName — ensures consistent key format for pricing lookup.\n */\nexport const normalizeModelName = parseModelName\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport { normalizeModelName } from \"./model-parser.js\"\nimport type { PricingFile, PricingRate, SessionMetadata, SessionPricing, Turn, TurnPricing } from \"./types.js\"\n\n// ── Constants ──────────────────────────────────────────────────────────\nconst PRICING_DIR = join(homedir(), \".claude-timeline\")\nconst PRICING_PATH = join(PRICING_DIR, \"pricing.json\")\nconst STALE_MS = 5 * 24 * 60 * 60 * 1000 // 5 days\nconst OPENROUTER_API = \"https://openrouter.ai/api/v1/models\"\n\n// ── Fallback table (compiled in) ────────────────────────────────────────\nconst FALLBACK_TABLE: Record<string, PricingRate> = {\n \"claude-opus-4-8\": {\n model: \"claude-opus-4-8\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-opus-4-7\": {\n model: \"claude-opus-4-7\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-sonnet-4-6\": {\n model: \"claude-sonnet-4-6\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-haiku-4-5\": {\n model: \"claude-haiku-4-5\",\n inputPerMTok: 1.0,\n outputPerMTok: 5.0,\n cacheReadPerMTok: 0.1,\n cacheWritePerMTok: 1.25,\n },\n \"claude-opus-4-6\": {\n model: \"claude-opus-4-6\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-opus-4-5\": {\n model: \"claude-opus-4-5\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-opus-4-1\": {\n model: \"claude-opus-4-1\",\n inputPerMTok: 15.0,\n outputPerMTok: 75.0,\n cacheReadPerMTok: 1.5,\n cacheWritePerMTok: 18.75,\n },\n \"claude-opus-4\": {\n model: \"claude-opus-4\",\n inputPerMTok: 15.0,\n outputPerMTok: 75.0,\n cacheReadPerMTok: 1.5,\n cacheWritePerMTok: 18.75,\n },\n \"claude-sonnet-4-5\": {\n model: \"claude-sonnet-4-5\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-sonnet-4\": {\n model: \"claude-sonnet-4\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-sonnet-3-7\": {\n model: \"claude-sonnet-3-7\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-haiku-3-5\": {\n model: \"claude-haiku-3-5\",\n inputPerMTok: 0.8,\n outputPerMTok: 4.0,\n cacheReadPerMTok: 0.08,\n cacheWritePerMTok: 1.0,\n },\n \"claude-haiku-3\": {\n model: \"claude-haiku-3\",\n inputPerMTok: 0.25,\n outputPerMTok: 1.25,\n cacheReadPerMTok: 0.03,\n cacheWritePerMTok: 0.3,\n },\n}\n\n// ── OpenRouter API ─────────────────────────────────────────────────────\n\ninterface OpenRouterModel {\n id: string\n name: string\n pricing: {\n prompt: string\n completion: string\n input_cache_read?: string\n input_cache_write?: string\n }\n}\n\n/** Normalize OpenRouter model ID to our internal format */\nfunction normalizeOpenRouterId(id: string): string {\n return id\n .replace(/^anthropic\\//, \"\") // strip provider prefix\n .replace(/\\./g, \"-\") // dots → hyphens\n .toLowerCase()\n}\n\n/** Parse OpenRouter pricing string to number, returns NaN if invalid */\nfunction parsePrice(s: string | undefined): number {\n if (!s) return Number.NaN\n const n = Number.parseFloat(s)\n return Number.isFinite(n) ? n : Number.NaN\n}\n\n/** Fetch pricing from OpenRouter API */\nexport async function fetchFromOpenRouter(): Promise<Record<string, PricingRate>> {\n const res = await fetch(OPENROUTER_API, {\n headers: {\n \"User-Agent\": \"claude-timeline/1.0\",\n Accept: \"application/json\",\n },\n })\n\n if (!res.ok) {\n throw new Error(`OpenRouter API error: ${res.status} ${res.statusText}`)\n }\n\n const json = (await res.json()) as { data: OpenRouterModel[] }\n const models: Record<string, PricingRate> = {}\n\n for (const item of json.data) {\n // Only include Anthropic/Claude models\n if (!item.id.startsWith(\"anthropic/claude\")) continue\n\n const prompt = parsePrice(item.pricing.prompt)\n const completion = parsePrice(item.pricing.completion)\n const cacheRead = parsePrice(item.pricing.input_cache_read)\n const cacheWrite = parsePrice(item.pricing.input_cache_write)\n\n // Skip if any required price is missing\n if ([prompt, completion, cacheRead, cacheWrite].some(Number.isNaN)) continue\n\n const model = normalizeOpenRouterId(item.id)\n models[model] = {\n model,\n inputPerMTok: prompt * 1_000_000,\n outputPerMTok: completion * 1_000_000,\n cacheReadPerMTok: cacheRead * 1_000_000,\n cacheWritePerMTok: cacheWrite * 1_000_000,\n }\n }\n\n if (Object.keys(models).length === 0) {\n throw new Error(\"No Claude models found in OpenRouter response\")\n }\n\n return models\n}\n\n// ── Cache Management ───────────────────────────────────────────────────\n\n/** Check if cached pricing data is stale (older than 5 days) */\nexport function isCacheStale(data: PricingFile): boolean {\n const fetchedAt = new Date(data.fetchedAt).getTime()\n return Date.now() - fetchedAt > STALE_MS\n}\n\n/** Load pricing file from disk, returns null if missing or invalid */\nfunction loadPricingFile(): PricingFile | null {\n try {\n if (!existsSync(PRICING_PATH)) return null\n const raw = readFileSync(PRICING_PATH, \"utf-8\")\n const data = JSON.parse(raw) as PricingFile\n if (!data.fetchedAt || !data.models || Object.keys(data.models).length === 0) return null\n return data\n } catch {\n return null\n }\n}\n\n/** Save pricing file to disk */\nexport function savePricingFile(models: Record<string, PricingRate>): void {\n if (!existsSync(PRICING_DIR)) mkdirSync(PRICING_DIR, { recursive: true })\n const file: PricingFile = {\n fetchedAt: new Date().toISOString(),\n models,\n }\n writeFileSync(PRICING_PATH, JSON.stringify(file, null, 2) + \"\\n\", \"utf-8\")\n}\n\n// ── Main Pricing Logic ─────────────────────────────────────────────────\n\n/** Pricing table — loaded once at module init */\nlet PRICING_TABLE: Record<string, PricingRate> = FALLBACK_TABLE\n\n/** Initialize pricing: load from cache or fetch from OpenRouter */\nexport async function initPricing(): Promise<void> {\n const cached = loadPricingFile()\n\n if (cached && !isCacheStale(cached)) {\n PRICING_TABLE = cached.models\n return\n }\n\n try {\n PRICING_TABLE = await fetchFromOpenRouter()\n savePricingFile(PRICING_TABLE)\n } catch (err) {\n console.warn(`Failed to fetch pricing from OpenRouter: ${(err as Error).message}`)\n if (cached) {\n console.warn(\"Using stale cached pricing data\")\n PRICING_TABLE = cached.models\n } else {\n console.warn(\"Using compiled-in fallback pricing\")\n }\n }\n}\n\n/** Force refresh pricing from OpenRouter (used by update-pricing CLI) */\nexport async function refreshPricing(): Promise<Record<string, PricingRate>> {\n const models = await fetchFromOpenRouter()\n savePricingFile(models)\n PRICING_TABLE = models\n return models\n}\n\n/**\n * Look up pricing for a model.\n * Falls back to claude-sonnet-4-6 rates if unknown.\n */\nexport function getPricing(modelName: string): PricingRate {\n const normalized = normalizeModelName(modelName)\n const rate = PRICING_TABLE[normalized]\n if (rate) return rate\n\n console.warn(`Unknown model \"${normalized}\" (raw: \"${modelName}\"), falling back to claude-sonnet-4-6 rates`)\n return { ...PRICING_TABLE[\"claude-sonnet-4-6\"], model: normalized }\n}\n\n/**\n * Calculate per-turn cost breakdown.\n * Uses turn-level model detection when available, otherwise falls back to session default rate.\n */\nexport function calculateTurnCost(turn: Turn, sessionRate: PricingRate): TurnPricing {\n const rate = turn.model\n ? getPricing(normalizeModelName(turn.model))\n : sessionRate\n\n const inputCost = (turn.tokenUsage.inputTokens / 1_000_000) * rate.inputPerMTok\n const outputCost = (turn.tokenUsage.outputTokens / 1_000_000) * rate.outputPerMTok\n const cacheReadCost = (turn.tokenUsage.cacheReadTokens / 1_000_000) * rate.cacheReadPerMTok\n const cacheWriteTokens = turn.tokenUsage.cacheCreation5mTokens + turn.tokenUsage.cacheCreation1hTokens\n const cacheWriteCost = (cacheWriteTokens / 1_000_000) * rate.cacheWritePerMTok\n const totalCost = inputCost + outputCost + cacheReadCost + cacheWriteCost\n\n return {\n inputCost,\n outputCost,\n cacheReadCost,\n cacheWriteCost,\n totalCost,\n }\n}\n\n/**\n * Calculate full session pricing from session metadata and turns\n */\nexport function calculateSessionCost(session: SessionMetadata, turns: Turn[]): SessionPricing {\n const rate = getPricing(normalizeModelName(session.model))\n const turnsPricing = turns.map((turn) => calculateTurnCost(turn, rate))\n const totalCost = turnsPricing.reduce((sum, t) => sum + t.totalCost, 0)\n\n // Compute per-model breakdown\n const modelBreakdown: SessionPricing[\"modelBreakdown\"] = {}\n for (let i = 0; i < turns.length; i++) {\n const turn = turns[i]\n const pricing = turnsPricing[i]\n const model = normalizeModelName(turn.model ?? session.model)\n\n if (!modelBreakdown[model]) {\n modelBreakdown[model] = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n cost: 0,\n turnCount: 0,\n }\n }\n\n const entry = modelBreakdown[model]\n entry.inputTokens += turn.tokenUsage.inputTokens\n entry.outputTokens += turn.tokenUsage.outputTokens\n entry.cacheReadTokens += turn.tokenUsage.cacheReadTokens\n entry.cacheCreationTokens += turn.tokenUsage.cacheCreation5mTokens + turn.tokenUsage.cacheCreation1hTokens\n entry.cost += pricing.totalCost\n entry.turnCount++\n }\n\n return {\n estimatedTotalCost: totalCost,\n turnsPricing,\n apiTotalCost: null,\n apiSnapshotCount: 0,\n apiLastSnapshotAt: null,\n totalCost,\n costSource: \"estimated\",\n modelBreakdown,\n pricingRate: rate,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pricing-5MZ5_SQc.mjs","names":[],"sources":["../packages/extractor/src/model-parser.ts","../packages/extractor/src/pricing.ts"],"sourcesContent":["/**\n * Model name parser for multi-model sessions.\n *\n * Strips provider prefixes and date suffixes from raw model strings\n * to produce a normalized key suitable for pricing lookups.\n *\n * Examples:\n * \"anthropic/claude-sonnet-4-20250514\" → \"claude-sonnet-4\"\n * \"claude-opus-4-20250514\" → \"claude-opus-4\"\n * \"claude-sonnet-4-6\" → \"claude-sonnet-4-6\"\n */\n\n/** Date suffix pattern: 8-digit date at the end of the string */\nconst DATE_SUFFIX_RE = /^(.+)-\\d{8}$/\n\n/**\n * Parse a raw model string into a normalized form for pricing lookups.\n *\n * - Strips provider prefix (e.g. \"anthropic/\")\n * - Strips date suffix (e.g. \"-20250514\")\n * - Lowercases the result\n * - Returns \"unknown\" for null/undefined/empty/whitespace\n */\nexport function parseModelName(raw: string | null | undefined): string {\n if (!raw) return \"unknown\"\n\n const trimmed = raw.trim()\n if (trimmed === \"\") return \"unknown\"\n\n // Strip provider prefix (e.g. \"anthropic/\")\n const slashIndex = trimmed.indexOf(\"/\")\n const withoutPrefix = slashIndex >= 0 ? trimmed.slice(slashIndex + 1) : trimmed\n\n // Strip date suffix (e.g. \"-20250514\")\n const dateMatch = DATE_SUFFIX_RE.exec(withoutPrefix)\n const withoutDate = dateMatch ? dateMatch[1] : withoutPrefix\n\n return withoutDate.toLowerCase()\n}\n\n/**\n * Alias for parseModelName — ensures consistent key format for pricing lookup.\n */\nexport const normalizeModelName = parseModelName\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport { normalizeModelName } from \"./model-parser.js\"\nimport type { PricingFile, PricingRate, SessionMetadata, SessionPricing, Turn, TurnPricing } from \"./types.js\"\n\n// ── Constants ──────────────────────────────────────────────────────────\nconst PRICING_DIR = join(homedir(), \".claude-timeline\")\nconst PRICING_PATH = join(PRICING_DIR, \"pricing.json\")\nconst STALE_MS = 5 * 24 * 60 * 60 * 1000 // 5 days\nconst OPENROUTER_API = \"https://openrouter.ai/api/v1/models\"\n\n// ── Fallback table (compiled in) ────────────────────────────────────────\nconst FALLBACK_TABLE: Record<string, PricingRate> = {\n \"claude-opus-4-8\": {\n model: \"claude-opus-4-8\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-opus-4-7\": {\n model: \"claude-opus-4-7\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-sonnet-4-6\": {\n model: \"claude-sonnet-4-6\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-haiku-4-5\": {\n model: \"claude-haiku-4-5\",\n inputPerMTok: 1.0,\n outputPerMTok: 5.0,\n cacheReadPerMTok: 0.1,\n cacheWritePerMTok: 1.25,\n },\n \"claude-opus-4-6\": {\n model: \"claude-opus-4-6\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-opus-4-5\": {\n model: \"claude-opus-4-5\",\n inputPerMTok: 5.0,\n outputPerMTok: 25.0,\n cacheReadPerMTok: 0.5,\n cacheWritePerMTok: 6.25,\n },\n \"claude-opus-4-1\": {\n model: \"claude-opus-4-1\",\n inputPerMTok: 15.0,\n outputPerMTok: 75.0,\n cacheReadPerMTok: 1.5,\n cacheWritePerMTok: 18.75,\n },\n \"claude-opus-4\": {\n model: \"claude-opus-4\",\n inputPerMTok: 15.0,\n outputPerMTok: 75.0,\n cacheReadPerMTok: 1.5,\n cacheWritePerMTok: 18.75,\n },\n \"claude-sonnet-4-5\": {\n model: \"claude-sonnet-4-5\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-sonnet-4\": {\n model: \"claude-sonnet-4\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-sonnet-3-7\": {\n model: \"claude-sonnet-3-7\",\n inputPerMTok: 3.0,\n outputPerMTok: 15.0,\n cacheReadPerMTok: 0.3,\n cacheWritePerMTok: 3.75,\n },\n \"claude-haiku-3-5\": {\n model: \"claude-haiku-3-5\",\n inputPerMTok: 0.8,\n outputPerMTok: 4.0,\n cacheReadPerMTok: 0.08,\n cacheWritePerMTok: 1.0,\n },\n \"claude-haiku-3\": {\n model: \"claude-haiku-3\",\n inputPerMTok: 0.25,\n outputPerMTok: 1.25,\n cacheReadPerMTok: 0.03,\n cacheWritePerMTok: 0.3,\n },\n}\n\n// ── OpenRouter API ─────────────────────────────────────────────────────\n\ninterface OpenRouterModel {\n id: string\n name: string\n pricing: {\n prompt: string\n completion: string\n input_cache_read?: string\n input_cache_write?: string\n }\n}\n\n/** Normalize OpenRouter model ID to our internal format */\nfunction normalizeOpenRouterId(id: string): string {\n return id\n .replace(/^anthropic\\//, \"\") // strip provider prefix\n .replace(/\\./g, \"-\") // dots → hyphens\n .toLowerCase()\n}\n\n/** Parse OpenRouter pricing string to number, returns NaN if invalid */\nfunction parsePrice(s: string | undefined): number {\n if (!s) return Number.NaN\n const n = Number.parseFloat(s)\n return Number.isFinite(n) ? n : Number.NaN\n}\n\n/** Fetch pricing from OpenRouter API */\nexport async function fetchFromOpenRouter(): Promise<Record<string, PricingRate>> {\n const res = await fetch(OPENROUTER_API, {\n headers: {\n \"User-Agent\": \"claude-timeline/1.0\",\n Accept: \"application/json\",\n },\n })\n\n if (!res.ok) {\n throw new Error(`OpenRouter API error: ${res.status} ${res.statusText}`)\n }\n\n const json = (await res.json()) as { data: OpenRouterModel[] }\n const models: Record<string, PricingRate> = {}\n\n for (const item of json.data) {\n // Only include Anthropic/Claude models\n if (!item.id.startsWith(\"anthropic/claude\")) continue\n\n const prompt = parsePrice(item.pricing.prompt)\n const completion = parsePrice(item.pricing.completion)\n const cacheRead = parsePrice(item.pricing.input_cache_read)\n const cacheWrite = parsePrice(item.pricing.input_cache_write)\n\n // Skip if any required price is missing\n if ([prompt, completion, cacheRead, cacheWrite].some(Number.isNaN)) continue\n\n const model = normalizeOpenRouterId(item.id)\n models[model] = {\n model,\n inputPerMTok: prompt * 1_000_000,\n outputPerMTok: completion * 1_000_000,\n cacheReadPerMTok: cacheRead * 1_000_000,\n cacheWritePerMTok: cacheWrite * 1_000_000,\n }\n }\n\n if (Object.keys(models).length === 0) {\n throw new Error(\"No Claude models found in OpenRouter response\")\n }\n\n return models\n}\n\n// ── Cache Management ───────────────────────────────────────────────────\n\n/** Check if cached pricing data is stale (older than 5 days) */\nexport function isCacheStale(data: PricingFile): boolean {\n const fetchedAt = new Date(data.fetchedAt).getTime()\n return Date.now() - fetchedAt > STALE_MS\n}\n\n/** Load pricing file from disk, returns null if missing or invalid */\nfunction loadPricingFile(): PricingFile | null {\n try {\n if (!existsSync(PRICING_PATH)) return null\n const raw = readFileSync(PRICING_PATH, \"utf-8\")\n const data = JSON.parse(raw) as PricingFile\n if (!data.fetchedAt || !data.models || Object.keys(data.models).length === 0) return null\n return data\n } catch {\n return null\n }\n}\n\n/** Save pricing file to disk */\nexport function savePricingFile(models: Record<string, PricingRate>): void {\n if (!existsSync(PRICING_DIR)) mkdirSync(PRICING_DIR, { recursive: true })\n const file: PricingFile = {\n fetchedAt: new Date().toISOString(),\n models,\n }\n writeFileSync(PRICING_PATH, JSON.stringify(file, null, 2) + \"\\n\", \"utf-8\")\n}\n\n// ── Main Pricing Logic ─────────────────────────────────────────────────\n\n/** Pricing table — loaded once at module init */\nlet PRICING_TABLE: Record<string, PricingRate> = FALLBACK_TABLE\n\n/** Initialize pricing: load from cache or fetch from OpenRouter */\nexport async function initPricing(): Promise<void> {\n const cached = loadPricingFile()\n\n if (cached && !isCacheStale(cached)) {\n PRICING_TABLE = cached.models\n return\n }\n\n try {\n PRICING_TABLE = await fetchFromOpenRouter()\n savePricingFile(PRICING_TABLE)\n } catch (err) {\n console.warn(`Failed to fetch pricing from OpenRouter: ${(err as Error).message}`)\n if (cached) {\n console.warn(\"Using stale cached pricing data\")\n PRICING_TABLE = cached.models\n } else {\n console.warn(\"Using compiled-in fallback pricing\")\n }\n }\n}\n\n/** Force refresh pricing from OpenRouter (used by update-pricing CLI) */\nexport async function refreshPricing(): Promise<Record<string, PricingRate>> {\n const models = await fetchFromOpenRouter()\n savePricingFile(models)\n PRICING_TABLE = models\n return models\n}\n\n/**\n * Look up pricing for a model.\n * Falls back to claude-sonnet-4-6 rates if unknown.\n */\nexport function getPricing(modelName: string): PricingRate {\n const normalized = normalizeModelName(modelName)\n const rate = PRICING_TABLE[normalized]\n if (rate) return rate\n\n console.warn(`Unknown model \"${normalized}\" (raw: \"${modelName}\"), falling back to claude-sonnet-4-6 rates`)\n return { ...PRICING_TABLE[\"claude-sonnet-4-6\"], model: normalized }\n}\n\n/**\n * Calculate per-turn cost breakdown.\n * Uses turn-level model detection when available, otherwise falls back to session default rate.\n */\nexport function calculateTurnCost(turn: Turn, sessionRate: PricingRate): TurnPricing {\n const rate = turn.model\n ? getPricing(normalizeModelName(turn.model))\n : sessionRate\n\n const inputCost = (turn.tokenUsage.inputTokens / 1_000_000) * rate.inputPerMTok\n const outputCost = (turn.tokenUsage.outputTokens / 1_000_000) * rate.outputPerMTok\n const cacheReadCost = (turn.tokenUsage.cacheReadTokens / 1_000_000) * rate.cacheReadPerMTok\n const cacheWriteTokens = turn.tokenUsage.cacheCreation5mTokens + turn.tokenUsage.cacheCreation1hTokens\n const cacheWriteCost = (cacheWriteTokens / 1_000_000) * rate.cacheWritePerMTok\n const totalCost = inputCost + outputCost + cacheReadCost + cacheWriteCost\n\n return {\n inputCost,\n outputCost,\n cacheReadCost,\n cacheWriteCost,\n totalCost,\n }\n}\n\n/**\n * Calculate full session pricing from session metadata and turns\n */\nexport function calculateSessionCost(session: SessionMetadata, turns: Turn[]): SessionPricing {\n const rate = getPricing(normalizeModelName(session.model))\n const turnsPricing = turns.map((turn) => calculateTurnCost(turn, rate))\n const totalCost = turnsPricing.reduce((sum, t) => sum + t.totalCost, 0)\n\n // Compute per-model breakdown\n const modelBreakdown: SessionPricing[\"modelBreakdown\"] = {}\n for (let i = 0; i < turns.length; i++) {\n const turn = turns[i]\n const pricing = turnsPricing[i]\n const model = normalizeModelName(turn.model ?? session.model)\n\n if (!modelBreakdown[model]) {\n modelBreakdown[model] = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n cost: 0,\n turnCount: 0,\n }\n }\n\n const entry = modelBreakdown[model]\n entry.inputTokens += turn.tokenUsage.inputTokens\n entry.outputTokens += turn.tokenUsage.outputTokens\n entry.cacheReadTokens += turn.tokenUsage.cacheReadTokens\n entry.cacheCreationTokens += turn.tokenUsage.cacheCreation5mTokens + turn.tokenUsage.cacheCreation1hTokens\n entry.cost += pricing.totalCost\n entry.turnCount++\n }\n\n return {\n estimatedTotalCost: totalCost,\n turnsPricing,\n apiTotalCost: null,\n apiSnapshotCount: 0,\n apiLastSnapshotAt: null,\n totalCost,\n costSource: \"estimated\",\n modelBreakdown,\n pricingRate: rate,\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { a as refreshPricing } from "./pricing-5MZ5_SQc.mjs";
2
+ export { refreshPricing };
package/dist/server.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
1
2
  //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -29984,7 +29985,8 @@ function getSession(dbPath, sessionId) {
29984
29985
  try {
29985
29986
  const row = db.prepare("SELECT * FROM sessions WHERE session_id = ?").get(sessionId);
29986
29987
  if (!row) throw new SessionNotFoundError(sessionId);
29987
- const model = row.model || getModelForSession(dbPath, sessionId);
29988
+ const rawModel = row.model || getModelForSession(dbPath, sessionId);
29989
+ const model = rawModel === "unknown" ? "claude-sonnet-4-6" : rawModel;
29988
29990
  const cwdRow = db.prepare("SELECT cwd, COUNT(*) as cnt FROM turns WHERE session_id = ? AND cwd IS NOT NULL GROUP BY cwd ORDER BY cnt DESC LIMIT 1").get(sessionId);
29989
29991
  const totalTokens = {
29990
29992
  inputTokens: row.total_input_tokens,
@@ -30071,7 +30073,7 @@ function listSessions(dbPath, limit = 20) {
30071
30073
  return db.prepare(`SELECT session_id, project_name, model, turn_count, first_timestamp, last_timestamp,
30072
30074
  total_input_tokens, total_output_tokens, total_cache_read, total_cache_creation
30073
30075
  FROM sessions ORDER BY last_timestamp DESC LIMIT ?`).all(limit).map((row) => {
30074
- const model = row.model || "claude-sonnet-4-6";
30076
+ const model = row.model && row.model !== "unknown" ? row.model : "claude-sonnet-4-6";
30075
30077
  const session = {
30076
30078
  sessionId: row.session_id,
30077
30079
  projectName: row.project_name,
@@ -31133,12 +31135,76 @@ function loadConfig() {
31133
31135
  };
31134
31136
  }
31135
31137
  function mountApiRoutes(app, config) {
31138
+ const TIMELINE_DIR = node_path.default.join((0, node_os.homedir)(), ".claude-timeline");
31139
+ const CONFIG_PATH = node_path.default.join(TIMELINE_DIR, "config.json");
31140
+ const CLAUDE_SETTINGS_PATH = node_path.default.join((0, node_os.homedir)(), ".claude", "settings.json");
31141
+ function isStatuslineInstalled() {
31142
+ if (!(0, node_fs.existsSync)(node_path.default.join(TIMELINE_DIR, "capture.js"))) return false;
31143
+ try {
31144
+ const cmd = JSON.parse((0, node_fs.readFileSync)(CLAUDE_SETTINGS_PATH, "utf-8")).statusLine?.command;
31145
+ return typeof cmd === "string" && cmd.includes("capture.js");
31146
+ } catch {
31147
+ return false;
31148
+ }
31149
+ }
31136
31150
  app.get("/api/health", (_req, res) => {
31137
31151
  res.json({
31138
31152
  status: "ok",
31139
31153
  version: "1.0.0"
31140
31154
  });
31141
31155
  });
31156
+ app.get("/api/status", async (_req, res) => {
31157
+ const dbExists = (0, node_fs.existsSync)(config.costStreamDbPath);
31158
+ const statuslineActive = isStatuslineInstalled();
31159
+ let sessionCount = 0;
31160
+ if (dbExists) try {
31161
+ const { CostStreamDb } = await Promise.resolve().then(() => require("./cost-stream-db-ZdfAm9Q-.js"));
31162
+ const db = new CostStreamDb(config.costStreamDbPath);
31163
+ sessionCount = db.getSessionIds().length;
31164
+ db.close();
31165
+ } catch (err) {
31166
+ console.error("[status] cost-stream-db import/query failed:", err);
31167
+ }
31168
+ res.json({
31169
+ costCapture: {
31170
+ installed: statuslineActive,
31171
+ dbExists,
31172
+ dbPath: config.costStreamDbPath,
31173
+ sessionCount
31174
+ },
31175
+ costMethod: config.costMethod
31176
+ });
31177
+ });
31178
+ app.put("/api/settings", (req, res) => {
31179
+ const costMethod = req.body?.costMethod;
31180
+ if (![
31181
+ "api",
31182
+ "estimated",
31183
+ "auto"
31184
+ ].includes(costMethod)) {
31185
+ res.status(400).json({
31186
+ error: "invalid_settings",
31187
+ message: "costMethod must be api, estimated, or auto"
31188
+ });
31189
+ return;
31190
+ }
31191
+ let existingConfig = {};
31192
+ try {
31193
+ if ((0, node_fs.existsSync)(CONFIG_PATH)) existingConfig = JSON.parse((0, node_fs.readFileSync)(CONFIG_PATH, "utf-8"));
31194
+ } catch {}
31195
+ existingConfig.costMethod = costMethod;
31196
+ try {
31197
+ if (!(0, node_fs.existsSync)(TIMELINE_DIR)) (0, node_fs.mkdirSync)(TIMELINE_DIR, { recursive: true });
31198
+ (0, node_fs.writeFileSync)(CONFIG_PATH, JSON.stringify(existingConfig, null, 2) + "\n");
31199
+ } catch {
31200
+ res.status(500).json({
31201
+ error: "write_failed",
31202
+ message: "Failed to save settings"
31203
+ });
31204
+ return;
31205
+ }
31206
+ res.json({ costMethod });
31207
+ });
31142
31208
  app.get("/api/sessions", async (req, res) => {
31143
31209
  try {
31144
31210
  const limit = Number(req.query.limit) || 20;
@@ -31235,3 +31301,4 @@ main().catch((err) => {
31235
31301
  process.exit(1);
31236
31302
  });
31237
31303
  //#endregion
31304
+ exports.__toESM = __toESM;