tokentracker-cli 0.84.8 → 0.85.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 (105) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-DAAenS6A.js → AchievementBadge-DEqbHlmn.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-DJ9GMKxL.js → AchievementsPage-BOaEu1AQ.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-DtpLrh2Q.js → AchievementsSection-C370ngO3.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-CkE1DwdN.js → BadgeDetailModal-D7Wkef0K.js} +1 -1
  5. package/dashboard/dist/assets/{Card-Cs2vVMHG.js → Card-mDEVjNdM.js} +1 -1
  6. package/dashboard/dist/assets/{ClawdAnimated-BCSNeob2.js → ClawdAnimated-DdsX-z8n.js} +1 -1
  7. package/dashboard/dist/assets/{CommandPalette-CFDTjp76.js → CommandPalette-DhxRIuHC.js} +1 -1
  8. package/dashboard/dist/assets/{CommunityStatsModal-D2oJpI9i.js → CommunityStatsModal-CG-9JPS6.js} +1 -1
  9. package/dashboard/dist/assets/{ConfirmModal-D0a_xdwF.js → ConfirmModal-COhxErLm.js} +1 -1
  10. package/dashboard/dist/assets/DashboardPage-tX0DKgFE.js +1 -0
  11. package/dashboard/dist/assets/{DevicePage-c9Fk13gJ.js → DevicePage-BsSeHx84.js} +1 -1
  12. package/dashboard/dist/assets/{DialogClose-jsfDvF2n.js → DialogClose-eyOW2cbx.js} +1 -1
  13. package/dashboard/dist/assets/DialogDescription-BwtE4lKl.js +1 -0
  14. package/dashboard/dist/assets/{DialogTitle-Dt835L7T.js → DialogTitle-H8sGiH7c.js} +1 -1
  15. package/dashboard/dist/assets/{FadeIn-DJssGrj8.js → FadeIn-pq2ja50M.js} +1 -1
  16. package/dashboard/dist/assets/{HeaderGithubStar-B9MwwA-P.js → HeaderGithubStar-DWgcpDHr.js} +1 -1
  17. package/dashboard/dist/assets/{HoverTooltip-DPLMJkai.js → HoverTooltip-CcJ_VR65.js} +1 -1
  18. package/dashboard/dist/assets/{IpCheckPage-BXKjxEHe.js → IpCheckPage-CNUN346Q.js} +1 -1
  19. package/dashboard/dist/assets/{LandingPage-BJNMQTec.js → LandingPage-yaoRGsNI.js} +2 -2
  20. package/dashboard/dist/assets/{LeaderboardAvatar-BNwbnOa3.js → LeaderboardAvatar-Ddai4I12.js} +1 -1
  21. package/dashboard/dist/assets/{LeaderboardPage-BOxejyMy.js → LeaderboardPage-Bajkxf63.js} +2 -2
  22. package/dashboard/dist/assets/{LeaderboardProfileModal-BEqUZCOD.js → LeaderboardProfileModal-BGYKDgIi.js} +3 -3
  23. package/dashboard/dist/assets/{LeaderboardProfilePage-CUDV5iyh.js → LeaderboardProfilePage-Ck5vPaeN.js} +1 -1
  24. package/dashboard/dist/assets/LimitsPage-Bqobtsw2.js +4 -0
  25. package/dashboard/dist/assets/{LocalOnlyNotice-B3BSeCHd.js → LocalOnlyNotice-e9KSE3wj.js} +1 -1
  26. package/dashboard/dist/assets/{LoginCard-2mAb696j.js → LoginCard-BhTSm2tb.js} +1 -1
  27. package/dashboard/dist/assets/{LoginModal-cacKhc6U.js → LoginModal-CRwuThde.js} +1 -1
  28. package/dashboard/dist/assets/{LoginPage-rnBlie8p.js → LoginPage-j1i74lcc.js} +1 -1
  29. package/dashboard/dist/assets/{PetPage-D3DhuS6O.js → PetPage-r62ViU1X.js} +1 -1
  30. package/dashboard/dist/assets/{PopoverPopup-CxATX7M9.js → PopoverPopup-DopNTyD4.js} +1 -1
  31. package/dashboard/dist/assets/{ProviderIcon-B5b9LkmM.js → ProviderIcon-c1LkPhYM.js} +1 -1
  32. package/dashboard/dist/assets/{ResetPasswordPage-BGKmQ_JW.js → ResetPasswordPage-DJdDNvtq.js} +1 -1
  33. package/dashboard/dist/assets/{SegmentedControl-BMeYCm2b.js → SegmentedControl-DhWVOTPN.js} +1 -1
  34. package/dashboard/dist/assets/{Select-CYHXq8im.js → Select-DeUmTmsJ.js} +1 -1
  35. package/dashboard/dist/assets/{SelectItemText-DvI4i06h.js → SelectItemText-pX0B5f7h.js} +1 -1
  36. package/dashboard/dist/assets/{ServiceStatusPage-dv26-VvI.js → ServiceStatusPage-ejIPz6oF.js} +1 -1
  37. package/dashboard/dist/assets/{SessionsPage-DBQruqkO.js → SessionsPage-WEbn90GW.js} +2 -2
  38. package/dashboard/dist/assets/{SettingsPage-BWRbQ3Iq.js → SettingsPage-B8t5gCeI.js} +1 -1
  39. package/dashboard/dist/assets/{SkillsPage-BonhKgAt.js → SkillsPage-BWMlzeAp.js} +1 -1
  40. package/dashboard/dist/assets/{TokenGalaxy-BBUD1kpU.js → TokenGalaxy-DIBRBvMj.js} +1 -1
  41. package/dashboard/dist/assets/{ToolbarRootContext-DPMm8uYz.js → ToolbarRootContext-C6KxLvnI.js} +1 -1
  42. package/dashboard/dist/assets/{TrendMonitor-CZVE6gID.js → TrendMonitor-BaMNh67w.js} +1 -1
  43. package/dashboard/dist/assets/{WidgetsPage-Bz9cieeZ.js → WidgetsPage-DDsy4M3x.js} +1 -1
  44. package/dashboard/dist/assets/{WrappedPage-CruMolZd.js → WrappedPage-CiNnUFRI.js} +1 -1
  45. package/dashboard/dist/assets/{__vite-browser-external-C3VH6nS4.js → __vite-browser-external-CM-H8-LW.js} +24 -16
  46. package/dashboard/dist/assets/{agent-logos-HIeYwIqN.js → agent-logos-BjVa6oe7.js} +1 -1
  47. package/dashboard/dist/assets/{arrow-up-right-n7Bi4Tvd.js → arrow-up-right-CCum1mqX.js} +1 -1
  48. package/dashboard/dist/assets/{check-C5Q44G5k.js → check-ySRQc1YP.js} +1 -1
  49. package/dashboard/dist/assets/{chevron-right-BsnptUU0.js → chevron-right-5vFloHew.js} +1 -1
  50. package/dashboard/dist/assets/{copy-BGuL9uFa.js → copy-mhfKVMKo.js} +1 -1
  51. package/dashboard/dist/assets/{download-BljgPwR2.js → download-GdrXglSx.js} +1 -1
  52. package/dashboard/dist/assets/{external-link-B4Oz8hUe.js → external-link-KGFaKc8s.js} +1 -1
  53. package/dashboard/dist/assets/{flame-ChLG_NZE.js → flame-BmNtNoS1.js} +1 -1
  54. package/dashboard/dist/assets/{format-tokens-DjuNk13Z.js → format-tokens-CCpCNw3d.js} +1 -1
  55. package/dashboard/dist/assets/{icons-CkO42M-u.js → icons-XLKZavTf.js} +1 -1
  56. package/dashboard/dist/assets/{index-CEsvYBWY.js → index-CsywTeCQ.js} +1 -1
  57. package/dashboard/dist/assets/{index-CGxawuWx.js → index-IKpmeZte.js} +1 -1
  58. package/dashboard/dist/assets/{index-D87oE_rV.js → index-_xU5U5Ct.js} +1 -1
  59. package/dashboard/dist/assets/{info-et8xtJ4v.js → info-DUV5UpmT.js} +1 -1
  60. package/dashboard/dist/assets/{limits-providers-vO_86yRy.js → limits-providers-DFkCZCZj.js} +1 -1
  61. package/dashboard/dist/assets/{link-2-Dge2rBkr.js → link-2-D7c64yLY.js} +1 -1
  62. package/dashboard/dist/assets/{loader-circle-Cs3wpqJe.js → loader-circle-sMONRK2M.js} +1 -1
  63. package/dashboard/dist/assets/{maximize-2-zn71uZX3.js → maximize-2-CVWRgrk9.js} +1 -1
  64. package/dashboard/dist/assets/{provider-display-CTt6mzLe.js → provider-display-DSKqVjgm.js} +1 -1
  65. package/dashboard/dist/assets/{react-BbHDQe6Q.js → react-c1q4mGkS.js} +1 -1
  66. package/dashboard/dist/assets/{search-3CMhatWb.js → search-CwD3s-_C.js} +1 -1
  67. package/dashboard/dist/assets/{skills-api-Cm1aiqWL.js → skills-api-BRXj8kjT.js} +1 -1
  68. package/dashboard/dist/assets/{store-DclXV77t.js → store-lqTW2gfK.js} +1 -1
  69. package/dashboard/dist/assets/{terminal-BC7uFcA7.js → terminal-DU1_KbKo.js} +1 -1
  70. package/dashboard/dist/assets/{trash-2-BT8V531F.js → trash-2-B8Y5Xc-K.js} +1 -1
  71. package/dashboard/dist/assets/{use-community-stats-DJr59SGu.js → use-community-stats-DYwwimvp.js} +1 -1
  72. package/dashboard/dist/assets/{use-limits-display-prefs-DU8OOFVb.js → use-limits-display-prefs-Bst6ybK3.js} +1 -1
  73. package/dashboard/dist/assets/{use-native-settings-BvSwI4BL.js → use-native-settings-Cqle4lgw.js} +1 -1
  74. package/dashboard/dist/assets/{use-ordered-list-Cq5AyV2x.js → use-ordered-list-Dtze0ahb.js} +1 -1
  75. package/dashboard/dist/assets/use-reduced-motion-CbZDR54N.js +1 -0
  76. package/dashboard/dist/assets/{use-session-efficiency-pref-WS8NhXuW.js → use-session-efficiency-pref-gwbDMHXa.js} +1 -1
  77. package/dashboard/dist/assets/{use-transform-CLYsi8xc.js → use-transform-CU0yxXpX.js} +1 -1
  78. package/dashboard/dist/assets/use-usage-limits-5y1I1T5_.js +1 -0
  79. package/dashboard/dist/assets/{useCurrency-D0GtEoJD.js → useCurrency-BfVNrywe.js} +1 -1
  80. package/dashboard/dist/assets/{useScrollLock-TIUBSeIM.js → useScrollLock-CF1m7AMJ.js} +1 -1
  81. package/dashboard/dist/assets/{useTokenFormat-B0TBL9La.js → useTokenFormat-DiRwVvRr.js} +1 -1
  82. package/dashboard/dist/assets/{zap-D89769CL.js → zap-BZtlWtuN.js} +1 -1
  83. package/dashboard/dist/index.html +1 -1
  84. package/dashboard/dist/ip-check.html +1 -1
  85. package/dashboard/dist/leaderboard.html +1 -1
  86. package/dashboard/dist/share.html +1 -1
  87. package/package.json +1 -1
  88. package/src/commands/status.js +114 -9
  89. package/src/commands/sync.js +151 -46
  90. package/src/lib/claude-categorizer.js +36 -9
  91. package/src/lib/diagnostics.js +86 -13
  92. package/src/lib/doctor.js +17 -0
  93. package/src/lib/grok-context-breakdown.js +794 -0
  94. package/src/lib/install-resolver.js +10 -2
  95. package/src/lib/local-api.js +21 -3
  96. package/src/lib/multi-install-parser.js +9 -1
  97. package/src/lib/pricing/seed-snapshot.json +1 -1
  98. package/src/lib/rollout.js +480 -53
  99. package/src/lib/session-analytics.js +332 -9
  100. package/src/lib/usage-limits.js +47 -2
  101. package/dashboard/dist/assets/DashboardPage-CMCqvpkA.js +0 -1
  102. package/dashboard/dist/assets/DialogDescription-Dwxj5h5w.js +0 -1
  103. package/dashboard/dist/assets/LimitsPage-ejlJN1vi.js +0 -4
  104. package/dashboard/dist/assets/use-reduced-motion-D1_q3aRK.js +0 -1
  105. package/dashboard/dist/assets/use-usage-limits-CGoQQDG-.js +0 -1
@@ -24,6 +24,7 @@ const {
24
24
  readQoderDbMessages,
25
25
  resolveKiroDbPath,
26
26
  resolveKiroJsonlPath,
27
+ resolveKiroBasePath,
27
28
  resolveHermesPath,
28
29
  resolveCopilotOtelPaths,
29
30
  normalizeCopilotDbPath,
@@ -45,6 +46,8 @@ const {
45
46
  gooseInstallOwnsCursor,
46
47
  zedInstallOwnsCursor,
47
48
  hermesInstallOwnsCursor,
49
+ kiroInstallOwnsCursor,
50
+ kiroCliInstallOwnsCursor,
48
51
  copilotOtelCursorHasLegacyCliUsage,
49
52
  pruneCopilotUsageClaims,
50
53
  parseCopilotIncremental,
@@ -443,7 +446,25 @@ async function cmdSync(argv, context = {}) {
443
446
  }
444
447
  let grokHookSignalConsumed = false;
445
448
 
446
- const claudeProjectsDir = path.join(home, ".claude", "projects");
449
+ // Claude Code home dual-install aware (#307): a Windows host may carry
450
+ // a native ~/.claude plus a WSL install reachable over \\wsl$. Unlike the
451
+ // SQLite providers, Claude UNIONS every allowed install instead of taking
452
+ // resolveAllWin32Paths' single pick: under the default wsl-first mode a
453
+ // WSL ~/.claude would otherwise silently evict the native one from
454
+ // collection — and from the "disk is truth" ground-truth repair below,
455
+ // which would then erase the native history. The file-hash and
456
+ // message-hash dedup layers make the union safe. Native is listed first
457
+ // so the cross-environment file dedup keeps the native copy as primary.
458
+ const claudeNativeHome = path.join(home, ".claude");
459
+ const wslClaudeHome = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
460
+ ? wsl.discoverWslHome(".claude")
461
+ : null;
462
+ const claudeInstallHomes = [];
463
+ if (process.platform !== "win32" || wsl.shouldProbeNative(process.env)) {
464
+ claudeInstallHomes.push(claudeNativeHome);
465
+ }
466
+ if (wslClaudeHome) claudeInstallHomes.push(wslClaudeHome);
467
+ const claudeProjectsDirs = claudeInstallHomes.map((h) => path.join(h, "projects"));
447
468
  const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
448
469
  const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
449
470
  const mimoHome = process.env.MIMO_HOME || path.join(xdgDataHome, "mimocode");
@@ -751,7 +772,18 @@ async function cmdSync(argv, context = {}) {
751
772
  openclawResult.eventsAggregated += openclawFallback.eventsAggregated;
752
773
  openclawResult.bucketsQueued += openclawFallback.bucketsQueued;
753
774
 
754
- const claudeFiles = sourceAllowed("claude") ? await listClaudeProjectFiles(claudeProjectsDir) : [];
775
+ let claudeFiles = [];
776
+ if (sourceAllowed("claude")) {
777
+ const seenClaudeFiles = new Set();
778
+ for (const dir of claudeProjectsDirs) {
779
+ for (const f of await listClaudeProjectFiles(dir)) {
780
+ if (!seenClaudeFiles.has(f)) {
781
+ seenClaudeFiles.add(f);
782
+ claudeFiles.push(f);
783
+ }
784
+ }
785
+ }
786
+ }
755
787
  if (isFullSourceScan) {
756
788
  await reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath, queueStatePath });
757
789
  await repairClaudeQueueFromGroundTruth({
@@ -760,6 +792,7 @@ async function cmdSync(argv, context = {}) {
760
792
  queueStatePath,
761
793
  projectQueuePath,
762
794
  projectQueueStatePath,
795
+ rootDirs: claudeProjectsDirs,
763
796
  });
764
797
  }
765
798
  let claudeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
@@ -1344,30 +1377,44 @@ async function cmdSync(argv, context = {}) {
1344
1377
  }
1345
1378
  }
1346
1379
 
1347
- // ── Kiro (SQLite-based, with JSONL fallback) ──
1380
+ // ── Kiro (SQLite-based, with JSONL fallback; dual-install aware) ──
1348
1381
  let kiroResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1349
- const kiroDbPath = resolveKiroDbPath();
1350
- const kiroJsonlPath = resolveKiroJsonlPath();
1351
- if (sourceAllowed("kiro") && (fssync.existsSync(kiroDbPath) || fssync.existsSync(kiroJsonlPath))) {
1352
- if (progress?.enabled) {
1353
- progress.start(`Parsing Kiro ${renderBar(0)} | buckets 0`);
1354
- }
1355
- try {
1356
- kiroResult = await parseKiroIncremental({
1357
- dbPath: kiroDbPath,
1358
- jsonlPath: kiroJsonlPath,
1359
- cursors,
1360
- queuePath,
1361
- onProgress: (p) => {
1362
- if (!progress?.enabled) return;
1363
- const pct = p.total > 0 ? p.index / p.total : 1;
1364
- progress.update(
1365
- `Parsing Kiro ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} records | buckets ${formatNumber(p.bucketsQueued)}`,
1366
- );
1367
- },
1368
- });
1369
- } catch (err) {
1370
- warnProviderParseFailure("Kiro", err, opts);
1382
+ if (sourceAllowed("kiro")) {
1383
+ const kiroNativeBase = resolveKiroBasePath(process.env);
1384
+ const wslKiroBase = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
1385
+ ? wsl.discoverWslHome(".config/Kiro/User/globalStorage/kiro.kiroagent")
1386
+ : null;
1387
+ const kiroPaths = resolveInstallPaths({ nativeValue: kiroNativeBase, wslValue: wslKiroBase });
1388
+ // resolveInstallPaths only checks the base dir (and skips the check off
1389
+ // win32); keep the original per-install db/jsonl presence gate so empty
1390
+ // installs never spin up a parse or seed a cursor namespace.
1391
+ const kiroHasData = (base) => Boolean(base)
1392
+ && (fssync.existsSync(resolveKiroDbPath(base)) || fssync.existsSync(resolveKiroJsonlPath(base)));
1393
+ if (!kiroHasData(kiroPaths.native)) kiroPaths.native = null;
1394
+ if (!kiroHasData(kiroPaths.wsl)) kiroPaths.wsl = null;
1395
+ if (kiroPaths.native || kiroPaths.wsl) {
1396
+ if (progress?.enabled) {
1397
+ progress.start(`Parsing Kiro ${renderBar(0)} | buckets 0`);
1398
+ }
1399
+ try {
1400
+ kiroResult = await multiInstallParse({
1401
+ paths: kiroPaths,
1402
+ parserFn: parseKiroIncremental,
1403
+ providerName: "kiro",
1404
+ cursors,
1405
+ getParams: (base) => ({
1406
+ basePath: base,
1407
+ dbPath: resolveKiroDbPath(base),
1408
+ jsonlPath: resolveKiroJsonlPath(base),
1409
+ }),
1410
+ queuePath,
1411
+ onProgress: makeProviderProgress("Kiro"),
1412
+ detectInstall: (base, flatState) =>
1413
+ kiroInstallOwnsCursor(resolveKiroDbPath(base), flatState),
1414
+ });
1415
+ } catch (err) {
1416
+ warnProviderParseFailure("Kiro", err, opts);
1417
+ }
1371
1418
  }
1372
1419
  }
1373
1420
 
@@ -1454,28 +1501,79 @@ async function cmdSync(argv, context = {}) {
1454
1501
  // token counts (billing is credit-based on Bedrock); we approximate at
1455
1502
  // 4 chars/token from user prompt chars and assistant response chars.
1456
1503
  let kiroCliResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1457
- const kiroCliDb = resolveKiroCliDbPath(process.env);
1458
- const kiroCliSessionFiles = sourceAllowed("kiro") ? resolveKiroCliSessionFiles(process.env) : [];
1459
- if (sourceAllowed("kiro") && (fssync.existsSync(kiroCliDb) || kiroCliSessionFiles.length > 0)) {
1460
- if (progress?.enabled) {
1461
- progress.start(`Parsing Kiro CLI ${renderBar(0)} | buckets 0`);
1504
+ if (sourceAllowed("kiro")) {
1505
+ const kiroCliDb = resolveKiroCliDbPath(process.env);
1506
+ const kiroCliSessionFiles = resolveKiroCliSessionFiles(process.env);
1507
+ const nativeCliPresent = fssync.existsSync(kiroCliDb) || kiroCliSessionFiles.length > 0;
1508
+
1509
+ // Explicit overrides pin a single install — never mix them with WSL
1510
+ // auto-discovery (mirrors the Hermes TOKENTRACKER_HERMES_HOME branch).
1511
+ const kiroCliOverride = Boolean(process.env.KIRO_CLI_DB_PATH || process.env.KIRO_HOME);
1512
+ let wslKiroCliEnv = null;
1513
+ let wslKiroCliMarker = null;
1514
+ if (!kiroCliOverride && process.platform === "win32" && wsl.shouldProbeWsl(process.env)) {
1515
+ // A WSL install owns BOTH a data dir (~/.local/share/kiro-cli) and a
1516
+ // sessions home (~/.kiro). Derive both from whichever probe hits so
1517
+ // the per-install env never falls back to native paths.
1518
+ const wslKiroHomeDir = wsl.discoverWslHome(".kiro");
1519
+ const wslCliDataDir = wsl.discoverWslHome(".local/share/kiro-cli");
1520
+ const wslHomeRoot = wslKiroHomeDir
1521
+ ? path.dirname(wslKiroHomeDir)
1522
+ : (wslCliDataDir ? path.dirname(path.dirname(path.dirname(wslCliDataDir))) : null);
1523
+ if (wslHomeRoot) {
1524
+ const wslCliDb = path.join(wslHomeRoot, ".local", "share", "kiro-cli", "data.sqlite3");
1525
+ wslKiroCliEnv = {
1526
+ ...process.env,
1527
+ KIRO_CLI_DB_PATH: wslCliDb,
1528
+ KIRO_HOME: path.join(wslHomeRoot, ".kiro"),
1529
+ };
1530
+ const wslCliPresent = fssync.existsSync(wslCliDb)
1531
+ || resolveKiroCliSessionFiles(wslKiroCliEnv).length > 0;
1532
+ if (wslCliPresent) wslKiroCliMarker = wslCliDb;
1533
+ }
1462
1534
  }
1463
- try {
1464
- kiroCliResult = await parseKiroCliIncremental({
1465
- cursors,
1466
- queuePath,
1535
+
1536
+ // Paths here are install markers only — the parser resolves its DB and
1537
+ // session files from the per-install env (KIRO_CLI_DB_PATH/KIRO_HOME).
1538
+ const kiroCliPaths = process.platform === "win32"
1539
+ ? wsl.resolveAllWin32Paths({
1540
+ nativeValue: nativeCliPresent ? kiroCliDb : null,
1541
+ wslValue: wslKiroCliMarker,
1467
1542
  env: process.env,
1468
- onProgress: (p) => {
1469
- if (!progress?.enabled) return;
1470
- const pct = p.total > 0 ? p.index / p.total : 1;
1471
- progress.update(
1472
- `Parsing Kiro CLI ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
1473
- );
1474
- },
1475
- });
1476
- } catch (err) {
1477
- if (!opts.auto) {
1478
- process.stderr.write(`Kiro CLI sync: ${err.message}\n`);
1543
+ platform: "win32",
1544
+ })
1545
+ : { native: nativeCliPresent ? kiroCliDb : null, wsl: null };
1546
+ const kiroCliEnvFor = (p) =>
1547
+ wslKiroCliEnv && p === wslKiroCliMarker ? wslKiroCliEnv : process.env;
1548
+ if (kiroCliPaths.native || kiroCliPaths.wsl) {
1549
+ if (progress?.enabled) {
1550
+ progress.start(`Parsing Kiro CLI ${renderBar(0)} | buckets 0`);
1551
+ }
1552
+ try {
1553
+ kiroCliResult = await multiInstallParse({
1554
+ paths: kiroCliPaths,
1555
+ parserFn: parseKiroCliIncremental,
1556
+ providerName: "kiroCli",
1557
+ cursors,
1558
+ // Per-install env MUST come from getParams: multiInstallParse
1559
+ // spreads shared params over it, so a top-level env would clobber
1560
+ // the per-install one.
1561
+ getParams: (p) => ({ env: kiroCliEnvFor(p) }),
1562
+ queuePath,
1563
+ onProgress: (p) => {
1564
+ if (!progress?.enabled) return;
1565
+ const pct = p.total > 0 ? p.index / p.total : 1;
1566
+ progress.update(
1567
+ `Parsing Kiro CLI ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
1568
+ );
1569
+ },
1570
+ detectInstall: (p, flatState) =>
1571
+ kiroCliInstallOwnsCursor(kiroCliEnvFor(p).KIRO_CLI_DB_PATH || kiroCliDb, flatState),
1572
+ });
1573
+ } catch (err) {
1574
+ if (!opts.auto) {
1575
+ process.stderr.write(`Kiro CLI sync: ${err.message}\n`);
1576
+ }
1479
1577
  }
1480
1578
  }
1481
1579
  }
@@ -4688,6 +4786,7 @@ async function repairClaudeQueueFromGroundTruth({
4688
4786
  queueStatePath = null,
4689
4787
  projectQueuePath = null,
4690
4788
  projectQueueStatePath = null,
4789
+ rootDirs = null,
4691
4790
  }) {
4692
4791
  if (!cursors || typeof cursors !== "object") return false;
4693
4792
  const migrations = (cursors.migrations ||= {});
@@ -4695,7 +4794,13 @@ async function repairClaudeQueueFromGroundTruth({
4695
4794
 
4696
4795
  let result;
4697
4796
  try {
4698
- result = await computeClaudeGroundTruthBuckets();
4797
+ // rootDirs must cover every install the incremental parser scans (native
4798
+ // AND WSL): this repair's semantics are "disk is truth" — it clears all
4799
+ // claude buckets and replaces claudeHashes wholesale, so scanning fewer
4800
+ // roots than sync would silently drop the missing install's history.
4801
+ result = await computeClaudeGroundTruthBuckets(
4802
+ Array.isArray(rootDirs) && rootDirs.length > 0 ? { rootDirs } : {},
4803
+ );
4699
4804
  } catch (e) {
4700
4805
  console.error("[sync] claude ground-truth repair: scan failed:", e?.message || e);
4701
4806
  return false;
@@ -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