tokentracker-cli 0.79.9 → 0.80.0
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/dashboard/dist/assets/{AchievementBadge-B1WOPlu0.js → AchievementBadge-D9jOMhlD.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-9kHEQicS.js → AchievementsPage-DuswZa4g.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-Dw05PYPr.js → AchievementsSection-BJPeE1Uq.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-DaWgiijJ.js → BadgeDetailModal-nNPIsy6i.js} +1 -1
- package/dashboard/dist/assets/{Card-DYhrLLM4.js → Card-VENq8eEp.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-BLy2LUKz.js → ClawdAnimated-Cq7TW0vv.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-CWYqfht3.js → CommunityStatsModal-y2VftQuq.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-DcE2C24Y.js → ConfirmModal-CWgnJ4hm.js} +1 -1
- package/dashboard/dist/assets/{Controls-CI7qw0-b.js → Controls-BUQ_iZZq.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-CAlgaBa7.js → DashboardPage-WkiygMmq.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-DinOyaeN.js → DevicePage-Dh3XDtNx.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-CjfSDrLo.js → DialogClose-KTFU_B3F.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DIra288n.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-f7gNmCca.js → DialogTitle-bza2pIek.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-D_5w0Qh0.js → FadeIn-Dm3FVNfr.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DTZyNKyL.js → HeaderGithubStar-CbSElzLt.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-D8CViF98.js → IpCheckPage-BDON7VCU.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-CqN5KEjV.js → LandingPage-Ck3CwZrt.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-CwUVO4wn.js → LeaderboardAvatar-GVUy0Ynq.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-B12HJ9LA.js → LeaderboardPage-C19Rkt44.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-DFzPpsnV.js → LeaderboardProfileModal-QcEq_o6E.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-Z0ZYpNFd.js → LeaderboardProfilePage-pFhI6292.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BpwioIa-.js → LimitsPage-ChV3Qezd.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-Covi6zdt.js → LocalOnlyNotice-ALvpjznF.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CRqbhwCx.js → LoginPage-CL-QrJxU.js} +1 -1
- package/dashboard/dist/assets/{PetPage-BY_yObVj.js → PetPage-DSuvdGgd.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-B2dE3Ary.js → PopoverPopup-jSDQ87Bi.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-C8IEJbVc.js → ResetPasswordPage-Dk2IDAJq.js} +1 -1
- package/dashboard/dist/assets/{Select-BmRLYSqA.js → Select-Cz6WTj7D.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-FC46bk8b.js → SelectItemText-BZyRjWCM.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-DipzOQVg.js → SettingsPage-CANz8HMM.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-CziypUTf.js → SkillsPage-WZkQG_83.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CJegJhVR.js → WidgetsPage-BEyJ62NV.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-DcMiKvzf.js → WrappedPage-Ck_dOx_w.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-S7bF1LwW.js → agent-logos-DpjHiAbZ.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-D0yeL56y.js → arrow-up-right-C4iYPBtK.js} +1 -1
- package/dashboard/dist/assets/check-BkPUxPZV.js +1 -0
- package/dashboard/dist/assets/{copy-D-z9ZtIu.js → copy-CHJ-38K6.js} +1 -1
- package/dashboard/dist/assets/{download-Dwxnd3QG.js → download-BSeG8u17.js} +1 -1
- package/dashboard/dist/assets/{flame-Bdqk9A75.js → flame-DeHwKcE-.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-DNVcCRwm.js → format-tokens-B-MJYJyp.js} +1 -1
- package/dashboard/dist/assets/{info-CMLT6oUI.js → info-DA_0xr_o.js} +1 -1
- package/dashboard/dist/assets/{link-2-BmiHu726.js → link-2-noUH83Lc.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CXVUQtzm.js → loader-circle-wL7KZtm0.js} +1 -1
- package/dashboard/dist/assets/{main-B9m79io-.js → main-BHfJDFcu.js} +3 -3
- package/dashboard/dist/assets/{provider-display-BoAesdke.js → provider-display-DxIClOGM.js} +1 -1
- package/dashboard/dist/assets/{trash-2-C9Rh2DAL.js → trash-2-B8TPUmxZ.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-aZwYxrXR.js → use-community-stats-CXD3MRRE.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-tJKR83RT.js → use-limits-display-prefs-C2pJVYTn.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-PjfJPK5v.js → use-native-settings-BeOBLEZa.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-D1QFmZUY.js → use-ordered-list-kAtW8pJm.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-4N3Su4wC.js → use-quality-per-dollar-pref-CFZUZM3S.js} +1 -1
- package/dashboard/dist/assets/{use-transform-D9nurInL.js → use-transform-B9YwpyKi.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DapTpi_6.js → use-usage-limits-DSX5iKPE.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-B0DH9ZZA.js → useCurrency-CKg5K4Nj.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DijV6DYS.js → useScrollLock-DyyY_Vyt.js} +1 -1
- package/dashboard/dist/assets/useTokenFormat-FfTjEH_m.js +1 -0
- package/dashboard/dist/assets/{zap-BVfhR3Ck.js → zap-B69x9NCT.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/init.js +11 -0
- package/src/commands/sync.js +160 -6
- package/src/lib/local-api.js +11 -5
- package/src/lib/openclaw-session-plugin.js +13 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +63 -2
- package/src/lib/usage-limits.js +9 -5
- package/dashboard/dist/assets/DialogDescription-DWs-KIUA.js +0 -1
- package/dashboard/dist/assets/check-Dn9BLYEf.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-nxdG6gi9.js +0 -1
package/src/commands/sync.js
CHANGED
|
@@ -172,6 +172,18 @@ const CLOUD_CONVERSATIONS_BACKFILL_KEY = "cloudConversationsBackfill_2026_06";
|
|
|
172
172
|
// ~/.codex/archived_sessions/ which sync does not scan) — clearing its bucket
|
|
173
173
|
// would lose that history (ref the v6 ground-truth-repair data-loss incident).
|
|
174
174
|
const CODEX_RESCAN_DEDUP_REPAIR_KEY = "codexRescanDedupRepair_2026_06";
|
|
175
|
+
// One-time repair (#169 follow-up): forked Codex rollouts replay the parent
|
|
176
|
+
// session's token history into the child file, and until the same-day burst
|
|
177
|
+
// detector landed in rollout.js the parser counted those replayed rows as live
|
|
178
|
+
// usage (PR #169's date guard only caught cross-day forks). Cursors sit at EOF
|
|
179
|
+
// on every already-parsed file, so the forward fix alone never corrects the
|
|
180
|
+
// inflated history. This re-runs the exact #187 guarded rebuild under a new key
|
|
181
|
+
// — the rebuild re-parses every codex file with the CURRENT (fork-aware)
|
|
182
|
+
// parser, so replay rows are excluded — and inherits all its safety properties
|
|
183
|
+
// (unreproducible-session skip, atomic throwaway rebuild, queue strip, upload
|
|
184
|
+
// offset reset for the cloud overwrite). Pre-gated: installs with no forked
|
|
185
|
+
// rollout on disk carry no fork phantom and mark done without the rebuild.
|
|
186
|
+
const CODEX_FORK_REPLAY_REPAIR_KEY = "codexForkReplayRepair_2026_07";
|
|
175
187
|
const DROID_DUP_SESSION_REPAIR_KEY = "droidDupSessionInflationRepair_2026_06";
|
|
176
188
|
const CODEX_COLD_SCAN_AUDIT_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
177
189
|
const CODEX_COLD_SCAN_AUDIT_MAX_SYNCS = 288;
|
|
@@ -405,7 +417,7 @@ async function cmdSync(argv) {
|
|
|
405
417
|
|
|
406
418
|
if (isFullSourceScan) {
|
|
407
419
|
await migrateRolloutCumulativeDeltaBuckets({ cursors, queuePath, rolloutFiles });
|
|
408
|
-
await repairCodexRescanInflation({
|
|
420
|
+
const codexRescanRepairRan = await repairCodexRescanInflation({
|
|
409
421
|
cursors,
|
|
410
422
|
queuePath,
|
|
411
423
|
queueStatePath,
|
|
@@ -413,6 +425,15 @@ async function cmdSync(argv) {
|
|
|
413
425
|
projectQueueStatePath,
|
|
414
426
|
rolloutFiles,
|
|
415
427
|
});
|
|
428
|
+
await repairCodexForkReplayInflation({
|
|
429
|
+
cursors,
|
|
430
|
+
queuePath,
|
|
431
|
+
queueStatePath,
|
|
432
|
+
projectQueuePath,
|
|
433
|
+
projectQueueStatePath,
|
|
434
|
+
rolloutFiles,
|
|
435
|
+
legacyRepairRan: codexRescanRepairRan,
|
|
436
|
+
});
|
|
416
437
|
await repairDroidDuplicateSessionInflation({ cursors, queuePath, queueStatePath });
|
|
417
438
|
await repairMimoClaudeMislabel({
|
|
418
439
|
cursors,
|
|
@@ -2097,6 +2118,7 @@ module.exports = {
|
|
|
2097
2118
|
migrateCursorUnknownBuckets,
|
|
2098
2119
|
migrateRolloutCumulativeDeltaBuckets,
|
|
2099
2120
|
repairCodexRescanInflation,
|
|
2121
|
+
repairCodexForkReplayInflation,
|
|
2100
2122
|
repairDroidDuplicateSessionInflation,
|
|
2101
2123
|
repairMimoClaudeMislabel,
|
|
2102
2124
|
reincludeClaudeMemObserverFiles,
|
|
@@ -2109,6 +2131,7 @@ module.exports = {
|
|
|
2109
2131
|
CURSOR_UNKNOWN_MIGRATION_KEY,
|
|
2110
2132
|
ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY,
|
|
2111
2133
|
CODEX_RESCAN_DEDUP_REPAIR_KEY,
|
|
2134
|
+
CODEX_FORK_REPLAY_REPAIR_KEY,
|
|
2112
2135
|
DROID_DUP_SESSION_REPAIR_KEY,
|
|
2113
2136
|
CLAUDE_MEM_OBSERVER_REINCLUDE_KEY,
|
|
2114
2137
|
GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY,
|
|
@@ -3157,6 +3180,11 @@ async function repairCodexRescanInflation({
|
|
|
3157
3180
|
projectQueuePath,
|
|
3158
3181
|
projectQueueStatePath,
|
|
3159
3182
|
rolloutFiles,
|
|
3183
|
+
// The fork-replay repair (#169 follow-up) re-runs this exact rebuild under
|
|
3184
|
+
// its own key: the rebuild always uses the CURRENT parser, so any parser fix
|
|
3185
|
+
// shipped since the last run is applied to the rebuilt history.
|
|
3186
|
+
migrationKey = CODEX_RESCAN_DEDUP_REPAIR_KEY,
|
|
3187
|
+
uploadNote = "reset_after_codex_rescan_dedup_2026_06",
|
|
3160
3188
|
}) {
|
|
3161
3189
|
if (!cursors || typeof cursors !== "object") return false;
|
|
3162
3190
|
const migrations = (cursors.migrations ||= {});
|
|
@@ -3167,7 +3195,7 @@ async function repairCodexRescanInflation({
|
|
|
3167
3195
|
// found). Treating the skip sentinel as "done" is what left users like #187
|
|
3168
3196
|
// permanently stuck on the inflated value after upgrading (the key was truthy
|
|
3169
3197
|
// so the guard never got a second chance).
|
|
3170
|
-
const priorRepair = migrations[
|
|
3198
|
+
const priorRepair = migrations[migrationKey];
|
|
3171
3199
|
if (priorRepair && !(typeof priorRepair === "object" && priorRepair.skipped)) return false;
|
|
3172
3200
|
|
|
3173
3201
|
// Codex session files THIS sync discovered (source === "codex").
|
|
@@ -3203,7 +3231,7 @@ async function repairCodexRescanInflation({
|
|
|
3203
3231
|
if (codexFileSet.has(fp)) continue; // exact file re-scanned this run
|
|
3204
3232
|
const id = codexSessionIdFromPath(fp);
|
|
3205
3233
|
if (id && scannedSessionIds.has(id)) continue; // same session scanned elsewhere (moved)
|
|
3206
|
-
migrations[
|
|
3234
|
+
migrations[migrationKey] = {
|
|
3207
3235
|
skipped: true,
|
|
3208
3236
|
reason: "codex_session_unreproducible",
|
|
3209
3237
|
at: new Date().toISOString(),
|
|
@@ -3462,7 +3490,7 @@ async function repairCodexRescanInflation({
|
|
|
3462
3490
|
}
|
|
3463
3491
|
uploadState.offset = 0;
|
|
3464
3492
|
uploadState.updatedAt = new Date().toISOString();
|
|
3465
|
-
uploadState.note =
|
|
3493
|
+
uploadState.note = uploadNote;
|
|
3466
3494
|
await fs.writeFile(queueStatePath, JSON.stringify(uploadState));
|
|
3467
3495
|
}
|
|
3468
3496
|
if (projectRepairEnabled && typeof projectQueueStatePath === "string" && projectQueueStatePath) {
|
|
@@ -3474,14 +3502,140 @@ async function repairCodexRescanInflation({
|
|
|
3474
3502
|
}
|
|
3475
3503
|
uploadState.offset = 0;
|
|
3476
3504
|
uploadState.updatedAt = new Date().toISOString();
|
|
3477
|
-
uploadState.note =
|
|
3505
|
+
uploadState.note = uploadNote;
|
|
3478
3506
|
await fs.writeFile(projectQueueStatePath, JSON.stringify(uploadState));
|
|
3479
3507
|
}
|
|
3480
3508
|
|
|
3481
|
-
migrations[
|
|
3509
|
+
migrations[migrationKey] = new Date().toISOString();
|
|
3482
3510
|
return true;
|
|
3483
3511
|
}
|
|
3484
3512
|
|
|
3513
|
+
// #169 follow-up: repair the historical inflation left by same-day forked
|
|
3514
|
+
// Codex rollout replays (see CODEX_FORK_REPLAY_REPAIR_KEY). Delegates to the
|
|
3515
|
+
// #187 guarded rebuild with the fork-repair key.
|
|
3516
|
+
async function repairCodexForkReplayInflation({
|
|
3517
|
+
cursors,
|
|
3518
|
+
queuePath,
|
|
3519
|
+
queueStatePath,
|
|
3520
|
+
projectQueuePath,
|
|
3521
|
+
projectQueueStatePath,
|
|
3522
|
+
rolloutFiles,
|
|
3523
|
+
// True when the #187 repair ran its rebuild earlier in THIS sync: that
|
|
3524
|
+
// rebuild already used the fork-aware parser, so the history is clean and a
|
|
3525
|
+
// second rebuild would be pure waste.
|
|
3526
|
+
legacyRepairRan = false,
|
|
3527
|
+
}) {
|
|
3528
|
+
if (!cursors || typeof cursors !== "object") return false;
|
|
3529
|
+
const migrations = (cursors.migrations ||= {});
|
|
3530
|
+
// Same completed-vs-skipped semantics as the #187 repair: an ISO string is
|
|
3531
|
+
// final, a {skipped:true} sentinel retries (the skip condition can clear).
|
|
3532
|
+
const prior = migrations[CODEX_FORK_REPLAY_REPAIR_KEY];
|
|
3533
|
+
if (prior && !(typeof prior === "object" && prior.skipped)) return false;
|
|
3534
|
+
|
|
3535
|
+
if (legacyRepairRan) {
|
|
3536
|
+
migrations[CODEX_FORK_REPLAY_REPAIR_KEY] = new Date().toISOString();
|
|
3537
|
+
return false;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
// Pre-gate: fork phantom can only exist on installs that have (or had) a
|
|
3541
|
+
// forked rollout. Scanning file heads is cheap; the rebuild re-parses every
|
|
3542
|
+
// codex file. Files forked AFTER the parser fix shipped never accrue
|
|
3543
|
+
// phantom, so a definitive "no forks" verdict may finalize — but ONLY when
|
|
3544
|
+
// this scan can actually stand for the install's whole codex history:
|
|
3545
|
+
// 1. every candidate head was readable (an unreadable candidate could be
|
|
3546
|
+
// the one forked rollout), AND
|
|
3547
|
+
// 2. every codex session recorded in cursors is covered by THIS scan's
|
|
3548
|
+
// file list (same UUID-keyed reproducibility rule as the #187 guard).
|
|
3549
|
+
// A forked rollout can be real yet absent from one run's rolloutFiles —
|
|
3550
|
+
// WSL/native split probing, a temporarily missing ~/.codex, a deleted
|
|
3551
|
+
// file whose phantom is already in history — and finalizing off such a
|
|
3552
|
+
// partial view would permanently foreclose the repair this migration
|
|
3553
|
+
// exists to perform, while the guarded repair itself would only have
|
|
3554
|
+
// deferred retryably.
|
|
3555
|
+
// Either failure defers with the retryable {skipped:true} sentinel.
|
|
3556
|
+
const forkScan = await scanForForkedCodexRollout(rolloutFiles);
|
|
3557
|
+
if (!forkScan.forked) {
|
|
3558
|
+
if (forkScan.indeterminate) {
|
|
3559
|
+
migrations[CODEX_FORK_REPLAY_REPAIR_KEY] = {
|
|
3560
|
+
skipped: true,
|
|
3561
|
+
reason: "fork_scan_indeterminate",
|
|
3562
|
+
at: new Date().toISOString(),
|
|
3563
|
+
};
|
|
3564
|
+
return false;
|
|
3565
|
+
}
|
|
3566
|
+
const scannedSessionIds = new Set();
|
|
3567
|
+
const scannedPaths = new Set();
|
|
3568
|
+
for (const entry of Array.isArray(rolloutFiles) ? rolloutFiles : []) {
|
|
3569
|
+
const fp = typeof entry === "string" ? entry : entry?.path;
|
|
3570
|
+
const src = typeof entry === "string" ? "codex" : String(entry?.source || "codex");
|
|
3571
|
+
if (!fp || src !== "codex") continue;
|
|
3572
|
+
scannedPaths.add(fp);
|
|
3573
|
+
const id = codexSessionIdFromPath(fp);
|
|
3574
|
+
if (id) scannedSessionIds.add(id);
|
|
3575
|
+
}
|
|
3576
|
+
if (cursors.files && typeof cursors.files === "object") {
|
|
3577
|
+
for (const fp of Object.keys(cursors.files)) {
|
|
3578
|
+
if (!isCodexSessionCursorPath(fp)) continue;
|
|
3579
|
+
if (scannedPaths.has(fp)) continue;
|
|
3580
|
+
const id = codexSessionIdFromPath(fp);
|
|
3581
|
+
if (id && scannedSessionIds.has(id)) continue;
|
|
3582
|
+
migrations[CODEX_FORK_REPLAY_REPAIR_KEY] = {
|
|
3583
|
+
skipped: true,
|
|
3584
|
+
reason: "codex_history_not_covered",
|
|
3585
|
+
at: new Date().toISOString(),
|
|
3586
|
+
};
|
|
3587
|
+
return false;
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
migrations[CODEX_FORK_REPLAY_REPAIR_KEY] = new Date().toISOString();
|
|
3591
|
+
return false;
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
return repairCodexRescanInflation({
|
|
3595
|
+
cursors,
|
|
3596
|
+
queuePath,
|
|
3597
|
+
queueStatePath,
|
|
3598
|
+
projectQueuePath,
|
|
3599
|
+
projectQueueStatePath,
|
|
3600
|
+
rolloutFiles,
|
|
3601
|
+
migrationKey: CODEX_FORK_REPLAY_REPAIR_KEY,
|
|
3602
|
+
uploadNote: "reset_after_codex_fork_replay_2026_07",
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
// Scans codex rollout heads for the fork marker. The child session_meta (with
|
|
3607
|
+
// forked_from_id) is the FIRST line of a forked rollout — observed at byte
|
|
3608
|
+
// offset ≤169 across every local sample — so a bounded head read is
|
|
3609
|
+
// sufficient. 64KB leaves ample margin for long base_instructions preceding
|
|
3610
|
+
// it; a substring false positive (the marker quoted in unrelated head content)
|
|
3611
|
+
// only costs one redundant guarded rebuild, never data. Tri-state result:
|
|
3612
|
+
// {forked:true} on a hit, {forked:false, indeterminate:false} only when every
|
|
3613
|
+
// candidate was inspected, {forked:false, indeterminate:true} when any head
|
|
3614
|
+
// read failed — the caller must not finalize "no forks" off a failed read.
|
|
3615
|
+
async function scanForForkedCodexRollout(rolloutFiles) {
|
|
3616
|
+
const HEAD_BYTES = 65536;
|
|
3617
|
+
let indeterminate = false;
|
|
3618
|
+
for (const entry of Array.isArray(rolloutFiles) ? rolloutFiles : []) {
|
|
3619
|
+
const fp = typeof entry === "string" ? entry : entry?.path;
|
|
3620
|
+
const src = typeof entry === "string" ? "codex" : String(entry?.source || "codex");
|
|
3621
|
+
if (!fp || src !== "codex") continue;
|
|
3622
|
+
let fh = null;
|
|
3623
|
+
try {
|
|
3624
|
+
fh = await fs.open(fp, "r");
|
|
3625
|
+
const buf = Buffer.alloc(HEAD_BYTES);
|
|
3626
|
+
const { bytesRead } = await fh.read(buf, 0, HEAD_BYTES, 0);
|
|
3627
|
+
if (buf.toString("utf8", 0, bytesRead).includes('"forked_from_id"')) {
|
|
3628
|
+
return { forked: true, indeterminate: false };
|
|
3629
|
+
}
|
|
3630
|
+
} catch (_e) {
|
|
3631
|
+
indeterminate = true;
|
|
3632
|
+
} finally {
|
|
3633
|
+
if (fh) await fh.close().catch(() => {});
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
return { forked: false, indeterminate };
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3485
3639
|
function isCodexSessionCursorPath(filePath) {
|
|
3486
3640
|
if (typeof filePath !== "string") return false;
|
|
3487
3641
|
const normalized = filePath.replace(/\\/g, "/");
|
package/src/lib/local-api.js
CHANGED
|
@@ -1054,15 +1054,17 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1054
1054
|
// sync is on. Cloud sync is a dashboard (WebView) preference persisted in
|
|
1055
1055
|
// localStorage; the dashboard mirrors it here via POST
|
|
1056
1056
|
// /functions/tokentracker-cloud-sync-pref so the auth-unaware popover can key
|
|
1057
|
-
// off the same flag. Defaults
|
|
1057
|
+
// off the same flag. Defaults ON, exactly like the dashboard toggle — every
|
|
1058
|
+
// consumer additionally requires a relayed refresh token, so the default only
|
|
1059
|
+
// takes effect for signed-in users; an explicit {enabled:false} still wins.
|
|
1058
1060
|
const cloudSyncPrefPath = path.join(trackerDataDir, "cloud-sync-pref.json");
|
|
1059
1061
|
let cloudSyncPrefCache;
|
|
1060
1062
|
function getCloudSyncPref() {
|
|
1061
1063
|
if (cloudSyncPrefCache === undefined) {
|
|
1062
1064
|
try {
|
|
1063
|
-
cloudSyncPrefCache = JSON.parse(fs.readFileSync(cloudSyncPrefPath, "utf8"))?.enabled
|
|
1065
|
+
cloudSyncPrefCache = JSON.parse(fs.readFileSync(cloudSyncPrefPath, "utf8"))?.enabled !== false;
|
|
1064
1066
|
} catch {
|
|
1065
|
-
cloudSyncPrefCache =
|
|
1067
|
+
cloudSyncPrefCache = true;
|
|
1066
1068
|
}
|
|
1067
1069
|
}
|
|
1068
1070
|
return cloudSyncPrefCache;
|
|
@@ -2579,14 +2581,18 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
2579
2581
|
if (p === "/functions/tokentracker-usage-limits") {
|
|
2580
2582
|
const { getUsageLimits, resetUsageLimitsCache } = require("./usage-limits");
|
|
2581
2583
|
try {
|
|
2582
|
-
const
|
|
2583
|
-
|
|
2584
|
+
const refreshParam = url.searchParams.get("refresh");
|
|
2585
|
+
const forceRefresh = refreshParam === "1" || refreshParam === "true";
|
|
2586
|
+
if (forceRefresh) {
|
|
2584
2587
|
resetUsageLimitsCache();
|
|
2585
2588
|
}
|
|
2586
2589
|
const data = await getUsageLimits({
|
|
2587
2590
|
home: os.homedir(),
|
|
2588
2591
|
env: process.env,
|
|
2589
2592
|
platform: process.platform,
|
|
2593
|
+
// Punches through the Claude disk fresh-cache (but not the 429
|
|
2594
|
+
// cooldown) — an explicit user refresh should hit upstream.
|
|
2595
|
+
forceRefresh,
|
|
2590
2596
|
});
|
|
2591
2597
|
json(res, data);
|
|
2592
2598
|
} catch (e) {
|
|
@@ -312,6 +312,19 @@ async function removeOpenclawSessionPluginConfig({
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
function runOpenclawCli(args, env = process.env) {
|
|
315
|
+
// Test-only escape hatch: skip spawning the real `openclaw` CLI. Behaves like
|
|
316
|
+
// the CLI being absent (which is the CI environment anyway), so callers take
|
|
317
|
+
// their graceful skip path. Avoids a ~2s `plugins install --link` per cmdInit
|
|
318
|
+
// on dev machines that happen to have openclaw installed.
|
|
319
|
+
if ((env && env.TOKENTRACKER_SKIP_OPENCLAW_CLI) === "1") {
|
|
320
|
+
return {
|
|
321
|
+
code: 1,
|
|
322
|
+
skippedReason: "openclaw-cli-missing",
|
|
323
|
+
error: "skipped via TOKENTRACKER_SKIP_OPENCLAW_CLI",
|
|
324
|
+
stdout: "",
|
|
325
|
+
stderr: "",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
315
328
|
let res;
|
|
316
329
|
try {
|
|
317
330
|
res = cp.spawnSync("openclaw", args, {
|