tokentracker-cli 0.93.4 → 0.93.5

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 (101) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-BcNvxrVM.js → AchievementBadge-td60VwNO.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-DhipQygJ.js → AchievementsPage-C3yKXgId.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-BJyb_mIr.js → AchievementsSection-XM39rQkD.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-CoZv7o6t.js → BadgeDetailModal-2TgBBPmw.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-eVBNtvcG.js → BotAnimated-CG6_x0d2.js} +1 -1
  6. package/dashboard/dist/assets/{Card-BEfG2im9.js → Card-BUnCudVB.js} +1 -1
  7. package/dashboard/dist/assets/{ClawdAnimated-C-30t4dC.js → ClawdAnimated-CNRu6v7I.js} +2 -2
  8. package/dashboard/dist/assets/{CommandPalette-DF-TGB1C.js → CommandPalette-D5vVqYkW.js} +1 -1
  9. package/dashboard/dist/assets/{CommunityStatsModal-D-0eI3Pz.js → CommunityStatsModal-D0V4iTI6.js} +1 -1
  10. package/dashboard/dist/assets/{ConfirmModal-BQ7Aor1C.js → ConfirmModal-CzxTtnXo.js} +1 -1
  11. package/dashboard/dist/assets/{Controls-COThRtMS.js → Controls-nR5gzX__.js} +1 -1
  12. package/dashboard/dist/assets/{DashboardPage-Ci_Gb5I1.js → DashboardPage-CpHXkuZn.js} +1 -1
  13. package/dashboard/dist/assets/{DevicePage-BmlmimUg.js → DevicePage-Emw9iVMd.js} +1 -1
  14. package/dashboard/dist/assets/{DialogClose-BdALEZzc.js → DialogClose-Bh-pvxsM.js} +1 -1
  15. package/dashboard/dist/assets/DialogDescription-DJ-RHxIt.js +1 -0
  16. package/dashboard/dist/assets/{DialogTitle-F6W1aTiE.js → DialogTitle-BG3ngtew.js} +1 -1
  17. package/dashboard/dist/assets/{FadeIn-S2d3D28d.js → FadeIn-DOEfAW5p.js} +1 -1
  18. package/dashboard/dist/assets/{HeaderGithubStar-DBBwOwc_.js → HeaderGithubStar-DslGxMu4.js} +1 -1
  19. package/dashboard/dist/assets/{HoverTooltip-rLWQC9Va.js → HoverTooltip-O2OKCBc3.js} +1 -1
  20. package/dashboard/dist/assets/{IpCheckPage-BpPoAIry.js → IpCheckPage-Dv6LbNU_.js} +1 -1
  21. package/dashboard/dist/assets/{LandingPage-BUm_ZMew.js → LandingPage-BBfteqDF.js} +2 -2
  22. package/dashboard/dist/assets/{LeaderboardAvatar-DQxbOYZb.js → LeaderboardAvatar-Ct6QagwN.js} +1 -1
  23. package/dashboard/dist/assets/{LeaderboardPage-DzLrFQm8.js → LeaderboardPage-CSbIdSm7.js} +2 -2
  24. package/dashboard/dist/assets/{LeaderboardProfileModal-Cq4B8Les.js → LeaderboardProfileModal-BuUWkmBN.js} +3 -3
  25. package/dashboard/dist/assets/{LeaderboardProfilePage-C-Xj65xf.js → LeaderboardProfilePage-fVnV0Lo-.js} +1 -1
  26. package/dashboard/dist/assets/{LimitsPage-Bjcmr0bd.js → LimitsPage-BHkkA3xN.js} +1 -1
  27. package/dashboard/dist/assets/{LocalOnlyNotice-9mWINkVj.js → LocalOnlyNotice-BLlrqreb.js} +1 -1
  28. package/dashboard/dist/assets/{LoginCard-BlKleF8M.js → LoginCard-CKV1uACo.js} +1 -1
  29. package/dashboard/dist/assets/{LoginModal-BUYa13hj.js → LoginModal-RCpQtFuG.js} +1 -1
  30. package/dashboard/dist/assets/{LoginPage-DbQhApOd.js → LoginPage-vMV2kpef.js} +1 -1
  31. package/dashboard/dist/assets/{PetPage-DuAf3OOf.js → PetPage-CTgwpnsA.js} +1 -1
  32. package/dashboard/dist/assets/{PopoverPopup-CG76MkbK.js → PopoverPopup-Bddr9R6x.js} +1 -1
  33. package/dashboard/dist/assets/{ProviderIcon-N7OTtgte.js → ProviderIcon-BeXu2voQ.js} +1 -1
  34. package/dashboard/dist/assets/{ResetPasswordPage-D48--1wC.js → ResetPasswordPage-CilGTQWZ.js} +1 -1
  35. package/dashboard/dist/assets/{SegmentedControl-zRvbFqyY.js → SegmentedControl-r5MP3Uyx.js} +1 -1
  36. package/dashboard/dist/assets/{Select-Bi3B21Fb.js → Select-BBUIbPTj.js} +1 -1
  37. package/dashboard/dist/assets/{SelectItemText-Bg8I618N.js → SelectItemText-CbuN7Sui.js} +1 -1
  38. package/dashboard/dist/assets/{ServiceStatusPage-CeeYx4XY.js → ServiceStatusPage-WgmQviZa.js} +1 -1
  39. package/dashboard/dist/assets/{SessionsPage-CZF3qLEm.js → SessionsPage-BDkdXQhW.js} +1 -1
  40. package/dashboard/dist/assets/{SettingsPage-D2wa8i5u.js → SettingsPage-CsF8cG7I.js} +2 -2
  41. package/dashboard/dist/assets/{SkillsPage-OxW2w6up.js → SkillsPage-CVGra4ar.js} +1 -1
  42. package/dashboard/dist/assets/{TokenGalaxy-DmtP_r86.js → TokenGalaxy-B0kFIG0N.js} +1 -1
  43. package/dashboard/dist/assets/{ToolbarRootContext-DC6BLLoO.js → ToolbarRootContext-BlWkiH2k.js} +1 -1
  44. package/dashboard/dist/assets/{TrendMonitor-OvvLCD3K.js → TrendMonitor-C18_ADhU.js} +1 -1
  45. package/dashboard/dist/assets/{WidgetsPage-DhOC8rae.js → WidgetsPage-Ck7L4vBj.js} +1 -1
  46. package/dashboard/dist/assets/{WrappedPage-C0boSobK.js → WrappedPage-Bm5HMj3a.js} +1 -1
  47. package/dashboard/dist/assets/{__vite-browser-external-D_dICEUB.js → __vite-browser-external-BCyVFzdP.js} +15 -15
  48. package/dashboard/dist/assets/{agent-logos-Dzyq5cDh.js → agent-logos-PZX7lWJo.js} +1 -1
  49. package/dashboard/dist/assets/{arrow-up-right-DrLfm7Vy.js → arrow-up-right-DPSDEZPh.js} +1 -1
  50. package/dashboard/dist/assets/{check-DAsdH-MF.js → check-GGrgivnu.js} +1 -1
  51. package/dashboard/dist/assets/{chevron-right-CdSgby9-.js → chevron-right-CmSOUizu.js} +1 -1
  52. package/dashboard/dist/assets/{copy-TuVoW7qK.js → copy-ChMyV-hy.js} +1 -1
  53. package/dashboard/dist/assets/{download-BZ61xuhi.js → download-DGUkE9Jc.js} +1 -1
  54. package/dashboard/dist/assets/{external-link-DJrRSKF9.js → external-link-B_W-UDpi.js} +1 -1
  55. package/dashboard/dist/assets/{flame-9JS9yMju.js → flame-C9ccP-R8.js} +1 -1
  56. package/dashboard/dist/assets/{format-tokens-Blp6lI_n.js → format-tokens-CkXJmbVP.js} +1 -1
  57. package/dashboard/dist/assets/{icons-CbF0cL6i.js → icons-D37cRKGk.js} +1 -1
  58. package/dashboard/dist/assets/{index-FnWiMfqV.js → index-C-_fimm3.js} +1 -1
  59. package/dashboard/dist/assets/{index-I81NzV7H.js → index-C9OzYqU0.js} +1 -1
  60. package/dashboard/dist/assets/{index-Bu3I2hmd.js → index-Dt6WIU71.js} +1 -1
  61. package/dashboard/dist/assets/{info-B_iryt84.js → info-C3-0C2W0.js} +1 -1
  62. package/dashboard/dist/assets/{limits-providers-Cli4pkKC.js → limits-providers-DB1Zr3WN.js} +1 -1
  63. package/dashboard/dist/assets/{link-2-pB3-7Waa.js → link-2-DqNEbzji.js} +1 -1
  64. package/dashboard/dist/assets/{loader-circle-DivA-LsF.js → loader-circle-BYoKm-f5.js} +1 -1
  65. package/dashboard/dist/assets/{maximize-2-WNMF5NUU.js → maximize-2-lVQDTDqs.js} +1 -1
  66. package/dashboard/dist/assets/{modifiers.esm-BaJJxzf2.js → modifiers.esm-Bww85WOd.js} +1 -1
  67. package/dashboard/dist/assets/module-D2D5r9gC.js +3 -0
  68. package/dashboard/dist/assets/{monitor-smartphone-pAR3IHow.js → monitor-smartphone--KubNwvP.js} +1 -1
  69. package/dashboard/dist/assets/{provider-display-CyYhYVar.js → provider-display-COV_Ft4A.js} +1 -1
  70. package/dashboard/dist/assets/{react-BldsIPA1.js → react-lNnu0y7U.js} +1 -1
  71. package/dashboard/dist/assets/{search-xIfdCEo0.js → search-CpHNMxpD.js} +1 -1
  72. package/dashboard/dist/assets/{skills-api-DZNsCHPo.js → skills-api-Cf2qRQ1h.js} +1 -1
  73. package/dashboard/dist/assets/{sortable.esm-DMaj448G.js → sortable.esm-DWdlF_7j.js} +1 -1
  74. package/dashboard/dist/assets/{store-VTEegajU.js → store-DJQqZJtg.js} +1 -1
  75. package/dashboard/dist/assets/{terminal-Bj8SotRv.js → terminal-Cban5byW.js} +1 -1
  76. package/dashboard/dist/assets/{trash-2-CWgY_FBc.js → trash-2-nelaUHSh.js} +1 -1
  77. package/dashboard/dist/assets/{use-community-stats-BOVvh_Gv.js → use-community-stats-CBupXLW7.js} +1 -1
  78. package/dashboard/dist/assets/{use-limits-display-prefs-MtLsQ5kN.js → use-limits-display-prefs-B4y_kzcd.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list-DjCZVinv.js → use-ordered-list-DEV58tGc.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-CRf4pBTt.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-DASihY0W.js → use-session-efficiency-pref-wSk1wV6R.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-tHlm2id8.js → use-transform-NKavwYrX.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-BBEHNJKl.js → use-usage-limits-Blr1HBCj.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-DgTCDMXb.js → useCurrency-B2704dMx.js} +1 -1
  85. package/dashboard/dist/assets/{useScrollLock-C3TFkbz0.js → useScrollLock-BuPQ8T9m.js} +1 -1
  86. package/dashboard/dist/assets/{useTokenFormat-BtqPANfZ.js → useTokenFormat-DPxAFuMj.js} +1 -1
  87. package/dashboard/dist/assets/{zap-CJNTZ87b.js → zap-BgtTdM4q.js} +1 -1
  88. package/dashboard/dist/index.html +1 -1
  89. package/dashboard/dist/ip-check.html +1 -1
  90. package/dashboard/dist/leaderboard.html +1 -1
  91. package/dashboard/dist/share.html +1 -1
  92. package/package.json +1 -1
  93. package/src/commands/serve.js +115 -4
  94. package/src/commands/sync.js +146 -3
  95. package/src/lib/opencode-go-limits.js +11 -3
  96. package/src/lib/pricing/seed-snapshot.json +1 -1
  97. package/src/lib/rollout.js +189 -5
  98. package/src/lib/usage-limits.js +119 -1
  99. package/dashboard/dist/assets/DialogDescription-DAKoQCEv.js +0 -1
  100. package/dashboard/dist/assets/module-CV1mYFsv.js +0 -3
  101. package/dashboard/dist/assets/use-reduced-motion-DjWNXuSi.js +0 -1
@@ -4422,8 +4422,7 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4422
4422
  // by the existing cursor-key + #426 fingerprint dedup in
4423
4423
  // parseOpencodeDbIncremental, so unioning here is safe — the downstream
4424
4424
  // parser collapses identical sessionID|messageID pairs before bucketing.
4425
- const collectRows = (rows, isV2) => {
4426
- const out = [];
4425
+ const appendRows = (rows, isV2, out) => {
4427
4426
  for (const row of rows) {
4428
4427
  if (!row || typeof row.data !== "string") continue;
4429
4428
  let data;
@@ -4450,7 +4449,6 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4450
4449
  data,
4451
4450
  });
4452
4451
  }
4453
- return out;
4454
4452
  };
4455
4453
 
4456
4454
  const readGeneration = (sql) => {
@@ -4477,11 +4475,11 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4477
4475
  const out = [];
4478
4476
 
4479
4477
  const rows1 = readGeneration(OPENCODE_DB_V1_MESSAGE_SQL);
4480
- if (rows1) out.push(...collectRows(rows1, false));
4478
+ if (rows1) appendRows(rows1, false, out);
4481
4479
 
4482
4480
  if (probe?.hasRows) {
4483
4481
  const rows2 = readGeneration(buildV2Sql(probe.sessionTable));
4484
- if (rows2) out.push(...collectRows(rows2, true));
4482
+ if (rows2) appendRows(rows2, true, out);
4485
4483
  }
4486
4484
 
4487
4485
  return out;
@@ -4551,10 +4549,195 @@ function isZcodeNativeMessage(data) {
4551
4549
  );
4552
4550
  }
4553
4551
 
4552
+ const ZCODE_NATIVE_USAGE_COLUMNS = new Set([
4553
+ "id",
4554
+ "logical_request_id",
4555
+ "attempt_index",
4556
+ "session_id",
4557
+ "provider_id",
4558
+ "model_id",
4559
+ "status",
4560
+ "started_at",
4561
+ "input_tokens",
4562
+ "output_tokens",
4563
+ "reasoning_tokens",
4564
+ "cache_creation_input_tokens",
4565
+ "cache_read_input_tokens",
4566
+ ]);
4567
+
4568
+ function detectZcodeNativeUsageLayout(dbPath, sqliteOptions = {}) {
4569
+ if (!dbPath || !fssync.existsSync(dbPath)) return null;
4570
+ let rows;
4571
+ try {
4572
+ rows = readSqliteJsonRows(
4573
+ dbPath,
4574
+ `SELECT 'model_usage' AS table_name, name FROM pragma_table_info('model_usage')
4575
+ UNION ALL
4576
+ SELECT 'session' AS table_name, name FROM pragma_table_info('session')`,
4577
+ {
4578
+ label: "ZCode",
4579
+ timeout: 10_000,
4580
+ maxBuffer: 1024 * 1024,
4581
+ ...sqliteOptions,
4582
+ throwOnReadFailure: true,
4583
+ },
4584
+ );
4585
+ } catch (_error) {
4586
+ return null;
4587
+ }
4588
+ const modelUsageColumns = new Set(
4589
+ rows
4590
+ .filter((row) => !row?.table_name || row.table_name === "model_usage")
4591
+ .map((row) => String(row?.name || "")),
4592
+ );
4593
+ if (![...ZCODE_NATIVE_USAGE_COLUMNS].every((name) => modelUsageColumns.has(name))) {
4594
+ return null;
4595
+ }
4596
+ const sessionColumns = new Set(
4597
+ rows
4598
+ .filter((row) => row?.table_name === "session")
4599
+ .map((row) => String(row?.name || "")),
4600
+ );
4601
+ return {
4602
+ hasSessionDirectory: sessionColumns.has("id") && sessionColumns.has("directory"),
4603
+ };
4604
+ }
4605
+
4606
+ function hasZcodeNativeUsageSchema(dbPath, sqliteOptions = {}) {
4607
+ if (!dbPath || !fssync.existsSync(dbPath)) return false;
4608
+ let snapshot = null;
4609
+ let effectiveDbPath = dbPath;
4610
+ if (isUncPath(dbPath)) {
4611
+ try {
4612
+ snapshot = snapshotSqliteDb(dbPath);
4613
+ effectiveDbPath = snapshot.path;
4614
+ } catch (_error) {
4615
+ // Fall through to the direct read: some UNC servers support SQLite's
4616
+ // read-only locking semantics even when the WSL bridge does not.
4617
+ }
4618
+ }
4619
+ try {
4620
+ return detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions) !== null;
4621
+ } finally {
4622
+ if (snapshot) snapshot.cleanup();
4623
+ }
4624
+ }
4625
+
4626
+ function buildZcodeNativeUsageSql({ hasSessionDirectory }) {
4627
+ const directorySelect = hasSessionDirectory ? ", s.directory AS directory" : "";
4628
+ const directoryJoin = hasSessionDirectory
4629
+ ? " LEFT JOIN session AS s ON s.id = mu.session_id"
4630
+ : "";
4631
+ return `SELECT
4632
+ mu.id,
4633
+ mu.logical_request_id,
4634
+ mu.attempt_index,
4635
+ mu.session_id,
4636
+ mu.provider_id,
4637
+ mu.model_id,
4638
+ mu.started_at,
4639
+ mu.input_tokens,
4640
+ mu.output_tokens,
4641
+ mu.reasoning_tokens,
4642
+ mu.cache_creation_input_tokens,
4643
+ mu.cache_read_input_tokens
4644
+ ${directorySelect}
4645
+ FROM model_usage AS mu${directoryJoin}
4646
+ WHERE mu.status = 'completed'
4647
+ AND trim(mu.model_id) != ''
4648
+ AND (
4649
+ mu.input_tokens > 0 OR mu.output_tokens > 0 OR mu.reasoning_tokens > 0 OR
4650
+ mu.cache_creation_input_tokens > 0 OR mu.cache_read_input_tokens > 0
4651
+ )
4652
+ ORDER BY mu.started_at ASC, mu.id ASC`;
4653
+ }
4654
+
4655
+ function readZcodeNativeUsageMessages(dbPath, sqliteOptions = {}) {
4656
+ let snapshot = null;
4657
+ let effectiveDbPath = dbPath;
4658
+ if (isUncPath(dbPath)) {
4659
+ try {
4660
+ snapshot = snapshotSqliteDb(dbPath);
4661
+ effectiveDbPath = snapshot.path;
4662
+ } catch (_error) {
4663
+ // Preserve the existing direct-read fallback for transient snapshot
4664
+ // failures and UNC implementations that support SQLite locking.
4665
+ }
4666
+ }
4667
+
4668
+ let rows;
4669
+ try {
4670
+ const layout = detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions);
4671
+ if (!layout) return null;
4672
+ rows = readSqliteJsonRows(effectiveDbPath, buildZcodeNativeUsageSql(layout), {
4673
+ label: "ZCode",
4674
+ maxBuffer: 50 * 1024 * 1024,
4675
+ timeout: 30_000,
4676
+ ...sqliteOptions,
4677
+ throwOnReadFailure: true,
4678
+ });
4679
+ } catch (_error) {
4680
+ return null;
4681
+ } finally {
4682
+ if (snapshot) snapshot.cleanup();
4683
+ }
4684
+
4685
+ const messages = [];
4686
+ for (const row of rows) {
4687
+ const providerID = String(row?.provider_id || "").trim();
4688
+ const modelID = String(row?.model_id || "").trim();
4689
+ const sessionID = String(row?.session_id || "").trim();
4690
+ const logicalRequestId = String(row?.logical_request_id || "").trim();
4691
+ const attemptIndex = toNonNegativeInt(row?.attempt_index);
4692
+ const id = String(row?.id || "").trim() ||
4693
+ (logicalRequestId ? `${logicalRequestId}#${attemptIndex}` : "");
4694
+ const startedAt = coerceEpochMs(row?.started_at);
4695
+ if (!providerID || !modelID || !sessionID || !id || !startedAt) continue;
4696
+
4697
+ // ZCode's persisted input/output columns are inclusive parents:
4698
+ // cache read/write are subsets of input, and reasoning is a subset of
4699
+ // output. TokenTracker stores those five columns disjointly, so split the
4700
+ // subsets here before entering the shared OpenCode parser. This preserves
4701
+ // sum(input + cache read + cache write + output + reasoning) without
4702
+ // billing either subset twice.
4703
+ const rawInput = toNonNegativeInt(row?.input_tokens);
4704
+ const rawOutput = toNonNegativeInt(row?.output_tokens);
4705
+ const cacheRead = toNonNegativeInt(row?.cache_read_input_tokens);
4706
+ const cacheWrite = toNonNegativeInt(row?.cache_creation_input_tokens);
4707
+ const reasoning = toNonNegativeInt(row?.reasoning_tokens);
4708
+
4709
+ const data = {
4710
+ id,
4711
+ sessionID,
4712
+ role: "assistant",
4713
+ providerID,
4714
+ modelID,
4715
+ time: { created: startedAt, completed: startedAt },
4716
+ tokens: {
4717
+ input: Math.max(0, rawInput - cacheRead - cacheWrite),
4718
+ output: Math.max(0, rawOutput - reasoning),
4719
+ reasoning,
4720
+ cache: {
4721
+ read: cacheRead,
4722
+ write: cacheWrite,
4723
+ },
4724
+ },
4725
+ };
4726
+ if (typeof row?.directory === "string" && row.directory.trim()) {
4727
+ data.path = { cwd: row.directory.trim() };
4728
+ }
4729
+ if (!isZcodeNativeMessage(data)) continue;
4730
+ messages.push({ id, sessionID, timeUpdated: startedAt, data });
4731
+ }
4732
+ return messages;
4733
+ }
4734
+
4554
4735
  // Read only genuine ZCode assistant messages (its own GLM models via Z.ai /
4555
4736
  // BigModel), dropping any bundled sub-agent turns. See isZcodeNativeMessage.
4556
4737
  function readZcodeDbMessages(dbPath, sqliteOptions = {}) {
4557
4738
  if (!dbPath || !fssync.existsSync(dbPath)) return [];
4739
+ const nativeMessages = readZcodeNativeUsageMessages(dbPath, sqliteOptions);
4740
+ if (nativeMessages !== null) return nativeMessages;
4558
4741
  const all = readOpencodeDbMessages(dbPath, sqliteOptions);
4559
4742
  return all.filter((m) => isZcodeNativeMessage(m.data));
4560
4743
  }
@@ -18054,6 +18237,7 @@ module.exports = {
18054
18237
  readOpencodeDbMessages,
18055
18238
  readMimoDbMessages,
18056
18239
  readZcodeDbMessages,
18240
+ hasZcodeNativeUsageSchema,
18057
18241
  resolveQoderDbPath,
18058
18242
  resolveQoderDbPaths,
18059
18243
  resolveQoderCnDbPaths,
@@ -66,6 +66,13 @@ const CLAUDE_LIMITS_CACHE_FRESH_TTL_MS = 10 * 60 * 1000;
66
66
  // error, mirroring the Claude stale-fallback path.
67
67
  const CODEX_LIMITS_CACHE_FILE = "codex-usage-limits-cache.json";
68
68
  const CODEX_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
69
+ // OpenCode Go hits opencode.ai over the public internet (15s timeout) with no local CLI to
70
+ // fall back on. A transient 5xx / timeout previously left the panel blank until the next
71
+ // 2-minute poll — persist the last successful windows so a blip serves stale bars instead
72
+ // of a flash of "fetch failed" (mirrors Claude / Codex / Antigravity).
73
+ const OPENCODE_GO_LIMITS_CACHE_FILE = "opencode-go-usage-limits-cache.json";
74
+ const OPENCODE_GO_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
75
+ const OPENCODE_GO_LOCAL_ESTIMATE_CACHE_MAX_AGE_MS = 60 * 60 * 1000;
69
76
  // A 429 from the usage endpoint carries a long `retry-after` (often 20+ minutes). Persist
70
77
  // the cooldown so every surface — this process, the menu bar app's embedded server, a later
71
78
  // restart — stops calling until it expires. Hammering during the cooldown just renews the
@@ -2398,6 +2405,82 @@ function writeCodexLimitsCache(limits, { home, nowMs = Date.now() } = {}) {
2398
2405
  } catch (_error) {}
2399
2406
  }
2400
2407
 
2408
+ function resolveOpencodeGoLimitsCachePath({ home } = {}) {
2409
+ return path.join(home || os.homedir(), ".tokentracker", "tracker", OPENCODE_GO_LIMITS_CACHE_FILE);
2410
+ }
2411
+
2412
+ function hasOpencodeGoWindow(limits) {
2413
+ return Boolean(limits?.primary_window || limits?.secondary_window || limits?.tertiary_window);
2414
+ }
2415
+
2416
+ function isOpencodeGoCacheWindowUsable(window, { nowMs } = {}) {
2417
+ if (!window || typeof window !== "object") return false;
2418
+ const resetAtMs = parseTimeMs(window.reset_at);
2419
+ if (resetAtMs === null) return true;
2420
+ return resetAtMs > nowMs;
2421
+ }
2422
+
2423
+ function normalizeOpencodeGoCachedLimits(
2424
+ raw,
2425
+ { nowMs = Date.now(), maxAgeMs } = {},
2426
+ ) {
2427
+ const effectiveMaxAge = Number.isFinite(maxAgeMs)
2428
+ ? maxAgeMs
2429
+ : raw?.source === "local-estimate"
2430
+ ? OPENCODE_GO_LOCAL_ESTIMATE_CACHE_MAX_AGE_MS
2431
+ : OPENCODE_GO_LIMITS_CACHE_MAX_AGE_MS;
2432
+ const cachedAtMs = parseTimeMs(raw?.cached_at);
2433
+ if (!Number.isFinite(cachedAtMs)) return null;
2434
+ if (cachedAtMs > nowMs + 60_000) return null;
2435
+ if (nowMs - cachedAtMs > effectiveMaxAge) return null;
2436
+
2437
+ const cached = {
2438
+ configured: true,
2439
+ error: null,
2440
+ source: typeof raw?.source === "string" ? raw.source : "api",
2441
+ subscription_status: typeof raw?.subscription_status === "string" ? raw.subscription_status : null,
2442
+ plan_label: typeof raw?.plan_label === "string" ? raw.plan_label : null,
2443
+ primary_window: isOpencodeGoCacheWindowUsable(raw?.primary_window, { nowMs }) ? raw.primary_window : null,
2444
+ secondary_window: isOpencodeGoCacheWindowUsable(raw?.secondary_window, { nowMs }) ? raw.secondary_window : null,
2445
+ tertiary_window: isOpencodeGoCacheWindowUsable(raw?.tertiary_window, { nowMs }) ? raw.tertiary_window : null,
2446
+ stale: true,
2447
+ cached_at: raw.cached_at,
2448
+ };
2449
+ return hasOpencodeGoWindow(cached) ? cached : null;
2450
+ }
2451
+
2452
+ function readOpencodeGoLimitsCache({ home, nowMs = Date.now() } = {}) {
2453
+ const cachePath = resolveOpencodeGoLimitsCachePath({ home });
2454
+ try {
2455
+ const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
2456
+ return normalizeOpencodeGoCachedLimits(parsed?.opencodeGo, { nowMs });
2457
+ } catch (_error) {
2458
+ return null;
2459
+ }
2460
+ }
2461
+
2462
+ function writeOpencodeGoLimitsCache(limits, { home, nowMs = Date.now() } = {}) {
2463
+ if (!limits?.configured || limits.error || !hasOpencodeGoWindow(limits)) return;
2464
+ const cachePath = resolveOpencodeGoLimitsCachePath({ home });
2465
+ const payload = {
2466
+ opencodeGo: {
2467
+ source: limits.source || "api",
2468
+ subscription_status: limits.subscription_status || null,
2469
+ plan_label: limits.plan_label || null,
2470
+ primary_window: limits.primary_window || null,
2471
+ secondary_window: limits.secondary_window || null,
2472
+ tertiary_window: limits.tertiary_window || null,
2473
+ cached_at: new Date(nowMs).toISOString(),
2474
+ },
2475
+ };
2476
+ try {
2477
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
2478
+ const tmpPath = `${cachePath}.${process.pid}.tmp`;
2479
+ fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
2480
+ fs.renameSync(tmpPath, cachePath);
2481
+ } catch (_error) {}
2482
+ }
2483
+
2401
2484
  function resolveClaudeRateLimitPath({ home } = {}) {
2402
2485
  return path.join(home || os.homedir(), ".tokentracker", "tracker", CLAUDE_RATE_LIMIT_FILE);
2403
2486
  }
@@ -3063,7 +3146,7 @@ async function fetchUsageLimitsUncached({
3063
3146
  : null;
3064
3147
 
3065
3148
  const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
3066
- const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGo, qoder, qoderCn, codingPlan, claudeServiceStatus] = await Promise.all([
3149
+ const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, claudeServiceStatus] = await Promise.all([
3067
3150
  claudeToken && !freshClaudeCache && !claudeRetryAtMs
3068
3151
  ? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
3069
3152
  (value) => ({ status: "fulfilled", value }),
@@ -3276,6 +3359,41 @@ async function fetchUsageLimitsUncached({
3276
3359
  }
3277
3360
  }
3278
3361
 
3362
+ // OpenCode Go: persist last successful windows so a transient timeout/5xx
3363
+ // (common on GFW-affected routes to opencode.ai) serves stale bars instead of
3364
+ // a flashing "fetch failed". Mirrors Codex / Claude / Antigravity.
3365
+ // Auth errors (401/403 invalid key, missing subscription, expired cookie) are
3366
+ // actionable and must not be hidden behind stale cache — they carry
3367
+ // `auth_error:true` from opencode-go-limits.js.
3368
+ const opencodeGoIsAuthError =
3369
+ Boolean(opencodeGoRaw?.auth_error) ||
3370
+ (typeof opencodeGoRaw?.error === "string" &&
3371
+ /Not signed in|auth cookie|Refresh the auth cookie|Failed to resolve Workspace ID.*401|Failed to resolve Workspace ID.*403/i.test(
3372
+ opencodeGoRaw.error,
3373
+ ));
3374
+ let opencodeGo;
3375
+ if (opencodeGoRaw && opencodeGoRaw.configured === false) {
3376
+ opencodeGo = opencodeGoRaw;
3377
+ } else if (opencodeGoIsAuthError) {
3378
+ opencodeGo = opencodeGoRaw;
3379
+ } else if (opencodeGoRaw?.subscription_status === "inactive") {
3380
+ opencodeGo = opencodeGoRaw;
3381
+ } else if (opencodeGoRaw && !opencodeGoRaw.error && hasOpencodeGoWindow(opencodeGoRaw)) {
3382
+ opencodeGo = {
3383
+ ...opencodeGoRaw,
3384
+ stale: false,
3385
+ cached_at: new Date(nowMs).toISOString(),
3386
+ };
3387
+ writeOpencodeGoLimitsCache(opencodeGo, { home, nowMs });
3388
+ } else {
3389
+ const cached = readOpencodeGoLimitsCache({ home, nowMs });
3390
+ if (cached) {
3391
+ opencodeGo = cached;
3392
+ } else {
3393
+ opencodeGo = opencodeGoRaw || { configured: true, error: "Unknown error" };
3394
+ }
3395
+ }
3396
+
3279
3397
  const data = {
3280
3398
  fetched_at: new Date(nowMs).toISOString(),
3281
3399
  claude: withPlanLabel(claude, claudePlanType, "Claude"),
@@ -1 +0,0 @@
1
- import{r as n,z as a}from"./__vite-browser-external-D_dICEUB.js";import{u as p}from"./DialogTitle-F6W1aTiE.js";import{u as c}from"./useScrollLock-C3TFkbz0.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,i=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};