tokentracker-cli 1.4.4 → 1.4.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 (122) hide show
  1. package/README.de.md +1 -0
  2. package/README.ja.md +1 -0
  3. package/README.ko.md +1 -0
  4. package/README.md +1 -0
  5. package/README.zh-CN.md +1 -0
  6. package/dashboard/dist/assets/{AchievementBadge-slL-XBCu.js → AchievementBadge-BmWRsZMx.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-BeFkGc5M.js → AchievementsPage-CyIP2nck.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-BrGUIipD.js → AchievementsSection-BeNyJ0LW.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-avmK1ODc.js → BadgeDetailModal-DIFnFzkv.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-DaEPdEZi.js → BotAnimated-CKVeJHit.js} +1 -1
  11. package/dashboard/dist/assets/{Card-CIZCccTA.js → Card-lQmyKbh4.js} +1 -1
  12. package/dashboard/dist/assets/{CloudCheckoutPage-BhRj0SYO.js → CloudCheckoutPage-B_8y6dyX.js} +1 -1
  13. package/dashboard/dist/assets/{CloudPage-CvaN8a54.js → CloudPage-BOQjUGAt.js} +2 -2
  14. package/dashboard/dist/assets/{CommandPalette-BUNuXmYF.js → CommandPalette-BV9LqokI.js} +1 -1
  15. package/dashboard/dist/assets/{CommunityStatsModal-BCI0WHPr.js → CommunityStatsModal-WELxKEFc.js} +1 -1
  16. package/dashboard/dist/assets/{ConfirmModal-CT7IwtLR.js → ConfirmModal-B8H5o8c_.js} +1 -1
  17. package/dashboard/dist/assets/{Controls-CtnMyxNH.js → Controls-9ynPx9rF.js} +1 -1
  18. package/dashboard/dist/assets/{DashboardPage-BuzyITeL.js → DashboardPage-B8z-U63p.js} +1 -1
  19. package/dashboard/dist/assets/{DateRangePopover-1Ps1-1dw.js → DateRangePopover-DEqWsV_8.js} +1 -1
  20. package/dashboard/dist/assets/{DevicePage-Dt-dVQAG.js → DevicePage-CUOlUySM.js} +1 -1
  21. package/dashboard/dist/assets/{DialogClose-D-DRTF1u.js → DialogClose-Dlg52bL6.js} +1 -1
  22. package/dashboard/dist/assets/DialogDescription-vuQFBlM_.js +1 -0
  23. package/dashboard/dist/assets/{DialogTitle-CiiRTn3_.js → DialogTitle-CduwXjqH.js} +1 -1
  24. package/dashboard/dist/assets/{ElectricLogo-k6RFJ2qC.js → ElectricLogo-DFt7_F2f.js} +1 -1
  25. package/dashboard/dist/assets/{FadeIn-CNHg83aK.js → FadeIn-DlyusE6n.js} +1 -1
  26. package/dashboard/dist/assets/{HeaderGithubStar-CIYW5dc3.js → HeaderGithubStar-tUtJoFHh.js} +1 -1
  27. package/dashboard/dist/assets/{HoverTooltip-Dn80DyPM.js → HoverTooltip-v-zkCqsX.js} +1 -1
  28. package/dashboard/dist/assets/{IpCheckPage-B29dYfoI.js → IpCheckPage-C5Y1uVEr.js} +1 -1
  29. package/dashboard/dist/assets/{LandingPage-Dzc8_QyW.js → LandingPage-DzOJOAj7.js} +2 -2
  30. package/dashboard/dist/assets/{LeaderboardPage-DXPcIhyQ.js → LeaderboardPage-BBZ_6tCw.js} +2 -2
  31. package/dashboard/dist/assets/{LeaderboardProBadge-DxEdj2Zq.js → LeaderboardProBadge-DakoXIQn.js} +1 -1
  32. package/dashboard/dist/assets/{LeaderboardProfileModal-DnKY2223.js → LeaderboardProfileModal-CINpES_x.js} +3 -3
  33. package/dashboard/dist/assets/{LeaderboardProfilePage-CY94ngqy.js → LeaderboardProfilePage-B6ISf-1l.js} +1 -1
  34. package/dashboard/dist/assets/{LimitsPage-AlvnAPCD.js → LimitsPage-UVX730CQ.js} +8 -8
  35. package/dashboard/dist/assets/{LocalOnlyNotice-EHw7BeYS.js → LocalOnlyNotice-BJSsmXF1.js} +1 -1
  36. package/dashboard/dist/assets/{LoginCard-E5E3i2kj.js → LoginCard-Dggv2aGv.js} +1 -1
  37. package/dashboard/dist/assets/{LoginModal-DxhQI9kh.js → LoginModal-B8qDd2kY.js} +1 -1
  38. package/dashboard/dist/assets/{LoginPage-DRwJn-Fw.js → LoginPage-Dv66U8yJ.js} +1 -1
  39. package/dashboard/dist/assets/{PetPage-DIpVTdiC.js → PetPage-CNwrQdKM.js} +2 -2
  40. package/dashboard/dist/assets/{PopoverPopup-Bjs7E91g.js → PopoverPopup-CqmvpYOi.js} +1 -1
  41. package/dashboard/dist/assets/{ProviderIcon-DqXIjym-.js → ProviderIcon-BF2Ch7wi.js} +1 -1
  42. package/dashboard/dist/assets/{ResetPasswordPage-DxSRYQgP.js → ResetPasswordPage-B0R8VUU3.js} +1 -1
  43. package/dashboard/dist/assets/{Select-BfjrcaiM.js → Select-idAiBnJG.js} +1 -1
  44. package/dashboard/dist/assets/{SelectItemText-Dpdlb0cU.js → SelectItemText-DPmmPY_D.js} +1 -1
  45. package/dashboard/dist/assets/{SelfHostPage-Cnp_PMaD.js → SelfHostPage-oR9cPK02.js} +1 -1
  46. package/dashboard/dist/assets/{ServiceStatusPage-fBwG-qXq.js → ServiceStatusPage-CdV9gfGF.js} +1 -1
  47. package/dashboard/dist/assets/{SessionsPage-BEk7AHdq.js → SessionsPage-BkD34VlL.js} +1 -1
  48. package/dashboard/dist/assets/{SettingsPage-BgML2jQR.js → SettingsPage-DsthWCyM.js} +3 -3
  49. package/dashboard/dist/assets/{SkillsPage-DcvVZ3Wn.js → SkillsPage-CzMk9s3x.js} +1 -1
  50. package/dashboard/dist/assets/{TokenGalaxy-D29yX2Hb.js → TokenGalaxy-DQ6o8YBp.js} +1 -1
  51. package/dashboard/dist/assets/{TooltipPopup-CjzmNK2l.js → TooltipPopup-CMQjA-HE.js} +1 -1
  52. package/dashboard/dist/assets/{TrendMonitor-DfnQrsrx.js → TrendMonitor-DrV1m8E7.js} +1 -1
  53. package/dashboard/dist/assets/{WidgetsPage-CRYomYfk.js → WidgetsPage-Cc16WM0Y.js} +1 -1
  54. package/dashboard/dist/assets/{WrappedPage-CvQI5zUw.js → WrappedPage-pMwbM4R7.js} +1 -1
  55. package/dashboard/dist/assets/{__vite-browser-external-BZ-i40Kf.js → __vite-browser-external-DCy_YLfe.js} +3 -3
  56. package/dashboard/dist/assets/{agent-logos-CvOX7WXW.js → agent-logos-Zw7LNkMO.js} +1 -1
  57. package/dashboard/dist/assets/{arrow-left-BrQrZJtW.js → arrow-left-DycYjE-k.js} +1 -1
  58. package/dashboard/dist/assets/{arrow-right-fC11gmFY.js → arrow-right-uNxLHoJ0.js} +1 -1
  59. package/dashboard/dist/assets/{arrow-up-right-CCMEqQQp.js → arrow-up-right-CmdJ7gd7.js} +1 -1
  60. package/dashboard/dist/assets/{calendar-days-OaIQ06qD.js → calendar-days-CJ33_t5O.js} +1 -1
  61. package/dashboard/dist/assets/{check-D888uFpx.js → check-Dbubk4Il.js} +1 -1
  62. package/dashboard/dist/assets/{chevron-down-BJ0BpS_w.js → chevron-down-BwvBVAOs.js} +1 -1
  63. package/dashboard/dist/assets/{cloud-checkout-BeOpEJTZ.js → cloud-checkout-IG-IOpeP.js} +1 -1
  64. package/dashboard/dist/assets/{composite-CUuXvKos.js → composite-IIgJPPIw.js} +1 -1
  65. package/dashboard/dist/assets/{download-BJ7CKwx5.js → download-DxtH7w6a.js} +1 -1
  66. package/dashboard/dist/assets/{external-link-CDaZwJVN.js → external-link-evKf3mnD.js} +1 -1
  67. package/dashboard/dist/assets/{flame-ChyUKs-9.js → flame-D2qOE_W4.js} +1 -1
  68. package/dashboard/dist/assets/{format-tokens-B6WX7zcH.js → format-tokens-CqDmm-sK.js} +1 -1
  69. package/dashboard/dist/assets/{gift-CQlAZUJ5.js → gift-Ge11HK1x.js} +1 -1
  70. package/dashboard/dist/assets/{host-mode-Gw6KD5OJ.js → host-mode-8_TNh7yE.js} +1 -1
  71. package/dashboard/dist/assets/{icons-C7gGTMuQ.js → icons-OmfWvE5K.js} +1 -1
  72. package/dashboard/dist/assets/{index-Bm2dDAVL.js → index-BYuEzP9p.js} +1 -1
  73. package/dashboard/dist/assets/{index-CLb64Tlj.js → index-BsYtyo4U.js} +1 -1
  74. package/dashboard/dist/assets/{index-C0pbymQa.js → index-tbe1aERA.js} +1 -1
  75. package/dashboard/dist/assets/{info-CvOmd88O.js → info-xhrso9aO.js} +1 -1
  76. package/dashboard/dist/assets/{link-2-BNEPxvlF.js → link-2-BgNpTaO4.js} +1 -1
  77. package/dashboard/dist/assets/{loader-circle-bRHxCgY3.js → loader-circle-DRooZ0e5.js} +1 -1
  78. package/dashboard/dist/assets/{modifiers.esm-D-X0-uvt.js → modifiers.esm-D-QAEpOZ.js} +1 -1
  79. package/dashboard/dist/assets/{monitor-smartphone-Bo7d-q73.js → monitor-smartphone-Bwu7__2B.js} +1 -1
  80. package/dashboard/dist/assets/{play-DqpGELN5.js → play-CBPvTiWM.js} +1 -1
  81. package/dashboard/dist/assets/{plus-2fA1tz7S.js → plus-B4lFIyP8.js} +1 -1
  82. package/dashboard/dist/assets/{provider-display-B1KigDYZ.js → provider-display-CQlWusXH.js} +1 -1
  83. package/dashboard/dist/assets/{react-DYja9tZG.js → react-DDOtS4Bd.js} +1 -1
  84. package/dashboard/dist/assets/{safePolygon-BerB8y-A.js → safePolygon-BGfBVKoy.js} +1 -1
  85. package/dashboard/dist/assets/{search-CU6UVI1L.js → search-DVyNq72J.js} +1 -1
  86. package/dashboard/dist/assets/{server-CZR4PTdb.js → server-EpUTEsOf.js} +1 -1
  87. package/dashboard/dist/assets/{shield-check-BGbNmrMP.js → shield-check-DRg8cPKV.js} +1 -1
  88. package/dashboard/dist/assets/{skills-api-CJLEzxDf.js → skills-api-y42dEF96.js} +1 -1
  89. package/dashboard/dist/assets/{sortable.esm-CJ1NZWER.js → sortable.esm-D2S1gXCK.js} +1 -1
  90. package/dashboard/dist/assets/{store-CJFdru_6.js → store-DfGPS2IR.js} +1 -1
  91. package/dashboard/dist/assets/{terminal-ClyQ8tvQ.js → terminal-hXIrrL2b.js} +1 -1
  92. package/dashboard/dist/assets/{trash-Dg2PWl5-.js → trash-cjaASRQl.js} +1 -1
  93. package/dashboard/dist/assets/{use-community-stats-Ca995kap.js → use-community-stats-B-nipjWt.js} +1 -1
  94. package/dashboard/dist/assets/{use-latest-request-guard-BKbMGVji.js → use-latest-request-guard-DIEYrSzn.js} +1 -1
  95. package/dashboard/dist/assets/{use-limits-display-prefs-CGyd8lvc.js → use-limits-display-prefs-BdHDwBBC.js} +1 -1
  96. package/dashboard/dist/assets/{use-native-settings-Dqeuqm9O.js → use-native-settings-7w71_h44.js} +1 -1
  97. package/dashboard/dist/assets/{use-ordered-list-Nj7R1iUu.js → use-ordered-list-CSMpK4ar.js} +1 -1
  98. package/dashboard/dist/assets/use-reduced-motion-HUGf223R.js +1 -0
  99. package/dashboard/dist/assets/{use-session-efficiency-pref-C_H5BaGV.js → use-session-efficiency-pref-BagBVWW6.js} +1 -1
  100. package/dashboard/dist/assets/{use-transform-CC90hurD.js → use-transform-sjGGXCNn.js} +1 -1
  101. package/dashboard/dist/assets/use-usage-limits-Nml-wUB-.js +1 -0
  102. package/dashboard/dist/assets/{useCurrency-CMHIL39_.js → useCurrency-DAKIY1nR.js} +1 -1
  103. package/dashboard/dist/assets/{usePositioner-CRSfrGQq.js → usePositioner-B7vVQ7th.js} +1 -1
  104. package/dashboard/dist/assets/{useTokenFormat-B5jhM_Kb.js → useTokenFormat-gtgw8k2Z.js} +1 -1
  105. package/dashboard/dist/assets/{zap-CKx8ySZs.js → zap-DFWOiTJ1.js} +1 -1
  106. package/dashboard/dist/index.html +1 -1
  107. package/dashboard/dist/ip-check.html +1 -1
  108. package/dashboard/dist/leaderboard.html +1 -1
  109. package/dashboard/dist/share.html +1 -1
  110. package/package.json +1 -1
  111. package/src/commands/sync.js +546 -31
  112. package/src/lib/async-queue-reader.js +37 -16
  113. package/src/lib/codex-context-breakdown.js +8 -1
  114. package/src/lib/codex-rollout-parser.js +65 -5
  115. package/src/lib/grok-limits.js +56 -0
  116. package/src/lib/pricing/seed-snapshot.json +1 -1
  117. package/src/lib/rollout.js +356 -8
  118. package/src/lib/session-analytics.js +1 -1
  119. package/src/lib/usage-limits.js +33 -1
  120. package/dashboard/dist/assets/DialogDescription-BOYDS1mM.js +0 -1
  121. package/dashboard/dist/assets/use-reduced-motion-CGMbDpHQ.js +0 -1
  122. package/dashboard/dist/assets/use-usage-limits-DtaaE7D8.js +0 -1
@@ -96,10 +96,13 @@ async function listRolloutFiles(sessionsDir, options = {}) {
96
96
  : null;
97
97
  const stats = options?.stats && typeof options.stats === "object" ? options.stats : null;
98
98
  if (dayInventoryCache) {
99
- if (dayInventoryCache.version !== 1) {
99
+ // v2: isRolloutFileName also accepts rollout-*.jsonl.zst/.zstd. Old v1
100
+ // day lists omit archives that were already on disk when the directory
101
+ // metadata last matched, so bump to force a one-time rescan.
102
+ if (dayInventoryCache.version !== 2) {
100
103
  dayInventoryCache.days = {};
101
104
  }
102
- dayInventoryCache.version = 1;
105
+ dayInventoryCache.version = 2;
103
106
  if (!dayInventoryCache.days || typeof dayInventoryCache.days !== "object") {
104
107
  dayInventoryCache.days = {};
105
108
  }
@@ -180,7 +183,75 @@ function isRolloutFileName(name) {
180
183
  if (typeof name !== "string") return false;
181
184
  if (name.includes("/") || name.includes("\\")) return false;
182
185
  if (name !== path.basename(name)) return false;
183
- return name.startsWith("rollout-") && name.endsWith(".jsonl");
186
+ if (!name.startsWith("rollout-")) return false;
187
+ return name.endsWith(".jsonl") || isCompressedRolloutFileName(name);
188
+ }
189
+
190
+ // Compressed rollout archives cannot be tailed by byte offset: the cursor
191
+ // stores compressed bytes while the parser consumes decompressed text. Treat
192
+ // them as content-addressed snapshots — one parse per (inode, size) version —
193
+ // reusing the DSH session's zstd machinery (built-in zlib first,
194
+ // `@mongodb-js/zstd` fallback) including its multi-frame handling. Returns
195
+ // null for a missing/non-file path; an empty archive yields no lines (the
196
+ // same outcome as an empty uncompressed rollout).
197
+ const CODEX_ROLLOUT_COMPRESSED_PATTERN = /^rollout-.*\.jsonl\.zstd?$/;
198
+ const CODEX_ROLLOUT_TEXT_MAX_BYTES = 256 * 1024 * 1024;
199
+
200
+ function isCompressedRolloutFileName(name) {
201
+ return typeof name === "string" && CODEX_ROLLOUT_COMPRESSED_PATTERN.test(name);
202
+ }
203
+
204
+ function isCompressedRolloutPath(filePath) {
205
+ if (typeof filePath !== "string") return false;
206
+ return isCompressedRolloutFileName(path.basename(filePath));
207
+ }
208
+
209
+ async function readCodexRolloutSnapshot(filePath, { maxOutputBytes = CODEX_ROLLOUT_TEXT_MAX_BYTES } = {}) {
210
+ const handle = await fs.open(filePath, "r").catch(() => null);
211
+ if (!handle) return null;
212
+ try {
213
+ const initialStat = await handle.stat().catch(() => null);
214
+ if (!initialStat || !initialStat.isFile()) return null;
215
+ const outputLimit = Number.isSafeInteger(maxOutputBytes) && maxOutputBytes >= 0
216
+ ? maxOutputBytes
217
+ : CODEX_ROLLOUT_TEXT_MAX_BYTES;
218
+ let data;
219
+ try {
220
+ data = await handle.readFile();
221
+ } catch (_error) {
222
+ // Transient mount/IO failures (e.g. WSL ESTALE) must not abort the rest
223
+ // of sync; treat like a missing archive and keep the prior cursor.
224
+ return null;
225
+ }
226
+ const metadata = {
227
+ size: data.length,
228
+ mtimeMs: Number.isFinite(initialStat.mtimeMs) ? initialStat.mtimeMs : 0,
229
+ inode: initialStat.ino || 0,
230
+ };
231
+ if (data.length === 0) return { ...metadata, unchanged: false, lines: [] };
232
+ let decoded;
233
+ try {
234
+ const inspected = inspectDshZstdFrames(data, outputLimit);
235
+ decoded = await decodeDshZstd(data, { maxOutputBytes: outputLimit, inspected });
236
+ } catch (_error) {
237
+ // Malformed / truncated / oversized archives must not abort the rest of
238
+ // the sync: return null so the caller keeps the prior cursor and retries.
239
+ return null;
240
+ }
241
+ // Fatal UTF-8: uncompressed JSONL parsing rejects invalid bytes under
242
+ // invalidRecordPolicy=throw. toString("utf8") would silently replace them
243
+ // and claim utf8Valid, so a corrupted discriminator could drop usage
244
+ // during repairCodexRescanInflation. Fail the snapshot instead.
245
+ let text;
246
+ try {
247
+ text = new TextDecoder("utf-8", { fatal: true }).decode(decoded);
248
+ } catch (_error) {
249
+ return null;
250
+ }
251
+ return { ...metadata, unchanged: false, lines: text.length > 0 ? text.split("\n") : [] };
252
+ } finally {
253
+ await handle.close().catch(() => {});
254
+ }
184
255
  }
185
256
 
186
257
  function directoryInventoryStatKey(st) {
@@ -206,7 +277,7 @@ async function listRolloutFilesDeep(dir) {
206
277
  const p = path.join(d, e.name);
207
278
  if (e.isDirectory()) {
208
279
  await walk(p);
209
- } else if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
280
+ } else if (e.isFile() && isRolloutFileName(e.name)) {
210
281
  out.push(p);
211
282
  }
212
283
  }
@@ -517,6 +588,16 @@ async function parseRolloutIncremental({
517
588
  ? prev?.codexUsageRecord || (prev?.lastTotal ? { sawTokenCount: true } : null)
518
589
  : null;
519
590
 
591
+ // Compressed archives are whole-file snapshots. If the compressed bytes
592
+ // changed under the same inode, force historical dedup; parseRolloutFile
593
+ // ignores retained baselines for the parse itself. Do NOT clear cursor
594
+ // fields here — a soft decode failure must leave the prior cursor intact.
595
+ const compressedSnapshotRescan = isCompressedRolloutPath(filePath)
596
+ && sameInode
597
+ && !truncated
598
+ && prevOffset > 0
599
+ && prevOffset !== st.size;
600
+
520
601
  const codexProjectFastPath =
521
602
  projectEnabled && (fileSource === DEFAULT_SOURCE || fileSource === "acode");
522
603
  const projectOffset = sameInode && !truncated ? Number(prev.projectOffset || 0) : 0;
@@ -575,7 +656,8 @@ async function parseRolloutIncremental({
575
656
  ? getAcodeEventDedup()
576
657
  : null;
577
658
  const codexEventTracker = rolloutEventDedup
578
- ? (needsHistoricalRolloutDedup({
659
+ ? (compressedSnapshotRescan
660
+ || needsHistoricalRolloutDedup({
579
661
  filePath,
580
662
  prev,
581
663
  sameInode,
@@ -629,6 +711,22 @@ async function parseRolloutIncremental({
629
711
  invalidRecordPolicy,
630
712
  });
631
713
 
714
+ if (result.snapshotUnavailable) {
715
+ // Leave cursors.files[key] untouched so the next readable snapshot is
716
+ // compared against the prior (inode, offset, baselines).
717
+ if (cb) {
718
+ cb({
719
+ index: idx + 1,
720
+ total: totalFiles,
721
+ filePath,
722
+ filesProcessed,
723
+ eventsAggregated,
724
+ bucketsQueued: touchedBuckets.size,
725
+ });
726
+ }
727
+ continue;
728
+ }
729
+
632
730
  const nextCursor = {
633
731
  inode,
634
732
  offset: result.endOffset,
@@ -2256,7 +2354,7 @@ function openclawUsageEventIdentity(obj, msg, usage, model) {
2256
2354
  function codexSessionIdFromPath(filePath) {
2257
2355
  if (typeof filePath !== "string") return null;
2258
2356
  const m = filePath.match(
2259
- /([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\.jsonl$/,
2357
+ /([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\.jsonl(?:\.zstd?)?$/,
2260
2358
  );
2261
2359
  return m ? m[1] : null;
2262
2360
  }
@@ -2291,6 +2389,108 @@ async function parseRolloutFile({
2291
2389
  const endOffset = st.size;
2292
2390
  const projectFileContexts = [];
2293
2391
  addProjectFileContext(projectFileContexts, projectContext);
2392
+ // Compressed archives are immutable snapshots, not append-only logs: a byte
2393
+ // range of the compressed file is not a prefix of the decompressed text, so
2394
+ // resume-in-the-middle is impossible. Parse the whole snapshot whenever its
2395
+ // (inode, size) identity is new; unchanged snapshots are skipped by the
2396
+ // caller before reaching this function.
2397
+ if (isCompressedRolloutPath(filePath)) {
2398
+ // Mirror the uncompressed exhausted-cursor guard. When a custom
2399
+ // publicRepoResolver disables the outer skip, an unchanged archive must
2400
+ // not be re-parsed with retained baselines (that inflates totals).
2401
+ if (startOffset >= endOffset) {
2402
+ return {
2403
+ endOffset,
2404
+ lastTotal,
2405
+ tokenUsageBaselines,
2406
+ lastModel,
2407
+ lastServiceTier: typeof lastServiceTier === "string" ? lastServiceTier : null,
2408
+ modelAttributionState: previousModelAttributionState,
2409
+ usageRecordState: previousUsageRecordState,
2410
+ eventsAggregated: 0,
2411
+ projectFileContexts,
2412
+ };
2413
+ }
2414
+ const snapshot = await readCodexRolloutSnapshot(filePath);
2415
+ if (!snapshot) {
2416
+ // Soft sync skips a bad archive and keeps the prior cursor so later
2417
+ // files still count. Strict rebuilds (invalidRecordPolicy=throw), used
2418
+ // by repairCodexRescanInflation, must fail closed: a silent skip here
2419
+ // would let another readable session satisfy the non-empty-bucket
2420
+ // safeguard and permanently delete this archive's prior usage.
2421
+ if (invalidRecordPolicy === "throw") {
2422
+ throw new Error(`unreadable Codex compressed archive: ${filePath}`);
2423
+ }
2424
+ return {
2425
+ endOffset: startOffset,
2426
+ lastTotal,
2427
+ tokenUsageBaselines,
2428
+ lastModel,
2429
+ lastServiceTier: typeof lastServiceTier === "string" ? lastServiceTier : null,
2430
+ modelAttributionState: previousModelAttributionState,
2431
+ usageRecordState: previousUsageRecordState,
2432
+ eventsAggregated: 0,
2433
+ projectFileContexts,
2434
+ // Tell the incremental caller not to rewrite cursors.files[key]. If the
2435
+ // archive inode changed under a transient decode miss, overwriting the
2436
+ // prior cursor would drop historical-dedup baselines and double-count.
2437
+ snapshotUnavailable: true,
2438
+ };
2439
+ }
2440
+ // Decompressed lines have no meaningful compressed-byte offsets; the
2441
+ // shared parser only needs their content plus termination flags. Byte
2442
+ // counters stay at the snapshot's compressed size so the cursor records
2443
+ // (inode, size) identity, not decompressed progress.
2444
+ const records = (async function* snapshotRecords() {
2445
+ for (let index = 0; index < snapshot.lines.length; index += 1) {
2446
+ const raw = snapshot.lines[index];
2447
+ const terminated = index + 1 < snapshot.lines.length || raw.endsWith("\n");
2448
+ yield {
2449
+ line: terminated ? raw : `${raw}`,
2450
+ physicalBytes: 0,
2451
+ terminated,
2452
+ utf8Valid: true,
2453
+ };
2454
+ }
2455
+ })();
2456
+ // Size changed under the same inode: drop retained baselines for THIS
2457
+ // parse only. Soft-fail above still returns the caller-supplied baselines
2458
+ // so the prior cursor is preserved when decode fails.
2459
+ const rescanBaselines = startOffset > 0 && startOffset < endOffset;
2460
+ const parsed = await parseRolloutRecords({
2461
+ records,
2462
+ startOffset: 0,
2463
+ endOffset: 0,
2464
+ lastTotal: rescanBaselines ? null : lastTotal,
2465
+ tokenUsageBaselines: rescanBaselines ? null : tokenUsageBaselines,
2466
+ lastModel: rescanBaselines ? null : lastModel,
2467
+ lastServiceTier: rescanBaselines ? null : lastServiceTier,
2468
+ modelAttributionState: rescanBaselines ? null : previousModelAttributionState,
2469
+ usageRecordState: rescanBaselines ? null : previousUsageRecordState,
2470
+ compactionResponseIds,
2471
+ hourlyState,
2472
+ touchedBuckets,
2473
+ source,
2474
+ projectState,
2475
+ projectTouchedBuckets,
2476
+ projectRef,
2477
+ projectKey,
2478
+ projectMetaCache,
2479
+ publicRepoCache,
2480
+ publicRepoResolver,
2481
+ projectContext,
2482
+ projectFileContexts,
2483
+ seenCodexEvents,
2484
+ sessionId,
2485
+ filePath,
2486
+ invalidRecordPolicy,
2487
+ });
2488
+ // parseRolloutRecords counts streamed bytes; snapshots have none, so pin
2489
+ // the cursor to the compressed size — the (inode, size) identity that
2490
+ // makes an unchanged archive skip on the next sync.
2491
+ parsed.endOffset = snapshot.size;
2492
+ return parsed;
2493
+ }
2294
2494
  if (startOffset >= endOffset) {
2295
2495
  return {
2296
2496
  endOffset,
@@ -2309,7 +2509,78 @@ async function parseRolloutFile({
2309
2509
  start: startOffset,
2310
2510
  end: endOffset - 1,
2311
2511
  });
2512
+ // Compressed snapshots never reach this branch (parseRolloutFile routes them
2513
+ // to parseRolloutRecords directly); the byte-counting stream below is only
2514
+ // meaningful for append-only uncompressed logs.
2515
+ const records = (async function* streamRecords() {
2516
+ const invalidUtf8 = invalidRecordPolicy === "throw" ? "throw" : "record";
2517
+ for await (const record of physicalJsonlRecords(stream, { invalidUtf8 })) {
2518
+ yield record;
2519
+ }
2520
+ })();
2521
+
2522
+ return parseRolloutRecords({
2523
+ records,
2524
+ startOffset,
2525
+ endOffset,
2526
+ lastTotal,
2527
+ tokenUsageBaselines,
2528
+ lastModel,
2529
+ lastServiceTier,
2530
+ modelAttributionState: previousModelAttributionState,
2531
+ usageRecordState: previousUsageRecordState,
2532
+ compactionResponseIds,
2533
+ hourlyState,
2534
+ touchedBuckets,
2535
+ source,
2536
+ projectState,
2537
+ projectTouchedBuckets,
2538
+ projectRef,
2539
+ projectKey,
2540
+ projectMetaCache,
2541
+ publicRepoCache,
2542
+ publicRepoResolver,
2543
+ projectContext,
2544
+ projectFileContexts,
2545
+ seenCodexEvents,
2546
+ sessionId,
2547
+ filePath,
2548
+ invalidRecordPolicy,
2549
+ });
2550
+ }
2312
2551
 
2552
+ async function parseRolloutRecords({
2553
+ records,
2554
+ startOffset,
2555
+ endOffset,
2556
+ lastTotal,
2557
+ tokenUsageBaselines,
2558
+ lastModel,
2559
+ lastServiceTier,
2560
+ modelAttributionState: previousModelAttributionState,
2561
+ usageRecordState: previousUsageRecordState = null,
2562
+ compactionResponseIds = null,
2563
+ hourlyState,
2564
+ touchedBuckets,
2565
+ source,
2566
+ projectState,
2567
+ projectTouchedBuckets,
2568
+ projectRef,
2569
+ projectKey,
2570
+ projectMetaCache,
2571
+ publicRepoCache,
2572
+ publicRepoResolver,
2573
+ projectContext,
2574
+ projectFileContexts: existingProjectFileContexts,
2575
+ seenCodexEvents,
2576
+ sessionId,
2577
+ filePath,
2578
+ invalidRecordPolicy,
2579
+ }) {
2580
+ const projectFileContexts = Array.isArray(existingProjectFileContexts)
2581
+ ? existingProjectFileContexts
2582
+ : [];
2583
+ addProjectFileContext(projectFileContexts, projectContext);
2313
2584
  let model = typeof lastModel === "string" ? lastModel : null;
2314
2585
  // Codex writes thread_settings_applied before the turn_context of the turn it
2315
2586
  // takes effect on, and token_count rows carry no turn id, so a row's tier is
@@ -2393,8 +2664,7 @@ async function parseRolloutFile({
2393
2664
  eventsAggregated += 1;
2394
2665
  }
2395
2666
 
2396
- const invalidUtf8 = invalidRecordPolicy === "throw" ? "throw" : "record";
2397
- for await (const record of physicalJsonlRecords(stream, { invalidUtf8 })) {
2667
+ for await (const record of records) {
2398
2668
  scannedEndOffset += record.physicalBytes;
2399
2669
  if (record.terminated) committedEndOffset = scannedEndOffset;
2400
2670
  if (!record.utf8Valid) {
@@ -2695,6 +2965,80 @@ async function scanRolloutProjectFileContexts({
2695
2965
  };
2696
2966
  }
2697
2967
 
2968
+ // Compressed archives cannot be scanned as byte streams: JSONL newlines in
2969
+ // the compressed payload are not record boundaries. Decode the snapshot,
2970
+ // then walk decompressed lines for cwd changes. Pin the cursor to the
2971
+ // compressed size so the next sync's unchanged check keeps working.
2972
+ if (isCompressedRolloutPath(filePath)) {
2973
+ const snapshot = await readCodexRolloutSnapshot(filePath);
2974
+ if (!snapshot) {
2975
+ if (invalidRecordPolicy === "throw") {
2976
+ throw new Error(`unreadable Codex compressed archive: ${filePath}`);
2977
+ }
2978
+ return {
2979
+ endOffset,
2980
+ lastTotal,
2981
+ tokenUsageBaselines,
2982
+ lastModel,
2983
+ lastServiceTier: lastServiceTier || null,
2984
+ modelAttributionState,
2985
+ eventsAggregated: 0,
2986
+ projectFileContexts,
2987
+ snapshotUnavailable: true,
2988
+ };
2989
+ }
2990
+ let currentCwd = null;
2991
+ for (const raw of snapshot.lines) {
2992
+ const line = typeof raw === "string" ? raw : "";
2993
+ if (
2994
+ !line ||
2995
+ !(
2996
+ line.includes('"turn_context"') ||
2997
+ line.includes('"session_meta"')
2998
+ ) ||
2999
+ !line.includes('"cwd"')
3000
+ ) {
3001
+ continue;
3002
+ }
3003
+ let obj;
3004
+ try {
3005
+ obj = JSON.parse(line);
3006
+ } catch (error) {
3007
+ if (invalidRecordPolicy === "throw") throw error;
3008
+ continue;
3009
+ }
3010
+ if (
3011
+ (obj?.type !== "turn_context" && obj?.type !== "session_meta") ||
3012
+ !obj?.payload ||
3013
+ typeof obj.payload !== "object" ||
3014
+ typeof obj.payload.cwd !== "string"
3015
+ ) {
3016
+ continue;
3017
+ }
3018
+ const nextCwd = obj.payload.cwd.trim();
3019
+ if (!nextCwd || nextCwd === currentCwd) continue;
3020
+ const context = await resolveProjectContextForPath({
3021
+ startDir: wsl.mapWslCwdToUnc(nextCwd, filePath),
3022
+ projectMetaCache,
3023
+ publicRepoCache,
3024
+ publicRepoResolver,
3025
+ projectState,
3026
+ });
3027
+ currentCwd = nextCwd;
3028
+ addProjectFileContext(projectFileContexts, context);
3029
+ }
3030
+ return {
3031
+ endOffset: snapshot.size,
3032
+ lastTotal,
3033
+ tokenUsageBaselines,
3034
+ lastModel,
3035
+ lastServiceTier: lastServiceTier || null,
3036
+ modelAttributionState,
3037
+ eventsAggregated: 0,
3038
+ projectFileContexts,
3039
+ };
3040
+ }
3041
+
2698
3042
  const stream = fssync.createReadStream(filePath, { start: 0, end: endOffset - 1 });
2699
3043
  let currentCwd = null;
2700
3044
  let scannedEndOffset = 0;
@@ -25360,6 +25704,10 @@ module.exports = {
25360
25704
  listRolloutFiles,
25361
25705
  listRolloutFilesDeep,
25362
25706
  codexSessionIdFromPath,
25707
+ isRolloutFileName,
25708
+ isCompressedRolloutFileName,
25709
+ isCompressedRolloutPath,
25710
+ readCodexRolloutSnapshot,
25363
25711
  filterColdCodexRolloutFiles,
25364
25712
  listClaudeProjectFiles,
25365
25713
  listGeminiSessionFiles,
@@ -1915,7 +1915,7 @@ function groupCodexFiles(filePaths) {
1915
1915
  const ordered = [];
1916
1916
  const bySession = new Map();
1917
1917
  for (const filePath of [...new Set(filePaths || [])]) {
1918
- const id = path.basename(filePath).match(/([0-9a-f-]{36})\.jsonl$/i)?.[1] || filePath;
1918
+ const id = path.basename(filePath).match(/([0-9a-f-]{36})\.jsonl(?:\.zstd?)?$/i)?.[1] || filePath;
1919
1919
  let group = bySession.get(id);
1920
1920
  if (!group) {
1921
1921
  group = [];
@@ -744,6 +744,33 @@ function normalizeCursorUsageSummary(body) {
744
744
  };
745
745
  }
746
746
 
747
+ /**
748
+ * Cursor's Sand "Grok Bot" RPC reports the shared Super weekly pool (same
749
+ * reset and about the same percent as Grok billing's creditUsagePercent).
750
+ * Once the Grok card lists a Grok Bot product share, that Sand bar would
751
+ * show the whole pool a second time under Cursor. Hide it only in that
752
+ * case. A real separate Sand quota (different reset, or a percent more than
753
+ * a point away from the pool) stays. Accounts with no Grok Bot product row
754
+ * keep the Sand bar.
755
+ */
756
+ function cursorSandDuplicatesGrokPool(cursor, grok) {
757
+ const sand = cursor?.quaternary_window;
758
+ const pool = grok?.primary_window;
759
+ if (!sand || !pool || cursor?.error || grok?.error) return false;
760
+ const products = Array.isArray(grok.products) ? grok.products : [];
761
+ if (!products.some((entry) => entry?.product === "GrokBot" && entry.window)) return false;
762
+ const sandReset = Date.parse(sand.reset_at || "");
763
+ const poolReset = Date.parse(pool.reset_at || "");
764
+ // Same instant. Two seconds covers one side dropping milliseconds.
765
+ // A different billing window is days apart, so it still stays visible.
766
+ if (!Number.isFinite(sandReset) || !Number.isFinite(poolReset)) return false;
767
+ if (Math.abs(sandReset - poolReset) > 2000) return false;
768
+ const sandPct = Number(sand.used_percent);
769
+ const poolPct = Number(pool.used_percent);
770
+ if (!Number.isFinite(sandPct) || !Number.isFinite(poolPct)) return false;
771
+ return Math.abs(sandPct - poolPct) <= 1;
772
+ }
773
+
747
774
  function normalizeCursorSandUsageStatus(body, { eligible = true } = {}) {
748
775
  if (!eligible || !body || body.includedLimitZero === true) return null;
749
776
  const usedPercent = clampPercent(body.usagePercent);
@@ -4403,11 +4430,15 @@ async function fetchUsageLimitsUncached({
4403
4430
  : { configured: true, error: reason?.message || "Unknown error" };
4404
4431
  }
4405
4432
 
4433
+ const cursorLimits = cursorSandDuplicatesGrokPool(cursor, grok)
4434
+ ? { ...cursor, quaternary_window: null }
4435
+ : cursor;
4436
+
4406
4437
  const data = {
4407
4438
  fetched_at: new Date(nowMs).toISOString(),
4408
4439
  claude: withPlanLabel(claude, claudePlanType, "Claude"),
4409
4440
  codex: withPlanLabel(codex, codex.plan_type, "Codex"),
4410
- cursor: withPlanLabel(cursor, cursor.membership_type, "Cursor"),
4441
+ cursor: withPlanLabel(cursorLimits, cursorLimits.membership_type, "Cursor"),
4411
4442
  // Kimi's subType (TYPE_PURCHASE/TYPE_EVENT) is the credit *source*, not a plan
4412
4443
  // tier, and membership.level is an opaque enum (LEVEL_INTERMEDIATE) with no
4413
4444
  // authoritative human-readable plan mapping. Both rendered as garbage like
@@ -4484,6 +4515,7 @@ module.exports = {
4484
4515
  fetchKimiLimits,
4485
4516
  normalizeCursorUsageSummary,
4486
4517
  normalizeCursorSandUsageStatus,
4518
+ cursorSandDuplicatesGrokPool,
4487
4519
  normalizeGeminiQuotaResponse,
4488
4520
  normalizeKimiUsageResponse,
4489
4521
  parseKiroUsageOutput,
@@ -1 +0,0 @@
1
- import{r as n,K as a}from"./__vite-browser-external-BZ-i40Kf.js";import{u as p}from"./DialogTitle-CiiRTn3_.js";import{u as c}from"./composite-CUuXvKos.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};
@@ -1 +0,0 @@
1
- import{r as o}from"./__vite-browser-external-BZ-i40Kf.js";import{h as r,i as t,p as s}from"./react-DYja9tZG.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};
@@ -1 +0,0 @@
1
- import{t as v,w as M,r as w,c as P,aS as O,aT as R,aU as S,aV as q}from"./__vite-browser-external-BZ-i40Kf.js";import{l as z,i as _,L as F,a as T}from"./use-limits-display-prefs-CGyd8lvc.js";import{u as j}from"./use-latest-request-guard-BKbMGVji.js";const x="native:notificationPermission";let D=null;function I(){return typeof window<"u"&&!!window.chrome?.webview}function L(){return v()?D||"unknown":I()?"granted":typeof Notification>"u"?"unsupported":Notification.permission}function $(e){if(typeof window>"u")return()=>{};const i=n=>{const s=n?.detail?.status;typeof s=="string"&&(D=s),e(L())};return window.addEventListener(x,i),e(L()),v()&&M({type:"getNotificationStatus"}),()=>window.removeEventListener(x,i)}async function B(){if(v()){M({type:"requestNotificationPermission"});return}I()||typeof Notification<"u"&&Notification.permission==="default"&&await Notification.requestPermission().catch(()=>"denied")}const K="tt.limitAlerts.enabled";function V(){try{return window.localStorage.getItem(K)==="1"}catch{return!1}}function le(){const[e,i]=w.useState(V),[n,s]=w.useState("unknown");w.useEffect(()=>$(s),[]);const y=w.useCallback(async o=>{const l=!!o;l&&await B();try{window.localStorage.setItem(K,l?"1":"0")}catch{}i(l)},[]);return{enabled:e,setEnabled:y,permissionBlocked:n==="denied"}}function G(e,i){if(!(e>0))return null;const n=(e-i)/e;return Number.isFinite(n)?Math.min(Math.max(n,0),1):null}function U(e,i,n=.03){return e>i+n}function H(e){const i=Math.max(0,Math.floor(e)),n=Math.floor(i/3600);return n>24?`${Math.floor(n/24)}d`:n>0?`${n}h`:`${Math.floor(i/60)}m`}function J(e,i){if(typeof e.windowSeconds=="number")return e.windowSeconds;if(e.windowSecondsField&&i){const n=i[e.windowSecondsField];return typeof n=="number"&&n>0?n:null}return null}function W(e){return e==null?NaN:typeof e=="number"?e*1e3:Date.parse(e)}function Y({usedPercent:e,windowSeconds:i,resetMs:n,mode:s,now:y=Date.now()}){const o=Math.min(Math.max(Number(e)||0,0),100)/100,l={pacePercent:null,paceOver:!1,expectedPercent:null,runsOutEta:null,projectedEnd:null};if(!(i>0)||!Number.isFinite(n)||n<=y)return l;const a=Math.max(0,(n-y)/1e3),r=G(i,a);if(r==null)return l;if(l.expectedPercent=Math.round(r*100),l.paceOver=U(o,r),o>0){const m=s==="remaining"?1-r:r;l.pacePercent=m*100}if(r>.02&&o>0){const m=i*r,c=o/m,f=o/r;f>=1&&c>0?l.runsOutEta=H((1-o)/c):l.projectedEnd=Math.round(Math.min(f,1)*100)}return l}const Q={claude:{windows(e){const i=Array.isArray(e.weekly_scoped)?e.weekly_scoped.map(n=>({key:`scoped-${n.label}`,label:n.label,window:n,pctField:"utilization",resetField:"resets_at",windowSeconds:604800})):[];return[{key:"5h",labelKey:"limits.label.claude_5h",window:e.five_hour,pctField:"utilization",resetField:"resets_at",windowSeconds:5*3600},{key:"7d",labelKey:"limits.label.claude_7d",window:e.seven_day,pctField:"utilization",resetField:"resets_at",windowSeconds:7*86400},{key:"opus",labelKey:"limits.label.claude_opus",window:e.seven_day_opus,pctField:"utilization",resetField:"resets_at",windowSeconds:7*86400},...i]}},codex:{extra:"codex_meta",windows(e){return[{key:"5h",labelKey:"limits.label.codex_5h",window:e.primary_window,windowSecondsField:"limit_window_seconds"},{key:"7d",labelKey:"limits.label.codex_7d",window:e.secondary_window,windowSecondsField:"limit_window_seconds"},{key:"credits",labelKey:"limits.label.codex_credits",window:e.credit_window},{key:"spark-5h",labelKey:"limits.label.codex_spark_5h",window:e.spark_primary_window,windowSecondsField:"limit_window_seconds"},{key:"spark-7d",labelKey:"limits.label.codex_spark_7d",window:e.spark_secondary_window,windowSecondsField:"limit_window_seconds"}]}},cursor:{windows(e){return[{key:"plan",labelKey:"limits.label.cursor_plan",window:e.primary_window,windowSecondsField:"limit_window_seconds"},{key:"auto",labelKey:"limits.label.cursor_auto",window:e.secondary_window,windowSecondsField:"limit_window_seconds"},{key:"api",labelKey:"limits.label.cursor_api",window:e.tertiary_window,windowSecondsField:"limit_window_seconds"},{key:"grok-bot",labelKey:"limits.label.cursor_grok_bot",window:e.quaternary_window,windowSecondsField:"limit_window_seconds"}]}},gemini:{windows(e){return[{key:"pro",labelKey:"limits.label.gemini_pro",window:e.primary_window},{key:"flash",labelKey:"limits.label.gemini_flash",window:e.secondary_window},{key:"lite",labelKey:"limits.label.gemini_lite",window:e.tertiary_window}]}},kimi:{extra:"kimi_parallel",windows(e){return[{key:"weekly",labelKey:"limits.label.kimi_weekly",window:e.primary_window,windowSeconds:7*86400},{key:"5h",labelKey:"limits.label.kimi_5h",window:e.secondary_window,windowSeconds:5*3600},{key:"total",labelKey:"limits.label.kimi_total",window:e.tertiary_window}]}},kiro:{extra:"kiro_credits",windows(e){return[{key:"month",labelKey:"limits.label.kiro_month",window:e.primary_window},{key:"bonus",labelKey:"limits.label.kiro_bonus",window:e.secondary_window}]}},grok:{windows(e){const i=typeof e.period_type=="string"?e.period_type:null,n=i==="weekly"?"limits.label.grok_week":i==="daily"?"limits.label.grok_day":"limits.label.grok_month",s=i==="weekly"?7*86400:i==="daily"?86400:void 0;return[{key:i==="weekly"?"week":i==="daily"?"day":"month",labelKey:n,window:e.primary_window,windowSeconds:s},{key:"ondemand",labelKey:"limits.label.grok_ondemand",window:e.secondary_window}]}},antigravity:{windows(e){return[{key:"claude-weekly",labelKey:"limits.label.antigravity_claude_weekly",window:e.primary_window},{key:"claude-5h",labelKey:"limits.label.antigravity_claude_5h",window:e.secondary_window},{key:"gemini-weekly",labelKey:"limits.label.antigravity_gemini_weekly",window:e.tertiary_window},{key:"gemini-5h",labelKey:"limits.label.antigravity_gemini_5h",window:e.quaternary_window}]}},copilot:{extra:"copilot_otel",windows(e){return[{key:"premium",labelKey:"limits.label.copilot_premium",window:e.primary_window},{key:"chat",labelKey:"limits.label.copilot_chat",window:e.secondary_window}]}},zcode:{extra:"zcode_reset_bank",windows(e){if(e.plan_kind==="coding-plan")return[{key:"5h",labelKey:"limits.label.zcode_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.zcode_weekly",window:e.secondary_window},{key:"tools",labelKey:"limits.label.zcode_tools",window:e.tertiary_window}];const i=Array.isArray(e.buckets)?e.buckets.filter(n=>n?.label&&n.window):[];return i.length?i.map((n,s)=>({key:`bucket-${n.entitlement_id||s}`,label:n.label,window:n.window,...n.period==="one_time"?{timeKind:"expiry"}:{}})):[{key:"glm52",labelKey:"limits.label.zcode_glm52",window:e.primary_window},{key:"glm5t",labelKey:"limits.label.zcode_glm5t",window:e.secondary_window}]}},opencodeGo:{windows(e){return[{key:"5h",labelKey:"limits.label.opencode_go_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.opencode_go_weekly",window:e.secondary_window},{key:"monthly",labelKey:"limits.label.opencode_go_monthly",window:e.tertiary_window}]}},qoder:{windows(e){return[{key:"credits",labelKey:"limits.label.qoder_credits",window:e.primary_window},{key:"ultimate",labelKey:"limits.label.qoder_ultimate",window:e.secondary_window,timeKind:"expiry"}]}},qoderCn:{windows(e){return[{key:"credits",labelKey:"limits.label.qoder_cn_credits",window:e.primary_window},{key:"addon",labelKey:"limits.label.qoder_cn_addon_credits",window:e.tertiary_window},{key:"ultimate",labelKey:"limits.label.qoder_cn_ultimate",window:e.secondary_window,timeKind:"expiry"}]}},codingPlan:{windows(e){return[{key:"5h",labelKey:"limits.label.ark_coding_plan_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.ark_coding_plan_weekly",window:e.secondary_window},{key:"monthly",labelKey:"limits.label.ark_coding_plan_monthly",window:e.tertiary_window}]}},commandCode:{windows(e){return[{key:"5h",labelKey:"limits.label.command_code_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.command_code_weekly",window:e.secondary_window}]}},agentPlan:{windows(e){return[{key:"5h",labelKey:"limits.label.ark_agent_plan_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.ark_agent_plan_weekly",window:e.secondary_window},{key:"monthly",labelKey:"limits.label.ark_agent_plan_monthly",window:e.tertiary_window}]}},devin:{windows(e){return[{key:"daily",labelKey:"limits.label.devin_daily",window:e.primary_window,windowSecondsField:"limit_window_seconds"},{key:"weekly",labelKey:"limits.label.devin_weekly",window:e.secondary_window,windowSecondsField:"limit_window_seconds"}]}}},A="tt.limitAlerts.cycles.v1";function X(){try{return JSON.parse(window.localStorage.getItem(A)||"{}")}catch{return{}}}function Z(e){try{window.localStorage.setItem(A,JSON.stringify(e))}catch{}}function ee(e,{now:i=Date.now()}={}){const n=[],s=Q;for(const[y,o]of Object.entries(e||{})){const l=s[y];if(!(!o?.configured||o.error||!l))for(const a of l.windows(o)){if(!a.window)continue;const r=Number(a.pctField==="utilization"?a.window.utilization:a.window.used_percent),m=a.resetField==="resets_at"?a.window.resets_at:a.window.reset_at,c=W(m),f=Y({usedPercent:r,windowSeconds:J(a,a.window),resetMs:c,mode:"used",now:i});!f.runsOutEta||!f.paceOver||r<20||!(c>i)||n.push({id:`${y}:${a.key}:${c}`,providerId:y,windowKey:a.key,resetMs:c,usedPercent:r,runsOutEta:f.runsOutEta})}}return n}function ie(e){const i=X(),n=ee(e);for(const o of n){if(i[o.id])continue;const l=z(o.providerId),a=P("limits.alert.title",{provider:l}),r=P("limits.alert.body",{eta:o.runsOutEta,percent:Math.round(o.usedPercent)});let m=O({title:a,body:r,id:o.id});!m&&typeof Notification<"u"&&Notification.permission==="granted"&&(new Notification(a,{body:r,tag:o.id}),m=!0),m&&(i[o.id]=Date.now())}const s=Date.now()-864e5,y=Object.fromEntries(Object.entries(i).filter(([o,l])=>{const a=Number(o.split(":").at(-1));return Number.isFinite(a)?a>=s:Number(l)>=s}).slice(-100));return Z(y),n.length}function h(e,i){return!e||i||!e.devin||e.devin.configured===!1&&Object.keys(e.devin).length===1?e:{...e,devin:{configured:!1}}}function re(e){const i=q(),n=i&&!!e?.initialState,[s,y]=w.useState(_),[o,l]=w.useState(()=>n?h(e?.initialState?.data??null,_()):null),[a,r]=w.useState(()=>n?e?.initialState?.error??null:null),[m,c]=w.useState(i&&!n),f=!!e?.initialRefresh,E=!!e?.publishToPreloadCache,b=j([s,i]);w.useEffect(()=>{if(typeof window>"u")return;const d=()=>y(_()),t=u=>{T(u.key)&&d()};return window.addEventListener(F,d),window.addEventListener("storage",t),()=>{window.removeEventListener(F,d),window.removeEventListener("storage",t)}},[]),w.useEffect(()=>{if(!(!i||!o||typeof window>"u"))try{window.localStorage.getItem(K)==="1"&&ie(o)}catch{}},[o,i]);const p=w.useCallback((d,t)=>{!i||!E||!d||typeof d!="object"||R(h(d,_()),{source:t})},[i,E]),C=w.useCallback(async()=>{if(!i)return null;const d=b();try{const t=await S({refresh:!0,devinEnabled:_()});if(!d())return null;const k=h(t&&typeof t=="object"?t:null,_());return l(k),r(null),c(!1),p(k,"manual-refresh"),k}catch(t){return d()&&(r(t?.message||String(t)),c(!1)),null}},[b,i,p]),g=w.useCallback(async()=>{if(!i)return;const d=b();try{const t=await S({devinEnabled:_()});if(!d())return;const u=t&&typeof t=="object"?t:null;l(h(u,_())),r(null),c(!1),p(u,"page-load")}catch(t){if(!d())return;r(t?.message||String(t)),c(!1)}},[b,i,p]);w.useEffect(()=>{if(!i||typeof window>"u"||typeof document>"u")return;const d=15e3;let t=Date.now();const u=()=>{if(document.visibilityState!=="visible")return;const k=Date.now();k-t<d||(t=k,g())};return window.addEventListener("focus",u),document.addEventListener("visibilitychange",u),()=>{window.removeEventListener("focus",u),document.removeEventListener("visibilitychange",u)}},[i,g]);const N=w.useRef(s);return w.useEffect(()=>{N.current!==s&&(N.current=s,i&&(s||l(d=>h(d,!1)),g()))},[s,i,g]),w.useEffect(()=>{if(!i){l(null),r(null),c(!1);return}if(n&&!f)return;const d=b();(async()=>{try{const t=await S({devinEnabled:_()});if(!d())return;const u=t&&typeof t=="object"?t:null;l(h(u,_())),r(null),p(u,"page-load")}catch(t){if(!d())return;r(t?.message||String(t))}finally{d()&&c(!1)}})()},[b,n,f,i,p]),{data:o,error:a,isLoading:m,refresh:C}}export{Q as P,J as a,le as b,Y as c,W as r,ie as s,re as u};