tokentracker-cli 0.77.1 → 0.78.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 (83) hide show
  1. package/README.de.md +24 -3
  2. package/README.ja.md +3 -3
  3. package/README.ko.md +3 -3
  4. package/README.md +24 -3
  5. package/README.zh-CN.md +3 -3
  6. package/dashboard/dist/assets/ActivityHeatmap-5352XxLR.js +42 -0
  7. package/dashboard/dist/assets/{Card-DP2HT77t.js → Card-Ca_hYHhx.js} +1 -1
  8. package/dashboard/dist/assets/{ClawdAnimated-ScYRJSG0.js → ClawdAnimated-DSgOS5ND.js} +1 -1
  9. package/dashboard/dist/assets/{CommunityStatsModal-CU46JMkg.js → CommunityStatsModal-o8uQVQxu.js} +1 -1
  10. package/dashboard/dist/assets/ConfirmModal-D1hi9XUf.js +1 -0
  11. package/dashboard/dist/assets/Controls-CwnKP56V.js +1 -0
  12. package/dashboard/dist/assets/DashboardPage-CDv0TIOL.js +19 -0
  13. package/dashboard/dist/assets/{DevicePage-BL9ew9-m.js → DevicePage-BlfIHcZX.js} +1 -1
  14. package/dashboard/dist/assets/DialogClose-CismfYwe.js +1 -0
  15. package/dashboard/dist/assets/DialogDescription-DWKp1yez.js +1 -0
  16. package/dashboard/dist/assets/{DialogTitle-Oqnn_dIL.js → DialogTitle-DuuTVbUb.js} +1 -1
  17. package/dashboard/dist/assets/{FadeIn-CFLwnHqt.js → FadeIn-DpnitRKo.js} +1 -1
  18. package/dashboard/dist/assets/{HeaderGithubStar-B397ELF4.js → HeaderGithubStar-D6zy-ADl.js} +1 -1
  19. package/dashboard/dist/assets/{IpCheckPage-CamsHe6o.js → IpCheckPage-yUNp0VNe.js} +1 -1
  20. package/dashboard/dist/assets/LandingPage-BvmpAaVn.js +4314 -0
  21. package/dashboard/dist/assets/LeaderboardAvatar-2WTIeXye.js +1 -0
  22. package/dashboard/dist/assets/LeaderboardPage-Bq3-VO7w.js +2 -0
  23. package/dashboard/dist/assets/{LeaderboardProfileModal-BIe6gBNu.js → LeaderboardProfileModal-DH0EEK6q.js} +3 -3
  24. package/dashboard/dist/assets/{LeaderboardProfilePage-DeVBEYtg.js → LeaderboardProfilePage-uJAng2JE.js} +3 -3
  25. package/dashboard/dist/assets/{LimitsPage-6I5EU7qe.js → LimitsPage-D8UohgJm.js} +2 -2
  26. package/dashboard/dist/assets/{LocalOnlyNotice-AyYKZloH.js → LocalOnlyNotice-Dv_BnDVV.js} +1 -1
  27. package/dashboard/dist/assets/{LoginPage-D8smF1gU.js → LoginPage-D6b6DZU3.js} +1 -1
  28. package/dashboard/dist/assets/PetPage-DfpBkuCa.js +1 -0
  29. package/dashboard/dist/assets/{PopoverPopup-DRWhz-bY.js → PopoverPopup-CjzdZn-9.js} +1 -1
  30. package/dashboard/dist/assets/{ResetPasswordPage-CzSqJfuA.js → ResetPasswordPage-BnOCFkwe.js} +1 -1
  31. package/dashboard/dist/assets/Select-CHtQAiLD.js +1 -0
  32. package/dashboard/dist/assets/{SelectItemText-DEYadcIK.js → SelectItemText-Vjqvf8wO.js} +1 -1
  33. package/dashboard/dist/assets/SettingsPage-B-iHGL4q.js +1 -0
  34. package/dashboard/dist/assets/SkillsPage-DtG2aV-q.js +1 -0
  35. package/dashboard/dist/assets/WidgetsPage-4kqTtWfK.js +1 -0
  36. package/dashboard/dist/assets/{WrappedPage-BuP9KXqF.js → WrappedPage-DMMRpani.js} +1 -1
  37. package/dashboard/dist/assets/agent-logos-D0vYkEpX.js +1 -0
  38. package/dashboard/dist/assets/{arrow-up-right-cFMBPEVS.js → arrow-up-right-BAQYGZkR.js} +1 -1
  39. package/dashboard/dist/assets/check-C3RPBZiS.js +1 -0
  40. package/dashboard/dist/assets/{copy-DYx8X3ZO.js → copy-gG_BLBs8.js} +1 -1
  41. package/dashboard/dist/assets/{download-B-hXZWKj.js → download-BQOcg3gT.js} +1 -1
  42. package/dashboard/dist/assets/{format-tokens-CngV51wN.js → format-tokens-CGK6lQAf.js} +1 -1
  43. package/dashboard/dist/assets/{info-qVF2hV50.js → info-C_BrDrcq.js} +1 -1
  44. package/dashboard/dist/assets/{link-2-DR-yGZFS.js → link-2-DFwCBbIZ.js} +1 -1
  45. package/dashboard/dist/assets/{loader-circle-0k9uueb5.js → loader-circle-BMFI_BJ8.js} +1 -1
  46. package/dashboard/dist/assets/{main-B8lT0e78.css → main-Dy8Y_qst.css} +1 -1
  47. package/dashboard/dist/assets/main-w0hAaCsy.js +1190 -0
  48. package/dashboard/dist/assets/{trash-2-DpxBaV_1.js → trash-2-GeZ6IzgV.js} +1 -1
  49. package/dashboard/dist/assets/{use-community-stats-Cbij-V8j.js → use-community-stats-pC8cmVof.js} +1 -1
  50. package/dashboard/dist/assets/{use-limits-display-prefs-q0TuryM0.js → use-limits-display-prefs-DnFpPcA3.js} +1 -1
  51. package/dashboard/dist/assets/use-native-settings-CvcOSUhn.js +1 -0
  52. package/dashboard/dist/assets/{use-ordered-list-CH-J7c04.js → use-ordered-list-CbMroVZ8.js} +2 -2
  53. package/dashboard/dist/assets/{use-quality-per-dollar-pref-DKLmkwxe.js → use-quality-per-dollar-pref-Bn3Al6yX.js} +1 -1
  54. package/dashboard/dist/assets/{use-usage-limits-DrmF6I_t.js → use-usage-limits-D08q_Dr8.js} +1 -1
  55. package/dashboard/dist/assets/{useCurrency-DwygZqrs.js → useCurrency-h3kqF52v.js} +1 -1
  56. package/dashboard/dist/assets/{useScrollLock-DVCKuErg.js → useScrollLock-9au2731u.js} +1 -1
  57. package/dashboard/dist/index.html +2 -2
  58. package/dashboard/dist/share.html +2 -2
  59. package/package.json +1 -1
  60. package/src/commands/status.js +293 -30
  61. package/src/commands/sync.js +494 -104
  62. package/src/lib/install-resolver.js +1 -0
  63. package/src/lib/local-api.js +202 -56
  64. package/src/lib/pricing/seed-snapshot.json +1 -1
  65. package/src/lib/rollout.js +1329 -40
  66. package/dashboard/dist/assets/ActivityHeatmap-DTZh551Z.js +0 -42
  67. package/dashboard/dist/assets/ConfirmModal-BYvz0dzg.js +0 -1
  68. package/dashboard/dist/assets/Controls-DPElSq0S.js +0 -1
  69. package/dashboard/dist/assets/DashboardPage-CN0buIso.js +0 -19
  70. package/dashboard/dist/assets/DialogClose-cqXrG903.js +0 -1
  71. package/dashboard/dist/assets/DialogDescription-Bo0iWtYz.js +0 -1
  72. package/dashboard/dist/assets/LandingPage-D4ygTHwO.js +0 -4312
  73. package/dashboard/dist/assets/LeaderboardAvatar-B39rNGCV.js +0 -1
  74. package/dashboard/dist/assets/LeaderboardPage-BFUOxX6P.js +0 -2
  75. package/dashboard/dist/assets/PetPage-BlMYvJn2.js +0 -1
  76. package/dashboard/dist/assets/Select-Cb8n7kQO.js +0 -1
  77. package/dashboard/dist/assets/SettingsPage-DlHXcFdJ.js +0 -1
  78. package/dashboard/dist/assets/SkillsPage--Kcv1pxG.js +0 -1
  79. package/dashboard/dist/assets/WidgetsPage-DwtJ9CKU.js +0 -1
  80. package/dashboard/dist/assets/agent-logos-DbwDXgg1.js +0 -1
  81. package/dashboard/dist/assets/check-t_ltRp2A.js +0 -1
  82. package/dashboard/dist/assets/main-CU04bNYH.js +0 -1172
  83. package/dashboard/dist/assets/use-native-settings-BCzogs1L.js +0 -1
@@ -24,6 +24,11 @@ const {
24
24
  resolveKiroJsonlPath,
25
25
  resolveHermesPath,
26
26
  resolveCopilotOtelPaths,
27
+ normalizeCopilotDbPath,
28
+ uniqueCopilotDbPaths,
29
+ coalesceCopilotDbStatesByIdentity,
30
+ resolveCopilotSessionStorePaths,
31
+ getCopilotSqliteFingerprint,
27
32
  resolveCopilotAppDbPaths,
28
33
  parseRolloutIncremental,
29
34
  parseClaudeIncremental,
@@ -37,7 +42,10 @@ const {
37
42
  gooseInstallOwnsCursor,
38
43
  zedInstallOwnsCursor,
39
44
  hermesInstallOwnsCursor,
45
+ copilotOtelCursorHasLegacyCliUsage,
46
+ pruneCopilotUsageClaims,
40
47
  parseCopilotIncremental,
48
+ parseCopilotSessionStoreIncremental,
41
49
  parseCopilotAppDbIncremental,
42
50
  resolveKimiWireFiles,
43
51
  parseKimiIncremental,
@@ -218,6 +226,50 @@ function warnProviderParseFailure(label, err, opts) {
218
226
  process.stderr.write(`${label} sync: ${err && err.message ? err.message : err}\n`);
219
227
  }
220
228
 
229
+ function mergeParseResult(total, next) {
230
+ return {
231
+ recordsProcessed: total.recordsProcessed + next.recordsProcessed,
232
+ eventsAggregated: total.eventsAggregated + next.eventsAggregated,
233
+ bucketsQueued: total.bucketsQueued + next.bucketsQueued,
234
+ };
235
+ }
236
+
237
+ function copilotAppBaselineTotal(baseline) {
238
+ return (
239
+ Math.max(0, Number(baseline?.input) || 0) +
240
+ Math.max(0, Number(baseline?.output) || 0) +
241
+ Math.max(0, Number(baseline?.cached) || 0) +
242
+ Math.max(0, Number(baseline?.reasoning) || 0)
243
+ );
244
+ }
245
+
246
+ function mergeCopilotAppDbStates(primary = {}, alias = {}) {
247
+ const primaryUpdatedAt = Date.parse(primary?.updatedAt || "") || 0;
248
+ const aliasUpdatedAt = Date.parse(alias?.updatedAt || "") || 0;
249
+ const newer = aliasUpdatedAt > primaryUpdatedAt ? alias : primary;
250
+ const sessionTotals = {};
251
+ for (const state of [primary, alias]) {
252
+ for (const [sessionId, baseline] of Object.entries(
253
+ state?.sessionTotals || {},
254
+ )) {
255
+ const current = sessionTotals[sessionId];
256
+ const baselineTotal = copilotAppBaselineTotal(baseline);
257
+ const currentTotal = copilotAppBaselineTotal(current);
258
+ const baselineUpdatedAt = Date.parse(baseline?.updatedAt || "") || 0;
259
+ const currentUpdatedAt = Date.parse(current?.updatedAt || "") || 0;
260
+ if (
261
+ !current ||
262
+ baselineTotal > currentTotal ||
263
+ (baselineTotal === currentTotal &&
264
+ baselineUpdatedAt > currentUpdatedAt)
265
+ ) {
266
+ sessionTotals[sessionId] = baseline;
267
+ }
268
+ }
269
+ }
270
+ return { ...primary, ...newer, sessionTotals };
271
+ }
272
+
221
273
  async function cmdSync(argv) {
222
274
  const opts = parseArgs(argv);
223
275
  const home = os.homedir();
@@ -261,14 +313,8 @@ async function cmdSync(argv) {
261
313
  }
262
314
  let grokHookSignalConsumed = false;
263
315
 
264
- const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
265
- const codeHome = process.env.CODE_HOME || path.join(home, ".code");
266
316
  const claudeProjectsDir = path.join(home, ".claude", "projects");
267
- const geminiHome = process.env.GEMINI_HOME || path.join(home, ".gemini");
268
- const geminiTmpDir = path.join(geminiHome, "tmp");
269
317
  const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
270
- const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
271
- const opencodeStorageDir = path.join(opencodeHome, "storage");
272
318
  const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
273
319
  const mimoHome = process.env.MIMO_HOME || path.join(xdgDataHome, "mimocode");
274
320
  const zcodeHome = process.env.ZCODE_HOME || path.join(home, ".zcode");
@@ -295,22 +341,36 @@ async function cmdSync(argv) {
295
341
 
296
342
  const sources = [];
297
343
  if (sourceAllowed("codex")) {
298
- sources.push({ source: "codex", sessionsDir: path.join(codexHome, "sessions"), codexInventoryCache: true });
299
- if (!isBackgroundLightweightSync) {
300
- sources.push(
301
- // Codex-Manager archives sessions to ~/.codex/archived_sessions/ on every
302
- // account/channel switch (issue #187). Scanning it too keeps that usage
303
- // counted instead of orphaning it in the cloud (a re-upload's upsert can
304
- // never delete cloud rows whose local source file has vanished). Safe
305
- // against double-counting: the codex event dedup keys on sessionUUID (in
306
- // the filename) + event timestamp, both stable across a sessions/ ->
307
- // archived_sessions/ move, so re-reading an archived copy is a no-op.
308
- { source: "codex", sessionsDir: path.join(codexHome, "archived_sessions"), deep: true },
309
- );
344
+ const codexNativeValue = process.env.CODEX_HOME || path.join(home, ".codex");
345
+ const wslCodexDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
346
+ ? wsl.discoverWslHome(".codex")
347
+ : null;
348
+ const codexPaths = resolveInstallPaths({ nativeValue: codexNativeValue, wslValue: wslCodexDir });
349
+ if (codexPaths.native) {
350
+ sources.push({ source: "codex", sessionsDir: path.join(codexPaths.native, "sessions"), codexInventoryCache: true });
351
+ if (!isBackgroundLightweightSync) {
352
+ sources.push({ source: "codex", sessionsDir: path.join(codexPaths.native, "archived_sessions"), deep: true });
353
+ }
354
+ }
355
+ if (codexPaths.wsl) {
356
+ sources.push({ source: "codex", sessionsDir: path.join(codexPaths.wsl, "sessions"), codexInventoryCache: true });
357
+ if (!isBackgroundLightweightSync) {
358
+ sources.push({ source: "codex", sessionsDir: path.join(codexPaths.wsl, "archived_sessions"), deep: true });
359
+ }
310
360
  }
311
361
  }
312
362
  if (sourceAllowed("every-code")) {
313
- sources.push({ source: "every-code", sessionsDir: path.join(codeHome, "sessions") });
363
+ const codeNativeValue = process.env.CODE_HOME || path.join(home, ".code");
364
+ const wslCodeDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
365
+ ? wsl.discoverWslHome(".code")
366
+ : null;
367
+ const codePaths = resolveInstallPaths({ nativeValue: codeNativeValue, wslValue: wslCodeDir });
368
+ if (codePaths.native) {
369
+ sources.push({ source: "every-code", sessionsDir: path.join(codePaths.native, "sessions") });
370
+ }
371
+ if (codePaths.wsl) {
372
+ sources.push({ source: "every-code", sessionsDir: path.join(codePaths.wsl, "sessions") });
373
+ }
314
374
  }
315
375
 
316
376
  const rolloutFiles = [];
@@ -480,7 +540,34 @@ async function cmdSync(argv) {
480
540
  }
481
541
  }
482
542
 
483
- const geminiFiles = sourceAllowed("gemini") ? await listGeminiSessionFiles(geminiTmpDir) : [];
543
+ let geminiPaths = null;
544
+ if (sourceAllowed("gemini") || sourceAllowed("antigravity")) {
545
+ const geminiNativeValue = process.env.GEMINI_HOME || path.join(home, ".gemini");
546
+ const wslGeminiDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
547
+ ? wsl.discoverWslHome(".gemini")
548
+ : null;
549
+ geminiPaths = resolveInstallPaths({ nativeValue: geminiNativeValue, wslValue: wslGeminiDir });
550
+ }
551
+
552
+ let geminiFiles = [];
553
+ if (sourceAllowed("gemini") && geminiPaths) {
554
+ const fileSets = [];
555
+ if (geminiPaths.native) {
556
+ fileSets.push(await listGeminiSessionFiles(path.join(geminiPaths.native, "tmp")));
557
+ }
558
+ if (geminiPaths.wsl) {
559
+ fileSets.push(await listGeminiSessionFiles(path.join(geminiPaths.wsl, "tmp")));
560
+ }
561
+ const seen = new Set();
562
+ for (const set of fileSets) {
563
+ for (const f of set) {
564
+ if (!seen.has(f)) {
565
+ seen.add(f);
566
+ geminiFiles.push(f);
567
+ }
568
+ }
569
+ }
570
+ }
484
571
  let geminiResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
485
572
  if (geminiFiles.length > 0) {
486
573
  if (progress?.enabled) {
@@ -510,7 +597,25 @@ async function cmdSync(argv) {
510
597
  }
511
598
  }
512
599
 
513
- const antigravityFiles = sourceAllowed("antigravity") ? await listAntigravityTranscripts(geminiHome) : [];
600
+ let antigravityFiles = [];
601
+ if (sourceAllowed("antigravity") && geminiPaths) {
602
+ const fileSets = [];
603
+ if (geminiPaths.native) {
604
+ fileSets.push(await listAntigravityTranscripts(geminiPaths.native));
605
+ }
606
+ if (geminiPaths.wsl) {
607
+ fileSets.push(await listAntigravityTranscripts(geminiPaths.wsl));
608
+ }
609
+ const seen = new Set();
610
+ for (const set of fileSets) {
611
+ for (const f of set) {
612
+ if (!seen.has(f)) {
613
+ seen.add(f);
614
+ antigravityFiles.push(f);
615
+ }
616
+ }
617
+ }
618
+ }
514
619
  let antigravityResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
515
620
  if (antigravityFiles.length > 0) {
516
621
  if (progress?.enabled) {
@@ -540,69 +645,89 @@ async function cmdSync(argv) {
540
645
  }
541
646
  }
542
647
 
543
- const opencodeFiles = sourceAllowed("opencode") ? await listOpencodeMessageFiles(opencodeStorageDir) : [];
544
648
  let opencodeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
545
- if (opencodeFiles.length > 0) {
546
- if (progress?.enabled) {
547
- progress.start(
548
- `Parsing Opencode ${renderBar(0)} 0/${formatNumber(opencodeFiles.length)} files | buckets 0`,
549
- );
550
- }
551
- try {
552
- opencodeResult = await parseOpencodeIncremental({
553
- messageFiles: opencodeFiles,
554
- cursors,
555
- queuePath,
556
- projectQueuePath,
557
- onProgress: (p) => {
558
- if (!progress?.enabled) return;
559
- const pct = p.total > 0 ? p.index / p.total : 1;
560
- progress.update(
561
- `Parsing Opencode ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
562
- p.total,
563
- )} files | buckets ${formatNumber(p.bucketsQueued)}`,
564
- );
565
- },
566
- source: "opencode",
567
- });
568
- } catch (err) {
569
- warnProviderParseFailure("Opencode", err, opts);
570
- }
571
- }
649
+ if (sourceAllowed("opencode")) {
650
+ const opencodeStorageNativeValue = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
651
+ const wslOpencodeStorageDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
652
+ ? wsl.discoverWslHome(".local/share/opencode")
653
+ : null;
654
+ const storagePaths = resolveInstallPaths({
655
+ nativeValue: opencodeStorageNativeValue,
656
+ wslValue: wslOpencodeStorageDir,
657
+ });
572
658
 
573
- // OpenCode v1.2+ stores messages in SQLite (opencode.db) instead of JSON files.
574
- const opencodeDbPath = path.join(opencodeHome, "opencode.db");
575
- let opencodeDbResult = { messagesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
576
- const dbMessages = sourceAllowed("opencode") ? readOpencodeDbMessages(opencodeDbPath) : [];
577
- if (dbMessages.length > 0) {
578
- if (progress?.enabled) {
579
- progress.start(
580
- `Parsing Opencode DB ${renderBar(0)} 0/${formatNumber(dbMessages.length)} msgs | buckets 0`,
581
- );
582
- }
583
- try {
584
- opencodeDbResult = await parseOpencodeDbIncremental({
585
- dbMessages,
586
- cursors,
587
- queuePath,
588
- projectQueuePath,
589
- onProgress: (p) => {
590
- if (!progress?.enabled) return;
591
- const pct = p.total > 0 ? p.index / p.total : 1;
592
- progress.update(
593
- `Parsing Opencode DB ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
594
- p.total,
595
- )} msgs | buckets ${formatNumber(p.bucketsQueued)}`,
596
- );
597
- },
598
- source: "opencode",
599
- });
600
- } catch (err) {
601
- warnProviderParseFailure("Opencode DB", err, opts);
602
- }
603
- opencodeResult.filesProcessed += opencodeDbResult.messagesProcessed;
604
- opencodeResult.eventsAggregated += opencodeDbResult.eventsAggregated;
605
- opencodeResult.bucketsQueued += opencodeDbResult.bucketsQueued;
659
+ const opencodeDbNativeValue = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
660
+ const wslOpencodeDbDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
661
+ ? wsl.discoverWslHome(".local/share/opencode")
662
+ : null;
663
+ const dbPaths = resolveInstallPaths({
664
+ nativeValue: opencodeDbNativeValue,
665
+ wslValue: wslOpencodeDbDir,
666
+ });
667
+
668
+ const parseOpencodeForInstall = async (options) => {
669
+ const { storageDir, dbDir, cursors } = options;
670
+ let filesResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
671
+ if (storageDir) {
672
+ const storagePath = path.join(storageDir, "storage");
673
+ const messageFiles = await listOpencodeMessageFiles(storagePath);
674
+ if (messageFiles.length > 0) {
675
+ filesResult = await parseOpencodeIncremental({
676
+ ...options,
677
+ messageFiles,
678
+ });
679
+ }
680
+ }
681
+
682
+ let dbResult = { messagesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
683
+ if (dbDir) {
684
+ const dbPath = path.join(dbDir, "opencode.db");
685
+ const dbMessages = readOpencodeDbMessages(dbPath);
686
+ if (dbMessages.length > 0) {
687
+ dbResult = await parseOpencodeDbIncremental({
688
+ ...options,
689
+ dbMessages,
690
+ });
691
+ }
692
+ }
693
+
694
+ return {
695
+ recordsProcessed: filesResult.filesProcessed + dbResult.messagesProcessed,
696
+ eventsAggregated: filesResult.eventsAggregated + dbResult.eventsAggregated,
697
+ bucketsQueued: filesResult.bucketsQueued + dbResult.bucketsQueued,
698
+ };
699
+ };
700
+
701
+ const opencodePaths = {
702
+ native: storagePaths.native || dbPaths.native,
703
+ wsl: storagePaths.wsl || dbPaths.wsl,
704
+ };
705
+
706
+ const multiResult = await multiInstallParse({
707
+ paths: opencodePaths,
708
+ parserFn: parseOpencodeForInstall,
709
+ providerName: "opencode",
710
+ cursors,
711
+ queuePath,
712
+ projectQueuePath,
713
+ getParams: (p, key) => ({ storageDir: storagePaths[key], dbDir: dbPaths[key] }),
714
+ onProgress: (p) => {
715
+ if (!progress?.enabled) return;
716
+ const pct = p.total > 0 ? p.index / p.total : 1;
717
+ progress.update(
718
+ `Parsing Opencode (${p.install || "default"}) ${renderBar(pct)} ${formatNumber(
719
+ p.index,
720
+ )}/${formatNumber(p.total)} | buckets ${formatNumber(p.bucketsQueued)}`,
721
+ );
722
+ },
723
+ source: "opencode",
724
+ });
725
+
726
+ opencodeResult = {
727
+ filesProcessed: multiResult.recordsProcessed,
728
+ eventsAggregated: multiResult.eventsAggregated,
729
+ bucketsQueued: multiResult.bucketsQueued,
730
+ };
606
731
  }
607
732
 
608
733
  async function parseOpencodeDbForInstall({ dbPath, readFn, source, cursorKey, ...rest }) {
@@ -1045,7 +1170,9 @@ async function cmdSync(argv) {
1045
1170
 
1046
1171
  // ── Kimi (passive wire.jsonl reader) ──
1047
1172
  let kimiResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1048
- const kimiWireFiles = sourceAllowed("kimi") ? resolveKimiWireFiles(process.env) : [];
1173
+ const kimiWireFiles = sourceAllowed("kimi")
1174
+ ? mergeBothFileSources({ resolveFiles: resolveKimiWireFiles, env: process.env })
1175
+ : [];
1049
1176
  if (kimiWireFiles.length > 0) {
1050
1177
  if (progress?.enabled) {
1051
1178
  progress.start(`Parsing Kimi Code ${renderBar(0)} | buckets 0`);
@@ -1326,19 +1453,135 @@ async function cmdSync(argv) {
1326
1453
  await repairGrokQueueFromSessionSnapshots({ cursors, queuePath, queueStatePath });
1327
1454
  }
1328
1455
 
1329
- // ── GitHub Copilot CLI / Chat extension (OTEL JSONL files) ──
1456
+ // ── GitHub Copilot App / CLI unified local runtime ──
1457
+ //
1458
+ // First adoption deliberately runs legacy OTEL + App DB once before the
1459
+ // session-store parser records its max-id barrier. Subsequent scans use
1460
+ // session-store as canonical for CLI/App requests: OTEL keeps only Chat
1461
+ // extension LogRecords, while App DB remains an observe-only baseline.
1330
1462
  let copilotResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1331
- const copilotPaths = sourceAllowed("copilot") ? resolveCopilotOtelPaths(process.env) : [];
1463
+ const copilotSourceAllowed = sourceAllowed("copilot");
1464
+ const adoptedCopilotStorePaths = new Set(
1465
+ Object.entries(cursors?.copilotStore?.dbs || {})
1466
+ .filter(([, state]) => state?.adoptedAt)
1467
+ .map(([dbPath]) => dbPath),
1468
+ );
1469
+ const copilotStorePaths = copilotSourceAllowed
1470
+ ? Array.from(
1471
+ new Set([
1472
+ ...resolveCopilotSessionStorePaths(process.env),
1473
+ ...adoptedCopilotStorePaths,
1474
+ ]),
1475
+ ).filter((dbPath) => {
1476
+ if (adoptedCopilotStorePaths.has(dbPath)) return true;
1477
+ try { return fssync.existsSync(dbPath); } catch (_e) { return false; }
1478
+ })
1479
+ : [];
1480
+ const copilotStoreWasActive = cursors?.copilotStore?.active === true;
1481
+ const copilotStoreAdoptionFingerprints = {};
1482
+ if (!copilotStoreWasActive) {
1483
+ for (const dbPath of copilotStorePaths) {
1484
+ try {
1485
+ copilotStoreAdoptionFingerprints[dbPath] =
1486
+ getCopilotSqliteFingerprint(dbPath);
1487
+ } catch (_e) {}
1488
+ }
1489
+ }
1490
+ let copilotHadLegacyCliOtelHistory = false;
1491
+ if (!copilotStoreWasActive) {
1492
+ copilotHadLegacyCliOtelHistory =
1493
+ await copilotOtelCursorHasLegacyCliUsage(cursors);
1494
+ if (cursors?.copilot && typeof cursors.copilot === "object") {
1495
+ if (copilotHadLegacyCliOtelHistory) {
1496
+ cursors.copilot.legacyCliHistory = true;
1497
+ } else {
1498
+ cursors.copilot.usageClaimsComplete = true;
1499
+ }
1500
+ }
1501
+ }
1502
+ let copilotLegacyHealthy = true;
1503
+ let copilotOtelUsageClaims =
1504
+ cursors?.copilot?.recentUsageEvents || [];
1505
+ let copilotStoreResult = {
1506
+ active: copilotStoreWasActive,
1507
+ healthy: false,
1508
+ adoptedThisRun: false,
1509
+ canonicalDbPaths: [],
1510
+ recordsProcessed: 0,
1511
+ eventsAggregated: 0,
1512
+ bucketsQueued: 0,
1513
+ usageClaims: cursors?.copilotStore?.recentEvents || [],
1514
+ };
1515
+ if (copilotSourceAllowed && copilotStoreWasActive) {
1516
+ if (progress?.enabled) {
1517
+ progress.start(`Parsing Copilot App/CLI ${renderBar(0)} | buckets 0`);
1518
+ }
1519
+ try {
1520
+ copilotStoreResult = await parseCopilotSessionStoreIncremental({
1521
+ dbPaths: copilotStorePaths,
1522
+ cursors,
1523
+ queuePath,
1524
+ env: process.env,
1525
+ otelUsageEvents: cursors?.copilot?.recentUsageEvents,
1526
+ onProgress: (p) => {
1527
+ if (!progress?.enabled) return;
1528
+ const pct = p.total > 0 ? p.index / p.total : 1;
1529
+ progress.update(
1530
+ `Parsing Copilot App/CLI ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} events | buckets ${formatNumber(p.bucketsQueued)}`,
1531
+ );
1532
+ },
1533
+ });
1534
+ copilotResult = mergeParseResult(copilotResult, copilotStoreResult);
1535
+ } catch (err) {
1536
+ warnProviderParseFailure("Copilot App/CLI session store", err, opts);
1537
+ }
1538
+ }
1539
+ const copilotPathKey = (dbPath) => {
1540
+ const normalized = path.normalize(dbPath);
1541
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1542
+ };
1543
+ const canonicalCopilotStorePaths = new Set(
1544
+ (copilotStoreResult.canonicalDbPaths || []).map(copilotPathKey),
1545
+ );
1546
+ const adoptedCopilotStorePathKeys = new Set(
1547
+ Object.entries(cursors?.copilotStore?.dbs || {})
1548
+ .filter(([, state]) => state?.adoptedAt)
1549
+ .map(([dbPath]) => copilotPathKey(dbPath)),
1550
+ );
1551
+ const explicitCopilotStorePath =
1552
+ typeof process.env.TOKENTRACKER_COPILOT_SESSION_STORE_DB === "string" &&
1553
+ process.env.TOKENTRACKER_COPILOT_SESSION_STORE_DB.trim()
1554
+ ? copilotPathKey(
1555
+ normalizeCopilotDbPath(
1556
+ process.env.TOKENTRACKER_COPILOT_SESSION_STORE_DB,
1557
+ process.env,
1558
+ ),
1559
+ )
1560
+ : null;
1561
+ const explicitStoreIsCanonical =
1562
+ explicitCopilotStorePath !== null &&
1563
+ canonicalCopilotStorePaths.has(explicitCopilotStorePath);
1564
+ const explicitStoreIsAdopted =
1565
+ explicitCopilotStorePath !== null &&
1566
+ adoptedCopilotStorePathKeys.has(explicitCopilotStorePath);
1567
+
1568
+ // ── GitHub Copilot CLI / Chat extension (OTEL JSONL files) ──
1569
+ const copilotPaths = copilotSourceAllowed ? resolveCopilotOtelPaths(process.env) : [];
1332
1570
  if (copilotPaths.length > 0) {
1333
1571
  if (progress?.enabled) {
1334
1572
  progress.start(`Parsing Copilot ${renderBar(0)} | buckets 0`);
1335
1573
  }
1336
1574
  try {
1337
- copilotResult = await parseCopilotIncremental({
1575
+ const copilotOtelResult = await parseCopilotIncremental({
1338
1576
  otelPaths: copilotPaths,
1339
1577
  cursors,
1340
1578
  queuePath,
1341
1579
  env: process.env,
1580
+ storeUsageEvents:
1581
+ copilotStoreResult.usageClaims ||
1582
+ cursors?.copilotStore?.recentEvents ||
1583
+ [],
1584
+ skipCliSpans: copilotStoreWasActive,
1342
1585
  onProgress: (p) => {
1343
1586
  if (!progress?.enabled) return;
1344
1587
  const pct = p.total > 0 ? p.index / p.total : 1;
@@ -1347,42 +1590,189 @@ async function cmdSync(argv) {
1347
1590
  );
1348
1591
  },
1349
1592
  });
1593
+ copilotOtelUsageClaims =
1594
+ copilotOtelResult.usageClaims ||
1595
+ cursors?.copilot?.recentUsageEvents ||
1596
+ [];
1597
+ copilotResult = mergeParseResult(copilotResult, copilotOtelResult);
1350
1598
  } catch (err) {
1599
+ copilotLegacyHealthy = false;
1351
1600
  warnProviderParseFailure("Copilot", err, opts);
1352
1601
  }
1353
1602
  }
1354
1603
 
1355
1604
  // ── GitHub Copilot App (passive data.db session summaries) ──
1356
- const copilotAppDbPaths = sourceAllowed("copilot")
1357
- ? resolveCopilotAppDbPaths(process.env).filter((p) => {
1358
- try { return fssync.existsSync(p); } catch (_e) { return false; }
1605
+ const copilotAppDbStates =
1606
+ cursors?.copilotApp?.dbs && typeof cursors.copilotApp.dbs === "object"
1607
+ ? cursors.copilotApp.dbs
1608
+ : null;
1609
+ const initialTrackedCopilotAppDbPaths = new Set(
1610
+ Object.keys(copilotAppDbStates || {}),
1611
+ );
1612
+ const copilotAppDbCandidates = uniqueCopilotDbPaths(
1613
+ [
1614
+ ...resolveCopilotAppDbPaths(process.env),
1615
+ ...initialTrackedCopilotAppDbPaths,
1616
+ ],
1617
+ process.env,
1618
+ Array.from(initialTrackedCopilotAppDbPaths),
1619
+ );
1620
+ coalesceCopilotDbStatesByIdentity(
1621
+ copilotAppDbStates,
1622
+ copilotAppDbCandidates,
1623
+ mergeCopilotAppDbStates,
1624
+ );
1625
+ const explicitStoreOwnsSingleApp =
1626
+ copilotAppDbCandidates.length === 1 &&
1627
+ explicitStoreIsAdopted;
1628
+ if (copilotAppDbStates && (copilotStoreWasActive || copilotStorePaths.length > 0)) {
1629
+ for (const dbPath of Object.keys(copilotAppDbStates)) {
1630
+ try {
1631
+ fssync.statSync(dbPath);
1632
+ } catch (err) {
1633
+ const matchingStorePath = copilotPathKey(
1634
+ path.join(path.dirname(dbPath), "session-store.db"),
1635
+ );
1636
+ if (err?.code === "ENOENT") {
1637
+ if (
1638
+ canonicalCopilotStorePaths.has(matchingStorePath) ||
1639
+ (explicitStoreOwnsSingleApp && explicitStoreIsCanonical)
1640
+ ) {
1641
+ delete copilotAppDbStates[dbPath];
1642
+ }
1643
+ } else {
1644
+ if (!copilotStoreWasActive) copilotLegacyHealthy = false;
1645
+ }
1646
+ }
1647
+ }
1648
+ }
1649
+ const trackedCopilotAppDbPaths = new Set(
1650
+ Object.keys(cursors?.copilotApp?.dbs || {}),
1651
+ );
1652
+ const rawCopilotAppDbPaths = copilotSourceAllowed
1653
+ ? Array.from(new Set([
1654
+ ...copilotAppDbCandidates,
1655
+ ...trackedCopilotAppDbPaths,
1656
+ ])).filter((dbPath) => {
1657
+ if (trackedCopilotAppDbPaths.has(dbPath)) return true;
1658
+ try { return fssync.existsSync(dbPath); } catch (_e) { return false; }
1359
1659
  })
1360
1660
  : [];
1661
+ const copilotAppDbPaths = uniqueCopilotDbPaths(
1662
+ rawCopilotAppDbPaths,
1663
+ process.env,
1664
+ Array.from(trackedCopilotAppDbPaths),
1665
+ );
1361
1666
  if (copilotAppDbPaths.length > 0) {
1362
1667
  if (progress?.enabled) {
1363
1668
  progress.start(`Parsing Copilot App ${renderBar(0)} | buckets 0`);
1364
1669
  }
1670
+ for (const appDbPath of copilotAppDbPaths) {
1671
+ const storePathKey = copilotPathKey(
1672
+ path.join(path.dirname(appDbPath), "session-store.db"),
1673
+ );
1674
+ const observeOnly =
1675
+ copilotStoreWasActive ||
1676
+ adoptedCopilotStorePathKeys.has(storePathKey) ||
1677
+ (copilotAppDbPaths.length === 1 && explicitStoreIsAdopted);
1678
+ const catchupRelevant =
1679
+ !observeOnly &&
1680
+ !copilotStoreWasActive &&
1681
+ copilotStorePaths.length > 0;
1682
+ try {
1683
+ const copilotAppResult = await parseCopilotAppDbIncremental({
1684
+ dbPaths: [appDbPath],
1685
+ cursors,
1686
+ queuePath,
1687
+ env: process.env,
1688
+ observeOnly,
1689
+ onProgress: (p) => {
1690
+ if (!progress?.enabled) return;
1691
+ const pct = p.total > 0 ? p.index / p.total : 1;
1692
+ progress.update(
1693
+ `Parsing Copilot App ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
1694
+ );
1695
+ },
1696
+ });
1697
+ copilotResult = mergeParseResult(copilotResult, copilotAppResult);
1698
+ if (catchupRelevant && copilotAppResult.dbErrors > 0) {
1699
+ copilotLegacyHealthy = false;
1700
+ }
1701
+ } catch (err) {
1702
+ if (catchupRelevant) copilotLegacyHealthy = false;
1703
+ warnProviderParseFailure("Copilot App", err, opts);
1704
+ }
1705
+ }
1706
+ }
1707
+
1708
+ if (
1709
+ copilotSourceAllowed &&
1710
+ !copilotStoreWasActive &&
1711
+ copilotStorePaths.length > 0 &&
1712
+ copilotLegacyHealthy
1713
+ ) {
1714
+ if (progress?.enabled) {
1715
+ progress.start(`Adopting Copilot App/CLI store ${renderBar(0)} | buckets 0`);
1716
+ }
1365
1717
  try {
1366
- const copilotAppResult = await parseCopilotAppDbIncremental({
1367
- dbPaths: copilotAppDbPaths,
1718
+ const copilotHasOtelCursorHistory =
1719
+ (Array.isArray(cursors?.copilot?.seenIds) &&
1720
+ cursors.copilot.seenIds.length > 0) ||
1721
+ (cursors?.copilot?.fileOffsets &&
1722
+ Object.keys(cursors.copilot.fileOffsets).length > 0);
1723
+ const copilotOtelClaimsIncomplete =
1724
+ copilotHadLegacyCliOtelHistory ||
1725
+ (copilotHasOtelCursorHistory &&
1726
+ cursors?.copilot?.usageClaimsComplete !== true);
1727
+ const appSessionIds = [];
1728
+ const appSessionTotals = {};
1729
+ for (const dbState of Object.values(cursors?.copilotApp?.dbs || {})) {
1730
+ for (const [sessionId, totals] of Object.entries(
1731
+ dbState?.sessionTotals || {},
1732
+ )) {
1733
+ const total =
1734
+ Number(totals?.input || 0) +
1735
+ Number(totals?.output || 0) +
1736
+ Number(totals?.cached || 0) +
1737
+ Number(totals?.reasoning || 0);
1738
+ if (total <= 0) continue;
1739
+ appSessionIds.push(sessionId);
1740
+ const prior = appSessionTotals[sessionId];
1741
+ const priorTotal =
1742
+ Number(prior?.input || 0) +
1743
+ Number(prior?.output || 0) +
1744
+ Number(prior?.cached || 0) +
1745
+ Number(prior?.reasoning || 0);
1746
+ if (total > priorTotal) appSessionTotals[sessionId] = totals;
1747
+ }
1748
+ }
1749
+ copilotStoreResult = await parseCopilotSessionStoreIncremental({
1750
+ dbPaths: copilotStorePaths,
1368
1751
  cursors,
1369
1752
  queuePath,
1370
1753
  env: process.env,
1371
- onProgress: (p) => {
1372
- if (!progress?.enabled) return;
1373
- const pct = p.total > 0 ? p.index / p.total : 1;
1374
- progress.update(
1375
- `Parsing Copilot App ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
1376
- );
1377
- },
1754
+ backfillOnFirstRun: !copilotOtelClaimsIncomplete,
1755
+ excludeSessionIdsOnFirstRun: appSessionIds,
1756
+ excludeSessionTotalsOnFirstRun: appSessionTotals,
1757
+ expectedFingerprints: copilotStoreAdoptionFingerprints,
1758
+ otelUsageEvents: copilotOtelUsageClaims,
1378
1759
  });
1379
- copilotResult = {
1380
- recordsProcessed: copilotResult.recordsProcessed + copilotAppResult.recordsProcessed,
1381
- eventsAggregated: copilotResult.eventsAggregated + copilotAppResult.eventsAggregated,
1382
- bucketsQueued: copilotResult.bucketsQueued + copilotAppResult.bucketsQueued,
1383
- };
1760
+ copilotResult = mergeParseResult(copilotResult, copilotStoreResult);
1384
1761
  } catch (err) {
1385
- warnProviderParseFailure("Copilot App", err, opts);
1762
+ warnProviderParseFailure("Copilot App/CLI session store", err, opts);
1763
+ }
1764
+ }
1765
+
1766
+ if (copilotSourceAllowed) {
1767
+ if (Array.isArray(cursors?.copilot?.recentUsageEvents)) {
1768
+ cursors.copilot.recentUsageEvents = pruneCopilotUsageClaims(
1769
+ cursors.copilot.recentUsageEvents,
1770
+ );
1771
+ }
1772
+ if (Array.isArray(cursors?.copilotStore?.recentEvents)) {
1773
+ cursors.copilotStore.recentEvents = pruneCopilotUsageClaims(
1774
+ cursors.copilotStore.recentEvents,
1775
+ );
1386
1776
  }
1387
1777
  }
1388
1778