tokentracker-cli 0.80.0 → 0.80.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.
- package/dashboard/dist/assets/{AchievementBadge-D9jOMhlD.js → AchievementBadge-lZzUk8UJ.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DuswZa4g.js → AchievementsPage-Ddht3Szx.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BJPeE1Uq.js → AchievementsSection-Bz9Enx-j.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-nNPIsy6i.js → BadgeDetailModal-C7QiFDH4.js} +1 -1
- package/dashboard/dist/assets/{Card-VENq8eEp.js → Card-Cs_xyoXz.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-Cq7TW0vv.js → ClawdAnimated-C1Y6W_Di.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-y2VftQuq.js → CommunityStatsModal-B1VvGpUY.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CWgnJ4hm.js → ConfirmModal-Cd-fM8lV.js} +1 -1
- package/dashboard/dist/assets/{Controls-BUQ_iZZq.js → Controls-D0ULfwWh.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-DXbecrQR.js +19 -0
- package/dashboard/dist/assets/{DevicePage-Dh3XDtNx.js → DevicePage-BJsSXmi0.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-KTFU_B3F.js → DialogClose-BkG6LVKN.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DDDcU_07.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-bza2pIek.js → DialogTitle-Do2zDkSF.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-Dm3FVNfr.js → FadeIn-BlxFR4Uo.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CbSElzLt.js → HeaderGithubStar-CsLBMMBv.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BDON7VCU.js → IpCheckPage-Bi1QPsiQ.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Ck3CwZrt.js → LandingPage-Ble4Qxzj.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-GVUy0Ynq.js → LeaderboardAvatar-D2Iqj9VP.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-C19Rkt44.js → LeaderboardPage-DImECEuN.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-QcEq_o6E.js → LeaderboardProfileModal-BGkNzlpU.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-pFhI6292.js → LeaderboardProfilePage-f-E74S02.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-ChV3Qezd.js → LimitsPage-BgTxwSrO.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-ALvpjznF.js → LocalOnlyNotice-D91Lp2zA.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CL-QrJxU.js → LoginPage-hbsmEr6V.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DSuvdGgd.js → PetPage-CIj7VSZN.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-jSDQ87Bi.js → PopoverPopup-DMgjzzjH.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-Dk2IDAJq.js → ResetPasswordPage-Cv1mRgno.js} +1 -1
- package/dashboard/dist/assets/{Select-Cz6WTj7D.js → Select-DvneEQak.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BZyRjWCM.js → SelectItemText-BQlJKuRz.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-CANz8HMM.js → SettingsPage-DpnL6Jm6.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-WZkQG_83.js → SkillsPage-Cz7d619N.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BEyJ62NV.js → WidgetsPage-BP1zxfmh.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-Ck_dOx_w.js → WrappedPage-2eIams41.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-DpjHiAbZ.js → agent-logos-C0_7FQ0s.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-C4iYPBtK.js → arrow-up-right-DBYYRxgR.js} +1 -1
- package/dashboard/dist/assets/check-B1MZEuWp.js +1 -0
- package/dashboard/dist/assets/{copy-CHJ-38K6.js → copy-BxhpNvJX.js} +1 -1
- package/dashboard/dist/assets/{download-BSeG8u17.js → download-DAvxmQ3t.js} +1 -1
- package/dashboard/dist/assets/{flame-DeHwKcE-.js → flame-DX9uRr7X.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-B-MJYJyp.js → format-tokens-mMtru3Ww.js} +1 -1
- package/dashboard/dist/assets/{info-DA_0xr_o.js → info-BLfeChk0.js} +1 -1
- package/dashboard/dist/assets/{link-2-noUH83Lc.js → link-2-JVAqARKl.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-wL7KZtm0.js → loader-circle--bezqxUZ.js} +1 -1
- package/dashboard/dist/assets/{main-BHfJDFcu.js → main-8AbVGr5W.js} +16 -16
- package/dashboard/dist/assets/{provider-display-DxIClOGM.js → provider-display-DUwBv-X6.js} +1 -1
- package/dashboard/dist/assets/{trash-2-B8TPUmxZ.js → trash-2-Z1EIC4PI.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-CXD3MRRE.js → use-community-stats-Dr1q98cg.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-C2pJVYTn.js → use-limits-display-prefs-DnacsQ0f.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-BeOBLEZa.js → use-native-settings-BgBQK2b0.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-kAtW8pJm.js → use-ordered-list-BBs8mEfc.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-CFZUZM3S.js → use-quality-per-dollar-pref-BSagz4Mk.js} +1 -1
- package/dashboard/dist/assets/{use-transform-B9YwpyKi.js → use-transform-CL6RuAA2.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DSX5iKPE.js → use-usage-limits-CzNniz6Z.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CKg5K4Nj.js → useCurrency-ByQkX7uG.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DyyY_Vyt.js → useScrollLock-D5wf8_sp.js} +1 -1
- package/dashboard/dist/assets/useTokenFormat-BpdwKlbn.js +1 -0
- package/dashboard/dist/assets/{zap-B69x9NCT.js → zap-v3s0o4Ig.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 +596 -6
- package/src/commands/sync.js +16 -1
- package/src/lib/codex-config.js +135 -40
- package/src/lib/codex-context-breakdown.js +123 -43
- package/src/lib/codex-rollout-parser.js +329 -32
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +167 -8
- package/dashboard/dist/assets/DashboardPage-WkiygMmq.js +0 -19
- package/dashboard/dist/assets/DialogDescription-DIra288n.js +0 -1
- package/dashboard/dist/assets/check-BkPUxPZV.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-FfTjEH_m.js +0 -1
package/src/lib/codex-config.js
CHANGED
|
@@ -55,12 +55,12 @@ async function restoreNotify({ configPath, notifyOriginalPath, expectedNotify })
|
|
|
55
55
|
const originalNotify = Array.isArray(original?.notify) ? original.notify : null;
|
|
56
56
|
const currentNotify = extractNotify(text);
|
|
57
57
|
|
|
58
|
-
if (expectedNotify &&
|
|
59
|
-
return { restored: false, skippedReason: "
|
|
58
|
+
if (!originalNotify && expectedNotify && currentNotify == null) {
|
|
59
|
+
return { restored: false, skippedReason: "no-backup-not-installed" };
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (
|
|
63
|
-
return { restored: false, skippedReason: "
|
|
62
|
+
if (expectedNotify && !arraysEqual(currentNotify, expectedNotify)) {
|
|
63
|
+
return { restored: false, skippedReason: "current-not-managed" };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const updated = originalNotify ? setNotify(text, originalNotify) : removeNotify(text);
|
|
@@ -161,15 +161,16 @@ function extractNotify(text) {
|
|
|
161
161
|
const lines = text.split(/\r?\n/);
|
|
162
162
|
for (let i = 0; i < lines.length; i++) {
|
|
163
163
|
const line = lines[i];
|
|
164
|
+
if (isTomlTableHeader(line)) break;
|
|
164
165
|
const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
|
|
165
166
|
if (!m) continue;
|
|
166
167
|
|
|
167
168
|
const rhs = (m[1] || "").trim();
|
|
168
169
|
const literal = readTomlArrayLiteral(lines, i, rhs);
|
|
169
|
-
if (!literal)
|
|
170
|
+
if (!literal) return null;
|
|
170
171
|
|
|
171
172
|
const parsed = parseTomlStringArray(literal);
|
|
172
|
-
|
|
173
|
+
return parsed;
|
|
173
174
|
}
|
|
174
175
|
return null;
|
|
175
176
|
}
|
|
@@ -182,6 +183,14 @@ function setNotify(text, notifyCmd) {
|
|
|
182
183
|
let replaced = false;
|
|
183
184
|
for (let i = 0; i < lines.length; i++) {
|
|
184
185
|
const line = lines[i];
|
|
186
|
+
if (isTomlTableHeader(line)) {
|
|
187
|
+
if (!replaced) {
|
|
188
|
+
out.push(notifyLine);
|
|
189
|
+
replaced = true;
|
|
190
|
+
}
|
|
191
|
+
out.push(...lines.slice(i));
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
185
194
|
const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
|
|
186
195
|
if (m) {
|
|
187
196
|
if (!replaced) {
|
|
@@ -190,7 +199,7 @@ function setNotify(text, notifyCmd) {
|
|
|
190
199
|
}
|
|
191
200
|
|
|
192
201
|
const rhs = (m[1] || "").trim();
|
|
193
|
-
i =
|
|
202
|
+
i = findNotifyBlockEnd(lines, i, rhs);
|
|
194
203
|
continue;
|
|
195
204
|
}
|
|
196
205
|
out.push(line);
|
|
@@ -211,10 +220,14 @@ function removeNotify(text) {
|
|
|
211
220
|
const out = [];
|
|
212
221
|
for (let i = 0; i < lines.length; i++) {
|
|
213
222
|
const line = lines[i];
|
|
223
|
+
if (isTomlTableHeader(line)) {
|
|
224
|
+
out.push(...lines.slice(i));
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
214
227
|
const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
|
|
215
228
|
if (m) {
|
|
216
229
|
const rhs = (m[1] || "").trim();
|
|
217
|
-
i =
|
|
230
|
+
i = findNotifyBlockEnd(lines, i, rhs);
|
|
218
231
|
continue;
|
|
219
232
|
}
|
|
220
233
|
out.push(line);
|
|
@@ -225,45 +238,78 @@ function removeNotify(text) {
|
|
|
225
238
|
function parseTomlStringArray(rhs) {
|
|
226
239
|
// Minimal parser for TOML basic/literal string arrays.
|
|
227
240
|
if (!rhs.startsWith("[") || !rhs.endsWith("]")) return null;
|
|
228
|
-
const inner = rhs.slice(1, -1).trim();
|
|
229
|
-
if (!inner) return [];
|
|
230
241
|
|
|
231
242
|
const parts = [];
|
|
232
|
-
let
|
|
233
|
-
let
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (ch
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
current = "";
|
|
243
|
+
let i = 1;
|
|
244
|
+
let expectValue = true;
|
|
245
|
+
|
|
246
|
+
function skipSpaceAndComments() {
|
|
247
|
+
while (i < rhs.length) {
|
|
248
|
+
const ch = rhs[i];
|
|
249
|
+
if (/\s/.test(ch)) {
|
|
250
|
+
i += 1;
|
|
251
|
+
continue;
|
|
242
252
|
}
|
|
243
|
-
|
|
253
|
+
if (ch === "#") {
|
|
254
|
+
while (i < rhs.length && rhs[i] !== "\n") i += 1;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
244
258
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseString() {
|
|
262
|
+
const quote = rhs[i];
|
|
263
|
+
if (quote !== '"' && quote !== "'") return null;
|
|
264
|
+
i += 1;
|
|
265
|
+
let value = "";
|
|
266
|
+
while (i < rhs.length) {
|
|
267
|
+
const ch = rhs[i];
|
|
268
|
+
if (ch === quote) {
|
|
269
|
+
i += 1;
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
if (ch === "\n" || ch === "\r") return null;
|
|
273
|
+
if (quote === '"' && ch === "\\") {
|
|
274
|
+
if (i + 1 >= rhs.length) return null;
|
|
275
|
+
const next = rhs[i + 1];
|
|
276
|
+
if (next === "b") value += "\b";
|
|
277
|
+
else if (next === "t") value += "\t";
|
|
278
|
+
else if (next === "n") value += "\n";
|
|
279
|
+
else if (next === "f") value += "\f";
|
|
280
|
+
else if (next === "r") value += "\r";
|
|
281
|
+
else if (next === '"' || next === "\\" || next === "/") value += next;
|
|
282
|
+
else return null;
|
|
283
|
+
i += 2;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
value += ch;
|
|
287
|
+
i += 1;
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
while (i < rhs.length) {
|
|
293
|
+
skipSpaceAndComments();
|
|
294
|
+
if (rhs[i] === "]") {
|
|
295
|
+
i += 1;
|
|
296
|
+
skipSpaceAndComments();
|
|
297
|
+
return i === rhs.length ? parts : null;
|
|
250
298
|
}
|
|
251
|
-
if (
|
|
252
|
-
if (i
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
else if (next === "t") current += "\t";
|
|
256
|
-
else if (next === "n") current += "\n";
|
|
257
|
-
else if (next === "f") current += "\f";
|
|
258
|
-
else if (next === "r") current += "\r";
|
|
259
|
-
else if (next === '"' || next === "\\" || next === "/") current += next;
|
|
260
|
-
else return null;
|
|
299
|
+
if (!expectValue) {
|
|
300
|
+
if (rhs[i] !== ",") return null;
|
|
301
|
+
i += 1;
|
|
302
|
+
expectValue = true;
|
|
261
303
|
continue;
|
|
262
304
|
}
|
|
263
|
-
|
|
305
|
+
|
|
306
|
+
const value = parseString();
|
|
307
|
+
if (value == null) return null;
|
|
308
|
+
parts.push(value);
|
|
309
|
+
expectValue = false;
|
|
264
310
|
}
|
|
265
311
|
|
|
266
|
-
return
|
|
312
|
+
return null;
|
|
267
313
|
}
|
|
268
314
|
|
|
269
315
|
function formatTomlStringArray(arr) {
|
|
@@ -278,6 +324,7 @@ function readTomlArrayLiteral(lines, startIndex, rhs) {
|
|
|
278
324
|
let quote = null;
|
|
279
325
|
let depth = 0;
|
|
280
326
|
let sawOpen = false;
|
|
327
|
+
let invalid = false;
|
|
281
328
|
|
|
282
329
|
function scanChunk(chunk) {
|
|
283
330
|
for (let i = 0; i < chunk.length; i++) {
|
|
@@ -295,7 +342,10 @@ function readTomlArrayLiteral(lines, startIndex, rhs) {
|
|
|
295
342
|
}
|
|
296
343
|
if (ch === "]") {
|
|
297
344
|
depth -= 1;
|
|
298
|
-
if (sawOpen && depth === 0)
|
|
345
|
+
if (sawOpen && depth === 0) {
|
|
346
|
+
if (!isTomlArrayTrailer(chunk.slice(i + 1))) invalid = true;
|
|
347
|
+
return i;
|
|
348
|
+
}
|
|
299
349
|
}
|
|
300
350
|
continue;
|
|
301
351
|
}
|
|
@@ -305,6 +355,10 @@ function readTomlArrayLiteral(lines, startIndex, rhs) {
|
|
|
305
355
|
continue;
|
|
306
356
|
}
|
|
307
357
|
if (quote === '"' && ch === "\\") {
|
|
358
|
+
if (i + 1 >= chunk.length) {
|
|
359
|
+
invalid = true;
|
|
360
|
+
return -1;
|
|
361
|
+
}
|
|
308
362
|
i += 1;
|
|
309
363
|
}
|
|
310
364
|
}
|
|
@@ -313,11 +367,13 @@ function readTomlArrayLiteral(lines, startIndex, rhs) {
|
|
|
313
367
|
|
|
314
368
|
const parts = [first];
|
|
315
369
|
let endPos = scanChunk(first);
|
|
370
|
+
if (invalid) return null;
|
|
316
371
|
if (endPos !== -1) return first.slice(0, endPos + 1).trim();
|
|
317
372
|
|
|
318
373
|
for (let j = startIndex + 1; j < lines.length; j++) {
|
|
319
374
|
const line = lines[j];
|
|
320
375
|
endPos = scanChunk(line);
|
|
376
|
+
if (invalid) return null;
|
|
321
377
|
if (endPos !== -1) {
|
|
322
378
|
parts.push(line.slice(0, endPos + 1));
|
|
323
379
|
return parts.join("\n").trim();
|
|
@@ -336,6 +392,7 @@ function findTomlArrayBlockEnd(lines, startIndex, rhs) {
|
|
|
336
392
|
let quote = null;
|
|
337
393
|
let depth = 0;
|
|
338
394
|
let sawOpen = false;
|
|
395
|
+
let invalid = false;
|
|
339
396
|
|
|
340
397
|
function scanChunk(chunk) {
|
|
341
398
|
for (let i = 0; i < chunk.length; i++) {
|
|
@@ -353,7 +410,10 @@ function findTomlArrayBlockEnd(lines, startIndex, rhs) {
|
|
|
353
410
|
}
|
|
354
411
|
if (ch === "]") {
|
|
355
412
|
depth -= 1;
|
|
356
|
-
if (sawOpen && depth === 0)
|
|
413
|
+
if (sawOpen && depth === 0) {
|
|
414
|
+
if (!isTomlArrayTrailer(chunk.slice(i + 1))) invalid = true;
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
357
417
|
}
|
|
358
418
|
continue;
|
|
359
419
|
}
|
|
@@ -363,6 +423,10 @@ function findTomlArrayBlockEnd(lines, startIndex, rhs) {
|
|
|
363
423
|
continue;
|
|
364
424
|
}
|
|
365
425
|
if (quote === '"' && ch === "\\") {
|
|
426
|
+
if (i + 1 >= chunk.length) {
|
|
427
|
+
invalid = true;
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
366
430
|
i += 1;
|
|
367
431
|
}
|
|
368
432
|
}
|
|
@@ -370,12 +434,43 @@ function findTomlArrayBlockEnd(lines, startIndex, rhs) {
|
|
|
370
434
|
}
|
|
371
435
|
|
|
372
436
|
if (scanChunk(first)) return startIndex;
|
|
437
|
+
if (invalid) return startIndex;
|
|
373
438
|
for (let j = startIndex + 1; j < lines.length; j++) {
|
|
374
439
|
if (scanChunk(lines[j])) return j;
|
|
440
|
+
if (invalid) return startIndex;
|
|
375
441
|
}
|
|
376
442
|
return startIndex;
|
|
377
443
|
}
|
|
378
444
|
|
|
445
|
+
function findNotifyBlockEnd(lines, startIndex, rhs) {
|
|
446
|
+
const endIndex = findTomlArrayBlockEnd(lines, startIndex, rhs);
|
|
447
|
+
const literal = readTomlArrayLiteral(lines, startIndex, rhs);
|
|
448
|
+
if (
|
|
449
|
+
endIndex !== startIndex ||
|
|
450
|
+
!rhs.trim().startsWith("[") ||
|
|
451
|
+
(literal && parseTomlStringArray(literal))
|
|
452
|
+
) {
|
|
453
|
+
return endIndex;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
for (let j = startIndex + 1; j < lines.length; j++) {
|
|
457
|
+
if (isTopLevelTomlBoundary(lines[j])) return j - 1;
|
|
458
|
+
}
|
|
459
|
+
return lines.length - 1;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function isTomlTableHeader(line) {
|
|
463
|
+
return /^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/.test(line);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function isTopLevelTomlBoundary(line) {
|
|
467
|
+
return /^\s*(?:\[\[?[^\]]+\]\]?|(?:[A-Za-z0-9_-]+|"[^"]+"|'[^']+')(?:\s*\.\s*(?:[A-Za-z0-9_-]+|"[^"]+"|'[^']+'))*\s*=)/.test(line);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function isTomlArrayTrailer(text) {
|
|
471
|
+
return /^\s*(?:#.*)?$/.test(text);
|
|
472
|
+
}
|
|
473
|
+
|
|
379
474
|
function arraysEqual(a, b) {
|
|
380
475
|
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
381
476
|
if (a.length !== b.length) return false;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
"use strict";
|
|
12
12
|
|
|
13
|
-
const
|
|
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
|
-
|
|
29
|
-
|
|
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-
|
|
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
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
from: fromKey,
|
|
189
|
-
|
|
190
|
-
|
|
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:
|
|
360
|
+
user_input: textResponseInputWeight,
|
|
282
361
|
conversation_history: textResponseHistoryWeight,
|
|
283
|
-
assistant_response:
|
|
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.
|
|
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
|
-
|
|
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 = {
|