tokentracker-cli 1.3.4 → 1.3.6
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.de.md +5 -5
- package/README.ja.md +3 -3
- package/README.ko.md +3 -3
- package/README.md +6 -5
- package/README.zh-CN.md +3 -3
- package/dashboard/dist/assets/{AchievementBadge-CKjtQp-U.js → AchievementBadge-CHDt55IP.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DpOXrabE.js → AchievementsPage-DpbRHmb8.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DiXyIoa0.js → AchievementsSection-Ck5CiX4P.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-CILTTkPF.js → BadgeDetailModal-BVPqlh0X.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-CrBHWiRi.js → BotAnimated-CVsv6kxT.js} +1 -1
- package/dashboard/dist/assets/{Card-DUItW3xI.js → Card-C9FjyAwh.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-C_t_PDlG.js → CloudCheckoutPage-ChkjId9l.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-pvJUrhya.js → CloudPage-D1nrt3MG.js} +1 -1
- package/dashboard/dist/assets/{CloudUsageExport-DQbDwiyk.js → CloudUsageExport-jXdoiD0e.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-BvHilrbV.js → CommandPalette-DFnEeZzc.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-CizxEYv-.js → CommunityStatsModal-CZDS8gPA.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-h9nISVy7.js → ConfirmModal-DRhgBoGE.js} +1 -1
- package/dashboard/dist/assets/{Controls-BjdIonTX.js → Controls-BmT6-x_L.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-Bxryr4qp.js → DashboardPage-DfwwXZBo.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-DpKh-0l8.js → DateRangePopover-DuqNLatr.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-DUJR9hJC.js → DevicePage-BtI0bWyg.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BmVAmN-o.js → DialogClose-DjnC3P4V.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-yy_QHg7O.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-BsaIbN8k.js → DialogTitle-B9NNSz5p.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-BpSt2P_W.js → FadeIn-fsVQmJGa.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-Cc_VHB0R.js → HeaderGithubStar-Cg-f1iZc.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-WFd21iP9.js → HoverTooltip-DRIRlzXH.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-C_mUPSRB.js → IpCheckPage-DaAubaJQ.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-DVR8ClZO.js → LandingPage-DjBGIjHk.js} +2 -2
- package/dashboard/dist/assets/LeaderboardPage-BkCXqhz7.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProBadge-BjX4EFl2.js → LeaderboardProBadge-Ds-wiWcN.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-vbPMNn7W.js → LeaderboardProfileModal-X_rfSQT5.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-CqClmuA-.js → LeaderboardProfilePage-rpl1Ozwj.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-BUvDBmgD.js +9 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-B6nDke_u.js → LocalOnlyNotice-LBH1DBrs.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-C28QVjd7.js → LoginCard-B0vRJEcg.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-Bx1btNhH.js → LoginModal-DdzgP43f.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DVOGNGYI.js → LoginPage-B4VCQxzn.js} +1 -1
- package/dashboard/dist/assets/{PetPage-nOKryTde.js → PetPage-Cmf6aXc9.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-Ch6NLnu6.js → PopoverPopup-BzfELnIC.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-BRY2br-g.js → ProviderIcon-ClHXNkQ0.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-ko1OQecQ.js → ResetPasswordPage-BeUpNt_6.js} +1 -1
- package/dashboard/dist/assets/{Select-Q8mS3_od.js → Select-BHEXdYHu.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-C1id8GDj.js → SelectItemText-aaQK_QIt.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-Dzb2bDiM.js → SelfHostPage-ClNhKnlP.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-ewFwz29J.js → ServiceStatusPage-Byd_s6FS.js} +1 -1
- package/dashboard/dist/assets/SessionsPage-u3_b4PO7.js +3 -0
- package/dashboard/dist/assets/SettingsPage-DweCAbsy.js +2 -0
- package/dashboard/dist/assets/{SkillsPage-CcG77tqU.js → SkillsPage-Bb-PVfux.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-D2HDrbQR.js → TokenGalaxy-CubA3PXu.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-C_bF2ZwO.js → TooltipPopup-DMliWhoK.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-BcyoN5ns.js → TrendMonitor-as8idaOy.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BirK0U_N.js → WidgetsPage-DDPR4v0R.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-thsXBuw6.js → WrappedPage-zZzgYz6s.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-Bq8nj-JV.js → __vite-browser-external-DWN9Fb-H.js} +12 -12
- package/dashboard/dist/assets/__vite-browser-external-LpFw4zXh.css +1 -0
- package/dashboard/dist/assets/agent-logos-CoNhCm9-.js +1 -0
- package/dashboard/dist/assets/{arrow-left-c_NOSfoq.js → arrow-left-C5hDp7fc.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-CP7drKCT.js → arrow-right-DEvMmSiA.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-DQ6QfCpo.js → arrow-up-right-DxY0neJ4.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-D_pxFQvT.js → calendar-days-bzh7-lab.js} +1 -1
- package/dashboard/dist/assets/{check-HAS31kTT.js → check-CSZvcC3X.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-BIMuk6oN.js → chevron-down-CsiGcG0G.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-COmDwrUT.js → cloud-checkout-z_eDVwm6.js} +1 -1
- package/dashboard/dist/assets/{composite-kLwqpqEk.js → composite-Cq0CmNAv.js} +1 -1
- package/dashboard/dist/assets/{download-9RgsZIiH.js → download-DqLpubIF.js} +1 -1
- package/dashboard/dist/assets/{external-link-C_gYjHGF.js → external-link-B8E8tbB3.js} +1 -1
- package/dashboard/dist/assets/{flame-BSsCvgT8.js → flame-CrJDlnro.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-N22Wqv1X.js → format-tokens-DPIGGOlt.js} +1 -1
- package/dashboard/dist/assets/gift-CCp4Hh50.js +1 -0
- package/dashboard/dist/assets/{host-mode-JzrhHX7F.js → host-mode-xzpMLcwP.js} +1 -1
- package/dashboard/dist/assets/{icons-pK99mIMv.js → icons-CbB9Y-eV.js} +1 -1
- package/dashboard/dist/assets/{index-BhviBroj.js → index-BiK5-tQp.js} +1 -1
- package/dashboard/dist/assets/{index-CmD84EJi.js → index-DX7nYrHb.js} +1 -1
- package/dashboard/dist/assets/{index-Dk43KNa6.js → index-zuC7FU-t.js} +1 -1
- package/dashboard/dist/assets/{info-D-RLFnSK.js → info-B3NKYwWG.js} +1 -1
- package/dashboard/dist/assets/{link-2-BiKXW9mV.js → link-2-BRc8d8M9.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-KVUaEKV2.js → loader-circle-Cm5Nsgp-.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-5NK8j3mT.js → modifiers.esm-CYLYBECB.js} +1 -1
- package/dashboard/dist/assets/module-BNnaKM__.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-u9yr-_Es.js → monitor-smartphone--IZ4D84F.js} +1 -1
- package/dashboard/dist/assets/{play-D0MaDCOx.js → play-CUrMOLtq.js} +1 -1
- package/dashboard/dist/assets/{plus-DTJVtoAJ.js → plus-0hrCDl4r.js} +1 -1
- package/dashboard/dist/assets/{provider-display-YhtG0I9Y.js → provider-display-CNK0FN3Q.js} +1 -1
- package/dashboard/dist/assets/{react-gIVxucOB.js → react-CCNSVHEA.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-C_sPnJ1L.js → safePolygon-DsolkMUX.js} +1 -1
- package/dashboard/dist/assets/{search-DdQRwFYV.js → search-DYmYb9xY.js} +1 -1
- package/dashboard/dist/assets/{server-M0rmbGWy.js → server-CYcqzdSW.js} +1 -1
- package/dashboard/dist/assets/{shield-check-9-sqQKdD.js → shield-check-5zABLIr6.js} +1 -1
- package/dashboard/dist/assets/{skills-api-B0qBWnuK.js → skills-api-CD-ojBGj.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-iqbm7x5D.js → sortable.esm-BCn2Ms6R.js} +1 -1
- package/dashboard/dist/assets/{store-DstuWd4a.js → store-B22kwLs0.js} +1 -1
- package/dashboard/dist/assets/{terminal-Dz0MxHNG.js → terminal-CTv-oTU2.js} +1 -1
- package/dashboard/dist/assets/{trash-DBMFOFVz.js → trash-C5MAh3GO.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-CacbAcUR.js → use-community-stats-BrgrxUD2.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-nBuQVZOR.js → use-latest-request-guard-B1_rbvgH.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-BOk5f-HM.js +1 -0
- package/dashboard/dist/assets/{use-native-settings-EAqAFx2x.js → use-native-settings-B2gc_m9W.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DGLjjU6Q.js → use-ordered-list-N3sGpJ0u.js} +1 -1
- package/dashboard/dist/assets/{use-pet-settings-C5MzWHE7.js → use-pet-settings-sWFXgR3m.js} +2 -2
- package/dashboard/dist/assets/use-reduced-motion-C3MBYVqm.js +1 -0
- package/dashboard/dist/assets/{use-transform-C25eCuz_.js → use-transform-BsaLmw46.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-wL7v-S0r.js +1 -0
- package/dashboard/dist/assets/{useCurrency-t3PYQi1l.js → useCurrency-CbCnjf-N.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-C59mj6kG.js → usePositioner-MepYlL0A.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-C0xQsBGf.js → useTokenFormat-Bq-rwuKa.js} +1 -1
- package/dashboard/dist/assets/{zap-CyH0tvKM.js → zap-1ah5e11g.js} +1 -1
- package/dashboard/dist/index.html +14 -12
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/llms.txt +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/cli.js +1 -1
- package/src/commands/init.js +43 -1
- package/src/commands/status.js +27 -0
- package/src/commands/sync.js +54 -4
- package/src/commands/uninstall.js +19 -0
- package/src/lib/claude-config.js +47 -15
- package/src/lib/codex-rollout-parser.js +11 -3
- package/src/lib/command-runner.js +15 -1
- package/src/lib/cursor-config.js +2 -1
- package/src/lib/gemini-config.js +3 -2
- package/src/lib/local-api.js +13 -3
- package/src/lib/notify-command.js +38 -0
- package/src/lib/openclaw-session-plugin.js +47 -14
- package/src/lib/passive-mode.js +12 -0
- package/src/lib/pricing/curated-overrides.json +42 -5
- package/src/lib/pricing/matcher.js +14 -5
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +199 -44
- package/src/lib/session-analytics.js +653 -60
- package/src/lib/session-performance.js +24 -1
- package/src/lib/usage-limits.js +183 -20
- package/src/lib/zcode-limits.js +218 -18
- package/src/lib/zcode-reset-credits.js +66 -0
- package/dashboard/dist/assets/DialogDescription-C0Cgq67g.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-D8ydzVpn.js +0 -2
- package/dashboard/dist/assets/LimitsPage-DaRggHge.js +0 -8
- package/dashboard/dist/assets/SessionsPage-C6Y8kbnM.js +0 -3
- package/dashboard/dist/assets/SettingsPage-nztCXEPs.js +0 -2
- package/dashboard/dist/assets/__vite-browser-external-BwmeEfv7.css +0 -1
- package/dashboard/dist/assets/agent-logos-CK6SzhI7.js +0 -1
- package/dashboard/dist/assets/module-WSx8viXK.js +0 -3
- package/dashboard/dist/assets/use-limits-display-prefs-fqg8hs6b.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-DK2oZ2uC.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-BhqnEai3.js +0 -1
package/src/lib/claude-config.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require("node:fs/promises");
|
|
|
2
2
|
const path = require("node:path");
|
|
3
3
|
|
|
4
4
|
const { ensureDir, readJson, writeJson } = require("./fs");
|
|
5
|
+
const { buildNotifyCommand, notifyIdentity } = require("./notify-command");
|
|
5
6
|
|
|
6
7
|
const DEFAULT_EVENT = "SessionEnd";
|
|
7
8
|
const CLAUDE_USAGE_EVENTS = ["Stop", DEFAULT_EVENT];
|
|
@@ -23,10 +24,10 @@ async function upsertClaudeHooks({ settingsPath, hookCommand, events }) {
|
|
|
23
24
|
|
|
24
25
|
for (const event of normalizeEventList(events)) {
|
|
25
26
|
const entries = normalizeEntries(hooks[event]);
|
|
26
|
-
const normalized = normalizeEntriesForCommand(entries, hookCommand);
|
|
27
|
+
const normalized = normalizeEntriesForCommand(entries, hookCommand, event);
|
|
27
28
|
let nextEntries = normalized.entries;
|
|
28
29
|
if (!hasHook(nextEntries, hookCommand)) {
|
|
29
|
-
nextEntries = nextEntries.concat([{ hooks: [{ type: "command", command: hookCommand }] }]);
|
|
30
|
+
nextEntries = nextEntries.concat([{ hooks: [{ type: "command", command: hookCommand, ...hookShell(hookCommand) }] }]);
|
|
30
31
|
changed = true;
|
|
31
32
|
}
|
|
32
33
|
if (normalized.changed) changed = true;
|
|
@@ -106,14 +107,14 @@ async function areClaudeHooksConfigured({ settingsPath, hookCommand, events }) {
|
|
|
106
107
|
// Generic Session-hook command builder. CodeBuddy CLI is a Claude-Code fork
|
|
107
108
|
// and uses the exact same settings.json hook schema, so this function works
|
|
108
109
|
// for any source that accepts the `node notify.cjs --source=<name>` contract.
|
|
109
|
-
function buildHookCommand(notifyPath, source) {
|
|
110
|
+
function buildHookCommand(notifyPath, source, options) {
|
|
110
111
|
const cmd = typeof notifyPath === "string" ? notifyPath : "";
|
|
111
112
|
const src = typeof source === "string" && source ? source : "claude";
|
|
112
|
-
return
|
|
113
|
+
return buildNotifyCommand(cmd, src, options);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
function buildClaudeHookCommand(notifyPath) {
|
|
116
|
-
return buildHookCommand(notifyPath, "claude");
|
|
116
|
+
function buildClaudeHookCommand(notifyPath, options) {
|
|
117
|
+
return buildHookCommand(notifyPath, "claude", options);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
function normalizeSettings(raw) {
|
|
@@ -140,12 +141,15 @@ function normalizeCommand(cmd) {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
function hasHook(entries, hookCommand) {
|
|
144
|
+
const shell = hookShell(hookCommand).shell;
|
|
145
|
+
const configured = (hook) => normalizeCommand(hook?.command) === normalizeCommand(hookCommand) &&
|
|
146
|
+
(!shell || (hook.shell === shell && !Object.hasOwn(hook, "args")));
|
|
143
147
|
for (const entry of entries) {
|
|
144
148
|
if (!entry || typeof entry !== "object") continue;
|
|
145
|
-
if (entry.command &&
|
|
149
|
+
if (entry.command && configured(entry)) return true;
|
|
146
150
|
const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
|
|
147
151
|
for (const hook of hooks) {
|
|
148
|
-
if (hook &&
|
|
152
|
+
if (hook && configured(hook)) return true;
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
155
|
return false;
|
|
@@ -169,14 +173,25 @@ function stripHookFromEntry(entry, hookCommand) {
|
|
|
169
173
|
return { entry: { ...entry, hooks: nextHooks }, removed: true };
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
function
|
|
176
|
+
function hookShell(command) {
|
|
177
|
+
return typeof command === "string" && command.trimStart().startsWith("& '")
|
|
178
|
+
? { shell: "powershell" } : {};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeEntriesForCommand(entries, hookCommand, event) {
|
|
182
|
+
const shell = hookShell(hookCommand);
|
|
183
|
+
const repair = (hook) => {
|
|
184
|
+
const next = { ...hook, type: "command", command: hookCommand, ...shell };
|
|
185
|
+
if (shell.shell) delete next.args;
|
|
186
|
+
return next;
|
|
187
|
+
};
|
|
173
188
|
let changed = false;
|
|
174
189
|
const nextEntries = entries.map((entry) => {
|
|
175
190
|
if (!entry || typeof entry !== "object") return entry;
|
|
176
191
|
if (entry.command && commandsEqual(entry.command, hookCommand)) {
|
|
177
|
-
if (entry.type !== "command") {
|
|
192
|
+
if (entry.type !== "command" || entry.command !== hookCommand || (shell.shell && (entry.shell !== shell.shell || Object.hasOwn(entry, "args")))) {
|
|
178
193
|
changed = true;
|
|
179
|
-
return
|
|
194
|
+
return repair(entry);
|
|
180
195
|
}
|
|
181
196
|
return entry;
|
|
182
197
|
}
|
|
@@ -184,9 +199,9 @@ function normalizeEntriesForCommand(entries, hookCommand) {
|
|
|
184
199
|
let hooksChanged = false;
|
|
185
200
|
const nextHooks = entry.hooks.map((hook) => {
|
|
186
201
|
if (hook && commandsEqual(hook.command, hookCommand)) {
|
|
187
|
-
if (hook.type !== "command") {
|
|
202
|
+
if (hook.type !== "command" || hook.command !== hookCommand || (shell.shell && (hook.shell !== shell.shell || Object.hasOwn(hook, "args")))) {
|
|
188
203
|
hooksChanged = true;
|
|
189
|
-
return
|
|
204
|
+
return repair(hook);
|
|
190
205
|
}
|
|
191
206
|
}
|
|
192
207
|
return hook;
|
|
@@ -195,13 +210,30 @@ function normalizeEntriesForCommand(entries, hookCommand) {
|
|
|
195
210
|
changed = true;
|
|
196
211
|
return { ...entry, hooks: nextHooks };
|
|
197
212
|
});
|
|
198
|
-
|
|
213
|
+
const seen = new Set();
|
|
214
|
+
const deduplicated = nextEntries.map((entry) => {
|
|
215
|
+
const matcher = entry?.matcher;
|
|
216
|
+
const scope = JSON.stringify(event === "Stop" || matcher == null || ["", "*", ".*"].includes(matcher) ? null : matcher);
|
|
217
|
+
const keep = (hook) => {
|
|
218
|
+
if (!commandsEqual(hook?.command, hookCommand)) return true;
|
|
219
|
+
if (seen.has(scope)) { changed = true; return false; }
|
|
220
|
+
seen.add(scope);
|
|
221
|
+
return true;
|
|
222
|
+
};
|
|
223
|
+
if (entry?.command) return keep(entry) ? entry : null;
|
|
224
|
+
if (!Array.isArray(entry?.hooks)) return entry;
|
|
225
|
+
const hooks = entry.hooks.filter(keep);
|
|
226
|
+
if (!hooks.length) return null;
|
|
227
|
+
return hooks.length === entry.hooks.length ? entry : { ...entry, hooks };
|
|
228
|
+
}).filter(Boolean);
|
|
229
|
+
return { entries: deduplicated, changed };
|
|
199
230
|
}
|
|
200
231
|
|
|
201
232
|
function commandsEqual(a, b) {
|
|
202
233
|
const left = normalizeCommand(a);
|
|
203
234
|
const right = normalizeCommand(b);
|
|
204
|
-
return Boolean(left && right && left === right
|
|
235
|
+
return Boolean(left && right && (left === right ||
|
|
236
|
+
(notifyIdentity(left) && notifyIdentity(left) === notifyIdentity(right))));
|
|
205
237
|
}
|
|
206
238
|
|
|
207
239
|
function quoteArg(value) {
|
|
@@ -713,6 +713,8 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
713
713
|
};
|
|
714
714
|
byModel.set(effectiveModel, row);
|
|
715
715
|
}
|
|
716
|
+
const subsetKeys = Object.keys(row).filter((key) => /^(?:long_context_|priority_)|^(?:usage_events|rerouted_usage_events)$/.test(key));
|
|
717
|
+
const before = Object.fromEntries(subsetKeys.map((key) => [key, row[key]]));
|
|
716
718
|
addInto(row, delta);
|
|
717
719
|
row.usage_events += 1;
|
|
718
720
|
if (attribution.selectedModel) {
|
|
@@ -758,6 +760,10 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
758
760
|
row.priority_long_context_reasoning_output_tokens += delta.reasoning_output_tokens;
|
|
759
761
|
}
|
|
760
762
|
}
|
|
763
|
+
return { ...delta, ...Object.fromEntries(subsetKeys.map((key) => [key, row[key] - before[key]])),
|
|
764
|
+
selected_models: attribution.selectedModel ? [attribution.selectedModel] : [],
|
|
765
|
+
reroute_reasons: attribution.rerouteReason ? [attribution.rerouteReason] : [],
|
|
766
|
+
model_attribution: attribution.rerouted ? "effective" : "selected" };
|
|
761
767
|
}
|
|
762
768
|
|
|
763
769
|
function ensureTool(name) {
|
|
@@ -927,7 +933,9 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
927
933
|
if (compactionResponseIds.has(record.responseId)) return;
|
|
928
934
|
compactionResponseIds.add(record.responseId);
|
|
929
935
|
if (!record.inRequestedRange) return;
|
|
930
|
-
recordModelUsage(record.delta, record.rawUsage, record.attribution);
|
|
936
|
+
const pricedDelta = recordModelUsage(record.delta, record.rawUsage, record.attribution);
|
|
937
|
+
if (typeof onUsage === "function") onUsage({ timestamp: record.timestamp,
|
|
938
|
+
delta: pricedDelta || record.delta, rawUsage: record.rawUsage, model: record.attribution.model, origin: "compaction" });
|
|
931
939
|
attributeTurn(record.delta);
|
|
932
940
|
}
|
|
933
941
|
|
|
@@ -1098,8 +1106,8 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
1098
1106
|
attributeTurn(null);
|
|
1099
1107
|
} else {
|
|
1100
1108
|
if (seenTokenEvents && delta?.total_tokens > 0) seenTokenEvents.add(eventKey);
|
|
1101
|
-
recordModelUsage(delta, lastUsage || rawDelta);
|
|
1102
|
-
if (typeof onUsage === "function") onUsage({ timestamp: ts, delta, rawUsage: lastUsage || rawDelta, model: attributedModel });
|
|
1109
|
+
const pricedDelta = recordModelUsage(delta, lastUsage || rawDelta);
|
|
1110
|
+
if (typeof onUsage === "function") onUsage({ timestamp: ts, delta: pricedDelta || delta, rawUsage: lastUsage || rawDelta, model: attributedModel });
|
|
1103
1111
|
attributeTurn(delta);
|
|
1104
1112
|
}
|
|
1105
1113
|
} else {
|
|
@@ -15,6 +15,7 @@ const fs = require("node:fs");
|
|
|
15
15
|
const os = require("node:os");
|
|
16
16
|
const path = require("node:path");
|
|
17
17
|
|
|
18
|
+
/** Run a command without a shell by default, optionally piping options.input to stdin. */
|
|
18
19
|
function runCommand(commandRunner, command, args, options = {}) {
|
|
19
20
|
const merged = {
|
|
20
21
|
encoding: "utf8",
|
|
@@ -29,6 +30,7 @@ function runCommand(commandRunner, command, args, options = {}) {
|
|
|
29
30
|
const {
|
|
30
31
|
timeout,
|
|
31
32
|
maxBuffer,
|
|
33
|
+
input,
|
|
32
34
|
completeWhen,
|
|
33
35
|
completionGraceMs = 250,
|
|
34
36
|
killProcessGroup = false,
|
|
@@ -64,7 +66,7 @@ function runCommand(commandRunner, command, args, options = {}) {
|
|
|
64
66
|
...spawnOptions,
|
|
65
67
|
detached: useProcessGroup || spawnOptions.detached,
|
|
66
68
|
shell: useShell,
|
|
67
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
69
|
+
stdio: [input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
68
70
|
});
|
|
69
71
|
} catch (error) {
|
|
70
72
|
resolve({ status: null, stdout: "", stderr: "", error });
|
|
@@ -168,6 +170,18 @@ function runCommand(commandRunner, command, args, options = {}) {
|
|
|
168
170
|
if (Number.isFinite(timeout) && timeout > 0) {
|
|
169
171
|
timer = setTimeout(() => stopChild({ timeoutExpired: true }), timeout);
|
|
170
172
|
}
|
|
173
|
+
if (input !== undefined) {
|
|
174
|
+
child.stdin?.on("error", (error) => {
|
|
175
|
+
signalChild("SIGKILL");
|
|
176
|
+
settle({ error });
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
child.stdin?.end(input);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
signalChild("SIGKILL");
|
|
182
|
+
settle({ error });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
171
185
|
});
|
|
172
186
|
}
|
|
173
187
|
|
package/src/lib/cursor-config.js
CHANGED
|
@@ -15,7 +15,8 @@ function resolveCursorPaths({ home, platform = process.platform, env = process.e
|
|
|
15
15
|
appDir = pathForPlatform.join(h, "Library", "Application Support", "Cursor");
|
|
16
16
|
} else if (platform === "win32") {
|
|
17
17
|
const appData =
|
|
18
|
-
(
|
|
18
|
+
((!home || h === os.homedir() || env !== process.env) &&
|
|
19
|
+
typeof env.APPDATA === "string" && env.APPDATA.trim()) ||
|
|
19
20
|
pathForPlatform.join(h, "AppData", "Roaming");
|
|
20
21
|
appDir = pathForPlatform.join(appData, "Cursor");
|
|
21
22
|
} else {
|
package/src/lib/gemini-config.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require("node:path");
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
4
|
|
|
5
5
|
const { ensureDir, readJson, writeJson } = require("./fs");
|
|
6
|
+
const { buildNotifyCommand } = require("./notify-command");
|
|
6
7
|
|
|
7
8
|
const DEFAULT_EVENT = "SessionEnd";
|
|
8
9
|
const DEFAULT_HOOK_NAME = "tokentracker";
|
|
@@ -99,9 +100,9 @@ async function isGeminiHookConfigured({
|
|
|
99
100
|
return hasHook(entries, { hookCommand, hookName });
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
function buildGeminiHookCommand(notifyPath) {
|
|
103
|
+
function buildGeminiHookCommand(notifyPath, options) {
|
|
103
104
|
const cmd = typeof notifyPath === "string" ? notifyPath : "";
|
|
104
|
-
return
|
|
105
|
+
return buildNotifyCommand(cmd, "gemini", options);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
function buildHookEntry({ hookCommand, hookName, matcher }) {
|
package/src/lib/local-api.js
CHANGED
|
@@ -1056,7 +1056,17 @@ function scanClaudeProjects(projectMap) {
|
|
|
1056
1056
|
// ---------------------------------------------------------------------------
|
|
1057
1057
|
|
|
1058
1058
|
function json(res, data, status) {
|
|
1059
|
-
|
|
1059
|
+
// Usage and cost payloads change on every sync, and cost is re-priced on
|
|
1060
|
+
// every read (pricing is applied per request, never stored in the queue) —
|
|
1061
|
+
// so a cached response can pin stale costs on screen indefinitely. WebView2
|
|
1062
|
+
// caches responses that carry no freshness directive, and the dashboard's
|
|
1063
|
+
// local fetch path omits `cache: "no-store"` (only the cloud path sets it).
|
|
1064
|
+
// Endpoints that care already set this header by hand; do it once here so
|
|
1065
|
+
// every JSON response is covered.
|
|
1066
|
+
res.writeHead(status || 200, {
|
|
1067
|
+
"Content-Type": "application/json",
|
|
1068
|
+
"Cache-Control": "no-store",
|
|
1069
|
+
});
|
|
1060
1070
|
res.end(JSON.stringify(data));
|
|
1061
1071
|
}
|
|
1062
1072
|
|
|
@@ -2610,7 +2620,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
2610
2620
|
const sessions = await buildSessionAnalytics({ force: refresh });
|
|
2611
2621
|
const wantsCsv = url.searchParams.get("format") === "csv";
|
|
2612
2622
|
const includeSessions = wantsCsv || ["1", "true"].includes(url.searchParams.get("include_sessions"));
|
|
2613
|
-
const result = summarizeSessions(sessions, { from, to, includeSessions });
|
|
2623
|
+
const result = summarizeSessions(sessions, { from, to, includeSessions, ...getTimeZoneContext(url) });
|
|
2614
2624
|
if (wantsCsv) {
|
|
2615
2625
|
const content = sessionsToCsv(result.sessions);
|
|
2616
2626
|
res.statusCode = 200;
|
|
@@ -2645,7 +2655,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
2645
2655
|
try {
|
|
2646
2656
|
const { buildSessionAnalytics, listSessionsForBrowser } = require("./session-analytics");
|
|
2647
2657
|
const sessions = await buildSessionAnalytics({ force: refresh });
|
|
2648
|
-
const result = listSessionsForBrowser(sessions, { from, to, limit });
|
|
2658
|
+
const result = listSessionsForBrowser(sessions, { from, to, limit, ...getTimeZoneContext(url) });
|
|
2649
2659
|
json(res, { from, to, ...result });
|
|
2650
2660
|
} catch (error) {
|
|
2651
2661
|
// Node fs errors embed the absolute path ("EACCES ... open '/Users/…'").
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function powershellLiteral(value) {
|
|
4
|
+
return `'${String(value || "").replace(/'/g, "''")}'`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function quotePosix(value) {
|
|
8
|
+
const text = String(value || "");
|
|
9
|
+
if (/^[A-Za-z0-9_\-./:@]+$/.test(text)) return text;
|
|
10
|
+
return `"${text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`')}"`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildNotifyCommand(notifyPath, source, { platform = process.platform, execPath = process.execPath } = {}) {
|
|
14
|
+
if (!/^[a-z0-9-]+$/i.test(source)) throw new Error("Invalid notify source");
|
|
15
|
+
if (platform === "win32") {
|
|
16
|
+
return `& ${powershellLiteral(execPath)} ${powershellLiteral(notifyPath)} --source=${source}`;
|
|
17
|
+
}
|
|
18
|
+
return `/usr/bin/env node ${quotePosix(notifyPath)} --source=${source}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Recognize only our complete notify invocation, never a user command merely
|
|
22
|
+
// containing notify.cjs or additional shell actions. Used to repair old hooks.
|
|
23
|
+
function notifyIdentity(command) {
|
|
24
|
+
if (typeof command !== "string") return null;
|
|
25
|
+
const tokens = command.match(/"(?:\\.|[^"\\])*"|'(?:''|[^'])*'|[^\s]+/g) || [];
|
|
26
|
+
const decode = (token) => token.startsWith("'")
|
|
27
|
+
? token.slice(1, -1).replace(/''/g, "'")
|
|
28
|
+
: token.startsWith('"') ? token.slice(1, -1).replace(/\\([\\"$`])/g, "$1") : token;
|
|
29
|
+
const args = tokens.map(decode);
|
|
30
|
+
if (args[0] === "&") args.shift();
|
|
31
|
+
if (args[0] === "/usr/bin/env" && args[1] === "node") args.shift();
|
|
32
|
+
if (args.length !== 3 || !/(?:^|[\\/])node(?:\.exe)?$/i.test(args[0])) return null;
|
|
33
|
+
if (!/(?:^|[\\/])notify\.cjs$/i.test(args[1]) || !/^--source=[a-z0-9-]+$/i.test(args[2])) return null;
|
|
34
|
+
const file = args[1].replace(/\\/g, "/");
|
|
35
|
+
return `${/^[a-z]:\//i.test(file) ? file.toLowerCase() : file}\u0000${args[2]}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { buildNotifyCommand, notifyIdentity };
|
|
@@ -371,7 +371,7 @@ function buildSessionPluginIndex({ trackerDir, packageName = "tokentracker-cli",
|
|
|
371
371
|
` const agentId = normalize(ctx && ctx.agentId) || parseAgentId(sessionKey);\n` +
|
|
372
372
|
` if (!agentId) return;\n` +
|
|
373
373
|
`\n` +
|
|
374
|
-
` const sessionInfo = resolveSessionInfo(agentId, sessionKey);\n` +
|
|
374
|
+
` const sessionInfo = await resolveSessionInfo(agentId, sessionKey);\n` +
|
|
375
375
|
` const sessionId = normalize(sessionInfo && sessionInfo.sessionId);\n` +
|
|
376
376
|
` if (!sessionId) return;\n` +
|
|
377
377
|
`\n` +
|
|
@@ -440,25 +440,58 @@ function buildSessionPluginIndex({ trackerDir, packageName = "tokentracker-cli",
|
|
|
440
440
|
` return out;\n` +
|
|
441
441
|
`}\n` +
|
|
442
442
|
`\n` +
|
|
443
|
-
`function resolveSessionInfo(agentId, sessionKey) {\n` +
|
|
443
|
+
`async function resolveSessionInfo(agentId, sessionKey) {\n` +
|
|
444
444
|
` const key = normalize(sessionKey);\n` +
|
|
445
445
|
` if (!key) return null;\n` +
|
|
446
446
|
` const sessionsPath = path.join(openclawHome, 'agents', agentId, 'sessions', 'sessions.json');\n` +
|
|
447
|
-
`
|
|
448
|
-
`
|
|
449
|
-
`
|
|
450
|
-
`
|
|
451
|
-
`
|
|
452
|
-
`
|
|
453
|
-
`
|
|
454
|
-
`
|
|
455
|
-
`
|
|
456
|
-
`
|
|
457
|
-
` }
|
|
458
|
-
` }
|
|
447
|
+
` if (fs.existsSync(sessionsPath)) {\n` +
|
|
448
|
+
` try {\n` +
|
|
449
|
+
` const raw = fs.readFileSync(sessionsPath, 'utf8');\n` +
|
|
450
|
+
` const parsed = JSON.parse(raw);\n` +
|
|
451
|
+
` if (parsed && typeof parsed === 'object') {\n` +
|
|
452
|
+
` const entry = parsed[key];\n` +
|
|
453
|
+
` if (entry && typeof entry === 'object' && normalize(entry.sessionId)) {\n` +
|
|
454
|
+
` return { sessionKey: key, sessionId: normalize(entry.sessionId), entry };\n` +
|
|
455
|
+
` }\n` +
|
|
456
|
+
` }\n` +
|
|
457
|
+
` } catch (_) {}\n` +
|
|
458
|
+
` }\n` +
|
|
459
|
+
` // OpenClaw >= 2026.9.9 keeps session state in a per-agent SQLite\n` +
|
|
460
|
+
` // database (~/.openclaw/agents/<id>/agent/openclaw-agent.sqlite);\n` +
|
|
461
|
+
` // sessions.json is no longer written. Without this fallback the\n` +
|
|
462
|
+
` // plugin would silently no-op on every agent_end for anyone on a\n` +
|
|
463
|
+
` // current OpenClaw install (issue: openclaw migration to sqlite).\n` +
|
|
464
|
+
` return resolveSessionInfoFromSqlite(agentId, key);\n` +
|
|
465
|
+
`}\n` +
|
|
466
|
+
`\n` +
|
|
467
|
+
`async function resolveSessionInfoFromSqlite(agentId, sessionKey) {\n` +
|
|
468
|
+
` const sqliteModule = await importSqliteSync();\n` +
|
|
469
|
+
` if (!sqliteModule) return null;\n` +
|
|
470
|
+
` const { DatabaseSync } = sqliteModule;\n` +
|
|
471
|
+
` const currentSessionQuery = "SELECT w.session_id FROM session_nodes n JOIN session_windows w ON w.session_id = n.current_session_id WHERE n.session_key = ? AND COALESCE(w.reason, '') != 'compaction'";\n` +
|
|
472
|
+
` const sessionQuery = "SELECT session_id FROM session_windows WHERE session_key = ? AND COALESCE(reason, '') != 'compaction' ORDER BY updated_at DESC LIMIT 1";\n` +
|
|
473
|
+
` const dbCandidates = [path.join(openclawHome, 'agents', agentId, 'agent', 'openclaw-agent.sqlite'), path.join(openclawHome, 'agents', agentId, 'openclaw-agent.sqlite')];\n` +
|
|
474
|
+
` for (const dbPath of dbCandidates) {\n` +
|
|
475
|
+
` if (!fs.existsSync(dbPath)) continue;\n` +
|
|
476
|
+
` let db;\n` +
|
|
477
|
+
` try { db = new DatabaseSync(dbPath, { readOnly: true }); } catch (_) { continue; }\n` +
|
|
478
|
+
` try {\n` +
|
|
479
|
+
` let row;\n` +
|
|
480
|
+
` try { row = db.prepare(currentSessionQuery).get(sessionKey); } catch (_) {}\n` +
|
|
481
|
+
` if (!row || !row.session_id) row = db.prepare(sessionQuery).get(sessionKey);\n` +
|
|
482
|
+
` if (!row || !row.session_id) continue;\n` +
|
|
483
|
+
` // The passive SQLite reader owns per-event accounting; never synthesize lifetime sums.\n` +
|
|
484
|
+
` return { sessionKey, sessionId: row.session_id, entry: {} };\n` +
|
|
485
|
+
` } catch (_) {}\n` +
|
|
486
|
+
` finally { try { db.close(); } catch (_) {} }\n` +
|
|
487
|
+
` }\n` +
|
|
459
488
|
` return null;\n` +
|
|
460
489
|
`}\n` +
|
|
461
490
|
`\n` +
|
|
491
|
+
`async function importSqliteSync() {\n` +
|
|
492
|
+
` try { return await import('node:sqlite'); } catch (_) { return null; }\n` +
|
|
493
|
+
`}\n` +
|
|
494
|
+
`\n` +
|
|
462
495
|
`function parseAgentId(sessionKey) {\n` +
|
|
463
496
|
` const s = normalize(sessionKey);\n` +
|
|
464
497
|
` if (!s || !s.startsWith('agent:')) return null;\n` +
|
package/src/lib/passive-mode.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
const fs = require("node:fs");
|
|
30
30
|
const path = require("node:path");
|
|
31
|
+
const { expandHome } = require("./scan-roots");
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* @typedef {Object} PassiveProvider
|
|
@@ -171,6 +172,17 @@ function detectPassiveProviders({ home, hookStatus, env = process.env }) {
|
|
|
171
172
|
settingsPath: path.join(home, ".workbuddy", "settings.json"),
|
|
172
173
|
}));
|
|
173
174
|
|
|
175
|
+
// WorkBuddy AI — international build; data + hooks in ~/.workbuddy-ai
|
|
176
|
+
const workbuddyAiHome = expandHome(env.WORKBUDDY_AI_HOME, home) || path.join(home, ".workbuddy-ai");
|
|
177
|
+
out.push(buildEntry({
|
|
178
|
+
name: "workbuddy-ai",
|
|
179
|
+
hookExpected: true,
|
|
180
|
+
hookInstalled: Boolean(hookStatus?.["workbuddy-ai"]),
|
|
181
|
+
logsDir: workbuddyAiHome,
|
|
182
|
+
logsPredicate: (_full, name) => name === "projects" || name.endsWith(".jsonl"),
|
|
183
|
+
settingsPath: path.join(workbuddyAiHome, "settings.json"),
|
|
184
|
+
}));
|
|
185
|
+
|
|
174
186
|
return out;
|
|
175
187
|
}
|
|
176
188
|
|
|
@@ -3,9 +3,43 @@
|
|
|
3
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
4
|
"units": "usd_per_million_tokens",
|
|
5
5
|
"deepseek_v4_pricing_note": "Official DeepSeek V4 pricing verified on 2026-08-21. Peak hours are 01:00-04:00 and 06:00-10:00 UTC; all other hours are off-peak at half price. Exact entries store peak rates. computeRowCost applies the time-of-use discount from each row's UTC hour_start.",
|
|
6
|
-
"iflytek_maas_pricing_note": "iFlytek MaaS inference prices supplied for AStudio on 2026-08-31. AStudio homepage: https://agent.xfyun.cn/. Official pricing source: https://maas.xfyun.cn/modelSquare. Source values are RMB per million tokens, converted at 7.2 RMB/USD, then rounded to two decimal places for billing. Models without a dedicated cache-hit price use the normal input rate; cache writes also use the normal input rate. iFlytek MaaS prices are source-scoped and do not inherit DeepSeek time-of-use discounts."
|
|
6
|
+
"iflytek_maas_pricing_note": "iFlytek MaaS inference prices supplied for AStudio on 2026-08-31. AStudio homepage: https://agent.xfyun.cn/. Official pricing source: https://maas.xfyun.cn/modelSquare. Source values are RMB per million tokens, converted at 7.2 RMB/USD, then rounded to two decimal places for billing. Models without a dedicated cache-hit price use the normal input rate; cache writes also use the normal input rate. iFlytek MaaS prices are source-scoped and do not inherit DeepSeek time-of-use discounts. Spark-X2.5 entries (spark-x2.5, spark-x2.5-4b, spark-x2.5-1.7b) were read from the model square on 2026-10-08; spark-x2.5 is listed at a limited-time 30% rate (0.96 / 3.6 / 0.14 RMB input / output / cache-hit, list price 3.2 / 12 / 0.48), and the two small models are free."
|
|
7
7
|
},
|
|
8
8
|
"exact": {
|
|
9
|
+
"claude-opus-5-5": {
|
|
10
|
+
"input": 4,
|
|
11
|
+
"output": 20,
|
|
12
|
+
"cache_read": 0.2,
|
|
13
|
+
"cache_write": 5,
|
|
14
|
+
"note": "Official Anthropic pricing verified 2026-10-11: https://platform.claude.com/docs/en/about-claude/pricing"
|
|
15
|
+
},
|
|
16
|
+
"claude-opus-5.5": {
|
|
17
|
+
"input": 4,
|
|
18
|
+
"output": 20,
|
|
19
|
+
"cache_read": 0.2,
|
|
20
|
+
"cache_write": 5,
|
|
21
|
+
"note": "Official Anthropic pricing verified 2026-10-11: https://platform.claude.com/docs/en/about-claude/pricing"
|
|
22
|
+
},
|
|
23
|
+
"claude-sonnet-5-5": {
|
|
24
|
+
"input": 2,
|
|
25
|
+
"output": 10,
|
|
26
|
+
"cache_read": 0.1,
|
|
27
|
+
"cache_write": 2.5,
|
|
28
|
+
"note": "Official Anthropic pricing verified 2026-10-11: https://platform.claude.com/docs/en/about-claude/pricing"
|
|
29
|
+
},
|
|
30
|
+
"claude-sonnet-5.5": {
|
|
31
|
+
"input": 2,
|
|
32
|
+
"output": 10,
|
|
33
|
+
"cache_read": 0.1,
|
|
34
|
+
"cache_write": 2.5,
|
|
35
|
+
"note": "Official Anthropic pricing verified 2026-10-11: https://platform.claude.com/docs/en/about-claude/pricing"
|
|
36
|
+
},
|
|
37
|
+
"k3-256k": {
|
|
38
|
+
"input": 3,
|
|
39
|
+
"output": 15,
|
|
40
|
+
"cache_read": 0.3,
|
|
41
|
+
"note": "Bare alias emitted by Kimi Code (modelAlias \"kimi-code/k3\" -> \"k3\"). Same rates as kimi-k3."
|
|
42
|
+
},
|
|
9
43
|
"cline-gateway-models": {
|
|
10
44
|
"input": 0,
|
|
11
45
|
"output": 0,
|
|
@@ -42,10 +76,10 @@
|
|
|
42
76
|
"note": "Opus 4.8 not yet in LiteLLM. Pin to the standard Opus tier (same as 4.6/4.7); remove once LiteLLM carries it."
|
|
43
77
|
},
|
|
44
78
|
"claude-sonnet-5": {
|
|
45
|
-
"input":
|
|
46
|
-
"output":
|
|
47
|
-
"cache_read": 0.
|
|
48
|
-
"cache_write":
|
|
79
|
+
"input": 2,
|
|
80
|
+
"output": 10,
|
|
81
|
+
"cache_read": 0.2,
|
|
82
|
+
"cache_write": 2.5,
|
|
49
83
|
"note": "Sonnet 5. LiteLLM added this model on ~2026-06-30 with matching rates, but pin explicitly so pricing is correct immediately rather than depending on the 24h local pricing-cache refresh cycle."
|
|
50
84
|
},
|
|
51
85
|
"claude-haiku-4-5": {
|
|
@@ -482,6 +516,9 @@
|
|
|
482
516
|
},
|
|
483
517
|
"source_exact": {
|
|
484
518
|
"acode": {
|
|
519
|
+
"spark-x2.5": { "input": 0.13, "output": 0.50, "cache_read": 0.02, "cache_write": 0.13 },
|
|
520
|
+
"spark-x2.5-4b": { "input": 0.00, "output": 0.00, "cache_read": 0.00, "cache_write": 0.00 },
|
|
521
|
+
"spark-x2.5-1.7b": { "input": 0.00, "output": 0.00, "cache_read": 0.00, "cache_write": 0.00 },
|
|
485
522
|
"xopglm53": { "input": 1.11, "output": 3.89, "cache_read": 0.28, "cache_write": 1.11 },
|
|
486
523
|
"xopdeepseekv4pro0813": { "input": 1.25, "output": 3.75, "cache_read": 0.04, "cache_write": 1.25 },
|
|
487
524
|
"xopdeepseekv4flash0731": { "input": 0.14, "output": 0.28, "cache_read": 0.03, "cache_write": 0.14 },
|
|
@@ -238,7 +238,13 @@ function lookupContainedExactCaseInsensitive(table, model) {
|
|
|
238
238
|
const lower = model.toLowerCase();
|
|
239
239
|
const keys = Object.keys(table).sort((a, b) => b.length - a.length);
|
|
240
240
|
for (const key of keys) {
|
|
241
|
-
|
|
241
|
+
const start = lower.indexOf(key.toLowerCase());
|
|
242
|
+
if (start < 0) continue;
|
|
243
|
+
// A model generation must not match a later numeric version (Opus 5.5
|
|
244
|
+
// is a separate SKU from Opus 5). Dated and provider suffixes still work.
|
|
245
|
+
const suffix = lower.slice(start + key.length);
|
|
246
|
+
if (/^[.\d]/.test(suffix)) continue;
|
|
247
|
+
return table[key];
|
|
242
248
|
}
|
|
243
249
|
return null;
|
|
244
250
|
}
|
|
@@ -256,6 +262,9 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
|
256
262
|
// 0. CURATED source exact. Source-specific prices apply only to their source,
|
|
257
263
|
// preventing collisions with public prices for same-named models from other CLIs.
|
|
258
264
|
const sourceKey = typeof source === "string" ? source.toLowerCase() : "";
|
|
265
|
+
if (sourceKey === "workbuddy-ai" && lower.trim() === "auto") {
|
|
266
|
+
return { hit: false, source: "miss", value: null };
|
|
267
|
+
}
|
|
259
268
|
if (sourceKey === "cline" && lower.endsWith(":free")) {
|
|
260
269
|
return {
|
|
261
270
|
hit: true,
|
|
@@ -289,10 +298,6 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
|
289
298
|
if (curatedDotExact) {
|
|
290
299
|
return { hit: true, source: "curated:exact-dot", value: curatedDotExact };
|
|
291
300
|
}
|
|
292
|
-
const curatedDotContainedExact = lookupContainedExactCaseInsensitive(curated.exact, dotForm);
|
|
293
|
-
if (curatedDotContainedExact) {
|
|
294
|
-
return { hit: true, source: "curated:exact-dot", value: curatedDotContainedExact };
|
|
295
|
-
}
|
|
296
301
|
|
|
297
302
|
// 2. LiteLLM exact
|
|
298
303
|
if (litellm && litellm[lookupModel]) {
|
|
@@ -302,6 +307,10 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
|
302
307
|
if (litellmDotExact) {
|
|
303
308
|
return { hit: true, source: "litellm:exact-dot", value: litellmDotExact };
|
|
304
309
|
}
|
|
310
|
+
const curatedDotContainedExact = lookupContainedExactCaseInsensitive(curated.exact, dotForm);
|
|
311
|
+
if (curatedDotContainedExact) {
|
|
312
|
+
return { hit: true, source: "curated:exact-dot", value: curatedDotContainedExact };
|
|
313
|
+
}
|
|
305
314
|
|
|
306
315
|
// 3. CURATED alias (literal mapping like "auto" -> "composer-1")
|
|
307
316
|
if (curated.alias && curated.alias[lookupModel] && curated.exact[curated.alias[lookupModel]]) {
|