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.
Files changed (37) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/dashboard/dist/assets/{Card-ANC9ZmIT.js → Card-jA08WeEw.js} +1 -1
  4. package/dashboard/dist/assets/{DashboardPage-DpZaCksZ.js → DashboardPage-chDVOYmG.js} +1 -1
  5. package/dashboard/dist/assets/{FadeIn-WXGyOn0H.js → FadeIn-DqSYXuUL.js} +1 -1
  6. package/dashboard/dist/assets/{HeaderGithubStar-DUkE0Dwd.js → HeaderGithubStar-C11rWv0B.js} +1 -1
  7. package/dashboard/dist/assets/{IpCheckPage-BPF8eGpg.js → IpCheckPage-CkEZ9yLK.js} +1 -1
  8. package/dashboard/dist/assets/{LandingPage-0uTpqpAU.js → LandingPage-BgckTHRQ.js} +1 -1
  9. package/dashboard/dist/assets/{LeaderboardPage-DzxRJEzb.js → LeaderboardPage-BCNW7UWp.js} +1 -1
  10. package/dashboard/dist/assets/{LeaderboardProfilePage-C3_oUxhG.js → LeaderboardProfilePage-BLATxMt-.js} +1 -1
  11. package/dashboard/dist/assets/{LimitsPage-BVuvoeY9.js → LimitsPage-arF--WgR.js} +1 -1
  12. package/dashboard/dist/assets/{LoginPage-CfkNRmT6.js → LoginPage-DpoFP0va.js} +1 -1
  13. package/dashboard/dist/assets/{PopoverPopup-CfxiYbJm.js → PopoverPopup-kdgc2H6C.js} +1 -1
  14. package/dashboard/dist/assets/{ProviderIcon-DiPzAed2.js → ProviderIcon-DV5r9qqP.js} +1 -1
  15. package/dashboard/dist/assets/{SettingsPage-Devu7beE.js → SettingsPage-Bb22ORmU.js} +1 -1
  16. package/dashboard/dist/assets/{SkillsPage-CSe8fW4V.js → SkillsPage-xhtBqVKC.js} +1 -1
  17. package/dashboard/dist/assets/{WidgetsPage-BrLp5YLk.js → WidgetsPage-CUoSVDET.js} +1 -1
  18. package/dashboard/dist/assets/{chevron-down-nFF6Yj_r.js → chevron-down-DYb2EChD.js} +1 -1
  19. package/dashboard/dist/assets/{download-DhSZ--68.js → download-C-_8o6dh.js} +1 -1
  20. package/dashboard/dist/assets/{leaderboard-columns-CvFdXrw5.js → leaderboard-columns-BgzBlYo7.js} +1 -1
  21. package/dashboard/dist/assets/{main-DtrPNYb7.js → main-11hApDak.js} +3 -3
  22. package/dashboard/dist/assets/{use-limits-display-prefs-Yy8t7tbB.js → use-limits-display-prefs-BeGKWUuk.js} +1 -1
  23. package/dashboard/dist/assets/{use-native-settings-uemf9RSH.js → use-native-settings-nTTHktn0.js} +1 -1
  24. package/dashboard/dist/assets/{use-reduced-motion-DH8DxE18.js → use-reduced-motion-DU8Gm6j1.js} +1 -1
  25. package/dashboard/dist/assets/{use-usage-limits-C3vUT6PH.js → use-usage-limits-DTPmEB8Y.js} +1 -1
  26. package/dashboard/dist/index.html +1 -1
  27. package/dashboard/dist/share.html +1 -1
  28. package/package.json +1 -1
  29. package/src/commands/init.js +9 -5
  30. package/src/commands/serve.js +0 -12
  31. package/src/commands/sync.js +370 -7
  32. package/src/lib/grok-hook.js +86 -7
  33. package/src/lib/pricing/curated-overrides.json +1 -1
  34. package/src/lib/pricing/seed-snapshot.json +1 -1
  35. package/src/lib/rollout.js +403 -140
  36. package/src/lib/subscriptions.js +92 -40
  37. package/src/lib/usage-limits.js +1 -1
@@ -1 +1 @@
1
- import{r as a}from"./main-DtrPNYb7.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};
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};
@@ -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-DtrPNYb7.js";import{C as y}from"./Card-ANC9ZmIT.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};
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};
@@ -1 +1 @@
1
- import{aG as t,aH as o,r,aI as s}from"./main-DtrPNYb7.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
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-DtrPNYb7.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};
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-DtrPNYb7.js"></script>
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-DtrPNYb7.js"></script>
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.19.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": {
@@ -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);
@@ -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");
@@ -14,7 +14,7 @@ const {
14
14
  readOpencodeDbMessages,
15
15
  resolveKiroDbPath,
16
16
  resolveKiroJsonlPath,
17
- resolveHermesDbPath,
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 hermesDbPath = resolveHermesDbPath();
504
- if (fssync.existsSync(hermesDbPath)) {
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
- dbPath: hermesDbPath,
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: grokHookSignal.totalTokens,
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 || {};