tokentracker-cli 1.4.7 → 1.4.10

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 (154) hide show
  1. package/README.de.md +2 -2
  2. package/README.ja.md +2 -2
  3. package/README.ko.md +2 -2
  4. package/README.md +11 -3
  5. package/README.zh-CN.md +3 -3
  6. package/bin/tracker.js +6 -0
  7. package/dashboard/dist/assets/{AchievementBadge-DF8q1x6R.js → AchievementBadge-BTqlXUP1.js} +1 -1
  8. package/dashboard/dist/assets/{AchievementsPage-B8Dg5myo.js → AchievementsPage-DbKUhaY-.js} +2 -2
  9. package/dashboard/dist/assets/{AchievementsSection-oPPiyP3y.js → AchievementsSection-El1F-OC7.js} +1 -1
  10. package/dashboard/dist/assets/{BadgeDetailModal-PneMW4B4.js → BadgeDetailModal--8PySLzP.js} +1 -1
  11. package/dashboard/dist/assets/{BotAnimated-GOlSvEiz.js → BotAnimated-DBcOS4vh.js} +1 -1
  12. package/dashboard/dist/assets/{Card-BT5G0GE0.js → Card-CeGs8TaY.js} +1 -1
  13. package/dashboard/dist/assets/{CloudCheckoutPage-BNeL5Muo.js → CloudCheckoutPage-DCg3VVAi.js} +1 -1
  14. package/dashboard/dist/assets/CloudPage-CmeFDrmk.js +2 -0
  15. package/dashboard/dist/assets/{CommandPalette-CiMSYHNK.js → CommandPalette-CrSNoZW9.js} +1 -1
  16. package/dashboard/dist/assets/{CommunityStatsModal-fK3BE9CW.js → CommunityStatsModal-ynB4j0Qb.js} +1 -1
  17. package/dashboard/dist/assets/{ConfirmModal-CrUlEHyZ.js → ConfirmModal-DDTUf-My.js} +1 -1
  18. package/dashboard/dist/assets/{Controls-CbjSwq6k.js → Controls-DXsJjEU1.js} +1 -1
  19. package/dashboard/dist/assets/DashboardPage-fr4sgbLB.js +1 -0
  20. package/dashboard/dist/assets/{DateRangePopover-E-2Z6imh.js → DateRangePopover-gTiPISL_.js} +1 -1
  21. package/dashboard/dist/assets/DevicePage-DvHVjCCH.js +1 -0
  22. package/dashboard/dist/assets/{DialogClose-xJKE22Y6.js → DialogClose-CXwouQe4.js} +1 -1
  23. package/dashboard/dist/assets/DialogDescription-BzbzETUC.js +1 -0
  24. package/dashboard/dist/assets/{DialogTitle-f8vnnH7z.js → DialogTitle-Cd_k-Nyw.js} +1 -1
  25. package/dashboard/dist/assets/{ElectricLogo-Ci9WZfHK.js → ElectricLogo-C4vUNTCI.js} +1 -1
  26. package/dashboard/dist/assets/{FadeIn-BXqtyAmn.js → FadeIn-CGM5RBFy.js} +1 -1
  27. package/dashboard/dist/assets/{HeaderGithubStar-BV5SYJMi.js → HeaderGithubStar-el5-DhVb.js} +1 -1
  28. package/dashboard/dist/assets/{HoverTooltip-D3gpW3Jh.js → HoverTooltip-C7YaWwJx.js} +1 -1
  29. package/dashboard/dist/assets/{IpCheckPage-G7fIWXpL.js → IpCheckPage-EKhbH9hJ.js} +1 -1
  30. package/dashboard/dist/assets/LandingPage-DJYOv1AM.js +2 -0
  31. package/dashboard/dist/assets/{LeaderboardPage-D2_wXazL.js → LeaderboardPage-CwzJxA8p.js} +2 -2
  32. package/dashboard/dist/assets/{LeaderboardProBadge-3xJ-mH2v.js → LeaderboardProBadge-DYLp9pJY.js} +1 -1
  33. package/dashboard/dist/assets/{LeaderboardProfileModal-23pH6Ud7.js → LeaderboardProfileModal-DqD2h9dD.js} +3 -3
  34. package/dashboard/dist/assets/{LeaderboardProfilePage-DSezSR58.js → LeaderboardProfilePage-CarYvpYe.js} +1 -1
  35. package/dashboard/dist/assets/LimitsPage-4T0BpfYS.js +6 -0
  36. package/dashboard/dist/assets/{LocalOnlyNotice-Dsk-d0Hr.js → LocalOnlyNotice-CAR-mqy0.js} +1 -1
  37. package/dashboard/dist/assets/{LoginCard-C7dugIIn.js → LoginCard-Czq-UgAU.js} +1 -1
  38. package/dashboard/dist/assets/{LoginModal-B7RuLsTF.js → LoginModal-D4uxfzZb.js} +1 -1
  39. package/dashboard/dist/assets/{LoginPage-u32vJQN9.js → LoginPage-Bg3BG0Wv.js} +1 -1
  40. package/dashboard/dist/assets/{PetPage-Dbc975oU.js → PetPage-BgwBxRyx.js} +2 -2
  41. package/dashboard/dist/assets/{PopoverPopup-D-yECvH9.js → PopoverPopup-k3o_Jp2b.js} +1 -1
  42. package/dashboard/dist/assets/{ProviderIcon-CHtZiJUN.js → ProviderIcon-Bbw6MUSf.js} +1 -1
  43. package/dashboard/dist/assets/{ResetPasswordPage-ctuIsNk0.js → ResetPasswordPage-DIKPVu5K.js} +1 -1
  44. package/dashboard/dist/assets/{Select-DN4Kq_R1.js → Select-kA0dV5Qw.js} +1 -1
  45. package/dashboard/dist/assets/{SelectItemText-C-HmeD8L.js → SelectItemText-D4YCIn1x.js} +1 -1
  46. package/dashboard/dist/assets/{SelfHostPage-BZOL3JMt.js → SelfHostPage-C3Y9qF3P.js} +1 -1
  47. package/dashboard/dist/assets/{ServiceStatusPage-C0sDYa8c.js → ServiceStatusPage-o1G3SJhV.js} +1 -1
  48. package/dashboard/dist/assets/{SessionsPage-l0-FkcJB.js → SessionsPage-CA-efxhU.js} +3 -3
  49. package/dashboard/dist/assets/SettingsPage-Mr84vxQL.js +5 -0
  50. package/dashboard/dist/assets/{SkillsPage-Cz6G9d3o.js → SkillsPage-BtRhLSpz.js} +1 -1
  51. package/dashboard/dist/assets/TokenGalaxy-CcFjCCuC.js +4426 -0
  52. package/dashboard/dist/assets/TooltipPopup-6aTx3jDf.js +1 -0
  53. package/dashboard/dist/assets/TrendMonitor-DoywXr8h.js +19 -0
  54. package/dashboard/dist/assets/{WidgetsPage-BKo3DBxT.js → WidgetsPage-BuGSzvR0.js} +1 -1
  55. package/dashboard/dist/assets/{WrappedPage-Blm3aPO5.js → WrappedPage-Bm6X-SUZ.js} +1 -1
  56. package/dashboard/dist/assets/__vite-browser-external-C9a2vCBN.css +1 -0
  57. package/dashboard/dist/assets/__vite-browser-external-ClVJNQu3.js +45 -0
  58. package/dashboard/dist/assets/{agent-logos-DMtDEx7_.js → agent-logos-Sc8Sejgx.js} +1 -1
  59. package/dashboard/dist/assets/{arrow-left-Bo9y7VzF.js → arrow-left-Cm1LG2i_.js} +1 -1
  60. package/dashboard/dist/assets/{arrow-right-BLMU8Y5U.js → arrow-right-WP3kIwiy.js} +1 -1
  61. package/dashboard/dist/assets/{arrow-up-right-B4hXO4sc.js → arrow-up-right-CjyprVMj.js} +1 -1
  62. package/dashboard/dist/assets/{calendar-days-D9HaEtgo.js → calendar-days-Bm88-Gcb.js} +1 -1
  63. package/dashboard/dist/assets/{check-BJNGKpX8.js → check-CzIxFg0I.js} +1 -1
  64. package/dashboard/dist/assets/{chevron-down-CoGC_RLK.js → chevron-down-bfGK8629.js} +1 -1
  65. package/dashboard/dist/assets/{cloud-checkout-Du5FoHqV.js → cloud-checkout-C9DFQ7yM.js} +1 -1
  66. package/dashboard/dist/assets/{composite-BfIUPpl8.js → composite-CdExmgTx.js} +1 -1
  67. package/dashboard/dist/assets/copy-CPPdujn2.js +1 -0
  68. package/dashboard/dist/assets/{download-C1K_v0C0.js → download-Cl6_dKeK.js} +1 -1
  69. package/dashboard/dist/assets/{external-link-D-xHhfj5.js → external-link-B0r6Wo4e.js} +1 -1
  70. package/dashboard/dist/assets/{flame-DXD6uvhM.js → flame-wEezvzXx.js} +1 -1
  71. package/dashboard/dist/assets/{format-tokens-B9riGvNj.js → format-tokens-vWpBpKpM.js} +1 -1
  72. package/dashboard/dist/assets/{gift-LPR-nD0J.js → gift-BFjblRqU.js} +1 -1
  73. package/dashboard/dist/assets/host-mode-Bc_iTzHk.js +1 -0
  74. package/dashboard/dist/assets/{icons-DLdtLqEt.js → icons-DX3dCNJP.js} +1 -1
  75. package/dashboard/dist/assets/{index-CXdJ7zMN.js → index-D-TnqdBv.js} +1 -1
  76. package/dashboard/dist/assets/{index-CC3cXeBa.js → index-D1Zdrpom.js} +1 -1
  77. package/dashboard/dist/assets/index-DBF1bJlm.js +502 -0
  78. package/dashboard/dist/assets/{index-BL11MboX.js → index-p8Gt3NFg.js} +1 -1
  79. package/dashboard/dist/assets/{info-tnXKCyuK.js → info-Cqi971MA.js} +1 -1
  80. package/dashboard/dist/assets/{link-2-DOk9Tcg1.js → link-2-79sm__cT.js} +1 -1
  81. package/dashboard/dist/assets/{loader-circle-DIl96Mw-.js → loader-circle-CDoVfOUb.js} +1 -1
  82. package/dashboard/dist/assets/local-usage-transfer-EcXnuwCj.js +1 -0
  83. package/dashboard/dist/assets/{modifiers.esm-2H_1taJ6.js → modifiers.esm-v5_BKSmY.js} +1 -1
  84. package/dashboard/dist/assets/module-C2SOSUuR.js +3 -0
  85. package/dashboard/dist/assets/{monitor-smartphone-tnp-03hs.js → monitor-smartphone-CO67DffO.js} +1 -1
  86. package/dashboard/dist/assets/{play-BGMuGTdN.js → play-CbvuGtc3.js} +1 -1
  87. package/dashboard/dist/assets/{plus-XETAaErq.js → plus-jc2gXlRM.js} +1 -1
  88. package/dashboard/dist/assets/{provider-display-BYNT4zPc.js → provider-display-BK3EbRLM.js} +1 -1
  89. package/dashboard/dist/assets/{react-OW7HALVO.js → react-DMs4C4qS.js} +1 -1
  90. package/dashboard/dist/assets/{safePolygon-CAfS8-ct.js → safePolygon-cY9H0GBN.js} +1 -1
  91. package/dashboard/dist/assets/{search-B7ZJhoau.js → search-fqTJoYYA.js} +1 -1
  92. package/dashboard/dist/assets/{server-CWrP7mtL.js → server-jpBtvTvr.js} +1 -1
  93. package/dashboard/dist/assets/{shield-check-ip5IQEK3.js → shield-check-DQiBlJbN.js} +1 -1
  94. package/dashboard/dist/assets/{skills-api-CpyoNTeB.js → skills-api-CN2r_rmt.js} +1 -1
  95. package/dashboard/dist/assets/{sortable.esm-YvzWzPDu.js → sortable.esm-DrS52yOK.js} +1 -1
  96. package/dashboard/dist/assets/{store-DRK6-JBg.js → store-K2Mk6aMn.js} +1 -1
  97. package/dashboard/dist/assets/{terminal-DQRoJSvh.js → terminal-Q4SruAlh.js} +1 -1
  98. package/dashboard/dist/assets/{trash-8h_26atF.js → trash-C1Ob-g8E.js} +1 -1
  99. package/dashboard/dist/assets/{use-community-stats-DYlhIXsp.js → use-community-stats-C-Gxpp9D.js} +1 -1
  100. package/dashboard/dist/assets/{use-latest-request-guard-AxJF9SEJ.js → use-latest-request-guard-DK-A4bBp.js} +1 -1
  101. package/dashboard/dist/assets/use-limits-display-prefs-ZauQzxE_.js +1 -0
  102. package/dashboard/dist/assets/{use-native-settings-BQgB6_PB.js → use-native-settings-BZLEIUK8.js} +1 -1
  103. package/dashboard/dist/assets/{use-ordered-list-ogi7Okzf.js → use-ordered-list-De_Mcm_H.js} +1 -1
  104. package/dashboard/dist/assets/use-reduced-motion-DatTtp2N.js +1 -0
  105. package/dashboard/dist/assets/{use-transform-BQqoo1et.js → use-transform-XCDD5IrX.js} +1 -1
  106. package/dashboard/dist/assets/use-usage-limits-BncGDhoF.js +1 -0
  107. package/dashboard/dist/assets/{useCurrency-Bv9g8mpq.js → useCurrency-DXPclxBT.js} +1 -1
  108. package/dashboard/dist/assets/{usePositioner-CQiZGknc.js → usePositioner-C-E1npCy.js} +1 -1
  109. package/dashboard/dist/assets/{useTokenFormat-BUVirY0k.js → useTokenFormat-B70xhy9u.js} +1 -1
  110. package/dashboard/dist/assets/{zap-DR3VD0yY.js → zap-D7gRgGCj.js} +1 -1
  111. package/dashboard/dist/index.html +5 -5
  112. package/dashboard/dist/ip-check.html +2 -2
  113. package/dashboard/dist/leaderboard.html +2 -2
  114. package/dashboard/dist/privacy.html +8 -2
  115. package/dashboard/dist/share.html +2 -2
  116. package/package.json +4 -3
  117. package/src/cli.js +10 -0
  118. package/src/commands/sync.js +18 -3
  119. package/src/lib/codex-rollout-parser.js +29 -2
  120. package/src/lib/codex-service-tier.js +13 -0
  121. package/src/lib/commandcode-limits.js +27 -11
  122. package/src/lib/grok-limits.js +1 -0
  123. package/src/lib/local-api.js +122 -11
  124. package/src/lib/local-usage-transfer.js +271 -0
  125. package/src/lib/opencode-go-limits.js +17 -6
  126. package/src/lib/pricing/curated-overrides.json +24 -0
  127. package/src/lib/pricing/index.js +50 -39
  128. package/src/lib/pricing/seed-snapshot.json +1 -1
  129. package/src/lib/provider-api-key-store.js +110 -0
  130. package/src/lib/rollout.js +27 -3
  131. package/src/lib/sentry.js +231 -0
  132. package/src/lib/session-analytics.js +15 -2
  133. package/src/lib/static-server.js +15 -2
  134. package/src/lib/usage-limits.js +74 -24
  135. package/src/lib/workbuddy-ipc.js +191 -0
  136. package/src/lib/workbuddy-limits.js +564 -0
  137. package/dashboard/dist/assets/CloudPage-26bhEYqI.js +0 -2
  138. package/dashboard/dist/assets/DashboardPage-CP_PMMga.js +0 -1
  139. package/dashboard/dist/assets/DevicePage-B0fLd80M.js +0 -1
  140. package/dashboard/dist/assets/DialogDescription-CXDxWzGi.js +0 -1
  141. package/dashboard/dist/assets/LandingPage-BQNOdAkR.js +0 -2
  142. package/dashboard/dist/assets/LimitsPage-BLJsmVVy.js +0 -9
  143. package/dashboard/dist/assets/SettingsPage-CrczfZiZ.js +0 -5
  144. package/dashboard/dist/assets/TokenGalaxy-B5IGaLQK.js +0 -4426
  145. package/dashboard/dist/assets/TooltipPopup-B6FqLWO7.js +0 -1
  146. package/dashboard/dist/assets/TrendMonitor-B4kJUwmF.js +0 -19
  147. package/dashboard/dist/assets/__vite-browser-external--HDUIUZr.js +0 -45
  148. package/dashboard/dist/assets/__vite-browser-external-B0m8a6Te.css +0 -1
  149. package/dashboard/dist/assets/host-mode-C2K0MMOs.js +0 -1
  150. package/dashboard/dist/assets/module-DOCYJaxl.js +0 -3
  151. package/dashboard/dist/assets/use-limits-display-prefs-DOZzuTwo.js +0 -1
  152. package/dashboard/dist/assets/use-reduced-motion-D5PHT_kx.js +0 -1
  153. package/dashboard/dist/assets/use-session-efficiency-pref-DP3hu3Rp.js +0 -1
  154. package/dashboard/dist/assets/use-usage-limits-DAf6O4aZ.js +0 -1
package/src/cli.js CHANGED
@@ -10,6 +10,16 @@ const { cmdWrapped } = require("./commands/wrapped");
10
10
  const { cmdSessions } = require("./commands/sessions");
11
11
 
12
12
  async function run(argv) {
13
+ // Best-effort Sentry init (no-op without DSN / when telemetry opted out).
14
+ try {
15
+ const { initSentry } = require("./lib/sentry");
16
+ const { resolveTrackerPaths } = require("./lib/tracker-paths");
17
+ const { trackerDir } = await resolveTrackerPaths().catch(() => ({ trackerDir: null }));
18
+ await initSentry({ trackerDir });
19
+ } catch {
20
+ /* never block CLI startup on telemetry */
21
+ }
22
+
13
23
  const [command, ...rest] = argv;
14
24
 
15
25
  // No args → launch dashboard
@@ -202,6 +202,18 @@ const {
202
202
  scanRootDirState,
203
203
  } = require("../lib/scan-roots");
204
204
  const { resolveRuntimeConfig, isLegacyInsforgeBaseUrl } = require("../lib/runtime-config");
205
+ const { resolveDeviceTokenPresence } = require("../lib/cloud-device-token");
206
+
207
+ function formatSyncUploadSummaryLine({ runtimeDeviceToken, uploadResult, tokenPresentOnDisk }) {
208
+ if (runtimeDeviceToken) {
209
+ return `- Uploaded: ${uploadResult.inserted} inserted, ${uploadResult.skipped} skipped`;
210
+ }
211
+ if (tokenPresentOnDisk) {
212
+ return "- Uploaded: skipped (handled by local server; device token is set)";
213
+ }
214
+ return "- Uploaded: skipped (no device token)";
215
+ }
216
+
205
217
  const { extractTokenCount } = require("../lib/codex-rollout-parser");
206
218
  const {
207
219
  consumeUsageDelta,
@@ -3763,9 +3775,11 @@ async function cmdSync(argv, context = {}) {
3763
3775
  "Sync finished:",
3764
3776
  `- Parsed files: ${totalParsed}`,
3765
3777
  `- New 30-min buckets queued: ${totalBuckets}`,
3766
- runtime.deviceToken
3767
- ? `- Uploaded: ${uploadResult.inserted} inserted, ${uploadResult.skipped} skipped`
3768
- : "- Uploaded: skipped (no device token)",
3778
+ formatSyncUploadSummaryLine({
3779
+ runtimeDeviceToken: runtime.deviceToken,
3780
+ uploadResult,
3781
+ tokenPresentOnDisk: resolveDeviceTokenPresence({ trackerDir, config, runtime }).set,
3782
+ }),
3769
3783
  runtime.deviceToken && pendingBytes > 0 && !opts.drain
3770
3784
  ? `- Remaining: ${formatBytes(pendingBytes)} pending (run sync again, or use --drain)`
3771
3785
  : null,
@@ -3900,6 +3914,7 @@ function recordCodexColdScanAudit(
3900
3914
  }
3901
3915
 
3902
3916
  module.exports = {
3917
+ formatSyncUploadSummaryLine,
3903
3918
  cmdSync,
3904
3919
  acquireSyncLock,
3905
3920
  readQueueBatch,
@@ -30,7 +30,7 @@ const {
30
30
  currentCodexModel,
31
31
  snapshotCodexModelAttributionState,
32
32
  } = require("./codex-model-attribution");
33
- const { readCodexServiceTier, isPriorityServiceTier } = require("./codex-service-tier");
33
+ const { readCodexServiceTier, isPriorityServiceTier, isUltrafastServiceTier } = require("./codex-service-tier");
34
34
  const {
35
35
  createUsageRecordState,
36
36
  extractTokenUsageRecord,
@@ -731,16 +731,27 @@ async function parseCodexRolloutFile(filePath, {
731
731
  priority_long_context_cache_creation_input_tokens: 0,
732
732
  priority_long_context_output_tokens: 0,
733
733
  priority_long_context_reasoning_output_tokens: 0,
734
+ ultrafast_input_tokens: 0,
735
+ ultrafast_cached_input_tokens: 0,
736
+ ultrafast_cache_creation_input_tokens: 0,
737
+ ultrafast_output_tokens: 0,
738
+ ultrafast_reasoning_output_tokens: 0,
739
+ ultrafast_long_context_input_tokens: 0,
740
+ ultrafast_long_context_cached_input_tokens: 0,
741
+ ultrafast_long_context_cache_creation_input_tokens: 0,
742
+ ultrafast_long_context_output_tokens: 0,
743
+ ultrafast_long_context_reasoning_output_tokens: 0,
734
744
  usage_events: 0,
735
745
  rerouted_usage_events: 0,
736
746
  long_context_usage_events: 0,
737
747
  priority_usage_events: 0,
748
+ ultrafast_usage_events: 0,
738
749
  selected_models: new Set(),
739
750
  reroute_reasons: new Set(),
740
751
  };
741
752
  byModel.set(effectiveModel, row);
742
753
  }
743
- const subsetKeys = Object.keys(row).filter((key) => /^(?:long_context_|priority_)|^(?:usage_events|rerouted_usage_events)$/.test(key));
754
+ const subsetKeys = Object.keys(row).filter((key) => /^(?:long_context_|priority_|ultrafast_)|^(?:usage_events|rerouted_usage_events)$/.test(key));
744
755
  const before = Object.fromEntries(subsetKeys.map((key) => [key, row[key]]));
745
756
  addInto(row, delta);
746
757
  row.usage_events += 1;
@@ -787,6 +798,22 @@ async function parseCodexRolloutFile(filePath, {
787
798
  row.priority_long_context_reasoning_output_tokens += delta.reasoning_output_tokens;
788
799
  }
789
800
  }
801
+ if (isUltrafastServiceTier(attribution.serviceTier)) {
802
+ row.ultrafast_usage_events += 1;
803
+ row.ultrafast_input_tokens += delta.input_tokens;
804
+ row.ultrafast_cached_input_tokens += delta.cached_input_tokens;
805
+ row.ultrafast_cache_creation_input_tokens += delta.cache_creation_input_tokens;
806
+ row.ultrafast_output_tokens += delta.output_tokens;
807
+ row.ultrafast_reasoning_output_tokens += delta.reasoning_output_tokens;
808
+ if (isLongContext) {
809
+ row.ultrafast_long_context_input_tokens += delta.input_tokens;
810
+ row.ultrafast_long_context_cached_input_tokens += delta.cached_input_tokens;
811
+ row.ultrafast_long_context_cache_creation_input_tokens +=
812
+ delta.cache_creation_input_tokens;
813
+ row.ultrafast_long_context_output_tokens += delta.output_tokens;
814
+ row.ultrafast_long_context_reasoning_output_tokens += delta.reasoning_output_tokens;
815
+ }
816
+ }
790
817
  return { ...delta, ...Object.fromEntries(subsetKeys.map((key) => [key, row[key] - before[key]])),
791
818
  selected_models: attribution.selectedModel ? [attribution.selectedModel] : [],
792
819
  reroute_reasons: attribution.rerouteReason ? [attribution.rerouteReason] : [],
@@ -25,6 +25,13 @@ const CODEX_SERVICE_TIER_MARKER = '"thread_settings_applied"';
25
25
  // does not silently fall back to Standard.
26
26
  const PRIORITY_SERVICE_TIERS = new Set(["priority", "fast"]);
27
27
 
28
+ // Ultrafast is a distinct paid speed tier (service_tier "ultrafast") for
29
+ // gpt-6-astra and gpt-6.1-sol. It is NOT Fast: Fast uses priority_* subsets and
30
+ // priority_multiplier (2x); Ultrafast uses ultrafast_* subsets and
31
+ // ultrafast_multiplier (6x). Mixing them into the priority columns would
32
+ // understate Ultrafast at 2x.
33
+ const ULTRAFAST_SERVICE_TIERS = new Set(["ultrafast"]);
34
+
28
35
  function readCodexServiceTier(obj) {
29
36
  if (!obj || obj.type !== "event_msg") return null;
30
37
  const payload = obj.payload;
@@ -41,9 +48,15 @@ function isPriorityServiceTier(tier) {
41
48
  return typeof tier === "string" && PRIORITY_SERVICE_TIERS.has(tier);
42
49
  }
43
50
 
51
+ function isUltrafastServiceTier(tier) {
52
+ return typeof tier === "string" && ULTRAFAST_SERVICE_TIERS.has(tier);
53
+ }
54
+
44
55
  module.exports = {
45
56
  CODEX_SERVICE_TIER_MARKER,
46
57
  PRIORITY_SERVICE_TIERS,
58
+ ULTRAFAST_SERVICE_TIERS,
47
59
  readCodexServiceTier,
48
60
  isPriorityServiceTier,
61
+ isUltrafastServiceTier,
49
62
  };
@@ -12,16 +12,20 @@
12
12
  // (resetAt is epoch milliseconds) plus a `credits` balance object; the
13
13
  // subscription payload carries `{ data: { planId, status, ... } }`.
14
14
  //
15
- // Auth resolution mirrors the CLI: `COMMAND_CODE_API_KEY` wins, otherwise the
16
- // `apiKey` field of `~/.commandcode/auth.json` (prod; written 0600 by
17
- // `cmd login`, never edited by hand). No key -> `{ configured: false }` so the
18
- // Limits panel hides the section instead of erroring.
15
+ // Auth resolution: `COMMAND_CODE_API_KEY` wins, then a key saved from
16
+ // TokenTracker's Limits panel (provider-api-key-store.js), then the `apiKey`
17
+ // field of `~/.commandcode/auth.json` (prod; written 0600 by `cmd login`,
18
+ // never edited by hand). The panel store is preferred over the CLI file
19
+ // because saving a key there is an explicit user action, while the CLI file
20
+ // can be a stale session. No key -> `{ configured: false }` so the Limits
21
+ // panel hides the section instead of erroring.
19
22
  //
20
23
  // The API exposes 5h + weekly rolling caps; extra pay-as-you-go credits
21
24
  // bypass the caps server-side, so the reported used/cap needs no adjustment.
22
25
 
23
26
  const fs = require("node:fs");
24
27
  const path = require("node:path");
28
+ const { readStoredApiKey } = require("./provider-api-key-store");
25
29
 
26
30
  const COMMANDCODE_API_BASE_URL = "https://api.commandcode.ai";
27
31
  const COMMANDCODE_API_KEY_ENV = "COMMAND_CODE_API_KEY";
@@ -57,25 +61,33 @@ function resolveCommandcodeAuthPath({ home, env = process.env } = {}) {
57
61
  return path.join(dir, COMMANDCODE_AUTH_DIRNAME, COMMANDCODE_AUTH_FILENAME);
58
62
  }
59
63
 
60
- function readCommandcodeApiKey({ home, env = process.env } = {}) {
64
+ function readCommandcodeCredential({ home, trackerDir, env = process.env } = {}) {
61
65
  const fromEnv =
62
66
  env && typeof env === "object" ? env[COMMANDCODE_API_KEY_ENV] : null;
63
- if (isNonEmptyString(fromEnv)) return fromEnv.trim();
67
+ if (isNonEmptyString(fromEnv)) return { apiKey: fromEnv.trim(), source: "env" };
68
+ const stored = readStoredApiKey({ provider: "command-code", trackerDir, home });
69
+ if (stored) return { apiKey: stored, source: "stored" };
64
70
  const authPath = resolveCommandcodeAuthPath({ home, env });
65
- if (!authPath) return null;
71
+ if (!authPath) return { apiKey: null, source: "none" };
66
72
  let raw;
67
73
  try {
68
74
  raw = fs.readFileSync(authPath, "utf8");
69
75
  } catch {
70
- return null;
76
+ return { apiKey: null, source: "none" };
71
77
  }
72
78
  let parsed;
73
79
  try {
74
80
  parsed = JSON.parse(raw);
75
81
  } catch {
76
- return null;
82
+ return { apiKey: null, source: "none" };
77
83
  }
78
- return isNonEmptyString(parsed?.apiKey) ? parsed.apiKey.trim() : null;
84
+ return isNonEmptyString(parsed?.apiKey)
85
+ ? { apiKey: parsed.apiKey.trim(), source: "cli" }
86
+ : { apiKey: null, source: "none" };
87
+ }
88
+
89
+ function readCommandcodeApiKey(opts = {}) {
90
+ return readCommandcodeCredential(opts).apiKey;
79
91
  }
80
92
 
81
93
  function normalizeResetAt(value) {
@@ -246,11 +258,13 @@ function withOrgParam(route, orgId) {
246
258
  // usage-limits.js converts both into the panel row.
247
259
  async function fetchCommandcodeLimits({
248
260
  home,
261
+ trackerDir,
249
262
  env = process.env,
250
263
  fetchImpl = fetch,
251
264
  baseUrl = COMMANDCODE_API_BASE_URL,
252
265
  } = {}) {
253
- const apiKey = readCommandcodeApiKey({ home, env });
266
+ const credential = readCommandcodeCredential({ home, trackerDir, env });
267
+ const apiKey = credential.apiKey;
254
268
  if (!apiKey) return { configured: false };
255
269
 
256
270
  const origin = resolveCommandcodeOrigin(baseUrl);
@@ -289,6 +303,7 @@ async function fetchCommandcodeLimits({
289
303
  return {
290
304
  configured: true,
291
305
  error: null,
306
+ credential_source: credential.source,
292
307
  plan_label: deriveCommandcodePlanLabel(planId),
293
308
  subscription_status: isNonEmptyString(subscription?.status)
294
309
  ? subscription.status.trim()
@@ -308,6 +323,7 @@ module.exports = {
308
323
  resolveCommandcodeHome,
309
324
  resolveCommandcodeAuthPath,
310
325
  readCommandcodeApiKey,
326
+ readCommandcodeCredential,
311
327
  deriveCommandcodePlanLabel,
312
328
  normalizeResetAt,
313
329
  normalizeCommandcodeWindow,
@@ -536,6 +536,7 @@ const GROK_PRODUCT_LABELS = new Map([
536
536
  ["GrokBot", "Grok Bot"],
537
537
  ["GrokChat", "Chat"],
538
538
  ["GrokImagine", "Imagine"],
539
+ ["GrokVoice", "Voice"],
539
540
  ["Cursor", "Cursor"],
540
541
  ]);
541
542
 
@@ -19,6 +19,7 @@ const { functionUrlFor, fetchFunctionResponse } = require("./function-url");
19
19
  const { createCloudDeviceTokenStore } = require("./cloud-device-token");
20
20
  const { parseBearerToken } = require("./bearer-token");
21
21
  const { writeJsonAtomicSync } = require("./atomic-json");
22
+ const { createLocalUsageTransfer, MAX_BYTES: LOCAL_USAGE_MAX_BYTES } = require("./local-usage-transfer");
22
23
 
23
24
  const SYNC_TIMEOUT_MS = 120_000;
24
25
  // A failed account-view request should not stall every dashboard refresh
@@ -401,17 +402,6 @@ function getRequestedUsageScope(url) {
401
402
  return normalizeUsageScope(url.searchParams.get("scope"));
402
403
  }
403
404
 
404
- async function scopedQueueRows(queuePath, url) {
405
- const scope = getRequestedUsageScope(url);
406
- const allRows = await readQueueData(queuePath);
407
- return {
408
- scope,
409
- allRows,
410
- rows: filterRowsByUsageScope(allRows, scope),
411
- excludedSources: listExcludedSources(allRows, scope),
412
- };
413
- }
414
-
415
405
  // ── Local achievements ───────────────────────────────────────────────────────
416
406
  // Local-only badges (the cloud nine live in scripts/ops/user-badges.sql).
417
407
  // Thresholds are ordered bronze → silver → gold → diamond. This module is the
@@ -1007,6 +997,17 @@ function buildProxyHeaders(headers) {
1007
997
 
1008
998
  function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir: isolatedTrackerDataDir = null, syncContext = null }) {
1009
999
  const qp = queuePath || resolveQueuePath();
1000
+ const localUsage = createLocalUsageTransfer({ queuePath: qp, normalizeRow: normalizeQueueRow,
1001
+ identity: () => ({ id: getOrCreateMachineId(qp), name: getSystemDeviceName(), platform: process.platform }) });
1002
+ async function scopedQueueRows(queuePath, url) {
1003
+ const scope = getRequestedUsageScope(url);
1004
+ const ownRows = await readQueueData(queuePath);
1005
+ const selected = url.searchParams.get("device_id") || "";
1006
+ // Only offline IDs select a snapshot; other IDs retain the local fallback.
1007
+ const device = selected.startsWith("local:") ? selected : "";
1008
+ const allRows = url.pathname.startsWith("/functions/tokentracker-usage-") ? await localUsage.view(ownRows, device) : ownRows;
1009
+ return { scope, allRows, rows: filterRowsByUsageScope(allRows, scope), excludedSources: listExcludedSources(allRows, scope) };
1010
+ }
1010
1011
  const normalizedServerVersion = typeof serverVersion === "string" ? serverVersion.trim() : "";
1011
1012
 
1012
1013
  // Server-side cookie relay: captures auth cookies from InsForge cloud responses
@@ -1639,6 +1640,32 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
1639
1640
 
1640
1641
  return async function handleLocalApi(req, res, url) {
1641
1642
  const p = url.pathname;
1643
+ if (p === "/api/local-usage") {
1644
+ if (!isAuthorizedLocalMutation(req)) { json(res, { code: "unauthorized" }, 401); return true; }
1645
+ try {
1646
+ if (req.method === "GET") {
1647
+ const from = url.searchParams.get("from") || "";
1648
+ const to = url.searchParams.get("to") || "";
1649
+ const context = getTimeZoneContext(url);
1650
+ const inRange = (row) => { const day = rowDayKey(row, context); return day && (!from || day >= from) && (!to || day <= to); };
1651
+ json(res, await localUsage.devices(await readQueueData(qp), inRange));
1652
+ } else if (req.method === "POST") {
1653
+ const body = JSON.parse((await readBodyLimited(req, LOCAL_USAGE_MAX_BYTES + 4096)).toString("utf8"));
1654
+ if (body.action === "export") json(res, { snapshot: await localUsage.exportSnapshot() });
1655
+ else if (body.action === "preview") json(res, await localUsage.preview(body.snapshot));
1656
+ else if (body.action === "import") json(res, await localUsage.importSnapshot(body.snapshot, body.token));
1657
+ else if (body.action === "remove") { await localUsage.remove(body.machine_id); json(res, { ok: true }); }
1658
+ else json(res, { code: "invalid_snapshot" }, 400);
1659
+ } else json(res, { code: "method_not_allowed" }, 405);
1660
+ } catch (error) {
1661
+ json(res, { code: error.code && error.status ? error.code : "transfer_failed" }, error.status || 400);
1662
+ }
1663
+ return true;
1664
+ }
1665
+ if (p.startsWith("/functions/tokentracker-usage-") && url.searchParams.get("device_id")?.startsWith("local:")) {
1666
+ try { await localUsage.view([], url.searchParams.get("device_id")); }
1667
+ catch (error) { json(res, { code: error.code || "transfer_failed" }, error.status || 500); return true; }
1668
+ }
1642
1669
  // Session recovery and the one-time native callback marker belong to local
1643
1670
  // clients. Reject foreign requests before consuming either, including preflights.
1644
1671
  const nativeMarkerRead = p === "/api/auth-bridge/verifier" && String(req.method).toUpperCase() === "GET";
@@ -2714,6 +2741,11 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
2714
2741
  const from = url.searchParams.get("from") || "";
2715
2742
  const to = url.searchParams.get("to") || "";
2716
2743
  const requestedSource = (url.searchParams.get("source") || "claude").trim().toLowerCase();
2744
+ const selected = url.searchParams.get("device_id") || "";
2745
+ if (selected.startsWith("local:") ? selected !== `local:${getOrCreateMachineId(qp)}` : !selected && await localUsage.hasImports()) {
2746
+ json(res, { from, to, ...unsupportedCategoryPayload(requestedSource), reason: "local_history_only" });
2747
+ return true;
2748
+ }
2717
2749
  if (requestedSource === "claude") {
2718
2750
  try {
2719
2751
  const result = await computeClaudeCategoryBreakdown({ from, to, projectDir: process.cwd() });
@@ -2978,6 +3010,84 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
2978
3010
  return true;
2979
3011
  }
2980
3012
 
3013
+ // --- provider API keys saved from the Limits panel ---
3014
+ // The key lives in a 0600 file next to the queue, never in the launch
3015
+ // environment: a global `launchctl setenv` reaches every launchd app on
3016
+ // macOS, which is exactly the cross-app leak in #837. GET only reports
3017
+ // whether a key exists and where it came from; it never echoes the secret.
3018
+ if (p === "/functions/tokentracker-provider-key") {
3019
+ const {
3020
+ envNameForProvider,
3021
+ isProviderId,
3022
+ normalizeApiKey,
3023
+ readStoredApiKey,
3024
+ writeStoredApiKey,
3025
+ } = require("./provider-api-key-store");
3026
+ const method = String(req.method || "GET").toUpperCase();
3027
+ if (method === "GET") {
3028
+ const provider = String(url.searchParams.get("provider") || "").trim();
3029
+ if (!isProviderId(provider)) {
3030
+ json(res, { error: "Unknown provider" }, 400);
3031
+ return true;
3032
+ }
3033
+ const envName = envNameForProvider(provider);
3034
+ const envValue = envName ? process.env[envName] : "";
3035
+ const hasEnvKey = typeof envValue === "string" && Boolean(envValue.trim());
3036
+ const hasStoredKey = Boolean(readStoredApiKey({ provider, trackerDir: trackerDataDir }));
3037
+ json(res, {
3038
+ provider,
3039
+ configured: hasEnvKey || hasStoredKey,
3040
+ source: hasEnvKey ? "env" : hasStoredKey ? "stored" : "none",
3041
+ });
3042
+ return true;
3043
+ }
3044
+ if (method === "POST" || method === "PUT") {
3045
+ if (!isAuthorizedLocalMutation(req)) {
3046
+ json(res, { ok: false, error: "Unauthorized" }, 401);
3047
+ return true;
3048
+ }
3049
+ let body;
3050
+ try {
3051
+ body = await readJsonBody(req);
3052
+ } catch {
3053
+ json(res, { ok: false, error: "Invalid JSON body" }, 400);
3054
+ return true;
3055
+ }
3056
+ const provider = typeof body?.provider === "string" ? body.provider.trim() : "";
3057
+ if (!isProviderId(provider)) {
3058
+ json(res, { ok: false, error: "Unknown provider" }, 400);
3059
+ return true;
3060
+ }
3061
+ if (!normalizeApiKey(body?.apiKey)) {
3062
+ json(res, { ok: false, error: "Invalid API key" }, 400);
3063
+ return true;
3064
+ }
3065
+ try {
3066
+ await writeStoredApiKey({ provider, trackerDir: trackerDataDir, apiKey: body.apiKey });
3067
+ } catch {
3068
+ json(res, { ok: false, error: "Could not save the API key" }, 500);
3069
+ return true;
3070
+ }
3071
+ // The in-memory aggregate would otherwise keep serving the previous
3072
+ // credential's row (or "not configured") until its TTL expires.
3073
+ try {
3074
+ const { resetUsageLimitsCache, resolveOpencodeGoLimitsCachePath } = require("./usage-limits");
3075
+ resetUsageLimitsCache({ invalidateInFlight: true });
3076
+ if (provider === "opencode-go") {
3077
+ // The disk snapshot is the stale-bars fallback; it must not
3078
+ // outlive the key that produced it.
3079
+ await fs.promises.unlink(resolveOpencodeGoLimitsCachePath({ trackerDir: trackerDataDir }));
3080
+ }
3081
+ } catch {
3082
+ /* cache invalidation is best effort; the stored key is authoritative */
3083
+ }
3084
+ json(res, { ok: true, configured: true, source: "stored" });
3085
+ return true;
3086
+ }
3087
+ json(res, { error: "Method Not Allowed" }, 405);
3088
+ return true;
3089
+ }
3090
+
2981
3091
  // --- cloud-sync preference mirror ---
2982
3092
  // The dashboard's Settings → Account → Cloud sync toggle (a WebView
2983
3093
  // localStorage flag) is mirrored here so the auth-unaware native popover can
@@ -3547,6 +3657,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
3547
3657
  }
3548
3658
  const data = await getUsageLimits({
3549
3659
  home: os.homedir(),
3660
+ trackerDir: trackerDataDir,
3550
3661
  env: process.env,
3551
3662
  platform: process.platform,
3552
3663
  // Punches through the Claude disk fresh-cache (but not the 429