tokentracker-cli 0.79.9 → 0.80.1

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 (74) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-B1WOPlu0.js → AchievementBadge-D617bvS9.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-9kHEQicS.js → AchievementsPage-mzE76Ah9.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-Dw05PYPr.js → AchievementsSection-8CYGBoPJ.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-DaWgiijJ.js → BadgeDetailModal-DM3veVmW.js} +1 -1
  5. package/dashboard/dist/assets/{Card-DYhrLLM4.js → Card-DWONuizy.js} +1 -1
  6. package/dashboard/dist/assets/{ClawdAnimated-BLy2LUKz.js → ClawdAnimated-CGQXyWuH.js} +1 -1
  7. package/dashboard/dist/assets/{CommunityStatsModal-CWYqfht3.js → CommunityStatsModal-B5yDmJ0o.js} +1 -1
  8. package/dashboard/dist/assets/{ConfirmModal-DcE2C24Y.js → ConfirmModal-d4wpsMPW.js} +1 -1
  9. package/dashboard/dist/assets/{Controls-CI7qw0-b.js → Controls-CMSqknaB.js} +1 -1
  10. package/dashboard/dist/assets/{DashboardPage-CAlgaBa7.js → DashboardPage-Cw6lmVSg.js} +1 -1
  11. package/dashboard/dist/assets/{DevicePage-DinOyaeN.js → DevicePage-CsPSjSLH.js} +1 -1
  12. package/dashboard/dist/assets/{DialogClose-CjfSDrLo.js → DialogClose-Cbs-z783.js} +1 -1
  13. package/dashboard/dist/assets/DialogDescription-B0fmBJxQ.js +1 -0
  14. package/dashboard/dist/assets/{DialogTitle-f7gNmCca.js → DialogTitle-C4mJiIQW.js} +1 -1
  15. package/dashboard/dist/assets/{FadeIn-D_5w0Qh0.js → FadeIn-xOJwCmZ8.js} +1 -1
  16. package/dashboard/dist/assets/{HeaderGithubStar-DTZyNKyL.js → HeaderGithubStar-oRz4rfC8.js} +1 -1
  17. package/dashboard/dist/assets/{IpCheckPage-D8CViF98.js → IpCheckPage-CTwL-Zmz.js} +1 -1
  18. package/dashboard/dist/assets/{LandingPage-CqN5KEjV.js → LandingPage-CqYJ3sZh.js} +1 -1
  19. package/dashboard/dist/assets/{LeaderboardAvatar-CwUVO4wn.js → LeaderboardAvatar-CuF6Kzkj.js} +1 -1
  20. package/dashboard/dist/assets/{LeaderboardPage-B12HJ9LA.js → LeaderboardPage-BvOPIj-b.js} +2 -2
  21. package/dashboard/dist/assets/{LeaderboardProfileModal-DFzPpsnV.js → LeaderboardProfileModal-DVXuKkRN.js} +3 -3
  22. package/dashboard/dist/assets/{LeaderboardProfilePage-Z0ZYpNFd.js → LeaderboardProfilePage-RE1CPqzM.js} +1 -1
  23. package/dashboard/dist/assets/{LimitsPage-BpwioIa-.js → LimitsPage-BovkRgac.js} +1 -1
  24. package/dashboard/dist/assets/{LocalOnlyNotice-Covi6zdt.js → LocalOnlyNotice-CTFFU_ip.js} +1 -1
  25. package/dashboard/dist/assets/{LoginPage-CRqbhwCx.js → LoginPage-mguBCTBZ.js} +1 -1
  26. package/dashboard/dist/assets/{PetPage-BY_yObVj.js → PetPage-C5KLH6Fm.js} +1 -1
  27. package/dashboard/dist/assets/{PopoverPopup-B2dE3Ary.js → PopoverPopup-CLDh5yIk.js} +1 -1
  28. package/dashboard/dist/assets/{ResetPasswordPage-C8IEJbVc.js → ResetPasswordPage-DnhgHQrV.js} +1 -1
  29. package/dashboard/dist/assets/{Select-BmRLYSqA.js → Select-DPSaPIlz.js} +1 -1
  30. package/dashboard/dist/assets/{SelectItemText-FC46bk8b.js → SelectItemText-jIBbXpqi.js} +1 -1
  31. package/dashboard/dist/assets/{SettingsPage-DipzOQVg.js → SettingsPage-BK0IqSjg.js} +1 -1
  32. package/dashboard/dist/assets/{SkillsPage-CziypUTf.js → SkillsPage-BQNoMpL2.js} +1 -1
  33. package/dashboard/dist/assets/{WidgetsPage-CJegJhVR.js → WidgetsPage-308d8icu.js} +1 -1
  34. package/dashboard/dist/assets/{WrappedPage-DcMiKvzf.js → WrappedPage-T720sQ9S.js} +1 -1
  35. package/dashboard/dist/assets/{agent-logos-S7bF1LwW.js → agent-logos-Blo5C96d.js} +1 -1
  36. package/dashboard/dist/assets/{arrow-up-right-D0yeL56y.js → arrow-up-right-DKz2Ua6U.js} +1 -1
  37. package/dashboard/dist/assets/check-fF6lQGN-.js +1 -0
  38. package/dashboard/dist/assets/{copy-D-z9ZtIu.js → copy-DcUFG0PD.js} +1 -1
  39. package/dashboard/dist/assets/{download-Dwxnd3QG.js → download-CNK1wtxV.js} +1 -1
  40. package/dashboard/dist/assets/{flame-Bdqk9A75.js → flame-BEhJZRaZ.js} +1 -1
  41. package/dashboard/dist/assets/{format-tokens-DNVcCRwm.js → format-tokens-Bsfbd8nj.js} +1 -1
  42. package/dashboard/dist/assets/{info-CMLT6oUI.js → info-DRJLv-aV.js} +1 -1
  43. package/dashboard/dist/assets/{link-2-BmiHu726.js → link-2-C0GdaANY.js} +1 -1
  44. package/dashboard/dist/assets/{loader-circle-CXVUQtzm.js → loader-circle-F9ymYeor.js} +1 -1
  45. package/dashboard/dist/assets/{main-B9m79io-.js → main-CThKxvKx.js} +3 -3
  46. package/dashboard/dist/assets/{provider-display-BoAesdke.js → provider-display-D4NdFjHN.js} +1 -1
  47. package/dashboard/dist/assets/{trash-2-C9Rh2DAL.js → trash-2-D9vulGMW.js} +1 -1
  48. package/dashboard/dist/assets/{use-community-stats-aZwYxrXR.js → use-community-stats-BU4dXEOM.js} +1 -1
  49. package/dashboard/dist/assets/{use-limits-display-prefs-tJKR83RT.js → use-limits-display-prefs-Bx2VJhwl.js} +1 -1
  50. package/dashboard/dist/assets/{use-native-settings-PjfJPK5v.js → use-native-settings-BSXaxaTC.js} +1 -1
  51. package/dashboard/dist/assets/{use-ordered-list-D1QFmZUY.js → use-ordered-list-OA0HwwEL.js} +1 -1
  52. package/dashboard/dist/assets/{use-quality-per-dollar-pref-4N3Su4wC.js → use-quality-per-dollar-pref-Bqooc4FA.js} +1 -1
  53. package/dashboard/dist/assets/{use-transform-D9nurInL.js → use-transform-CZ9FdRGw.js} +1 -1
  54. package/dashboard/dist/assets/{use-usage-limits-DapTpi_6.js → use-usage-limits-Ct_A4wTr.js} +1 -1
  55. package/dashboard/dist/assets/{useCurrency-B0DH9ZZA.js → useCurrency-D4bvdaFB.js} +1 -1
  56. package/dashboard/dist/assets/{useScrollLock-DijV6DYS.js → useScrollLock-ZsWO-GoL.js} +1 -1
  57. package/dashboard/dist/assets/useTokenFormat-B3huMEws.js +1 -0
  58. package/dashboard/dist/assets/{zap-BVfhR3Ck.js → zap-C9Mp-GNU.js} +1 -1
  59. package/dashboard/dist/index.html +1 -1
  60. package/dashboard/dist/share.html +1 -1
  61. package/package.json +1 -1
  62. package/src/commands/init.js +607 -6
  63. package/src/commands/sync.js +176 -7
  64. package/src/lib/codex-config.js +135 -40
  65. package/src/lib/codex-context-breakdown.js +123 -43
  66. package/src/lib/codex-rollout-parser.js +329 -32
  67. package/src/lib/local-api.js +11 -5
  68. package/src/lib/openclaw-session-plugin.js +13 -0
  69. package/src/lib/pricing/seed-snapshot.json +1 -1
  70. package/src/lib/rollout.js +230 -10
  71. package/src/lib/usage-limits.js +9 -5
  72. package/dashboard/dist/assets/DialogDescription-DWs-KIUA.js +0 -1
  73. package/dashboard/dist/assets/check-Dn9BLYEf.js +0 -1
  74. package/dashboard/dist/assets/useTokenFormat-nxdG6gi9.js +0 -1
@@ -10,7 +10,7 @@
10
10
 
11
11
  "use strict";
12
12
 
13
- const fs = require("node:fs");
13
+ const crypto = require("node:crypto");
14
14
  const os = require("node:os");
15
15
  const path = require("node:path");
16
16
 
@@ -25,8 +25,8 @@ const {
25
25
 
26
26
  const {
27
27
  parseCodexRolloutFile,
28
- listCodexSessionFiles,
29
- dayKeyToIsoBounds,
28
+ discoverCodexSessionFiles,
29
+ rolloutSessionIdFromPath,
30
30
  finalizeToolRows,
31
31
  finalizeSkillRows,
32
32
  finalizeExecRows,
@@ -120,25 +120,55 @@ function buildDateRange({ period, date }) {
120
120
  // ---------------------------------------------------------------------------
121
121
 
122
122
  const CACHE = new Map();
123
+ const PARSED_GROUP_CACHE = new Map();
123
124
  const CACHE_TTL_MS = 60_000;
124
- const CACHE_SCHEMA_VERSION = "codex-context-v2";
125
-
126
- function maxMtimeMs(files) {
127
- let max = 0;
128
- for (const filePath of files) {
129
- try {
130
- const st = fs.statSync(filePath);
131
- if (st.mtimeMs > max) max = st.mtimeMs;
132
- } catch {}
133
- }
134
- return max;
135
- }
125
+ const CACHE_SCHEMA_VERSION = "codex-context-v4";
126
+ const MAX_PARSED_GROUP_CACHE_ENTRIES = 4096;
136
127
 
137
128
  function cacheTimeZoneKey(timeZoneContext) {
138
129
  if (!timeZoneContext) return "";
139
130
  return `${timeZoneContext.timeZone || ""}|${Number.isFinite(timeZoneContext.offsetMinutes) ? timeZoneContext.offsetMinutes : ""}`;
140
131
  }
141
132
 
133
+ function statIdentity(filePath, stat) {
134
+ return JSON.stringify([filePath, stat.size, stat.mtimeMs, stat.ctimeMs, stat.ino]);
135
+ }
136
+
137
+ function inventorySignature(entries) {
138
+ const hash = crypto.createHash("sha256");
139
+ for (const { filePath, stat, candidate } of entries) {
140
+ hash.update(statIdentity(filePath, stat));
141
+ hash.update(candidate ? "1" : "0");
142
+ hash.update("\0");
143
+ }
144
+ return hash.digest("hex");
145
+ }
146
+
147
+ function parsedGroupCacheKey(entries, { from, to, timeZoneContext }) {
148
+ const hash = crypto.createHash("sha256");
149
+ hash.update(CACHE_SCHEMA_VERSION);
150
+ hash.update(from || "");
151
+ hash.update("\0");
152
+ hash.update(to || "");
153
+ hash.update("\0");
154
+ hash.update(cacheTimeZoneKey(timeZoneContext));
155
+ for (const { filePath, stat } of entries) {
156
+ hash.update(statIdentity(filePath, stat));
157
+ hash.update("\0");
158
+ }
159
+ return hash.digest("hex");
160
+ }
161
+
162
+ function rememberParsedGroup(key, value) {
163
+ PARSED_GROUP_CACHE.delete(key);
164
+ PARSED_GROUP_CACHE.set(key, value);
165
+ while (PARSED_GROUP_CACHE.size > MAX_PARSED_GROUP_CACHE_ENTRIES) {
166
+ const oldest = PARSED_GROUP_CACHE.keys().next().value;
167
+ if (!oldest) break;
168
+ PARSED_GROUP_CACHE.delete(oldest);
169
+ }
170
+ }
171
+
142
172
  // ---------------------------------------------------------------------------
143
173
  // Main entry
144
174
  // ---------------------------------------------------------------------------
@@ -151,6 +181,9 @@ async function computeCodexContextBreakdown({
151
181
  codexDir = null,
152
182
  top = 20,
153
183
  timeZoneContext = null,
184
+ exhaustive = false,
185
+ nowMs = Date.now(),
186
+ includeDiagnostics = false,
154
187
  } = {}) {
155
188
  let fromKey = from;
156
189
  let toKey = to;
@@ -160,10 +193,33 @@ async function computeCodexContextBreakdown({
160
193
  toKey = range?.to || null;
161
194
  }
162
195
 
163
- const { fromIso, toIso } = dayKeyToIsoBounds(fromKey, toKey);
164
- const baseDir = codexDir || path.join(os.homedir(), ".codex", "sessions");
165
-
166
- const files = await listCodexSessionFiles(baseDir);
196
+ const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
197
+ const roots = codexDir
198
+ ? [codexDir]
199
+ : [path.join(codexHome, "sessions"), path.join(codexHome, "archived_sessions")];
200
+ const baseDir = roots.join(path.delimiter);
201
+ const diagnostics = {
202
+ cache_hit: false,
203
+ full_metadata_audit: false,
204
+ discovered_files: 0,
205
+ candidate_files: 0,
206
+ stat_calls: 0,
207
+ metadata_cache_hits: 0,
208
+ opened_files: 0,
209
+ parsed_files: 0,
210
+ parse_cache_hits: 0,
211
+ json_parse_calls: 0,
212
+ };
213
+ const discovered = discoverCodexSessionFiles(roots, {
214
+ from: fromKey,
215
+ to: toKey,
216
+ timeZoneContext,
217
+ exhaustive,
218
+ nowMs,
219
+ diagnostics,
220
+ });
221
+ const candidates = discovered.filter((entry) => entry.candidate);
222
+ const fileSignature = inventorySignature(discovered);
167
223
  const cacheKey = [
168
224
  CACHE_SCHEMA_VERSION,
169
225
  baseDir,
@@ -171,24 +227,48 @@ async function computeCodexContextBreakdown({
171
227
  toKey || "",
172
228
  cacheTimeZoneKey(timeZoneContext),
173
229
  Number.isFinite(top) ? top : 20,
174
- files.length,
175
- maxMtimeMs(files),
230
+ exhaustive ? "exhaustive" : "bounded",
231
+ fileSignature,
176
232
  ].join("|");
177
233
  const cached = CACHE.get(cacheKey);
178
234
  if (cached && Date.now() - cached.at < CACHE_TTL_MS) {
179
- return cached.value;
235
+ diagnostics.cache_hit = true;
236
+ return includeDiagnostics ? { ...cached.value, diagnostics } : cached.value;
180
237
  }
181
238
 
182
239
  const sessions = [];
240
+ const seenTokenEvents = new Set();
241
+
242
+ const candidateGroups = new Map();
243
+ for (const entry of candidates) {
244
+ const sessionKey = rolloutSessionIdFromPath(entry.filePath) || entry.filePath;
245
+ if (!candidateGroups.has(sessionKey)) candidateGroups.set(sessionKey, []);
246
+ candidateGroups.get(sessionKey).push(entry);
247
+ }
183
248
 
184
- for (const filePath of files) {
185
- const parsed = await parseCodexRolloutFile(filePath, {
186
- fromIso,
187
- toIso,
188
- from: fromKey,
189
- to: toKey,
190
- timeZoneContext,
191
- });
249
+ for (const entries of candidateGroups.values()) {
250
+ const filePaths = entries.map((entry) => entry.filePath);
251
+ const cacheable = Boolean(rolloutSessionIdFromPath(filePaths[0]));
252
+ const parsedCacheKey = cacheable
253
+ ? parsedGroupCacheKey(entries, { from: fromKey, to: toKey, timeZoneContext })
254
+ : null;
255
+ let parsed = parsedCacheKey ? PARSED_GROUP_CACHE.get(parsedCacheKey) : null;
256
+ if (parsed) {
257
+ // Cacheable groups are partitioned by rollout UUID, so their token event
258
+ // keys cannot overlap and cache hits need not backfill seenTokenEvents.
259
+ diagnostics.parse_cache_hits += 1;
260
+ PARSED_GROUP_CACHE.delete(parsedCacheKey);
261
+ PARSED_GROUP_CACHE.set(parsedCacheKey, parsed);
262
+ } else {
263
+ parsed = await parseCodexRolloutFile(filePaths.length === 1 ? filePaths[0] : filePaths, {
264
+ from: fromKey,
265
+ to: toKey,
266
+ timeZoneContext,
267
+ diagnostics,
268
+ seenTokenEvents,
269
+ });
270
+ if (parsedCacheKey) rememberParsedGroup(parsedCacheKey, parsed);
271
+ }
192
272
  if (!parsed || !parsed.totals || !parsed.totals.total_tokens) continue;
193
273
  sessions.push(parsed);
194
274
  }
@@ -261,26 +341,25 @@ async function computeCodexContextBreakdown({
261
341
  }))
262
342
  .sort((a, b) => (b.totals?.total_tokens || 0) - (a.totals?.total_tokens || 0));
263
343
 
264
- const nonTextToolTotal = toolRows.reduce((sum, row) => {
265
- if ((row.raw_name || row.name) === "text_response") return sum;
266
- return sum + Number(row.totals?.total_tokens || 0);
267
- }, 0);
268
- const displayedMessageTotal = Math.max(
269
- 0,
270
- Number(grand.total_tokens || 0) - Number(grand.reasoning_output_tokens || 0) - nonTextToolTotal,
271
- );
272
344
  const textResponse = toolRows.find((row) => (row.raw_name || row.name) === "text_response");
273
345
  const textResponseTotals = textResponse?.totals || emptyTotals();
274
346
  const textResponseHistoryWeight = Math.max(
275
347
  0,
276
348
  Number(textResponseTotals.cached_input_tokens || 0) + Number(textResponseTotals.cache_creation_input_tokens || 0),
277
349
  );
350
+ const textResponseInputWeight = Math.max(0, Number(textResponseTotals.input_tokens || 0));
351
+ const textResponseOutputWeight = Math.max(
352
+ 0,
353
+ Number(textResponseTotals.output_tokens || 0) - Number(textResponseTotals.reasoning_output_tokens || 0),
354
+ );
355
+ const displayedMessageTotal =
356
+ textResponseInputWeight + textResponseHistoryWeight + textResponseOutputWeight;
278
357
  const messageAlloc = allocateByLargestRemainder(
279
358
  displayedMessageTotal,
280
359
  {
281
- user_input: Math.max(0, Number(textResponseTotals.input_tokens || 0)),
360
+ user_input: textResponseInputWeight,
282
361
  conversation_history: textResponseHistoryWeight,
283
- assistant_response: Math.max(0, Number(textResponseTotals.output_tokens || 0)),
362
+ assistant_response: textResponseOutputWeight,
284
363
  },
285
364
  ["user_input", "conversation_history", "assistant_response"],
286
365
  );
@@ -350,7 +429,7 @@ async function computeCodexContextBreakdown({
350
429
  scope: "supported",
351
430
  breakdown_status: "ok",
352
431
  totals: grand,
353
- session_count: sessions.length,
432
+ session_count: new Set(sessions.map((session) => session.sessionId || session.filePath)).size,
354
433
  message_count: sessions.reduce((a, s) => a + (s.turnCount || 0), 0),
355
434
  message_breakdown: {
356
435
  categories: messageBreakdown,
@@ -387,12 +466,13 @@ async function computeCodexContextBreakdown({
387
466
  },
388
467
  };
389
468
 
390
- CACHE.set(cacheKey, { at: Date.now(), value: result });
469
+ const cacheValue = { ...result };
470
+ CACHE.set(cacheKey, { at: Date.now(), value: cacheValue });
391
471
  while (CACHE.size > 32) {
392
472
  const oldest = [...CACHE.entries()].sort((a, b) => a[1].at - b[1].at)[0];
393
473
  if (oldest) CACHE.delete(oldest[0]);
394
474
  }
395
- return result;
475
+ return includeDiagnostics ? { ...result, diagnostics } : result;
396
476
  }
397
477
 
398
478
  module.exports = {