tokentracker-cli 1.4.1 → 1.4.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 (122) hide show
  1. package/README.de.md +5 -4
  2. package/README.ja.md +5 -4
  3. package/README.ko.md +5 -4
  4. package/README.md +5 -4
  5. package/README.zh-CN.md +5 -4
  6. package/dashboard/dist/assets/{AchievementBadge-DWECUOAX.js → AchievementBadge-WGbdoboF.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-Bakfwwn_.js → AchievementsPage-BvybbsXv.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-DiEX2_5U.js → AchievementsSection-ChNzTFSR.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-ClVMnxVW.js → BadgeDetailModal-DhI9Ejz3.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-D7eyrAPs.js → BotAnimated-Bch0Yxej.js} +1 -1
  11. package/dashboard/dist/assets/{Card-CsAQxZZ8.js → Card-DWdL7d1T.js} +1 -1
  12. package/dashboard/dist/assets/{CloudCheckoutPage-BAN-DtXB.js → CloudCheckoutPage-BjGoOz6P.js} +1 -1
  13. package/dashboard/dist/assets/{CloudPage-D9sNlUAy.js → CloudPage-Btb6a97x.js} +2 -2
  14. package/dashboard/dist/assets/{CommandPalette-D9TNT6un.js → CommandPalette-DwP3k7LE.js} +1 -1
  15. package/dashboard/dist/assets/{CommunityStatsModal-CotRRQNH.js → CommunityStatsModal-mzqLxpwE.js} +1 -1
  16. package/dashboard/dist/assets/{ConfirmModal-C_il5anp.js → ConfirmModal-Dm76q4EX.js} +1 -1
  17. package/dashboard/dist/assets/{Controls-Bd1kODP4.js → Controls-ClZjOBoD.js} +1 -1
  18. package/dashboard/dist/assets/{DashboardPage-De0EL8Hn.js → DashboardPage-Doc8f4NL.js} +1 -1
  19. package/dashboard/dist/assets/{DateRangePopover-quw6hpxy.js → DateRangePopover-vfTUePPg.js} +1 -1
  20. package/dashboard/dist/assets/{DevicePage-CP20lVYv.js → DevicePage-Jjg5q0C2.js} +1 -1
  21. package/dashboard/dist/assets/{DialogClose-BEUL9bD6.js → DialogClose-Ci5mPZ6X.js} +1 -1
  22. package/dashboard/dist/assets/DialogDescription-Di5ev5K6.js +1 -0
  23. package/dashboard/dist/assets/{DialogTitle-C--sXGjf.js → DialogTitle-DI21oZxH.js} +1 -1
  24. package/dashboard/dist/assets/{ElectricLogo-DhP7S1Cu.js → ElectricLogo-DfeZBwPZ.js} +1 -1
  25. package/dashboard/dist/assets/{FadeIn-BPH15_jE.js → FadeIn-C7SIILTc.js} +1 -1
  26. package/dashboard/dist/assets/{HeaderGithubStar-DziE8s6w.js → HeaderGithubStar-RStLbadn.js} +1 -1
  27. package/dashboard/dist/assets/{HoverTooltip-tQyY2CuV.js → HoverTooltip-DI7uAp1_.js} +1 -1
  28. package/dashboard/dist/assets/{IpCheckPage-BjoB05O7.js → IpCheckPage-DTZNTJVh.js} +1 -1
  29. package/dashboard/dist/assets/{LandingPage-Cav49COr.js → LandingPage-Df5scNlw.js} +2 -2
  30. package/dashboard/dist/assets/{LeaderboardPage-CHjFKLvs.js → LeaderboardPage-Ck3SqtnI.js} +2 -2
  31. package/dashboard/dist/assets/{LeaderboardProBadge-D97MfK5W.js → LeaderboardProBadge-CGx6m7R6.js} +1 -1
  32. package/dashboard/dist/assets/{LeaderboardProfileModal-NNTwtWSu.js → LeaderboardProfileModal-DuP7rgfv.js} +3 -3
  33. package/dashboard/dist/assets/{LeaderboardProfilePage-xI81GH3D.js → LeaderboardProfilePage-B5qUJkZX.js} +1 -1
  34. package/dashboard/dist/assets/{LimitsPage-BPOo3FOW.js → LimitsPage-Bv_Lcvcz.js} +1 -1
  35. package/dashboard/dist/assets/{LocalOnlyNotice-j8Tijts2.js → LocalOnlyNotice-Cg4y02JM.js} +1 -1
  36. package/dashboard/dist/assets/{LoginCard-C7_n6J7k.js → LoginCard-Wz0uJKXx.js} +1 -1
  37. package/dashboard/dist/assets/{LoginModal-DwlNscw5.js → LoginModal-C13v_Gs0.js} +1 -1
  38. package/dashboard/dist/assets/{LoginPage-BqhWatt2.js → LoginPage-COH9xXtd.js} +1 -1
  39. package/dashboard/dist/assets/{PetPage-CJlC0V0i.js → PetPage-BWCnURub.js} +2 -2
  40. package/dashboard/dist/assets/{PopoverPopup-wleZ4DTI.js → PopoverPopup-Dw811N-_.js} +1 -1
  41. package/dashboard/dist/assets/{ProviderIcon-biLVsOGr.js → ProviderIcon-CHxhYzK-.js} +1 -1
  42. package/dashboard/dist/assets/{ResetPasswordPage-5Sl45OsN.js → ResetPasswordPage-Ll6jshqg.js} +1 -1
  43. package/dashboard/dist/assets/{Select-77l2QNqa.js → Select-BbooQ3sa.js} +1 -1
  44. package/dashboard/dist/assets/{SelectItemText-BQbmtjd3.js → SelectItemText-DBiW70wi.js} +1 -1
  45. package/dashboard/dist/assets/{SelfHostPage-C8hlgVu4.js → SelfHostPage-BMHJxz5q.js} +1 -1
  46. package/dashboard/dist/assets/{ServiceStatusPage-DrY9Mef8.js → ServiceStatusPage-D6LH0ac-.js} +1 -1
  47. package/dashboard/dist/assets/{SessionsPage-BDTsH3Mk.js → SessionsPage-Mrer6v17.js} +1 -1
  48. package/dashboard/dist/assets/{SettingsPage-C_id9Go3.js → SettingsPage-D_7P_y_l.js} +3 -3
  49. package/dashboard/dist/assets/{SkillsPage-kb6_5uUk.js → SkillsPage-Ka7xm-1v.js} +1 -1
  50. package/dashboard/dist/assets/{TokenGalaxy-BoS0yUFS.js → TokenGalaxy-BEURDPFq.js} +1 -1
  51. package/dashboard/dist/assets/{TooltipPopup-LV1aWIRZ.js → TooltipPopup-ZiuFFARC.js} +1 -1
  52. package/dashboard/dist/assets/{TrendMonitor-DN-nwcSA.js → TrendMonitor-H73lyGEf.js} +1 -1
  53. package/dashboard/dist/assets/{WidgetsPage-C6lvwceS.js → WidgetsPage-_7iCvyTk.js} +1 -1
  54. package/dashboard/dist/assets/{WrappedPage-CfC39wWP.js → WrappedPage-DEXNb6-k.js} +1 -1
  55. package/dashboard/dist/assets/{__vite-browser-external-DpZbUcLL.js → __vite-browser-external-BkNP5Ecj.js} +2 -2
  56. package/dashboard/dist/assets/{agent-logos-CvcdCGj5.js → agent-logos-OVBc_0cF.js} +1 -1
  57. package/dashboard/dist/assets/{arrow-left-BX4LX5TF.js → arrow-left-DDphsclm.js} +1 -1
  58. package/dashboard/dist/assets/{arrow-right-Aktjo2RL.js → arrow-right-B1aNq0Ki.js} +1 -1
  59. package/dashboard/dist/assets/{arrow-up-right-kkQt6Du0.js → arrow-up-right-yTM9azz_.js} +1 -1
  60. package/dashboard/dist/assets/{calendar-days-OvLTNZBH.js → calendar-days-D22ithNF.js} +1 -1
  61. package/dashboard/dist/assets/{check-v5Ie379p.js → check-Dk7tknvE.js} +1 -1
  62. package/dashboard/dist/assets/{chevron-down-CPlz1cYW.js → chevron-down-DdyEzQ4f.js} +1 -1
  63. package/dashboard/dist/assets/{cloud-checkout-C_Rhiwno.js → cloud-checkout-BJjbipnH.js} +1 -1
  64. package/dashboard/dist/assets/{composite-DD_boZdD.js → composite-DEqP00XV.js} +1 -1
  65. package/dashboard/dist/assets/{download-NqHXZ-zn.js → download-C9ixNyzn.js} +1 -1
  66. package/dashboard/dist/assets/{external-link-B0D03J0f.js → external-link-DL4HSsLw.js} +1 -1
  67. package/dashboard/dist/assets/{flame-av87we58.js → flame-DqNH65nB.js} +1 -1
  68. package/dashboard/dist/assets/{format-tokens-B43Ehkbb.js → format-tokens-CyVjKz4I.js} +1 -1
  69. package/dashboard/dist/assets/{gift-CZus7M21.js → gift-ChJQQe_G.js} +1 -1
  70. package/dashboard/dist/assets/{host-mode-Dd9fzAdq.js → host-mode-DpUDpoOz.js} +1 -1
  71. package/dashboard/dist/assets/{icons-CnScwnf_.js → icons-CJiHeOaU.js} +1 -1
  72. package/dashboard/dist/assets/{index-Cj9NdXOj.js → index-72LQsAaf.js} +1 -1
  73. package/dashboard/dist/assets/{index-BZTIoCof.js → index-BucEg3fu.js} +1 -1
  74. package/dashboard/dist/assets/{index-D3XPaYEL.js → index-CrFlTSra.js} +1 -1
  75. package/dashboard/dist/assets/{info-BUmCmjwd.js → info-DReYqxC7.js} +1 -1
  76. package/dashboard/dist/assets/{link-2-t-wSLB2a.js → link-2-kIZbhK2S.js} +1 -1
  77. package/dashboard/dist/assets/{loader-circle-DM-FdXeX.js → loader-circle-1An-X85y.js} +1 -1
  78. package/dashboard/dist/assets/{modifiers.esm-D5AKZNa7.js → modifiers.esm-FoVheEKT.js} +1 -1
  79. package/dashboard/dist/assets/{monitor-smartphone-CDHFLZS-.js → monitor-smartphone-Ch6lr_P2.js} +1 -1
  80. package/dashboard/dist/assets/{play-BIQH--yZ.js → play-CbJ6ZF1K.js} +1 -1
  81. package/dashboard/dist/assets/{plus-CztG5eum.js → plus-C-5ID3a9.js} +1 -1
  82. package/dashboard/dist/assets/{provider-display-B2gZ38Oh.js → provider-display-BFzRtoL2.js} +2 -2
  83. package/dashboard/dist/assets/{react-D1u6HsZx.js → react-D4CR_fLQ.js} +1 -1
  84. package/dashboard/dist/assets/{safePolygon-ClG4WM06.js → safePolygon-DP21Ibqj.js} +1 -1
  85. package/dashboard/dist/assets/{search-NNy_fIbJ.js → search-CaznkY7U.js} +1 -1
  86. package/dashboard/dist/assets/{server-N94HJtJY.js → server-CUOmC6xZ.js} +1 -1
  87. package/dashboard/dist/assets/{shield-check-BGfaKXG9.js → shield-check-Dr2KBMmA.js} +1 -1
  88. package/dashboard/dist/assets/{skills-api-CWydQc4G.js → skills-api-BAz_fblS.js} +1 -1
  89. package/dashboard/dist/assets/{sortable.esm-BF83ljWQ.js → sortable.esm-knEfm5hE.js} +1 -1
  90. package/dashboard/dist/assets/{store-l5WfCa7P.js → store-CL97jcij.js} +1 -1
  91. package/dashboard/dist/assets/{terminal-CRBThKUQ.js → terminal-EjFyreWj.js} +1 -1
  92. package/dashboard/dist/assets/{trash-BXN5tg81.js → trash-ByqaZXkQ.js} +1 -1
  93. package/dashboard/dist/assets/{use-community-stats-dHketgyF.js → use-community-stats-Bs4RDJZE.js} +1 -1
  94. package/dashboard/dist/assets/{use-latest-request-guard-CdM1Pn_p.js → use-latest-request-guard-CG3Ir1cP.js} +1 -1
  95. package/dashboard/dist/assets/{use-limits-display-prefs-dOm4SCFU.js → use-limits-display-prefs-tACgCxeR.js} +1 -1
  96. package/dashboard/dist/assets/{use-native-settings-GL9uAidm.js → use-native-settings-CWjXyJXi.js} +1 -1
  97. package/dashboard/dist/assets/{use-ordered-list-Btz_NSiA.js → use-ordered-list-DsuC9JQa.js} +1 -1
  98. package/dashboard/dist/assets/use-reduced-motion-CwFguzkm.js +1 -0
  99. package/dashboard/dist/assets/{use-session-efficiency-pref-BPZel3HL.js → use-session-efficiency-pref-NeoJE8BL.js} +1 -1
  100. package/dashboard/dist/assets/{use-transform-D2xePGbE.js → use-transform-BWGKOsya.js} +1 -1
  101. package/dashboard/dist/assets/{use-usage-limits-vEmyPwMY.js → use-usage-limits-CFWFNwHS.js} +1 -1
  102. package/dashboard/dist/assets/{useCurrency-DnhS0llv.js → useCurrency-B_C4_P5x.js} +1 -1
  103. package/dashboard/dist/assets/{usePositioner-7yozJJJ0.js → usePositioner-B0L5_o38.js} +1 -1
  104. package/dashboard/dist/assets/{useTokenFormat-DvjyXv8i.js → useTokenFormat-DH1GWHSU.js} +1 -1
  105. package/dashboard/dist/assets/{zap-CElRpX2w.js → zap-Chv1p7pS.js} +1 -1
  106. package/dashboard/dist/index.html +13 -11
  107. package/dashboard/dist/ip-check.html +1 -1
  108. package/dashboard/dist/leaderboard.html +1 -1
  109. package/dashboard/dist/llms.txt +3 -2
  110. package/dashboard/dist/share.html +1 -1
  111. package/package.json +2 -2
  112. package/src/commands/doctor.js +15 -1
  113. package/src/commands/init.js +9 -0
  114. package/src/commands/status.js +20 -2
  115. package/src/commands/sync.js +25 -0
  116. package/src/lib/cloud-device-token.js +77 -10
  117. package/src/lib/diagnostics.js +4 -1
  118. package/src/lib/pricing/index.js +2 -1
  119. package/src/lib/pricing/seed-snapshot.json +1 -1
  120. package/src/lib/rollout.js +402 -0
  121. package/dashboard/dist/assets/DialogDescription-DpkD2qwW.js +0 -1
  122. package/dashboard/dist/assets/use-reduced-motion-D9n2I_eN.js +0 -1
@@ -17079,6 +17079,405 @@ async function parseReasonixIncremental({ telemetryFiles, cursors, queuePath, on
17079
17079
  return { recordsProcessed, eventsAggregated, bucketsQueued };
17080
17080
  }
17081
17081
 
17082
+ // ─────────────────────────────────────────────────────────────────────────────
17083
+ // Muse Code (Meta) — passive session.jsonl reader.
17084
+ //
17085
+ // ~/.local/share/muse/sessions/YYYY/MM/DD/<session>/session.jsonl
17086
+ // ~/.local/share/muse/sessions/YYYY/MM/DD/<session>/subagent/<id>/session.jsonl
17087
+ //
17088
+ // Every record is `{id, stream, sequence, recorded_at (epoch µs), payload_type,
17089
+ // payload}`. Per-model-step usage rides on a `runtime.session` record whose
17090
+ // `payload.event.kind` is `goal_usage_attribution`:
17091
+ //
17092
+ // quantity: {unit:"tokens", reported:true, input_tokens, output_tokens,
17093
+ // cached_tokens, reasoning_tokens, main_llm_steps}
17094
+ //
17095
+ // Only `usage_family:"provider"` + `reported:true` rows are model usage (tool and
17096
+ // reminder rows are zero placeholders). The usage row names no model: the model
17097
+ // comes from the `run.model.configured` record of the same `run_id`.
17098
+ //
17099
+ // Token semantics (verified against the session snapshot `cost_ledger`, where
17100
+ // `input_tokens` equals the sum of these rows and `cache_read_tokens` equals
17101
+ // `cached_tokens`): `input_tokens` INCLUDES cache reads, and `reasoning_tokens`
17102
+ // is a subset of `output_tokens`. Subagent logs are separate streams whose usage
17103
+ // is not repeated in the parent log, so each file is counted once. No prompt,
17104
+ // response or path text is read or stored; only counts, timestamps and the
17105
+ // model id.
17106
+ function resolveMuseHome(env = process.env) {
17107
+ if (env.TOKENTRACKER_MUSE_HOME) return expandHomePath(env.TOKENTRACKER_MUSE_HOME, env);
17108
+ return path.join(env.HOME || require("node:os").homedir(), ".local", "share", "muse");
17109
+ }
17110
+
17111
+ const MUSE_SCAN_MAX_DEPTH = 8;
17112
+ const MUSE_READ_CHUNK_BYTES = 8 * 1024 * 1024;
17113
+ const MUSE_WATERMARK_IDS_MAX = 100;
17114
+
17115
+ function collectMuseSessionFiles(dir, files, depth = 0) {
17116
+ if (depth > MUSE_SCAN_MAX_DEPTH) return;
17117
+ let entries;
17118
+ try { entries = fssync.readdirSync(dir, { withFileTypes: true }); } catch { return; }
17119
+ for (const entry of entries) {
17120
+ // Dot directories (.msp-view-v1) hold derived views, not the session log.
17121
+ if (entry.name.startsWith(".")) continue;
17122
+ const full = path.join(dir, entry.name);
17123
+ if (entry.isDirectory()) collectMuseSessionFiles(full, files, depth + 1);
17124
+ else if (entry.isFile() && entry.name === "session.jsonl") files.push(full);
17125
+ }
17126
+ }
17127
+
17128
+ function resolveMuseSessionFiles(env = process.env) {
17129
+ const sessionsDir = path.join(resolveMuseHome(env), "sessions");
17130
+ const files = [];
17131
+ collectMuseSessionFiles(sessionsDir, files);
17132
+ return files;
17133
+ }
17134
+
17135
+ // The model Muse is configured to use, for rows whose run has no model record
17136
+ // in the portion of the log we read. Reads only the `model` string.
17137
+ function resolveMuseDefaultModel(env = process.env) {
17138
+ const fallback = "muse";
17139
+ try {
17140
+ const configHome = env.XDG_CONFIG_HOME || path.join(env.HOME || require("node:os").homedir(), ".config");
17141
+ const settings = JSON.parse(fssync.readFileSync(path.join(configHome, "muse", "settings.json"), "utf8"));
17142
+ return typeof settings?.model === "string" && settings.model.trim() ? settings.model.trim() : fallback;
17143
+ } catch {
17144
+ return fallback;
17145
+ }
17146
+ }
17147
+
17148
+ // A permission transaction wraps its records as JSON strings under `children`;
17149
+ // unwrap them so a model or usage record is never missed inside a frame.
17150
+ function* museRecords(entry) {
17151
+ if (entry && Array.isArray(entry.children) && entry.retained_frame) {
17152
+ for (const child of entry.children) {
17153
+ if (!child || typeof child.record_json !== "string") continue;
17154
+ try { yield JSON.parse(child.record_json); } catch { /* skip */ }
17155
+ }
17156
+ return;
17157
+ }
17158
+ yield entry;
17159
+ }
17160
+
17161
+
17162
+ const MUSE_HEAD_FINGERPRINT_BYTES = 256;
17163
+ const MUSE_PREFIX_FINGERPRINT_MAX_BYTES = 8 * 1024 * 1024;
17164
+
17165
+ // Fingerprints for Muse session logs so an in-place rewrite is not mistaken
17166
+ // for an append. Prefer a hash of the entire consumed prefix (capped); fall
17167
+ // back to head+tail samples only when the prefix exceeds the cap.
17168
+ function museRangeFingerprint(fd, offset, length) {
17169
+ const n = Math.max(0, Number(length) || 0);
17170
+ if (n <= 0) return null;
17171
+ const buf = Buffer.alloc(n);
17172
+ const read = fssync.readSync(fd, buf, 0, n, Math.max(0, Number(offset) || 0));
17173
+ if (read <= 0) return null;
17174
+ return crypto.createHash("sha256").update(buf.subarray(0, read)).digest("hex");
17175
+ }
17176
+
17177
+ function museHeadFingerprint(fd, size) {
17178
+ return museRangeFingerprint(fd, 0, Math.min(MUSE_HEAD_FINGERPRINT_BYTES, Math.max(0, Number(size) || 0)));
17179
+ }
17180
+
17181
+ function musePrefixFingerprint(fd, size) {
17182
+ const total = Math.max(0, Number(size) || 0);
17183
+ if (total <= 0) return null;
17184
+ if (total <= MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
17185
+ const hash = crypto.createHash("sha256");
17186
+ let offset = 0;
17187
+ const chunk = Buffer.alloc(Math.min(64 * 1024, total));
17188
+ while (offset < total) {
17189
+ const want = Math.min(chunk.length, total - offset);
17190
+ const read = fssync.readSync(fd, chunk, 0, want, offset);
17191
+ if (read <= 0) break;
17192
+ hash.update(chunk.subarray(0, read));
17193
+ offset += read;
17194
+ }
17195
+ return offset === total ? hash.digest("hex") : null;
17196
+ }
17197
+ // Large logs: sample head and tail of the consumed prefix.
17198
+ const head = museHeadFingerprint(fd, total);
17199
+ const tailLen = Math.min(MUSE_HEAD_FINGERPRINT_BYTES, total);
17200
+ const tail = museRangeFingerprint(fd, total - tailLen, tailLen);
17201
+ return head && tail ? `${head}:${tail}` : null;
17202
+ }
17203
+
17204
+ function museResumeLooksAppendOnly(fd, prevSize, prev, currentPrefixFp, currentMtimeMs, currentSize) {
17205
+ if (prev?.forceRewind === true) return false;
17206
+ if (!(prevSize > 0)) return true;
17207
+ if (typeof prev?.prefixFp === "string" && prev.prefixFp && prev.prefixFp !== currentPrefixFp) return false;
17208
+ // Legacy cursors without prefixFp: reject when the head sample diverges.
17209
+ if (!prev?.prefixFp && typeof prev?.headFp === "string" && prev.headFp) {
17210
+ const head = museHeadFingerprint(fd, prevSize);
17211
+ if (head && head !== prev.headFp) return false;
17212
+ }
17213
+ // Equal-size rewrite above the full-prefix cap (or any equal-size rewrite that
17214
+ // preserves sampled bytes) still bumps mtime on real filesystems. Only apply
17215
+ // when the size is unchanged — growth from an append also changes mtime.
17216
+ if (
17217
+ Number(currentSize) === Number(prevSize) &&
17218
+ Number.isFinite(Number(prev?.mtimeMs)) &&
17219
+ Number.isFinite(Number(currentMtimeMs)) &&
17220
+ Number(prev.mtimeMs) !== Number(currentMtimeMs)
17221
+ ) {
17222
+ return false;
17223
+ }
17224
+ // Resume offset must be a JSONL record boundary.
17225
+ const buf = Buffer.alloc(1);
17226
+ const read = fssync.readSync(fd, buf, 0, 1, prevSize - 1);
17227
+ return read === 1 && buf[0] === 0x0a;
17228
+ }
17229
+
17230
+
17231
+ // Subagent logs live under .../sessions/<session>/subagent/... . Matching any
17232
+ // ancestor named "subagent" would misclassify roots when the Muse home path
17233
+ // itself contains that segment (e.g. TOKENTRACKER_MUSE_HOME=/data/subagent/muse).
17234
+ function isMuseSubagentSessionFile(filePath, env = process.env) {
17235
+ const sessionsRoot = path.join(resolveMuseHome(env), "sessions");
17236
+ const relative = path.relative(sessionsRoot, filePath);
17237
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
17238
+ // Outside the configured sessions root — fall back to a local segment check
17239
+ // that still requires the subagent directory to be a parent of session.jsonl.
17240
+ const parts = filePath.split(path.sep);
17241
+ const sessionIdx = parts.lastIndexOf("session.jsonl");
17242
+ return sessionIdx > 0 && parts.slice(0, sessionIdx).includes("subagent");
17243
+ }
17244
+ return relative.split(path.sep).includes("subagent");
17245
+ }
17246
+
17247
+ async function parseMuseIncremental({ sessionFiles, cursors, queuePath, onProgress, env } = {}) {
17248
+ await ensureDir(path.dirname(queuePath));
17249
+ const state = cursors.muse && typeof cursors.muse === "object" ? cursors.muse : {};
17250
+ const seenIds = new Set(Array.isArray(state.seenIds) ? state.seenIds : []);
17251
+ const fileOffsets =
17252
+ state.fileOffsets && typeof state.fileOffsets === "object" ? { ...state.fileOffsets } : {};
17253
+ const files = Array.isArray(sessionFiles) ? sessionFiles : resolveMuseSessionFiles(env || process.env);
17254
+ const defaultModel = resolveMuseDefaultModel(env || process.env);
17255
+
17256
+ const hourlyState = normalizeHourlyState(cursors?.hourly);
17257
+ const touchedBuckets = new Set();
17258
+ const cb = typeof onProgress === "function" ? onProgress : null;
17259
+ let recordsProcessed = 0;
17260
+ let eventsAggregated = 0;
17261
+
17262
+ const handleRecord = (rec, fileState) => {
17263
+ if (!rec || typeof rec !== "object") return;
17264
+ const payload = rec.payload;
17265
+ if (!payload || typeof payload !== "object") return;
17266
+ if (rec.payload_type === "run.model.configured") {
17267
+ const modelRec = payload.record;
17268
+ const runId = modelRec?.run_stream?.id;
17269
+ if (typeof modelRec?.model_id === "string" && modelRec.model_id) {
17270
+ if (runId) fileState.runModels[runId] = modelRec.model_id;
17271
+ fileState.model = modelRec.model_id;
17272
+ }
17273
+ return;
17274
+ }
17275
+ if (rec.payload_type !== "runtime.session") return;
17276
+ const event = payload.event;
17277
+ if (!event || event.kind !== "goal_usage_attribution") return;
17278
+ const usage = event.record;
17279
+ const quantity = usage?.quantity;
17280
+ if (!quantity || usage.usage_family !== "provider" || quantity.reported !== true) return;
17281
+ const usageId = typeof usage.usage_id === "string" ? usage.usage_id : null;
17282
+ if (!usageId || seenIds.has(usageId)) return;
17283
+ // A rewritten log is re-read from the start, and `seenIds` keeps only the
17284
+ // newest 10k ids. Rows older than the last one counted from this file are
17285
+ // already in the queue, so skip them even if their id was evicted. Rows at
17286
+ // exactly that timestamp are skipped only when their id was counted, so a
17287
+ // new row that shares the timestamp is kept.
17288
+ const micros = Number(rec.recorded_at);
17289
+ if (fileState.skipThrough && Number.isFinite(micros)) {
17290
+ if (micros < fileState.skipThrough) return;
17291
+ if (micros === fileState.skipThrough && fileState.skipIds.has(usageId)) return;
17292
+ }
17293
+
17294
+ recordsProcessed++;
17295
+ const inputTotal = toNonNegativeInt(quantity.input_tokens);
17296
+ const output = toNonNegativeInt(quantity.output_tokens);
17297
+ const cached = Math.min(toNonNegativeInt(quantity.cached_tokens), inputTotal);
17298
+ const reasoning = Math.min(toNonNegativeInt(quantity.reasoning_tokens), output);
17299
+ seenIds.add(usageId);
17300
+ if (inputTotal === 0 && output === 0) return;
17301
+
17302
+ if (!Number.isFinite(micros) || micros <= 0) return;
17303
+ const bucketStart = toUtcHalfHourStart(new Date(Math.floor(micros / 1000)).toISOString());
17304
+ if (!bucketStart) return;
17305
+
17306
+ const input = inputTotal - cached;
17307
+ const model = fileState.runModels[payload.run_id] || fileState.model || defaultModel;
17308
+ // One conversation per root session, charged to its first usage record.
17309
+ // Subagent logs are part of their parent's conversation.
17310
+ const conversations = fileState.counted || fileState.isSubagent ? 0 : 1;
17311
+ fileState.counted = true;
17312
+ const bucket = getHourlyBucket(hourlyState, "muse", model, bucketStart);
17313
+ addTotals(bucket.totals, {
17314
+ input_tokens: input,
17315
+ cached_input_tokens: cached,
17316
+ cache_creation_input_tokens: 0,
17317
+ output_tokens: output,
17318
+ reasoning_output_tokens: reasoning,
17319
+ total_tokens: input + cached + output,
17320
+ conversation_count: conversations,
17321
+ });
17322
+ touchedBuckets.add(bucketKey("muse", model, bucketStart));
17323
+ eventsAggregated++;
17324
+ // Advance only for rows that reached a bucket, remembering the ids that
17325
+ // share the newest timestamp.
17326
+ if (micros > fileState.watermark) {
17327
+ fileState.watermark = micros;
17328
+ fileState.watermarkIds = [usageId];
17329
+ } else if (micros === fileState.watermark && fileState.watermarkIds.length < MUSE_WATERMARK_IDS_MAX) {
17330
+ fileState.watermarkIds.push(usageId);
17331
+ }
17332
+ };
17333
+
17334
+ for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
17335
+ const filePath = files[fileIdx];
17336
+ // Open first and stat the descriptor, so the size and inode describe the
17337
+ // file that is actually read (no check-then-open race).
17338
+ let fd;
17339
+ try { fd = fssync.openSync(filePath, "r"); } catch { continue; }
17340
+ let stat;
17341
+ try { stat = fssync.fstatSync(fd); } catch {
17342
+ try { fssync.closeSync(fd); } catch { /* ignore */ }
17343
+ continue;
17344
+ }
17345
+
17346
+ const prev = fileOffsets[filePath] || {};
17347
+ const prevSize = Number(prev.size) || 0;
17348
+ const inodeChanged = typeof prev.ino === "number" && prev.ino !== stat.ino;
17349
+ const prefixFp = musePrefixFingerprint(fd, Math.min(prevSize, stat.size));
17350
+ const headFp = museHeadFingerprint(fd, stat.size);
17351
+ // Same-inode compaction can shrink then grow past prevSize, rewrite the
17352
+ // middle at equal size, or be observed while empty mid-rewrite. Validate
17353
+ // the consumed-prefix fingerprint (and forceRewind) before append-only.
17354
+ const appendOnly = !inodeChanged && stat.size >= prevSize &&
17355
+ museResumeLooksAppendOnly(fd, prevSize, prev, prefixFp, stat.mtimeMs, stat.size);
17356
+ const rewound = !appendOnly;
17357
+ const startOffset = rewound ? 0 : prevSize;
17358
+ if (stat.size <= startOffset) {
17359
+ const emptyObserved = stat.size <= 0 && prevSize > 0;
17360
+ fileOffsets[filePath] = {
17361
+ ...prev,
17362
+ size: Math.min(prevSize, stat.size),
17363
+ mtimeMs: stat.mtimeMs,
17364
+ ino: stat.ino,
17365
+ headFp,
17366
+ prefixFp: stat.size > 0 ? musePrefixFingerprint(fd, stat.size) : null,
17367
+ // An empty observation between truncate and rewrite must not clear
17368
+ // watermark-based replay protection on the next population.
17369
+ forceRewind: emptyObserved || prev.forceRewind === true || rewound,
17370
+ };
17371
+ try { fssync.closeSync(fd); } catch { /* ignore */ }
17372
+ continue;
17373
+ }
17374
+
17375
+ const fileState = {
17376
+ // Keep known model attribution across rewinds: compaction may drop the
17377
+ // original run.model.configured rows while later usage for those runs
17378
+ // remains. Replayed configuration records still overwrite these maps.
17379
+ model: prev.model || null,
17380
+ runModels: { ...(prev.runModels || {}) },
17381
+ // A rewritten log keeps its flag: its usage ids were already counted.
17382
+ counted: prev.counted === true,
17383
+ // Latest usage timestamp (epoch µs) counted from this file; on a rewrite
17384
+ // it becomes the point below which rows are not counted again.
17385
+ watermark: Number(prev.watermark) || 0,
17386
+ watermarkIds: Array.isArray(prev.watermarkIds) ? prev.watermarkIds.slice(0, MUSE_WATERMARK_IDS_MAX) : [],
17387
+ skipThrough: rewound ? Number(prev.watermark) || 0 : 0,
17388
+ skipIds: new Set(rewound && Array.isArray(prev.watermarkIds) ? prev.watermarkIds : []),
17389
+ isSubagent: isMuseSubagentSessionFile(filePath, env || process.env),
17390
+ };
17391
+
17392
+ let consumed = 0;
17393
+ // Hash the bytes we actually parse so a rewrite between read and cursor
17394
+ // save cannot invent a matching fingerprint for unread content.
17395
+ const consumedHash = crypto.createHash("sha256");
17396
+ let hashedPrefix = startOffset === 0;
17397
+ if (startOffset > 0 && startOffset <= MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
17398
+ // Include the already-trusted prefix so the saved fingerprint covers
17399
+ // the full consumed region, not only this sync's delta.
17400
+ let off = 0;
17401
+ const pre = Buffer.alloc(Math.min(64 * 1024, startOffset));
17402
+ while (off < startOffset) {
17403
+ const want = Math.min(pre.length, startOffset - off);
17404
+ const read = fssync.readSync(fd, pre, 0, want, off);
17405
+ if (read <= 0) break;
17406
+ consumedHash.update(pre.subarray(0, read));
17407
+ off += read;
17408
+ }
17409
+ hashedPrefix = off === startOffset;
17410
+ } else if (startOffset > MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
17411
+ hashedPrefix = false;
17412
+ }
17413
+ {
17414
+ let position = startOffset;
17415
+ let carry = Buffer.alloc(0);
17416
+ while (position < stat.size) {
17417
+ const length = Math.min(MUSE_READ_CHUNK_BYTES, stat.size - position);
17418
+ const chunk = Buffer.alloc(length);
17419
+ const read = fssync.readSync(fd, chunk, 0, length, position);
17420
+ if (read <= 0) break;
17421
+ position += read;
17422
+ const data = carry.length ? Buffer.concat([carry, chunk.subarray(0, read)]) : chunk.subarray(0, read);
17423
+ // Only complete lines are consumed, so a record still being written is
17424
+ // read whole on the next sync instead of being skipped.
17425
+ const lastNewline = data.lastIndexOf(0x0a);
17426
+ if (lastNewline < 0) { carry = data; continue; }
17427
+ const complete = data.subarray(0, lastNewline + 1);
17428
+ carry = Buffer.from(data.subarray(lastNewline + 1));
17429
+ if (hashedPrefix) consumedHash.update(complete);
17430
+ consumed += complete.length;
17431
+ for (const line of complete.toString("utf8").split("\n")) {
17432
+ if (!line.trim()) continue;
17433
+ let entry;
17434
+ try { entry = JSON.parse(line); } catch { continue; }
17435
+ for (const rec of museRecords(entry)) handleRecord(rec, fileState);
17436
+ }
17437
+ }
17438
+ }
17439
+ const runModelKeys = Object.keys(fileState.runModels);
17440
+ const keptRunModels = {};
17441
+ // Bound the cursor: later records reference recent runs.
17442
+ for (const key of runModelKeys.slice(-50)) keptRunModels[key] = fileState.runModels[key];
17443
+ const consumedSize = startOffset + consumed;
17444
+ // Fingerprint from the same descriptor we read — never reopen (CodeQL
17445
+ // file-system-race) and never trust a post-read rewrite.
17446
+ let savedPrefixFp = null;
17447
+ if (hashedPrefix && consumedSize <= MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
17448
+ savedPrefixFp = consumedHash.digest("hex");
17449
+ } else {
17450
+ try { savedPrefixFp = musePrefixFingerprint(fd, consumedSize); } catch { /* best-effort */ }
17451
+ }
17452
+ fileOffsets[filePath] = {
17453
+ size: consumedSize,
17454
+ mtimeMs: stat.mtimeMs,
17455
+ ino: stat.ino,
17456
+ headFp,
17457
+ prefixFp: savedPrefixFp,
17458
+ forceRewind: false,
17459
+ model: fileState.model,
17460
+ runModels: keptRunModels,
17461
+ counted: fileState.counted,
17462
+ watermark: fileState.watermark,
17463
+ watermarkIds: fileState.watermarkIds,
17464
+ };
17465
+ try { fssync.closeSync(fd); } catch { /* ignore */ }
17466
+ if (cb) {
17467
+ cb({ index: fileIdx + 1, total: files.length, recordsProcessed, eventsAggregated, bucketsQueued: touchedBuckets.size });
17468
+ }
17469
+ }
17470
+
17471
+ const seenArr = Array.from(seenIds);
17472
+ const cappedSeen = seenArr.length > 10_000 ? seenArr.slice(seenArr.length - 10_000) : seenArr;
17473
+ const bucketsQueued = await enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets });
17474
+ const updatedAt = new Date().toISOString();
17475
+ hourlyState.updatedAt = updatedAt;
17476
+ cursors.hourly = hourlyState;
17477
+ cursors.muse = { ...state, seenIds: cappedSeen, fileOffsets, updatedAt };
17478
+ return { recordsProcessed, eventsAggregated, bucketsQueued };
17479
+ }
17480
+
17082
17481
  async function parseCraftIncremental({
17083
17482
  sessionFiles,
17084
17483
  cursors,
@@ -24865,6 +25264,9 @@ module.exports = {
24865
25264
  resolveReasonixTelemetryFiles,
24866
25265
  normalizeReasonixModel,
24867
25266
  parseReasonixIncremental,
25267
+ resolveMuseHome,
25268
+ resolveMuseSessionFiles,
25269
+ parseMuseIncremental,
24868
25270
  // Exposed for regression tests covering cache-token accounting.
24869
25271
  normalizeGeminiTokens,
24870
25272
  normalizeOpencodeTokens,
@@ -1 +0,0 @@
1
- import{r as n,K as a}from"./__vite-browser-external-DpZbUcLL.js";import{u as p}from"./DialogTitle-C--sXGjf.js";import{u as c}from"./composite-DD_boZdD.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-DpZbUcLL.js";import{h as r,i as t,p as s}from"./react-D1u6HsZx.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};