@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.
- package/dist/cli.js +1025 -46
- package/dist/db-reader.d.ts.map +1 -1
- package/dist/{pricing-DTmya3JY.mjs → pricing-5MZ5_SQc.mjs} +1 -1
- package/dist/{pricing-DTmya3JY.mjs.map → pricing-5MZ5_SQc.mjs.map} +1 -1
- package/dist/pricing-B9Z0E171.mjs +2 -0
- package/dist/server.cjs +69 -2
- package/dist/web/assets/{index-Dr0FGYfS.js → index-BbY4gr3z.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/db-reader-BrPRGqww.mjs +0 -1028
- package/dist/db-reader-BrPRGqww.mjs.map +0 -1
- package/dist/db-reader-CPXmkt55.mjs +0 -2
- package/dist/pricing-B-rwfwDB.mjs +0 -2
package/dist/db-reader.d.ts.map
CHANGED
|
@@ -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,
|
|
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-
|
|
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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"}
|
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
|
|
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
|
|
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;
|