tokentracker-cli 1.0.0 → 1.0.2

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 (101) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-Dpf3VBcS.js → AchievementBadge-CBGtv8Gf.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-BSGbLI5V.js → AchievementsPage-DBNXAqHB.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-C10HXoq1.js → AchievementsSection-D9ndOgjA.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-DajrEc3e.js → BadgeDetailModal-B_xo1k4z.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-BpbTCJML.js → BotAnimated-OaYL-NrW.js} +1 -1
  6. package/dashboard/dist/assets/{Card-CDCj927L.js → Card-BprDyWQy.js} +1 -1
  7. package/dashboard/dist/assets/{ClawdAnimated-CiTkm04x.js → ClawdAnimated-DKzj5hnk.js} +2 -2
  8. package/dashboard/dist/assets/{CommandPalette-CYHUUX7M.js → CommandPalette-Y7y8uy9B.js} +1 -1
  9. package/dashboard/dist/assets/{CommunityStatsModal-BHZehk5P.js → CommunityStatsModal-DtJjD6Gk.js} +1 -1
  10. package/dashboard/dist/assets/{ConfirmModal-DZlT_xe_.js → ConfirmModal-Dnq9wDSB.js} +1 -1
  11. package/dashboard/dist/assets/{Controls-Bcijikco.js → Controls-B3LyuY9G.js} +1 -1
  12. package/dashboard/dist/assets/DashboardPage-uyz3GHPG.js +1 -0
  13. package/dashboard/dist/assets/{DevicePage-DzisRlts.js → DevicePage-TBvBfX-E.js} +1 -1
  14. package/dashboard/dist/assets/{DialogClose-CQ9hFUNv.js → DialogClose-C0xfVNxI.js} +1 -1
  15. package/dashboard/dist/assets/DialogDescription-B1_lWQbi.js +1 -0
  16. package/dashboard/dist/assets/{DialogTitle--bItxq3K.js → DialogTitle-h96NnlwF.js} +1 -1
  17. package/dashboard/dist/assets/{FadeIn-v5sAHZBu.js → FadeIn-BlT9LOhg.js} +1 -1
  18. package/dashboard/dist/assets/{HeaderGithubStar-Bg8R51fN.js → HeaderGithubStar-BC8PlJFf.js} +1 -1
  19. package/dashboard/dist/assets/{HoverTooltip-DToNFKkJ.js → HoverTooltip-D2H0rvOx.js} +1 -1
  20. package/dashboard/dist/assets/{IpCheckPage-BmFXYXZm.js → IpCheckPage-Bf9pTUyL.js} +1 -1
  21. package/dashboard/dist/assets/{LandingPage-COpRacX0.js → LandingPage-CqFZZcgl.js} +2 -2
  22. package/dashboard/dist/assets/{LeaderboardAvatar-D0l8_L_d.js → LeaderboardAvatar-Bglw3mWk.js} +1 -1
  23. package/dashboard/dist/assets/{LeaderboardPage-BJHS44Fq.js → LeaderboardPage-Ba4rstvv.js} +2 -2
  24. package/dashboard/dist/assets/{LeaderboardProfileModal-C1K9rSGn.js → LeaderboardProfileModal-DITfDxdj.js} +3 -3
  25. package/dashboard/dist/assets/{LeaderboardProfilePage-CvR48D_y.js → LeaderboardProfilePage-9DgMqaVi.js} +1 -1
  26. package/dashboard/dist/assets/{LimitsPage-BN79yMkb.js → LimitsPage-Ct5IA5Ny.js} +1 -1
  27. package/dashboard/dist/assets/{LocalOnlyNotice-BwTJpkDW.js → LocalOnlyNotice-DCgOvXBZ.js} +1 -1
  28. package/dashboard/dist/assets/{LoginCard-BMXAg1rZ.js → LoginCard-Bo3St7wp.js} +1 -1
  29. package/dashboard/dist/assets/{LoginModal-Bf0F_UPK.js → LoginModal-CqN9Lhx0.js} +1 -1
  30. package/dashboard/dist/assets/{LoginPage-BylH2waa.js → LoginPage-CLvn_ZAo.js} +1 -1
  31. package/dashboard/dist/assets/{PetPage-X4v_J-Uj.js → PetPage-B-tmGNaA.js} +1 -1
  32. package/dashboard/dist/assets/{PopoverPopup-yEQVU5C_.js → PopoverPopup-D0KIhAfw.js} +1 -1
  33. package/dashboard/dist/assets/{ProviderIcon-KRXIghW0.js → ProviderIcon-DLytDN3-.js} +1 -1
  34. package/dashboard/dist/assets/{ResetPasswordPage-DQQF2rsl.js → ResetPasswordPage-CGVTHdB9.js} +1 -1
  35. package/dashboard/dist/assets/{SegmentedControl-VkVwcoXk.js → SegmentedControl-B7gk-3QK.js} +1 -1
  36. package/dashboard/dist/assets/{Select-CxW2C8OG.js → Select-qbqB_qtp.js} +1 -1
  37. package/dashboard/dist/assets/{SelectItemText-D6vhaI0Q.js → SelectItemText-Qzhp3UFE.js} +1 -1
  38. package/dashboard/dist/assets/{ServiceStatusPage-CCKqUZhI.js → ServiceStatusPage-DPe_EeVu.js} +1 -1
  39. package/dashboard/dist/assets/{SessionsPage-Huqt77Qt.js → SessionsPage-Dza7qrmT.js} +1 -1
  40. package/dashboard/dist/assets/{SettingsPage-CpX8GYce.js → SettingsPage-C_Am0uFM.js} +2 -2
  41. package/dashboard/dist/assets/{SkillsPage-S1dtu_Cd.js → SkillsPage-Bsu5m-xp.js} +1 -1
  42. package/dashboard/dist/assets/{TokenGalaxy-BayM0FOG.js → TokenGalaxy-COH6RLu3.js} +1 -1
  43. package/dashboard/dist/assets/{ToolbarRootContext-B1KG8B5q.js → ToolbarRootContext-B_XUhyzz.js} +1 -1
  44. package/dashboard/dist/assets/{TrendMonitor-DYqz9nLw.js → TrendMonitor-DMgbTVNO.js} +1 -1
  45. package/dashboard/dist/assets/{WidgetsPage-WuzVeCBG.js → WidgetsPage-gQTmgsN6.js} +1 -1
  46. package/dashboard/dist/assets/{WrappedPage-Bpn9GYHz.js → WrappedPage-B5nLQG1F.js} +1 -1
  47. package/dashboard/dist/assets/__vite-browser-external-DUAYhIIg.js +45 -0
  48. package/dashboard/dist/assets/{agent-logos-DVE956SY.js → agent-logos-B92se84f.js} +1 -1
  49. package/dashboard/dist/assets/{arrow-up-right-B7aZy0wJ.js → arrow-up-right-THgpMHTL.js} +1 -1
  50. package/dashboard/dist/assets/{check-55YI8PGo.js → check-CZ5GlBuF.js} +1 -1
  51. package/dashboard/dist/assets/{composite--O2XKSlW.js → composite-pohmvEEN.js} +1 -1
  52. package/dashboard/dist/assets/{copy-DDVFmqF5.js → copy-BEu6UqzK.js} +1 -1
  53. package/dashboard/dist/assets/{download-DmIFrL1S.js → download-580FSMzx.js} +1 -1
  54. package/dashboard/dist/assets/{external-link-DwG_AAOo.js → external-link-DSl6rKSu.js} +1 -1
  55. package/dashboard/dist/assets/{flame-ANwvantJ.js → flame-CuoqhgO0.js} +1 -1
  56. package/dashboard/dist/assets/{format-tokens-BzZ_DfX2.js → format-tokens-yngxPaJs.js} +1 -1
  57. package/dashboard/dist/assets/{icons-CdyVgzlu.js → icons-C5P7F226.js} +1 -1
  58. package/dashboard/dist/assets/{index-D8g1p_Ee.js → index-1CFe9s91.js} +1 -1
  59. package/dashboard/dist/assets/{index-B-1uJU_R.js → index-CVccZBhd.js} +1 -1
  60. package/dashboard/dist/assets/{index-D0VhCSlC.js → index-DkLVCz9v.js} +1 -1
  61. package/dashboard/dist/assets/{info-n2XpcOlm.js → info-BtYRYa79.js} +1 -1
  62. package/dashboard/dist/assets/{link-2-Bq9GUX22.js → link-2-D4bPs6lx.js} +1 -1
  63. package/dashboard/dist/assets/{loader-circle-fkYFxXuJ.js → loader-circle-CAcXf7Ue.js} +1 -1
  64. package/dashboard/dist/assets/{maximize-2-C5v08iPD.js → maximize-2-B3xxE3HC.js} +1 -1
  65. package/dashboard/dist/assets/{modifiers.esm-AG_bzlWZ.js → modifiers.esm-DvoWZTDt.js} +1 -1
  66. package/dashboard/dist/assets/{monitor-smartphone-BEjlJhuw.js → monitor-smartphone-CkyNQoTv.js} +1 -1
  67. package/dashboard/dist/assets/{plus-BfdtMxnD.js → plus-BCXK4rAO.js} +1 -1
  68. package/dashboard/dist/assets/{provider-display-BvjuoQty.js → provider-display-Y5ggva-H.js} +1 -1
  69. package/dashboard/dist/assets/{react-f77D-LSn.js → react-CjlEHr8p.js} +1 -1
  70. package/dashboard/dist/assets/{search-Dmczow8i.js → search-Dh5kBuL2.js} +1 -1
  71. package/dashboard/dist/assets/{skills-api-BuqPQei_.js → skills-api-XhL8INjh.js} +1 -1
  72. package/dashboard/dist/assets/{sortable.esm-CDAVQaFQ.js → sortable.esm-CFa-CzqV.js} +1 -1
  73. package/dashboard/dist/assets/{store-C2inKcM-.js → store-Bt47laU0.js} +1 -1
  74. package/dashboard/dist/assets/{terminal-C1O422UZ.js → terminal-BXW29s08.js} +1 -1
  75. package/dashboard/dist/assets/{trash-CXOKPZfQ.js → trash-CYw5PqQr.js} +1 -1
  76. package/dashboard/dist/assets/{use-community-stats-CKfyxLvl.js → use-community-stats-C5t_95Jj.js} +1 -1
  77. package/dashboard/dist/assets/{use-latest-request-guard-C3rQR4BN.js → use-latest-request-guard-CcrXitYE.js} +1 -1
  78. package/dashboard/dist/assets/{use-limits-display-prefs-Ic4GUPRY.js → use-limits-display-prefs-DktuWI-1.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list-Cvbz_wcj.js → use-ordered-list-DUHxxfXc.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-dYTH0OxK.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-u_qW99gD.js → use-session-efficiency-pref-CDSUns2p.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-CVGiBxJK.js → use-transform-DkLOu1mx.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-MPu696v0.js → use-usage-limits-BTbbhOUs.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-BOBz18If.js → useCurrency-C7QiSZUH.js} +1 -1
  85. package/dashboard/dist/assets/{useTokenFormat-BooCzJJZ.js → useTokenFormat-Kv5h6RPd.js} +1 -1
  86. package/dashboard/dist/assets/{zap-M_x9dAwo.js → zap-DOdxB6Nn.js} +1 -1
  87. package/dashboard/dist/index.html +1 -1
  88. package/dashboard/dist/ip-check.html +1 -1
  89. package/dashboard/dist/leaderboard.html +1 -1
  90. package/dashboard/dist/share.html +1 -1
  91. package/package.json +1 -1
  92. package/src/commands/status.js +3 -0
  93. package/src/commands/sync.js +10 -1
  94. package/src/lib/codex-rollout-parser.js +86 -8
  95. package/src/lib/codex-usage-record.js +195 -0
  96. package/src/lib/pricing/seed-snapshot.json +1 -1
  97. package/src/lib/rollout.js +154 -1
  98. package/dashboard/dist/assets/DashboardPage-yREqx-tx.js +0 -1
  99. package/dashboard/dist/assets/DialogDescription-CLXWH3lG.js +0 -1
  100. package/dashboard/dist/assets/__vite-browser-external-B1XxVtUQ.js +0 -45
  101. package/dashboard/dist/assets/use-reduced-motion-DewlF5tk.js +0 -1
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+
3
+ // Codex rollouts written since CLI 0.151 carry a top-level
4
+ // {"type":"token_usage_record","payload":{usage,turn_token_usage,thread_token_usage,...}}
5
+ // next to the event_msg token_count that has always carried usage (issue
6
+ // #652). While both are written they describe the same model response, so
7
+ // token_count stays the only source of normal usage and a record is counted
8
+ // in exactly one case:
9
+ //
10
+ // Compaction. Codex bills the compaction model call in a record that is
11
+ // immediately followed by a top-level `compacted` line, and the next
12
+ // token_count repeats the previous cumulative total (its last_token_usage is
13
+ // the post-compaction context size, not a response). The cumulative delta is
14
+ // zero, so without the record the call is lost. Inside a file that has
15
+ // already shown a token_count, such a record is held as `compaction` and
16
+ // counted when the next token_count arrives with a total identical to the
17
+ // previous one. If that total advanced, the token_count already covers the
18
+ // call and the record is dropped. Each compaction is counted once per
19
+ // payload.response_id: a fork replays its parent's records and a copied
20
+ // rollout repeats them, both with the same id. A record without one is never
21
+ // counted.
22
+ //
23
+ // A file whose records are never matched by a token_count is not counted from
24
+ // its records. It is flagged `recordOnly` instead, and sync surfaces a
25
+ // warning. The proof is the same ordering Codex keeps for every response: its
26
+ // token_count is written after its record and before the next record or the
27
+ // turn end. So a record followed by another record or a turn end with no
28
+ // token_count in between flags the file; a sync that stops between a record
29
+ // and its token_count does not.
30
+ //
31
+ // payload.usage is the per-response delta; turn_token_usage and
32
+ // thread_token_usage are cumulative and are never read. All state below is
33
+ // persisted in the sync cursor and the session parser resume state, so the
34
+ // record, the `compacted` line and the token_count may land in different
35
+ // reads.
36
+
37
+ const { canonicalUsage, sameUsage } = require("./codex-token-usage");
38
+
39
+ function extractTokenUsageRecord(obj) {
40
+ if (obj?.type !== "token_usage_record") return null;
41
+ const usage = obj.payload?.usage;
42
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) return null;
43
+ const timestamp = typeof obj.timestamp === "string" ? obj.timestamp : null;
44
+ if (!timestamp) return null;
45
+ // canonicalUsage folds cache_write_input_tokens into
46
+ // cache_creation_input_tokens exactly as the token_count path does.
47
+ const canonical = canonicalUsage(usage);
48
+ if (!canonical) return null;
49
+ const responseId = typeof obj.payload.response_id === "string" && obj.payload.response_id
50
+ ? obj.payload.response_id
51
+ : null;
52
+ return { usage: canonical, timestamp, responseId };
53
+ }
54
+
55
+ // Compaction calls already counted by sync, by response_id, kept in core
56
+ // cursor state as cursors.codexCompactionResponseIds (oldest first). The id
57
+ // is unique per API response and survives fork replays and file copies, so
58
+ // it is the only dedup key a compaction needs. Compactions are rare (19 on
59
+ // the maintainer's corpus), so the cap is far above any real history.
60
+ const MAX_COMPACTION_RESPONSE_IDS = 20000;
61
+
62
+ function createCompactionResponseIds(cursors) {
63
+ const stored = Array.isArray(cursors.codexCompactionResponseIds)
64
+ ? cursors.codexCompactionResponseIds
65
+ : [];
66
+ const ids = new Set(stored);
67
+ const added = [];
68
+ return {
69
+ has: (id) => ids.has(id),
70
+ add(id) {
71
+ if (ids.has(id)) return;
72
+ ids.add(id);
73
+ added.push(id);
74
+ },
75
+ persist() {
76
+ if (added.length === 0) return;
77
+ cursors.codexCompactionResponseIds = [...stored, ...added].slice(-MAX_COMPACTION_RESPONSE_IDS);
78
+ },
79
+ };
80
+ }
81
+
82
+ function isCodexTurnEndEvent(obj) {
83
+ if (obj?.type !== "event_msg") return false;
84
+ const type = obj.payload?.type;
85
+ return type === "task_complete" || type === "turn_aborted";
86
+ }
87
+
88
+ function isPlainObject(value) {
89
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
90
+ }
91
+
92
+ function createUsageRecordState(snapshot) {
93
+ const compaction = snapshot?.compaction;
94
+ return {
95
+ sawTokenCount: Boolean(snapshot?.sawTokenCount),
96
+ unmatchedRecord: Boolean(snapshot?.unmatchedRecord),
97
+ recordOnly: Boolean(snapshot?.recordOnly),
98
+ compaction: isPlainObject(compaction) && isPlainObject(compaction.candidate)
99
+ ? { candidate: compaction.candidate, compacted: Boolean(compaction.compacted) }
100
+ : null,
101
+ };
102
+ }
103
+
104
+ // Called for every token_count carrying an info object.
105
+ function noteTokenCount(state) {
106
+ state.sawTokenCount = true;
107
+ state.unmatchedRecord = false;
108
+ state.recordOnly = false;
109
+ }
110
+
111
+ // Called for every record. `candidate` is what the caller would count if the
112
+ // record turns out to be a compaction call (null when it cannot be counted).
113
+ function noteUsageRecord(state, candidate) {
114
+ if (state.sawTokenCount) {
115
+ state.compaction = candidate ? { candidate, compacted: false } : null;
116
+ return;
117
+ }
118
+ if (state.unmatchedRecord) state.recordOnly = true;
119
+ state.unmatchedRecord = true;
120
+ }
121
+
122
+ // Turn end: a record still waiting for its token_count will not get one.
123
+ function noteTurnEnd(state) {
124
+ if (state.sawTokenCount) return;
125
+ if (state.unmatchedRecord) state.recordOnly = true;
126
+ state.unmatchedRecord = false;
127
+ }
128
+
129
+ // True while the line right after a record decides whether it was a
130
+ // compaction call.
131
+ function awaitsCompactedLine(state) {
132
+ return Boolean(state?.compaction && !state.compaction.compacted);
133
+ }
134
+
135
+ // Called for each complete line after a record, other than a record. Only a
136
+ // `compacted` line directly after the record keeps it as a candidate.
137
+ function noteLineAfterUsageRecord(state, isCompactedLine) {
138
+ if (!awaitsCompactedLine(state)) return;
139
+ if (isCompactedLine) state.compaction.compacted = true;
140
+ else state.compaction = null;
141
+ }
142
+
143
+ // Called for every token_count carrying an info object, with the cumulative
144
+ // total that was active before it. Returns the compaction candidate to count,
145
+ // or null.
146
+ function takeCompactionOnTokenCount(state, previousTotal, totalUsage) {
147
+ const compaction = state.compaction;
148
+ state.compaction = null;
149
+ if (!compaction?.compacted) return null;
150
+ if (!canonicalUsage(previousTotal) || !sameUsage(previousTotal, totalUsage)) return null;
151
+ return compaction.candidate;
152
+ }
153
+
154
+ function isRecordOnly(state) {
155
+ return Boolean(state?.recordOnly && !state.sawTokenCount);
156
+ }
157
+
158
+ function snapshotUsageRecordState(state) {
159
+ if (!state || (!state.sawTokenCount && !state.unmatchedRecord && !state.recordOnly)) return null;
160
+ const out = { sawTokenCount: state.sawTokenCount };
161
+ if (state.unmatchedRecord) out.unmatchedRecord = true;
162
+ if (state.recordOnly) out.recordOnly = true;
163
+ if (state.compaction) out.compaction = state.compaction;
164
+ return out;
165
+ }
166
+
167
+ // Sync keeps the flagged rollouts in core cursor state:
168
+ // cursors.codexUsageRecordOnlyFiles = { [rolloutPath]: true }.
169
+ function countRecordOnlyFiles(cursors) {
170
+ const flagged = cursors?.codexUsageRecordOnlyFiles;
171
+ return isPlainObject(flagged) ? Object.keys(flagged).length : 0;
172
+ }
173
+
174
+ function formatRecordOnlyWarning(count) {
175
+ if (!count) return null;
176
+ return `${count} Codex session(s) report usage only via token_usage_record; not counted yet (see #652)`;
177
+ }
178
+
179
+ module.exports = {
180
+ MAX_COMPACTION_RESPONSE_IDS,
181
+ createCompactionResponseIds,
182
+ countRecordOnlyFiles,
183
+ formatRecordOnlyWarning,
184
+ awaitsCompactedLine,
185
+ createUsageRecordState,
186
+ extractTokenUsageRecord,
187
+ isCodexTurnEndEvent,
188
+ isRecordOnly,
189
+ noteLineAfterUsageRecord,
190
+ noteTokenCount,
191
+ noteTurnEnd,
192
+ noteUsageRecord,
193
+ snapshotUsageRecordState,
194
+ takeCompactionOnTokenCount,
195
+ };