tokentracker-cli 0.5.92 → 0.5.94
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/README.md +7 -6
- package/dashboard/dist/assets/{main-CHSJgtKj.js → main-BvlEZHQ6.js} +179 -178
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +3 -1
- package/src/commands/init.js +41 -2
- package/src/commands/serve.js +11 -0
- package/src/commands/status.js +31 -2
- package/src/commands/sync.js +28 -0
- package/src/commands/uninstall.js +18 -1
- package/src/lib/claude-config.js +16 -2
- package/src/lib/local-api.js +11 -116
- package/src/lib/pricing/curated-overrides.json +33 -0
- package/src/lib/pricing/index.js +135 -0
- package/src/lib/pricing/litellm-fetcher.js +172 -0
- package/src/lib/pricing/matcher.js +149 -0
- package/src/lib/pricing/seed-snapshot.json +1 -0
- package/src/lib/rollout.js +284 -0
- package/src/lib/tracker-paths.js +1 -0
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
]
|
|
136
136
|
}
|
|
137
137
|
</script>
|
|
138
|
-
<script type="module" crossorigin src="/assets/main-
|
|
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-
|
|
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.
|
|
3
|
+
"version": "0.5.94",
|
|
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",
|
package/src/commands/init.js
CHANGED
|
@@ -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 {
|
package/src/commands/serve.js
CHANGED
|
@@ -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
|
[
|
package/src/commands/status.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
"",
|
package/src/commands/sync.js
CHANGED
|
@@ -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}`
|
package/src/lib/claude-config.js
CHANGED
|
@@ -71,9 +71,17 @@ async function isClaudeHookConfigured({ settingsPath, hookCommand, event = DEFAU
|
|
|
71
71
|
return hasHook(entries, hookCommand);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
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
|
-
|
|
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
|
};
|
package/src/lib/local-api.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
}
|