tokentracker-cli 0.53.2 → 0.53.3

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-CZasqY2H.js} +1 -1
  2. package/dashboard/dist/assets/{Card-CJiPCvnj.js → Card-Cjd-ZKzN.js} +1 -1
  3. package/dashboard/dist/assets/{DashboardPage-CrdiIz7q.js → DashboardPage-BCa11vC2.js} +1 -1
  4. package/dashboard/dist/assets/{DevicePage-DBjdZA2N.js → DevicePage-BFCRnDcz.js} +1 -1
  5. package/dashboard/dist/assets/{DialogTitle--jWfmv_b.js → DialogTitle-Btt5G5wB.js} +1 -1
  6. package/dashboard/dist/assets/{FadeIn-BrYhLQwK.js → FadeIn-gIH1bR94.js} +1 -1
  7. package/dashboard/dist/assets/{HeaderGithubStar-DGtt-L8c.js → HeaderGithubStar-BuzgYTZ5.js} +1 -1
  8. package/dashboard/dist/assets/{IpCheckPage-ToZsVEOj.js → IpCheckPage-Cn4cjpf4.js} +1 -1
  9. package/dashboard/dist/assets/{LandingPage-DgB2CT3R.js → LandingPage-BaPLcNme.js} +1 -1
  10. package/dashboard/dist/assets/{LeaderboardAvatar-94z-hs6u.js → LeaderboardAvatar-DMaYoU7B.js} +1 -1
  11. package/dashboard/dist/assets/{LeaderboardPage-hXIlg-XS.js → LeaderboardPage-D-qMCc2C.js} +3 -3
  12. package/dashboard/dist/assets/{LeaderboardProfileModal-BcppEyBi.js → LeaderboardProfileModal-CYzcdwRb.js} +1 -1
  13. package/dashboard/dist/assets/{LeaderboardProfilePage-BfZVW6cn.js → LeaderboardProfilePage-CQCqbWZE.js} +1 -1
  14. package/dashboard/dist/assets/{LimitsPage-CdcWyyh0.js → LimitsPage-M7a-TKW5.js} +1 -1
  15. package/dashboard/dist/assets/{LocalOnlyNotice-CBRDpg-i.js → LocalOnlyNotice-k-0-ORjj.js} +1 -1
  16. package/dashboard/dist/assets/{LoginPage-DLkWQvC1.js → LoginPage-BxAbRsn_.js} +1 -1
  17. package/dashboard/dist/assets/{PopoverPopup-DMl0dwMU.js → PopoverPopup-C4YO4_N-.js} +1 -1
  18. package/dashboard/dist/assets/{ResetPasswordPage-CwwTPCTr.js → ResetPasswordPage-Dqd9baMc.js} +1 -1
  19. package/dashboard/dist/assets/{Select-D3gMIEdB.js → Select-DXD3JLWK.js} +1 -1
  20. package/dashboard/dist/assets/{SelectItemText-GvEnESYK.js → SelectItemText-CTgRQ8ND.js} +1 -1
  21. package/dashboard/dist/assets/{SettingsPage-B3v6IkYh.js → SettingsPage-B2HxneYg.js} +1 -1
  22. package/dashboard/dist/assets/{SkillsPage-DX_mIIl8.js → SkillsPage-DEFIbkAX.js} +1 -1
  23. package/dashboard/dist/assets/{WidgetsPage-DgqWQ9EA.js → WidgetsPage-DObILWDk.js} +1 -1
  24. package/dashboard/dist/assets/{WrappedPage-CR02q4cO.js → WrappedPage-CHKlz7Tc.js} +1 -1
  25. package/dashboard/dist/assets/{agent-logos-B3gxKbbb.js → agent-logos-CO0k-UGZ.js} +1 -1
  26. package/dashboard/dist/assets/{arrow-up-right-DJo_-PxX.js → arrow-up-right-B_p-ZPxR.js} +1 -1
  27. package/dashboard/dist/assets/{download-BwsW6vug.js → download-zHPmUqi6.js} +1 -1
  28. package/dashboard/dist/assets/{info-C-aAjjA6.js → info-Cje0RF1w.js} +1 -1
  29. package/dashboard/dist/assets/{main-BbEXIjk8.js → main-DADupgLA.js} +2 -2
  30. package/dashboard/dist/assets/{use-limits-display-prefs-BUrvvpbW.js → use-limits-display-prefs-DIfRSzm0.js} +1 -1
  31. package/dashboard/dist/assets/{use-native-settings-SsQX6gzC.js → use-native-settings-BuBDLdTx.js} +1 -1
  32. package/dashboard/dist/assets/{use-usage-limits-LOfhEW65.js → use-usage-limits-CFd1PQLO.js} +1 -1
  33. package/dashboard/dist/assets/{useCurrency-CVVJYL2V.js → useCurrency-CIJjQNSk.js} +1 -1
  34. package/dashboard/dist/assets/{useScrollLock-Dm_66oFo.js → useScrollLock-DO1uWkvz.js} +1 -1
  35. package/dashboard/dist/index.html +1 -1
  36. package/dashboard/dist/share.html +1 -1
  37. package/package.json +1 -1
  38. package/src/commands/sync.js +198 -0
  39. package/src/lib/pricing/seed-snapshot.json +1 -1
  40. package/src/lib/rollout.js +50 -0
@@ -102,6 +102,13 @@ async function parseRolloutIncremental({
102
102
  cursors.files = {};
103
103
  }
104
104
 
105
+ // Persisted set of seen Codex event keys (sessionUUID:eventTimestamp). Mirrors
106
+ // the claudeHashes pattern: it makes an inode-changing re-scan idempotent so an
107
+ // external rewrite of a session file (Codex-Manager's atomic provider-patch on
108
+ // account switch, issue #187) cannot re-count already-counted events.
109
+ const prevCodexHashes = Array.isArray(cursors?.codexHashes) ? cursors.codexHashes : [];
110
+ const seenCodexEvents = new Set(prevCodexHashes);
111
+
105
112
  for (let idx = 0; idx < rolloutFiles.length; idx++) {
106
113
  const entry = rolloutFiles[idx];
107
114
  const filePath = typeof entry === "string" ? entry : entry?.path;
@@ -147,6 +154,8 @@ async function parseRolloutIncremental({
147
154
  projectMetaCache,
148
155
  publicRepoCache,
149
156
  publicRepoResolver,
157
+ seenCodexEvents,
158
+ sessionId: codexSessionIdFromPath(filePath),
150
159
  });
151
160
 
152
161
  cursors.files[key] = {
@@ -176,6 +185,7 @@ async function parseRolloutIncremental({
176
185
  const projectBucketsQueued = projectEnabled
177
186
  ? await enqueueTouchedProjectBuckets({ projectQueuePath, projectState, projectTouchedBuckets })
178
187
  : 0;
188
+ cursors.codexHashes = Array.from(seenCodexEvents);
179
189
  hourlyState.updatedAt = new Date().toISOString();
180
190
  cursors.hourly = hourlyState;
181
191
  if (projectState) {
@@ -718,6 +728,22 @@ async function parseOpenclawSessionFile({
718
728
  return { endOffset, eventsAggregated };
719
729
  }
720
730
 
731
+ /**
732
+ * Extract the session UUID from a Codex rollout file path
733
+ * (`rollout-<datetime>-<uuid>.jsonl`). Used as the stable per-session scope for
734
+ * event dedup: it survives both an inode-changing rewrite (Codex-Manager
735
+ * atomically rewrites session files to patch the provider on account switch,
736
+ * issue #187) and a sessions/ -> archived_sessions/ move. Returns null when the
737
+ * name has no UUID, in which case the caller falls back to the full path.
738
+ */
739
+ function codexSessionIdFromPath(filePath) {
740
+ if (typeof filePath !== "string") return null;
741
+ const m = filePath.match(
742
+ /([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$/,
743
+ );
744
+ return m ? m[1] : null;
745
+ }
746
+
721
747
  async function parseRolloutFile({
722
748
  filePath,
723
749
  startOffset,
@@ -733,6 +759,8 @@ async function parseRolloutFile({
733
759
  projectMetaCache,
734
760
  publicRepoCache,
735
761
  publicRepoResolver,
762
+ seenCodexEvents,
763
+ sessionId,
736
764
  }) {
737
765
  const st = await fs.stat(filePath);
738
766
  const endOffset = st.size;
@@ -830,6 +858,28 @@ async function parseRolloutFile({
830
858
  const bucketStart = toUtcHalfHourStart(tokenTimestamp);
831
859
  if (!bucketStart) continue;
832
860
 
861
+ // Idempotent re-scan dedup (issue #187). Codex usage is parsed incrementally
862
+ // by (inode, offset): when the inode changes the file is re-scanned from
863
+ // offset 0 and every event's delta is re-added to the PERSISTENT hourly
864
+ // buckets. External tools rewrite session files without changing the token
865
+ // data — Codex-Manager atomically rewrites them (new inode) to patch the
866
+ // provider on every account/channel switch — so without dedup each switch
867
+ // double-counts the rewritten sessions. `totals` is already advanced above,
868
+ // so skipping an already-seen event keeps the cumulative-delta chain intact
869
+ // while preventing the re-add; genuinely new turns carry new timestamps and
870
+ // are still counted. Key = sessionUUID:eventTimestamp (both stable across the
871
+ // rewrite and across a sessions/ -> archived_sessions/ move).
872
+ //
873
+ // Scoped to the `codex` source: Codex-Manager (the tool that does the atomic
874
+ // rewrite) manages Codex. Other rollout-format sources (e.g. every-code) have
875
+ // their own model re-alignment that legitimately re-reads prior events, which
876
+ // this dedup would otherwise suppress.
877
+ if (seenCodexEvents && source === "codex") {
878
+ const dedupKey = `${sessionId || filePath}:${tokenTimestamp}`;
879
+ if (seenCodexEvents.has(dedupKey)) continue;
880
+ seenCodexEvents.add(dedupKey);
881
+ }
882
+
833
883
  const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
834
884
  addTotals(bucket.totals, delta);
835
885
  touchedBuckets.add(bucketKey(source, model, bucketStart));