tokentracker-cli 0.19.0 → 0.20.0
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 +2 -2
- package/README.zh-CN.md +2 -2
- package/dashboard/dist/assets/{Card-ANC9ZmIT.js → Card-jA08WeEw.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DpZaCksZ.js → DashboardPage-chDVOYmG.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-WXGyOn0H.js → FadeIn-DqSYXuUL.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DUkE0Dwd.js → HeaderGithubStar-C11rWv0B.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BPF8eGpg.js → IpCheckPage-CkEZ9yLK.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-0uTpqpAU.js → LandingPage-BgckTHRQ.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-DzxRJEzb.js → LeaderboardPage-BCNW7UWp.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-C3_oUxhG.js → LeaderboardProfilePage-BLATxMt-.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BVuvoeY9.js → LimitsPage-arF--WgR.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CfkNRmT6.js → LoginPage-DpoFP0va.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-CfxiYbJm.js → PopoverPopup-kdgc2H6C.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-DiPzAed2.js → ProviderIcon-DV5r9qqP.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-Devu7beE.js → SettingsPage-Bb22ORmU.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-CSe8fW4V.js → SkillsPage-xhtBqVKC.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BrLp5YLk.js → WidgetsPage-CUoSVDET.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-nFF6Yj_r.js → chevron-down-DYb2EChD.js} +1 -1
- package/dashboard/dist/assets/{download-DhSZ--68.js → download-C-_8o6dh.js} +1 -1
- package/dashboard/dist/assets/{leaderboard-columns-CvFdXrw5.js → leaderboard-columns-BgzBlYo7.js} +1 -1
- package/dashboard/dist/assets/{main-DtrPNYb7.js → main-11hApDak.js} +3 -3
- package/dashboard/dist/assets/{use-limits-display-prefs-Yy8t7tbB.js → use-limits-display-prefs-BeGKWUuk.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-uemf9RSH.js → use-native-settings-nTTHktn0.js} +1 -1
- package/dashboard/dist/assets/{use-reduced-motion-DH8DxE18.js → use-reduced-motion-DU8Gm6j1.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-C3vUT6PH.js → use-usage-limits-DTPmEB8Y.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/init.js +9 -5
- package/src/commands/serve.js +0 -12
- package/src/commands/sync.js +370 -7
- package/src/lib/grok-hook.js +86 -7
- package/src/lib/pricing/curated-overrides.json +1 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +403 -140
- package/src/lib/subscriptions.js +92 -40
- package/src/lib/usage-limits.js +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a}from"./main-
|
|
1
|
+
import{r as a}from"./main-11hApDak.js";const d=["claude","codex","cursor","gemini","kimi","kiro","copilot","antigravity"],S={claude:"Claude",codex:"Codex",cursor:"Cursor",gemini:"Gemini",kimi:"Kimi",kiro:"Kiro",copilot:"GitHub Copilot",antigravity:"Antigravity"},v={claude:"/brand-logos/claude-code.svg",codex:"/brand-logos/codex.svg",cursor:"/brand-logos/cursor.svg",gemini:"/brand-logos/gemini.svg",kimi:"/brand-logos/kimi.svg",kiro:"/brand-logos/kiro.svg",copilot:"/brand-logos/copilot.svg",antigravity:"/brand-logos/antigravity.svg"},l="tt.limits.providerOrder",g="tt.limits.providerVisibility";function w(){if(typeof window>"u")return[...d];try{const i=window.localStorage.getItem(l);if(!i)return[...d];const s=JSON.parse(i);if(!Array.isArray(s))return[...d];const r=s.filter(c=>d.includes(c));for(const c of d)r.includes(c)||r.push(c);return r}catch{return[...d]}}function y(){const i=Object.fromEntries(d.map(s=>[s,!0]));if(typeof window>"u")return i;try{const s=window.localStorage.getItem(g);if(!s)return i;const r=JSON.parse(s);if(!r||typeof r!="object")return i;const c={...i};for(const u of d)typeof r[u]=="boolean"&&(c[u]=r[u]);return c}catch{return i}}function C(){const[i,s]=a.useState(w),[r,c]=a.useState(y);a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(l,JSON.stringify(i))}catch{}},[i]),a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(g,JSON.stringify(r))}catch{}},[r]),a.useEffect(()=>{if(typeof window>"u")return;const o=t=>{t.key===l&&s(w()),t.key===g&&c(y())};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)},[]);const u=a.useCallback(o=>{c(t=>({...t,[o]:!t[o]}))},[]),b=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<=0)return t;const n=[...t];return[n[e-1],n[e]]=[n[e],n[e-1]],n})},[]),p=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<0||e>=t.length-1)return t;const n=[...t];return[n[e],n[e+1]]=[n[e+1],n[e]],n})},[]),O=a.useCallback((o,t)=>{o!==t&&s(e=>{const n=e.indexOf(o),m=e.indexOf(t);if(n<0||m<0)return e;const f=[...e],[I]=f.splice(n,1);return f.splice(m,0,I),f})},[]),k=a.useCallback(()=>{s([...d]),c(Object.fromEntries(d.map(o=>[o,!0])))},[]),x=a.useMemo(()=>i.filter(o=>r[o]!==!1),[i,r]);return{order:i,visibility:r,visibleOrdered:x,toggle:u,moveUp:b,moveDown:p,moveToward:O,reset:k}}export{v as L,S as a,C as u};
|
package/dashboard/dist/assets/{use-native-settings-uemf9RSH.js → use-native-settings-nTTHktn0.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{D as a,a5 as x,r as n,aB as g,aC as m,aD as b,aE as u,aF as f,az as h}from"./main-
|
|
1
|
+
import{D as a,a5 as x,r as n,aB as g,aC as m,aD as b,aE as u,aF as f,az as h}from"./main-11hApDak.js";import{C as y}from"./Card-jA08WeEw.js";function p({checked:s,onChange:t,disabled:e,ariaLabel:i}){return a.jsx("button",{type:"button",role:"switch","aria-checked":s,"aria-label":i,onClick:t,disabled:e,className:x("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-oai-brand-500 disabled:opacity-50 disabled:cursor-not-allowed",s?"bg-oai-brand-500":"bg-oai-gray-300 dark:bg-oai-gray-700"),children:a.jsx("span",{className:x("inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",s?"translate-x-[18px]":"translate-x-[3px]")})})}function j({label:s,hint:t,control:e}){return a.jsxs("div",{className:"flex items-center justify-between gap-4 py-3",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("div",{className:"text-sm text-oai-gray-900 dark:text-oai-gray-200",children:s}),t?a.jsx("div",{className:"mt-0.5 text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),a.jsx("div",{className:"shrink-0",children:e})]})}function N({title:s,subtitle:t,action:e,children:i}){return a.jsxs(y,{children:[a.jsxs("div",{className:"mb-3 flex items-start justify-between gap-4",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("h2",{className:"text-sm font-medium text-oai-gray-500 dark:text-oai-gray-300 uppercase tracking-wide",children:s}),t?a.jsx("p",{className:"mt-1 truncate text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),e?a.jsx("div",{className:"shrink-0",children:e}):null]}),a.jsx("div",{className:"-mb-3 divide-y divide-oai-gray-200/60 dark:divide-oai-gray-800/60",children:i})]})}function w({options:s,value:t,onChange:e}){return a.jsx("div",{className:"inline-flex items-center rounded-lg border border-oai-gray-200 bg-oai-gray-50 p-0.5 dark:border-oai-gray-800 dark:bg-oai-gray-900",children:s.map(({value:i,label:d,Icon:l})=>{const r=t===i;return a.jsxs("button",{type:"button",onClick:()=>e(i),"aria-pressed":r,className:x("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors",r?"bg-white text-oai-black shadow-sm dark:bg-oai-gray-800 dark:text-white":"text-oai-gray-500 hover:text-oai-black dark:text-oai-gray-400 dark:hover:text-white"),children:[l?a.jsx(l,{className:"h-3.5 w-3.5","aria-hidden":!0}):null,a.jsx("span",{children:d})]},i)})})}function S(){const[s,t]=n.useState(null),e=g()&&m();n.useEffect(()=>{if(!e)return;const r=b(o=>t(o));return u(),r},[e]);const i=n.useCallback((r,o)=>{e&&(t(c=>c&&{...c,[r]:o}),f(r,o))},[e]),d=n.useCallback(r=>{e&&h(r)},[e]),l=n.useCallback(()=>{e&&u()},[e]);return{available:e,settings:s,setSetting:i,runAction:d,refresh:l}}export{N as S,p as T,j as a,w as b,S as u};
|
package/dashboard/dist/assets/{use-reduced-motion-DH8DxE18.js → use-reduced-motion-DU8Gm6j1.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{aG as t,aH as o,r,aI as s}from"./main-
|
|
1
|
+
import{aG as t,aH as o,r,aI as s}from"./main-11hApDak.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r,aj as l}from"./main-
|
|
1
|
+
import{r,aj as l}from"./main-11hApDak.js";function y(i){const[o,a]=r.useState(null),[u,s]=r.useState(null),[c,f]=r.useState(!0),n=!!i?.initialRefresh,g=r.useCallback(async()=>{try{const e=await l({refresh:!0});a(e&&typeof e=="object"?e:null),s(null)}catch(e){s(e?.message||String(e))}},[]);return r.useEffect(()=>{let e=!1;return(async()=>{try{const t=await l(n?{refresh:!0}:{});if(e)return;a(t&&typeof t=="object"?t:null),s(null)}catch(t){if(e)return;s(t?.message||String(t))}finally{e||f(!1)}})(),()=>{e=!0}},[n]),{data:o,error:u,isLoading:c,refresh:g}}export{y as u};
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
]
|
|
211
211
|
}
|
|
212
212
|
</script>
|
|
213
|
-
<script type="module" crossorigin src="/assets/main-
|
|
213
|
+
<script type="module" crossorigin src="/assets/main-11hApDak.js"></script>
|
|
214
214
|
<link rel="stylesheet" crossorigin href="/assets/main-CITVpx5B.css">
|
|
215
215
|
</head>
|
|
216
216
|
<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-11hApDak.js"></script>
|
|
55
55
|
<link rel="stylesheet" crossorigin href="/assets/main-CITVpx5B.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.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Every Code, Hermes, GitHub Copilot, Kimi Code, CodeBuddy, Grok Build, oh-my-pi, pi, Craft Agents, Kilo CLI, Kilo Code)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
package/src/commands/init.js
CHANGED
|
@@ -125,8 +125,6 @@ async function cmdInit(argv) {
|
|
|
125
125
|
config: existingConfig || {},
|
|
126
126
|
env: process.env,
|
|
127
127
|
});
|
|
128
|
-
const baseUrl = runtime.baseUrl;
|
|
129
|
-
let dashboardUrl = runtime.dashboardUrl || DEFAULT_DASHBOARD_URL;
|
|
130
128
|
const notifyPath = path.join(binDir, "notify.cjs");
|
|
131
129
|
const appDir = path.join(trackerDir, "app");
|
|
132
130
|
const trackerBinPath = path.join(appDir, "bin", "tracker.js");
|
|
@@ -172,7 +170,6 @@ async function cmdInit(argv) {
|
|
|
172
170
|
setup = await runSetup({
|
|
173
171
|
opts,
|
|
174
172
|
home,
|
|
175
|
-
baseUrl,
|
|
176
173
|
trackerDir,
|
|
177
174
|
binDir,
|
|
178
175
|
configPath,
|
|
@@ -302,7 +299,6 @@ async function buildDryRunSummary({ opts, home, trackerDir, notifyPath, runtime
|
|
|
302
299
|
async function runSetup({
|
|
303
300
|
opts,
|
|
304
301
|
home,
|
|
305
|
-
baseUrl,
|
|
306
302
|
trackerDir,
|
|
307
303
|
binDir,
|
|
308
304
|
configPath,
|
|
@@ -323,10 +319,18 @@ async function runSetup({
|
|
|
323
319
|
|
|
324
320
|
await installLocalTrackerApp({ appDir });
|
|
325
321
|
|
|
322
|
+
const existingPlainConfig =
|
|
323
|
+
existingConfig && typeof existingConfig === "object" && !Array.isArray(existingConfig)
|
|
324
|
+
? existingConfig
|
|
325
|
+
: {};
|
|
326
326
|
const config = {
|
|
327
|
+
...existingPlainConfig,
|
|
327
328
|
installedAt,
|
|
328
|
-
baseUrl: DEFAULT_BASE_URL,
|
|
329
|
+
baseUrl: opts.baseUrl || existingPlainConfig.baseUrl || DEFAULT_BASE_URL,
|
|
329
330
|
};
|
|
331
|
+
if (opts.dashboardUrl) {
|
|
332
|
+
config.dashboardUrl = opts.dashboardUrl;
|
|
333
|
+
}
|
|
330
334
|
|
|
331
335
|
await writeJson(configPath, config);
|
|
332
336
|
await chmod600IfPossible(configPath);
|
package/src/commands/serve.js
CHANGED
|
@@ -44,18 +44,6 @@ async function cmdServe(argv) {
|
|
|
44
44
|
process.stdout.write(`Runtime refresh warning: ${e?.message || e}\n`);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// 0.1 Ensure config.json baseUrl matches the canonical default
|
|
48
|
-
try {
|
|
49
|
-
const { DEFAULT_BASE_URL } = require("../lib/runtime-config");
|
|
50
|
-
const { readJson, writeJson } = require("../lib/fs");
|
|
51
|
-
const cfgPath = path.join(trackerDir, "config.json");
|
|
52
|
-
const cfg = await readJson(cfgPath);
|
|
53
|
-
if (cfg && cfg.baseUrl !== DEFAULT_BASE_URL) {
|
|
54
|
-
cfg.baseUrl = DEFAULT_BASE_URL;
|
|
55
|
-
await writeJson(cfgPath, cfg);
|
|
56
|
-
}
|
|
57
|
-
} catch { /* ignore */ }
|
|
58
|
-
|
|
59
47
|
// 1. Optional sync
|
|
60
48
|
if (opts.sync) {
|
|
61
49
|
process.stdout.write("Syncing local data...\n");
|
package/src/commands/sync.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
readOpencodeDbMessages,
|
|
15
15
|
resolveKiroDbPath,
|
|
16
16
|
resolveKiroJsonlPath,
|
|
17
|
-
|
|
17
|
+
resolveHermesPath,
|
|
18
18
|
resolveCopilotOtelPaths,
|
|
19
19
|
parseRolloutIncremental,
|
|
20
20
|
parseClaudeIncremental,
|
|
@@ -48,7 +48,6 @@ const {
|
|
|
48
48
|
parseKilocodeIncremental,
|
|
49
49
|
bucketKey,
|
|
50
50
|
totalsKey,
|
|
51
|
-
groupBucketKey,
|
|
52
51
|
claudeMessageDedupKey,
|
|
53
52
|
} = require("../lib/rollout");
|
|
54
53
|
const { computeClaudeGroundTruthBuckets } = require("../lib/claude-categorizer");
|
|
@@ -73,6 +72,7 @@ const { resolveRuntimeConfig } = require("../lib/runtime-config");
|
|
|
73
72
|
const CURSOR_UNKNOWN_MIGRATION_KEY = "cursorUnknownPurge_2026_04";
|
|
74
73
|
const ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY = "rolloutCumulativeDeltaReparse_2026_05";
|
|
75
74
|
const CLAUDE_MEM_OBSERVER_REINCLUDE_KEY = "claudeMemObserverReinclude_2026_05_v3";
|
|
75
|
+
const GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY = "grokAppendOnlyRepair_2026_05_v4";
|
|
76
76
|
const CLAUDE_MEM_OBSERVER_PATH_SEGMENT = "--claude-mem-observer-sessions";
|
|
77
77
|
// v1 had a cursor-format bug (wrote plain integer instead of {inode, offset,
|
|
78
78
|
// updatedAt}), which made parseClaudeIncremental reread every jsonl from
|
|
@@ -500,13 +500,13 @@ async function cmdSync(argv) {
|
|
|
500
500
|
|
|
501
501
|
// ── Hermes Agent (SQLite-based) ──
|
|
502
502
|
let hermesResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
503
|
-
const
|
|
504
|
-
if (fssync.existsSync(
|
|
503
|
+
const hermesPath = resolveHermesPath();
|
|
504
|
+
if (fssync.existsSync(hermesPath)) {
|
|
505
505
|
if (progress?.enabled) {
|
|
506
506
|
progress.start(`Parsing Hermes ${renderBar(0)} | buckets 0`);
|
|
507
507
|
}
|
|
508
508
|
hermesResult = await parseHermesIncremental({
|
|
509
|
-
|
|
509
|
+
hermesPath,
|
|
510
510
|
cursors,
|
|
511
511
|
queuePath,
|
|
512
512
|
onProgress: (p) => {
|
|
@@ -681,18 +681,36 @@ async function cmdSync(argv) {
|
|
|
681
681
|
? grokHookSignal.sessionId.trim()
|
|
682
682
|
: null;
|
|
683
683
|
if (hookSessionId) {
|
|
684
|
+
const hookContextTokens =
|
|
685
|
+
grokHookSignal.contextTokensUsed != null
|
|
686
|
+
? grokHookSignal.contextTokensUsed
|
|
687
|
+
: grokHookSignal.totalTokens;
|
|
688
|
+
const hookTotalTokens =
|
|
689
|
+
grokHookSignal.totalTokens != null
|
|
690
|
+
? grokHookSignal.totalTokens
|
|
691
|
+
: hookContextTokens;
|
|
684
692
|
grokSessionInputs.unshift({
|
|
685
693
|
sessionId: hookSessionId,
|
|
694
|
+
sessionDir:
|
|
695
|
+
typeof grokHookSignal.sessionDir === "string" ? grokHookSignal.sessionDir : undefined,
|
|
696
|
+
updatesPath:
|
|
697
|
+
typeof grokHookSignal.updatesPath === "string" ? grokHookSignal.updatesPath : undefined,
|
|
698
|
+
signalsPath:
|
|
699
|
+
typeof grokHookSignal.signalsPath === "string" ? grokHookSignal.signalsPath : undefined,
|
|
700
|
+
summaryPath:
|
|
701
|
+
typeof grokHookSignal.summaryPath === "string" ? grokHookSignal.summaryPath : undefined,
|
|
686
702
|
signals: {
|
|
687
|
-
contextTokensUsed:
|
|
703
|
+
contextTokensUsed: hookContextTokens,
|
|
704
|
+
totalTokens: hookTotalTokens,
|
|
705
|
+
totalTokensBeforeCompaction: grokHookSignal.totalTokensBeforeCompaction,
|
|
688
706
|
assistantMessageCount: grokHookSignal.messageCount,
|
|
689
707
|
primaryModelId: grokHookSignal.model,
|
|
690
708
|
lastActiveAt: grokHookSignal.lastActive,
|
|
691
709
|
},
|
|
692
710
|
summary: { updated_at: grokHookSignal.lastActive },
|
|
693
711
|
});
|
|
712
|
+
grokHookSignalConsumed = true;
|
|
694
713
|
}
|
|
695
|
-
grokHookSignalConsumed = true;
|
|
696
714
|
}
|
|
697
715
|
if (grokSessionInputs.length > 0) {
|
|
698
716
|
if (progress?.enabled) {
|
|
@@ -717,6 +735,9 @@ async function cmdSync(argv) {
|
|
|
717
735
|
bucketsQueued: grokResult.bucketsQueued + grokScanResult.bucketsQueued,
|
|
718
736
|
};
|
|
719
737
|
}
|
|
738
|
+
if (opts.repairGrok) {
|
|
739
|
+
await repairGrokQueueFromSessionSnapshots({ cursors, queuePath, queueStatePath });
|
|
740
|
+
}
|
|
720
741
|
|
|
721
742
|
// ── GitHub Copilot CLI (OTEL JSONL files) ──
|
|
722
743
|
let copilotResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
@@ -899,6 +920,7 @@ function parseArgs(argv) {
|
|
|
899
920
|
fromRetry: false,
|
|
900
921
|
fromOpenclaw: false,
|
|
901
922
|
drain: false,
|
|
923
|
+
repairGrok: false,
|
|
902
924
|
};
|
|
903
925
|
for (let i = 0; i < argv.length; i++) {
|
|
904
926
|
const a = argv[i];
|
|
@@ -907,6 +929,7 @@ function parseArgs(argv) {
|
|
|
907
929
|
else if (a === "--from-retry") out.fromRetry = true;
|
|
908
930
|
else if (a === "--from-openclaw") out.fromOpenclaw = true;
|
|
909
931
|
else if (a === "--drain") out.drain = true;
|
|
932
|
+
else if (a === "--repair-grok") out.repairGrok = true;
|
|
910
933
|
else throw new Error(`Unknown option: ${a}`);
|
|
911
934
|
}
|
|
912
935
|
return out;
|
|
@@ -917,9 +940,11 @@ module.exports = {
|
|
|
917
940
|
migrateCursorUnknownBuckets,
|
|
918
941
|
migrateRolloutCumulativeDeltaBuckets,
|
|
919
942
|
reincludeClaudeMemObserverFiles,
|
|
943
|
+
repairGrokQueueFromSessionSnapshots,
|
|
920
944
|
CURSOR_UNKNOWN_MIGRATION_KEY,
|
|
921
945
|
ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY,
|
|
922
946
|
CLAUDE_MEM_OBSERVER_REINCLUDE_KEY,
|
|
947
|
+
GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY,
|
|
923
948
|
};
|
|
924
949
|
|
|
925
950
|
function normalizeString(value) {
|
|
@@ -1289,6 +1314,344 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
1289
1314
|
return { buckets: Array.from(bucketMap.values()), nextOffset: offset };
|
|
1290
1315
|
}
|
|
1291
1316
|
|
|
1317
|
+
function normalizeGrokRepairSource(value) {
|
|
1318
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function normalizeGrokRepairModel(value) {
|
|
1322
|
+
return typeof value === "string" && value.trim() ? value.trim() : "grok-build";
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function normalizeGrokRepairNumber(value) {
|
|
1326
|
+
const n = Number(value);
|
|
1327
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function toGrokRepairHalfHourStart(value) {
|
|
1331
|
+
if (value == null) return null;
|
|
1332
|
+
const millis =
|
|
1333
|
+
typeof value === "number"
|
|
1334
|
+
? value < 10_000_000_000
|
|
1335
|
+
? value * 1000
|
|
1336
|
+
: value
|
|
1337
|
+
: Date.parse(String(value));
|
|
1338
|
+
if (!Number.isFinite(millis)) return null;
|
|
1339
|
+
const halfHourMs = 30 * 60 * 1000;
|
|
1340
|
+
return new Date(Math.floor(millis / halfHourMs) * halfHourMs).toISOString();
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
function estimateGrokRepairTotals(totalTokens, conversationCount) {
|
|
1344
|
+
const total = Math.trunc(normalizeGrokRepairNumber(totalTokens));
|
|
1345
|
+
const inputTokens = Math.round(total * 0.8);
|
|
1346
|
+
const outputTokens = Math.max(0, total - inputTokens);
|
|
1347
|
+
return {
|
|
1348
|
+
input_tokens: inputTokens,
|
|
1349
|
+
cached_input_tokens: 0,
|
|
1350
|
+
cache_creation_input_tokens: 0,
|
|
1351
|
+
output_tokens: outputTokens,
|
|
1352
|
+
reasoning_output_tokens: 0,
|
|
1353
|
+
total_tokens: total,
|
|
1354
|
+
billable_total_tokens: total,
|
|
1355
|
+
conversation_count: Math.trunc(normalizeGrokRepairNumber(conversationCount)),
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function addGrokRepairTotals(target, delta) {
|
|
1360
|
+
target.input_tokens += delta.input_tokens;
|
|
1361
|
+
target.cached_input_tokens += delta.cached_input_tokens;
|
|
1362
|
+
target.cache_creation_input_tokens += delta.cache_creation_input_tokens;
|
|
1363
|
+
target.output_tokens += delta.output_tokens;
|
|
1364
|
+
target.reasoning_output_tokens += delta.reasoning_output_tokens;
|
|
1365
|
+
target.total_tokens += delta.total_tokens;
|
|
1366
|
+
target.billable_total_tokens += delta.billable_total_tokens;
|
|
1367
|
+
target.conversation_count += delta.conversation_count;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function buildGrokRepairRowsFromSnapshots(sessionSnapshots) {
|
|
1371
|
+
if (!sessionSnapshots || typeof sessionSnapshots !== "object") return [];
|
|
1372
|
+
|
|
1373
|
+
const buckets = new Map();
|
|
1374
|
+
for (const snapshot of Object.values(sessionSnapshots)) {
|
|
1375
|
+
if (!snapshot || typeof snapshot !== "object") continue;
|
|
1376
|
+
const totalTokens = Math.trunc(normalizeGrokRepairNumber(snapshot.totalTokens));
|
|
1377
|
+
if (totalTokens <= 0) continue;
|
|
1378
|
+
|
|
1379
|
+
const hourStart = toGrokRepairHalfHourStart(
|
|
1380
|
+
snapshot.lastEventTimestamp || snapshot.updatedAt,
|
|
1381
|
+
);
|
|
1382
|
+
if (!hourStart) continue;
|
|
1383
|
+
|
|
1384
|
+
const model = normalizeGrokRepairModel(snapshot.model);
|
|
1385
|
+
const key = bucketKey("grok", model, hourStart);
|
|
1386
|
+
let totals = buckets.get(key);
|
|
1387
|
+
if (!totals) {
|
|
1388
|
+
totals = {
|
|
1389
|
+
source: "grok",
|
|
1390
|
+
model,
|
|
1391
|
+
hour_start: hourStart,
|
|
1392
|
+
input_tokens: 0,
|
|
1393
|
+
cached_input_tokens: 0,
|
|
1394
|
+
cache_creation_input_tokens: 0,
|
|
1395
|
+
output_tokens: 0,
|
|
1396
|
+
reasoning_output_tokens: 0,
|
|
1397
|
+
total_tokens: 0,
|
|
1398
|
+
billable_total_tokens: 0,
|
|
1399
|
+
conversation_count: 0,
|
|
1400
|
+
};
|
|
1401
|
+
buckets.set(key, totals);
|
|
1402
|
+
}
|
|
1403
|
+
addGrokRepairTotals(
|
|
1404
|
+
totals,
|
|
1405
|
+
estimateGrokRepairTotals(totalTokens, snapshot.messageCount),
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
return Array.from(buckets.values()).sort((a, b) => {
|
|
1410
|
+
const timeCompare = a.hour_start.localeCompare(b.hour_start);
|
|
1411
|
+
return timeCompare || a.model.localeCompare(b.model);
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function applyGrokRepairHourlyState(cursors, rows) {
|
|
1416
|
+
const hourly = cursors.hourly && typeof cursors.hourly === "object" ? cursors.hourly : {};
|
|
1417
|
+
const buckets = hourly.buckets && typeof hourly.buckets === "object" ? hourly.buckets : {};
|
|
1418
|
+
const groupQueued =
|
|
1419
|
+
hourly.groupQueued && typeof hourly.groupQueued === "object" ? hourly.groupQueued : {};
|
|
1420
|
+
|
|
1421
|
+
for (const key of Object.keys(buckets)) {
|
|
1422
|
+
if (key.startsWith("grok|")) {
|
|
1423
|
+
delete buckets[key];
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
for (const key of Object.keys(groupQueued)) {
|
|
1427
|
+
if (key.startsWith("grok|")) {
|
|
1428
|
+
delete groupQueued[key];
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
for (const row of rows) {
|
|
1433
|
+
const totals = {
|
|
1434
|
+
input_tokens: row.input_tokens,
|
|
1435
|
+
cached_input_tokens: row.cached_input_tokens,
|
|
1436
|
+
cache_creation_input_tokens: row.cache_creation_input_tokens,
|
|
1437
|
+
output_tokens: row.output_tokens,
|
|
1438
|
+
reasoning_output_tokens: row.reasoning_output_tokens,
|
|
1439
|
+
total_tokens: row.total_tokens,
|
|
1440
|
+
billable_total_tokens: row.billable_total_tokens,
|
|
1441
|
+
conversation_count: row.conversation_count,
|
|
1442
|
+
};
|
|
1443
|
+
buckets[bucketKey("grok", row.model, row.hour_start)] = {
|
|
1444
|
+
totals,
|
|
1445
|
+
queuedKey: totalsKey(totals),
|
|
1446
|
+
source: "grok",
|
|
1447
|
+
hour_start: row.hour_start,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
cursors.hourly = {
|
|
1452
|
+
...hourly,
|
|
1453
|
+
version: 3,
|
|
1454
|
+
buckets,
|
|
1455
|
+
groupQueued,
|
|
1456
|
+
updatedAt: typeof hourly.updatedAt === "string" ? hourly.updatedAt : null,
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
async function resetGrokRepairUploadOffset(queueStatePath) {
|
|
1461
|
+
if (typeof queueStatePath !== "string" || !queueStatePath) return false;
|
|
1462
|
+
let state = {};
|
|
1463
|
+
try {
|
|
1464
|
+
state = JSON.parse(await fs.readFile(queueStatePath, "utf8"));
|
|
1465
|
+
} catch (_e) {
|
|
1466
|
+
state = {};
|
|
1467
|
+
}
|
|
1468
|
+
state.offset = 0;
|
|
1469
|
+
state.updatedAt = new Date().toISOString();
|
|
1470
|
+
state.note = "reset_after_grok_append_only_repair_2026_05_v4";
|
|
1471
|
+
await ensureDir(path.dirname(queueStatePath));
|
|
1472
|
+
await fs.writeFile(queueStatePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
1473
|
+
return true;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function hasAppliedGrokRepairMigration(value) {
|
|
1477
|
+
if (!value) return false;
|
|
1478
|
+
if (value === true) return true;
|
|
1479
|
+
if (value && typeof value === "object") {
|
|
1480
|
+
if (value.status === "applied" || value.status === "noop") return true;
|
|
1481
|
+
if (value.status) return false;
|
|
1482
|
+
return value.rowsWritten != null || value.rowsRemoved != null;
|
|
1483
|
+
}
|
|
1484
|
+
return false;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function serializeGrokRepairRow(row) {
|
|
1488
|
+
return JSON.stringify({
|
|
1489
|
+
source: "grok",
|
|
1490
|
+
model: normalizeGrokRepairModel(row.model),
|
|
1491
|
+
hour_start: row.hour_start,
|
|
1492
|
+
input_tokens: row.input_tokens || 0,
|
|
1493
|
+
cached_input_tokens: row.cached_input_tokens || 0,
|
|
1494
|
+
cache_creation_input_tokens: row.cache_creation_input_tokens || 0,
|
|
1495
|
+
output_tokens: row.output_tokens || 0,
|
|
1496
|
+
reasoning_output_tokens: row.reasoning_output_tokens || 0,
|
|
1497
|
+
total_tokens: row.total_tokens || 0,
|
|
1498
|
+
billable_total_tokens: row.billable_total_tokens || 0,
|
|
1499
|
+
conversation_count: row.conversation_count || 0,
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
async function backupExistingFile(filePath) {
|
|
1504
|
+
if (typeof filePath !== "string" || !filePath) return null;
|
|
1505
|
+
try {
|
|
1506
|
+
const stat = await fs.stat(filePath);
|
|
1507
|
+
if (!stat.isFile()) return null;
|
|
1508
|
+
} catch (e) {
|
|
1509
|
+
if (e?.code === "ENOENT" || e?.code === "ENOTDIR") return null;
|
|
1510
|
+
throw e;
|
|
1511
|
+
}
|
|
1512
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1513
|
+
const backupPath = `${filePath}.bak.${stamp}`;
|
|
1514
|
+
await fs.copyFile(filePath, backupPath);
|
|
1515
|
+
return backupPath;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
async function repairGrokQueueFromSessionSnapshots({ cursors, queuePath, queueStatePath } = {}) {
|
|
1519
|
+
if (!cursors || typeof cursors !== "object") return false;
|
|
1520
|
+
const grokState = (cursors.grok ||= {});
|
|
1521
|
+
const migrations = (grokState.migrations ||= {});
|
|
1522
|
+
if (hasAppliedGrokRepairMigration(migrations[GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY])) {
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
let raw = "";
|
|
1527
|
+
try {
|
|
1528
|
+
raw = await fs.readFile(queuePath, "utf8");
|
|
1529
|
+
} catch (e) {
|
|
1530
|
+
if (e?.code !== "ENOENT") throw e;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
const latestGrokRows = new Map();
|
|
1534
|
+
let existingGrokRows = 0;
|
|
1535
|
+
for (const line of raw.split("\n")) {
|
|
1536
|
+
if (!line.trim()) continue;
|
|
1537
|
+
let row;
|
|
1538
|
+
try {
|
|
1539
|
+
row = JSON.parse(line);
|
|
1540
|
+
} catch (_e) {
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (normalizeGrokRepairSource(row?.source) === "grok") {
|
|
1545
|
+
const model = normalizeGrokRepairModel(row.model);
|
|
1546
|
+
const hourStart = typeof row.hour_start === "string" ? row.hour_start : null;
|
|
1547
|
+
if (!hourStart) continue;
|
|
1548
|
+
existingGrokRows += 1;
|
|
1549
|
+
latestGrokRows.set(bucketKey("grok", model, hourStart), {
|
|
1550
|
+
...row,
|
|
1551
|
+
source: "grok",
|
|
1552
|
+
model,
|
|
1553
|
+
hour_start: hourStart,
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (existingGrokRows === 0) {
|
|
1559
|
+
migrations[GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY] = {
|
|
1560
|
+
status: "noop",
|
|
1561
|
+
appliedAt: new Date().toISOString(),
|
|
1562
|
+
existingGrokRows: 0,
|
|
1563
|
+
rowsWritten: 0,
|
|
1564
|
+
snapshotsUsed: 0,
|
|
1565
|
+
uploadOffsetReset: false,
|
|
1566
|
+
};
|
|
1567
|
+
return false;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
const repairRows = buildGrokRepairRowsFromSnapshots(grokState.sessionSnapshots);
|
|
1571
|
+
if (repairRows.length === 0) {
|
|
1572
|
+
migrations[GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY] = {
|
|
1573
|
+
status: "skipped",
|
|
1574
|
+
appliedAt: new Date().toISOString(),
|
|
1575
|
+
reason: "missing-session-snapshots",
|
|
1576
|
+
existingGrokRows,
|
|
1577
|
+
rowsWritten: 0,
|
|
1578
|
+
snapshotsUsed: 0,
|
|
1579
|
+
uploadOffsetReset: false,
|
|
1580
|
+
};
|
|
1581
|
+
return false;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
applyGrokRepairHourlyState(cursors, repairRows);
|
|
1585
|
+
|
|
1586
|
+
const repairLines = [];
|
|
1587
|
+
const repairKeys = new Set();
|
|
1588
|
+
for (const row of repairRows) {
|
|
1589
|
+
const key = bucketKey("grok", row.model, row.hour_start);
|
|
1590
|
+
repairKeys.add(key);
|
|
1591
|
+
const current = latestGrokRows.get(key);
|
|
1592
|
+
if (current && totalsKey(current) === totalsKey(row)) continue;
|
|
1593
|
+
repairLines.push(serializeGrokRepairRow(row));
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
const zeroTotals = {
|
|
1597
|
+
input_tokens: 0,
|
|
1598
|
+
cached_input_tokens: 0,
|
|
1599
|
+
cache_creation_input_tokens: 0,
|
|
1600
|
+
output_tokens: 0,
|
|
1601
|
+
reasoning_output_tokens: 0,
|
|
1602
|
+
total_tokens: 0,
|
|
1603
|
+
billable_total_tokens: 0,
|
|
1604
|
+
conversation_count: 0,
|
|
1605
|
+
};
|
|
1606
|
+
let staleRowsRetracted = 0;
|
|
1607
|
+
for (const [key, row] of latestGrokRows.entries()) {
|
|
1608
|
+
if (repairKeys.has(key)) continue;
|
|
1609
|
+
if (totalsKey(row) === totalsKey(zeroTotals)) continue;
|
|
1610
|
+
staleRowsRetracted += 1;
|
|
1611
|
+
repairLines.push(serializeGrokRepairRow({
|
|
1612
|
+
...zeroTotals,
|
|
1613
|
+
model: row.model,
|
|
1614
|
+
hour_start: row.hour_start,
|
|
1615
|
+
}));
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
if (repairLines.length === 0) {
|
|
1619
|
+
migrations[GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY] = {
|
|
1620
|
+
status: "noop",
|
|
1621
|
+
appliedAt: new Date().toISOString(),
|
|
1622
|
+
existingGrokRows,
|
|
1623
|
+
rowsWritten: 0,
|
|
1624
|
+
staleRowsRetracted,
|
|
1625
|
+
snapshotsUsed: repairRows.length,
|
|
1626
|
+
uploadOffsetReset: false,
|
|
1627
|
+
};
|
|
1628
|
+
return false;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
await ensureDir(path.dirname(queuePath));
|
|
1632
|
+
const queueBackupPath = await backupExistingFile(queuePath);
|
|
1633
|
+
const queueStateBackupPath = await backupExistingFile(queueStatePath);
|
|
1634
|
+
await fs.appendFile(queuePath, `${repairLines.join("\n")}\n`, "utf8");
|
|
1635
|
+
|
|
1636
|
+
const uploadOffsetReset = await resetGrokRepairUploadOffset(queueStatePath);
|
|
1637
|
+
migrations[GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY] = {
|
|
1638
|
+
status: "applied",
|
|
1639
|
+
appliedAt: new Date().toISOString(),
|
|
1640
|
+
existingGrokRows,
|
|
1641
|
+
rowsWritten: repairLines.length,
|
|
1642
|
+
staleRowsRetracted,
|
|
1643
|
+
snapshotsUsed: Object.values(grokState.sessionSnapshots || {}).filter((snapshot) => {
|
|
1644
|
+
if (!snapshot || typeof snapshot !== "object") return false;
|
|
1645
|
+
if (Math.trunc(normalizeGrokRepairNumber(snapshot.totalTokens)) <= 0) return false;
|
|
1646
|
+
return Boolean(toGrokRepairHalfHourStart(snapshot.lastEventTimestamp || snapshot.updatedAt));
|
|
1647
|
+
}).length,
|
|
1648
|
+
uploadOffsetReset,
|
|
1649
|
+
queueBackupPath,
|
|
1650
|
+
queueStateBackupPath,
|
|
1651
|
+
};
|
|
1652
|
+
return true;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1292
1655
|
async function migrateCursorUnknownBuckets({ cursors, queuePath }) {
|
|
1293
1656
|
if (!cursors || typeof cursors !== "object") return;
|
|
1294
1657
|
cursors.migrations = cursors.migrations || {};
|