tokentracker-cli 0.5.92 → 0.5.93

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.
@@ -135,7 +135,7 @@
135
135
  ]
136
136
  }
137
137
  </script>
138
- <script type="module" crossorigin src="/assets/main-CHSJgtKj.js"></script>
138
+ <script type="module" crossorigin src="/assets/main-BvlEZHQ6.js"></script>
139
139
  <link rel="stylesheet" crossorigin href="/assets/main-DI4xYfKR.css">
140
140
  </head>
141
141
  <body>
@@ -51,7 +51,7 @@
51
51
  "description": "Shareable Token Tracker dashboard snapshot."
52
52
  }
53
53
  </script>
54
- <script type="module" crossorigin src="/assets/main-CHSJgtKj.js"></script>
54
+ <script type="module" crossorigin src="/assets/main-BvlEZHQ6.js"></script>
55
55
  <link rel="stylesheet" crossorigin href="/assets/main-DI4xYfKR.css">
56
56
  </head>
57
57
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokentracker-cli",
3
- "version": "0.5.92",
3
+ "version": "0.5.93",
4
4
  "description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Kiro, Gemini, OpenCode, OpenClaw, Hermes, GitHub Copilot)",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -31,6 +31,8 @@
31
31
  "dev:shim": "node scripts/dev-bin-shim.cjs",
32
32
  "graph:auto-index": "node scripts/graph/auto-index.cjs",
33
33
  "graph:scip": "node scripts/graph/generate-scip.cjs",
34
+ "pricing:build-seed": "node scripts/build-pricing-seed.cjs",
35
+ "prepublishOnly": "node scripts/build-pricing-seed.cjs",
34
36
  "test": "node --test test/*.test.js",
35
37
  "validate:copy": "node scripts/validate-copy-registry.cjs",
36
38
  "validate:guardrails": "node scripts/validate-architecture-guardrails.cjs",
@@ -22,6 +22,7 @@ const {
22
22
  const {
23
23
  upsertClaudeHook,
24
24
  buildClaudeHookCommand,
25
+ buildHookCommand,
25
26
  isClaudeHookConfigured,
26
27
  } = require("../lib/claude-config");
27
28
  const {
@@ -303,6 +304,12 @@ function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
303
304
  const claudeDir = path.join(home, ".claude");
304
305
  const claudeSettingsPath = path.join(claudeDir, "settings.json");
305
306
  const claudeHookCommand = buildClaudeHookCommand(notifyPath);
307
+ // CodeBuddy CLI (Tencent) is a Claude-Code fork — same settings.json hook
308
+ // schema, same SessionEnd event. We install the same hook with a different
309
+ // --source token so notify.cjs / sync know which provider triggered.
310
+ const codebuddyDir = process.env.CODEBUDDY_HOME || path.join(home, ".codebuddy");
311
+ const codebuddySettingsPath = path.join(codebuddyDir, "settings.json");
312
+ const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
306
313
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
307
314
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
308
315
  const geminiHookCommand = buildGeminiHookCommand(notifyPath);
@@ -319,6 +326,9 @@ function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
319
326
  claudeDir,
320
327
  claudeSettingsPath,
321
328
  claudeHookCommand,
329
+ codebuddyDir,
330
+ codebuddySettingsPath,
331
+ codebuddyHookCommand,
322
332
  geminiConfigDir,
323
333
  geminiSettingsPath,
324
334
  geminiHookCommand,
@@ -415,6 +425,20 @@ async function applyIntegrationSetup({ home, trackerDir, notifyPath, notifyOrigi
415
425
  }
416
426
  }
417
427
 
428
+ // CodeBuddy: Claude-Code fork. Install the SessionEnd hook so finished
429
+ // sessions trigger notify.cjs → tracker sync; passive scan still runs as a
430
+ // safety net for sessions that don't fire SessionEnd cleanly.
431
+ const codebuddyDirExists = await isDir(context.codebuddyDir);
432
+ if (codebuddyDirExists) {
433
+ await upsertClaudeHook({
434
+ settingsPath: context.codebuddySettingsPath,
435
+ hookCommand: context.codebuddyHookCommand,
436
+ });
437
+ summary.push({ label: "CodeBuddy", status: "installed", detail: "Hooks installed" });
438
+ } else {
439
+ summary.push({ label: "CodeBuddy", status: "skipped", detail: "Config not found" });
440
+ }
441
+
418
442
  const openclawBefore = await probeOpenclawSessionPluginState({
419
443
  home,
420
444
  trackerDir,
@@ -523,6 +547,21 @@ async function previewIntegrations({ context }) {
523
547
  summary.push({ label: "Claude", status: "skipped", detail: "Config not found" });
524
548
  }
525
549
 
550
+ const codebuddyDirExists = await isDir(context.codebuddyDir);
551
+ if (codebuddyDirExists) {
552
+ const configured = await isClaudeHookConfigured({
553
+ settingsPath: context.codebuddySettingsPath,
554
+ hookCommand: context.codebuddyHookCommand,
555
+ });
556
+ summary.push({
557
+ label: "CodeBuddy",
558
+ status: "installed",
559
+ detail: configured ? "Hooks already installed" : "Will install hooks",
560
+ });
561
+ } else {
562
+ summary.push({ label: "CodeBuddy", status: "skipped", detail: "Config not found" });
563
+ }
564
+
526
565
  const geminiConfigExists = await isDir(context.geminiConfigDir);
527
566
  if (geminiConfigExists) {
528
567
  const configured = await isGeminiHookConfigured({
@@ -771,7 +810,7 @@ try {
771
810
  const originalPath =
772
811
  source === 'every-code'
773
812
  ? codeOriginalPath
774
- : source === 'claude' || source === 'opencode' || source === 'gemini'
813
+ : source === 'claude' || source === 'opencode' || source === 'gemini' || source === 'codebuddy'
775
814
  ? null
776
815
  : codexOriginalPath;
777
816
  if (originalPath) {
@@ -816,7 +855,7 @@ function isSelfNotify(cmd) {
816
855
  `;
817
856
  }
818
857
 
819
- module.exports = { cmdInit };
858
+ module.exports = { cmdInit, buildNotifyHandler };
820
859
 
821
860
  async function probeFile(p) {
822
861
  try {
@@ -6,6 +6,7 @@ const cp = require("node:child_process");
6
6
 
7
7
  const { resolveTrackerPaths } = require("../lib/tracker-paths");
8
8
  const { createLocalApiHandler, resolveQueuePath } = require("../lib/local-api");
9
+ const { ensurePricingLoaded } = require("../lib/pricing");
9
10
  const { serveStaticFile } = require("../lib/static-server");
10
11
  const { openInBrowser } = require("../lib/browser-auth");
11
12
 
@@ -64,6 +65,16 @@ async function cmdServe(argv) {
64
65
  const queuePath = resolveQueuePath();
65
66
  const dashboardDir = resolveDashboardDir();
66
67
 
68
+ // 2.1 Refresh LiteLLM pricing data in the background. The seed snapshot is
69
+ // already loaded synchronously at require-time, so cost calculation is
70
+ // functional right now; ensurePricingLoaded() only upgrades to fresh
71
+ // disk cache or upstream data. Awaiting it here would block startup
72
+ // for the full 10s fetch timeout when offline / behind a firewall.
73
+ const { cacheDir } = await resolveTrackerPaths();
74
+ ensurePricingLoaded({ cachePath: path.join(cacheDir, "pricing.json") }).catch(
75
+ (e) => process.stdout.write(`Pricing refresh warning: ${e?.message || e}\n`),
76
+ );
77
+
67
78
  if (!dashboardDir) {
68
79
  process.stderr.write(
69
80
  [
@@ -5,7 +5,11 @@ const fssync = require("node:fs");
5
5
 
6
6
  const { readJson } = require("../lib/fs");
7
7
  const { readCodexNotify, readEveryCodeNotify } = require("../lib/codex-config");
8
- const { isClaudeHookConfigured, buildClaudeHookCommand } = require("../lib/claude-config");
8
+ const {
9
+ isClaudeHookConfigured,
10
+ buildClaudeHookCommand,
11
+ buildHookCommand,
12
+ } = require("../lib/claude-config");
9
13
  const {
10
14
  resolveGeminiConfigDir,
11
15
  resolveGeminiSettingsPath,
@@ -20,7 +24,12 @@ const { collectTrackerDiagnostics } = require("../lib/diagnostics");
20
24
  const { probeOpenclawHookState } = require("../lib/openclaw-hook");
21
25
  const { probeOpenclawSessionPluginState } = require("../lib/openclaw-session-plugin");
22
26
  const { resolveTrackerPaths } = require("../lib/tracker-paths");
23
- const { resolveKimiWireFiles, resolveKiroCliDbPath } = require("../lib/rollout");
27
+ const {
28
+ resolveKimiWireFiles,
29
+ resolveKiroCliDbPath,
30
+ resolveCodebuddyHome,
31
+ resolveCodebuddyProjectFiles,
32
+ } = require("../lib/rollout");
24
33
 
25
34
  async function cmdStatus(argv = []) {
26
35
  const opts = parseArgs(argv);
@@ -46,11 +55,16 @@ async function cmdStatus(argv = []) {
46
55
  const codeHome = process.env.CODE_HOME || path.join(home, ".code");
47
56
  const codeConfigPath = path.join(codeHome, "config.toml");
48
57
  const claudeSettingsPath = path.join(home, ".claude", "settings.json");
58
+ const codebuddySettingsPath = path.join(
59
+ process.env.CODEBUDDY_HOME || path.join(home, ".codebuddy"),
60
+ "settings.json",
61
+ );
49
62
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
50
63
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
51
64
  const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
52
65
  const notifyPath = path.join(binDir, "notify.cjs");
53
66
  const claudeHookCommand = buildClaudeHookCommand(notifyPath);
67
+ const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
54
68
  const geminiHookCommand = buildGeminiHookCommand(notifyPath);
55
69
 
56
70
  const config = await readJson(configPath);
@@ -74,6 +88,10 @@ async function cmdStatus(argv = []) {
74
88
  settingsPath: claudeSettingsPath,
75
89
  hookCommand: claudeHookCommand,
76
90
  });
91
+ const codebuddyHookConfigured = await isClaudeHookConfigured({
92
+ settingsPath: codebuddySettingsPath,
93
+ hookCommand: codebuddyHookCommand,
94
+ });
77
95
  const geminiHookConfigured = await isGeminiHookConfigured({
78
96
  settingsPath: geminiSettingsPath,
79
97
  hookCommand: geminiHookCommand,
@@ -125,6 +143,14 @@ async function cmdStatus(argv = []) {
125
143
  const kiroCliDbPath = resolveKiroCliDbPath(process.env);
126
144
  const kiroCliInstalled = fssync.existsSync(kiroCliDbPath);
127
145
 
146
+ // CodeBuddy — passive scan only (no hooks). Surface the file count so
147
+ // operators can confirm jsonl logs are being discovered.
148
+ const codebuddyHome = resolveCodebuddyHome(process.env);
149
+ const codebuddyInstalled = fssync.existsSync(codebuddyHome);
150
+ const codebuddyFiles = codebuddyInstalled
151
+ ? resolveCodebuddyProjectFiles(process.env)
152
+ : [];
153
+
128
154
  const copilotToken = readCopilotOauthToken({ home });
129
155
  const copilotOtel = describeCopilotOtelStatus({ home, env: process.env });
130
156
  const copilotLines = formatCopilotLines({ token: copilotToken, otel: copilotOtel });
@@ -157,6 +183,9 @@ async function cmdStatus(argv = []) {
157
183
  kiroCliInstalled
158
184
  ? `- Kiro CLI: SQLite data.sqlite3 found (tokens approximated from char lengths, merged under 'kiro' source)`
159
185
  : null,
186
+ codebuddyInstalled
187
+ ? `- CodeBuddy hooks: ${codebuddyHookConfigured ? "set" : "unset"} (${codebuddyFiles.length} session jsonl file${codebuddyFiles.length !== 1 ? "s" : ""} found)`
188
+ : null,
160
189
  ...copilotLines,
161
190
  ...subscriptionLines,
162
191
  "",
@@ -27,6 +27,8 @@ const {
27
27
  parseCopilotIncremental,
28
28
  resolveKimiWireFiles,
29
29
  parseKimiIncremental,
30
+ resolveCodebuddyProjectFiles,
31
+ parseCodebuddyIncremental,
30
32
  resolveKiroCliSessionFiles,
31
33
  resolveKiroCliDbPath,
32
34
  parseKiroCliIncremental,
@@ -418,6 +420,30 @@ async function cmdSync(argv) {
418
420
  });
419
421
  }
420
422
 
423
+ // ── CodeBuddy CLI (passive ~/.codebuddy/projects/**/*.jsonl reader) ──
424
+ // Tencent's CodeBuddy CLI is a Claude Code clone; no hook system, so we
425
+ // tail the per-session JSONL conversation logs incrementally on each sync.
426
+ let codebuddyResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
427
+ const codebuddyFiles = resolveCodebuddyProjectFiles(process.env);
428
+ if (codebuddyFiles.length > 0) {
429
+ if (progress?.enabled) {
430
+ progress.start(`Parsing CodeBuddy ${renderBar(0)} | buckets 0`);
431
+ }
432
+ codebuddyResult = await parseCodebuddyIncremental({
433
+ projectFiles: codebuddyFiles,
434
+ cursors,
435
+ queuePath,
436
+ env: process.env,
437
+ onProgress: (p) => {
438
+ if (!progress?.enabled) return;
439
+ const pct = p.total > 0 ? p.index / p.total : 1;
440
+ progress.update(
441
+ `Parsing CodeBuddy ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
442
+ );
443
+ },
444
+ });
445
+ }
446
+
421
447
  // ── GitHub Copilot CLI (OTEL JSONL files) ──
422
448
  let copilotResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
423
449
  const copilotPaths = resolveCopilotOtelPaths(process.env);
@@ -536,6 +562,7 @@ async function cmdSync(argv) {
536
562
  kiroCliResult.recordsProcessed +
537
563
  hermesResult.recordsProcessed +
538
564
  kimiResult.recordsProcessed +
565
+ codebuddyResult.recordsProcessed +
539
566
  copilotResult.recordsProcessed;
540
567
  const totalBuckets =
541
568
  parseResult.bucketsQueued +
@@ -548,6 +575,7 @@ async function cmdSync(argv) {
548
575
  kiroCliResult.bucketsQueued +
549
576
  hermesResult.bucketsQueued +
550
577
  kimiResult.bucketsQueued +
578
+ codebuddyResult.bucketsQueued +
551
579
  copilotResult.bucketsQueued;
552
580
  process.stdout.write(
553
581
  [
@@ -3,7 +3,7 @@ const path = require("node:path");
3
3
  const fs = require("node:fs/promises");
4
4
 
5
5
  const { restoreCodexNotify, restoreEveryCodeNotify } = require("../lib/codex-config");
6
- const { removeClaudeHook, buildClaudeHookCommand } = require("../lib/claude-config");
6
+ const { removeClaudeHook, buildClaudeHookCommand, buildHookCommand } = require("../lib/claude-config");
7
7
  const {
8
8
  resolveGeminiConfigDir,
9
9
  resolveGeminiSettingsPath,
@@ -24,6 +24,8 @@ async function cmdUninstall(argv) {
24
24
  const codeHome = process.env.CODE_HOME || path.join(home, ".code");
25
25
  const codeConfigPath = path.join(codeHome, "config.toml");
26
26
  const claudeSettingsPath = path.join(home, ".claude", "settings.json");
27
+ const codebuddyDir = process.env.CODEBUDDY_HOME || path.join(home, ".codebuddy");
28
+ const codebuddySettingsPath = path.join(codebuddyDir, "settings.json");
27
29
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
28
30
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
29
31
  const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
@@ -33,11 +35,13 @@ async function cmdUninstall(argv) {
33
35
  const codexNotifyCmd = ["/usr/bin/env", "node", notifyPath];
34
36
  const codeNotifyCmd = ["/usr/bin/env", "node", notifyPath, "--source=every-code"];
35
37
  const claudeHookCommand = buildClaudeHookCommand(notifyPath);
38
+ const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
36
39
  const geminiHookCommand = buildGeminiHookCommand(notifyPath);
37
40
 
38
41
  const codexConfigExists = await isFile(codexConfigPath);
39
42
  const codeConfigExists = await isFile(codeConfigPath);
40
43
  const claudeConfigExists = await isFile(claudeSettingsPath);
44
+ const codebuddyConfigExists = await isFile(codebuddySettingsPath);
41
45
  const geminiConfigExists = await isDir(geminiConfigDir);
42
46
  const opencodeConfigExists = await isDir(opencodeConfigDir);
43
47
  const codexRestore = codexConfigExists
@@ -57,6 +61,12 @@ async function cmdUninstall(argv) {
57
61
  const claudeRemove = claudeConfigExists
58
62
  ? await removeClaudeHook({ settingsPath: claudeSettingsPath, hookCommand: claudeHookCommand })
59
63
  : { removed: false, skippedReason: "config-missing" };
64
+ const codebuddyRemove = codebuddyConfigExists
65
+ ? await removeClaudeHook({
66
+ settingsPath: codebuddySettingsPath,
67
+ hookCommand: codebuddyHookCommand,
68
+ })
69
+ : { removed: false, skippedReason: "config-missing" };
60
70
  const geminiRemove = geminiConfigExists
61
71
  ? await removeGeminiHook({ settingsPath: geminiSettingsPath, hookCommand: geminiHookCommand })
62
72
  : { removed: false, skippedReason: "config-missing" };
@@ -104,6 +114,13 @@ async function cmdUninstall(argv) {
104
114
  ? "- Claude hooks: no change"
105
115
  : "- Claude hooks: skipped"
106
116
  : "- Claude hooks: skipped (settings.json not found)",
117
+ codebuddyConfigExists
118
+ ? codebuddyRemove?.removed
119
+ ? `- CodeBuddy hooks removed: ${codebuddySettingsPath}`
120
+ : codebuddyRemove?.skippedReason === "hook-missing"
121
+ ? "- CodeBuddy hooks: no change"
122
+ : "- CodeBuddy hooks: skipped"
123
+ : "- CodeBuddy hooks: skipped (settings.json not found)",
107
124
  geminiConfigExists
108
125
  ? geminiRemove?.removed
109
126
  ? `- Gemini hooks removed: ${geminiSettingsPath}`
@@ -71,9 +71,17 @@ async function isClaudeHookConfigured({ settingsPath, hookCommand, event = DEFAU
71
71
  return hasHook(entries, hookCommand);
72
72
  }
73
73
 
74
- function buildClaudeHookCommand(notifyPath) {
74
+ // Generic Session-hook command builder. CodeBuddy CLI is a Claude-Code fork
75
+ // and uses the exact same settings.json hook schema, so this function works
76
+ // for any source that accepts the `node notify.cjs --source=<name>` contract.
77
+ function buildHookCommand(notifyPath, source) {
75
78
  const cmd = typeof notifyPath === "string" ? notifyPath : "";
76
- return `/usr/bin/env node ${quoteArg(cmd)} --source=claude`;
79
+ const src = typeof source === "string" && source ? source : "claude";
80
+ return `/usr/bin/env node ${quoteArg(cmd)} --source=${src}`;
81
+ }
82
+
83
+ function buildClaudeHookCommand(notifyPath) {
84
+ return buildHookCommand(notifyPath, "claude");
77
85
  }
78
86
 
79
87
  function normalizeSettings(raw) {
@@ -187,4 +195,10 @@ module.exports = {
187
195
  removeClaudeHook,
188
196
  isClaudeHookConfigured,
189
197
  buildClaudeHookCommand,
198
+ buildHookCommand,
199
+ // Aliases for callers that want a name unbiased toward Claude (the schema
200
+ // applies equally to CodeBuddy and any future Claude-Code fork).
201
+ upsertSessionHook: upsertClaudeHook,
202
+ removeSessionHook: removeClaudeHook,
203
+ isSessionHookConfigured: isClaudeHookConfigured,
190
204
  };
@@ -9,124 +9,18 @@ const SYNC_TIMEOUT_MS = 120_000;
9
9
  const TRACKER_BIN = path.resolve(__dirname, "../../bin/tracker.js");
10
10
 
11
11
  // ---------------------------------------------------------------------------
12
- // Per-model pricing (USD per million tokens)
12
+ // Per-model pricing delegated to src/lib/pricing/
13
+ // - CURATED overrides (kiro-*, hy3-*, composer-*, kimi-for-coding, etc.)
14
+ // - LiteLLM live data (mainstream claude / gpt-5 / gemini), 24h disk-cached
15
+ // - Bundled seed snapshot for first-install / offline fallback
13
16
  // ---------------------------------------------------------------------------
14
17
 
15
- // Rates per million tokens (USD). Sources: LiteLLM, OpenAI, Google, OpenRouter.
16
- const MODEL_PRICING = {
17
- // ── Anthropic Claude ──
18
- "claude-opus-4-6": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
19
- "claude-opus-4-5-20250414": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
20
- "claude-sonnet-4-6": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
21
- "claude-sonnet-4-5-20250514": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
22
- "claude-sonnet-4-20250514": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
23
- "claude-haiku-4-5-20251001": { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 },
24
- "claude-3-5-sonnet-20241022": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
25
- "claude-3-5-haiku-20241022": { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 },
26
- // ── OpenAI GPT / Codex ──
27
- "gpt-5": { input: 1.25, output: 10, cache_read: 0.125 },
28
- "gpt-5-fast": { input: 1.25, output: 10, cache_read: 0.125 },
29
- "gpt-5-high": { input: 1.25, output: 10, cache_read: 0.125 },
30
- "gpt-5-high-fast": { input: 1.25, output: 10, cache_read: 0.125 },
31
- "gpt-5-codex": { input: 1.25, output: 10, cache_read: 0.125 },
32
- "gpt-5-codex-high-fast": { input: 1.25, output: 10, cache_read: 0.125 },
33
- "gpt-5.1-codex": { input: 1.25, output: 10, cache_read: 0.125 },
34
- "gpt-5.1-codex-mini": { input: 0.25, output: 2, cache_read: 0.025 },
35
- "gpt-5.1-codex-max": { input: 1.25, output: 10, cache_read: 0.125 },
36
- "gpt-5.1-codex-max-high-fast": { input: 1.25, output: 10, cache_read: 0.125 },
37
- "gpt-5.1-codex-max-xhigh-fast": { input: 1.25, output: 10, cache_read: 0.125 },
38
- "gpt-5.1-codex-high": { input: 1.25, output: 10, cache_read: 0.125 },
39
- "gpt-5.1-codex-max-high": { input: 1.25, output: 10, cache_read: 0.125 },
40
- "gpt-5.2": { input: 1.75, output: 14, cache_read: 0.175 },
41
- "gpt-5.2-high": { input: 1.75, output: 14, cache_read: 0.175 },
42
- "gpt-5.2-high-fast": { input: 1.75, output: 14, cache_read: 0.175 },
43
- "gpt-5.2-codex": { input: 1.75, output: 14, cache_read: 0.175 },
44
- "gpt-5.2-codex-high": { input: 1.75, output: 14, cache_read: 0.175 },
45
- "gpt-5.3-codex": { input: 1.75, output: 14, cache_read: 0.175 },
46
- "gpt-5.3-codex-high": { input: 1.75, output: 14, cache_read: 0.175 },
47
- "gpt-5.4": { input: 2.5, output: 15, cache_read: 0.25 },
48
- "gpt-5.4-mini": { input: 0.75, output: 4.5, cache_read: 0.075 },
49
- "gpt-5.4-medium": { input: 1.5, output: 10, cache_read: 0.15 },
50
- "o3": { input: 2, output: 8, cache_read: 0.5 },
51
- // ── Google Gemini (official: ai.google.dev/pricing) ──
52
- "gemini-2.5-pro": { input: 1.25, output: 10, cache_read: 0.125 },
53
- "gemini-2.5-pro-preview-06-05": { input: 1.25, output: 10, cache_read: 0.125 },
54
- "gemini-2.5-pro-preview-05-06": { input: 1.25, output: 10, cache_read: 0.125 },
55
- "gemini-2.5-flash": { input: 0.3, output: 2.5, cache_read: 0.03 },
56
- "gemini-3-flash-preview": { input: 0.5, output: 3, cache_read: 0.05 },
57
- "gemini-3-pro-preview": { input: 2, output: 12, cache_read: 0.2 },
58
- "gemini-3.1-pro-preview": { input: 2, output: 12, cache_read: 0.2 },
59
- // ── Cursor Composer ──
60
- "composer-1": { input: 1.25, output: 10, cache_read: 0.125 },
61
- "composer-1.5": { input: 3.5, output: 17.5, cache_read: 0.35 },
62
- "composer-2": { input: 0.5, output: 2.5, cache_read: 0.2 },
63
- "composer-2-fast": { input: 1.5, output: 7.5, cache_read: 0.15 },
64
- // ── Moonshot Kimi (official: platform.moonshot.ai) ──
65
- "kimi-for-coding": { input: 0.6, output: 2, cache_read: 0.15 },
66
- "kimi-k2.5": { input: 0.6, output: 2, cache_read: 0.15 },
67
- "kimi-k2.5-free": { input: 0, output: 0, cache_read: 0 },
68
- // ── AWS Kiro (Kiro IDE + Kiro CLI — both route through Bedrock, most
69
- // commonly claude-sonnet-4; rates mirror the sonnet-4 table below so
70
- // costs stay consistent with the real underlying model when a Bedrock
71
- // model_id isn't exposed). Mirrored byte-for-byte in
72
- // dashboard/edge-patches/tokentracker-leaderboard-refresh.ts for
73
- // leaderboard estimated_cost_usd. ──
74
- "kiro-agent": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
75
- "kiro-cli-agent": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
76
- // ── Misc / Free ──
77
- "glm-4.7-free": { input: 0, output: 0, cache_read: 0 },
78
- "nemotron-3-super-free": { input: 0, output: 0, cache_read: 0 },
79
- "mimo-v2-pro-free": { input: 0, output: 0, cache_read: 0 },
80
- "minimax-m2.1-free": { input: 0, output: 0, cache_read: 0 },
81
- "MiniMax-M2.1": { input: 0.5, output: 3, cache_read: 0.05 },
82
- };
83
-
84
- const ZERO_PRICING = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
85
-
86
- function getModelPricing(model) {
87
- if (!model) return ZERO_PRICING;
88
- const exact = MODEL_PRICING[model];
89
- if (exact) return exact;
90
- // Fuzzy match for common model families
91
- const lower = model.toLowerCase();
92
- if (lower.includes("opus")) return MODEL_PRICING["claude-opus-4-6"];
93
- if (lower.includes("haiku")) return MODEL_PRICING["claude-haiku-4-5-20251001"];
94
- if (lower.includes("sonnet")) return MODEL_PRICING["claude-sonnet-4-6"];
95
- if (lower.includes("gpt-5.4")) return MODEL_PRICING["gpt-5.4"];
96
- if (lower.includes("gpt-5.3")) return MODEL_PRICING["gpt-5.3-codex"];
97
- if (lower.includes("gpt-5.2")) return MODEL_PRICING["gpt-5.2"];
98
- if (lower.includes("gpt-5.1")) return MODEL_PRICING["gpt-5.1-codex"];
99
- if (lower.includes("gpt-5")) return MODEL_PRICING["gpt-5"];
100
- if (lower.includes("gemini-3")) return MODEL_PRICING["gemini-3-flash-preview"];
101
- if (lower.includes("gemini-2.5")) return MODEL_PRICING["gemini-2.5-pro"];
102
- if (lower.includes("kimi")) return MODEL_PRICING["kimi-k2.5"];
103
- if (lower.includes("kiro")) return MODEL_PRICING["kiro-cli-agent"];
104
- if (lower.includes("composer")) return MODEL_PRICING["composer-1"];
105
- if (lower === "auto") return MODEL_PRICING["composer-1"];
106
- return ZERO_PRICING;
107
- }
108
-
109
- function computeRowCost(row) {
110
- const pricing = getModelPricing(row.model);
111
- // For OpenAI/Codex-family rollouts, `output_tokens` already includes any
112
- // reasoning tokens (the OpenAI API's `completion_tokens` is inclusive),
113
- // so adding a separate `reasoning_output_tokens * output_rate` term
114
- // double-charges that slice. ccusage models this the same way. For other
115
- // sources we keep the explicit reasoning term because `reasoning` is not
116
- // guaranteed to be folded into `output_tokens`.
117
- const reasoningIncludedInOutput = row.source === "codex" || row.source === "every-code";
118
- const reasoningCost = reasoningIncludedInOutput
119
- ? 0
120
- : (row.reasoning_output_tokens || 0) * (pricing.output || 0);
121
- return (
122
- ((row.input_tokens || 0) * (pricing.input || 0) +
123
- (row.output_tokens || 0) * (pricing.output || 0) +
124
- (row.cached_input_tokens || 0) * (pricing.cache_read || 0) +
125
- (row.cache_creation_input_tokens || 0) * (pricing.cache_write || 0) +
126
- reasoningCost) /
127
- 1_000_000
128
- );
129
- }
18
+ const {
19
+ MODEL_PRICING,
20
+ getModelPricing,
21
+ computeRowCost,
22
+ ensurePricingLoaded,
23
+ } = require("./pricing");
130
24
 
131
25
  // ---------------------------------------------------------------------------
132
26
  // Queue data helpers
@@ -1316,4 +1210,5 @@ module.exports = {
1316
1210
  MODEL_PRICING,
1317
1211
  getModelPricing,
1318
1212
  computeRowCost,
1213
+ ensurePricingLoaded,
1319
1214
  };
@@ -0,0 +1,33 @@
1
+ {
2
+ "_meta": {
3
+ "note": "Curated price overrides. Always wins over LiteLLM. Two reasons to live here: (1) self-defined alias names that LiteLLM will never carry (kiro-*, hy3-*, composer-*, kimi-for-coding, free-tier OpenRouter routes); (2) prices we want to pin even if LiteLLM has the model (e.g. cache_write fields LiteLLM often omits). Units: USD per million tokens. Edit this file to override pricing without redeploying.",
4
+ "units": "usd_per_million_tokens"
5
+ },
6
+ "exact": {
7
+ "kiro-agent": { "input": 3, "output": 15, "cache_read": 0.3, "cache_write": 3.75 },
8
+ "kiro-cli-agent": { "input": 3, "output": 15, "cache_read": 0.3, "cache_write": 3.75 },
9
+ "hy3-preview-agent":{ "input": 0, "output": 0, "cache_read": 0 },
10
+ "hy3-preview": { "input": 0, "output": 0, "cache_read": 0 },
11
+ "composer-1": { "input": 1.25, "output": 10, "cache_read": 0.125 },
12
+ "composer-1.5": { "input": 3.5, "output": 17.5, "cache_read": 0.35 },
13
+ "composer-2": { "input": 0.5, "output": 2.5, "cache_read": 0.2 },
14
+ "composer-2-fast": { "input": 1.5, "output": 7.5, "cache_read": 0.15 },
15
+ "kimi-for-coding": { "input": 0.6, "output": 2, "cache_read": 0.15 },
16
+ "kimi-k2.5": { "input": 0.6, "output": 2, "cache_read": 0.15 },
17
+ "kimi-k2.5-free": { "input": 0, "output": 0, "cache_read": 0 },
18
+ "glm-4.7-free": { "input": 0, "output": 0, "cache_read": 0 },
19
+ "nemotron-3-super-free":{ "input": 0, "output": 0, "cache_read": 0 },
20
+ "mimo-v2-pro-free": { "input": 0, "output": 0, "cache_read": 0 },
21
+ "minimax-m2.1-free":{ "input": 0, "output": 0, "cache_read": 0 },
22
+ "MiniMax-M2.1": { "input": 0.5, "output": 3, "cache_read": 0.05 }
23
+ },
24
+ "alias": {
25
+ "auto": "composer-1"
26
+ },
27
+ "fuzzy": [
28
+ { "match": "kiro", "ref": "kiro-cli-agent" },
29
+ { "match": "hy3", "ref": "hy3-preview-agent" },
30
+ { "match": "composer", "ref": "composer-1" },
31
+ { "match": "kimi", "ref": "kimi-k2.5" }
32
+ ]
33
+ }