tokentracker-cli 0.67.0 → 0.68.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 (52) hide show
  1. package/README.ja.md +2 -0
  2. package/README.ko.md +2 -0
  3. package/README.md +2 -0
  4. package/README.zh-CN.md +2 -0
  5. package/dashboard/dist/assets/{ActivityHeatmap-BLL6xjLG.js → ActivityHeatmap-stlH952K.js} +1 -1
  6. package/dashboard/dist/assets/{Card-BB6LiYM8.js → Card-CofgedFz.js} +1 -1
  7. package/dashboard/dist/assets/{ConfirmModal-CX5gv4tk.js → ConfirmModal-n09o_RU7.js} +1 -1
  8. package/dashboard/dist/assets/{DashboardPage-DMvTS8wX.js → DashboardPage-iBPCT4vZ.js} +1 -1
  9. package/dashboard/dist/assets/{DevicePage-vyJEBiMw.js → DevicePage-BGkZILd3.js} +1 -1
  10. package/dashboard/dist/assets/{DialogTitle-R2g9r9PR.js → DialogTitle-BbxEzA03.js} +1 -1
  11. package/dashboard/dist/assets/{FadeIn-CQUbHzNN.js → FadeIn-E5ehYYk1.js} +1 -1
  12. package/dashboard/dist/assets/{HeaderGithubStar-FwJX-pXB.js → HeaderGithubStar-CndrILKO.js} +1 -1
  13. package/dashboard/dist/assets/{IpCheckPage-D_-1u2LS.js → IpCheckPage-5E_xMiUW.js} +1 -1
  14. package/dashboard/dist/assets/{LandingPage-C-lWMwXP.js → LandingPage-CgTigoW1.js} +1 -1
  15. package/dashboard/dist/assets/{LeaderboardAvatar-DskcTB9C.js → LeaderboardAvatar-Dvo78IQE.js} +1 -1
  16. package/dashboard/dist/assets/{LeaderboardPage-BjS-vLBW.js → LeaderboardPage-C4ksvNko.js} +2 -2
  17. package/dashboard/dist/assets/{LeaderboardProfileModal-C6k4VVFS.js → LeaderboardProfileModal-UHYujOQG.js} +1 -1
  18. package/dashboard/dist/assets/{LeaderboardProfilePage-o4fbDnzy.js → LeaderboardProfilePage-CNXemSOF.js} +1 -1
  19. package/dashboard/dist/assets/{LimitsPage-qv3ovWi8.js → LimitsPage-DHAbTe14.js} +1 -1
  20. package/dashboard/dist/assets/{LocalOnlyNotice-C1qyMDpn.js → LocalOnlyNotice-DzocMcPr.js} +1 -1
  21. package/dashboard/dist/assets/{LoginPage-BKNFuYEQ.js → LoginPage-CVFvbmek.js} +1 -1
  22. package/dashboard/dist/assets/{PopoverPopup-leUqNKuk.js → PopoverPopup-CH29Dl4m.js} +1 -1
  23. package/dashboard/dist/assets/{ResetPasswordPage-D5yP5dZa.js → ResetPasswordPage-Oj3VwcUT.js} +1 -1
  24. package/dashboard/dist/assets/{Select-Bq8XjKo9.js → Select-COJepoOt.js} +1 -1
  25. package/dashboard/dist/assets/{SelectItemText-BUE4AAIY.js → SelectItemText-B1dgrCIE.js} +1 -1
  26. package/dashboard/dist/assets/{SettingsPage-DXTRXh8r.js → SettingsPage-CG0Eiwb0.js} +1 -1
  27. package/dashboard/dist/assets/{SkillsPage-Cz17a9ep.js → SkillsPage-DAht7mz-.js} +1 -1
  28. package/dashboard/dist/assets/{WidgetsPage-CSYCzAMh.js → WidgetsPage-CCunqqWC.js} +1 -1
  29. package/dashboard/dist/assets/{WrappedPage-BM2BvUUN.js → WrappedPage-taTQKVSr.js} +1 -1
  30. package/dashboard/dist/assets/{agent-logos-DfysM5Ft.js → agent-logos-CQnbfUYM.js} +1 -1
  31. package/dashboard/dist/assets/{arrow-up-right-KJ5nNRaR.js → arrow-up-right-gF_F8D6S.js} +1 -1
  32. package/dashboard/dist/assets/{download-DPMzNjm9.js → download-DD5R30tG.js} +1 -1
  33. package/dashboard/dist/assets/{main-V7ji8RkP.js → main-BS4wXJQk.js} +2 -2
  34. package/dashboard/dist/assets/{use-limits-display-prefs-BhRZ2Vbm.js → use-limits-display-prefs-CWkXT8WC.js} +1 -1
  35. package/dashboard/dist/assets/{use-native-settings-CUwxJneK.js → use-native-settings-CkkMdU8D.js} +1 -1
  36. package/dashboard/dist/assets/{use-ordered-list-Cg8uqKy8.js → use-ordered-list-DnvBYQP_.js} +1 -1
  37. package/dashboard/dist/assets/{use-quality-per-dollar-pref-DFYl9bWd.js → use-quality-per-dollar-pref-Cunr2tA3.js} +1 -1
  38. package/dashboard/dist/assets/{use-usage-limits-TD09WmDE.js → use-usage-limits-DO7Asd2u.js} +1 -1
  39. package/dashboard/dist/assets/{useCurrency-mDQb_32d.js → useCurrency-Bz7Vj74A.js} +1 -1
  40. package/dashboard/dist/assets/{useScrollLock-CP5GKZtC.js → useScrollLock-BnSHhdwy.js} +1 -1
  41. package/dashboard/dist/index.html +1 -1
  42. package/dashboard/dist/share.html +1 -1
  43. package/package.json +1 -1
  44. package/src/commands/serve.js +32 -1
  45. package/src/commands/status.js +2 -2
  46. package/src/commands/sync.js +151 -70
  47. package/src/lib/browser-auth.js +120 -20
  48. package/src/lib/install-resolver.js +62 -0
  49. package/src/lib/multi-install-parser.js +103 -0
  50. package/src/lib/pricing/seed-snapshot.json +1 -1
  51. package/src/lib/rollout.js +104 -40
  52. package/src/lib/wsl-probe.js +21 -0
@@ -7,6 +7,7 @@ const crypto = require("node:crypto");
7
7
  const { ensureDir } = require("./fs");
8
8
  const { readSqliteJsonRows } = require("./sqlite-reader");
9
9
  const wsl = require("./wsl-probe");
10
+ const { resolveInstallPaths } = require("./install-resolver");
10
11
 
11
12
  const DEFAULT_SOURCE = "codex";
12
13
  const DEFAULT_MODEL = "unknown";
@@ -3787,23 +3788,11 @@ function resolveHermesPath(env = process.env, deps = {}) {
3787
3788
  }
3788
3789
  const home = require("node:os").homedir();
3789
3790
  const defaultPath = path.join(home, ".hermes");
3790
- // On Windows, scan BOTH the native install (%LOCALAPPDATA%\hermes) and
3791
- // any WSL distros running Hermes Agent. Each install has its own independent
3792
- // state.db with different sessions, so there is no double-counting risk.
3793
- // TOKENTRACKER_WSL_MODE controls which source takes priority.
3794
3791
  if (process.platform === "win32") {
3795
- let nativePath = null;
3796
- if (wsl.shouldProbeNative(env)) {
3797
- const localAppData = typeof env.LOCALAPPDATA === "string" ? env.LOCALAPPDATA.trim() : "";
3798
- if (localAppData.length > 0) {
3799
- const candidate = path.join(localAppData, "hermes");
3800
- try {
3801
- if (fssync.existsSync(candidate)) nativePath = candidate;
3802
- } catch (_e) { }
3803
- }
3804
- }
3805
- const wslPath = wsl.shouldProbeWsl(env) ? discoverWslHermesHome(deps) : null;
3806
- const picked = wsl.pickWin32Path({ wslValue: wslPath, nativeValue: nativePath, env, platform: "win32" });
3792
+ const localAppData = typeof env.LOCALAPPDATA === "string" ? env.LOCALAPPDATA.trim() : "";
3793
+ const nativeValue = localAppData.length > 0 ? path.join(localAppData, "hermes") : null;
3794
+ const paths = resolveInstallPaths({ nativeValue, wslDir: ".hermes" }, env, deps);
3795
+ const picked = paths.native || paths.wsl;
3807
3796
  if (picked) return picked;
3808
3797
  const mode = wsl.getWslMode(env);
3809
3798
  if (mode === "wsl-only" || mode === "native-only") return null;
@@ -3838,17 +3827,13 @@ function discoverWslHermesHome(deps = {}) {
3838
3827
  return wsl.discoverWslHome(".hermes", deps);
3839
3828
  }
3840
3829
 
3841
- function pickWin32ProviderPath({ env = process.env, nativeValue, wslProviderDir, wslValue }) {
3842
- const nativeCandidate = wsl.shouldProbeNative(env) ? nativeValue : null;
3843
- const wslCandidate = wslValue !== undefined
3844
- ? wslValue
3845
- : (wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(wslProviderDir) : null);
3846
- return wsl.pickWin32Path({
3847
- wslValue: wslCandidate,
3848
- nativeValue: nativeCandidate,
3849
- env,
3850
- platform: "win32",
3851
- });
3830
+ function pickWin32ProviderPath({ env = process.env, nativeValue, wslProviderDir, wslValue, deps = {} }) {
3831
+ const paths = resolveInstallPaths({ nativeValue, wslDir: wslProviderDir, wslValue }, env, deps);
3832
+ return paths.native || paths.wsl;
3833
+ }
3834
+
3835
+ function resolveAllWin32ProviderPaths({ env = process.env, nativeValue, wslProviderDir, wslValue, deps = {} }) {
3836
+ return resolveInstallPaths({ nativeValue, wslDir: wslProviderDir, wslValue }, env, deps);
3852
3837
  }
3853
3838
 
3854
3839
  function resolveHermesDbPath(env = process.env) {
@@ -3936,6 +3921,86 @@ function readHermesSessions(dbPath, lastCompletedEpoch, unfinishedSessionIds = [
3936
3921
  }
3937
3922
  }
3938
3923
 
3924
+ // ── Dual-install cursor ownership probes ────────────────────────────────────
3925
+ // When a flat (pre-namespaced) cursor migrates to per-install namespaces
3926
+ // (multiInstallParse "both" mode), the migration must know which install the
3927
+ // flat cursor was tracking: only then may the OTHER install's namespace start
3928
+ // empty so its full history backfills. Guessing wrong wipes dedup state
3929
+ // (snapshots / sessionTotals / threadTotals) for an already-counted install,
3930
+ // and every re-read session lands in the hourly buckets a second time.
3931
+ // These probes answer "does this install's DB contain the flat cursor's own
3932
+ // ids?" — sampled from the cursor's per-session dedup maps. Any failure or
3933
+ // missing evidence returns false so the caller falls back to seeding every
3934
+ // namespace (bounded backfill loss, never a double count).
3935
+
3936
+ function sqliteDbContainsIds(dbPath, table, ids, sqliteOptions = {}) {
3937
+ if (!dbPath || !Array.isArray(ids) || ids.length === 0) return false;
3938
+ if (!fssync.existsSync(dbPath)) return false;
3939
+ const inList = ids.map(sqliteStringLiteral).join(",");
3940
+ const sql = `SELECT id FROM ${table} WHERE id IN (${inList}) LIMIT 1`;
3941
+
3942
+ let snapshot = null;
3943
+ let effectiveDbPath = dbPath;
3944
+ if (isUncPath(dbPath)) {
3945
+ try {
3946
+ snapshot = snapshotSqliteDb(dbPath);
3947
+ effectiveDbPath = snapshot.path;
3948
+ } catch (_e) { }
3949
+ }
3950
+ try {
3951
+ const rows = readSqliteJsonRows(effectiveDbPath, sql, {
3952
+ label: "InstallProbe",
3953
+ maxBuffer: 1024 * 1024,
3954
+ timeout: 10_000,
3955
+ ...sqliteOptions,
3956
+ });
3957
+ return Array.isArray(rows) && rows.length > 0;
3958
+ } catch (_e) {
3959
+ return false;
3960
+ } finally {
3961
+ if (snapshot) snapshot.cleanup();
3962
+ }
3963
+ }
3964
+
3965
+ // Sample the most recently inserted keys — most likely to still exist in the
3966
+ // DB (providers may delete old sessions, which would blind the probe).
3967
+ function sampleRecentKeys(obj, limit = 16) {
3968
+ if (!obj || typeof obj !== "object") return [];
3969
+ const keys = Object.keys(obj).filter((k) => typeof k === "string" && k.length > 0);
3970
+ return keys.slice(-limit);
3971
+ }
3972
+
3973
+ function gooseInstallOwnsCursor(dbPath, flatState, sqliteOptions) {
3974
+ return sqliteDbContainsIds(dbPath, "sessions", sampleRecentKeys(flatState?.sessionTotals), sqliteOptions);
3975
+ }
3976
+
3977
+ function zedInstallOwnsCursor(dbPath, flatState, sqliteOptions) {
3978
+ return sqliteDbContainsIds(dbPath, "threads", sampleRecentKeys(flatState?.threadTotals), sqliteOptions);
3979
+ }
3980
+
3981
+ function hermesInstallOwnsCursor(hermesPath, flatState, sqliteOptions) {
3982
+ const ids = new Set();
3983
+ const collect = (state) => {
3984
+ if (!state || typeof state !== "object") return;
3985
+ for (const key of sampleRecentKeys(state.snapshots)) ids.add(key);
3986
+ if (Array.isArray(state.unfinishedSessionIds)) {
3987
+ for (const id of state.unfinishedSessionIds) {
3988
+ if (typeof id === "string" && id.length > 0) ids.add(id);
3989
+ }
3990
+ }
3991
+ };
3992
+ collect(flatState);
3993
+ if (flatState?.profiles && typeof flatState.profiles === "object") {
3994
+ for (const profileState of Object.values(flatState.profiles)) collect(profileState);
3995
+ }
3996
+ const sample = [...ids].slice(-16);
3997
+ if (sample.length === 0) return false;
3998
+
3999
+ const dbPaths = resolveAllHermesDBPaths({ hermesPath });
4000
+ const allDbs = [dbPaths.default, ...Object.values(dbPaths.profiles)].filter(Boolean);
4001
+ return allDbs.some((db) => sqliteDbContainsIds(db, "sessions", sample, sqliteOptions));
4002
+ }
4003
+
3939
4004
  function hasLegacyHermesDefaultState(hermesState) {
3940
4005
  return (
3941
4006
  typeof hermesState.lastStartedAt === "number" ||
@@ -6526,13 +6591,11 @@ function resolveZedDbPath(env = process.env) {
6526
6591
  if (process.platform === "win32") {
6527
6592
  const local = env.LOCALAPPDATA || path.join(home, "AppData", "Local");
6528
6593
  const native = path.join(local, "Zed", "threads", "threads.db");
6529
- let nativeExists = null;
6530
- if (wsl.shouldProbeNative(env)) {
6531
- try { if (fssync.existsSync(native)) nativeExists = native; } catch (_e) { }
6532
- }
6533
- const wslDir = wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(".local/share/zed/threads", { env }) : null;
6534
- const wslValue = wslDir ? path.join(wslDir, "threads.db") : null;
6535
- const picked = wsl.pickWin32Path({ wslValue, nativeValue: nativeExists, env, platform: "win32" });
6594
+ const wslThreadsDir = wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(".local/share/zed/threads", { env }) : null;
6595
+ const wslDbPath = wslThreadsDir && fssync.existsSync(path.join(wslThreadsDir, "threads.db"))
6596
+ ? path.join(wslThreadsDir, "threads.db") : null;
6597
+ const paths = resolveInstallPaths({ nativeValue: native, wslValue: wslDbPath }, env);
6598
+ const picked = paths.native || paths.wsl;
6536
6599
  if (picked) return picked;
6537
6600
  const mode = wsl.getWslMode(env);
6538
6601
  return mode === "wsl-only" || mode === "native-only" ? null : native;
@@ -6909,13 +6972,11 @@ function resolveGooseDbPath(env = process.env) {
6909
6972
  } else if (process.platform === "win32") {
6910
6973
  const appData = env.APPDATA || path.join(home, "AppData", "Roaming");
6911
6974
  const native = path.join(appData, "goose", "sessions", "sessions.db");
6912
- let nativeExists = null;
6913
- if (wsl.shouldProbeNative(env)) {
6914
- try { if (fssync.existsSync(native)) nativeExists = native; } catch (_e) { }
6915
- }
6916
6975
  const wslDir = wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(".local/share/goose/sessions", { env }) : null;
6917
- const wslValue = wslDir ? path.join(wslDir, "sessions.db") : null;
6918
- const picked = wsl.pickWin32Path({ wslValue, nativeValue: nativeExists, env, platform: "win32" });
6976
+ const wslValue = wslDir && fssync.existsSync(path.join(wslDir, "sessions.db"))
6977
+ ? path.join(wslDir, "sessions.db") : null;
6978
+ const paths = resolveInstallPaths({ nativeValue: native, wslValue }, env);
6979
+ const picked = paths.native || paths.wsl;
6919
6980
  if (picked) candidates.push(picked);
6920
6981
  for (const c of candidates) {
6921
6982
  if (fssync.existsSync(c)) return c;
@@ -9919,6 +9980,9 @@ module.exports = {
9919
9980
  parseCursorApiIncremental,
9920
9981
  parseKiroIncremental,
9921
9982
  parseHermesIncremental,
9983
+ gooseInstallOwnsCursor,
9984
+ zedInstallOwnsCursor,
9985
+ hermesInstallOwnsCursor,
9922
9986
  parseCopilotIncremental,
9923
9987
  resolveKimiWireFiles,
9924
9988
  resolveKimiDefaultModel,
@@ -16,6 +16,7 @@ const WSL_MODES = new Set([
16
16
  "native-first",
17
17
  "wsl-only",
18
18
  "native-only",
19
+ "both",
19
20
  ]);
20
21
 
21
22
  function defaultRunWsl(args, { utf16 = false } = {}) {
@@ -98,6 +99,7 @@ function pickWin32Path({
98
99
 
99
100
  const mode = getWslMode(env);
100
101
 
102
+ if (mode === "both") return wslValue ?? nativeValue ?? null;
101
103
  if (mode === "wsl-only") return wslValue ?? null;
102
104
  if (mode === "native-only") return nativeValue ?? null;
103
105
  if (mode === "native-first") return nativeValue ?? wslValue ?? null;
@@ -105,6 +107,24 @@ function pickWin32Path({
105
107
  return wslValue ?? nativeValue ?? null;
106
108
  }
107
109
 
110
+ function resolveAllWin32Paths({
111
+ nativeValue,
112
+ wslValue,
113
+ env = process.env,
114
+ platform = process.platform,
115
+ }) {
116
+ if (platform !== "win32") return { native: null, wsl: null };
117
+
118
+ const mode = getWslMode(env);
119
+
120
+ if (mode === "both") {
121
+ return { native: nativeValue ?? null, wsl: wslValue ?? null };
122
+ }
123
+
124
+ const single = pickWin32Path({ wslValue, nativeValue, env, platform });
125
+ return { native: single, wsl: null };
126
+ }
127
+
108
128
  function lookupWslUser(distroName, runWsl, useCache) {
109
129
  if (useCache && _cachedWslUsers.has(distroName)) {
110
130
  return _cachedWslUsers.get(distroName);
@@ -177,5 +197,6 @@ module.exports = {
177
197
  shouldProbeWsl,
178
198
  shouldProbeNative,
179
199
  pickWin32Path,
200
+ resolveAllWin32Paths,
180
201
  normalizeWslMode,
181
202
  };