tokentracker-cli 0.93.4 → 0.93.6

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 (102) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-BcNvxrVM.js → AchievementBadge-DsAclDw6.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-DhipQygJ.js → AchievementsPage-D__EwC45.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-BJyb_mIr.js → AchievementsSection-BAZNM3Cn.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-CoZv7o6t.js → BadgeDetailModal-Dgeer5oB.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-eVBNtvcG.js → BotAnimated-CwrScgrI.js} +1 -1
  6. package/dashboard/dist/assets/{Card-BEfG2im9.js → Card-D7GmLoqP.js} +1 -1
  7. package/dashboard/dist/assets/{ClawdAnimated-C-30t4dC.js → ClawdAnimated-BEXgb5J_.js} +2 -2
  8. package/dashboard/dist/assets/{CommandPalette-DF-TGB1C.js → CommandPalette-B9Mn3Qt2.js} +1 -1
  9. package/dashboard/dist/assets/{CommunityStatsModal-D-0eI3Pz.js → CommunityStatsModal-eJ_a1FMd.js} +1 -1
  10. package/dashboard/dist/assets/{ConfirmModal-BQ7Aor1C.js → ConfirmModal-CumOr2J1.js} +1 -1
  11. package/dashboard/dist/assets/{Controls-COThRtMS.js → Controls-DxnaHBzv.js} +1 -1
  12. package/dashboard/dist/assets/{DashboardPage-Ci_Gb5I1.js → DashboardPage-BpRRXCRr.js} +1 -1
  13. package/dashboard/dist/assets/{DevicePage-BmlmimUg.js → DevicePage-4yTYf29M.js} +1 -1
  14. package/dashboard/dist/assets/{DialogClose-BdALEZzc.js → DialogClose-BbderFCY.js} +1 -1
  15. package/dashboard/dist/assets/DialogDescription-C5Ujj254.js +1 -0
  16. package/dashboard/dist/assets/{DialogTitle-F6W1aTiE.js → DialogTitle-6epeLIn0.js} +1 -1
  17. package/dashboard/dist/assets/{FadeIn-S2d3D28d.js → FadeIn-BFLojPGG.js} +1 -1
  18. package/dashboard/dist/assets/{HeaderGithubStar-DBBwOwc_.js → HeaderGithubStar-CPBNn61n.js} +1 -1
  19. package/dashboard/dist/assets/{HoverTooltip-rLWQC9Va.js → HoverTooltip-DbxnLj8e.js} +1 -1
  20. package/dashboard/dist/assets/{IpCheckPage-BpPoAIry.js → IpCheckPage-CfPtXAAF.js} +1 -1
  21. package/dashboard/dist/assets/{LandingPage-BUm_ZMew.js → LandingPage-CocswfFj.js} +2 -2
  22. package/dashboard/dist/assets/{LeaderboardAvatar-DQxbOYZb.js → LeaderboardAvatar-iZLxxZX-.js} +1 -1
  23. package/dashboard/dist/assets/{LeaderboardPage-DzLrFQm8.js → LeaderboardPage-yqauB4Q1.js} +2 -2
  24. package/dashboard/dist/assets/{LeaderboardProfileModal-Cq4B8Les.js → LeaderboardProfileModal-BWu8jbBW.js} +3 -3
  25. package/dashboard/dist/assets/{LeaderboardProfilePage-C-Xj65xf.js → LeaderboardProfilePage-B77gOTzr.js} +1 -1
  26. package/dashboard/dist/assets/{LimitsPage-Bjcmr0bd.js → LimitsPage-C9NeIcft.js} +1 -1
  27. package/dashboard/dist/assets/{LocalOnlyNotice-9mWINkVj.js → LocalOnlyNotice-HPXWU8TT.js} +1 -1
  28. package/dashboard/dist/assets/{LoginCard-BlKleF8M.js → LoginCard-D592L-fh.js} +1 -1
  29. package/dashboard/dist/assets/{LoginModal-BUYa13hj.js → LoginModal-C7Qm7CVf.js} +1 -1
  30. package/dashboard/dist/assets/{LoginPage-DbQhApOd.js → LoginPage-DkYw5ZQK.js} +1 -1
  31. package/dashboard/dist/assets/{PetPage-DuAf3OOf.js → PetPage-BOhvAJp4.js} +1 -1
  32. package/dashboard/dist/assets/{PopoverPopup-CG76MkbK.js → PopoverPopup-BK9aqQn1.js} +1 -1
  33. package/dashboard/dist/assets/{ProviderIcon-N7OTtgte.js → ProviderIcon-d0_vhIkS.js} +1 -1
  34. package/dashboard/dist/assets/{ResetPasswordPage-D48--1wC.js → ResetPasswordPage-UDNlpFVu.js} +1 -1
  35. package/dashboard/dist/assets/{SegmentedControl-zRvbFqyY.js → SegmentedControl-DE5m1pTf.js} +1 -1
  36. package/dashboard/dist/assets/{Select-Bi3B21Fb.js → Select-Z2xTQ55f.js} +1 -1
  37. package/dashboard/dist/assets/{SelectItemText-Bg8I618N.js → SelectItemText-Daq-XRZg.js} +1 -1
  38. package/dashboard/dist/assets/{ServiceStatusPage-CeeYx4XY.js → ServiceStatusPage-Cfr-QDZ7.js} +1 -1
  39. package/dashboard/dist/assets/{SessionsPage-CZF3qLEm.js → SessionsPage-nHPj6IAt.js} +1 -1
  40. package/dashboard/dist/assets/{SettingsPage-D2wa8i5u.js → SettingsPage-NvxUG_9m.js} +2 -2
  41. package/dashboard/dist/assets/{SkillsPage-OxW2w6up.js → SkillsPage-zFbZa42f.js} +1 -1
  42. package/dashboard/dist/assets/{TokenGalaxy-DmtP_r86.js → TokenGalaxy-CjAY6rey.js} +1 -1
  43. package/dashboard/dist/assets/{ToolbarRootContext-DC6BLLoO.js → ToolbarRootContext-DnC8LT_8.js} +1 -1
  44. package/dashboard/dist/assets/{TrendMonitor-OvvLCD3K.js → TrendMonitor-DAAlILwJ.js} +1 -1
  45. package/dashboard/dist/assets/{WidgetsPage-DhOC8rae.js → WidgetsPage-CXsfi-mq.js} +1 -1
  46. package/dashboard/dist/assets/{WrappedPage-C0boSobK.js → WrappedPage-ByYzOeZ-.js} +1 -1
  47. package/dashboard/dist/assets/__vite-browser-external-HdCplPKs.js +45 -0
  48. package/dashboard/dist/assets/{agent-logos-Dzyq5cDh.js → agent-logos-BW-ajJ7p.js} +1 -1
  49. package/dashboard/dist/assets/{arrow-up-right-DrLfm7Vy.js → arrow-up-right-CA5IJkqo.js} +1 -1
  50. package/dashboard/dist/assets/{check-DAsdH-MF.js → check-BBjF3bSm.js} +1 -1
  51. package/dashboard/dist/assets/{chevron-right-CdSgby9-.js → chevron-right-DCvGp9iJ.js} +1 -1
  52. package/dashboard/dist/assets/{copy-TuVoW7qK.js → copy-ahF8LGeu.js} +1 -1
  53. package/dashboard/dist/assets/{download-BZ61xuhi.js → download-DBm8dmvt.js} +1 -1
  54. package/dashboard/dist/assets/{external-link-DJrRSKF9.js → external-link-C1sJSL7J.js} +1 -1
  55. package/dashboard/dist/assets/{flame-9JS9yMju.js → flame-B6xukEYd.js} +1 -1
  56. package/dashboard/dist/assets/{format-tokens-Blp6lI_n.js → format-tokens-6TqXlT3q.js} +1 -1
  57. package/dashboard/dist/assets/{icons-CbF0cL6i.js → icons-pGe_pInY.js} +1 -1
  58. package/dashboard/dist/assets/{index-I81NzV7H.js → index-Chb8mb2X.js} +1 -1
  59. package/dashboard/dist/assets/{index-FnWiMfqV.js → index-I9O8xChC.js} +1 -1
  60. package/dashboard/dist/assets/{index-Bu3I2hmd.js → index-RPV0zdZ9.js} +1 -1
  61. package/dashboard/dist/assets/{info-B_iryt84.js → info-DBfJv0yu.js} +1 -1
  62. package/dashboard/dist/assets/{limits-providers-Cli4pkKC.js → limits-providers-DPQsdVq5.js} +1 -1
  63. package/dashboard/dist/assets/{link-2-pB3-7Waa.js → link-2-DNEARrPY.js} +1 -1
  64. package/dashboard/dist/assets/{loader-circle-DivA-LsF.js → loader-circle-BCFHUr8X.js} +1 -1
  65. package/dashboard/dist/assets/{maximize-2-WNMF5NUU.js → maximize-2-1EmlA2R-.js} +1 -1
  66. package/dashboard/dist/assets/{modifiers.esm-BaJJxzf2.js → modifiers.esm-DGXFSuEq.js} +1 -1
  67. package/dashboard/dist/assets/module-D2D5r9gC.js +3 -0
  68. package/dashboard/dist/assets/{monitor-smartphone-pAR3IHow.js → monitor-smartphone-DsPPhvsT.js} +1 -1
  69. package/dashboard/dist/assets/{provider-display-CyYhYVar.js → provider-display-C20LtBC5.js} +1 -1
  70. package/dashboard/dist/assets/{react-BldsIPA1.js → react-B4FPJgs_.js} +1 -1
  71. package/dashboard/dist/assets/{search-xIfdCEo0.js → search-mkIFPKzW.js} +1 -1
  72. package/dashboard/dist/assets/{skills-api-DZNsCHPo.js → skills-api-D24vlu2H.js} +1 -1
  73. package/dashboard/dist/assets/{sortable.esm-DMaj448G.js → sortable.esm-BNDcP0sW.js} +1 -1
  74. package/dashboard/dist/assets/{store-VTEegajU.js → store-B2nqaAqP.js} +1 -1
  75. package/dashboard/dist/assets/{terminal-Bj8SotRv.js → terminal-BxVMmvEx.js} +1 -1
  76. package/dashboard/dist/assets/{trash-2-CWgY_FBc.js → trash-2-crt9lDvj.js} +1 -1
  77. package/dashboard/dist/assets/{use-community-stats-BOVvh_Gv.js → use-community-stats-CTB_uHWV.js} +1 -1
  78. package/dashboard/dist/assets/{use-limits-display-prefs-MtLsQ5kN.js → use-limits-display-prefs-DXXZpjGY.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list-DjCZVinv.js → use-ordered-list-BVNb5QWU.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-aSaQtoJu.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-DASihY0W.js → use-session-efficiency-pref-Di8nY6Lr.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-tHlm2id8.js → use-transform-Dx9C2nBC.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-BBEHNJKl.js → use-usage-limits-D9vQFB1H.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-DgTCDMXb.js → useCurrency-7dkDciam.js} +1 -1
  85. package/dashboard/dist/assets/{useScrollLock-C3TFkbz0.js → useScrollLock-BUVUIPTX.js} +1 -1
  86. package/dashboard/dist/assets/{useTokenFormat-BtqPANfZ.js → useTokenFormat-DFSIAsAb.js} +1 -1
  87. package/dashboard/dist/assets/{zap-CJNTZ87b.js → zap-Png-VJSP.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 +159 -9
  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 +252 -22
  98. package/src/lib/usage-limits.js +119 -1
  99. package/dashboard/dist/assets/DialogDescription-DAKoQCEv.js +0 -1
  100. package/dashboard/dist/assets/__vite-browser-external-D_dICEUB.js +0 -1575
  101. package/dashboard/dist/assets/module-CV1mYFsv.js +0 -3
  102. package/dashboard/dist/assets/use-reduced-motion-DjWNXuSi.js +0 -1
@@ -24,6 +24,26 @@ const CLAUDE_MEM_OBSERVER_PROJECT_REF =
24
24
  "https://local.tokentracker/claude-mem/observer-sessions";
25
25
  const PROJECT_ABSENT_CONTEXT_RESCAN_MS = 24 * 60 * 60 * 1000;
26
26
  const DEFAULT_CODEX_COLD_SKIP_RECENT_DAYS = 2;
27
+ const FILE_METADATA_CONCURRENCY = 32;
28
+
29
+ async function mapConcurrent(items, concurrency, mapper) {
30
+ if (!Array.isArray(items) || items.length === 0) return [];
31
+ const results = new Array(items.length);
32
+ let nextIndex = 0;
33
+ const workerCount = Math.min(
34
+ items.length,
35
+ Math.max(1, Math.floor(Number(concurrency) || 1)),
36
+ );
37
+ const workers = Array.from({ length: workerCount }, async () => {
38
+ while (nextIndex < items.length) {
39
+ const index = nextIndex;
40
+ nextIndex += 1;
41
+ results[index] = await mapper(items[index], index);
42
+ }
43
+ });
44
+ await Promise.all(workers);
45
+ return results;
46
+ }
27
47
 
28
48
  async function listRolloutFiles(sessionsDir, options = {}) {
29
49
  const out = [];
@@ -41,23 +61,37 @@ async function listRolloutFiles(sessionsDir, options = {}) {
41
61
  dayInventoryCache.days = {};
42
62
  }
43
63
  }
44
- const years = await safeReadDir(sessionsDir);
45
- for (const y of years) {
46
- if (!/^[0-9]{4}$/.test(y.name) || !y.isDirectory()) continue;
47
- const yearDir = path.join(sessionsDir, y.name);
48
- const months = await safeReadDir(yearDir);
49
- for (const m of months) {
50
- if (!/^[0-9]{2}$/.test(m.name) || !m.isDirectory()) continue;
51
- const monthDir = path.join(yearDir, m.name);
64
+ const years = (await safeReadDir(sessionsDir))
65
+ .filter((entry) => /^[0-9]{4}$/.test(entry.name) && entry.isDirectory());
66
+ const monthGroups = await mapConcurrent(
67
+ years,
68
+ FILE_METADATA_CONCURRENCY,
69
+ async (year) => {
70
+ const yearDir = path.join(sessionsDir, year.name);
71
+ const months = await safeReadDir(yearDir);
72
+ return months
73
+ .filter((entry) => /^[0-9]{2}$/.test(entry.name) && entry.isDirectory())
74
+ .map((entry) => path.join(yearDir, entry.name));
75
+ },
76
+ );
77
+ const monthDirs = monthGroups.flat();
78
+ const dayGroups = await mapConcurrent(
79
+ monthDirs,
80
+ FILE_METADATA_CONCURRENCY,
81
+ async (monthDir) => {
52
82
  const days = await safeReadDir(monthDir);
53
- for (const d of days) {
54
- if (!/^[0-9]{2}$/.test(d.name) || !d.isDirectory()) continue;
55
- const dayDir = path.join(monthDir, d.name);
56
- const files = await listRolloutDayFiles(dayDir, { dayInventoryCache, stats });
57
- out.push(...files);
58
- }
59
- }
60
- }
83
+ return days
84
+ .filter((entry) => /^[0-9]{2}$/.test(entry.name) && entry.isDirectory())
85
+ .map((entry) => path.join(monthDir, entry.name));
86
+ },
87
+ );
88
+ const dayDirs = dayGroups.flat();
89
+ const fileGroups = await mapConcurrent(
90
+ dayDirs,
91
+ FILE_METADATA_CONCURRENCY,
92
+ (dayDir) => listRolloutDayFiles(dayDir, { dayInventoryCache, stats }),
93
+ );
94
+ for (const files of fileGroups) out.push(...files);
61
95
 
62
96
  out.sort((a, b) => a.localeCompare(b));
63
97
  return out;
@@ -354,6 +388,18 @@ async function parseRolloutIncremental({
354
388
  return false;
355
389
  };
356
390
 
391
+ // Metadata reads are independent while parsing and bucket mutation are not.
392
+ // Prefetch stats with bounded concurrency, then retain the original stable
393
+ // file order for parsing, deduplication, cursor updates, and queue writes.
394
+ const rolloutStats = await mapConcurrent(
395
+ rolloutFiles,
396
+ FILE_METADATA_CONCURRENCY,
397
+ async (entry) => {
398
+ const filePath = typeof entry === "string" ? entry : entry?.path;
399
+ if (!filePath) return null;
400
+ return fs.stat(filePath).catch(() => null);
401
+ },
402
+ );
357
403
  for (let idx = 0; idx < rolloutFiles.length; idx++) {
358
404
  const entry = rolloutFiles[idx];
359
405
  const filePath = typeof entry === "string" ? entry : entry?.path;
@@ -363,7 +409,7 @@ async function parseRolloutIncremental({
363
409
  ? defaultSource
364
410
  : normalizeSourceInput(entry?.source) || defaultSource;
365
411
  if (syncDiagnostics && fileSource === DEFAULT_SOURCE) syncDiagnostics.stat_candidates += 1;
366
- const st = await fs.stat(filePath).catch(() => null);
412
+ const st = rolloutStats[idx];
367
413
  if (!st || !st.isFile()) continue;
368
414
 
369
415
  const key = filePath;
@@ -4422,8 +4468,7 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4422
4468
  // by the existing cursor-key + #426 fingerprint dedup in
4423
4469
  // parseOpencodeDbIncremental, so unioning here is safe — the downstream
4424
4470
  // parser collapses identical sessionID|messageID pairs before bucketing.
4425
- const collectRows = (rows, isV2) => {
4426
- const out = [];
4471
+ const appendRows = (rows, isV2, out) => {
4427
4472
  for (const row of rows) {
4428
4473
  if (!row || typeof row.data !== "string") continue;
4429
4474
  let data;
@@ -4450,7 +4495,6 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4450
4495
  data,
4451
4496
  });
4452
4497
  }
4453
- return out;
4454
4498
  };
4455
4499
 
4456
4500
  const readGeneration = (sql) => {
@@ -4477,11 +4521,11 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4477
4521
  const out = [];
4478
4522
 
4479
4523
  const rows1 = readGeneration(OPENCODE_DB_V1_MESSAGE_SQL);
4480
- if (rows1) out.push(...collectRows(rows1, false));
4524
+ if (rows1) appendRows(rows1, false, out);
4481
4525
 
4482
4526
  if (probe?.hasRows) {
4483
4527
  const rows2 = readGeneration(buildV2Sql(probe.sessionTable));
4484
- if (rows2) out.push(...collectRows(rows2, true));
4528
+ if (rows2) appendRows(rows2, true, out);
4485
4529
  }
4486
4530
 
4487
4531
  return out;
@@ -4551,10 +4595,195 @@ function isZcodeNativeMessage(data) {
4551
4595
  );
4552
4596
  }
4553
4597
 
4598
+ const ZCODE_NATIVE_USAGE_COLUMNS = new Set([
4599
+ "id",
4600
+ "logical_request_id",
4601
+ "attempt_index",
4602
+ "session_id",
4603
+ "provider_id",
4604
+ "model_id",
4605
+ "status",
4606
+ "started_at",
4607
+ "input_tokens",
4608
+ "output_tokens",
4609
+ "reasoning_tokens",
4610
+ "cache_creation_input_tokens",
4611
+ "cache_read_input_tokens",
4612
+ ]);
4613
+
4614
+ function detectZcodeNativeUsageLayout(dbPath, sqliteOptions = {}) {
4615
+ if (!dbPath || !fssync.existsSync(dbPath)) return null;
4616
+ let rows;
4617
+ try {
4618
+ rows = readSqliteJsonRows(
4619
+ dbPath,
4620
+ `SELECT 'model_usage' AS table_name, name FROM pragma_table_info('model_usage')
4621
+ UNION ALL
4622
+ SELECT 'session' AS table_name, name FROM pragma_table_info('session')`,
4623
+ {
4624
+ label: "ZCode",
4625
+ timeout: 10_000,
4626
+ maxBuffer: 1024 * 1024,
4627
+ ...sqliteOptions,
4628
+ throwOnReadFailure: true,
4629
+ },
4630
+ );
4631
+ } catch (_error) {
4632
+ return null;
4633
+ }
4634
+ const modelUsageColumns = new Set(
4635
+ rows
4636
+ .filter((row) => !row?.table_name || row.table_name === "model_usage")
4637
+ .map((row) => String(row?.name || "")),
4638
+ );
4639
+ if (![...ZCODE_NATIVE_USAGE_COLUMNS].every((name) => modelUsageColumns.has(name))) {
4640
+ return null;
4641
+ }
4642
+ const sessionColumns = new Set(
4643
+ rows
4644
+ .filter((row) => row?.table_name === "session")
4645
+ .map((row) => String(row?.name || "")),
4646
+ );
4647
+ return {
4648
+ hasSessionDirectory: sessionColumns.has("id") && sessionColumns.has("directory"),
4649
+ };
4650
+ }
4651
+
4652
+ function hasZcodeNativeUsageSchema(dbPath, sqliteOptions = {}) {
4653
+ if (!dbPath || !fssync.existsSync(dbPath)) return false;
4654
+ let snapshot = null;
4655
+ let effectiveDbPath = dbPath;
4656
+ if (isUncPath(dbPath)) {
4657
+ try {
4658
+ snapshot = snapshotSqliteDb(dbPath);
4659
+ effectiveDbPath = snapshot.path;
4660
+ } catch (_error) {
4661
+ // Fall through to the direct read: some UNC servers support SQLite's
4662
+ // read-only locking semantics even when the WSL bridge does not.
4663
+ }
4664
+ }
4665
+ try {
4666
+ return detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions) !== null;
4667
+ } finally {
4668
+ if (snapshot) snapshot.cleanup();
4669
+ }
4670
+ }
4671
+
4672
+ function buildZcodeNativeUsageSql({ hasSessionDirectory }) {
4673
+ const directorySelect = hasSessionDirectory ? ", s.directory AS directory" : "";
4674
+ const directoryJoin = hasSessionDirectory
4675
+ ? " LEFT JOIN session AS s ON s.id = mu.session_id"
4676
+ : "";
4677
+ return `SELECT
4678
+ mu.id,
4679
+ mu.logical_request_id,
4680
+ mu.attempt_index,
4681
+ mu.session_id,
4682
+ mu.provider_id,
4683
+ mu.model_id,
4684
+ mu.started_at,
4685
+ mu.input_tokens,
4686
+ mu.output_tokens,
4687
+ mu.reasoning_tokens,
4688
+ mu.cache_creation_input_tokens,
4689
+ mu.cache_read_input_tokens
4690
+ ${directorySelect}
4691
+ FROM model_usage AS mu${directoryJoin}
4692
+ WHERE mu.status = 'completed'
4693
+ AND trim(mu.model_id) != ''
4694
+ AND (
4695
+ mu.input_tokens > 0 OR mu.output_tokens > 0 OR mu.reasoning_tokens > 0 OR
4696
+ mu.cache_creation_input_tokens > 0 OR mu.cache_read_input_tokens > 0
4697
+ )
4698
+ ORDER BY mu.started_at ASC, mu.id ASC`;
4699
+ }
4700
+
4701
+ function readZcodeNativeUsageMessages(dbPath, sqliteOptions = {}) {
4702
+ let snapshot = null;
4703
+ let effectiveDbPath = dbPath;
4704
+ if (isUncPath(dbPath)) {
4705
+ try {
4706
+ snapshot = snapshotSqliteDb(dbPath);
4707
+ effectiveDbPath = snapshot.path;
4708
+ } catch (_error) {
4709
+ // Preserve the existing direct-read fallback for transient snapshot
4710
+ // failures and UNC implementations that support SQLite locking.
4711
+ }
4712
+ }
4713
+
4714
+ let rows;
4715
+ try {
4716
+ const layout = detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions);
4717
+ if (!layout) return null;
4718
+ rows = readSqliteJsonRows(effectiveDbPath, buildZcodeNativeUsageSql(layout), {
4719
+ label: "ZCode",
4720
+ maxBuffer: 50 * 1024 * 1024,
4721
+ timeout: 30_000,
4722
+ ...sqliteOptions,
4723
+ throwOnReadFailure: true,
4724
+ });
4725
+ } catch (_error) {
4726
+ return null;
4727
+ } finally {
4728
+ if (snapshot) snapshot.cleanup();
4729
+ }
4730
+
4731
+ const messages = [];
4732
+ for (const row of rows) {
4733
+ const providerID = String(row?.provider_id || "").trim();
4734
+ const modelID = String(row?.model_id || "").trim();
4735
+ const sessionID = String(row?.session_id || "").trim();
4736
+ const logicalRequestId = String(row?.logical_request_id || "").trim();
4737
+ const attemptIndex = toNonNegativeInt(row?.attempt_index);
4738
+ const id = String(row?.id || "").trim() ||
4739
+ (logicalRequestId ? `${logicalRequestId}#${attemptIndex}` : "");
4740
+ const startedAt = coerceEpochMs(row?.started_at);
4741
+ if (!providerID || !modelID || !sessionID || !id || !startedAt) continue;
4742
+
4743
+ // ZCode's persisted input/output columns are inclusive parents:
4744
+ // cache read/write are subsets of input, and reasoning is a subset of
4745
+ // output. TokenTracker stores those five columns disjointly, so split the
4746
+ // subsets here before entering the shared OpenCode parser. This preserves
4747
+ // sum(input + cache read + cache write + output + reasoning) without
4748
+ // billing either subset twice.
4749
+ const rawInput = toNonNegativeInt(row?.input_tokens);
4750
+ const rawOutput = toNonNegativeInt(row?.output_tokens);
4751
+ const cacheRead = toNonNegativeInt(row?.cache_read_input_tokens);
4752
+ const cacheWrite = toNonNegativeInt(row?.cache_creation_input_tokens);
4753
+ const reasoning = toNonNegativeInt(row?.reasoning_tokens);
4754
+
4755
+ const data = {
4756
+ id,
4757
+ sessionID,
4758
+ role: "assistant",
4759
+ providerID,
4760
+ modelID,
4761
+ time: { created: startedAt, completed: startedAt },
4762
+ tokens: {
4763
+ input: Math.max(0, rawInput - cacheRead - cacheWrite),
4764
+ output: Math.max(0, rawOutput - reasoning),
4765
+ reasoning,
4766
+ cache: {
4767
+ read: cacheRead,
4768
+ write: cacheWrite,
4769
+ },
4770
+ },
4771
+ };
4772
+ if (typeof row?.directory === "string" && row.directory.trim()) {
4773
+ data.path = { cwd: row.directory.trim() };
4774
+ }
4775
+ if (!isZcodeNativeMessage(data)) continue;
4776
+ messages.push({ id, sessionID, timeUpdated: startedAt, data });
4777
+ }
4778
+ return messages;
4779
+ }
4780
+
4554
4781
  // Read only genuine ZCode assistant messages (its own GLM models via Z.ai /
4555
4782
  // BigModel), dropping any bundled sub-agent turns. See isZcodeNativeMessage.
4556
4783
  function readZcodeDbMessages(dbPath, sqliteOptions = {}) {
4557
4784
  if (!dbPath || !fssync.existsSync(dbPath)) return [];
4785
+ const nativeMessages = readZcodeNativeUsageMessages(dbPath, sqliteOptions);
4786
+ if (nativeMessages !== null) return nativeMessages;
4558
4787
  const all = readOpencodeDbMessages(dbPath, sqliteOptions);
4559
4788
  return all.filter((m) => isZcodeNativeMessage(m.data));
4560
4789
  }
@@ -18054,6 +18283,7 @@ module.exports = {
18054
18283
  readOpencodeDbMessages,
18055
18284
  readMimoDbMessages,
18056
18285
  readZcodeDbMessages,
18286
+ hasZcodeNativeUsageSchema,
18057
18287
  resolveQoderDbPath,
18058
18288
  resolveQoderDbPaths,
18059
18289
  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};