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.
- package/README.de.md +1 -0
- package/README.ja.md +1 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dashboard/dist/assets/{AchievementBadge-slL-XBCu.js → AchievementBadge-BmWRsZMx.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-BeFkGc5M.js → AchievementsPage-CyIP2nck.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BrGUIipD.js → AchievementsSection-BeNyJ0LW.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-avmK1ODc.js → BadgeDetailModal-DIFnFzkv.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-DaEPdEZi.js → BotAnimated-CKVeJHit.js} +1 -1
- package/dashboard/dist/assets/{Card-CIZCccTA.js → Card-lQmyKbh4.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-BhRj0SYO.js → CloudCheckoutPage-B_8y6dyX.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-CvaN8a54.js → CloudPage-BOQjUGAt.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-BUNuXmYF.js → CommandPalette-BV9LqokI.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BCI0WHPr.js → CommunityStatsModal-WELxKEFc.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CT7IwtLR.js → ConfirmModal-B8H5o8c_.js} +1 -1
- package/dashboard/dist/assets/{Controls-CtnMyxNH.js → Controls-9ynPx9rF.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-BuzyITeL.js → DashboardPage-B8z-U63p.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-1Ps1-1dw.js → DateRangePopover-DEqWsV_8.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-Dt-dVQAG.js → DevicePage-CUOlUySM.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-D-DRTF1u.js → DialogClose-Dlg52bL6.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-vuQFBlM_.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-CiiRTn3_.js → DialogTitle-CduwXjqH.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-k6RFJ2qC.js → ElectricLogo-DFt7_F2f.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CNHg83aK.js → FadeIn-DlyusE6n.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CIYW5dc3.js → HeaderGithubStar-tUtJoFHh.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-Dn80DyPM.js → HoverTooltip-v-zkCqsX.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-B29dYfoI.js → IpCheckPage-C5Y1uVEr.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Dzc8_QyW.js → LandingPage-DzOJOAj7.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-DXPcIhyQ.js → LeaderboardPage-BBZ_6tCw.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-DxEdj2Zq.js → LeaderboardProBadge-DakoXIQn.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-DnKY2223.js → LeaderboardProfileModal-CINpES_x.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CY94ngqy.js → LeaderboardProfilePage-B6ISf-1l.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-AlvnAPCD.js → LimitsPage-UVX730CQ.js} +8 -8
- package/dashboard/dist/assets/{LocalOnlyNotice-EHw7BeYS.js → LocalOnlyNotice-BJSsmXF1.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-E5E3i2kj.js → LoginCard-Dggv2aGv.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DxhQI9kh.js → LoginModal-B8qDd2kY.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DRwJn-Fw.js → LoginPage-Dv66U8yJ.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DIpVTdiC.js → PetPage-CNwrQdKM.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-Bjs7E91g.js → PopoverPopup-CqmvpYOi.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-DqXIjym-.js → ProviderIcon-BF2Ch7wi.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DxSRYQgP.js → ResetPasswordPage-B0R8VUU3.js} +1 -1
- package/dashboard/dist/assets/{Select-BfjrcaiM.js → Select-idAiBnJG.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-Dpdlb0cU.js → SelectItemText-DPmmPY_D.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-Cnp_PMaD.js → SelfHostPage-oR9cPK02.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-fBwG-qXq.js → ServiceStatusPage-CdV9gfGF.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-BEk7AHdq.js → SessionsPage-BkD34VlL.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-BgML2jQR.js → SettingsPage-DsthWCyM.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-DcvVZ3Wn.js → SkillsPage-CzMk9s3x.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-D29yX2Hb.js → TokenGalaxy-DQ6o8YBp.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-CjzmNK2l.js → TooltipPopup-CMQjA-HE.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-DfnQrsrx.js → TrendMonitor-DrV1m8E7.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CRYomYfk.js → WidgetsPage-Cc16WM0Y.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-CvQI5zUw.js → WrappedPage-pMwbM4R7.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-BZ-i40Kf.js → __vite-browser-external-DCy_YLfe.js} +3 -3
- package/dashboard/dist/assets/{agent-logos-CvOX7WXW.js → agent-logos-Zw7LNkMO.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-BrQrZJtW.js → arrow-left-DycYjE-k.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-fC11gmFY.js → arrow-right-uNxLHoJ0.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CCMEqQQp.js → arrow-up-right-CmdJ7gd7.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-OaIQ06qD.js → calendar-days-CJ33_t5O.js} +1 -1
- package/dashboard/dist/assets/{check-D888uFpx.js → check-Dbubk4Il.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-BJ0BpS_w.js → chevron-down-BwvBVAOs.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-BeOpEJTZ.js → cloud-checkout-IG-IOpeP.js} +1 -1
- package/dashboard/dist/assets/{composite-CUuXvKos.js → composite-IIgJPPIw.js} +1 -1
- package/dashboard/dist/assets/{download-BJ7CKwx5.js → download-DxtH7w6a.js} +1 -1
- package/dashboard/dist/assets/{external-link-CDaZwJVN.js → external-link-evKf3mnD.js} +1 -1
- package/dashboard/dist/assets/{flame-ChyUKs-9.js → flame-D2qOE_W4.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-B6WX7zcH.js → format-tokens-CqDmm-sK.js} +1 -1
- package/dashboard/dist/assets/{gift-CQlAZUJ5.js → gift-Ge11HK1x.js} +1 -1
- package/dashboard/dist/assets/{host-mode-Gw6KD5OJ.js → host-mode-8_TNh7yE.js} +1 -1
- package/dashboard/dist/assets/{icons-C7gGTMuQ.js → icons-OmfWvE5K.js} +1 -1
- package/dashboard/dist/assets/{index-Bm2dDAVL.js → index-BYuEzP9p.js} +1 -1
- package/dashboard/dist/assets/{index-CLb64Tlj.js → index-BsYtyo4U.js} +1 -1
- package/dashboard/dist/assets/{index-C0pbymQa.js → index-tbe1aERA.js} +1 -1
- package/dashboard/dist/assets/{info-CvOmd88O.js → info-xhrso9aO.js} +1 -1
- package/dashboard/dist/assets/{link-2-BNEPxvlF.js → link-2-BgNpTaO4.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-bRHxCgY3.js → loader-circle-DRooZ0e5.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-D-X0-uvt.js → modifiers.esm-D-QAEpOZ.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-Bo7d-q73.js → monitor-smartphone-Bwu7__2B.js} +1 -1
- package/dashboard/dist/assets/{play-DqpGELN5.js → play-CBPvTiWM.js} +1 -1
- package/dashboard/dist/assets/{plus-2fA1tz7S.js → plus-B4lFIyP8.js} +1 -1
- package/dashboard/dist/assets/{provider-display-B1KigDYZ.js → provider-display-CQlWusXH.js} +1 -1
- package/dashboard/dist/assets/{react-DYja9tZG.js → react-DDOtS4Bd.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-BerB8y-A.js → safePolygon-BGfBVKoy.js} +1 -1
- package/dashboard/dist/assets/{search-CU6UVI1L.js → search-DVyNq72J.js} +1 -1
- package/dashboard/dist/assets/{server-CZR4PTdb.js → server-EpUTEsOf.js} +1 -1
- package/dashboard/dist/assets/{shield-check-BGbNmrMP.js → shield-check-DRg8cPKV.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CJLEzxDf.js → skills-api-y42dEF96.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-CJ1NZWER.js → sortable.esm-D2S1gXCK.js} +1 -1
- package/dashboard/dist/assets/{store-CJFdru_6.js → store-DfGPS2IR.js} +1 -1
- package/dashboard/dist/assets/{terminal-ClyQ8tvQ.js → terminal-hXIrrL2b.js} +1 -1
- package/dashboard/dist/assets/{trash-Dg2PWl5-.js → trash-cjaASRQl.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-Ca995kap.js → use-community-stats-B-nipjWt.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-BKbMGVji.js → use-latest-request-guard-DIEYrSzn.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CGyd8lvc.js → use-limits-display-prefs-BdHDwBBC.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-Dqeuqm9O.js → use-native-settings-7w71_h44.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-Nj7R1iUu.js → use-ordered-list-CSMpK4ar.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-HUGf223R.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-C_H5BaGV.js → use-session-efficiency-pref-BagBVWW6.js} +1 -1
- package/dashboard/dist/assets/{use-transform-CC90hurD.js → use-transform-sjGGXCNn.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-Nml-wUB-.js +1 -0
- package/dashboard/dist/assets/{useCurrency-CMHIL39_.js → useCurrency-DAKIY1nR.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-CRSfrGQq.js → usePositioner-B7vVQ7th.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-B5jhM_Kb.js → useTokenFormat-gtgw8k2Z.js} +1 -1
- package/dashboard/dist/assets/{zap-CKx8ySZs.js → zap-DFWOiTJ1.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/sync.js +546 -31
- package/src/lib/async-queue-reader.js +37 -16
- package/src/lib/codex-context-breakdown.js +8 -1
- package/src/lib/codex-rollout-parser.js +65 -5
- package/src/lib/grok-limits.js +56 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +356 -8
- package/src/lib/session-analytics.js +1 -1
- package/src/lib/usage-limits.js +33 -1
- package/dashboard/dist/assets/DialogDescription-BOYDS1mM.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-CGMbDpHQ.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-DtaaE7D8.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
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() &&
|
|
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
|
-
? (
|
|
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
|
-
|
|
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
|
|
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 = [];
|
package/src/lib/usage-limits.js
CHANGED
|
@@ -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(
|
|
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};
|