tokentracker-cli 0.99.0 → 1.0.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 (108) hide show
  1. package/README.de.md +6 -5
  2. package/README.ja.md +6 -5
  3. package/README.ko.md +6 -5
  4. package/README.md +7 -5
  5. package/README.zh-CN.md +6 -5
  6. package/dashboard/dist/assets/{AchievementBadge-kRsjlB3p.js → AchievementBadge-pib0F8p8.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-C3xybY0k.js → AchievementsPage-BMnub6CL.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-y5mok2w0.js → AchievementsSection-DG_-yxAt.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-IFLXYuKp.js → BadgeDetailModal-Sk2dHIkJ.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-D6x0PDt2.js → BotAnimated-VwwvOniA.js} +1 -1
  11. package/dashboard/dist/assets/{Card-CiKwEyVu.js → Card-COfjRX96.js} +1 -1
  12. package/dashboard/dist/assets/{ClawdAnimated-BQFcAIOy.js → ClawdAnimated-C3sF5c29.js} +2 -2
  13. package/dashboard/dist/assets/{CommandPalette-BvpX8PM_.js → CommandPalette-DvJFPsR2.js} +1 -1
  14. package/dashboard/dist/assets/{CommunityStatsModal-D5rNlwfO.js → CommunityStatsModal-DfYHIeKr.js} +1 -1
  15. package/dashboard/dist/assets/{ConfirmModal-CR0Evu8-.js → ConfirmModal-DtoHm0RD.js} +1 -1
  16. package/dashboard/dist/assets/{Controls-lyLUkbJW.js → Controls-DHHn10dy.js} +1 -1
  17. package/dashboard/dist/assets/{DashboardPage-X8CLr_au.js → DashboardPage-DJ_nDPjA.js} +1 -1
  18. package/dashboard/dist/assets/DevicePage-DlXWUpau.js +1 -0
  19. package/dashboard/dist/assets/{DialogClose-Bplj_718.js → DialogClose-BLegTe7F.js} +1 -1
  20. package/dashboard/dist/assets/DialogDescription-Dl_wqXjc.js +1 -0
  21. package/dashboard/dist/assets/{DialogTitle-Ckzf1tdi.js → DialogTitle-BZb-SEg0.js} +1 -1
  22. package/dashboard/dist/assets/{FadeIn-C2Jqe95f.js → FadeIn-Dsk0V4DK.js} +1 -1
  23. package/dashboard/dist/assets/{HeaderGithubStar-DgE7Cd5u.js → HeaderGithubStar-B20nLMWQ.js} +1 -1
  24. package/dashboard/dist/assets/{HoverTooltip-CEmo0N-P.js → HoverTooltip-tGFmzZiJ.js} +1 -1
  25. package/dashboard/dist/assets/{IpCheckPage-BwkpsLaF.js → IpCheckPage-BK7XI_aT.js} +1 -1
  26. package/dashboard/dist/assets/{LandingPage-CA2klwbE.js → LandingPage-BYX6NXbl.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardAvatar-B_PUenGZ.js → LeaderboardAvatar-CIDqKSNn.js} +1 -1
  28. package/dashboard/dist/assets/{LeaderboardPage-D6HGW3jX.js → LeaderboardPage-BxIpRUwn.js} +2 -2
  29. package/dashboard/dist/assets/{LeaderboardProfileModal-Bfo1dSsS.js → LeaderboardProfileModal-DJlNN9YG.js} +3 -3
  30. package/dashboard/dist/assets/{LeaderboardProfilePage-2OqwrqOs.js → LeaderboardProfilePage-D4HSgrjG.js} +1 -1
  31. package/dashboard/dist/assets/{LimitsPage-BHrkLXHZ.js → LimitsPage-BKTMBbKS.js} +1 -1
  32. package/dashboard/dist/assets/{LocalOnlyNotice-C45MmWYt.js → LocalOnlyNotice-DpZiPX4A.js} +1 -1
  33. package/dashboard/dist/assets/{LoginCard-BikBibax.js → LoginCard-Cd03cg54.js} +1 -1
  34. package/dashboard/dist/assets/{LoginModal-D0Aq85Zg.js → LoginModal-yPZi5JsE.js} +1 -1
  35. package/dashboard/dist/assets/{LoginPage-B-kQaDQ_.js → LoginPage-BTL9oeVV.js} +1 -1
  36. package/dashboard/dist/assets/{PetPage-ZgPREiwD.js → PetPage-BWMSKd_3.js} +1 -1
  37. package/dashboard/dist/assets/{PopoverPopup-eQgg3h1i.js → PopoverPopup-Dba9PYte.js} +1 -1
  38. package/dashboard/dist/assets/{ProviderIcon-nDBPxn0N.js → ProviderIcon-DXI7Ztov.js} +1 -1
  39. package/dashboard/dist/assets/{ResetPasswordPage-CjsOtiGr.js → ResetPasswordPage-wKDU07tl.js} +1 -1
  40. package/dashboard/dist/assets/{SegmentedControl-6NcttkEH.js → SegmentedControl-DZh5rJHn.js} +1 -1
  41. package/dashboard/dist/assets/{Select-CzEhPICe.js → Select-CiJIsDf1.js} +1 -1
  42. package/dashboard/dist/assets/{SelectItemText-BwvzfXwj.js → SelectItemText-B8YkLJtE.js} +1 -1
  43. package/dashboard/dist/assets/{ServiceStatusPage-C35wpxrT.js → ServiceStatusPage-CF_bMZMz.js} +1 -1
  44. package/dashboard/dist/assets/{SessionsPage-a30adkhf.js → SessionsPage-B0PnK1TY.js} +1 -1
  45. package/dashboard/dist/assets/{SettingsPage-C_7ZPBUF.js → SettingsPage-onYZIk-h.js} +2 -2
  46. package/dashboard/dist/assets/{SkillsPage-DsxkFnrV.js → SkillsPage-CSyoblke.js} +1 -1
  47. package/dashboard/dist/assets/{TokenGalaxy-D_bXRotA.js → TokenGalaxy-kK6wMGuy.js} +1 -1
  48. package/dashboard/dist/assets/{ToolbarRootContext-CL7ma7ba.js → ToolbarRootContext-CnffyHPW.js} +1 -1
  49. package/dashboard/dist/assets/{TrendMonitor-QZBF2IEY.js → TrendMonitor-CpxDm5q_.js} +1 -1
  50. package/dashboard/dist/assets/{WidgetsPage-CWlxR0H-.js → WidgetsPage-vBedlR_d.js} +1 -1
  51. package/dashboard/dist/assets/{WrappedPage-D8XQ72nF.js → WrappedPage-D25YzXGY.js} +1 -1
  52. package/dashboard/dist/assets/{__vite-browser-external-CTGqjrCe.js → __vite-browser-external-OxXsgcds.js} +3 -3
  53. package/dashboard/dist/assets/{agent-logos-BscA25fS.js → agent-logos-frktji23.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-up-right-DTxCEGbR.js → arrow-up-right-BtBcf39e.js} +1 -1
  55. package/dashboard/dist/assets/{check-CCEoh2wj.js → check-PVwhLtMf.js} +1 -1
  56. package/dashboard/dist/assets/{composite-B2s880f5.js → composite-BxlyIOoF.js} +1 -1
  57. package/dashboard/dist/assets/{copy-CJVQglwG.js → copy-s5UqKoXC.js} +1 -1
  58. package/dashboard/dist/assets/{download-Cn-OpYm6.js → download-Ns8TNfey.js} +1 -1
  59. package/dashboard/dist/assets/{external-link-BnYlUn56.js → external-link-zpvfw1sA.js} +1 -1
  60. package/dashboard/dist/assets/{flame-D4yFLV1L.js → flame-DhPzeD5Z.js} +1 -1
  61. package/dashboard/dist/assets/{format-tokens-CBUNbBvp.js → format-tokens-C4eAHkPw.js} +1 -1
  62. package/dashboard/dist/assets/{icons-CcgNgAvb.js → icons-CNeKwVFm.js} +1 -1
  63. package/dashboard/dist/assets/{index-CTjeDkbu.js → index-9qLVgAvh.js} +1 -1
  64. package/dashboard/dist/assets/{index-CkELwUyg.js → index-B5UHZH4-.js} +1 -1
  65. package/dashboard/dist/assets/{index-ZWeEKbo-.js → index-D8ciCYQM.js} +1 -1
  66. package/dashboard/dist/assets/{info-DoehRSQU.js → info-p2pKHzPt.js} +1 -1
  67. package/dashboard/dist/assets/{link-2-D8xSsPqy.js → link-2-BQhZyxUQ.js} +1 -1
  68. package/dashboard/dist/assets/{loader-circle-DMu0-X5r.js → loader-circle-BnFE5J64.js} +1 -1
  69. package/dashboard/dist/assets/{maximize-2-DLyYsZvE.js → maximize-2-Zt1L2xuV.js} +1 -1
  70. package/dashboard/dist/assets/{modifiers.esm-Dm5Xy2jA.js → modifiers.esm-Sbhv1CrS.js} +1 -1
  71. package/dashboard/dist/assets/{monitor-smartphone-5uZGQNGJ.js → monitor-smartphone-Pmy9ktPf.js} +1 -1
  72. package/dashboard/dist/assets/{plus-8fnU0dkt.js → plus-FPP4P4tW.js} +1 -1
  73. package/dashboard/dist/assets/{provider-display-B3ed-pSc.js → provider-display-zpzUqFZ8.js} +2 -2
  74. package/dashboard/dist/assets/{react-Gft01KYk.js → react-C_5Bvle-.js} +1 -1
  75. package/dashboard/dist/assets/{search-jqzJg2kG.js → search-7f0zYUoC.js} +1 -1
  76. package/dashboard/dist/assets/{skills-api-Bwn_xcHV.js → skills-api-CdEcHyY0.js} +1 -1
  77. package/dashboard/dist/assets/{sortable.esm-CYjzLCp7.js → sortable.esm-DwOWVPK8.js} +1 -1
  78. package/dashboard/dist/assets/{store-CgJHe9jU.js → store-D3n2aJ7w.js} +1 -1
  79. package/dashboard/dist/assets/{terminal-CZ5j8yEQ.js → terminal-C6zDcArO.js} +1 -1
  80. package/dashboard/dist/assets/{trash-DtMLjI_S.js → trash-C_bcv-F0.js} +1 -1
  81. package/dashboard/dist/assets/{use-community-stats-Px3dHGlR.js → use-community-stats-fCkrn5Kd.js} +1 -1
  82. package/dashboard/dist/assets/{use-latest-request-guard-B7Vw4Wsd.js → use-latest-request-guard-DD2Nt1qS.js} +1 -1
  83. package/dashboard/dist/assets/{use-limits-display-prefs-DWWrcMPi.js → use-limits-display-prefs-a9HtQcOE.js} +1 -1
  84. package/dashboard/dist/assets/{use-ordered-list-CevbB79U.js → use-ordered-list-B6-Nb6rL.js} +1 -1
  85. package/dashboard/dist/assets/use-reduced-motion-DyXrwS-h.js +1 -0
  86. package/dashboard/dist/assets/{use-session-efficiency-pref-DoIaI0vm.js → use-session-efficiency-pref-gD9i2h4k.js} +1 -1
  87. package/dashboard/dist/assets/{use-transform-qRwUetOk.js → use-transform-DP6mCHUn.js} +1 -1
  88. package/dashboard/dist/assets/{use-usage-limits-DlP7yg78.js → use-usage-limits-TdwK-gf2.js} +1 -1
  89. package/dashboard/dist/assets/{useCurrency-DRUe8YoY.js → useCurrency-Bbxwhe-A.js} +1 -1
  90. package/dashboard/dist/assets/{useTokenFormat-CmLbznUC.js → useTokenFormat-CzeTrne_.js} +1 -1
  91. package/dashboard/dist/assets/{zap-CS80c_uN.js → zap-CucEQWyU.js} +1 -1
  92. package/dashboard/dist/index.html +13 -11
  93. package/dashboard/dist/ip-check.html +1 -1
  94. package/dashboard/dist/leaderboard.html +1 -1
  95. package/dashboard/dist/llms.txt +3 -2
  96. package/dashboard/dist/share.html +1 -1
  97. package/package.json +2 -2
  98. package/src/commands/init.js +10 -0
  99. package/src/commands/status.js +16 -0
  100. package/src/commands/sync.js +43 -1
  101. package/src/lib/codex-rollout-parser.js +86 -8
  102. package/src/lib/codex-usage-record.js +195 -0
  103. package/src/lib/pricing/seed-snapshot.json +1 -1
  104. package/src/lib/rollout.js +261 -11
  105. package/src/lib/skills-manager.js +2 -2
  106. package/dashboard/dist/assets/DevicePage-RCIANm45.js +0 -1
  107. package/dashboard/dist/assets/DialogDescription-C2EzE7DU.js +0 -1
  108. package/dashboard/dist/assets/use-reduced-motion-CCuEY-79.js +0 -1
@@ -26,6 +26,20 @@ const {
26
26
  readCodexServiceTier,
27
27
  isPriorityServiceTier,
28
28
  } = require("./codex-service-tier");
29
+ const {
30
+ awaitsCompactedLine,
31
+ createCompactionResponseIds,
32
+ createUsageRecordState,
33
+ extractTokenUsageRecord,
34
+ isCodexTurnEndEvent,
35
+ isRecordOnly,
36
+ noteLineAfterUsageRecord,
37
+ noteTokenCount,
38
+ noteTurnEnd,
39
+ noteUsageRecord,
40
+ snapshotUsageRecordState,
41
+ takeCompactionOnTokenCount,
42
+ } = require("./codex-usage-record");
29
43
  const {
30
44
  DEVIN_TABLE_PROBE_SQL,
31
45
  devinUsageSql,
@@ -258,6 +272,10 @@ async function parseRolloutIncremental({
258
272
  if (!cursors.files || typeof cursors.files !== "object") {
259
273
  cursors.files = {};
260
274
  }
275
+ // Counted Codex compaction calls by response_id (issue #652). Core cursor
276
+ // state, so a fork replay or a copied rollout under any path or day shard
277
+ // sees every compaction counted before.
278
+ const compactionResponseIds = createCompactionResponseIds(cursors);
261
279
 
262
280
  // Persisted set of seen Codex event keys (sessionUUID:eventTimestamp). Mirrors
263
281
  // the claudeHashes pattern: it makes an inode-changing re-scan idempotent so an
@@ -480,6 +498,12 @@ async function parseRolloutIncremental({
480
498
  const modelAttributionState = sameInode && !truncated
481
499
  ? prev.modelAttributionState || null
482
500
  : null;
501
+ // Only valid for the bytes before the resume offset; a rescan from 0
502
+ // rebuilds it. A cursor from before #652 with a lastTotal has seen a
503
+ // token_count already.
504
+ const usageRecordState = startOffset > 0
505
+ ? prev?.codexUsageRecord || (prev?.lastTotal ? { sawTokenCount: true } : null)
506
+ : null;
483
507
 
484
508
  const codexProjectFastPath =
485
509
  projectEnabled && (fileSource === DEFAULT_SOURCE || fileSource === "acode");
@@ -575,6 +599,8 @@ async function parseRolloutIncremental({
575
599
  lastModel,
576
600
  lastServiceTier,
577
601
  modelAttributionState,
602
+ usageRecordState,
603
+ compactionResponseIds,
578
604
  hourlyState,
579
605
  touchedBuckets,
580
606
  source: fileSource,
@@ -601,6 +627,20 @@ async function parseRolloutIncremental({
601
627
  modelAttributionState: result.modelAttributionState,
602
628
  updatedAt: new Date().toISOString(),
603
629
  };
630
+ const nextUsageRecordState = projectContextOnlyScan
631
+ ? usageRecordState
632
+ : result.usageRecordState;
633
+ if (nextUsageRecordState) nextCursor.codexUsageRecord = nextUsageRecordState;
634
+ // Files whose usage only exists as token_usage_record rows are not
635
+ // counted (issue #652); keep them in core cursor state so sync and status
636
+ // can warn without loading per-file cursor shards.
637
+ if (fileSource === DEFAULT_SOURCE) {
638
+ if (isRecordOnly(nextUsageRecordState)) {
639
+ (cursors.codexUsageRecordOnlyFiles ||= {})[key] = true;
640
+ } else if (cursors.codexUsageRecordOnlyFiles?.[key]) {
641
+ delete cursors.codexUsageRecordOnlyFiles[key];
642
+ }
643
+ }
604
644
  if (codexProjectFastPath) {
605
645
  nextCursor.projectOffset = result.endOffset;
606
646
  nextCursor.projectFileContext = buildProjectFileContext(
@@ -643,6 +683,12 @@ async function parseRolloutIncremental({
643
683
  projectState.updatedAt = new Date().toISOString();
644
684
  cursors.projectHourly = projectState;
645
685
  }
686
+ compactionResponseIds.persist();
687
+ // A deleted rollout no longer needs a warning. The map is empty unless a
688
+ // record-only writer exists, so this is normally free.
689
+ for (const flaggedPath of Object.keys(cursors.codexUsageRecordOnlyFiles || {})) {
690
+ if (!fssync.existsSync(flaggedPath)) delete cursors.codexUsageRecordOnlyFiles[flaggedPath];
691
+ }
646
692
 
647
693
  return { filesProcessed, eventsAggregated, bucketsQueued, projectBucketsQueued };
648
694
  }
@@ -2071,6 +2117,8 @@ async function parseRolloutFile({
2071
2117
  lastModel,
2072
2118
  lastServiceTier,
2073
2119
  modelAttributionState: previousModelAttributionState,
2120
+ usageRecordState: previousUsageRecordState = null,
2121
+ compactionResponseIds = null,
2074
2122
  hourlyState,
2075
2123
  touchedBuckets,
2076
2124
  source,
@@ -2098,6 +2146,7 @@ async function parseRolloutFile({
2098
2146
  lastModel,
2099
2147
  lastServiceTier: typeof lastServiceTier === "string" ? lastServiceTier : null,
2100
2148
  modelAttributionState: previousModelAttributionState,
2149
+ usageRecordState: previousUsageRecordState,
2101
2150
  eventsAggregated: 0,
2102
2151
  projectFileContexts,
2103
2152
  };
@@ -2142,6 +2191,54 @@ async function parseRolloutFile({
2142
2191
  let eventsAggregated = 0;
2143
2192
  let scannedEndOffset = startOffset;
2144
2193
  let committedEndOffset = startOffset;
2194
+ // Compaction records (issue #652, rules in codex-usage-record.js). Codex only.
2195
+ const usageRecordState = source === DEFAULT_SOURCE && compactionResponseIds
2196
+ ? createUsageRecordState(previousUsageRecordState)
2197
+ : null;
2198
+
2199
+ // What a record adds if it turns out to be a compaction call: model, tier
2200
+ // and project are those in effect when the record was written. A record
2201
+ // without a response_id cannot be deduplicated and is never counted.
2202
+ function buildCompactionEvent(record) {
2203
+ if (!record.responseId) return null;
2204
+ const delta = normalizeUsage(record.usage);
2205
+ if (isAllZeroUsage(delta)) return null;
2206
+ delta.conversation_count = 1;
2207
+ const bucketStart = toUtcHalfHourStart(record.timestamp);
2208
+ if (!bucketStart) return null;
2209
+ return {
2210
+ responseId: record.responseId,
2211
+ bucketStart,
2212
+ model,
2213
+ serviceTier,
2214
+ projectKey: currentProjectKey,
2215
+ projectRef: currentProjectRef,
2216
+ delta,
2217
+ };
2218
+ }
2219
+
2220
+ // Counted once per response_id across every rollout: a fork replays its
2221
+ // parent's records and a copied rollout repeats them, both with the same id.
2222
+ function addCompactionEvent(event) {
2223
+ if (compactionResponseIds.has(event.responseId)) return;
2224
+ compactionResponseIds.add(event.responseId);
2225
+ const bucket = getHourlyBucket(hourlyState, source, event.model, event.bucketStart);
2226
+ addTotals(bucket.totals, event.delta);
2227
+ if (isPriorityServiceTier(event.serviceTier)) addPriorityUsage(bucket.totals, event.delta);
2228
+ touchedBuckets.add(bucketKey(source, event.model, event.bucketStart));
2229
+ if (event.projectKey && projectState && projectTouchedBuckets) {
2230
+ const projectBucket = getProjectBucket(
2231
+ projectState,
2232
+ event.projectKey,
2233
+ source,
2234
+ event.bucketStart,
2235
+ event.projectRef,
2236
+ );
2237
+ addTotals(projectBucket.totals, event.delta);
2238
+ projectTouchedBuckets.add(projectBucketKey(event.projectKey, source, event.bucketStart));
2239
+ }
2240
+ eventsAggregated += 1;
2241
+ }
2145
2242
 
2146
2243
  const invalidUtf8 = invalidRecordPolicy === "throw" ? "throw" : "record";
2147
2244
  for await (const record of physicalJsonlRecords(stream, { invalidUtf8 })) {
@@ -2154,6 +2251,26 @@ async function parseRolloutFile({
2154
2251
 
2155
2252
  const { line } = record;
2156
2253
  if (!line) continue;
2254
+ // Only a `compacted` line directly after a record keeps it as a
2255
+ // compaction candidate. A line that is not complete yet decides nothing:
2256
+ // the read stops before it and the next sync sees it whole.
2257
+ if (awaitsCompactedLine(usageRecordState)) {
2258
+ let next;
2259
+ if (!record.terminated || line.includes('"compacted"')) {
2260
+ try {
2261
+ next = JSON.parse(line);
2262
+ } catch {
2263
+ next = undefined;
2264
+ }
2265
+ }
2266
+ if (next !== undefined || record.terminated) {
2267
+ noteLineAfterUsageRecord(usageRecordState, next?.type === "compacted");
2268
+ }
2269
+ if (next?.type === "compacted") {
2270
+ committedEndOffset = scannedEndOffset;
2271
+ continue;
2272
+ }
2273
+ }
2157
2274
  const maybeTokenCount = line.includes('"token_count"');
2158
2275
  const maybeModelReroute =
2159
2276
  !maybeTokenCount &&
@@ -2168,7 +2285,19 @@ async function parseRolloutFile({
2168
2285
  const maybeServiceTier =
2169
2286
  !maybeTokenCount && !maybeModelReroute && !maybeTurnContext &&
2170
2287
  line.includes(CODEX_SERVICE_TIER_MARKER);
2171
- if (!maybeTokenCount && !maybeTurnContext && !maybeModelReroute && !maybeServiceTier) {
2288
+ const maybeUsageRecord =
2289
+ !maybeTokenCount &&
2290
+ Boolean(usageRecordState) &&
2291
+ (line.includes('"token_usage_record"') ||
2292
+ (usageRecordState.unmatchedRecord &&
2293
+ (line.includes('"task_complete"') || line.includes('"turn_aborted"'))));
2294
+ if (
2295
+ !maybeTokenCount &&
2296
+ !maybeTurnContext &&
2297
+ !maybeModelReroute &&
2298
+ !maybeServiceTier &&
2299
+ !maybeUsageRecord
2300
+ ) {
2172
2301
  if (invalidRecordPolicy === "throw" || !record.terminated) {
2173
2302
  try {
2174
2303
  JSON.parse(line);
@@ -2230,11 +2359,32 @@ async function parseRolloutFile({
2230
2359
  continue;
2231
2360
  }
2232
2361
 
2362
+ if (usageRecordState) {
2363
+ const usageRecord = extractTokenUsageRecord(obj);
2364
+ if (usageRecord) {
2365
+ noteUsageRecord(usageRecordState, buildCompactionEvent(usageRecord));
2366
+ continue;
2367
+ }
2368
+ if (isCodexTurnEndEvent(obj)) {
2369
+ noteTurnEnd(usageRecordState);
2370
+ continue;
2371
+ }
2372
+ }
2373
+
2233
2374
  const token = extractTokenCount(obj);
2234
2375
  if (!token) continue;
2235
2376
 
2236
2377
  const info = token.info;
2237
2378
  if (!info || typeof info !== "object") continue;
2379
+ if (usageRecordState) {
2380
+ noteTokenCount(usageRecordState);
2381
+ const compaction = takeCompactionOnTokenCount(
2382
+ usageRecordState,
2383
+ usageDeltaState.lastTotal,
2384
+ info.total_token_usage,
2385
+ );
2386
+ if (compaction) addCompactionEvent(compaction);
2387
+ }
2238
2388
 
2239
2389
  const tokenTimestamp = typeof token.timestamp === "string" ? token.timestamp : null;
2240
2390
  if (!tokenTimestamp) continue;
@@ -2352,6 +2502,9 @@ async function parseRolloutFile({
2352
2502
  lastModel: model,
2353
2503
  lastServiceTier: serviceTier,
2354
2504
  modelAttributionState: snapshotCodexModelAttributionState(modelAttributionState),
2505
+ usageRecordState: usageRecordState
2506
+ ? snapshotUsageRecordState(usageRecordState)
2507
+ : previousUsageRecordState,
2355
2508
  eventsAggregated,
2356
2509
  projectFileContexts,
2357
2510
  };
@@ -3465,7 +3618,7 @@ function deriveOpencodeMessageFingerprint({ msg, totals, source }) {
3465
3618
  totals.reasoning_output_tokens,
3466
3619
  model,
3467
3620
  provider,
3468
- ].join("");
3621
+ ].join("\u0000");
3469
3622
  // Hashed rather than stored raw: the fingerprint is persisted per message in
3470
3623
  // cursors.json, and heavy OpenCode users carry tens of thousands of entries.
3471
3624
  return crypto.createHash("sha256").update(raw).digest("base64url").slice(0, 22);
@@ -15313,6 +15466,17 @@ function primeAgentSourceForProvider(provider) {
15313
15466
  return slug ? `prime-agent-${slug}` : "prime-agent";
15314
15467
  }
15315
15468
 
15469
+ // Maps one parsed JSONL line to { id, message, timestamp } or null. pi-family
15470
+ // files wrap each record as { type: "message", id, timestamp, message }.
15471
+ function readPiMessageEntry(entry) {
15472
+ if (!entry || entry.type !== "message") return null;
15473
+ return {
15474
+ id: typeof entry.id === "string" && entry.id ? entry.id : null,
15475
+ message: entry.message,
15476
+ timestamp: entry.timestamp,
15477
+ };
15478
+ }
15479
+
15316
15480
  async function parsePiLikeIncremental({
15317
15481
  sessionFiles,
15318
15482
  cursors,
@@ -15326,6 +15490,7 @@ async function parsePiLikeIncremental({
15326
15490
  resolveSessionFiles,
15327
15491
  resolveDefaultModel,
15328
15492
  sourceForProvider,
15493
+ readEntry = readPiMessageEntry,
15329
15494
  } = {}) {
15330
15495
  await ensureDir(path.dirname(queuePath));
15331
15496
  const projectEnabled = typeof projectQueuePath === "string" && projectQueuePath.length > 0;
@@ -15427,15 +15592,16 @@ async function parsePiLikeIncremental({
15427
15592
  let entry;
15428
15593
  try { entry = JSON.parse(line); } catch { continue; }
15429
15594
 
15430
- if (!entry || entry.type !== "message") continue;
15595
+ const record = readEntry(entry);
15596
+ if (!record) continue;
15431
15597
 
15432
- const msg = entry.message;
15598
+ const msg = record.message;
15433
15599
  if (!msg || msg.role !== "assistant") continue;
15434
15600
 
15435
15601
  const usage = msg.usage;
15436
15602
  if (!usage || typeof usage !== "object") continue;
15437
15603
 
15438
- const entryId = typeof entry.id === "string" && entry.id ? entry.id : null;
15604
+ const entryId = record.id;
15439
15605
  if (!entryId) continue;
15440
15606
  if (seenIds.has(entryId)) continue;
15441
15607
 
@@ -15461,8 +15627,8 @@ async function parsePiLikeIncremental({
15461
15627
  let tsMs = null;
15462
15628
  if (Number.isFinite(Number(msg.timestamp)) && Number(msg.timestamp) > 0) {
15463
15629
  tsMs = Number(msg.timestamp);
15464
- } else if (typeof entry.timestamp === "string" && entry.timestamp) {
15465
- const parsed = Date.parse(entry.timestamp);
15630
+ } else if (typeof record.timestamp === "string" && record.timestamp) {
15631
+ const parsed = Date.parse(record.timestamp);
15466
15632
  if (Number.isFinite(parsed) && parsed > 0) tsMs = parsed;
15467
15633
  }
15468
15634
  if (tsMs == null) {
@@ -15573,11 +15739,12 @@ async function parsePiLikeIncremental({
15573
15739
  if (!line || !line.trim()) continue;
15574
15740
  let entry;
15575
15741
  try { entry = JSON.parse(line); } catch { continue; }
15576
- const msg = entry?.type === "message" ? entry.message : null;
15742
+ const record = readEntry(entry);
15743
+ const msg = record ? record.message : null;
15577
15744
  const usage = msg?.role === "assistant" ? msg.usage : null;
15578
15745
  if (!usage || typeof usage !== "object") continue;
15579
15746
 
15580
- const entryId = typeof entry.id === "string" && entry.id ? entry.id : null;
15747
+ const entryId = record.id;
15581
15748
  if (!entryId || projectSeenIds.has(entryId)) continue;
15582
15749
 
15583
15750
  const input = toNonNegativeInt(usage.input);
@@ -15599,8 +15766,8 @@ async function parsePiLikeIncremental({
15599
15766
  let tsMs = null;
15600
15767
  if (Number.isFinite(Number(msg.timestamp)) && Number(msg.timestamp) > 0) {
15601
15768
  tsMs = Number(msg.timestamp);
15602
- } else if (typeof entry.timestamp === "string" && entry.timestamp) {
15603
- const parsed = Date.parse(entry.timestamp);
15769
+ } else if (typeof record.timestamp === "string" && record.timestamp) {
15770
+ const parsed = Date.parse(record.timestamp);
15604
15771
  if (Number.isFinite(parsed) && parsed > 0) tsMs = parsed;
15605
15772
  }
15606
15773
  const bucketStart = tsMs == null
@@ -15700,6 +15867,85 @@ async function parsePrimeAgentIncremental(options = {}) {
15700
15867
  });
15701
15868
  }
15702
15869
 
15870
+ // ─────────────────────────────────────────────────────────────────────────────
15871
+ // MiniMax Code (MiniMax's desktop coding agent) — passive JSONL reader
15872
+ // (~/.minimax/v2/sessions/YYYY/MM/DD/<HH-MM-SS-mmm>-session_<id>/messages.jsonl)
15873
+ //
15874
+ // Records carry pi-shaped usage (input excludes cacheRead) and a ms-epoch
15875
+ // message.timestamp, but have no type:"message" wrapper and dedupe on the
15876
+ // top-level message_id. MiniMax Code routes to many upstream models and records
15877
+ // the model per message. Its usage.cost block is always 0, so it is ignored in
15878
+ // favor of normal pricing. Migrated legacy sessions replay as model
15879
+ // "historical-transcript" with all-zero usage and are dropped by the engine's
15880
+ // zero-token guard. There is no cwd header, so no project attribution.
15881
+ // ─────────────────────────────────────────────────────────────────────────────
15882
+
15883
+ const MINIMAX_CODE_SOURCE = "minimax-code";
15884
+ const MINIMAX_CODE_SESSION_FILE = "messages.jsonl";
15885
+ // sessions / YYYY / MM / DD / <session dir> / messages.jsonl
15886
+ const MINIMAX_CODE_MAX_SESSION_DEPTH = 5;
15887
+
15888
+ function resolveMinimaxCodeHome(env = process.env) {
15889
+ if (env.TOKENTRACKER_MINIMAX_HOME) return expandHomePath(env.TOKENTRACKER_MINIMAX_HOME, env);
15890
+ const home = env.HOME || require("node:os").homedir();
15891
+ if (process.platform === "win32") {
15892
+ return pickWin32ProviderPath({
15893
+ env,
15894
+ nativeValue: path.join(home, ".minimax"),
15895
+ wslProviderDir: ".minimax",
15896
+ });
15897
+ }
15898
+ return path.join(home, ".minimax");
15899
+ }
15900
+
15901
+ function resolveMinimaxCodeSessionsDir(env = process.env) {
15902
+ const minimaxHome = resolveMinimaxCodeHome(env);
15903
+ return minimaxHome ? path.join(minimaxHome, "v2", "sessions") : null;
15904
+ }
15905
+
15906
+ function resolveMinimaxCodeSessionFiles(env = process.env) {
15907
+ const sessionsDir = resolveMinimaxCodeSessionsDir(env);
15908
+ if (!sessionsDir) return [];
15909
+ const files = [];
15910
+ const walk = (dir, depth) => {
15911
+ let entries;
15912
+ try { entries = fssync.readdirSync(dir, { withFileTypes: true }); } catch { return; }
15913
+ for (const entry of entries) {
15914
+ const fullPath = path.join(dir, entry.name);
15915
+ if (entry.isDirectory()) {
15916
+ if (depth < MINIMAX_CODE_MAX_SESSION_DEPTH) walk(fullPath, depth + 1);
15917
+ } else if (entry.isFile() && entry.name === MINIMAX_CODE_SESSION_FILE && depth > 1) {
15918
+ files.push(fullPath);
15919
+ }
15920
+ }
15921
+ };
15922
+ walk(sessionsDir, 1);
15923
+ files.sort((a, b) => a.localeCompare(b));
15924
+ return files;
15925
+ }
15926
+
15927
+ function readMinimaxCodeEntry(entry) {
15928
+ if (!entry || typeof entry !== "object" || !entry.message) return null;
15929
+ return {
15930
+ id: typeof entry.message_id === "string" && entry.message_id ? entry.message_id : null,
15931
+ message: entry.message,
15932
+ timestamp: null,
15933
+ };
15934
+ }
15935
+
15936
+ async function parseMinimaxCodeIncremental(options = {}) {
15937
+ return parsePiLikeIncremental({
15938
+ ...options,
15939
+ // Session files have no cwd header, so project attribution is unsupported.
15940
+ projectQueuePath: undefined,
15941
+ stateKey: "minimaxCode",
15942
+ resolveSessionFiles: resolveMinimaxCodeSessionFiles,
15943
+ resolveDefaultModel: () => `${MINIMAX_CODE_SOURCE}-unknown`,
15944
+ sourceForProvider: () => MINIMAX_CODE_SOURCE,
15945
+ readEntry: readMinimaxCodeEntry,
15946
+ });
15947
+ }
15948
+
15703
15949
  // ─────────────────────────────────────────────────────────────────────────────
15704
15950
  // Craft Agents (lukilabs/craft-agents-oss) — passive JSONL reader
15705
15951
  //
@@ -22935,6 +23181,10 @@ module.exports = {
22935
23181
  resolvePrimeAgentSessionFiles,
22936
23182
  resolvePrimeAgentDefaultModel,
22937
23183
  parsePrimeAgentIncremental,
23184
+ resolveMinimaxCodeHome,
23185
+ resolveMinimaxCodeSessionsDir,
23186
+ resolveMinimaxCodeSessionFiles,
23187
+ parseMinimaxCodeIncremental,
22938
23188
  resolveCraftConfigDir,
22939
23189
  resolveCraftWorkspaceRoots,
22940
23190
  resolveCraftSessionFiles,
@@ -490,13 +490,13 @@ function hashDirectory(dir) {
490
490
  continue;
491
491
  }
492
492
  const execBit = process.platform === "win32" ? 0 : stat.mode & 0o111 ? 1 : 0;
493
- hash.update(`${rel}${execBit}`);
493
+ hash.update(`${rel}\u0000${execBit}\u0000`);
494
494
  try {
495
495
  hash.update(fs.readFileSync(abs));
496
496
  } catch (_e) {
497
497
  // unreadable file — fold its absence in deterministically
498
498
  }
499
- hash.update("");
499
+ hash.update("\u0000");
500
500
  }
501
501
  }
502
502
  };
@@ -1 +0,0 @@
1
- import{a as b,r as l,j as r,c as e,i as v}from"./__vite-browser-external-CTGqjrCe.js";const x={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1,VITE_APP_VERSION:"0.99.0",VITE_INSFORGE_ANON_KEY:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3OC0xMjM0LTU2NzgtOTBhYi1jZGVmMTIzNDU2NzgiLCJlbWFpbCI6ImFub25AaW5zZm9yZ2UuY29tIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODExNDU5NDd9.T0auta_IrVIh0uXW1bob5QSnzvsnJmN28r5XkSGEuQY",VITE_INSFORGE_BASE_URL:"https://srctyff5.us-east.insforge.app"};function f(){return v(),`${(x&&"https://srctyff5.us-east.insforge.app"||"https://srctyff5.us-east.insforge.app").replace(/\/$/,"")}/functions/tokentracker-device-flow-grant`}function p(s){return(s||"").toUpperCase().replace(/\s+/g,"").replace(/[^A-Z0-9-]/g,"")}function h(){const s=b(),[m,g]=l.useState(""),[i,a]=l.useState({kind:"idle",message:""});l.useEffect(()=>{try{const d=new URLSearchParams(window.location.search).get("user_code");d&&g(p(d))}catch{}},[]);async function u(c){if(c?.preventDefault?.(),!s?.signedIn){a({kind:"error",message:e("device.approval.error.sign_in")});return}const d=p(m);if(!/^[A-HJKMNP-Z2-9]{4}-[A-HJKMNP-Z2-9]{4}$/.test(d)){a({kind:"error",message:e("device.approval.error.invalid_code")});return}a({kind:"working",message:e("device.approval.action.approving")});try{const n=await s.getAccessToken?.();if(!n){a({kind:"error",message:e("device.approval.error.no_token")});return}const o=await fetch(f(),{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({user_code:d})}),t=await o.json().catch(()=>({}));o.ok?a({kind:"success",message:t.status==="already_approved"?t.client_info?e("device.approval.success.already_client",{client:t.client_info}):e("device.approval.success.already"):t.client_info?e("device.approval.success.approved_client",{client:t.client_info}):e("device.approval.success.approved")}):o.status===404?a({kind:"error",message:e("device.approval.error.not_found")}):o.status===410?a({kind:"error",message:e("device.approval.error.expired")}):o.status===401?a({kind:"error",message:e("device.approval.error.session_expired")}):a({kind:"error",message:t?.error?e("device.approval.error.server_detail",{message:t.error}):e("device.approval.error.server",{status:o.status})})}catch(n){a({kind:"error",message:e("device.approval.error.network",{message:n?.message||n})})}}return r.jsx("div",{className:"min-h-screen flex items-center justify-center bg-white dark:bg-oai-gray-950 px-6 py-12",children:r.jsxs("div",{className:"w-full max-w-md rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 p-8",children:[r.jsx("h1",{className:"text-2xl font-semibold text-oai-gray-900 dark:text-white mb-2",children:e("device.approval.title")}),r.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400 mb-6",children:e("device.approval.description")}),s?.signedIn?null:r.jsx("div",{className:"rounded-md border border-amber-200 bg-amber-50 dark:bg-amber-900/20 dark:border-amber-800 p-4 mb-4",children:r.jsx("p",{className:"text-sm text-amber-700 dark:text-amber-300",children:e("device.approval.sign_in_banner")})}),r.jsxs("form",{onSubmit:u,className:"space-y-4",children:[r.jsxs("label",{className:"block",children:[r.jsx("span",{className:"block text-xs font-medium uppercase tracking-wide text-oai-gray-500 dark:text-oai-gray-400 mb-1",children:e("device.approval.code_label")}),r.jsx("input",{type:"text",value:m,onChange:c=>g(p(c.target.value)),placeholder:"XXXX-XXXX",spellCheck:!1,autoCapitalize:"characters",maxLength:9,className:"w-full font-mono text-xl tracking-widest text-center rounded-md border border-oai-gray-300 dark:border-oai-gray-700 bg-white dark:bg-oai-gray-900 text-oai-gray-900 dark:text-white px-4 py-3 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-emerald-600"})]}),r.jsx("button",{type:"submit",disabled:i.kind==="working"||!s?.signedIn,className:"w-full rounded-md bg-emerald-600 hover:bg-emerald-500 disabled:bg-oai-gray-300 disabled:cursor-not-allowed text-white font-medium py-3 transition-colors",children:i.kind==="working"?e("device.approval.action.approving"):e("device.approval.action.approve")})]}),i.kind==="success"&&r.jsx("div",{className:"mt-4 rounded-md border border-emerald-200 bg-emerald-50 dark:bg-emerald-900/20 dark:border-emerald-800 p-4",children:r.jsx("p",{className:"text-sm text-emerald-700 dark:text-emerald-300",children:i.message})}),i.kind==="error"&&r.jsx("div",{className:"mt-4 rounded-md border border-red-200 bg-red-50 dark:bg-red-900/20 dark:border-red-800 p-4",children:r.jsx("p",{className:"text-sm text-red-700 dark:text-red-300",children:i.message})})]})})}export{h as default};
@@ -1 +0,0 @@
1
- import{r as n,v as a}from"./__vite-browser-external-CTGqjrCe.js";import{u as p}from"./DialogTitle-Ckzf1tdi.js";import{u as c}from"./composite-B2s880f5.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,i=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
@@ -1 +0,0 @@
1
- import{r as o}from"./__vite-browser-external-CTGqjrCe.js";import{h as r,i as t,p as s}from"./react-Gft01KYk.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};