@ipv9/tokentracker-cli 0.39.37 → 0.39.39

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 (79) hide show
  1. package/README.md +41 -9
  2. package/dashboard/dist/assets/{Card-DKmo-v8a.js → Card-BbRYRVXf.js} +1 -1
  3. package/dashboard/dist/assets/DashboardPage-DYKHv0m3.js +60 -0
  4. package/dashboard/dist/assets/{FadeIn-Cf30rPTW.js → FadeIn-CjAacJ5t.js} +1 -1
  5. package/dashboard/dist/assets/{IpCheckPage-D3CL8XP0.js → IpCheckPage-BGALK8Vy.js} +1 -1
  6. package/dashboard/dist/assets/{LimitsPage-BqST7DxD.js → LimitsPage-LkZGPYvg.js} +2 -2
  7. package/dashboard/dist/assets/{LocalOnlyNotice-CqHzPooT.js → LocalOnlyNotice-Dxo2vFMQ.js} +1 -1
  8. package/dashboard/dist/assets/PopoverPopup-DP9fwdtl.js +1 -0
  9. package/dashboard/dist/assets/Select-DPE53NmM.js +1 -0
  10. package/dashboard/dist/assets/SelectItemText-D69v77is.js +12 -0
  11. package/dashboard/dist/assets/SettingsPage-Dxzqpi02.js +1 -0
  12. package/dashboard/dist/assets/SkillsPage-Bo7e39AB.js +1 -0
  13. package/dashboard/dist/assets/WidgetsPage-HHcIr_Eq.js +1 -0
  14. package/dashboard/dist/assets/WrappedPage-C-_jLXXU.js +1 -0
  15. package/dashboard/dist/assets/{arrow-up-right-BJzUcPig.js → arrow-up-right-BJvJHUGj.js} +1 -1
  16. package/dashboard/dist/assets/{download-CsXweDLj.js → download-CnzY_EgN.js} +1 -1
  17. package/dashboard/dist/assets/format-1w-q5n2f.js +1 -0
  18. package/dashboard/dist/assets/main-1kxAVM3m.css +1 -0
  19. package/dashboard/dist/assets/main-3gBEI6Jl.js +694 -0
  20. package/dashboard/dist/assets/mock-data-CJOMGcL9.js +1 -0
  21. package/dashboard/dist/assets/use-limits-display-prefs-D1nT9-mr.js +1 -0
  22. package/dashboard/dist/assets/use-native-settings-Dh6NzqjY.js +1 -0
  23. package/dashboard/dist/assets/use-usage-limits-BXR6lzGf.js +1 -0
  24. package/dashboard/dist/assets/useCurrency-Hhs5jbWw.js +1 -0
  25. package/dashboard/dist/index.html +16 -28
  26. package/dashboard/dist/llms.txt +0 -2
  27. package/dashboard/dist/sitemap.xml +0 -12
  28. package/package.json +7 -4
  29. package/src/cli.js +4 -13
  30. package/src/commands/doctor.js +1 -3
  31. package/src/commands/init.js +63 -53
  32. package/src/commands/serve.js +2 -50
  33. package/src/commands/status.js +1 -55
  34. package/src/commands/sync.js +134 -227
  35. package/src/lib/{browser-auth.js → browser-open.js} +3 -149
  36. package/src/lib/claude-categorizer.js +204 -32
  37. package/src/lib/codex-context-breakdown.js +83 -7
  38. package/src/lib/codex-rollout-parser.js +52 -10
  39. package/src/lib/diagnostics.js +1 -49
  40. package/src/lib/doctor.js +12 -87
  41. package/src/lib/file-identity.js +69 -0
  42. package/src/lib/init-flow.js +6 -3
  43. package/src/lib/local-api.js +6 -364
  44. package/src/lib/pricing/curated-overrides.json +1 -1
  45. package/src/lib/pricing/seed-snapshot.json +1 -1
  46. package/src/lib/rollout.js +392 -144
  47. package/src/lib/runtime-config.js +0 -32
  48. package/src/lib/sqlite-reader.js +83 -8
  49. package/dashboard/dist/assets/ActivityHeatmap-DPZ_Aj6E.js +0 -42
  50. package/dashboard/dist/assets/DashboardPage-C9Q5HCDh.js +0 -19
  51. package/dashboard/dist/assets/DevicePage-DhQFtROo.js +0 -1
  52. package/dashboard/dist/assets/DialogTitle-DduJCLiL.js +0 -1
  53. package/dashboard/dist/assets/InsforgeUserHeaderControls-CDqYVWBQ.js +0 -1
  54. package/dashboard/dist/assets/LandingPage-9345o0vr.js +0 -4356
  55. package/dashboard/dist/assets/LeaderboardAvatar-DNORVh3n.js +0 -1
  56. package/dashboard/dist/assets/LeaderboardPage-DBsdCCzY.js +0 -6
  57. package/dashboard/dist/assets/LeaderboardProfileModal-BuYdBVh_.js +0 -72
  58. package/dashboard/dist/assets/LeaderboardProfilePage-D4Fe1LOk.js +0 -1
  59. package/dashboard/dist/assets/LoginPage-BmcybmeK.js +0 -1
  60. package/dashboard/dist/assets/PopoverPopup-CDL7kqy5.js +0 -1
  61. package/dashboard/dist/assets/Select-BkzhG41t.js +0 -1
  62. package/dashboard/dist/assets/SelectItemText-BmRzs9ud.js +0 -1
  63. package/dashboard/dist/assets/SettingsPage-gXQl_o6s.js +0 -1
  64. package/dashboard/dist/assets/SkillsPage-BRXYEsKm.js +0 -1
  65. package/dashboard/dist/assets/WidgetsPage-CLHLz2UW.js +0 -1
  66. package/dashboard/dist/assets/WrappedPage-p4ZFwQjO.js +0 -1
  67. package/dashboard/dist/assets/index-BeoRn2gJ.js +0 -2
  68. package/dashboard/dist/assets/info-CuLr7ZIa.js +0 -1
  69. package/dashboard/dist/assets/main-CPGI-smk.js +0 -1004
  70. package/dashboard/dist/assets/main-DAcSUW2t.css +0 -1
  71. package/dashboard/dist/assets/use-limits-display-prefs-DOB09__q.js +0 -1
  72. package/dashboard/dist/assets/use-native-settings-JSylS2xS.js +0 -1
  73. package/dashboard/dist/assets/use-spring-qjUEvMQX.js +0 -1
  74. package/dashboard/dist/assets/use-usage-limits-B0bzPxNR.js +0 -1
  75. package/dashboard/dist/assets/useCurrency-BRorShq7.js +0 -1
  76. package/dashboard/dist/assets/useOpenInteractionType-DPP-6ZsL.js +0 -12
  77. package/dashboard/dist/share.html +0 -140
  78. package/src/commands/device-login.js +0 -161
  79. package/src/lib/upload-throttle.js +0 -148
@@ -0,0 +1 @@
1
+ import{t as A}from"./format-1w-q5n2f.js";function ht(){if(typeof Intl>"u"||!Intl.DateTimeFormat)return null;try{return Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch{return null}}function kt(t=new Date){const e=t instanceof Date?t:new Date(t);if(!Number.isFinite(e.getTime()))return 0;const n=e.getTimezoneOffset();return Number.isFinite(n)?-n:0}function q(t){if(!Number.isFinite(t)||t===0)return"UTC";const e=t>=0?"+":"-",n=Math.abs(t),o=String(Math.floor(n/60)).padStart(2,"0"),a=String(n%60).padStart(2,"0");return`UTC${e}${o}:${a}`}function Dt({timeZone:t,offsetMinutes:e}={}){const n=q(e);return t&&t!=="UTC"?`${t} (${n})`:n}function bt({timeZone:t,offsetMinutes:e}={}){return Number.isFinite(e)?q(e):t||"UTC"}function wt({timeZone:t,offsetMinutes:e,date:n=new Date}={}){const o=n instanceof Date?n:new Date(n);if(!Number.isFinite(o.getTime()))return"";if(t&&typeof Intl<"u"&&Intl.DateTimeFormat)try{const s=new Intl.DateTimeFormat("en-CA",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(o).reduce((m,c)=>(c.type&&c.value&&(m[c.type]=c.value),m),{}),u=s.year,i=s.month,_=s.day;if(u&&i&&_)return`${u}-${i}-${_}`}catch{}if(typeof e=="number"&&Number.isFinite(e)){const a=new Date(o.getTime()+e*6e4);return G(a)}return Q(o)}function J({timeZone:t,offsetMinutes:e,date:n=new Date}={}){const o=n instanceof Date?n:new Date(n);if(!Number.isFinite(o.getTime()))return null;if(t&&typeof Intl<"u"&&Intl.DateTimeFormat)try{const s=new Intl.DateTimeFormat("en-CA",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(o).reduce((m,c)=>(c.type&&c.value&&(m[c.type]=c.value),m),{}),u=Number(s.year),i=Number(s.month),_=Number(s.day);if(Number.isFinite(u)&&Number.isFinite(i)&&Number.isFinite(_))return{year:u,month:i,day:_}}catch{}if(typeof e=="number"&&Number.isFinite(e)){const a=new Date(o.getTime()+e*6e4);return{year:a.getUTCFullYear(),month:a.getUTCMonth()+1,day:a.getUTCDate()}}return{year:o.getFullYear(),month:o.getMonth()+1,day:o.getDate()}}function yt({timeZone:t,offsetMinutes:e}={}){return t?`tz:${t}`:typeof e=="number"&&Number.isFinite(e)?`offset:${Math.trunc(e)}`:"utc"}function G(t){const e=t.getUTCFullYear(),n=String(t.getUTCMonth()+1).padStart(2,"0"),o=String(t.getUTCDate()).padStart(2,"0");return`${e}-${n}-${o}`}function Q(t){const e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),o=String(t.getDate()).padStart(2,"0");return`${e}-${n}-${o}`}function g(t){return new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate())).toISOString().slice(0,10)}function F(t){const e=t instanceof Date?t:new Date(t);if(!Number.isFinite(e.getTime()))return"";const n=e.getFullYear(),o=String(e.getMonth()+1).padStart(2,"0"),a=String(e.getDate()).padStart(2,"0");return`${n}-${o}-${a}`}function X(t){if(!t)return null;const e=String(t).trim(),n=e.split("-");if(n.length!==3)return null;const o=Number(n[0]),a=Number(n[1])-1,r=Number(n[2]);if(!Number.isFinite(o)||!Number.isFinite(a)||!Number.isFinite(r))return null;const s=new Date(Date.UTC(o,a,r));return Number.isFinite(s.getTime())&&g(s)===e?s:null}function Z(t){if(!t)return"";const e=t.year,n=String(t.month).padStart(2,"0"),o=String(t.day).padStart(2,"0");return!e||!n||!o?"":`${e}-${n}-${o}`}function tt(t,{timeZone:e,offsetMinutes:n,now:o}={}){const a=o instanceof Date&&Number.isFinite(o.getTime())?o:new Date,r=J({timeZone:e,offsetMinutes:n,date:a});if(!r){const i=F(new Date);return{from:i,to:i}}const s=Z(r),u=X(s);if(!u)return{from:s,to:s};if(t==="day")return{from:s,to:s};if(t==="24h"){const i=new Date(u);return i.setUTCDate(i.getUTCDate()-1),{from:g(i),to:s}}if(t==="week"){const i=new Date(u);return i.setUTCDate(i.getUTCDate()-6),{from:g(i),to:s}}if(t==="month"){const i=new Date(u);return i.setUTCDate(i.getUTCDate()-29),{from:g(i),to:s}}if(t==="total"){const i=new Date(Date.UTC(u.getUTCFullYear(),u.getUTCMonth()-23,1));return{from:g(i),to:s}}return tt("week")}function Y(t){if(typeof t!="string")return null;const e=/^(\d{4})-(\d{2})-(\d{2})$/.exec(t.trim());if(!e)return null;const n=Number(e[1]),o=Number(e[2])-1,a=Number(e[3]),r=new Date(Date.UTC(n,o,a));return Number.isFinite(r.getTime())&&g(r)===t.trim()?r:null}function v(t,e){return new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()+e))}function et(t,e){const n=Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate())-Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate());return Math.floor(n/864e5)}function L(t,e){if(!Array.isArray(t)||t.length===0)return 0;const n=t.length,o=(n-1)*e,a=Math.floor(o),r=o-a,s=t[a]??t[n-1],u=t[Math.min(n-1,a+1)]??t[n-1];return Math.round(s+(u-s)*r)}function nt(t){return t<=0?0:t>=4?4:t}function z({weeks:t=52,now:e,weekStartsOn:n="sun"}={}){const o=e instanceof Date&&Number.isFinite(e.getTime())?e:new Date,a=F(o),r=Y(a)||new Date(Date.UTC(o.getFullYear(),o.getMonth(),o.getDate())),s=n==="mon"?1:0,u=r.getUTCDay(),i=v(r,-((u-s+7)%7)),_=v(i,-7*(Math.max(1,t)-1));return{from:g(_),to:a}}function ot({dailyRows:t,weeks:e=52,to:n,weekStartsOn:o="sun"}={}){const a=Y(n)||new Date(Date.UTC(new Date().getFullYear(),new Date().getMonth(),new Date().getDate())),{from:r}=z({weeks:e,now:a,weekStartsOn:o}),s=Y(r)||v(a,-(e*7-1)),u=(()=>{const h=(s.getUTCDay()-(o==="mon"?1:0)+7)%7;return v(s,-h)})(),i=new Map;for(const l of Array.isArray(t)?t:[]){const d=typeof l?.day=="string"?l.day:null;if(!d)continue;const h=A(l?.conversation_count)??0,y=A(l?.billable_total_tokens??l?.total_tokens)??0;i.set(d,{value:Math.max(0,h),tokens:Math.max(0,y),models:l?.models||null})}const _=et(u,a)+1,m=Math.ceil(_/7),c=[];for(let l=0;l<_;l++){const d=v(u,l),h=g(d),y=i.get(h),D=y?y.value:0;D>0&&c.push(D)}c.sort((l,d)=>l-d);const p=L(c,.5),C=L(c,.75),w=L(c,.9);function S(l){return!l||l<=0?0:l<=p?1:l<=C?2:l<=w?3:4}const f=[];for(let l=0;l<m;l++){const d=[];for(let h=0;h<7;h++){const y=l*7+h,D=v(u,y);if(D.getTime()>a.getTime()){d.push(null);continue}const T=g(D),b=i.get(T),M=b?b.value:0,N=b?b.tokens:0,x=b?b.models:null;d.push({day:T,value:M,total_tokens:N,level:nt(S(M)),models:x})}f.push(d)}const k=f.length>e?f.slice(f.length-e):f;return{from:r,to:g(a),weeks:k,thresholds:{t1:p,t2:C,t3:w}}}function at({dailyRows:t,to:e}={}){const n=Y(e)||new Date(Date.UTC(new Date().getFullYear(),new Date().getMonth(),new Date().getDate())),o=new Map;for(const r of Array.isArray(t)?t:[]){const s=typeof r?.day=="string"?r.day:null;if(!s)continue;const u=A(r?.conversation_count??r?.billable_total_tokens??r?.total_tokens)??0;o.set(s,Math.max(0,u))}let a=0;for(let r=0;r<370;r++){const s=g(v(n,-r));if((o.get(s)??0)>0){a+=1;continue}break}return a}const P={},H="tokentracker",st=["tokentracker/tokentracker","spacedriveapp/spacedrive","acme/alpha","acme/beta","neo/nebula","signal/flux","matrix/terminal","orbit/atlas","lumen/core","delta/horizon"];function rt(){if(typeof window<"u"){const t=new URLSearchParams(window.location.search),e=String(t.get("mock")||"").toLowerCase();if(e==="0"||e==="false"||e==="off"||e==="no")return!1;if(e==="1"||e==="true"||e==="on"||e==="yes")return!0}if(typeof import.meta<"u"&&P){const t="".toLowerCase();if(t==="1"||t==="true")return!0}return!1}function ut(){if(typeof import.meta<"u"&&P){const t="".trim();if(t)return t;const e="".trim();if(e)return e}if(typeof window<"u"){const t=new URLSearchParams(window.location.search),e=String(t.get("mock_now")||"").trim();if(e)return e;const n=String(t.get("mock_today")||"").trim();if(n)return n}return""}function it(t){if(!t)return null;const e=String(t).trim();if(!e)return null;if(/^\d{4}-\d{2}-\d{2}$/.test(e)){const[o,a,r]=e.split("-").map(Number);if(!Number.isFinite(o)||!Number.isFinite(a)||!Number.isFinite(r))return null;const s=new Date(o,a-1,r,12,0,0);return Number.isFinite(s.getTime())?s:null}const n=new Date(e);return Number.isFinite(n.getTime())?n:null}function Mt(){return rt()?it(ut()):null}function ct(){if(typeof import.meta<"u"&&P){const t="".trim();if(t)return t}if(typeof window<"u"){const t=new URLSearchParams(window.location.search),e=String(t.get("mock_seed")||"").trim();if(e)return e}return H}function lt(){if(typeof import.meta<"u"&&P){const t="".trim(),e=Number(t);if(Number.isFinite(e)&&e>0)return Math.floor(e)}if(typeof window<"u"){const t=new URLSearchParams(window.location.search),e=String(t.get("mock_missing")||"").trim(),n=Number(e);if(Number.isFinite(n)&&n>0)return Math.floor(n)}return 0}function I(t){return(t==null?ct():String(t)).trim()||H}function R(t){let e=2166136261;for(let n=0;n<t.length;n+=1)e^=t.charCodeAt(n),e=Math.imul(e,16777619);return e>>>0}function U(t){if(typeof t!="string")return null;const e=/^(\d{4})-(\d{2})-(\d{2})$/.exec(t.trim());if(!e)return null;const n=Number(e[1]),o=Number(e[2])-1,a=Number(e[3]),r=new Date(Date.UTC(n,o,a));return Number.isFinite(r.getTime())&&g(r)===t.trim()?r:null}function K(t,e){return new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()+e))}function W(t,e){return new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()+e,1))}function _t(t){return`${t.getUTCFullYear()}-${String(t.getUTCMonth()+1).padStart(2,"0")}`}function $({from:t,to:e,seed:n}){const o=U(F(new Date))||new Date,a=U(t)||o,r=U(e)||a,s=[],u=I(n),i=Math.floor((Date.UTC(r.getUTCFullYear(),r.getUTCMonth(),r.getUTCDate())-Date.UTC(a.getUTCFullYear(),a.getUTCMonth(),a.getUTCDate()))/864e5)+1;for(let _=0;_<i;_+=1){const m=K(a,_),c=g(m),p=R(`${u}:${c}`),C=p%1e3/1e3,w=.6+.4*Math.sin(_/6*Math.PI*.5),S=m.getUTCDay()===0||m.getUTCDay()===6?.65:1,f=18e3+Math.round(12e3*C),k=Math.max(0,Math.round(f*w*S)),l=Math.round(k*.46),d=Math.round(k*.34),h=Math.round(k*.14),y=Math.max(0,k-l-d-h),D={};if(k>0){const T=["gpt-4o","claude-3.5-sonnet","gemini-1.5-pro","deepseek-coder"],b=1+p%3;let M=k;for(let N=0;N<b;N++){const x=T[(p+N)%T.length];if(N===b-1)D[x]=M;else{const j=Math.round(M*(.3+.4*((p+N*17)%10)/10));D[x]=j,M-=j}}}s.push({day:c,total_tokens:k,billable_total_tokens:k,input_tokens:l,output_tokens:d,cached_input_tokens:h,reasoning_output_tokens:y,conversation_count:1+p%5,models:D})}return s}function mt({day:t,seed:e}){const n=U(t)||U(F(new Date))||new Date,o=g(n),a=I(e),r=Array.from({length:48},(u,i)=>{const _=Math.floor(i/2),m=i%2===0?0:30,c=R(`${a}:${o}:${_}:${m}`),p=c%1e3/1e3,C=(_+m/60)/24,w=.4+.6*Math.sin(C*Math.PI*2),S=900+Math.round(700*p),f=Math.max(0,Math.round(S*w)),k=Math.round(f*.46),l=Math.round(f*.34),d=Math.round(f*.14),h=Math.max(0,f-k-l-d),y={};if(f>0){const D=["gpt-4o","claude-3.5-sonnet","gemini-1.5-pro","deepseek-coder"],T=1+c%2;let b=f;for(let M=0;M<T;M++){const N=D[(c+M)%D.length];if(M===T-1)y[N]=b;else{const x=Math.round(b*(.3+.4*((c+M*17)%10)/10));y[N]=x,b-=x}}}return{hour:`${o}T${String(_).padStart(2,"0")}:${String(m).padStart(2,"0")}:00`,total_tokens:f,billable_total_tokens:f,input_tokens:k,output_tokens:l,cached_input_tokens:d,reasoning_output_tokens:h,models:y}}),s=Math.max(0,Math.min(48,lt()));if(s>0){const u=Date.now(),i=r.map((c,p)=>{const C=Date.parse(c.hour);return Number.isFinite(C)&&C<=u?{index:p,ts:C}:null}).filter(Boolean),_=Math.max(0,i.length-s),m=i.slice(_);for(const c of m){const p=r[c.index];p&&(r[c.index]={...p,missing:!0})}}return r}function dt({months:t=24,to:e,seed:n}){const o=U(e)||U(F(new Date))||new Date,a=new Date(Date.UTC(o.getUTCFullYear(),o.getUTCMonth(),1)),r=I(n),s=[];for(let u=t-1;u>=0;u-=1){const i=W(a,-u),_=_t(i),m=R(`${r}:${_}`),c=m%1e3/1e3,p=.7+.3*Math.sin(s.length/6*Math.PI*.5),C=36e4+Math.round(2e5*c),w=Math.max(0,Math.round(C*p)),S=Math.round(w*.46),f=Math.round(w*.34),k=Math.round(w*.14),l=Math.max(0,w-S-f-k),d={};if(w>0){const h=["gpt-4o","claude-3.5-sonnet","gemini-1.5-pro","deepseek-coder"],y=1+m%3;let D=w;for(let T=0;T<y;T++){const b=h[(m+T)%h.length];if(T===y-1)d[b]=D;else{const M=Math.round(D*(.3+.4*((m+T*17)%10)/10));d[b]=M,D-=M}}}s.push({month:_,total_tokens:w,billable_total_tokens:w,input_tokens:S,output_tokens:f,cached_input_tokens:k,reasoning_output_tokens:l,models:d})}return s}function O(t){return t.reduce((e,n)=>(e.total_tokens+=Number(n.total_tokens||0),e.billable_total_tokens+=Number(n.billable_total_tokens||n.total_tokens||0),e.input_tokens+=Number(n.input_tokens||0),e.output_tokens+=Number(n.output_tokens||0),e.cached_input_tokens+=Number(n.cached_input_tokens||0),e.reasoning_output_tokens+=Number(n.reasoning_output_tokens||0),e.conversation_count+=Number(n.conversation_count||0),e),{total_tokens:0,billable_total_tokens:0,input_tokens:0,output_tokens:0,cached_input_tokens:0,reasoning_output_tokens:0,conversation_count:0})}function gt(t,e=1.75){const n=Number(t||0);return!Number.isFinite(n)||n<=0?"0.000000":(n*e/1e6).toFixed(6)}function E(t,e){const n=Number.isFinite(e)?e:0;return{total_tokens:Math.max(0,Math.round(t.total_tokens*n)),billable_total_tokens:Math.max(0,Math.round((t.billable_total_tokens??t.total_tokens)*n)),input_tokens:Math.max(0,Math.round(t.input_tokens*n)),output_tokens:Math.max(0,Math.round(t.output_tokens*n)),cached_input_tokens:Math.max(0,Math.round(t.cached_input_tokens*n)),reasoning_output_tokens:Math.max(0,Math.round(t.reasoning_output_tokens*n))}}function B(t){return{...t,total_cost_usd:gt(t.total_tokens)}}function Tt({from:t,to:e,seed:n}={}){const o=$({from:t,to:e,seed:n});return{from:t,to:e,data:o}}function Ut({day:t,seed:e}={}){const n=U(t)||new Date,o=g(n),a=mt({day:o,seed:e});return{day:o,data:a}}function Ct({months:t=24,to:e,seed:n}={}){const o=U(e)||U(F(new Date))||new Date,a=g(o),r=dt({months:t,to:a,seed:n}),s=W(new Date(Date.UTC(o.getUTCFullYear(),o.getUTCMonth(),1)),-(t-1));return{from:g(s),to:a,months:t,data:r}}function Nt({from:t,to:e,seed:n,rolling:o=!0}={}){const a=$({from:t,to:e,seed:n}),r=O(a),s=B(r),u=o?ft({to:e,seed:n}):null;return{from:t,to:e,days:a.length,totals:s,...u?{rolling:u}:{}}}function ft({to:t,seed:e}={}){const n=U(t)||U(F(new Date))||new Date,o=g(n),a=g(K(n,-6)),r=g(K(n,-29)),s=$({from:a,to:o,seed:e}),u=$({from:r,to:o,seed:e});return{last_7d:V({rows:s,from:a,to:o}),last_30d:V({rows:u,from:r,to:o})}}function V({rows:t,from:e,to:n}={}){const o=O(t||[]),a=(t||[]).filter(s=>Number(s.billable_total_tokens??s.total_tokens)>0).length,r=a>0?Math.floor(o.billable_total_tokens/a):0;return{from:e,to:n,totals:{billable_total_tokens:o.billable_total_tokens,conversation_count:o.conversation_count},active_days:a,avg_per_active_day:r}}function Ft({seed:t,limit:e=3}={}){const n=I(t),o=st.map((a,r)=>{const u=12e4+R(`${n}:${a}`)%9e5,i=r%4*4200,_=Math.max(0,u+i),m=Math.max(0,Math.round(_*.96));return{project_key:a,project_ref:`https://github.com/${a}`,total_tokens:String(_),billable_total_tokens:String(m)}}).sort((a,r)=>Number(r.billable_total_tokens)-Number(a.billable_total_tokens)).slice(0,Math.max(1,Math.min(10,Math.floor(Number(e)||3))));return{generated_at:new Date().toISOString(),entries:o}}function St({weeks:t=52,to:e,weekStartsOn:n="sun",seed:o}={}){const a=z({weeks:t,now:U(e)||U(F(new Date))||new Date,weekStartsOn:n}),r=$({from:a.from,to:a.to,seed:o});return{...ot({dailyRows:r,weeks:t,to:a.to,weekStartsOn:n}),week_starts_on:n,active_days:r.filter(u=>Number(u.billable_total_tokens??u.total_tokens)>0).length,streak_days:at({dailyRows:r,to:a.to})}}function xt({from:t,to:e,seed:n}={}){const o=$({from:t,to:e,seed:n}),a=O(o),s=[{source:"codex",weight:.7,models:[{model:"gpt-5.2-codex",model_id:"gpt-5.2-codex",weight:.2},{model:"unknown",model_id:"unknown",weight:.8}]},{source:"claude",weight:.2,models:[{model:"claude-3.5",model_id:"claude-3.5",weight:.3},{model:"unknown",model_id:"unknown",weight:.7}]},{source:"every-code",weight:.1,models:[{model:"unknown",model_id:"unknown",weight:1}]}].map(u=>{const i=E(a,u.weight),_=u.models.map(m=>{const c=E(i,m.weight);return{model:m.model,model_id:m.model_id||m.model,totals:B(c)}});return{source:u.source,totals:B(i),models:_}});return{from:t,to:e,days:o.length,sources:s,pricing:{model:"mock",pricing_mode:"add",source:"mock",effective_from:F(new Date),rates_per_million_usd:{input:"1.750000",cached_input:"0.175000",output:"14.000000",reasoning_output:"14.000000"}}}}function vt({from:t,to:e,source:n="claude"}={}){if(n==="codex")return{from:t,to:e,source:"codex",scope:"supported",totals:{input_tokens:142e4,cached_input_tokens:588e4,cache_creation_input_tokens:92e4,output_tokens:256e4,reasoning_output_tokens:61e4,total_tokens:1078e4},session_count:188,message_count:5436,tool_calls_breakdown:{categories:[{name:"Execution",calls:742,totals:{input_tokens:32e4,cached_input_tokens:104e4,cache_creation_input_tokens:11e4,output_tokens:46e4,reasoning_output_tokens:12e4,total_tokens:193e4},tools:[{name:"exec_command",calls:510,totals:{input_tokens:25e4,cached_input_tokens:82e4,cache_creation_input_tokens:8e4,output_tokens:37e4,reasoning_output_tokens:9e4,total_tokens:152e4}},{name:"write_stdin",calls:232,totals:{input_tokens:7e4,cached_input_tokens:22e4,cache_creation_input_tokens:3e4,output_tokens:9e4,reasoning_output_tokens:3e4,total_tokens:41e4}}]},{name:"Browser",calls:406,totals:{input_tokens:21e4,cached_input_tokens:88e4,cache_creation_input_tokens:9e4,output_tokens:42e4,reasoning_output_tokens:1e5,total_tokens:17e5},tools:[{name:"take_snapshot",calls:180,totals:{input_tokens:1e5,cached_input_tokens:4e5,cache_creation_input_tokens:4e4,output_tokens:18e4,reasoning_output_tokens:4e4,total_tokens:76e4}},{name:"click",calls:126,totals:{input_tokens:6e4,cached_input_tokens:25e4,cache_creation_input_tokens:3e4,output_tokens:12e4,reasoning_output_tokens:35e3,total_tokens:495e3}},{name:"evaluate_script",calls:100,totals:{input_tokens:5e4,cached_input_tokens:23e4,cache_creation_input_tokens:2e4,output_tokens:12e4,reasoning_output_tokens:25e3,total_tokens:445e3}}]}],tools_total:363e4,privacy:{includes_inputs:!1,note:"Aggregated tool names only; no tool arguments or outputs are included."}},exec_command_breakdown:{by_type:[{name:"test",calls:168,failures:14,duration_ms:482e3,max_duration_ms:18400,output_chars:45e4,output_lines:8600,totals:{input_tokens:9e4,cached_input_tokens:32e4,output_tokens:16e4,reasoning_output_tokens:42e3,total_tokens:612e3}},{name:"build",calls:116,failures:9,duration_ms:392e3,max_duration_ms:22100,output_chars:31e4,output_lines:5200,totals:{input_tokens:7e4,cached_input_tokens:24e4,output_tokens:13e4,reasoning_output_tokens:3e4,total_tokens:47e4}}],by_exit:[{name:"completed:0",calls:438,failures:0,duration_ms:104e4,max_duration_ms:22100,output_chars:83e4,output_lines:13100,totals:{input_tokens:22e4,cached_input_tokens:78e4,output_tokens:35e4,reasoning_output_tokens:82e3,total_tokens:1432e3}},{name:"completed:1",calls:72,failures:72,duration_ms:155e3,max_duration_ms:11800,output_chars:12e4,output_lines:2300,totals:{input_tokens:3e4,cached_input_tokens:11e4,output_tokens:55e3,reasoning_output_tokens:8e3,total_tokens:195e3}}]}};if(n!=="claude")return{from:t,to:e,source:n,scope:"unsupported",totals:{input_tokens:0,cached_input_tokens:0,cache_creation_input_tokens:0,output_tokens:0,reasoning_output_tokens:0,total_tokens:0},categories:["system_prefix","conversation_history","user_input","tool_calls","subagents","reasoning","assistant_response"].map(s=>({key:s,totals:{input_tokens:0,cached_input_tokens:0,cache_creation_input_tokens:0,output_tokens:0,reasoning_output_tokens:0,total_tokens:0},percent:0})),session_count:0,message_count:0};const o=[{key:"system_prefix",total:12896539},{key:"conversation_history",total:320207200},{key:"user_input",total:998025},{key:"tool_calls",total:4808739},{key:"subagents",total:1022828},{key:"reasoning",total:11073118},{key:"assistant_response",total:1289174}],a=o.reduce((s,u)=>s+u.total,0),r=o.map(({key:s,total:u})=>({key:s,totals:{input_tokens:s==="user_input"?u:0,cached_input_tokens:s==="conversation_history"?Math.round(u*.95):0,cache_creation_input_tokens:s==="system_prefix"?u:s==="conversation_history"?Math.round(u*.05):0,output_tokens:["tool_calls","subagents","reasoning","assistant_response"].includes(s)?u:0,reasoning_output_tokens:s==="reasoning"?u:0,total_tokens:u},percent:Number((u/a*100).toFixed(2))}));return{from:t,to:e,source:"claude",scope:"supported",totals:{input_tokens:998025,cached_input_tokens:Math.round(320207200*.95),cache_creation_input_tokens:12896539+Math.round(320207200*.05),output_tokens:18193859,reasoning_output_tokens:11073118,total_tokens:a},categories:r,session_count:312,message_count:12046}}export{yt as a,ot as b,at as c,wt as d,kt as e,g as f,z as g,ht as h,rt as i,Mt as j,tt as k,Dt as l,bt as m,Tt as n,Nt as o,xt as p,Ut as q,Ft as r,Ct as s,St as t,vt as u};
@@ -0,0 +1 @@
1
+ import{r as o,$ as I,a0 as h,a1 as C,a2 as R}from"./main-3gBEI6Jl.js";const d=["claude","codex","cursor","gemini","kimi","zai","kiro","copilot","antigravity"],Y={claude:"Claude",codex:"Codex",cursor:"Cursor",gemini:"Gemini",kimi:"Kimi",zai:"Z.AI",kiro:"Kiro",copilot:"GitHub Copilot",antigravity:"Antigravity"},P={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",zai:null,kiro:"/brand-logos/kiro.svg",copilot:"/brand-logos/copilot.svg",antigravity:"/brand-logos/antigravity.svg"},w="tt.limits.providerOrder",m="tt.limits.providerVisibility",S="tt.limits.displayMode",p="limitsDisplayMode",f=Object.freeze({USED:"used",REMAINING:"remaining"}),E=new Set(Object.values(f));function O(){if(typeof window>"u")return[...d];try{const n=window.localStorage.getItem(w);if(!n)return[...d];const a=JSON.parse(n);if(!Array.isArray(a))return[...d];const r=a.filter(c=>d.includes(c));for(const c of d)r.includes(c)||r.push(c);return r}catch{return[...d]}}function D(){const n=Object.fromEntries(d.map(a=>[a,!0]));if(typeof window>"u")return n;try{const a=window.localStorage.getItem(m);if(!a)return n;const r=JSON.parse(a);if(!r||typeof r!="object")return n;const c={...n};for(const u of d)typeof r[u]=="boolean"&&(c[u]=r[u]);return c}catch{return n}}function v(){if(typeof window>"u")return f.USED;try{const n=window.localStorage.getItem(S);return E.has(n)?n:f.USED}catch{return f.USED}}function T(){const[n,a]=o.useState(O),[r,c]=o.useState(D),[u,l]=o.useState(v),g=o.useCallback(t=>{E.has(t)&&(l(t),I()&&h(p,t))},[]);o.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(w,JSON.stringify(n))}catch{}},[n]),o.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(m,JSON.stringify(r))}catch{}},[r]),o.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(S,u)}catch{}},[u]),o.useEffect(()=>{if(!I())return;const t=C(e=>{const i=e?.[p];E.has(i)&&l(i)});return R(),t},[]),o.useEffect(()=>{if(typeof window>"u")return;const t=e=>{e.key===w&&a(O()),e.key===m&&c(D()),e.key===S&&l(v())};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[]);const k=o.useCallback(t=>{c(e=>({...e,[t]:!e[t]}))},[]),x=o.useCallback(t=>{a(e=>{const i=e.indexOf(t);if(i<=0)return e;const s=[...e];return[s[i-1],s[i]]=[s[i],s[i-1]],s})},[]),M=o.useCallback(t=>{a(e=>{const i=e.indexOf(t);if(i<0||i>=e.length-1)return e;const s=[...e];return[s[i],s[i+1]]=[s[i+1],s[i]],s})},[]),_=o.useCallback((t,e)=>{t!==e&&a(i=>{const s=i.indexOf(t),b=i.indexOf(e);if(s<0||b<0)return i;const y=[...i],[N]=y.splice(s,1);return y.splice(b,0,N),y})},[]),L=o.useCallback(()=>{a([...d]),c(Object.fromEntries(d.map(t=>[t,!0]))),g(f.USED)},[g]),A=o.useMemo(()=>n.filter(t=>r[t]!==!1),[n,r]);return{order:n,visibility:r,displayMode:u,setDisplayMode:g,visibleOrdered:A,toggle:k,moveUp:x,moveDown:M,moveToward:_,reset:L}}export{f as L,P as a,Y as b,T as u};
@@ -0,0 +1 @@
1
+ import{j as a,x,r as n,b0 as g,b1 as m,a1 as b,a2 as u,a0 as f,a$ as h}from"./main-3gBEI6Jl.js";import{C as y}from"./Card-BbRYRVXf.js";function k({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 p({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,k as T,p as a,w as b,S as u};
@@ -0,0 +1 @@
1
+ import{H as U,r as p,F as b}from"./main-3gBEI6Jl.js";import{i as y,n as M,o as H,p as L,q as j,r as z,s as B,t as _,u as C}from"./mock-data-CJOMGcL9.js";const f={usageSummary:"tokentracker-usage-summary",usageDaily:"tokentracker-usage-daily",usageHourly:"tokentracker-usage-hourly",usageMonthly:"tokentracker-usage-monthly",usageHeatmap:"tokentracker-usage-heatmap",usageModelBreakdown:"tokentracker-usage-model-breakdown",usageCategoryBreakdown:"tokentracker-usage-category-breakdown",projectUsageSummary:"tokentracker-project-usage-summary",userStatus:"tokentracker-user-status",localSync:"tokentracker-local-sync",usageLimits:"tokentracker-usage-limits"};async function m(t,e,a){const s=new URL(`/functions/${t}`,window.location.origin);if(e)for(const[c,i]of Object.entries(e))i!=null&&i!==""&&s.searchParams.set(c,String(i));const{accessToken:o,...r}=a||{},n=await fetch(s.toString(),{headers:{Accept:"application/json"},cache:"no-store",...r});if(!n.ok){const c=new Error(`Request failed with HTTP ${n.status}`);throw c.status=n.status,c}return n.json()}function d({timeZone:t,tzOffsetMinutes:e}={}){const a={},s=typeof t=="string"?t.trim():"";return s&&(a.tz=s),Number.isFinite(e)&&(a.tz_offset_minutes=String(Math.trunc(e))),a}function h({source:t,model:e}={}){const a={},s=typeof t=="string"?t.trim().toLowerCase():"";s&&(a.source=s);const o=typeof e=="string"?e.trim():"";return o&&(a.model=o),a}async function T({from:t,to:e,source:a,model:s,timeZone:o,tzOffsetMinutes:r,rolling:n=!1,accessToken:c}={}){if(y())return H({from:t,to:e,seed:c,rolling:n});const i=d({timeZone:o,tzOffsetMinutes:r}),l=h({source:a,model:s}),k=n?{rolling:"1"}:{};return m(f.usageSummary,{from:t,to:e,...l,...i,...k},{accessToken:c})}async function x({from:t,to:e,source:a,limit:s,timeZone:o,tzOffsetMinutes:r,accessToken:n}={}){if(y())return z({seed:n,limit:s});const c=d({timeZone:o,tzOffsetMinutes:r}),l={...h({source:a}),...c};return t&&(l.from=t),e&&(l.to=e),s!=null&&(l.limit=String(s)),m(f.projectUsageSummary,l)}async function A(t={}){if(y()){const e=new Date().toISOString();return{user_id:"local-user",created_at:e,pro:{active:!1,sources:[],expires_at:null,partial:!1,as_of:e},subscriptions:{partial:!1,as_of:e,items:[]}}}return m(f.userStatus)}async function $({signal:t}={}){const e=await U(),a=await fetch(`/functions/${f.localSync}`,{method:"POST",headers:{Accept:"application/json",...e},cache:"no-store",signal:t}),s=await a.json().catch(()=>({ok:!1,error:`Local sync request failed with HTTP ${a.status}`}));if(!a.ok||s?.ok===!1){const o=s?.error||s?.message||`Local sync request failed with HTTP ${a.status}`,r=new Error(o);throw r.status=a.status,r}return s}async function q({from:t,to:e,source:a,timeZone:s,tzOffsetMinutes:o,accessToken:r}={}){if(y())return L({from:t,to:e,seed:r});const n=d({timeZone:s,tzOffsetMinutes:o}),c=h({source:a});return m(f.usageModelBreakdown,{from:t,to:e,...c,...n},{accessToken:r})}async function R({from:t,to:e,source:a="claude",timeZone:s,tzOffsetMinutes:o}={}){if(y())return C({from:t,to:e,source:a});const r=d({timeZone:s,tzOffsetMinutes:o});return m(f.usageCategoryBreakdown,{from:t,to:e,source:a,...r})}async function F({from:t,to:e,source:a,model:s,timeZone:o,tzOffsetMinutes:r,accessToken:n}={}){if(y())return M({from:t,to:e,seed:n});const c=d({timeZone:o,tzOffsetMinutes:r}),i=h({source:a,model:s});return m(f.usageDaily,{from:t,to:e,...i,...c},{accessToken:n})}async function I({day:t,source:e,model:a,timeZone:s,tzOffsetMinutes:o,accessToken:r}={}){if(y())return j({day:t,seed:r});const n=d({timeZone:s,tzOffsetMinutes:o}),c=h({source:e,model:a}),i=t?{day:t,...c,...n}:{...c,...n};return m(f.usageHourly,i,{accessToken:r})}async function O({months:t,to:e,source:a,model:s,timeZone:o,tzOffsetMinutes:r,accessToken:n}={}){if(y())return B({months:t,to:e,seed:n});const c=d({timeZone:o,tzOffsetMinutes:r}),i=h({source:a,model:s});return m(f.usageMonthly,{...t?{months:String(t)}:{},...e?{to:e}:{},...i,...c},{accessToken:n})}async function P(t={}){const e=t?.refresh?{refresh:"1"}:void 0;return m(f.usageLimits,e)}async function v({weeks:t,to:e,weekStartsOn:a,source:s,model:o,timeZone:r,tzOffsetMinutes:n,accessToken:c}={}){if(y())return _({weeks:t,to:e,weekStartsOn:a,seed:c});const i=d({timeZone:r,tzOffsetMinutes:n}),l=h({source:s,model:o});return m(f.usageHeatmap,{weeks:String(t),to:e,week_starts_on:a,...l,...i},{accessToken:c})}function J(t){const e=!!t?.initialState,[a,s]=p.useState(()=>e?t?.initialState?.data??null:null),[o,r]=p.useState(()=>e?t?.initialState?.error??null:null),[n,c]=p.useState(!e),i=!!t?.initialRefresh,l=!!t?.publishToPreloadCache,k=p.useCallback((u,g)=>{!l||!u||typeof u!="object"||b(u,{source:g})},[l]),w=p.useCallback(async()=>{try{const u=await P({refresh:!0}),g=u&&typeof u=="object"?u:null;s(g),r(null),k(g,"manual-refresh")}catch(u){r(u?.message||String(u))}},[k]);return p.useEffect(()=>{if(e&&!i)return;let u=!1;return(async()=>{try{const g=await P(i?{refresh:!0}:{});if(u)return;const S=g&&typeof g=="object"?g:null;s(S),r(null),k(S,"page-load")}catch(g){if(u)return;r(g?.message||String(g))}finally{u||c(!1)}})(),()=>{u=!0}},[e,i,k]),{data:a,error:o,isLoading:n,refresh:w}}export{F as a,x as b,O as c,I as d,T as e,q as f,v as g,R as h,A as i,$ as t,J as u};
@@ -0,0 +1 @@
1
+ import{c as e,r as t,aZ as c,a_ as r,C as a}from"./main-3gBEI6Jl.js";const o=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]],C=e("info",o),n=Object.freeze({currency:a,rate:1,symbol:"$",rates:{...r},rateSource:"default",rateFetchedAt:null,setCurrency:()=>{}});function y(){return t.useContext(c)??n}export{C as I,y as u};
@@ -3,19 +3,15 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Token Tracker — Token Usage Tracker for AI Agent CLIs</title>
7
- <meta name="description" content="Automatically monitor token usage across Claude Code, Cursor, Codex, and more. Local-first, privacy-focused, and zero manual configuration." />
8
- <meta property="og:site_name" content="Token Tracker" />
9
- <meta property="og:title" content="Token Tracker — Token Usage Tracker for AI Agent CLIs" />
10
- <meta property="og:description" content="Automatically monitor token usage across Claude Code, Cursor, Codex, and more. Local-first, privacy-focused, and zero manual configuration." />
11
- <meta property="og:image" content="https://www.tokentracker.cc/dashboard-dark.png" />
6
+ <title>TokenTracker</title>
7
+ <meta name="description" content="See how many tokens your AI coding tools use locally." />
8
+ <meta property="og:site_name" content="TokenTracker" />
9
+ <meta property="og:title" content="TokenTracker" />
10
+ <meta property="og:description" content="See how many tokens your AI coding tools use locally." />
12
11
  <meta property="og:type" content="website" />
13
- <meta property="og:url" content="https://www.tokentracker.cc/" />
14
- <meta name="twitter:card" content="summary_large_image" />
15
- <meta name="twitter:title" content="Token Tracker Token Usage Tracker for AI Agent CLIs" />
16
- <meta name="twitter:description" content="Automatically monitor token usage across Claude Code, Cursor, Codex, and more. Local-first, privacy-focused, and zero manual configuration." />
17
- <meta name="twitter:image" content="https://www.tokentracker.cc/dashboard-dark.png" />
18
- <link rel="canonical" href="https://www.tokentracker.cc/" />
12
+ <meta name="twitter:card" content="summary" />
13
+ <meta name="twitter:title" content="TokenTracker" />
14
+ <meta name="twitter:description" content="See how many tokens your AI coding tools use — locally." />
19
15
  <link rel="icon" href="/icon.svg" type="image/svg+xml" />
20
16
  <link rel="icon" href="/favicon.ico" sizes="any" />
21
17
  <link rel="icon" href="/favicon-32.png" sizes="32x32" type="image/png" />
@@ -146,12 +142,6 @@
146
142
  "name": "Home",
147
143
  "item": "https://www.tokentracker.cc/"
148
144
  },
149
- {
150
- "@type": "ListItem",
151
- "position": 2,
152
- "name": "Leaderboard",
153
- "item": "https://www.tokentracker.cc/leaderboard"
154
- },
155
145
  {
156
146
  "@type": "ListItem",
157
147
  "position": 3,
@@ -227,7 +217,7 @@
227
217
  "name": "Does Token Tracker work offline?",
228
218
  "acceptedAnswer": {
229
219
  "@type": "Answer",
230
- "text": "Yes. Token Tracker is local-first. All parsing, aggregation, and dashboard rendering happens on your machine. Cloud sync is optional and used only for the public leaderboard."
220
+ "text": "Yes. Token Tracker is local-only. All parsing, aggregation, and dashboard rendering happens on your machine, and nothing is uploaded anywhere."
231
221
  }
232
222
  }
233
223
  ]
@@ -235,8 +225,8 @@
235
225
  ]
236
226
  }
237
227
  </script>
238
- <script type="module" crossorigin src="/assets/main-CPGI-smk.js"></script>
239
- <link rel="stylesheet" crossorigin href="/assets/main-DAcSUW2t.css">
228
+ <script type="module" crossorigin src="/assets/main-3gBEI6Jl.js"></script>
229
+ <link rel="stylesheet" crossorigin href="/assets/main-1kxAVM3m.css">
240
230
  </head>
241
231
  <body>
242
232
  <main class="aeo-seed-content" aria-label="Token Tracker AI-readable summary">
@@ -261,8 +251,8 @@
261
251
  <h2>What you get after installing</h2>
262
252
  <p>
263
253
  Token Tracker runs entirely on your machine. After <code>npx tokentracker-cli</code>, a local
264
- dashboard at <code>http://localhost:7680</code> exposes the following views — none of them require an
265
- account, and your data never leaves your device unless you opt into cloud sync.
254
+ dashboard at <code>http://localhost:7680</code> exposes the following views — your data stays on
255
+ your device. There is no upload path.
266
256
  </p>
267
257
  <ul>
268
258
  <li><strong>Unified usage dashboard</strong> — input, output, cached, and cache-creation tokens across all 13 AI coding tools.</li>
@@ -273,7 +263,7 @@
273
263
  <li><strong>Skills browser</strong> — discover and install agent skills from the open ecosystem.</li>
274
264
  <li><strong>macOS menu bar app & desktop widget</strong> — at-a-glance usage without opening the dashboard.</li>
275
265
  </ul>
276
- <p><em>Privacy by design</em>: token counts only, never prompts or conversation content. Local-first; cloud sync is optional and powers only the public leaderboard.</p>
266
+ <p><em>Privacy by design</em>: token counts only, never prompts or conversation content. Local-only nothing is uploaded.</p>
277
267
 
278
268
  <h2>Supported AI coding agent CLIs</h2>
279
269
  <ul>
@@ -311,8 +301,7 @@
311
301
  (<code>src/lib/rollout.js</code>) normalizes 9 distinct log formats into a unified schema: half-hour
312
302
  UTC buckets keyed by <code>(source, model, hour_start)</code>. Aggregated buckets are written to
313
303
  <code>queue.jsonl</code> and served from a local HTTP API on port 7680. The React dashboard reads
314
- from those endpoints; cloud sync (optional) uploads bucketed counts to InsForge for the public
315
- leaderboard.
304
+ from those endpoints. Nothing is uploaded anywhere.
316
305
  </p>
317
306
 
318
307
  <h2>Frequently asked questions</h2>
@@ -332,12 +321,11 @@
332
321
  <p>Input, output, cached, and cache-creation tokens per model, project, and time window. It also surfaces rate limits and subscription status for Claude Pro, ChatGPT plans, Cursor, Gemini, Kiro, and Antigravity.</p>
333
322
 
334
323
  <h3>Does Token Tracker work offline?</h3>
335
- <p>Yes. Token Tracker is local-first. All parsing, aggregation, and dashboard rendering happen on your machine. Cloud sync is optional and powers only the public leaderboard.</p>
324
+ <p>Yes. Token Tracker is local-only. All parsing, aggregation, and dashboard rendering happen on your machine, and nothing is uploaded.</p>
336
325
 
337
326
  <h2>Resources</h2>
338
327
  <ul>
339
328
  <li><a href="https://www.tokentracker.cc/">Homepage and install instructions</a></li>
340
- <li><a href="https://www.tokentracker.cc/leaderboard">Public token usage leaderboard</a></li>
341
329
  <li><a href="https://www.tokentracker.cc/ip-check">Claude IP region checker</a></li>
342
330
  <li><a href="https://www.npmjs.com/package/tokentracker-cli">npm: tokentracker-cli</a></li>
343
331
  <li><a href="https://github.com/mm7894215/TokenTracker">GitHub: mm7894215/TokenTracker</a></li>
@@ -9,7 +9,6 @@ Kiro, Hermes Agent, GitHub Copilot, Kimi Code, oh-my-pi (omp), and CodeBuddy.
9
9
 
10
10
  ## Priority URLs
11
11
  - [Homepage](https://www.tokentracker.cc/)
12
- - [Leaderboard](https://www.tokentracker.cc/leaderboard)
13
12
  - [Claude IP check](https://www.tokentracker.cc/ip-check)
14
13
 
15
14
  ## Post-install (local) views
@@ -47,7 +46,6 @@ on the user's own machine — they are not public URLs:
47
46
  - [feed.xml](https://www.tokentracker.cc/feed.xml)
48
47
  - [robots.txt](https://www.tokentracker.cc/robots.txt)
49
48
  - Data principle: token counts only, never prompts or conversation content.
50
- - Local-first: all parsing, aggregation, and dashboard rendering happen on-device. Cloud sync is optional.
51
49
 
52
50
  ## Contact
53
51
  - [GitHub issues](https://github.com/mm7894215/TokenTracker/issues)
@@ -6,22 +6,10 @@
6
6
  <changefreq>weekly</changefreq>
7
7
  <priority>1.0</priority>
8
8
  </url>
9
- <url>
10
- <loc>https://www.tokentracker.cc/leaderboard</loc>
11
- <lastmod>2026-05-07</lastmod>
12
- <changefreq>daily</changefreq>
13
- <priority>0.9</priority>
14
- </url>
15
9
  <url>
16
10
  <loc>https://www.tokentracker.cc/ip-check</loc>
17
11
  <lastmod>2026-05-07</lastmod>
18
12
  <changefreq>monthly</changefreq>
19
13
  <priority>0.5</priority>
20
14
  </url>
21
- <url>
22
- <loc>https://www.tokentracker.cc/share</loc>
23
- <lastmod>2026-05-07</lastmod>
24
- <changefreq>weekly</changefreq>
25
- <priority>0.4</priority>
26
- </url>
27
15
  </urlset>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ipv9/tokentracker-cli",
3
- "version": "0.39.37",
3
+ "version": "0.39.39",
4
4
  "description": "Local-first token and cost dashboard for 22 AI coding tools, including Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Copilot, Antigravity, Zed, and Goose.",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -23,13 +23,17 @@
23
23
  "access": "public"
24
24
  },
25
25
  "scripts": {
26
- "ci:local": "npm run dashboard:build && npm test && npm run validate:copy && npm run validate:ui-hardcode && npm run validate:guardrails && node --test test/architecture-guardrails.test.js",
26
+ "ci:local": "npm run dashboard:build && npm --prefix dashboard run test && npm test && npm run validate:copy && npm run validate:ui-hardcode && npm run validate:guardrails && npm run docs:openwiki:check && node --test test/architecture-guardrails.test.js",
27
27
  "copy:pull": "node scripts/copy-sync.cjs pull",
28
28
  "copy:push": "node scripts/copy-sync.cjs push",
29
29
  "dashboard:build": "npm --prefix dashboard run build",
30
30
  "dashboard:dev": "npm --prefix dashboard run dev",
31
31
  "dashboard:open": "bash scripts/open-dashboard.sh",
32
32
  "dashboard:preview": "npm --prefix dashboard run preview",
33
+ "docs:openwiki:check": "node scripts/openwiki-check-facts.cjs",
34
+ "docs:openwiki:extract": "node scripts/openwiki-extract-facts.cjs",
35
+ "docs:openwiki:update": "bash scripts/docs-update.sh",
36
+ "docs:openwiki:verify": "bash scripts/openwiki-verify.sh",
33
37
  "dev:shim": "node scripts/dev-bin-shim.cjs",
34
38
  "audit:tokens": "node scripts/audit-token-correctness.cjs",
35
39
  "graph:auto-index": "node scripts/graph/auto-index.cjs",
@@ -76,8 +80,7 @@
76
80
  "zed",
77
81
  "goose",
78
82
  "local-first",
79
- "rate-limits",
80
- "leaderboard"
83
+ "rate-limits"
81
84
  ],
82
85
  "license": "MIT",
83
86
  "engines": {
package/src/cli.js CHANGED
@@ -5,7 +5,6 @@ const { cmdDiagnostics } = require("./commands/diagnostics");
5
5
  const { cmdDoctor } = require("./commands/doctor");
6
6
  const { cmdUninstall } = require("./commands/uninstall");
7
7
  const { cmdServe } = require("./commands/serve");
8
- const { cmdDeviceLogin } = require("./commands/device-login");
9
8
  const { cmdWrapped } = require("./commands/wrapped");
10
9
 
11
10
  async function run(argv) {
@@ -44,9 +43,6 @@ async function run(argv) {
44
43
  case "uninstall":
45
44
  await cmdUninstall(rest);
46
45
  return;
47
- case "device-login":
48
- await cmdDeviceLogin(rest);
49
- return;
50
46
  case "wrapped":
51
47
  await cmdWrapped(rest);
52
48
  return;
@@ -64,29 +60,24 @@ function printHelp() {
64
60
  "Usage:",
65
61
  " npx --yes @ipv9/tokentracker-cli Start local dashboard",
66
62
  " npx --yes @ipv9/tokentracker-cli [--debug] serve [--port 7680] [--open] [--no-open] [--sync]",
67
- " npx --yes @ipv9/tokentracker-cli [--debug] init [--yes] [--dry-run] [--no-open] [--link-code <code>]",
63
+ " npx --yes @ipv9/tokentracker-cli [--debug] init [--yes] [--dry-run] [--no-open]",
68
64
  " npx --yes @ipv9/tokentracker-cli [--debug] sync [--auto] [--drain] [--from-openclaw]",
69
65
  " npx --yes @ipv9/tokentracker-cli [--debug] status [--probe-keychain] [--probe-keychain-details]",
70
66
  " npx --yes @ipv9/tokentracker-cli [--debug] diagnostics [--out diagnostics.json]",
71
- " npx --yes @ipv9/tokentracker-cli [--debug] doctor [--json] [--out doctor.json] [--base-url <url>]",
67
+ " npx --yes @ipv9/tokentracker-cli [--debug] doctor [--json] [--out doctor.json]",
72
68
  " npx --yes @ipv9/tokentracker-cli [--debug] uninstall [--purge]",
73
- " npx --yes @ipv9/tokentracker-cli [--debug] device-login [--json] [--base-url <url>]",
74
69
  " npx --yes @ipv9/tokentracker-cli [--debug] wrapped [--year 2026] [--json]",
75
70
  "",
76
71
  "Notes:",
77
- " - init: consent first, local setup next, browser sign-in last.",
72
+ " - init: consent first, then local setup. This build is local-only.",
78
73
  " - --yes skips the consent menu (non-interactive safe).",
79
74
  " - --dry-run previews changes without writing files.",
80
- " - optional: --link-code <code> skips browser login when provided by Dashboard.",
81
75
  " - Every Code notify installs when ~/.code/config.toml exists.",
82
76
  " - OpenClaw hook auto-links when OpenClaw is installed (requires gateway restart).",
83
- " - auto sync waits for a device token.",
84
- " - optional: --dashboard-url for hosted landing.",
85
77
  " - serve prints the local dashboard URL; pass --open to ask the OS to open a browser.",
86
- " - sync parses ~/.codex/sessions/**/rollout-*.jsonl and ~/.code/sessions/**/rollout-*.jsonl, then uploads token deltas only when cloud credentials are configured.",
78
+ " - sync parses ~/.codex/sessions/**/rollout-*.jsonl and ~/.code/sessions/**/rollout-*.jsonl into the local queue. Nothing is uploaded.",
87
79
  " - --from-openclaw marks sync runs triggered by OpenClaw hooks.",
88
80
  " - --debug shows original backend errors.",
89
- " - device-login pairs a headless CLI / SSH session with a browser sign-in (15-min code).",
90
81
  "",
91
82
  ].join("\n"),
92
83
  );
@@ -17,7 +17,6 @@ async function cmdDoctor(argv = []) {
17
17
  const config =
18
18
  configStatus.status === "ok" && isPlainObject(configStatus.value) ? configStatus.value : {};
19
19
  const runtime = resolveRuntimeConfig({
20
- cli: { baseUrl: opts.baseUrl },
21
20
  config,
22
21
  env: process.env,
23
22
  });
@@ -62,12 +61,11 @@ async function cmdDoctor(argv = []) {
62
61
  }
63
62
 
64
63
  function parseArgs(argv) {
65
- const out = { json: false, out: null, baseUrl: null };
64
+ const out = { json: false, out: null };
66
65
  for (let i = 0; i < argv.length; i += 1) {
67
66
  const arg = argv[i];
68
67
  if (arg === "--json") out.json = true;
69
68
  else if (arg === "--out") out.out = argv[++i] || null;
70
- else if (arg === "--base-url") out.baseUrl = argv[++i] || null;
71
69
  else throw new Error(`Unknown option: ${arg}`);
72
70
  }
73
71
  return out;
@@ -57,7 +57,6 @@ const {
57
57
  resolvePiAgentDir,
58
58
  piAgentDirCollidesWithOmp,
59
59
  } = require("../lib/rollout");
60
- const { resolveRuntimeConfig, DEFAULT_BASE_URL } = require("../lib/runtime-config");
61
60
  const {
62
61
  BOLD,
63
62
  DIM,
@@ -86,7 +85,6 @@ const ASCII_LOGO = [
86
85
  ].join("\n");
87
86
 
88
87
  const DIVIDER = "----------------------------------------------";
89
- const DEFAULT_DASHBOARD_URL = "https://www.tokentracker.cc";
90
88
 
91
89
  // Single source of truth for the welcome screen's provider count + sample list.
92
90
  // Keep in sync with the supported-tools table in CLAUDE.md.
@@ -120,11 +118,6 @@ async function cmdInit(argv) {
120
118
  const linkCodeStatePath = path.join(trackerDir, "link_code_state.json");
121
119
 
122
120
  const existingConfig = await readJson(configPath);
123
- const runtime = resolveRuntimeConfig({
124
- cli: { baseUrl: opts.baseUrl, dashboardUrl: opts.dashboardUrl },
125
- config: existingConfig || {},
126
- env: process.env,
127
- });
128
121
  const notifyPath = path.join(binDir, "notify.cjs");
129
122
  const appDir = path.join(trackerDir, "app");
130
123
  const trackerBinPath = path.join(appDir, "bin", "tracker.js");
@@ -156,7 +149,6 @@ async function cmdInit(argv) {
156
149
  home,
157
150
  trackerDir,
158
151
  notifyPath,
159
- runtime,
160
152
  });
161
153
  renderLocalReport({ summary: preview.summary, isDryRun: true });
162
154
  renderAccountNotLinked({ context: "dry-run" });
@@ -178,7 +170,6 @@ async function cmdInit(argv) {
178
170
  notifyPath,
179
171
  appDir,
180
172
  trackerBinPath,
181
- runtime,
182
173
  existingConfig,
183
174
  });
184
175
  } catch (err) {
@@ -280,20 +271,10 @@ function renderAccountNotLinked({ context } = {}) {
280
271
  renderLocalSuccess();
281
272
  }
282
273
 
283
- function shouldUseBrowserAuth({ deviceToken, opts }) {
284
- if (deviceToken) return false;
285
- if (opts.noAuth) return false;
286
- if (opts.linkCode) return false;
287
- if (opts.email || opts.password) return false;
288
- return true;
289
- }
290
-
291
- async function buildDryRunSummary({ opts, home, trackerDir, notifyPath, runtime }) {
292
- const deviceToken = runtime?.deviceToken || null;
293
- const pendingBrowserAuth = shouldUseBrowserAuth({ deviceToken, opts });
274
+ async function buildDryRunSummary({ opts, home, trackerDir, notifyPath }) {
294
275
  const context = buildIntegrationTargets({ home, trackerDir, notifyPath });
295
276
  const summary = await previewIntegrations({ context });
296
- return { summary, pendingBrowserAuth, deviceToken };
277
+ return { summary };
297
278
  }
298
279
 
299
280
  async function runSetup({
@@ -307,15 +288,12 @@ async function runSetup({
307
288
  notifyPath,
308
289
  appDir,
309
290
  trackerBinPath,
310
- runtime,
311
291
  existingConfig,
312
292
  }) {
313
293
  await ensureDir(trackerDir);
314
294
  await ensureDir(binDir);
315
- let deviceToken = runtime?.deviceToken || null;
316
295
  let deviceId = existingConfig?.deviceId || null;
317
296
  const installedAt = existingConfig?.installedAt || new Date().toISOString();
318
- let pendingBrowserAuth = false;
319
297
 
320
298
  await installLocalTrackerApp({ appDir });
321
299
 
@@ -323,13 +301,33 @@ async function runSetup({
323
301
  existingConfig && typeof existingConfig === "object" && !Array.isArray(existingConfig)
324
302
  ? existingConfig
325
303
  : {};
304
+ // Scrub cloud credentials and endpoints on upgrade. Spreading the previous
305
+ // config forward kept a live InsForge bearer token in config.json, and this
306
+ // branch also removed the `status` / `diagnostics` lines that used to reveal
307
+ // it — so the user is told "local-only, nothing is uploaded" while a valid
308
+ // credential sits on disk with nothing left to report or rotate it.
309
+ const {
310
+ deviceToken: _removedDeviceToken,
311
+ baseUrl: _removedBaseUrl,
312
+ dashboardUrl: _removedDashboardUrl,
313
+ ...carriedConfig
314
+ } = existingPlainConfig;
326
315
  const config = {
327
- ...existingPlainConfig,
316
+ ...carriedConfig,
328
317
  installedAt,
329
- baseUrl: opts.baseUrl || existingPlainConfig.baseUrl || DEFAULT_BASE_URL,
330
318
  };
331
- if (opts.dashboardUrl) {
332
- config.dashboardUrl = opts.dashboardUrl;
319
+
320
+ // Files written by the removed cloud paths. The only code that ever deleted
321
+ // relay-cookies.json was clearRelayCookies(), which went with the auth proxy,
322
+ // so an upgrading machine would otherwise keep a refresh token forever.
323
+ for (const stale of ["relay-cookies.json", "upload.throttle.json", "auto.retry.json"]) {
324
+ try {
325
+ await fs.unlink(path.join(trackerDir, stale));
326
+ } catch (error) {
327
+ if (error?.code !== "ENOENT") {
328
+ process.stderr.write(`[tokentracker] could not remove ${stale}: ${error.message}\n`);
329
+ }
330
+ }
333
331
  }
334
332
 
335
333
  await writeJson(configPath, config);
@@ -350,8 +348,6 @@ async function runSetup({
350
348
 
351
349
  return {
352
350
  summary,
353
- pendingBrowserAuth,
354
- deviceToken,
355
351
  deviceId,
356
352
  installedAt,
357
353
  };
@@ -803,8 +799,6 @@ function arraysEqual(a, b) {
803
799
 
804
800
  function parseArgs(argv) {
805
801
  const out = {
806
- baseUrl: null,
807
- dashboardUrl: null,
808
802
  email: null,
809
803
  password: null,
810
804
  deviceName: null,
@@ -817,13 +811,33 @@ function parseArgs(argv) {
817
811
 
818
812
  for (let i = 0; i < argv.length; i++) {
819
813
  const a = argv[i];
820
- if (a === "--base-url") out.baseUrl = argv[++i] || null;
821
- else if (a === "--dashboard-url") out.dashboardUrl = argv[++i] || null;
822
- else if (a === "--email") out.email = argv[++i] || null;
823
- else if (a === "--password") out.password = argv[++i] || null;
824
- else if (a === "--device-name") out.deviceName = argv[++i] || null;
825
- else if (a === "--link-code") out.linkCode = argv[++i] || null;
826
- else if (a === "--no-auth") out.noAuth = true;
814
+ // Cloud sign-in flags (--email/--password/--device-name/--link-code/
815
+ // --no-auth) are gone: there is no account to sign into. Accept and
816
+ // ignore --no-auth so existing scripts and hooks do not start failing
817
+ // with "Unknown option"; the others were only ever used with it.
818
+ if (a === "--no-auth") {
819
+ // Accepted and ignored: it was the "skip sign-in" opt-out, and there is
820
+ // no sign-in left to skip. Silently tolerated so existing hooks and
821
+ // scripts do not start failing with "Unknown option".
822
+ out.noAuth = true;
823
+ } else if (a === "--email" || a === "--password" || a === "--device-name") {
824
+ i += 1;
825
+ } else if (
826
+ a.startsWith("--email=") ||
827
+ a.startsWith("--password=") ||
828
+ a.startsWith("--device-name=")
829
+ ) {
830
+ // Swallow the `=` form too. Falling through raised
831
+ // `Unknown option: --password=hunter2`, and bin/tracker.js prints the
832
+ // stack — putting the secret on stderr and into any captured log.
833
+ } else if (a === "--link-code" || a.startsWith("--link-code=")) {
834
+ // Fail loudly. Unlike the others this was documented standalone, so a
835
+ // user pairing a device would otherwise see "Setup complete!" and
836
+ // believe the code was honored.
837
+ throw new Error(
838
+ "--link-code is no longer supported: TokenTracker is local-only and has no device pairing.",
839
+ );
840
+ }
827
841
  else if (a === "--no-open") out.noOpen = true;
828
842
  else if (a === "--yes") out.yes = true;
829
843
  else if (a === "--dry-run") out.dryRun = true;
@@ -883,7 +897,6 @@ const codexOriginalPath = ${JSON.stringify(originalPath)};
883
897
  const codeOriginalPath = ${JSON.stringify(path.join(trackerDir, "code_notify_original.json"))};
884
898
  const trackerBinPath = ${JSON.stringify(trackerBinPath)};
885
899
  const depsMarkerPath = path.join(trackerDir, 'app', 'bin', 'tracker.js');
886
- const configPath = path.join(trackerDir, 'config.json');
887
900
  const fallbackPkg = ${JSON.stringify(fallbackPkg)};
888
901
  const selfPath = path.resolve(__filename);
889
902
  const home = os.homedir();
@@ -919,20 +932,17 @@ if (debugEnabled) {
919
932
  }
920
933
 
921
934
  // Throttle spawn: at most once per 20 seconds.
935
+ // Parsing local logs and uploading to the cloud are two separate decisions.
936
+ // This hook makes only the first one. It runs whether or not a device token
937
+ // exists, because a local-only install still needs its queue refreshed when a
938
+ // session ends. Upload stays gated on a device token inside sync itself, so
939
+ // no credential here means local parse happens and nothing is transmitted.
922
940
  try {
923
- const throttlePath = path.join(trackerDir, 'sync.throttle');
924
- let deviceToken = process.env.TOKENTRACKER_DEVICE_TOKEN || null;
925
- if (!deviceToken) {
926
- try {
927
- const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
928
- if (cfg && typeof cfg.deviceToken === 'string') deviceToken = cfg.deviceToken;
929
- } catch (_) {}
930
- }
931
- const canSync = Boolean(deviceToken && deviceToken.length > 0);
932
- const now = Date.now();
933
- let last = 0;
934
- try { last = Number(fs.readFileSync(throttlePath, 'utf8')) || 0; } catch (_) {}
935
- if (canSync && now - last > 20_000) {
941
+ const throttlePath = path.join(trackerDir, 'sync.throttle');
942
+ const now = Date.now();
943
+ let last = 0;
944
+ try { last = Number(fs.readFileSync(throttlePath, 'utf8')) || 0; } catch (_) {}
945
+ if (now - last > 20_000) {
936
946
  try { fs.writeFileSync(throttlePath, String(now), 'utf8'); } catch (_) {}
937
947
  const hasLocalRuntime = fs.existsSync(trackerBinPath);
938
948
  const hasLocalDeps = fs.existsSync(depsMarkerPath);