tokentracker-cli 0.88.6 → 0.88.8

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 (110) hide show
  1. package/README.de.md +4 -4
  2. package/README.ja.md +3 -3
  3. package/README.ko.md +3 -3
  4. package/README.md +6 -5
  5. package/README.zh-CN.md +5 -4
  6. package/dashboard/dist/assets/{AchievementBadge-CgVttPJy.js → AchievementBadge-CzfK_8kl.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-2MeyWMmO.js → AchievementsPage-PVft5lyI.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-hZGNcWfs.js → AchievementsSection-DyzHGzys.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-DBMexTtI.js → BadgeDetailModal-CFsRpXG1.js} +1 -1
  10. package/dashboard/dist/assets/{Card-Ca6NJlW2.js → Card-Bi9dmQTP.js} +1 -1
  11. package/dashboard/dist/assets/{ClawdAnimated-D5GOjZtF.js → ClawdAnimated-BxcZjV9b.js} +1 -1
  12. package/dashboard/dist/assets/{CommandPalette-CPLhHuIS.js → CommandPalette-DHVK5yTu.js} +1 -1
  13. package/dashboard/dist/assets/{CommunityStatsModal-BFyZtlgh.js → CommunityStatsModal-BmMh7Avp.js} +1 -1
  14. package/dashboard/dist/assets/{ConfirmModal-DzpUyVbg.js → ConfirmModal-BjTQXMg4.js} +1 -1
  15. package/dashboard/dist/assets/{Controls-CIPVemCR.js → Controls-CZUbEggT.js} +1 -1
  16. package/dashboard/dist/assets/DashboardPage-C3x4Hew6.js +1 -0
  17. package/dashboard/dist/assets/{DevicePage-CORcVYoS.js → DevicePage-1qf6aiEH.js} +1 -1
  18. package/dashboard/dist/assets/{DialogClose-BMizP3FN.js → DialogClose-CRrlkVf0.js} +1 -1
  19. package/dashboard/dist/assets/DialogDescription-tD0gEESM.js +1 -0
  20. package/dashboard/dist/assets/{DialogTitle-CAlyWmQy.js → DialogTitle-DhGyZ0Ue.js} +1 -1
  21. package/dashboard/dist/assets/{FadeIn-R8uHDC1n.js → FadeIn-M8ECNbPG.js} +1 -1
  22. package/dashboard/dist/assets/{HeaderGithubStar-D-H9BYBq.js → HeaderGithubStar-BKtQc1Si.js} +1 -1
  23. package/dashboard/dist/assets/{HoverTooltip-C6D0Qf3J.js → HoverTooltip-Bo1zVtke.js} +1 -1
  24. package/dashboard/dist/assets/{IpCheckPage-BDKN_XOv.js → IpCheckPage-B78HcgA1.js} +1 -1
  25. package/dashboard/dist/assets/{LandingPage-CVS7Gi16.js → LandingPage-DSlanH3Y.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardAvatar-By9ZRxCx.js → LeaderboardAvatar-Cs4IYwTC.js} +1 -1
  27. package/dashboard/dist/assets/{LeaderboardPage-B2FWLDbM.js → LeaderboardPage-DXlo-PNE.js} +2 -2
  28. package/dashboard/dist/assets/{LeaderboardProfileModal-bKfAVxsg.js → LeaderboardProfileModal-CVM6FjLt.js} +3 -3
  29. package/dashboard/dist/assets/{LeaderboardProfilePage-NSZpmCOS.js → LeaderboardProfilePage-BO-MTMcH.js} +1 -1
  30. package/dashboard/dist/assets/{LimitsPage-DEeKWZ3u.js → LimitsPage-BohIYn9J.js} +1 -1
  31. package/dashboard/dist/assets/{LocalOnlyNotice-CJRoGtCr.js → LocalOnlyNotice-BQ7eYwB8.js} +1 -1
  32. package/dashboard/dist/assets/{LoginCard-DsotnrVc.js → LoginCard-DhOJyCcc.js} +1 -1
  33. package/dashboard/dist/assets/{LoginModal-CBzSJfD-.js → LoginModal-Dfo2DSA9.js} +1 -1
  34. package/dashboard/dist/assets/{LoginPage-Dr7dP5R6.js → LoginPage-CN-cXdsD.js} +1 -1
  35. package/dashboard/dist/assets/{PetPage-D_FQtbbK.js → PetPage-CQY2YUNm.js} +1 -1
  36. package/dashboard/dist/assets/{PopoverPopup-BBIGg4i-.js → PopoverPopup-D0bYgqkq.js} +1 -1
  37. package/dashboard/dist/assets/{ProviderIcon-C6NT8xCP.js → ProviderIcon-BfiriW8d.js} +1 -1
  38. package/dashboard/dist/assets/{ResetPasswordPage-BbDZdmyR.js → ResetPasswordPage-CQyX1BF6.js} +1 -1
  39. package/dashboard/dist/assets/{SegmentedControl-C0YbTO7C.js → SegmentedControl-DWhf_XGR.js} +1 -1
  40. package/dashboard/dist/assets/{Select-CO90ysTb.js → Select-Dy6krqIZ.js} +1 -1
  41. package/dashboard/dist/assets/{SelectItemText-CI5GNKhv.js → SelectItemText-D1296h92.js} +1 -1
  42. package/dashboard/dist/assets/{ServiceStatusPage-CV71dVHd.js → ServiceStatusPage-BgC3i1y-.js} +1 -1
  43. package/dashboard/dist/assets/{SessionsPage-B5jvyh-X.js → SessionsPage-Dyq0Q8tm.js} +1 -1
  44. package/dashboard/dist/assets/{SettingsPage-DokT0qhX.js → SettingsPage-QCZksfny.js} +1 -1
  45. package/dashboard/dist/assets/{SkillsPage-BYAAiPpI.js → SkillsPage-BUWMr2_o.js} +1 -1
  46. package/dashboard/dist/assets/{TokenGalaxy-m_5kxlC_.js → TokenGalaxy-BHauFZfa.js} +1 -1
  47. package/dashboard/dist/assets/{ToolbarRootContext-TYR3c0G7.js → ToolbarRootContext-1sViYfay.js} +1 -1
  48. package/dashboard/dist/assets/{TrendMonitor-DjVbRbLd.js → TrendMonitor-DM26Zn7d.js} +1 -1
  49. package/dashboard/dist/assets/{WidgetsPage-DwImvB2Y.js → WidgetsPage-DhjPczRN.js} +1 -1
  50. package/dashboard/dist/assets/{WrappedPage-CEcqtP_W.js → WrappedPage-BsEFzfrx.js} +1 -1
  51. package/dashboard/dist/assets/{__vite-browser-external-BOpcJ7Bn.js → __vite-browser-external-BW4ueyBB.js} +6 -3
  52. package/dashboard/dist/assets/agent-logos-IRTgTlkV.js +1 -0
  53. package/dashboard/dist/assets/{arrow-up-right-C0nGIX35.js → arrow-up-right-BapNuXPQ.js} +1 -1
  54. package/dashboard/dist/assets/{check-Cpk8oXcn.js → check-lpIv67Hh.js} +1 -1
  55. package/dashboard/dist/assets/{chevron-right-DsKtZU0a.js → chevron-right-jYAPg2OX.js} +1 -1
  56. package/dashboard/dist/assets/{copy-COeB2IQx.js → copy-CEdsXYF2.js} +1 -1
  57. package/dashboard/dist/assets/{download-Dvw5E6xA.js → download-CGJA0mZj.js} +1 -1
  58. package/dashboard/dist/assets/{external-link-COe8flh4.js → external-link-DTmmieCB.js} +1 -1
  59. package/dashboard/dist/assets/{flame-Dza_-WNE.js → flame-C-orSEmd.js} +1 -1
  60. package/dashboard/dist/assets/{format-tokens-CKDbxlqC.js → format-tokens-BQuK3UBV.js} +1 -1
  61. package/dashboard/dist/assets/{icons-Dj3nlViF.js → icons-DuVOMXCd.js} +1 -1
  62. package/dashboard/dist/assets/{index-DvR1xY_C.js → index-5hluBV6Y.js} +1 -1
  63. package/dashboard/dist/assets/{index-CBgWOlov.js → index-CKG-dDk-.js} +1 -1
  64. package/dashboard/dist/assets/{index-DCpYik68.js → index-DvLGX2GN.js} +1 -1
  65. package/dashboard/dist/assets/{info-B9M6Luq-.js → info-EKHZ3tlo.js} +1 -1
  66. package/dashboard/dist/assets/{limits-providers-CAZ_eIVT.js → limits-providers-DgnwvabV.js} +1 -1
  67. package/dashboard/dist/assets/{link-2-PL-NJZyN.js → link-2-A2f7492I.js} +1 -1
  68. package/dashboard/dist/assets/{loader-circle-CQ-akiLL.js → loader-circle-DiBM_0cy.js} +1 -1
  69. package/dashboard/dist/assets/{maximize-2-9EZoXvlF.js → maximize-2-CmJrrSbJ.js} +1 -1
  70. package/dashboard/dist/assets/{modifiers.esm-Bc1a9Bqm.js → modifiers.esm-v5P5znue.js} +1 -1
  71. package/dashboard/dist/assets/{monitor-smartphone-DR9MHu8R.js → monitor-smartphone-jmB0EaLx.js} +1 -1
  72. package/dashboard/dist/assets/{provider-display-DAH8KAyP.js → provider-display-BJXN_8Ft.js} +2 -2
  73. package/dashboard/dist/assets/{react-gkeEFJtr.js → react-DRhZt4b6.js} +1 -1
  74. package/dashboard/dist/assets/{search-B67Xwb-N.js → search-D9j_51N0.js} +1 -1
  75. package/dashboard/dist/assets/{skills-api-aZca3hZV.js → skills-api-Dywy4E7M.js} +1 -1
  76. package/dashboard/dist/assets/{sortable.esm-Crm57xTH.js → sortable.esm-BYYlH2M3.js} +1 -1
  77. package/dashboard/dist/assets/{store-DisKKZeI.js → store-Dt6GNo2E.js} +1 -1
  78. package/dashboard/dist/assets/{terminal-DGueqM2Q.js → terminal-BfWjr1gW.js} +1 -1
  79. package/dashboard/dist/assets/{trash-2-b5d9XdRV.js → trash-2-Ceal5Y6z.js} +1 -1
  80. package/dashboard/dist/assets/{use-community-stats-DOPcq80h.js → use-community-stats-2G4i1ZwJ.js} +1 -1
  81. package/dashboard/dist/assets/{use-limits-display-prefs-DGfPkvee.js → use-limits-display-prefs--qpfQM72.js} +1 -1
  82. package/dashboard/dist/assets/{use-ordered-list-Dyul6pNM.js → use-ordered-list-BRg8W35v.js} +1 -1
  83. package/dashboard/dist/assets/use-reduced-motion-BDYfqZDy.js +1 -0
  84. package/dashboard/dist/assets/{use-session-efficiency-pref-BPzM7u7L.js → use-session-efficiency-pref-DHwJtj_8.js} +1 -1
  85. package/dashboard/dist/assets/{use-transform-C5yhWdHM.js → use-transform-DaIhV0Zy.js} +1 -1
  86. package/dashboard/dist/assets/{use-usage-limits-C6S69l9t.js → use-usage-limits-COggDJdz.js} +1 -1
  87. package/dashboard/dist/assets/{useCurrency-BfJWbenA.js → useCurrency-0g-kQuZf.js} +1 -1
  88. package/dashboard/dist/assets/{useScrollLock-Btq-Jtjh.js → useScrollLock-CMMjcyJf.js} +1 -1
  89. package/dashboard/dist/assets/{useTokenFormat-DaXiI8qf.js → useTokenFormat-BuVZXkVC.js} +1 -1
  90. package/dashboard/dist/assets/{zap-DMOIXlpH.js → zap-BhYCdFZP.js} +1 -1
  91. package/dashboard/dist/brand-logos/reasonix.png +0 -0
  92. package/dashboard/dist/index.html +9 -9
  93. package/dashboard/dist/ip-check.html +1 -1
  94. package/dashboard/dist/leaderboard.html +1 -1
  95. package/dashboard/dist/llms.txt +3 -2
  96. package/dashboard/dist/share.html +1 -1
  97. package/package.json +2 -2
  98. package/src/commands/init.js +10 -0
  99. package/src/commands/status.js +27 -5
  100. package/src/commands/sync.js +177 -5
  101. package/src/lib/install-resolver.js +35 -0
  102. package/src/lib/opencode-go-limits.js +122 -13
  103. package/src/lib/pricing/seed-snapshot.json +1 -1
  104. package/src/lib/rollout.js +768 -0
  105. package/src/lib/subscriptions.js +1 -0
  106. package/src/lib/usage-limits.js +17 -2
  107. package/dashboard/dist/assets/DashboardPage-h2LfrNwp.js +0 -1
  108. package/dashboard/dist/assets/DialogDescription-B_xemJjT.js +0 -1
  109. package/dashboard/dist/assets/agent-logos-Cwc0bFNx.js +0 -1
  110. package/dashboard/dist/assets/use-reduced-motion-UUonF8mY.js +0 -1
@@ -62,6 +62,8 @@ const {
62
62
  piAgentDirCollidesWithOmp,
63
63
  resolveCraftSessionFiles,
64
64
  resolveCraftConfigDir,
65
+ resolveReasonixHome,
66
+ resolveReasonixTelemetryFiles,
65
67
  resolveKilocodeTaskFiles,
66
68
  resolveRoocodeTaskFiles,
67
69
  resolveZedDbPath,
@@ -72,6 +74,8 @@ const {
72
74
  resolveGooseDbPath,
73
75
  listDroidSettingsFiles,
74
76
  resolveDroidSessionsDir,
77
+ resolveDshHome,
78
+ resolveDshSessionFiles,
75
79
  resolveTraeStoragePath,
76
80
  readTraeEntitlementFromStorage,
77
81
  resolveGrokBuildSessions,
@@ -85,7 +89,7 @@ const {
85
89
  } = require("../lib/rollout");
86
90
  const wsl = require("../lib/wsl-probe");
87
91
  const { getWslMode, isInvalidWslMode, shouldProbeWsl, discoverWslHome } = wsl;
88
- const { resolveInstallPaths } = require("../lib/install-resolver");
92
+ const { resolveInstallPaths, resolveZcodeNativeDbPath } = require("../lib/install-resolver");
89
93
  const { probeGrokHookState, resolveGrokHome } = require("../lib/grok-hook");
90
94
  const { probeOmpHookState } = require("../lib/omp-hook");
91
95
 
@@ -428,6 +432,11 @@ async function cmdStatus(argv = []) {
428
432
  const craftInstalled = Boolean(craftConfigDir && fssync.existsSync(craftConfigDir));
429
433
  const craftFiles = craftInstalled ? resolveCraftSessionFiles(process.env) : [];
430
434
 
435
+ // Reasonix — passive scan of content-free cumulative telemetry sidecars.
436
+ const reasonixHome = resolveReasonixHome(process.env);
437
+ const reasonixInstalled = Boolean(reasonixHome && fssync.existsSync(reasonixHome));
438
+ const reasonixFiles = reasonixInstalled ? resolveReasonixTelemetryFiles(process.env) : [];
439
+
431
440
  // Kilo CLI (kilo.ai @kilocode/plugin) — passive scan of kilo.db.
432
441
  const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
433
442
  const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
@@ -456,10 +465,7 @@ async function cmdStatus(argv = []) {
456
465
  const mimoDbPath = mimoActive.join(" | ");
457
466
 
458
467
  // ZCode (Z.ai's coding agent — OpenCode-fork SQLite) — passive scan of db.sqlite.
459
- const zcodeHome = process.env.ZCODE_HOME || path.join(home, ".zcode");
460
- const zcodeNativeValue = process.platform === "win32" && typeof process.env.APPDATA === "string"
461
- ? path.join(process.env.APPDATA.trim(), ".zcode", "cli", "db", "db.sqlite")
462
- : path.join(zcodeHome, "cli", "db", "db.sqlite");
468
+ const zcodeNativeValue = resolveZcodeNativeDbPath({ home });
463
469
  const wslZcodeDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
464
470
  ? wsl.discoverWslHome(".zcode")
465
471
  : null;
@@ -613,6 +619,10 @@ async function cmdStatus(argv = []) {
613
619
  const droidSessionsDir = resolveDroidSessionsDir(process.env);
614
620
  const droidSettingsFiles = listDroidSettingsFiles(process.env);
615
621
  const droidInstalled = droidSettingsFiles.length > 0;
622
+ const dshHome = resolveDshHome(process.env);
623
+ const dshSessionsDir = path.join(dshHome, "sessions");
624
+ const dshSessionFiles = await resolveDshSessionFiles(process.env);
625
+ const dshInstalled = dshSessionFiles.length > 0;
616
626
 
617
627
  // Trae SOLO (ByteDance AI IDE) — passive entitlement snapshot reader.
618
628
  const traeStoragePath = resolveTraeStoragePath(process.env);
@@ -866,6 +876,9 @@ async function cmdStatus(argv = []) {
866
876
  craft: craftInstalled
867
877
  ? { installed: true, files: craftFiles.length }
868
878
  : { installed: false },
879
+ reasonix: reasonixInstalled
880
+ ? { installed: true, files: reasonixFiles.length }
881
+ : { installed: false },
869
882
  anythingllm: anythingllmInstalled
870
883
  ? { installed: true, detail: anythingllmDbPath }
871
884
  : { installed: false },
@@ -900,6 +913,9 @@ async function cmdStatus(argv = []) {
900
913
  droid: droidInstalled
901
914
  ? { installed: true, files: droidSettingsFiles.length, detail: droidSessionsDir }
902
915
  : { installed: false },
916
+ dsh: dshInstalled
917
+ ? { installed: true, files: dshSessionFiles.length, detail: dshSessionsDir }
918
+ : { installed: false },
903
919
  trae: traeInstalled
904
920
  ? {
905
921
  installed: true,
@@ -1001,6 +1017,9 @@ async function cmdStatus(argv = []) {
1001
1017
  craftInstalled
1002
1018
  ? `- Craft Agents: passive reader (${craftFiles.length} session jsonl file${craftFiles.length !== 1 ? "s" : ""} found)`
1003
1019
  : null,
1020
+ reasonixInstalled
1021
+ ? `- Reasonix: passive reader (${reasonixFiles.length} telemetry file${reasonixFiles.length !== 1 ? "s" : ""} found)`
1022
+ : null,
1004
1023
  anythingllmInstalled
1005
1024
  ? `- AnythingLLM Desktop: passive reader (${anythingllmDbPath})`
1006
1025
  : null,
@@ -1059,6 +1078,9 @@ async function cmdStatus(argv = []) {
1059
1078
  droidInstalled
1060
1079
  ? `- Droid (Factory): passive reader (${droidSettingsFiles.length} session${droidSettingsFiles.length !== 1 ? "s" : ""} in ${droidSessionsDir}, cumulative-delta)`
1061
1080
  : null,
1081
+ dshInstalled
1082
+ ? `- DeepSeek Harness: passive reader (${dshSessionFiles.length} session${dshSessionFiles.length !== 1 ? "s" : ""} in ${dshSessionsDir})`
1083
+ : null,
1062
1084
  traeInstalled
1063
1085
  // Deliberately NOT "passive reader": every other line with that wording
1064
1086
  // means tokens are being counted. Trae encrypts its session transcripts
@@ -5,7 +5,7 @@ const fssync = require("node:fs");
5
5
  const cp = require("node:child_process");
6
6
  const readline = require("node:readline");
7
7
 
8
- const { resolveInstallPaths, ensureFlatCursor } = require("../lib/install-resolver");
8
+ const { resolveInstallPaths, resolveZcodeNativeDbPath, ensureFlatCursor } = require("../lib/install-resolver");
9
9
  const { multiInstallParse, mergeBothFileSources } = require("../lib/multi-install-parser");
10
10
  const wsl = require("../lib/wsl-probe");
11
11
  const {
@@ -80,6 +80,8 @@ const {
80
80
  piAgentDirCollidesWithOmp,
81
81
  resolveCraftSessionFiles,
82
82
  parseCraftIncremental,
83
+ resolveReasonixTelemetryFiles,
84
+ parseReasonixIncremental,
83
85
  resolveGrokBuildSessions,
84
86
  parseGrokBuildIncremental,
85
87
  listAntigravityTranscripts,
@@ -106,6 +108,8 @@ const {
106
108
  parseDroidIncremental,
107
109
  droidSessionIdFromPath,
108
110
  resolveDroidModel,
111
+ resolveDshSessionFiles,
112
+ parseDshIncremental,
109
113
  bucketKey,
110
114
  toUtcHalfHourStart,
111
115
  totalsKey,
@@ -244,8 +248,10 @@ const CODEX_COLD_SCAN_AUDIT_MAX_SYNCS = 288;
244
248
  // one-time repair purges all source=mimo data from the local queues + cursor
245
249
  // state so the next sync (providerID-filtered reader) rebuilds it correctly.
246
250
  const MIMO_PROVIDER_REPAIR_KEY = "mimoClaudeMislabelRepair_2026_06";
251
+ const DSH_LEGACY_SOURCE_MIGRATION_KEY = "deepseekHarnessSourceMigration_2026_08";
247
252
  const AUTO_SYNC_SOURCE_ALIASES = new Map([
248
253
  ["code", "every-code"],
254
+ ["deepseek", "dsh"],
249
255
  ["everycode", "every-code"],
250
256
  ["kilo", "kilo-cli"],
251
257
  ["kilo-code", "kilocode"],
@@ -263,6 +269,7 @@ const AUTO_SYNC_SOURCES = new Set([
263
269
  "craft",
264
270
  "cursor",
265
271
  "droid",
272
+ "dsh",
266
273
  "every-code",
267
274
  "gemini",
268
275
  "goose",
@@ -280,6 +287,7 @@ const AUTO_SYNC_SOURCES = new Set([
280
287
  "pi",
281
288
  "qoder",
282
289
  "qoder-cn",
290
+ "reasonix",
283
291
  "roocode",
284
292
  "workbuddy",
285
293
  "zcode",
@@ -289,6 +297,7 @@ const BACKGROUND_AUTO_SYNC_SOURCES = new Set([
289
297
  // Keep unscoped native 5-minute syncs bounded to dated local session trees.
290
298
  "codex",
291
299
  "every-code",
300
+ "reasonix",
292
301
  ]);
293
302
 
294
303
  function warnProviderParseFailure(label, err, opts) {
@@ -576,7 +585,6 @@ async function cmdSync(argv, context = {}) {
576
585
  const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
577
586
  const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
578
587
  const mimoHome = process.env.MIMO_HOME || path.join(xdgDataHome, "mimocode");
579
- const zcodeHome = process.env.ZCODE_HOME || path.join(home, ".zcode");
580
588
 
581
589
  // OpenClaw session plugin integration: lifecycle hooks request an
582
590
  // OpenClaw-only auto sync so unrelated providers do not get walked.
@@ -1369,9 +1377,7 @@ async function cmdSync(argv, context = {}) {
1369
1377
  // ── ZCode (Z.ai's coding agent — OpenCode-fork SQLite) ──
1370
1378
  let zcodeResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1371
1379
  if (sourceAllowed("zcode")) {
1372
- const zcodeNativeValue = process.platform === "win32" && typeof process.env.APPDATA === "string"
1373
- ? path.join(process.env.APPDATA.trim(), ".zcode", "cli", "db", "db.sqlite")
1374
- : path.join(zcodeHome, "cli", "db", "db.sqlite");
1380
+ const zcodeNativeValue = resolveZcodeNativeDbPath({ home });
1375
1381
  const wslZcodeDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
1376
1382
  ? wsl.discoverWslHome(".zcode")
1377
1383
  : null;
@@ -1398,6 +1404,32 @@ async function cmdSync(argv, context = {}) {
1398
1404
  };
1399
1405
  }
1400
1406
 
1407
+ // ── DeepSeek Harness — passive read of ~/.dsh/sessions session logs ──
1408
+ let dshResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1409
+ if (sourceAllowed("dsh")) {
1410
+ await migrateLegacyDeepseekHarnessSource({ cursors, queuePath, queueStatePath });
1411
+ const dshSessionFiles = await resolveDshSessionFiles(process.env);
1412
+ if (dshSessionFiles.length > 0) {
1413
+ if (progress?.enabled) {
1414
+ progress.start(
1415
+ `Parsing DeepSeek Harness ${renderBar(0)} 0/${formatNumber(
1416
+ dshSessionFiles.length,
1417
+ )} sessions | buckets 0`,
1418
+ );
1419
+ }
1420
+ try {
1421
+ dshResult = await parseDshIncremental({
1422
+ sessionFiles: dshSessionFiles,
1423
+ cursors,
1424
+ queuePath,
1425
+ onProgress: makeProviderProgress("DeepSeek Harness"),
1426
+ });
1427
+ } catch (err) {
1428
+ warnProviderParseFailure("DeepSeek Harness", err, opts);
1429
+ }
1430
+ }
1431
+ }
1432
+
1401
1433
  // ── AnythingLLM Desktop (workspace_chats.response.metrics) ──
1402
1434
  let anythingllmResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1403
1435
  if (sourceAllowed("anythingllm")) {
@@ -2075,6 +2107,25 @@ async function cmdSync(argv, context = {}) {
2075
2107
  }
2076
2108
  }
2077
2109
 
2110
+ // Reasonix — content-free cumulative telemetry sidecars only.
2111
+ let reasonixResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
2112
+ const reasonixFiles = sourceAllowed("reasonix")
2113
+ ? mergeBothFileSources({ resolveFiles: resolveReasonixTelemetryFiles, env: process.env })
2114
+ : [];
2115
+ if (reasonixFiles.length > 0) {
2116
+ try {
2117
+ reasonixResult = await parseReasonixIncremental({
2118
+ telemetryFiles: reasonixFiles,
2119
+ cursors,
2120
+ queuePath,
2121
+ env: process.env,
2122
+ onProgress: makeProviderProgress("Reasonix"),
2123
+ });
2124
+ } catch (err) {
2125
+ warnProviderParseFailure("Reasonix", err, opts);
2126
+ }
2127
+ }
2128
+
2078
2129
  // ── Grok Build (xAI) ──
2079
2130
  let grokResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
2080
2131
  // Full passive scan of all Grok sessions (historical + any not covered by hook)
@@ -2513,6 +2564,7 @@ async function cmdSync(argv, context = {}) {
2513
2564
  ompResult.recordsProcessed +
2514
2565
  piResult.recordsProcessed +
2515
2566
  craftResult.recordsProcessed +
2567
+ reasonixResult.recordsProcessed +
2516
2568
  grokResult.recordsProcessed +
2517
2569
  copilotResult.recordsProcessed +
2518
2570
  anythingllmResult.recordsProcessed +
@@ -2523,6 +2575,7 @@ async function cmdSync(argv, context = {}) {
2523
2575
  roocodeResult.recordsProcessed +
2524
2576
  zedResult.recordsProcessed +
2525
2577
  gooseResult.recordsProcessed +
2578
+ dshResult.recordsProcessed +
2526
2579
  droidResult.recordsProcessed;
2527
2580
  const totalBuckets =
2528
2581
  parseResult.bucketsQueued +
@@ -2545,6 +2598,7 @@ async function cmdSync(argv, context = {}) {
2545
2598
  ompResult.bucketsQueued +
2546
2599
  piResult.bucketsQueued +
2547
2600
  craftResult.bucketsQueued +
2601
+ reasonixResult.bucketsQueued +
2548
2602
  grokResult.bucketsQueued +
2549
2603
  copilotResult.bucketsQueued +
2550
2604
  anythingllmResult.bucketsQueued +
@@ -2555,6 +2609,7 @@ async function cmdSync(argv, context = {}) {
2555
2609
  roocodeResult.bucketsQueued +
2556
2610
  zedResult.bucketsQueued +
2557
2611
  gooseResult.bucketsQueued +
2612
+ dshResult.bucketsQueued +
2558
2613
  droidResult.bucketsQueued;
2559
2614
  const skipNoOpCursorCommit =
2560
2615
  opts.auto &&
@@ -2888,6 +2943,8 @@ module.exports = {
2888
2943
  repairCodebuddyLogJsonlOverlap,
2889
2944
  repairWorkbuddyContextUsage,
2890
2945
  WORKBUDDY_CONTEXT_USAGE_REPAIR_KEY,
2946
+ migrateLegacyDeepseekHarnessSource,
2947
+ DSH_LEGACY_SOURCE_MIGRATION_KEY,
2891
2948
  repairCodexRescanInflation,
2892
2949
  repairCodexForkReplayInflation,
2893
2950
  repairCodexInterleavedUsageInflation,
@@ -2912,6 +2969,121 @@ module.exports = {
2912
2969
  CLOUD_CONVERSATIONS_BACKFILL_KEY,
2913
2970
  };
2914
2971
 
2972
+ // The pre-merge Harness prototype briefly emitted source="deepseek" before
2973
+ // settling on the official CLI id, source="dsh". Relabel the local history,
2974
+ // append explicit zero rows for every old cloud key, and replay from offset 0.
2975
+ // That preserves the latest canonical totals while making remote whole-row
2976
+ // upserts remove the stale alias instead of displaying both sources or summing
2977
+ // the same session twice.
2978
+ async function migrateLegacyDeepseekHarnessSource({ cursors, queuePath, queueStatePath } = {}) {
2979
+ if (!cursors || typeof cursors !== "object") return false;
2980
+ const migrations = (cursors.migrations ||= {});
2981
+ if (migrations[DSH_LEGACY_SOURCE_MIGRATION_KEY]) return false;
2982
+
2983
+ let raw = "";
2984
+ try {
2985
+ raw = await fs.readFile(queuePath, "utf8");
2986
+ } catch (error) {
2987
+ if (error?.code !== "ENOENT") throw error;
2988
+ }
2989
+
2990
+ const output = [];
2991
+ const legacyKeys = new Map();
2992
+ const latestMigratedRows = new Map();
2993
+ const latestExplicitCanonicalRows = new Map();
2994
+ for (const line of raw.split("\n")) {
2995
+ if (!line.trim()) continue;
2996
+ let row;
2997
+ try {
2998
+ row = JSON.parse(line);
2999
+ } catch (_error) {
3000
+ output.push(line);
3001
+ continue;
3002
+ }
3003
+
3004
+ const isLegacy =
3005
+ row?.source === "deepseek" &&
3006
+ typeof row.model === "string" &&
3007
+ typeof row.hour_start === "string";
3008
+ if (isLegacy) {
3009
+ const key = `${row.model}|${row.hour_start}`;
3010
+ legacyKeys.set(key, { model: row.model, hour_start: row.hour_start });
3011
+ row = {
3012
+ ...row,
3013
+ source: "dsh",
3014
+ billable_total_tokens: row.billable_total_tokens ?? row.total_tokens,
3015
+ };
3016
+ }
3017
+ const serialized = JSON.stringify(row);
3018
+ output.push(serialized);
3019
+ if (row?.source === "dsh" && typeof row.model === "string" && typeof row.hour_start === "string") {
3020
+ const key = `${row.model}|${row.hour_start}`;
3021
+ if (isLegacy) latestMigratedRows.set(key, serialized);
3022
+ else latestExplicitCanonicalRows.set(key, serialized);
3023
+ }
3024
+ }
3025
+
3026
+ if (legacyKeys.size === 0) {
3027
+ migrations[DSH_LEGACY_SOURCE_MIGRATION_KEY] = {
3028
+ status: "noop",
3029
+ appliedAt: new Date().toISOString(),
3030
+ reason: "no-legacy-deepseek-rows",
3031
+ };
3032
+ return false;
3033
+ }
3034
+
3035
+ // Re-append the last canonical row for each affected key so local readers'
3036
+ // last-row-wins contract remains correct even when an old alias line was
3037
+ // physically later than an already-emitted dsh row.
3038
+ for (const key of legacyKeys.keys()) {
3039
+ const canonical = latestExplicitCanonicalRows.get(key) || latestMigratedRows.get(key);
3040
+ if (canonical) output.push(canonical);
3041
+ }
3042
+ for (const { model, hour_start } of legacyKeys.values()) {
3043
+ output.push(JSON.stringify({
3044
+ source: "deepseek",
3045
+ model,
3046
+ hour_start,
3047
+ input_tokens: 0,
3048
+ cached_input_tokens: 0,
3049
+ cache_creation_input_tokens: 0,
3050
+ output_tokens: 0,
3051
+ reasoning_output_tokens: 0,
3052
+ total_tokens: 0,
3053
+ billable_total_tokens: 0,
3054
+ conversation_count: 0,
3055
+ }));
3056
+ }
3057
+
3058
+ await ensureDir(path.dirname(queuePath));
3059
+ const tmpPath = `${queuePath}.tmp.${process.pid}.${Date.now()}`;
3060
+ await fs.writeFile(tmpPath, `${output.join("\n")}\n`, "utf8");
3061
+ await fs.rename(tmpPath, queuePath);
3062
+
3063
+ if (typeof queueStatePath === "string" && queueStatePath) {
3064
+ let state = {};
3065
+ try {
3066
+ state = JSON.parse(await fs.readFile(queueStatePath, "utf8"));
3067
+ if (!state || typeof state !== "object") state = {};
3068
+ } catch (_error) {
3069
+ state = {};
3070
+ }
3071
+ state.offset = 0;
3072
+ state.updatedAt = new Date().toISOString();
3073
+ state.note = "reset_after_deepseek_harness_source_migration_2026_08";
3074
+ await ensureDir(path.dirname(queueStatePath));
3075
+ await fs.writeFile(queueStatePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
3076
+ }
3077
+
3078
+ migrations[DSH_LEGACY_SOURCE_MIGRATION_KEY] = {
3079
+ status: "applied",
3080
+ appliedAt: new Date().toISOString(),
3081
+ migratedBuckets: legacyKeys.size,
3082
+ retractedBuckets: legacyKeys.size,
3083
+ };
3084
+ return true;
3085
+ }
3086
+
2915
3087
  function normalizeString(value) {
2916
3088
  if (typeof value !== "string") return null;
2917
3089
  const trimmed = value.trim();
@@ -1,4 +1,5 @@
1
1
  const fssync = require("node:fs");
2
+ const os = require("node:os");
2
3
  const path = require("node:path");
3
4
  const wsl = require("./wsl-probe");
4
5
 
@@ -48,6 +49,39 @@ function pathExists(p, existsSync) {
48
49
  try { return (existsSync || fssync.existsSync)(p) ? p : null; } catch (_e) { return null; }
49
50
  }
50
51
 
52
+ // ZCode stores its CLI DB under the user's home dir on every platform
53
+ // (~/.zcode/cli/db/db.sqlite), Windows included. The win32 APPDATA guess
54
+ // (introduced with the WSL dual-install work) silently missed the native
55
+ // install, so probe home first and keep APPDATA only as a fallback for
56
+ // installs that do live under Roaming. Falls back to the home path when
57
+ // neither exists so resolveInstallPaths can null it uniformly.
58
+ function resolveZcodeNativeDbPath({
59
+ home = os.homedir(),
60
+ env = process.env,
61
+ platform = process.platform,
62
+ deps = {},
63
+ } = {}) {
64
+ const existsSync = deps.existsSync || fssync.existsSync;
65
+ const zcodeHomeOverride =
66
+ typeof env.ZCODE_HOME === "string" ? env.ZCODE_HOME.trim() : "";
67
+ const zcodeHome = zcodeHomeOverride
68
+ ? path.resolve(zcodeHomeOverride)
69
+ : path.join(home, ".zcode");
70
+ // An explicit ZCODE_HOME pins the install: when its database is missing we
71
+ // must not fall back to an APPDATA database, or sync could read a different
72
+ // ZCode installation than the one the user pointed at.
73
+ const candidates = [
74
+ path.join(zcodeHome, "cli", "db", "db.sqlite"),
75
+ ...(!zcodeHomeOverride &&
76
+ platform === "win32" &&
77
+ typeof env.APPDATA === "string" &&
78
+ env.APPDATA.trim()
79
+ ? [path.join(env.APPDATA.trim(), ".zcode", "cli", "db", "db.sqlite")]
80
+ : []),
81
+ ];
82
+ return candidates.find((p) => existsSync(p)) || candidates[0];
83
+ }
84
+
51
85
  function hasAnyChild(p, children, existsSync) {
52
86
  const ex = existsSync || fssync.existsSync;
53
87
  return children.some((c) => { try { return ex(path.join(p, c)); } catch (_e) { return false; } });
@@ -99,6 +133,7 @@ function ensureFlatCursor(cursors, providerName, env, preferredKey) {
99
133
 
100
134
  module.exports = {
101
135
  resolveInstallPaths,
136
+ resolveZcodeNativeDbPath,
102
137
  ensureNamespacedCursors,
103
138
  ensureFlatCursor,
104
139
  };
@@ -5,23 +5,27 @@ const { readSqliteJsonRowsAsync } = require("./sqlite-reader");
5
5
 
6
6
  // OpenCode Go usage limits.
7
7
  //
8
- // Two data sources are available:
8
+ // Three data sources are available, in priority order:
9
9
  //
10
- // 1. Web scrape of the workspace dashboard
11
- // (https://opencode.ai/workspace/<id>/go) for the server's authoritative
12
- // rolling (5h) / weekly / monthly usagePercent and subscription state.
13
- // The cookie is sent as `Cookie: auth=<OPENCODE_GO_AUTH_COOKIE>` per
14
- // slkiser/opencode-quota#41.
10
+ // 1. The official OpenCode Go usage API
11
+ // (GET https://opencode.ai/zen/go/v1/usage) with
12
+ // `Authorization: Bearer <OPENCODE_GO_API_KEY>`. It returns the server's
13
+ // authoritative rolling (5h), weekly, and monthly usage windows.
15
14
  //
16
- // 2. Local opencode.db cost aggregation, explicitly enabled with
15
+ // 2. Web scrape of the workspace dashboard
16
+ // (https://opencode.ai/workspace/<id>/go) for existing users configured
17
+ // with OPENCODE_GO_AUTH_COOKIE. This is retained as a compatibility path.
18
+ //
19
+ // 3. Local opencode.db cost aggregation, explicitly enabled with
17
20
  // TOKENTRACKER_OPENCODE_GO_LOCAL_ESTIMATE=1. The `opencode` CLI records
18
21
  // every Go turn's USD `cost` in SQLite, so cost ÷ Go's dollar caps is a
19
22
  // useful local estimate. It cannot prove that the account still has an
20
23
  // active Go subscription, because the database contains historical turns
21
24
  // but no current entitlement state.
22
25
  //
23
- // The returned shape is shared by both sources. `source` is `'web'` for
24
- // authoritative dashboard data or `'local-estimate'` for the opt-in estimate.
26
+ // The returned shape is shared by all sources. `source` is `'api'` for the
27
+ // official endpoint, `'web'` for the compatibility scrape, or `'local-estimate'`
28
+ // for the opt-in estimate.
25
29
 
26
30
  const SCRAPED_NUMBER_PATTERN = "([0-9]+(?:\\.[0-9]+)?)";
27
31
 
@@ -38,11 +42,12 @@ function windowFieldRegex(windowKey, field) {
38
42
  return new RegExp(`${windowKey}[^}]*?${field}"?\\s*[:=]+\\s*${SCRAPED_NUMBER_PATTERN}`);
39
43
  }
40
44
 
45
+ const GO_USAGE_API_URL = "https://opencode.ai/zen/go/v1/usage";
41
46
  const DASHBOARD_URL_PREFIX = "https://opencode.ai/workspace/";
42
47
  const DASHBOARD_URL_SUFFIX = "/go";
43
48
  const USER_AGENT =
44
49
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Gecko/20100101 Firefox/148.0";
45
- const DEFAULT_SCRAPE_TIMEOUT_MS = 10_000;
50
+ const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
46
51
  const LOCAL_ESTIMATE_ENV = "TOKENTRACKER_OPENCODE_GO_LOCAL_ESTIMATE";
47
52
 
48
53
  function isTruthyEnvValue(value) {
@@ -78,6 +83,13 @@ function readConfig(env = process.env) {
78
83
  return { workspaceId, authCookie };
79
84
  }
80
85
 
86
+ function readApiKey(env = process.env) {
87
+ if (!env || typeof env !== "object") return "";
88
+ return typeof env.OPENCODE_GO_API_KEY === "string"
89
+ ? env.OPENCODE_GO_API_KEY.trim()
90
+ : "";
91
+ }
92
+
81
93
  function clampPercent(value) {
82
94
  if (value === null || value === undefined || value === "") return null;
83
95
  let n = Number(value);
@@ -486,6 +498,84 @@ function localGoResult(local) {
486
498
  };
487
499
  }
488
500
 
501
+ // --- Official usage API (exact server-side usage, API-key-gated) -------------
502
+
503
+ async function fetchOpencodeGoApiLimits({ apiKey, fetchImpl, nowMs, timeoutMs }) {
504
+ let response;
505
+ try {
506
+ response = await withTimeout(fetchImpl, timeoutMs)(GO_USAGE_API_URL, {
507
+ method: "GET",
508
+ headers: {
509
+ Authorization: `Bearer ${apiKey}`,
510
+ Accept: "application/json",
511
+ "User-Agent": USER_AGENT,
512
+ },
513
+ });
514
+ } catch (err) {
515
+ return { configured: true, error: sanitizeMessage(err?.message || err) };
516
+ }
517
+
518
+ if (response.status === 401) {
519
+ return {
520
+ configured: true,
521
+ error: "OpenCode Go API key is missing, invalid, expired, or not entitled to an OpenCode Go subscription.",
522
+ auth_error: true,
523
+ };
524
+ }
525
+ if (response.status === 403) {
526
+ return {
527
+ configured: true,
528
+ error: "OpenCode Go subscription required for this API key.",
529
+ auth_error: true,
530
+ };
531
+ }
532
+ if (!response.ok) {
533
+ return { configured: true, error: `OpenCode Go usage API error ${response.status}` };
534
+ }
535
+
536
+ let payload;
537
+ try {
538
+ payload = JSON.parse(await response.text());
539
+ } catch (err) {
540
+ return {
541
+ configured: true,
542
+ error: `Could not parse OpenCode Go usage API response: ${sanitizeMessage(err?.message || err)}`,
543
+ };
544
+ }
545
+
546
+ const rolling = buildWindow({
547
+ usagePercent: payload?.rollingUsage?.usagePercent,
548
+ resetInSec: payload?.rollingUsage?.resetInSec,
549
+ nowMs,
550
+ });
551
+ const weekly = buildWindow({
552
+ usagePercent: payload?.weeklyUsage?.usagePercent,
553
+ resetInSec: payload?.weeklyUsage?.resetInSec,
554
+ nowMs,
555
+ });
556
+ const monthly = buildWindow({
557
+ usagePercent: payload?.monthlyUsage?.usagePercent,
558
+ resetInSec: payload?.monthlyUsage?.resetInSec,
559
+ nowMs,
560
+ });
561
+
562
+ if (!rolling && !weekly && !monthly) {
563
+ return {
564
+ configured: true,
565
+ error: "OpenCode Go usage API response did not include any known usage windows.",
566
+ };
567
+ }
568
+
569
+ return {
570
+ configured: true,
571
+ error: null,
572
+ subscription_status: "active",
573
+ primary_window: rolling,
574
+ secondary_window: weekly,
575
+ tertiary_window: monthly,
576
+ };
577
+ }
578
+
489
579
  // --- Web scrape (exact server-side usage, cookie-gated) ---------------------
490
580
 
491
581
  async function scrapeOpencodeGoWeb({ cfg, fetchImpl, nowMs, timeoutMs }) {
@@ -571,14 +661,31 @@ async function fetchOpencodeGoLimits({
571
661
  env = process.env,
572
662
  fetchImpl = fetch,
573
663
  nowMs = Date.now(),
574
- timeoutMs = DEFAULT_SCRAPE_TIMEOUT_MS,
664
+ timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
575
665
  sqliteOptions = {},
576
666
  } = {}) {
667
+ const apiKey = readApiKey(env);
577
668
  const cfg = readConfig(env);
578
669
  const allowLocalEstimate = localEstimateEnabled(env);
579
670
 
580
- // A configured cookie gives us the only authoritative subscription state.
581
- // Never replace an explicit inactive page with historical local usage.
671
+ // Prefer the documented API whenever an API key is explicitly configured.
672
+ // An authentication failure is actionable and must not be hidden by silently
673
+ // falling back to a possibly stale cookie. For a transient API failure, an
674
+ // existing cookie remains a compatibility fallback.
675
+ if (apiKey) {
676
+ const api = await fetchOpencodeGoApiLimits({ apiKey, fetchImpl, nowMs, timeoutMs });
677
+ if (!api?.error && (api.primary_window || api.secondary_window || api.tertiary_window)) {
678
+ return { ...api, source: "api" };
679
+ }
680
+ if (api?.auth_error) {
681
+ const { auth_error, ...result } = api;
682
+ return result;
683
+ }
684
+ if (!cfg) return api || { configured: true, error: "OpenCode Go unavailable" };
685
+ }
686
+
687
+ // Keep the cookie-backed dashboard path for existing configurations and as a
688
+ // fallback when the official API is temporarily unavailable.
582
689
  if (cfg) {
583
690
  const web = await scrapeOpencodeGoWeb({ cfg, fetchImpl, nowMs, timeoutMs });
584
691
  if (web?.subscription_status === "inactive") return web;
@@ -602,7 +709,9 @@ async function fetchOpencodeGoLimits({
602
709
 
603
710
  module.exports = {
604
711
  fetchOpencodeGoLimits,
712
+ fetchOpencodeGoApiLimits,
605
713
  readConfig,
714
+ readApiKey,
606
715
  localEstimateEnabled,
607
716
  detectSubscriptionStatus,
608
717
  extractWindows,