tokentracker-cli 0.26.2 → 0.26.4

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 (42) hide show
  1. package/dashboard/dist/assets/{ActivityHeatmap-BX2q-PW5.js → ActivityHeatmap-D3cm-gHm.js} +1 -1
  2. package/dashboard/dist/assets/{Card-CKdycEDk.js → Card-B6axUG8L.js} +1 -1
  3. package/dashboard/dist/assets/{DashboardPage-T_BImOGr.js → DashboardPage-DoJoQwbf.js} +1 -1
  4. package/dashboard/dist/assets/{DevicePage-DqFrE2fo.js → DevicePage-eqp2bFdZ.js} +1 -1
  5. package/dashboard/dist/assets/{DialogTitle-BPUtAz2U.js → DialogTitle-hAOVtFkS.js} +1 -1
  6. package/dashboard/dist/assets/{FadeIn-ortmqOpl.js → FadeIn-3jTuP_xI.js} +1 -1
  7. package/dashboard/dist/assets/{HeaderGithubStar-Cl4nchsC.js → HeaderGithubStar-CvXZrAGP.js} +1 -1
  8. package/dashboard/dist/assets/{IpCheckPage-CxRBCdXW.js → IpCheckPage-Cxd-BFTg.js} +1 -1
  9. package/dashboard/dist/assets/{LandingPage-DoIw_oXY.js → LandingPage-D0tsm4l3.js} +1 -1
  10. package/dashboard/dist/assets/{LeaderboardAvatar-GMFZci05.js → LeaderboardAvatar-n6_qkxp9.js} +1 -1
  11. package/dashboard/dist/assets/{LeaderboardPage-BzkrPpnp.js → LeaderboardPage-CGtx6u3n.js} +3 -3
  12. package/dashboard/dist/assets/{LeaderboardProfileModal-CAcqZ15e.js → LeaderboardProfileModal-BWqzy6Yq.js} +1 -1
  13. package/dashboard/dist/assets/{LeaderboardProfilePage-D7jyFQk5.js → LeaderboardProfilePage-eH9bX1wQ.js} +1 -1
  14. package/dashboard/dist/assets/{LimitsPage-fdq2MG4P.js → LimitsPage-2IvCDnuQ.js} +1 -1
  15. package/dashboard/dist/assets/{LocalOnlyNotice-XEv4_b68.js → LocalOnlyNotice-uJRb4VZ7.js} +1 -1
  16. package/dashboard/dist/assets/{LoginPage-CJ31pW6G.js → LoginPage-Cicac58j.js} +1 -1
  17. package/dashboard/dist/assets/{PopoverPopup-cEzMmw1k.js → PopoverPopup-C5N80T5y.js} +1 -1
  18. package/dashboard/dist/assets/{ProviderIcon-CkKiJH4q.js → ProviderIcon-_T6uI96n.js} +1 -1
  19. package/dashboard/dist/assets/{SettingsPage-D--hzVuV.js → SettingsPage-BwvB1IJ9.js} +1 -1
  20. package/dashboard/dist/assets/{SkillsPage-BTxPA7dW.js → SkillsPage-D4sEV5zV.js} +1 -1
  21. package/dashboard/dist/assets/{WidgetsPage-Bh4zs5u8.js → WidgetsPage-DvtstAPv.js} +1 -1
  22. package/dashboard/dist/assets/{WrappedPage-COhqXKs7.js → WrappedPage-BNQp7iJr.js} +1 -1
  23. package/dashboard/dist/assets/{arrow-up-right-B0tVW8XM.js → arrow-up-right-C1ViLV3k.js} +1 -1
  24. package/dashboard/dist/assets/check-DNb6JJp_.js +1 -0
  25. package/dashboard/dist/assets/{chevron-down-Cz58bkoU.js → chevron-down-D6ghZItu.js} +1 -1
  26. package/dashboard/dist/assets/{download-Gv8d-j81.js → download-DPh7dfoc.js} +1 -1
  27. package/dashboard/dist/assets/{info-bDEPb8dB.js → info-BkhlnyVt.js} +1 -1
  28. package/dashboard/dist/assets/{main-tm3dyfMf.js → main-iTwSqSnl.js} +2 -2
  29. package/dashboard/dist/assets/{use-limits-display-prefs-BvHWn0OQ.js → use-limits-display-prefs-DvqHsZKU.js} +1 -1
  30. package/dashboard/dist/assets/{use-native-settings-CI_YvzEo.js → use-native-settings-Dl2CIdof.js} +1 -1
  31. package/dashboard/dist/assets/{use-reduced-motion-pCeztzVC.js → use-reduced-motion-D9_5HfpE.js} +1 -1
  32. package/dashboard/dist/assets/{use-usage-limits-CKWnCeca.js → use-usage-limits-DJCSTSn7.js} +1 -1
  33. package/dashboard/dist/assets/{useCurrency-DLoIpDjL.js → useCurrency-DGZ0g8J1.js} +1 -1
  34. package/dashboard/dist/index.html +1 -1
  35. package/dashboard/dist/share.html +1 -1
  36. package/package.json +1 -1
  37. package/src/commands/sync.js +16 -4
  38. package/src/lib/claude-categorizer.js +59 -18
  39. package/src/lib/pricing/matcher.js +20 -0
  40. package/src/lib/pricing/seed-snapshot.json +1 -1
  41. package/src/lib/rollout.js +4 -7
  42. package/dashboard/dist/assets/check-Dhwj43_0.js +0 -1
@@ -1 +1 @@
1
- import{r as a}from"./main-tm3dyfMf.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-iTwSqSnl.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{C as a,S as x,r as n,aG as g,aH as m,aI as b,aJ as u,aK as f,aE as h}from"./main-tm3dyfMf.js";import{C as y}from"./Card-CKdycEDk.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{C as a,S as x,r as n,aG as g,aH as m,aI as b,aJ as u,aK as f,aE as h}from"./main-iTwSqSnl.js";import{C as y}from"./Card-B6axUG8L.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{aL as t,aM as o,r,aN as s}from"./main-tm3dyfMf.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
1
+ import{aL as t,aM as o,r,aN as s}from"./main-iTwSqSnl.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
@@ -1 +1 @@
1
- import{r,am as l}from"./main-tm3dyfMf.js";function m(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{m as u};
1
+ import{r,am as l}from"./main-iTwSqSnl.js";function m(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{m as u};
@@ -1 +1 @@
1
- import{r as e,aB as t,aC as r,H as c}from"./main-tm3dyfMf.js";const a=Object.freeze({currency:c,rate:1,symbol:"$",rates:{...r},rateSource:"default",rateFetchedAt:null,setCurrency:()=>{}});function u(){return e.useContext(t)??a}export{u};
1
+ import{r as e,aB as t,aC as r,H as c}from"./main-iTwSqSnl.js";const a=Object.freeze({currency:c,rate:1,symbol:"$",rates:{...r},rateSource:"default",rateFetchedAt:null,setCurrency:()=>{}});function u(){return e.useContext(t)??a}export{u};
@@ -210,7 +210,7 @@
210
210
  ]
211
211
  }
212
212
  </script>
213
- <script type="module" crossorigin src="/assets/main-tm3dyfMf.js"></script>
213
+ <script type="module" crossorigin src="/assets/main-iTwSqSnl.js"></script>
214
214
  <link rel="stylesheet" crossorigin href="/assets/main-smnMFIqE.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-tm3dyfMf.js"></script>
54
+ <script type="module" crossorigin src="/assets/main-iTwSqSnl.js"></script>
55
55
  <link rel="stylesheet" crossorigin href="/assets/main-smnMFIqE.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.26.2",
3
+ "version": "0.26.4",
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, Roo Code, Zed Agent, Goose)",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -104,9 +104,21 @@ const CLAUDE_MEM_OBSERVER_PATH_SEGMENT = "--claude-mem-observer-sessions";
104
104
  // (DeepSeek/Kimi/Mimo/MiniMax anthropic-compatible endpoints, plus Claude
105
105
  // Code's sub-agent / thinking transport paths). The repaired ground truth
106
106
  // was therefore inflated by 1.6–3.7x on those providers — v3 left it that
107
- // way. v4 re-runs the same five-step atomic repair against the corrected
108
- // `claudeMessageDedupKey()` (msgId is globally unique on its own per the
109
- // Anthropic protocol, so the reqId requirement was always unnecessary).
107
+ // way.
108
+ // v6 was bumped in 0.26.3 to re-run the repair with the zero-usage dedup fix
109
+ // applied. SHIPPING 0.26.3 caused catastrophic data loss on every upgrader
110
+ // whose ~/.claude session jsonls had been pruned by Claude Code's own
111
+ // cleanup: the repair does atomic-drop + rescan, so any hour_start no longer
112
+ // represented in the on-disk logs is silently removed from queue.jsonl. On
113
+ // the reporter's machine this wiped 2.17B claude tokens (-1.27B opus-4-7,
114
+ // -474M opus-4-6, -376M sonnet-4-5, -48M haiku, -6M sonnet-4-6). The
115
+ // upload-offset reset also propagated the damage to the cloud.
116
+ // 0.26.4 HALTS back at v4 so the buggy atomic-rewrite path stops auto-firing
117
+ // on existing installs. The dedup fixes in parseClaudeFile /
118
+ // categorizeSessionFile / computeClaudeGroundTruthBuckets are KEPT — they are
119
+ // correct in isolation, and any future repair will produce the right answer
120
+ // for whatever data is actually on disk. A targeted, log-gap-safe mimo
121
+ // migration will ship later under its own key.
110
122
  const CLAUDE_GROUND_TRUTH_REPAIR_KEY = "claudeGroundTruthRepair_2026_05_v4";
111
123
 
112
124
  async function cmdSync(argv) {
@@ -2112,7 +2124,7 @@ async function repairClaudeQueueFromGroundTruth({
2112
2124
  }
2113
2125
  st.offset = 0;
2114
2126
  st.updatedAt = new Date().toISOString();
2115
- st.note = "reset_after_claude_repair_2026_05_v4";
2127
+ st.note = "reset_after_claude_repair_2026_05_v6";
2116
2128
  await fs.writeFile(projectQueueStatePath, JSON.stringify(st));
2117
2129
  }
2118
2130
  }
@@ -54,6 +54,44 @@ function emptyCategoryMap() {
54
54
  return out;
55
55
  }
56
56
 
57
+ function toNonNegativeNumber(value) {
58
+ const n = Number(value || 0);
59
+ return Number.isFinite(n) ? Math.max(0, n) : 0;
60
+ }
61
+
62
+ function readClaudeUsageTotals(usage) {
63
+ if (!usage || typeof usage !== "object") return null;
64
+ const input_tokens = toNonNegativeNumber(usage.input_tokens);
65
+ const cached_input_tokens = toNonNegativeNumber(usage.cache_read_input_tokens);
66
+ const cache_creation_input_tokens = toNonNegativeNumber(usage.cache_creation_input_tokens);
67
+ const output_tokens = toNonNegativeNumber(usage.output_tokens);
68
+ const reasoning_output_tokens = toNonNegativeNumber(usage.reasoning_output_tokens);
69
+ const total_tokens =
70
+ input_tokens + cached_input_tokens + cache_creation_input_tokens + output_tokens;
71
+
72
+ return {
73
+ input_tokens,
74
+ cached_input_tokens,
75
+ cache_creation_input_tokens,
76
+ output_tokens,
77
+ reasoning_output_tokens,
78
+ total_tokens,
79
+ };
80
+ }
81
+
82
+ function hasNonZeroClaudeUsage(usage) {
83
+ const totals = readClaudeUsageTotals(usage);
84
+ if (!totals) return false;
85
+ return (
86
+ totals.input_tokens +
87
+ totals.cached_input_tokens +
88
+ totals.cache_creation_input_tokens +
89
+ totals.output_tokens +
90
+ totals.reasoning_output_tokens >
91
+ 0
92
+ );
93
+ }
94
+
57
95
  function extractExecCommands(content) {
58
96
  const commands = [];
59
97
  for (const block of Array.isArray(content) ? content : []) {
@@ -481,10 +519,19 @@ async function categorizeSessionFile(filePath, { fromIso, toIso, seenHashes }, b
481
519
  if (toIso && ts > toIso) continue;
482
520
 
483
521
  const hash = claudeMessageDedupKey(obj);
484
- if (hash) {
485
- if (seenHashes.has(hash)) continue;
486
- seenHashes.add(hash);
487
- }
522
+ if (hash && seenHashes.has(hash)) continue;
523
+
524
+ // Defer the hash-add until we've confirmed this entry carries non-zero
525
+ // usage. Mirrors the same fix in rollout.js parseClaudeFile. Without it,
526
+ // a zero-token streaming snapshot (e.g. Mimo's pre-response thinking
527
+ // entry that shares the final response's message.id) would claim the
528
+ // bare-msgId dedup key — Mimo has no requestId, so claudeMessageDedupKey
529
+ // falls back to bare msgId — and pre-empt the real token-bearing entry,
530
+ // collapsing Mimo into a tiny fraction of its real total.
531
+ const usage = obj?.message?.usage;
532
+ if (!hasNonZeroClaudeUsage(usage)) continue;
533
+
534
+ if (hash) seenHashes.add(hash);
488
535
 
489
536
  classifyOneMessage(obj, sessionState, breakdown, toolLedger, skillLedger, execLedger);
490
537
  counted += 1;
@@ -1195,7 +1242,8 @@ async function computeClaudeGroundTruthBuckets({ rootDir = null } = {}) {
1195
1242
  continue;
1196
1243
  }
1197
1244
  const usage = obj?.message?.usage;
1198
- if (!usage || typeof usage !== "object") continue;
1245
+ const totals = readClaudeUsageTotals(usage);
1246
+ if (!totals || !hasNonZeroClaudeUsage(usage)) continue;
1199
1247
 
1200
1248
  const hash = claudeMessageDedupKey(obj);
1201
1249
  if (hash) {
@@ -1208,25 +1256,18 @@ async function computeClaudeGroundTruthBuckets({ rootDir = null } = {}) {
1208
1256
  const hourStart = ts ? toUtcHalfHourStart(ts) : null;
1209
1257
  if (!hourStart) continue;
1210
1258
 
1211
- const inputTok = Math.max(0, Number(usage.input_tokens || 0));
1212
- const cacheRead = Math.max(0, Number(usage.cache_read_input_tokens || 0));
1213
- const cacheCreate = Math.max(0, Number(usage.cache_creation_input_tokens || 0));
1214
- const outputTok = Math.max(0, Number(usage.output_tokens || 0));
1215
- const reasoningTok = Math.max(0, Number(usage.reasoning_output_tokens || 0));
1216
- const total = inputTok + cacheRead + cacheCreate + outputTok;
1217
-
1218
1259
  const key = `${model}|${hourStart}`;
1219
1260
  let acc = buckets.get(key);
1220
1261
  if (!acc) {
1221
1262
  acc = bucketAccumulator();
1222
1263
  buckets.set(key, acc);
1223
1264
  }
1224
- acc.input_tokens += inputTok;
1225
- acc.cached_input_tokens += cacheRead;
1226
- acc.cache_creation_input_tokens += cacheCreate;
1227
- acc.output_tokens += outputTok;
1228
- acc.reasoning_output_tokens += reasoningTok;
1229
- acc.total_tokens += total;
1265
+ acc.input_tokens += totals.input_tokens;
1266
+ acc.cached_input_tokens += totals.cached_input_tokens;
1267
+ acc.cache_creation_input_tokens += totals.cache_creation_input_tokens;
1268
+ acc.output_tokens += totals.output_tokens;
1269
+ acc.reasoning_output_tokens += totals.reasoning_output_tokens;
1270
+ acc.total_tokens += totals.total_tokens;
1230
1271
  }
1231
1272
  rl.close();
1232
1273
  stream.close?.();
@@ -6,6 +6,7 @@
6
6
  // 3. CURATED alias (e.g. "auto" -> "composer-1")
7
7
  // 4. CURATED fuzzy substring (e.g. "kiro-future-xyz" matches via "kiro")
8
8
  // 5. LiteLLM suffix-strip (gpt-5-codex-high-fast -> gpt-5-codex)
9
+ // 5b. LiteLLM provider-prefix strip (mimo-v2.5-pro -> openrouter/xiaomi/mimo-v2.5-pro)
9
10
  // 6. LiteLLM reverse substring (longest-key first)
10
11
  // 7. null (caller decides what to do — typically zero-pricing + negative cache)
11
12
 
@@ -169,6 +170,25 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
169
170
  }
170
171
  }
171
172
 
173
+ // 5b. LiteLLM provider-prefix strip. Queue rows store the bare model name
174
+ // (e.g. "mimo-v2.5-pro"), but LiteLLM keys are provider-qualified (e.g.
175
+ // "openrouter/xiaomi/mimo-v2.5-pro"), so the exact lookups above miss. Match
176
+ // any key whose path suffix equals the bare model. When several providers
177
+ // expose the same model, pick the lexicographically smallest key so the
178
+ // resolved price is deterministic and independent of JSON ordering. Runs
179
+ // AFTER curated alias/fuzzy so e.g. Cursor's "auto" still resolves to
180
+ // composer-1 rather than a LiteLLM "*/auto" entry.
181
+ if (litellm) {
182
+ const suffix = "/" + lower;
183
+ let best = null;
184
+ for (const key of Object.keys(litellm)) {
185
+ if (key.length > suffix.length && key.toLowerCase().endsWith(suffix)) {
186
+ if (best === null || key < best) best = key;
187
+ }
188
+ }
189
+ if (best) return { hit: true, source: "litellm:prefix-strip", value: litellm[best] };
190
+ }
191
+
172
192
  // 6. LiteLLM reverse substring (longest-key first)
173
193
  if (litellm) {
174
194
  const sorted = getSortedKeys(litellm);