tokentracker-cli 0.53.2 → 0.53.4

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 (40) hide show
  1. package/dashboard/dist/assets/{ActivityHeatmap-CoVxkSjr.js → ActivityHeatmap-BJ980nAD.js} +1 -1
  2. package/dashboard/dist/assets/{Card-CJiPCvnj.js → Card-B6Lf_P1k.js} +1 -1
  3. package/dashboard/dist/assets/{DashboardPage-CrdiIz7q.js → DashboardPage-B15gBRRP.js} +1 -1
  4. package/dashboard/dist/assets/{DevicePage-DBjdZA2N.js → DevicePage-CxIy3ahX.js} +1 -1
  5. package/dashboard/dist/assets/{DialogTitle--jWfmv_b.js → DialogTitle-Cw5xQZ_R.js} +1 -1
  6. package/dashboard/dist/assets/{FadeIn-BrYhLQwK.js → FadeIn-DfuANxLc.js} +1 -1
  7. package/dashboard/dist/assets/{HeaderGithubStar-DGtt-L8c.js → HeaderGithubStar-CoQWIKv4.js} +1 -1
  8. package/dashboard/dist/assets/{IpCheckPage-ToZsVEOj.js → IpCheckPage-SVk0e_8x.js} +1 -1
  9. package/dashboard/dist/assets/{LandingPage-DgB2CT3R.js → LandingPage-CPAmE-yI.js} +1 -1
  10. package/dashboard/dist/assets/{LeaderboardAvatar-94z-hs6u.js → LeaderboardAvatar-DHTK-O9A.js} +1 -1
  11. package/dashboard/dist/assets/{LeaderboardPage-hXIlg-XS.js → LeaderboardPage-o8abeuhO.js} +3 -3
  12. package/dashboard/dist/assets/{LeaderboardProfileModal-BcppEyBi.js → LeaderboardProfileModal-D7dt_DwK.js} +1 -1
  13. package/dashboard/dist/assets/{LeaderboardProfilePage-BfZVW6cn.js → LeaderboardProfilePage-CVTBYIoO.js} +1 -1
  14. package/dashboard/dist/assets/{LimitsPage-CdcWyyh0.js → LimitsPage-C-yuMLq4.js} +1 -1
  15. package/dashboard/dist/assets/{LocalOnlyNotice-CBRDpg-i.js → LocalOnlyNotice-DcKDnOEU.js} +1 -1
  16. package/dashboard/dist/assets/{LoginPage-DLkWQvC1.js → LoginPage-CVD0_gAd.js} +1 -1
  17. package/dashboard/dist/assets/{PopoverPopup-DMl0dwMU.js → PopoverPopup-uE2yt3Ya.js} +1 -1
  18. package/dashboard/dist/assets/{ResetPasswordPage-CwwTPCTr.js → ResetPasswordPage-CkLEg7Xj.js} +1 -1
  19. package/dashboard/dist/assets/{Select-D3gMIEdB.js → Select-T_lwTOia.js} +1 -1
  20. package/dashboard/dist/assets/{SelectItemText-GvEnESYK.js → SelectItemText-CBG4W9IV.js} +1 -1
  21. package/dashboard/dist/assets/{SettingsPage-B3v6IkYh.js → SettingsPage-CfVhlxOc.js} +1 -1
  22. package/dashboard/dist/assets/{SkillsPage-DX_mIIl8.js → SkillsPage-BI6ugQVd.js} +1 -1
  23. package/dashboard/dist/assets/{WidgetsPage-DgqWQ9EA.js → WidgetsPage-C0eeE5n9.js} +1 -1
  24. package/dashboard/dist/assets/{WrappedPage-CR02q4cO.js → WrappedPage-M5xKJN8-.js} +1 -1
  25. package/dashboard/dist/assets/{agent-logos-B3gxKbbb.js → agent-logos-Ce9EWr5X.js} +1 -1
  26. package/dashboard/dist/assets/{arrow-up-right-DJo_-PxX.js → arrow-up-right-Dm5l83hw.js} +1 -1
  27. package/dashboard/dist/assets/{download-BwsW6vug.js → download-Bi2Iqqa-.js} +1 -1
  28. package/dashboard/dist/assets/{info-C-aAjjA6.js → info-DQFlgzy1.js} +1 -1
  29. package/dashboard/dist/assets/{main-BbEXIjk8.js → main-MlcZt0XW.js} +15 -15
  30. package/dashboard/dist/assets/{use-limits-display-prefs-BUrvvpbW.js → use-limits-display-prefs-BH_wSPHR.js} +1 -1
  31. package/dashboard/dist/assets/{use-native-settings-SsQX6gzC.js → use-native-settings-Bpwi96vW.js} +1 -1
  32. package/dashboard/dist/assets/{use-usage-limits-LOfhEW65.js → use-usage-limits-DxpPWR_A.js} +1 -1
  33. package/dashboard/dist/assets/{useCurrency-CVVJYL2V.js → useCurrency-B4tP0bFn.js} +1 -1
  34. package/dashboard/dist/assets/{useScrollLock-Dm_66oFo.js → useScrollLock-BLDk4uMc.js} +1 -1
  35. package/dashboard/dist/index.html +1 -1
  36. package/dashboard/dist/share.html +1 -1
  37. package/package.json +2 -2
  38. package/src/commands/sync.js +210 -1
  39. package/src/lib/pricing/seed-snapshot.json +1 -1
  40. package/src/lib/rollout.js +75 -0
@@ -42,6 +42,30 @@ async function listRolloutFiles(sessionsDir) {
42
42
  return out;
43
43
  }
44
44
 
45
+ // Collect rollout-*.jsonl at ANY depth under dir. listRolloutFiles requires the
46
+ // strict YYYY/MM/DD/ nesting Codex itself writes, but Codex-Manager archives
47
+ // sessions FLAT into ~/.codex/archived_sessions/ (issue #187), so the strict
48
+ // scanner misses them. This recursive variant handles both flat and nested
49
+ // layouts; safe because the codex event dedup keys on sessionUUID + timestamp,
50
+ // so an archived copy of an already-counted session re-reads as a no-op.
51
+ async function listRolloutFilesDeep(dir) {
52
+ const out = [];
53
+ async function walk(d) {
54
+ const entries = await safeReadDir(d);
55
+ for (const e of entries) {
56
+ const p = path.join(d, e.name);
57
+ if (e.isDirectory()) {
58
+ await walk(p);
59
+ } else if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
60
+ out.push(p);
61
+ }
62
+ }
63
+ }
64
+ await walk(dir);
65
+ out.sort((a, b) => a.localeCompare(b));
66
+ return out;
67
+ }
68
+
45
69
  async function listClaudeProjectFiles(projectsDir) {
46
70
  const out = [];
47
71
  await walkClaudeProjects(projectsDir, out);
@@ -102,6 +126,13 @@ async function parseRolloutIncremental({
102
126
  cursors.files = {};
103
127
  }
104
128
 
129
+ // Persisted set of seen Codex event keys (sessionUUID:eventTimestamp). Mirrors
130
+ // the claudeHashes pattern: it makes an inode-changing re-scan idempotent so an
131
+ // external rewrite of a session file (Codex-Manager's atomic provider-patch on
132
+ // account switch, issue #187) cannot re-count already-counted events.
133
+ const prevCodexHashes = Array.isArray(cursors?.codexHashes) ? cursors.codexHashes : [];
134
+ const seenCodexEvents = new Set(prevCodexHashes);
135
+
105
136
  for (let idx = 0; idx < rolloutFiles.length; idx++) {
106
137
  const entry = rolloutFiles[idx];
107
138
  const filePath = typeof entry === "string" ? entry : entry?.path;
@@ -147,6 +178,8 @@ async function parseRolloutIncremental({
147
178
  projectMetaCache,
148
179
  publicRepoCache,
149
180
  publicRepoResolver,
181
+ seenCodexEvents,
182
+ sessionId: codexSessionIdFromPath(filePath),
150
183
  });
151
184
 
152
185
  cursors.files[key] = {
@@ -176,6 +209,7 @@ async function parseRolloutIncremental({
176
209
  const projectBucketsQueued = projectEnabled
177
210
  ? await enqueueTouchedProjectBuckets({ projectQueuePath, projectState, projectTouchedBuckets })
178
211
  : 0;
212
+ cursors.codexHashes = Array.from(seenCodexEvents);
179
213
  hourlyState.updatedAt = new Date().toISOString();
180
214
  cursors.hourly = hourlyState;
181
215
  if (projectState) {
@@ -718,6 +752,22 @@ async function parseOpenclawSessionFile({
718
752
  return { endOffset, eventsAggregated };
719
753
  }
720
754
 
755
+ /**
756
+ * Extract the session UUID from a Codex rollout file path
757
+ * (`rollout-<datetime>-<uuid>.jsonl`). Used as the stable per-session scope for
758
+ * event dedup: it survives both an inode-changing rewrite (Codex-Manager
759
+ * atomically rewrites session files to patch the provider on account switch,
760
+ * issue #187) and a sessions/ -> archived_sessions/ move. Returns null when the
761
+ * name has no UUID, in which case the caller falls back to the full path.
762
+ */
763
+ function codexSessionIdFromPath(filePath) {
764
+ if (typeof filePath !== "string") return null;
765
+ const m = filePath.match(
766
+ /([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$/,
767
+ );
768
+ return m ? m[1] : null;
769
+ }
770
+
721
771
  async function parseRolloutFile({
722
772
  filePath,
723
773
  startOffset,
@@ -733,6 +783,8 @@ async function parseRolloutFile({
733
783
  projectMetaCache,
734
784
  publicRepoCache,
735
785
  publicRepoResolver,
786
+ seenCodexEvents,
787
+ sessionId,
736
788
  }) {
737
789
  const st = await fs.stat(filePath);
738
790
  const endOffset = st.size;
@@ -830,6 +882,28 @@ async function parseRolloutFile({
830
882
  const bucketStart = toUtcHalfHourStart(tokenTimestamp);
831
883
  if (!bucketStart) continue;
832
884
 
885
+ // Idempotent re-scan dedup (issue #187). Codex usage is parsed incrementally
886
+ // by (inode, offset): when the inode changes the file is re-scanned from
887
+ // offset 0 and every event's delta is re-added to the PERSISTENT hourly
888
+ // buckets. External tools rewrite session files without changing the token
889
+ // data — Codex-Manager atomically rewrites them (new inode) to patch the
890
+ // provider on every account/channel switch — so without dedup each switch
891
+ // double-counts the rewritten sessions. `totals` is already advanced above,
892
+ // so skipping an already-seen event keeps the cumulative-delta chain intact
893
+ // while preventing the re-add; genuinely new turns carry new timestamps and
894
+ // are still counted. Key = sessionUUID:eventTimestamp (both stable across the
895
+ // rewrite and across a sessions/ -> archived_sessions/ move).
896
+ //
897
+ // Scoped to the `codex` source: Codex-Manager (the tool that does the atomic
898
+ // rewrite) manages Codex. Other rollout-format sources (e.g. every-code) have
899
+ // their own model re-alignment that legitimately re-reads prior events, which
900
+ // this dedup would otherwise suppress.
901
+ if (seenCodexEvents && source === "codex") {
902
+ const dedupKey = `${sessionId || filePath}:${tokenTimestamp}`;
903
+ if (seenCodexEvents.has(dedupKey)) continue;
904
+ seenCodexEvents.add(dedupKey);
905
+ }
906
+
833
907
  const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
834
908
  addTotals(bucket.totals, delta);
835
909
  touchedBuckets.add(bucketKey(source, model, bucketStart));
@@ -8557,6 +8631,7 @@ function isCjkCodePoint(code) {
8557
8631
 
8558
8632
  module.exports = {
8559
8633
  listRolloutFiles,
8634
+ listRolloutFilesDeep,
8560
8635
  listClaudeProjectFiles,
8561
8636
  listGeminiSessionFiles,
8562
8637
  listOpencodeMessageFiles,