tokentracker-cli 0.84.9 → 0.86.0

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 (109) 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 +8 -7
  5. package/README.zh-CN.md +5 -4
  6. package/dashboard/dist/assets/{AchievementBadge-BgBFpxmU.js → AchievementBadge-BgbH_mX4.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-DHPD3Fnu.js → AchievementsPage-CQqkh1JS.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-DouWVD-3.js → AchievementsSection-Dmkxpn0s.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-Df9eQiA3.js → BadgeDetailModal-7OQf37lk.js} +1 -1
  10. package/dashboard/dist/assets/{Card-CQ9wBnz6.js → Card-1PmWZRTK.js} +1 -1
  11. package/dashboard/dist/assets/{ClawdAnimated-XIO-I-5v.js → ClawdAnimated-CMJFagpX.js} +1 -1
  12. package/dashboard/dist/assets/{CommandPalette-Cy1cJ91B.js → CommandPalette-D4OrwF1w.js} +1 -1
  13. package/dashboard/dist/assets/{CommunityStatsModal-CwKG4LQM.js → CommunityStatsModal-BDC9EcwZ.js} +1 -1
  14. package/dashboard/dist/assets/{ConfirmModal-BNcWqick.js → ConfirmModal-22DL5C_9.js} +1 -1
  15. package/dashboard/dist/assets/DashboardPage-CEXWzDtt.js +1 -0
  16. package/dashboard/dist/assets/{DevicePage-pJ2jNkZV.js → DevicePage-8uhHB7kX.js} +1 -1
  17. package/dashboard/dist/assets/{DialogClose-C2SOtkJN.js → DialogClose-BcJx6VK-.js} +1 -1
  18. package/dashboard/dist/assets/DialogDescription-D6zxtOM5.js +1 -0
  19. package/dashboard/dist/assets/{DialogTitle-CGSbB8G7.js → DialogTitle-gRzunAqv.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-CZSiw7me.js → FadeIn-4rlPdBVe.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-ZmQhX_kR.js → HeaderGithubStar-CR003aO3.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-D_YdG369.js → HoverTooltip-Dgz-dM8Q.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-du886dF0.js → IpCheckPage-B4PJRJP_.js} +1 -1
  24. package/dashboard/dist/assets/{LandingPage-BuAak8lU.js → LandingPage-3JQe-jOZ.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardAvatar-nU0ARYnN.js → LeaderboardAvatar-CL9LxiEf.js} +1 -1
  26. package/dashboard/dist/assets/{LeaderboardPage-CngwCvC7.js → LeaderboardPage-DhIzM2Z2.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-CXZJ0e-T.js → LeaderboardProfileModal-bbf5uJR5.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-CZve6s6b.js → LeaderboardProfilePage-CzuPxscw.js} +1 -1
  29. package/dashboard/dist/assets/{LimitsPage-CWvoJPk8.js → LimitsPage-B59rb2gg.js} +1 -1
  30. package/dashboard/dist/assets/{LocalOnlyNotice-DyUAAyyE.js → LocalOnlyNotice-BkK-6n1R.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-eEiRfbdv.js → LoginCard-BFGCzEjE.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-C16WdIQm.js → LoginModal-yhkyscFn.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-B2WqpE1t.js → LoginPage-DwU5s0F7.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-DtQWrGmI.js → PetPage-BPscYR6z.js} +1 -1
  35. package/dashboard/dist/assets/{PopoverPopup-CRhqtFLB.js → PopoverPopup-E7PyGTTB.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-DWNH_wYu.js → ProviderIcon-CvmnzwKU.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-DzCczcWZ.js → ResetPasswordPage-DD6qX616.js} +1 -1
  38. package/dashboard/dist/assets/{SegmentedControl-CV9ccq6R.js → SegmentedControl-DjGBL_l1.js} +1 -1
  39. package/dashboard/dist/assets/{Select-BrogNNk0.js → Select-B2MGmsi2.js} +1 -1
  40. package/dashboard/dist/assets/{SelectItemText-Bpu3swt0.js → SelectItemText-BaTwA_ZF.js} +1 -1
  41. package/dashboard/dist/assets/{ServiceStatusPage-D_FIrMJP.js → ServiceStatusPage-0K0y8A6m.js} +1 -1
  42. package/dashboard/dist/assets/{SessionsPage-C8TvTy1o.js → SessionsPage-CxrtRm3B.js} +2 -2
  43. package/dashboard/dist/assets/{SettingsPage-Bd-tzJAR.js → SettingsPage-CyvSWvDP.js} +1 -1
  44. package/dashboard/dist/assets/{SkillsPage-CKGU9wQ4.js → SkillsPage-BVOU8t3t.js} +1 -1
  45. package/dashboard/dist/assets/{TokenGalaxy-DhHW6L-U.js → TokenGalaxy-w7bwGloC.js} +1 -1
  46. package/dashboard/dist/assets/{ToolbarRootContext-DQo-4pPB.js → ToolbarRootContext-BAzpDZej.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-Ejl8d45g.js → TrendMonitor-DyT7Q4xp.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-CY7f4wCD.js → WidgetsPage-eDDtopQT.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-B4-B2v-v.js → WrappedPage-CKkbdWHF.js} +1 -1
  50. package/dashboard/dist/assets/{__vite-browser-external-BNUFXCfp.js → __vite-browser-external-SU8sCTLB.js} +23 -16
  51. package/dashboard/dist/assets/{agent-logos-BRzutmeR.js → agent-logos-bkh9XZJd.js} +1 -1
  52. package/dashboard/dist/assets/{arrow-up-right-BcrbPk8c.js → arrow-up-right-D16zCIw8.js} +1 -1
  53. package/dashboard/dist/assets/{check-BMxeFPfQ.js → check-BFL17JWQ.js} +1 -1
  54. package/dashboard/dist/assets/{chevron-right-DOstgiRf.js → chevron-right-CUJgrtkp.js} +1 -1
  55. package/dashboard/dist/assets/{copy-BsKO524t.js → copy-CRQiCjgs.js} +1 -1
  56. package/dashboard/dist/assets/{download-dadqxej7.js → download-Cd12nDrQ.js} +1 -1
  57. package/dashboard/dist/assets/{external-link-CTu3BMeU.js → external-link-CvAfnsG-.js} +1 -1
  58. package/dashboard/dist/assets/{flame-B_IvWcMn.js → flame-CCi681Yl.js} +1 -1
  59. package/dashboard/dist/assets/{format-tokens-DIT6hLSb.js → format-tokens-BAVt8uTZ.js} +1 -1
  60. package/dashboard/dist/assets/{icons-mWku76bu.js → icons-CbKC96gR.js} +1 -1
  61. package/dashboard/dist/assets/{index-D5-w-aGy.js → index--TDvaL1y.js} +1 -1
  62. package/dashboard/dist/assets/{index-D-gQV-hF.js → index-B5b6PCh5.js} +1 -1
  63. package/dashboard/dist/assets/{index-CboN9E82.js → index-B9LWn-mo.js} +1 -1
  64. package/dashboard/dist/assets/{info-x6l352SW.js → info-BPFpD--M.js} +1 -1
  65. package/dashboard/dist/assets/{limits-providers-Bo12VlLJ.js → limits-providers-Cyedt-WJ.js} +1 -1
  66. package/dashboard/dist/assets/{link-2-eo_xPxH1.js → link-2-BkEPPdfC.js} +1 -1
  67. package/dashboard/dist/assets/{loader-circle-C4n1iGBv.js → loader-circle-CzASiylJ.js} +1 -1
  68. package/dashboard/dist/assets/{maximize-2-pR8xwsI_.js → maximize-2-CkwnHUJn.js} +1 -1
  69. package/dashboard/dist/assets/{provider-display-ErggQWu4.js → provider-display-BXBBedC-.js} +2 -2
  70. package/dashboard/dist/assets/{react-DYKg02Aq.js → react-BXSctDQj.js} +1 -1
  71. package/dashboard/dist/assets/{search-B_jbUwPS.js → search-_qGma9tM.js} +1 -1
  72. package/dashboard/dist/assets/{skills-api-DY5ruGmw.js → skills-api-DdmJjnJ9.js} +1 -1
  73. package/dashboard/dist/assets/{store-Cu8i1y26.js → store-owYCN530.js} +1 -1
  74. package/dashboard/dist/assets/{terminal-D3j6X68s.js → terminal-Clzw-KEF.js} +1 -1
  75. package/dashboard/dist/assets/{trash-2-CFpwgJw5.js → trash-2-C5Giwp_E.js} +1 -1
  76. package/dashboard/dist/assets/{use-community-stats-C0fKq66G.js → use-community-stats-Dk8UjLEr.js} +1 -1
  77. package/dashboard/dist/assets/{use-limits-display-prefs-TOp4UAnI.js → use-limits-display-prefs-CArM595Q.js} +1 -1
  78. package/dashboard/dist/assets/{use-native-settings-BZie_7z1.js → use-native-settings-Cpd69kiF.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list-Bq92YyUp.js → use-ordered-list--CzwhBTx.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-CcILnpU1.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-BcpytkES.js → use-session-efficiency-pref-DUXO8cCg.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-Dmmrne2z.js → use-transform-Cg3kuMj_.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-Cw0Z9Tew.js → use-usage-limits-DY9_mV28.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-DyyonINj.js → useCurrency-BIe86hMn.js} +1 -1
  85. package/dashboard/dist/assets/{useScrollLock-DvWw86lJ.js → useScrollLock-7-hejkNC.js} +1 -1
  86. package/dashboard/dist/assets/{useTokenFormat-CBkvGfJ1.js → useTokenFormat-DEtLbRgQ.js} +1 -1
  87. package/dashboard/dist/assets/{zap-CnYA39Ug.js → zap-DUxY1d-O.js} +1 -1
  88. package/dashboard/dist/index.html +12 -10
  89. package/dashboard/dist/ip-check.html +1 -1
  90. package/dashboard/dist/leaderboard.html +1 -1
  91. package/dashboard/dist/llms.txt +3 -2
  92. package/dashboard/dist/share.html +1 -1
  93. package/package.json +2 -2
  94. package/src/commands/init.js +1 -0
  95. package/src/commands/status.js +129 -9
  96. package/src/commands/sync.js +265 -54
  97. package/src/lib/claude-categorizer.js +36 -9
  98. package/src/lib/diagnostics.js +86 -13
  99. package/src/lib/doctor.js +17 -0
  100. package/src/lib/grok-context-breakdown.js +794 -0
  101. package/src/lib/install-resolver.js +10 -2
  102. package/src/lib/local-api.js +21 -3
  103. package/src/lib/multi-install-parser.js +9 -1
  104. package/src/lib/pricing/seed-snapshot.json +1 -1
  105. package/src/lib/rollout.js +788 -44
  106. package/src/lib/session-analytics.js +332 -9
  107. package/dashboard/dist/assets/DashboardPage-Dns_1yXL.js +0 -1
  108. package/dashboard/dist/assets/DialogDescription-Bhfqqmll.js +0 -1
  109. package/dashboard/dist/assets/use-reduced-motion-ZiVeOkvb.js +0 -1
@@ -1189,9 +1189,24 @@ function toUtcHalfHourStart(ts) {
1189
1189
  ).toISOString();
1190
1190
  }
1191
1191
 
1192
- async function computeClaudeGroundTruthBuckets({ rootDir = null } = {}) {
1193
- const root = rootDir || defaultClaudeProjectsDir();
1194
- const files = listSessionFiles(root);
1192
+ async function computeClaudeGroundTruthBuckets({ rootDir = null, rootDirs = null } = {}) {
1193
+ // Multi-root (#307): a Windows host may scan a native ~/.claude/projects
1194
+ // plus a WSL install over \\wsl$. `rootDirs` wins over the legacy single
1195
+ // `rootDir`; duplicated session files synced between environments collapse
1196
+ // via the msgId+reqId dedup below.
1197
+ const roots = Array.isArray(rootDirs) && rootDirs.length > 0
1198
+ ? rootDirs
1199
+ : [rootDir || defaultClaudeProjectsDir()];
1200
+ const files = [];
1201
+ const seenFiles = new Set();
1202
+ for (const root of roots) {
1203
+ for (const fp of listSessionFiles(root)) {
1204
+ if (!seenFiles.has(fp)) {
1205
+ seenFiles.add(fp);
1206
+ files.push(fp);
1207
+ }
1208
+ }
1209
+ }
1195
1210
  const buckets = new Map(); // `${model}|${hourStart}` → totals
1196
1211
  const seenHashes = new Set();
1197
1212
  const userMessageBuckets = new Map(); // for conversation_count tracking
@@ -1204,7 +1219,9 @@ async function computeClaudeGroundTruthBuckets({ rootDir = null } = {}) {
1204
1219
  continue;
1205
1220
  }
1206
1221
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
1207
- const isMainSession = !fp.includes("/subagents/");
1222
+ // Separator-agnostic: WSL roots are UNC paths where path.join produces
1223
+ // backslashes (mirrors the same check in rollout.js parseClaudeFile).
1224
+ const isMainSession = !/[\\/]subagents[\\/]/.test(fp);
1208
1225
 
1209
1226
  for await (const line of rl) {
1210
1227
  if (!line) continue;
@@ -1224,11 +1241,21 @@ async function computeClaudeGroundTruthBuckets({ rootDir = null } = {}) {
1224
1241
  typeof content === "string" ||
1225
1242
  (Array.isArray(content) && content.some((b) => b?.type === "text"));
1226
1243
  if (hasText) {
1227
- const ts = typeof userObj?.timestamp === "string" ? userObj.timestamp : null;
1228
- const hourStart = ts ? toUtcHalfHourStart(ts) : null;
1229
- if (hourStart) {
1230
- const k = `unknown|${hourStart}`;
1231
- userMessageBuckets.set(k, (userMessageBuckets.get(k) || 0) + 1);
1244
+ // Dedup by line uuid (mirrors parseClaudeFile): with multiple
1245
+ // roots the same session file exists under both the native and
1246
+ // the \\wsl$ path, and without this every user message — and so
1247
+ // conversation_count would double. Usage rows are already
1248
+ // guarded by claudeMessageDedupKey below.
1249
+ const userKey =
1250
+ typeof userObj?.uuid === "string" && userObj.uuid ? `u:${userObj.uuid}` : null;
1251
+ if (!userKey || !seenHashes.has(userKey)) {
1252
+ if (userKey) seenHashes.add(userKey);
1253
+ const ts = typeof userObj?.timestamp === "string" ? userObj.timestamp : null;
1254
+ const hourStart = ts ? toUtcHalfHourStart(ts) : null;
1255
+ if (hourStart) {
1256
+ const k = `unknown|${hourStart}`;
1257
+ userMessageBuckets.set(k, (userMessageBuckets.get(k) || 0) + 1);
1258
+ }
1232
1259
  }
1233
1260
  }
1234
1261
  }
@@ -18,12 +18,43 @@ const { probeOpenclawHookState } = require("./openclaw-hook");
18
18
  const { probeOpenclawSessionPluginState } = require("./openclaw-session-plugin");
19
19
  const { probeGrokHookState } = require("./grok-hook");
20
20
  const { resolveTrackerPaths } = require("./tracker-paths");
21
- // TASK-011: Kiro CLI DB path inlined here to avoid pulling the ~4000-line
21
+ const wsl = require("./wsl-probe");
22
+ // TASK-011: Kiro paths inlined here to avoid pulling the ~4000-line
22
23
  // rollout module on every `tokentracker status` / `diagnostics` call.
23
- // rollout.js still exports resolveKiroCliDbPath for external callers.
24
+ // rollout.js still exports resolveKiroCliDbPath / resolveKiroBasePath for
25
+ // external callers; keep the platform branches in lockstep.
26
+ function resolveKiroIdeBaseInline(env, home) {
27
+ const suffix = ["Kiro", "User", "globalStorage", "kiro.kiroagent"];
28
+ if (process.platform === "win32") {
29
+ const appData = typeof env.APPDATA === "string" && env.APPDATA.trim().length > 0
30
+ ? env.APPDATA.trim()
31
+ : path.join(home, "AppData", "Roaming");
32
+ return path.join(appData, ...suffix);
33
+ }
34
+ if (process.platform === "linux") {
35
+ const configHome = typeof env.XDG_CONFIG_HOME === "string" && env.XDG_CONFIG_HOME.trim().length > 0
36
+ ? env.XDG_CONFIG_HOME.trim()
37
+ : path.join(home, ".config");
38
+ return path.join(configHome, ...suffix);
39
+ }
40
+ return path.join(home, "Library", "Application Support", ...suffix);
41
+ }
42
+
24
43
  function resolveKiroCliDbPathInline(env, home) {
25
44
  if (env.KIRO_CLI_DB_PATH) return env.KIRO_CLI_DB_PATH;
26
45
  const effectiveHome = env.HOME || home;
46
+ if (process.platform === "win32") {
47
+ const localAppData = typeof env.LOCALAPPDATA === "string" && env.LOCALAPPDATA.trim().length > 0
48
+ ? env.LOCALAPPDATA.trim()
49
+ : path.join(effectiveHome, "AppData", "Local");
50
+ return path.join(localAppData, "kiro-cli", "data.sqlite3");
51
+ }
52
+ if (process.platform === "linux") {
53
+ const dataHome = typeof env.XDG_DATA_HOME === "string" && env.XDG_DATA_HOME.trim().length > 0
54
+ ? env.XDG_DATA_HOME.trim()
55
+ : path.join(effectiveHome, ".local", "share");
56
+ return path.join(dataHome, "kiro-cli", "data.sqlite3");
57
+ }
27
58
  return path.join(
28
59
  effectiveHome,
29
60
  "Library",
@@ -106,22 +137,42 @@ async function collectTrackerDiagnostics({
106
137
  // Kiro IDE and Kiro CLI sub-path presence — merged under one "kiro" source
107
138
  // at token/cost aggregation level; operators need visibility of both
108
139
  // sub-paths here for debugging.
109
- const kiroIdeDevDataDir = path.join(
110
- home,
111
- "Library",
112
- "Application Support",
113
- "Kiro",
114
- "User",
115
- "globalStorage",
116
- "kiro.kiroagent",
117
- "dev_data",
118
- );
140
+ const kiroIdeDevDataDir = path.join(resolveKiroIdeBaseInline(process.env, home), "dev_data");
119
141
  const kiroIdePresent =
120
142
  (await safeStatSize(path.join(kiroIdeDevDataDir, "devdata.sqlite"))) > 0 ||
121
143
  (await safeStatSize(path.join(kiroIdeDevDataDir, "tokens_generated.jsonl"))) > 0;
122
144
  const kiroCliDbPath = resolveKiroCliDbPathInline(process.env, home);
123
145
  const kiroCliPresent = require("node:fs").existsSync(kiroCliDbPath);
124
146
 
147
+ // WSL installs (win32 only) — surfaced so `tracker doctor --json` can
148
+ // confirm dual-install discovery without running a sync.
149
+ let kiroWslInstalls = null;
150
+ let claudeWslProjects = null;
151
+ if (process.platform === "win32" && wsl.shouldProbeWsl(process.env)) {
152
+ const wslClaudeHome = wsl.discoverWslHome(".claude");
153
+ if (wslClaudeHome) claudeWslProjects = redactWslUser(path.join(wslClaudeHome, "projects"));
154
+ const wslIdeBase = wsl.discoverWslHome(".config/Kiro/User/globalStorage/kiro.kiroagent");
155
+ const wslKiroHomeDir = wsl.discoverWslHome(".kiro");
156
+ const wslCliDataDir = wsl.discoverWslHome(".local/share/kiro-cli");
157
+ const wslHomeRoot = wslKiroHomeDir
158
+ ? path.dirname(wslKiroHomeDir)
159
+ : (wslCliDataDir ? path.dirname(path.dirname(path.dirname(wslCliDataDir))) : null);
160
+ const wslCliDb = wslHomeRoot
161
+ ? path.join(wslHomeRoot, ".local", "share", "kiro-cli", "data.sqlite3")
162
+ : null;
163
+ if (wslIdeBase || wslHomeRoot) {
164
+ kiroWslInstalls = {
165
+ ide_dev_data: wslIdeBase ? redactWslUser(path.join(wslIdeBase, "dev_data")) : null,
166
+ ide_present: Boolean(wslIdeBase) && (
167
+ (await safeStatSize(path.join(wslIdeBase, "dev_data", "devdata.sqlite"))) > 0 ||
168
+ (await safeStatSize(path.join(wslIdeBase, "dev_data", "tokens_generated.jsonl"))) > 0
169
+ ),
170
+ cli_db: wslCliDb ? redactWslUser(wslCliDb) : null,
171
+ cli_present: Boolean(wslCliDb) && require("node:fs").existsSync(wslCliDb),
172
+ };
173
+ }
174
+ }
175
+
125
176
  const lastSuccessAt = uploadThrottle.lastSuccessMs
126
177
  ? new Date(uploadThrottle.lastSuccessMs).toISOString()
127
178
  : null;
@@ -143,6 +194,8 @@ async function collectTrackerDiagnostics({
143
194
  code_home: redactValue(codeHome, home),
144
195
  code_config: redactValue(codeConfigPath, home),
145
196
  claude_config: redactValue(claudeConfigPath, home),
197
+ claude_projects: redactValue(path.join(home, ".claude", "projects"), home),
198
+ ...(process.platform === "win32" ? { claude_projects_wsl: claudeWslProjects } : {}),
146
199
  gemini_config: redactValue(geminiSettingsPath, home),
147
200
  opencode_config: redactValue(opencodeConfigDir, home),
148
201
  grok_home: redactValue(grokHome, home),
@@ -154,6 +207,9 @@ async function collectTrackerDiagnostics({
154
207
  kiro: {
155
208
  ide_present: kiroIdePresent,
156
209
  cli_present: kiroCliPresent,
210
+ ...(process.platform === "win32"
211
+ ? { wsl_mode: wsl.getWslMode(process.env), wsl_installs: kiroWslInstalls }
212
+ : {}),
157
213
  cli_approximation:
158
214
  "Kiro CLI does not persist explicit token counts (billing is credit-based on Bedrock). Tokens are approximated at 4 chars/token from user prompt chars and assistant response chars. Source rows that came through this path have model='kiro-cli-agent' when the underlying model is unknown (auto-routing); known Bedrock ARNs canonicalize to their short name (e.g. claude-sonnet-4).",
159
215
  merge_policy:
@@ -243,6 +299,17 @@ function redactValue(value, home) {
243
299
  return value.startsWith(homeNorm) ? `~${value.slice(homeNorm.length)}` : value;
244
300
  }
245
301
 
302
+ // UNC WSL paths embed the distro name AND the Linux username; doctor reports
303
+ // are shared for support, so mask the user segment the same way redactValue
304
+ // masks $HOME (\\wsl$\Ubuntu\home\alice\... → \\wsl$\Ubuntu\home\~\...).
305
+ function redactWslUser(value) {
306
+ if (typeof value !== "string") return value;
307
+ return value.replace(
308
+ /^([\\/]{2}wsl(?:\$|\.localhost)[\\/][^\\/]+[\\/]home[\\/])[^\\/]+/i,
309
+ "$1~",
310
+ );
311
+ }
312
+
246
313
  function redactError(message, home) {
247
314
  if (typeof message !== "string") return message;
248
315
  if (typeof home !== "string" || home.length === 0) return message;
@@ -275,4 +342,10 @@ function parseEpochMsToIso(v) {
275
342
  return d.toISOString();
276
343
  }
277
344
 
278
- module.exports = { collectTrackerDiagnostics };
345
+ module.exports = {
346
+ collectTrackerDiagnostics,
347
+ // Exported for the parity test that pins these inline copies to the
348
+ // canonical resolvers in rollout.js (see the lockstep note above).
349
+ resolveKiroIdeBaseInline,
350
+ resolveKiroCliDbPathInline,
351
+ };
package/src/lib/doctor.js CHANGED
@@ -320,6 +320,23 @@ function buildDiagnosticsChecks(diagnostics) {
320
320
  meta: { last_error: uploadError ? uploadError.message || null : null },
321
321
  });
322
322
 
323
+ // Dual-install visibility (#306): surface resolved Kiro installs in the
324
+ // human-readable doctor output, not just the embedded diagnostics JSON.
325
+ const kiro = diagnostics?.kiro;
326
+ if (kiro && typeof kiro.wsl_mode === "string") {
327
+ const fmt = (present) => (present ? "yes" : "no");
328
+ const wslPart = kiro.wsl_installs
329
+ ? `wsl ide=${fmt(kiro.wsl_installs.ide_present)} cli=${fmt(kiro.wsl_installs.cli_present)}`
330
+ : "wsl not detected";
331
+ checks.push({
332
+ id: "kiro.installs",
333
+ status: "ok",
334
+ detail: `native ide=${fmt(kiro.ide_present)} cli=${fmt(kiro.cli_present)}; ${wslPart} (mode ${kiro.wsl_mode})`,
335
+ critical: false,
336
+ meta: { wsl_mode: kiro.wsl_mode, wsl_installs: kiro.wsl_installs || null },
337
+ });
338
+ }
339
+
323
340
  return checks;
324
341
  }
325
342