tokentracker-cli 0.94.1 → 0.95.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.de.md +9 -7
  2. package/README.ja.md +8 -6
  3. package/README.ko.md +8 -6
  4. package/README.md +23 -10
  5. package/README.zh-CN.md +8 -6
  6. package/dashboard/dist/assets/{AchievementBadge-DnmgEa4q.js → AchievementBadge-Cyju9OnL.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-B7YTbAuK.js → AchievementsPage-Bh2dQR-I.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-CwgT5sqH.js → AchievementsSection-DSFAM0Zp.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-Dets6jzW.js → BadgeDetailModal-CgVF0VI0.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-5KwUvC9F.js → BotAnimated-DI-rTxI9.js} +1 -1
  11. package/dashboard/dist/assets/{Card-jf9w-4-N.js → Card-DAlV-PSI.js} +1 -1
  12. package/dashboard/dist/assets/{ClawdAnimated-CGxDVQ0c.js → ClawdAnimated-oOYKgcpq.js} +2 -2
  13. package/dashboard/dist/assets/{CommandPalette-B1bUUf32.js → CommandPalette-bYE5uD63.js} +1 -1
  14. package/dashboard/dist/assets/CommunityStatsModal-DCx0sQjk.js +1 -0
  15. package/dashboard/dist/assets/{ConfirmModal-BR-aIRH9.js → ConfirmModal-DrYf9m15.js} +1 -1
  16. package/dashboard/dist/assets/{Controls-CbHsJAqV.js → Controls-DAift9Z4.js} +1 -1
  17. package/dashboard/dist/assets/DashboardPage-CLswnOil.js +1 -0
  18. package/dashboard/dist/assets/{DevicePage-DDlicLLn.js → DevicePage-uWLgMC4Z.js} +1 -1
  19. package/dashboard/dist/assets/{DialogClose-B89uLXqp.js → DialogClose-NitVrdVC.js} +1 -1
  20. package/dashboard/dist/assets/DialogDescription-CQuJCP4L.js +1 -0
  21. package/dashboard/dist/assets/{DialogTitle-jOyycYKF.js → DialogTitle-CA_ZstJL.js} +1 -1
  22. package/dashboard/dist/assets/{FadeIn-BEND42ze.js → FadeIn-CIkYDamA.js} +1 -1
  23. package/dashboard/dist/assets/{HeaderGithubStar-Dacl1WGS.js → HeaderGithubStar-CYTJNLpL.js} +1 -1
  24. package/dashboard/dist/assets/{HoverTooltip-B2Mh2LTi.js → HoverTooltip-jh3sI8NC.js} +1 -1
  25. package/dashboard/dist/assets/{IpCheckPage-CPEuRhiU.js → IpCheckPage-Brpe-N7B.js} +1 -1
  26. package/dashboard/dist/assets/{LandingPage-BYWNR_dx.js → LandingPage-D4PjaNcN.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardAvatar-B9_5LDVj.js → LeaderboardAvatar-D9dc6frq.js} +1 -1
  28. package/dashboard/dist/assets/{LeaderboardPage-_Hl1NaIa.js → LeaderboardPage-CEuqopiC.js} +2 -2
  29. package/dashboard/dist/assets/{LeaderboardProfileModal-Cydp7CyA.js → LeaderboardProfileModal-TeYdXeIA.js} +3 -3
  30. package/dashboard/dist/assets/{LeaderboardProfilePage-Dz0ecujs.js → LeaderboardProfilePage-DmtuwJkL.js} +1 -1
  31. package/dashboard/dist/assets/{LimitsPage-CmdT2qOZ.js → LimitsPage-BJW3teYA.js} +2 -2
  32. package/dashboard/dist/assets/{LocalOnlyNotice-DhuPGUkw.js → LocalOnlyNotice-V8_KFKOD.js} +1 -1
  33. package/dashboard/dist/assets/{LoginCard-DQtHaX_5.js → LoginCard-ud0lCY_u.js} +1 -1
  34. package/dashboard/dist/assets/{LoginModal-DvpPRnYV.js → LoginModal-Bnemx4UD.js} +1 -1
  35. package/dashboard/dist/assets/{LoginPage-DOjfmdwb.js → LoginPage-v89BHssa.js} +1 -1
  36. package/dashboard/dist/assets/{PetPage-Dd6U9fh2.js → PetPage-Bg9kEi7o.js} +1 -1
  37. package/dashboard/dist/assets/{PopoverPopup-B1V01u10.js → PopoverPopup-CCHVwh0w.js} +1 -1
  38. package/dashboard/dist/assets/{ProviderIcon-BLRe55c7.js → ProviderIcon-BecpRylD.js} +1 -1
  39. package/dashboard/dist/assets/{ResetPasswordPage-BJ95vy1S.js → ResetPasswordPage-CsUtFeUr.js} +1 -1
  40. package/dashboard/dist/assets/{SegmentedControl-DacS_m9M.js → SegmentedControl-3rBqbzW_.js} +1 -1
  41. package/dashboard/dist/assets/{Select-bQDO5BEA.js → Select-DZ2ll7Qv.js} +1 -1
  42. package/dashboard/dist/assets/{SelectItemText-CUGrs9CY.js → SelectItemText-Dr1W47W6.js} +1 -1
  43. package/dashboard/dist/assets/{ServiceStatusPage-CsERvmVC.js → ServiceStatusPage-Cc1LXdAh.js} +1 -1
  44. package/dashboard/dist/assets/{SessionsPage-0VvqEfPx.js → SessionsPage-ytOFN1h9.js} +1 -1
  45. package/dashboard/dist/assets/{SettingsPage-9EHh2HfD.js → SettingsPage-2wYQ2ly6.js} +2 -2
  46. package/dashboard/dist/assets/SkillsPage-BEDAwBTN.js +1 -0
  47. package/dashboard/dist/assets/{TokenGalaxy-DSKTHQSs.js → TokenGalaxy-DxG0dBeG.js} +1 -1
  48. package/dashboard/dist/assets/{ToolbarRootContext-DP4mQLbq.js → ToolbarRootContext-DG1pr5UP.js} +1 -1
  49. package/dashboard/dist/assets/TrendMonitor-CeuEQ0XC.js +19 -0
  50. package/dashboard/dist/assets/{WidgetsPage-Cr6Wg9QX.js → WidgetsPage-ALDlZ5aR.js} +1 -1
  51. package/dashboard/dist/assets/{WrappedPage-DqQvRcnA.js → WrappedPage-BF59ouR-.js} +1 -1
  52. package/dashboard/dist/assets/{__vite-browser-external-BHz2N7Ih.css → __vite-browser-external-CCMdjCX2.css} +1 -1
  53. package/dashboard/dist/assets/__vite-browser-external-DXtgoDkV.js +45 -0
  54. package/dashboard/dist/assets/{agent-logos-BjIh_mqf.js → agent-logos-D6Ya1qw1.js} +1 -1
  55. package/dashboard/dist/assets/{arrow-up-right-BIhbN0NO.js → arrow-up-right-DUR69elJ.js} +1 -1
  56. package/dashboard/dist/assets/{check-U-wNzPvd.js → check-9_1xp5h5.js} +1 -1
  57. package/dashboard/dist/assets/{composite-BR91CxNl.js → composite-CEzovOpb.js} +1 -1
  58. package/dashboard/dist/assets/{copy-Bw3hXBCu.js → copy-DEoLQqJE.js} +1 -1
  59. package/dashboard/dist/assets/{download-WOqcQzvK.js → download-BNjfcJ1g.js} +1 -1
  60. package/dashboard/dist/assets/{external-link-BZtf6WfS.js → external-link-fV_WJ5f_.js} +1 -1
  61. package/dashboard/dist/assets/{flame-B5kp2VI9.js → flame-DqpuFJ14.js} +1 -1
  62. package/dashboard/dist/assets/{format-tokens-BOCnuMuJ.js → format-tokens-CatddjmK.js} +1 -1
  63. package/dashboard/dist/assets/{icons-CA3FiIa_.js → icons-CBT1KI7V.js} +1 -1
  64. package/dashboard/dist/assets/{index-DjWC63Bs.js → index-BK3SzL7v.js} +1 -1
  65. package/dashboard/dist/assets/{index-DQaanK52.js → index-_2LEUZTl.js} +1 -1
  66. package/dashboard/dist/assets/{index-mEG0HZ-a.js → index-tK7s2SeO.js} +1 -1
  67. package/dashboard/dist/assets/{info-Djer9i3-.js → info-DBotcfT9.js} +1 -1
  68. package/dashboard/dist/assets/{limits-providers-F-RnNFAb.js → limits-providers-CjMU709b.js} +1 -1
  69. package/dashboard/dist/assets/{link-2-DV4ocD0l.js → link-2-Wc54x2fh.js} +1 -1
  70. package/dashboard/dist/assets/{loader-circle-DJK72c-o.js → loader-circle-nRhGIddE.js} +1 -1
  71. package/dashboard/dist/assets/{maximize-2-BwBA_lMm.js → maximize-2-Dw3_GuKW.js} +1 -1
  72. package/dashboard/dist/assets/{modifiers.esm-B3sl4qyZ.js → modifiers.esm-rTZ5BUxp.js} +1 -1
  73. package/dashboard/dist/assets/module-xpSFyNBK.js +3 -0
  74. package/dashboard/dist/assets/{monitor-smartphone-BUTpM4ng.js → monitor-smartphone-CNVAqV_N.js} +1 -1
  75. package/dashboard/dist/assets/{plus-BA9RP5L6.js → plus-B5oPOXhp.js} +1 -1
  76. package/dashboard/dist/assets/{provider-display-DqsJzGSr.js → provider-display-DhAUWfC6.js} +2 -2
  77. package/dashboard/dist/assets/{react-BYw5AVSz.js → react-BKACWrsB.js} +1 -1
  78. package/dashboard/dist/assets/{search-BzcnFWVD.js → search-DgddmV09.js} +1 -1
  79. package/dashboard/dist/assets/{skills-api-vAME1P7G.js → skills-api-BT1uiqeq.js} +1 -1
  80. package/dashboard/dist/assets/{sortable.esm-BpEiRqqI.js → sortable.esm-UCoNfZ6z.js} +1 -1
  81. package/dashboard/dist/assets/{store-GeT5fhRX.js → store-ZswY8Voz.js} +1 -1
  82. package/dashboard/dist/assets/{terminal-CZCMYQ8K.js → terminal-BtQyotJS.js} +1 -1
  83. package/dashboard/dist/assets/{trash-2-DzlWEwKO.js → trash-2-Dc_NLbWs.js} +1 -1
  84. package/dashboard/dist/assets/{use-community-stats-DOvJfj-i.js → use-community-stats-yOKag_ou.js} +1 -1
  85. package/dashboard/dist/assets/use-latest-request-guard-BEiszvlI.js +1 -0
  86. package/dashboard/dist/assets/{use-limits-display-prefs-BmLs4HyN.js → use-limits-display-prefs-C-l_Mxvy.js} +1 -1
  87. package/dashboard/dist/assets/{use-ordered-list-BgozDSSV.js → use-ordered-list-CKjRaO6K.js} +1 -1
  88. package/dashboard/dist/assets/use-reduced-motion-BlOkflqU.js +1 -0
  89. package/dashboard/dist/assets/{use-session-efficiency-pref-HP1ckVkS.js → use-session-efficiency-pref-DPI_--W2.js} +1 -1
  90. package/dashboard/dist/assets/{use-transform-B4VSogOx.js → use-transform-BQUem5OA.js} +1 -1
  91. package/dashboard/dist/assets/use-usage-limits-lvkMBnNR.js +1 -0
  92. package/dashboard/dist/assets/{useCurrency-CSuzCBAL.js → useCurrency-C5xhS1QV.js} +1 -1
  93. package/dashboard/dist/assets/{useTokenFormat-ClnL9OFe.js → useTokenFormat-FBMikfEq.js} +1 -1
  94. package/dashboard/dist/assets/{zap-C_T9CCiS.js → zap-BsCNYelz.js} +1 -1
  95. package/dashboard/dist/brand-logos/lmstudio.svg +12 -0
  96. package/dashboard/dist/brand-logos/unsloth.svg +1 -0
  97. package/dashboard/dist/index.html +16 -12
  98. package/dashboard/dist/ip-check.html +2 -2
  99. package/dashboard/dist/leaderboard.html +2 -2
  100. package/dashboard/dist/llms.txt +4 -2
  101. package/dashboard/dist/share.html +2 -2
  102. package/package.json +2 -2
  103. package/src/commands/device-login.js +5 -10
  104. package/src/commands/init.js +28 -1
  105. package/src/commands/serve.js +6 -0
  106. package/src/commands/status.js +75 -8
  107. package/src/commands/sync.js +189 -18
  108. package/src/lib/cloud-account.js +102 -29
  109. package/src/lib/fs.js +30 -0
  110. package/src/lib/local-api.js +99 -13
  111. package/src/lib/pricing/index.js +5 -0
  112. package/src/lib/pricing/matcher.js +15 -0
  113. package/src/lib/pricing/seed-snapshot.json +1 -1
  114. package/src/lib/rollout.js +1418 -28
  115. package/src/lib/usage-limits.js +507 -55
  116. package/src/lib/zcode-limits.js +65 -8
  117. package/dashboard/dist/assets/CommunityStatsModal-DRDpZycV.js +0 -1
  118. package/dashboard/dist/assets/DashboardPage-B5XMQoxy.js +0 -1
  119. package/dashboard/dist/assets/DialogDescription-DiVbwvTR.js +0 -1
  120. package/dashboard/dist/assets/SkillsPage-B1AcLX41.js +0 -1
  121. package/dashboard/dist/assets/TrendMonitor-rabtcE5w.js +0 -19
  122. package/dashboard/dist/assets/__vite-browser-external-C8KwSyTz.js +0 -45
  123. package/dashboard/dist/assets/chevron-right-Du-QVNj4.js +0 -1
  124. package/dashboard/dist/assets/module-D2D5r9gC.js +0 -3
  125. package/dashboard/dist/assets/use-reduced-motion-Bc3RFJdg.js +0 -1
  126. package/dashboard/dist/assets/use-usage-limits-DNNhD2H6.js +0 -1
@@ -69,8 +69,14 @@ const {
69
69
  resolveKilocodeTaskFiles,
70
70
  resolveRoocodeTaskFiles,
71
71
  resolveZedDbPath,
72
+ resolveLmstudioHome,
73
+ resolveLmstudioLogFiles,
74
+ resolveUnslothDbPath,
72
75
  resolveQoderDbPaths,
73
76
  resolveQoderCnDbPaths,
77
+ resolveQoderProjectsDir,
78
+ resolveQoderCnProjectsDir,
79
+ listQoderNewSessionFiles,
74
80
  resolveClaudeScienceDbPaths,
75
81
  resolveAnythingllmDbPath,
76
82
  resolveGooseDbPath,
@@ -127,6 +133,13 @@ function formatResolvedPaths(paths, filename) {
127
133
  return active;
128
134
  }
129
135
 
136
+ // Combine a legacy DB path with its new-JSONL projects-dir path for the
137
+ // machine-readable summary. Either side may be "" (absent); both absent
138
+ // yields "" so callers can fall back to "not found".
139
+ function summarizeQoderDetail(dbPath, newPath) {
140
+ return [dbPath, newPath].filter(Boolean).join(" + ") || "";
141
+ }
142
+
130
143
  // The Trae SOLO entitlement snapshot is read directly from the Trae Local
131
144
  // State storage.json via the shared parser (readTraeEntitlementFromStorage).
132
145
  // The queue.jsonl contract is token-count-only (CLAUDE.md privacy rule), so
@@ -488,15 +501,28 @@ async function cmdStatus(argv = []) {
488
501
  const zcodeInstalled = zcodeActive.length > 0;
489
502
  const zcodeDbPath = zcodeActive.join(" | ");
490
503
 
491
- // Qoder Desktop 1.18+ — token usage lives in SharedClientCache/local.db.
504
+ // Qoder Desktop 1.18+ — token usage lives in SharedClientCache/local.db (legacy)
505
+ // and since 2026-08 (app 0.1.2+) also in ~/.qoder/projects JSONL (credit-based).
492
506
  const qoderPaths = resolveQoderDbPaths({
493
507
  home,
494
508
  env: process.env,
495
509
  platform: process.platform,
496
510
  });
497
511
  const qoderActive = formatResolvedPaths(qoderPaths);
498
- const qoderInstalled = qoderActive.length > 0;
512
+ // New JSONL location (com.qoder.app.stable + ~/.qoder/projects)
513
+ const qoderProjectsDirResolved = resolveQoderProjectsDir({ home, env: process.env });
514
+ let qoderNewFiles = [];
515
+ try {
516
+ qoderNewFiles = await listQoderNewSessionFiles(qoderProjectsDirResolved);
517
+ } catch (_e) {
518
+ qoderNewFiles = [];
519
+ }
520
+ const qoderNewInstalled = qoderNewFiles.length > 0;
521
+ const qoderInstalled = qoderActive.length > 0 || qoderNewInstalled;
499
522
  const qoderDbPath = qoderActive.join(" | ");
523
+ const qoderNewPath = qoderNewFiles.length > 0
524
+ ? `${qoderProjectsDirResolved} (${qoderNewFiles.length} sessions)`
525
+ : "";
500
526
 
501
527
  // Qoder CN (国内版) — same schema, separate Application Support/QoderCN dir.
502
528
  const qoderCnPaths = resolveQoderCnDbPaths({
@@ -505,8 +531,28 @@ async function cmdStatus(argv = []) {
505
531
  platform: process.platform,
506
532
  });
507
533
  const qoderCnActive = formatResolvedPaths(qoderCnPaths);
508
- const qoderCnInstalled = qoderCnActive.length > 0;
534
+ // CN new-JSONL mirrors sync.js: CN and international currently share
535
+ // ~/.qoder/projects, and sync only parses the CN dir when it diverges from
536
+ // the international one (same files must not count under two sources). Only
537
+ // report CN JSONL when the dirs diverge.
538
+ const qoderCnProjectsDirResolved = resolveQoderCnProjectsDir({ home, env: process.env });
539
+ const qoderCnSharesIntlDir = path.normalize(qoderCnProjectsDirResolved) === path.normalize(qoderProjectsDirResolved)
540
+ || (process.platform === "win32"
541
+ && path.normalize(qoderCnProjectsDirResolved).toLowerCase() === path.normalize(qoderProjectsDirResolved).toLowerCase());
542
+ let qoderCnNewFiles = [];
543
+ if (!qoderCnSharesIntlDir) {
544
+ try {
545
+ qoderCnNewFiles = await listQoderNewSessionFiles(qoderCnProjectsDirResolved);
546
+ } catch (_e) {
547
+ qoderCnNewFiles = [];
548
+ }
549
+ }
550
+ const qoderCnNewInstalled = qoderCnNewFiles.length > 0;
551
+ const qoderCnInstalled = qoderCnActive.length > 0 || qoderCnNewInstalled;
509
552
  const qoderCnDbPath = qoderCnActive.join(" | ");
553
+ const qoderCnNewPath = qoderCnNewFiles.length > 0
554
+ ? `${qoderCnProjectsDirResolved} (${qoderCnNewFiles.length} sessions)`
555
+ : "";
510
556
 
511
557
  // Claude Science — token usage lives on the `frames` table of operon-cli.db.
512
558
  // Unlike the native/WSL pair other providers resolve to, this is an open-ended
@@ -637,6 +683,11 @@ async function cmdStatus(argv = []) {
637
683
  const dshSessionsDir = dshHomes.map((homeDir) => path.join(homeDir, "sessions")).join(", ");
638
684
  const dshSessionFiles = await resolveDshSessionFiles(process.env);
639
685
  const dshInstalled = dshSessionFiles.length > 0;
686
+ const lmstudioHome = resolveLmstudioHome(process.env);
687
+ const lmstudioLogFiles = await resolveLmstudioLogFiles(process.env);
688
+ const lmstudioInstalled = lmstudioLogFiles.length > 0;
689
+ const unslothDbPath = resolveUnslothDbPath(process.env);
690
+ const unslothInstalled = Boolean(unslothDbPath && fssync.existsSync(unslothDbPath));
640
691
 
641
692
  // Trae SOLO (ByteDance AI IDE) — passive entitlement snapshot reader.
642
693
  const traeStoragePath = resolveTraeStoragePath(process.env);
@@ -937,10 +988,10 @@ async function cmdStatus(argv = []) {
937
988
  ? { installed: true, detail: zcodeDbPath }
938
989
  : { installed: false },
939
990
  qoder: qoderInstalled
940
- ? { installed: true, detail: qoderDbPath }
991
+ ? { installed: true, detail: summarizeQoderDetail(qoderDbPath, qoderNewPath) }
941
992
  : { installed: false },
942
993
  "qoder-cn": qoderCnInstalled
943
- ? { installed: true, detail: qoderCnDbPath }
994
+ ? { installed: true, detail: summarizeQoderDetail(qoderCnDbPath, qoderCnNewPath) }
944
995
  : { installed: false },
945
996
  "claude-science": claudeScienceInstalled
946
997
  ? { installed: true, detail: claudeScienceDbPath }
@@ -961,6 +1012,16 @@ async function cmdStatus(argv = []) {
961
1012
  dsh: dshInstalled
962
1013
  ? { installed: true, files: dshSessionFiles.length, detail: dshSessionsDir }
963
1014
  : { installed: false },
1015
+ lmstudio: lmstudioInstalled
1016
+ ? {
1017
+ installed: true,
1018
+ files: lmstudioLogFiles.length,
1019
+ detail: path.join(lmstudioHome, "server-logs"),
1020
+ }
1021
+ : { installed: false },
1022
+ unsloth: unslothInstalled
1023
+ ? { installed: true, detail: unslothDbPath }
1024
+ : { installed: false },
964
1025
  trae: traeInstalled
965
1026
  ? {
966
1027
  installed: true,
@@ -1089,10 +1150,10 @@ async function cmdStatus(argv = []) {
1089
1150
  ? `- ZCode: passive reader (${zcodeDbPath})`
1090
1151
  : null,
1091
1152
  qoderInstalled
1092
- ? `- Qoder: passive reader (${qoderDbPath})`
1153
+ ? `- Qoder: passive reader (${[qoderDbPath, qoderNewPath].filter(Boolean).join(" + ") || "not found"}${qoderNewInstalled && qoderActive.length > 0 ? " [legacy DB + new JSONL (both tracked)]" : ""}${qoderNewInstalled && qoderActive.length === 0 ? " [new JSONL only]" : ""})`
1093
1154
  : null,
1094
1155
  qoderCnInstalled
1095
- ? `- Qoder CN: passive reader (${qoderCnDbPath})`
1156
+ ? `- Qoder CN: passive reader (${summarizeQoderDetail(qoderCnDbPath, qoderCnNewPath) || "not found"}${qoderCnNewInstalled && qoderCnActive.length > 0 ? " [legacy DB + new JSONL (both tracked)]" : ""}${qoderCnNewInstalled && qoderCnActive.length === 0 ? " [new JSONL only]" : ""})`
1096
1157
  : null,
1097
1158
  claudeScienceInstalled
1098
1159
  ? `- Claude Science: passive reader (${claudeScienceDbPath})`
@@ -1137,6 +1198,12 @@ async function cmdStatus(argv = []) {
1137
1198
  dshInstalled
1138
1199
  ? `- DeepSeek Harness: passive reader (${dshSessionFiles.length} session${dshSessionFiles.length !== 1 ? "s" : ""} in ${dshSessionsDir})`
1139
1200
  : null,
1201
+ lmstudioInstalled
1202
+ ? `- LM Studio: passive reader (${lmstudioLogFiles.length} log${lmstudioLogFiles.length !== 1 ? "s" : ""} in ${path.join(lmstudioHome, "server-logs")})`
1203
+ : null,
1204
+ unslothInstalled
1205
+ ? `- Unsloth Studio: passive reader (${unslothDbPath})`
1206
+ : null,
1140
1207
  traeInstalled
1141
1208
  // Deliberately NOT "passive reader": every other line with that wording
1142
1209
  // means tokens are being counted. Trae encrypts its session transcripts
@@ -1438,4 +1505,4 @@ function parseEpochMsToIso(v) {
1438
1505
  return d.toISOString();
1439
1506
  }
1440
1507
 
1441
- module.exports = { cmdStatus };
1508
+ module.exports = { cmdStatus, summarizeQoderDetail };
@@ -15,6 +15,7 @@ const {
15
15
  chmod600IfPossible,
16
16
  openLock,
17
17
  inspectLock,
18
+ updateJsonLocked,
18
19
  } = require("../lib/fs");
19
20
  const { physicalJsonlRecords } = require("../lib/jsonl-lines");
20
21
  const {
@@ -33,6 +34,10 @@ const {
33
34
  resolveQoderDbPaths,
34
35
  resolveQoderCnDbPaths,
35
36
  readQoderDbMessages,
37
+ resolveQoderProjectsDir,
38
+ resolveQoderCnProjectsDir,
39
+ listQoderNewSessionFiles,
40
+ parseQoderNewIncremental,
36
41
  resolveKiroDbPath,
37
42
  resolveKiroJsonlPath,
38
43
  resolveKiroBasePath,
@@ -104,6 +109,10 @@ const {
104
109
  parseRoocodeIncremental,
105
110
  resolveZedDbPath,
106
111
  parseZedIncremental,
112
+ resolveLmstudioLogFiles,
113
+ parseLmstudioIncremental,
114
+ resolveUnslothDbPath,
115
+ parseUnslothIncremental,
107
116
  resolveAnythingllmDbPath,
108
117
  parseAnythingllmIncremental,
109
118
  resolveGooseDbPath,
@@ -260,6 +269,7 @@ const CODEX_COLD_SCAN_AUDIT_MAX_SYNCS = 288;
260
269
  const MIMO_PROVIDER_REPAIR_KEY = "mimoClaudeMislabelRepair_2026_06";
261
270
  const DSH_LEGACY_SOURCE_MIGRATION_KEY = "deepseekHarnessSourceMigration_2026_08";
262
271
  const ZCODE_NATIVE_USAGE_REPAIR_KEY = "zcodeNativeUsageRepair_2026_08";
272
+ const ZCODE_INCLUSIVE_TOKEN_REPAIR_KEY = "zcodeInclusiveTokenRepair_2026_09";
263
273
  const AUTO_SYNC_SOURCE_ALIASES = new Map([
264
274
  ["code", "every-code"],
265
275
  ["deepseek", "dsh"],
@@ -291,6 +301,7 @@ const AUTO_SYNC_SOURCES = new Set([
291
301
  "kiro",
292
302
  "kimi",
293
303
  "kimi-code",
304
+ "lmstudio",
294
305
  "mimo",
295
306
  "omp",
296
307
  "opencode",
@@ -301,6 +312,7 @@ const AUTO_SYNC_SOURCES = new Set([
301
312
  "reasonix",
302
313
  "roocode",
303
314
  "trae-cn",
315
+ "unsloth",
304
316
  "workbuddy",
305
317
  "zcode",
306
318
  "zed",
@@ -561,6 +573,8 @@ async function cmdSync(argv, context = {}) {
561
573
  legacyBaseUrlMigration = {
562
574
  previousDeviceToken,
563
575
  replacementDeviceToken,
576
+ hadPersistedAnonKey: Object.prototype.hasOwnProperty.call(config, "anonKey"),
577
+ persistedAnonKey: config.anonKey,
564
578
  };
565
579
  }
566
580
  const codexCursorRoots = [process.env.CODEX_HOME || path.join(home, ".codex")];
@@ -1259,6 +1273,39 @@ async function cmdSync(argv, context = {}) {
1259
1273
  }
1260
1274
  }
1261
1275
 
1276
+ // Qoder new (JSONL) — com.qoder.app.stable / ~/.qoder/projects (2026-08+)
1277
+ // The new Electron app no longer writes SharedClientCache/local.db; all
1278
+ // recent sessions live in ~/.qoder/projects as flat JSONL with credit-based
1279
+ // usage. Parse them into the same "qoder" source so history is continuous.
1280
+ // The legacy DB block above remains as a fallback for pre-migration rows.
1281
+ if (sourceAllowed("qoder")) {
1282
+ try {
1283
+ const projectsDir = resolveQoderProjectsDir({ home, env: process.env });
1284
+ const sessionFiles = await listQoderNewSessionFiles(projectsDir);
1285
+ if (sessionFiles.length > 0) {
1286
+ if (progress?.enabled) {
1287
+ progress.start(`Parsing Qoder (new) ${renderBar(0)} | buckets 0`);
1288
+ }
1289
+ const parsed = await parseQoderNewIncremental({
1290
+ sessionFiles,
1291
+ cursors,
1292
+ queuePath,
1293
+ projectQueuePath,
1294
+ onProgress: makeProviderProgress("Qoder (new)"),
1295
+ sourceKey: "qoder",
1296
+ cursorKey: "qoderNew",
1297
+ });
1298
+ qoderResult = {
1299
+ recordsProcessed: qoderResult.recordsProcessed + (parsed.messagesProcessed || 0),
1300
+ eventsAggregated: qoderResult.eventsAggregated + (parsed.eventsAggregated || 0),
1301
+ bucketsQueued: qoderResult.bucketsQueued + (parsed.bucketsQueued || 0),
1302
+ };
1303
+ }
1304
+ } catch (err) {
1305
+ warnProviderParseFailure("Qoder (new)", err, opts);
1306
+ }
1307
+ }
1308
+
1262
1309
  // ── Qoder CN (国内版) — same SharedClientCache/local.db schema, separate
1263
1310
  // Application Support/QoderCN data directory. Tracked as its own source
1264
1311
  // with its own cursor namespace: the two DBs each number rowids from 1, so
@@ -1310,6 +1357,44 @@ async function cmdSync(argv, context = {}) {
1310
1357
  }
1311
1358
  }
1312
1359
 
1360
+ // Qoder CN new (JSONL) — currently shares ~/.qoder/projects with international;
1361
+ // keep distinct parsing only when CN projects dir diverges (future CN split)
1362
+ // to avoid double-counting the same JSONL under two sources.
1363
+ if (sourceAllowed("qoder-cn")) {
1364
+ try {
1365
+ const cnProjectsDir = resolveQoderCnProjectsDir({ home, env: process.env });
1366
+ const intlProjectsDir = resolveQoderProjectsDir({ home, env: process.env });
1367
+ const projectsDirKey = (p) => {
1368
+ const normalized = path.normalize(p);
1369
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1370
+ };
1371
+ if (projectsDirKey(cnProjectsDir) !== projectsDirKey(intlProjectsDir)) {
1372
+ const sessionFiles = await listQoderNewSessionFiles(cnProjectsDir);
1373
+ if (sessionFiles.length > 0) {
1374
+ if (progress?.enabled) {
1375
+ progress.start(`Parsing Qoder CN (new) ${renderBar(0)} | buckets 0`);
1376
+ }
1377
+ const parsed = await parseQoderNewIncremental({
1378
+ sessionFiles,
1379
+ cursors,
1380
+ queuePath,
1381
+ projectQueuePath,
1382
+ onProgress: makeProviderProgress("Qoder CN (new)"),
1383
+ sourceKey: "qoder-cn",
1384
+ cursorKey: "qoderCnNew",
1385
+ });
1386
+ qoderCnResult = {
1387
+ recordsProcessed: qoderCnResult.recordsProcessed + (parsed.messagesProcessed || 0),
1388
+ eventsAggregated: qoderCnResult.eventsAggregated + (parsed.eventsAggregated || 0),
1389
+ bucketsQueued: qoderCnResult.bucketsQueued + (parsed.bucketsQueued || 0),
1390
+ };
1391
+ }
1392
+ }
1393
+ } catch (err) {
1394
+ warnProviderParseFailure("Qoder CN (new)", err, opts);
1395
+ }
1396
+ }
1397
+
1313
1398
  // ── Claude Science (Anthropic's local research workbench, issue #246) ──
1314
1399
  // Per-frame token usage lives on the `frames` table of operon-cli.db.
1315
1400
  // Passive, incremental, subtract-on-change. There can be more than one DB:
@@ -1432,6 +1517,13 @@ async function cmdSync(argv, context = {}) {
1432
1517
  projectQueueStatePath,
1433
1518
  });
1434
1519
  }
1520
+ await repairZcodeInclusiveTokenMigration({
1521
+ cursors,
1522
+ queuePath,
1523
+ queueStatePath,
1524
+ projectQueuePath,
1525
+ projectQueueStatePath,
1526
+ });
1435
1527
  zcodeResult = await multiInstallParse({
1436
1528
  paths: zcodePaths, parserFn: parseOpencodeDbForInstall, providerName: "zcode",
1437
1529
  cursors, getParams: (p) => ({ dbPath: p, readFn: readZcodeDbMessages, source: "zcode", cursorKey: "zcode" }),
@@ -1477,6 +1569,47 @@ async function cmdSync(argv, context = {}) {
1477
1569
  }
1478
1570
  }
1479
1571
 
1572
+ // ── LM Studio and Unsloth Studio — passive inference usage ──
1573
+ let lmstudioResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1574
+ if (sourceAllowed("lmstudio")) {
1575
+ const lmstudioLogFiles = await resolveLmstudioLogFiles(process.env);
1576
+ if (lmstudioLogFiles.length > 0) {
1577
+ if (progress?.enabled) {
1578
+ progress.start(
1579
+ `Parsing LM Studio ${renderBar(0)} 0/${formatNumber(lmstudioLogFiles.length)} logs | buckets 0`,
1580
+ );
1581
+ }
1582
+ try {
1583
+ lmstudioResult = await parseLmstudioIncremental({
1584
+ logFiles: lmstudioLogFiles,
1585
+ cursors,
1586
+ queuePath,
1587
+ onProgress: makeProviderProgress("LM Studio"),
1588
+ });
1589
+ } catch (err) {
1590
+ warnProviderParseFailure("LM Studio", err, opts);
1591
+ }
1592
+ }
1593
+ }
1594
+
1595
+ let unslothResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1596
+ if (sourceAllowed("unsloth")) {
1597
+ const unslothDbPath = resolveUnslothDbPath(process.env);
1598
+ if (unslothDbPath && fssync.existsSync(unslothDbPath)) {
1599
+ if (progress?.enabled) progress.start(`Parsing Unsloth ${renderBar(0)} | buckets 0`);
1600
+ try {
1601
+ unslothResult = await parseUnslothIncremental({
1602
+ dbPath: unslothDbPath,
1603
+ cursors,
1604
+ queuePath,
1605
+ onProgress: makeProviderProgress("Unsloth"),
1606
+ });
1607
+ } catch (err) {
1608
+ warnProviderParseFailure("Unsloth", err, opts);
1609
+ }
1610
+ }
1611
+ }
1612
+
1480
1613
  // ── AnythingLLM Desktop (workspace_chats.response.metrics) ──
1481
1614
  let anythingllmResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1482
1615
  if (sourceAllowed("anythingllm")) {
@@ -2747,6 +2880,8 @@ async function cmdSync(argv, context = {}) {
2747
2880
  reasonixResult.recordsProcessed +
2748
2881
  grokResult.recordsProcessed +
2749
2882
  copilotResult.recordsProcessed +
2883
+ lmstudioResult.recordsProcessed +
2884
+ unslothResult.recordsProcessed +
2750
2885
  anythingllmResult.recordsProcessed +
2751
2886
  kiloResult.recordsProcessed +
2752
2887
  mimoResult.recordsProcessed +
@@ -2783,6 +2918,8 @@ async function cmdSync(argv, context = {}) {
2783
2918
  reasonixResult.bucketsQueued +
2784
2919
  grokResult.bucketsQueued +
2785
2920
  copilotResult.bucketsQueued +
2921
+ lmstudioResult.bucketsQueued +
2922
+ unslothResult.bucketsQueued +
2786
2923
  anythingllmResult.bucketsQueued +
2787
2924
  kiloResult.bucketsQueued +
2788
2925
  mimoResult.bucketsQueued +
@@ -2835,6 +2972,9 @@ async function cmdSync(argv, context = {}) {
2835
2972
  progress?.stop();
2836
2973
 
2837
2974
  const runtimeConfig = config ? { ...config } : {};
2975
+ if (legacyBaseUrlMigration) {
2976
+ delete runtimeConfig.anonKey;
2977
+ }
2838
2978
  if (legacyBaseUrlMigration?.replacementDeviceToken) {
2839
2979
  runtimeConfig.deviceToken = legacyBaseUrlMigration.replacementDeviceToken;
2840
2980
  }
@@ -2888,6 +3028,7 @@ async function cmdSync(argv, context = {}) {
2888
3028
  const drainWithToken = (deviceToken) =>
2889
3029
  drainQueueToCloud({
2890
3030
  baseUrl: runtime.baseUrl,
3031
+ anonKey: runtime.anonKey,
2891
3032
  deviceToken,
2892
3033
  queuePath,
2893
3034
  queueStatePath,
@@ -2917,15 +3058,25 @@ async function cmdSync(argv, context = {}) {
2917
3058
  if (legacyBaseUrlMigration && uploadResult.batches > 0) {
2918
3059
  // device-login does not share the sync lock and may have written a
2919
3060
  // fresh current-backend config while the scan/upload was running.
2920
- // Re-read before committing, merge only while the legacy marker still
2921
- // exists, and never clobber a concurrently completed login.
2922
- const latestConfig = (await readJson(configPath)) || config;
2923
- if (isLegacyInsforgeBaseUrl(latestConfig.baseUrl)) {
2924
- latestConfig.deviceToken = successfulDeviceToken;
2925
- delete latestConfig.baseUrl;
2926
- await writeJson(configPath, latestConfig);
2927
- await chmod600IfPossible(configPath);
2928
- }
3061
+ // Re-read before committing and only remove the anonymous key this
3062
+ // migration observed. A concurrent login may replace the backend URL
3063
+ // while preserving the old key, or another writer may replace the key
3064
+ // while the legacy URL is still present.
3065
+ await updateJsonLocked(configPath, async (latestConfig) => {
3066
+ const hasLegacyBaseUrl = isLegacyInsforgeBaseUrl(latestConfig.baseUrl);
3067
+ const hasUnchangedLegacyAnonKey =
3068
+ legacyBaseUrlMigration.hadPersistedAnonKey &&
3069
+ latestConfig.anonKey === legacyBaseUrlMigration.persistedAnonKey;
3070
+ if (!hasLegacyBaseUrl && !hasUnchangedLegacyAnonKey) return null;
3071
+ if (hasLegacyBaseUrl) {
3072
+ latestConfig.deviceToken = successfulDeviceToken;
3073
+ delete latestConfig.baseUrl;
3074
+ }
3075
+ if (hasUnchangedLegacyAnonKey) {
3076
+ delete latestConfig.anonKey;
3077
+ }
3078
+ return latestConfig;
3079
+ });
2929
3080
  }
2930
3081
  // Record success so the exponential backoff step resets — otherwise
2931
3082
  // a single past failure keeps us pessimistically throttled forever.
@@ -3138,6 +3289,7 @@ module.exports = {
3138
3289
  repairDroidDuplicateSessionInflation,
3139
3290
  repairMimoClaudeMislabel,
3140
3291
  repairZcodeNativeUsageMigration,
3292
+ repairZcodeInclusiveTokenMigration,
3141
3293
  reincludeClaudeMemObserverFiles,
3142
3294
  repairGrokQueueFromSessionSnapshots,
3143
3295
  applyCloudConversationsBackfill,
@@ -3613,7 +3765,7 @@ const AUTO_RETRY_MAX_DELAY_MS = 2 * 60 * 60 * 1000;
3613
3765
  const INGEST_SLUG = "tokentracker-ingest";
3614
3766
  const MAX_INGEST_BUCKETS = 500;
3615
3767
 
3616
- async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePath, maxBatches = 5, batchSize = 200 }) {
3768
+ async function drainQueueToCloud({ baseUrl, anonKey, deviceToken, queuePath, queueStatePath, maxBatches = 5, batchSize = 200 }) {
3617
3769
  const state = (await readJson(queueStatePath)) || { offset: 0 };
3618
3770
  let offset = Number(state.offset || 0);
3619
3771
  let inserted = 0;
@@ -3632,7 +3784,6 @@ async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePa
3632
3784
  if (result.buckets.length === 0 && result.sessionStates.length === 0) break;
3633
3785
 
3634
3786
  const root = baseUrl.replace(/\/$/, "");
3635
- const anonKey = process.env.TOKENTRACKER_INSFORGE_ANON_KEY || "";
3636
3787
  const headers = {
3637
3788
  "Content-Type": "application/json",
3638
3789
  Accept: "application/json",
@@ -4116,7 +4267,7 @@ async function repairMimoClaudeMislabel({
4116
4267
  return true;
4117
4268
  }
4118
4269
 
4119
- async function resetUploadOffsetForZcodeNativeRepair(queueStatePath) {
4270
+ async function resetUploadOffsetForZcodeRepair(queueStatePath, note) {
4120
4271
  if (typeof queueStatePath !== "string" || !queueStatePath) return false;
4121
4272
  let state = {};
4122
4273
  try {
@@ -4126,7 +4277,7 @@ async function resetUploadOffsetForZcodeNativeRepair(queueStatePath) {
4126
4277
  }
4127
4278
  state.offset = 0;
4128
4279
  state.updatedAt = new Date().toISOString();
4129
- state.note = "reset_after_zcode_native_usage_repair_2026_08";
4280
+ state.note = note;
4130
4281
  await ensureDir(path.dirname(queueStatePath));
4131
4282
  await fs.writeFile(queueStatePath, JSON.stringify(state, null, 2) + "\n", "utf8");
4132
4283
  return true;
@@ -4188,7 +4339,9 @@ async function dropZcodeQueueRows(filePath, { retainRetractions = false } = {})
4188
4339
  return { removed, retractions: retractions.size };
4189
4340
  }
4190
4341
 
4191
- async function repairZcodeNativeUsageMigration({
4342
+ async function repairZcodeUsageMigration({
4343
+ migrationKey,
4344
+ queueStateNote,
4192
4345
  cursors,
4193
4346
  queuePath,
4194
4347
  queueStatePath,
@@ -4197,7 +4350,7 @@ async function repairZcodeNativeUsageMigration({
4197
4350
  } = {}) {
4198
4351
  if (!cursors || typeof cursors !== "object") return false;
4199
4352
  const migrations = (cursors.migrations ||= {});
4200
- if (migrations[ZCODE_NATIVE_USAGE_REPAIR_KEY]) return false;
4353
+ if (migrations[migrationKey]) return false;
4201
4354
 
4202
4355
  const mainRepair = await dropZcodeQueueRows(queuePath, { retainRetractions: true });
4203
4356
  const projectRepair = projectQueuePath
@@ -4225,11 +4378,13 @@ async function repairZcodeNativeUsageMigration({
4225
4378
  }
4226
4379
  delete cursors.zcode;
4227
4380
 
4228
- if (mainRepair.removed > 0) await resetUploadOffsetForZcodeNativeRepair(queueStatePath);
4381
+ if (mainRepair.removed > 0) {
4382
+ await resetUploadOffsetForZcodeRepair(queueStatePath, queueStateNote);
4383
+ }
4229
4384
  if (projectRepair.removed > 0) {
4230
- await resetUploadOffsetForZcodeNativeRepair(projectQueueStatePath);
4385
+ await resetUploadOffsetForZcodeRepair(projectQueueStatePath, queueStateNote);
4231
4386
  }
4232
- migrations[ZCODE_NATIVE_USAGE_REPAIR_KEY] = {
4387
+ migrations[migrationKey] = {
4233
4388
  appliedAt: new Date().toISOString(),
4234
4389
  removedMain: mainRepair.removed,
4235
4390
  removedProject: projectRepair.removed,
@@ -4238,6 +4393,22 @@ async function repairZcodeNativeUsageMigration({
4238
4393
  return true;
4239
4394
  }
4240
4395
 
4396
+ async function repairZcodeNativeUsageMigration(options = {}) {
4397
+ return repairZcodeUsageMigration({
4398
+ ...options,
4399
+ migrationKey: ZCODE_NATIVE_USAGE_REPAIR_KEY,
4400
+ queueStateNote: "reset_after_zcode_native_usage_repair_2026_08",
4401
+ });
4402
+ }
4403
+
4404
+ async function repairZcodeInclusiveTokenMigration(options = {}) {
4405
+ return repairZcodeUsageMigration({
4406
+ ...options,
4407
+ migrationKey: ZCODE_INCLUSIVE_TOKEN_REPAIR_KEY,
4408
+ queueStateNote: "reset_after_zcode_inclusive_token_repair_2026_09",
4409
+ });
4410
+ }
4411
+
4241
4412
  async function repairGrokQueueFromSessionSnapshots({ cursors, queuePath, queueStatePath } = {}) {
4242
4413
  if (!cursors || typeof cursors !== "object") return false;
4243
4414
  const grokState = (cursors.grok ||= {});