tokentracker-cli 0.67.0 → 0.68.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 (46) hide show
  1. package/dashboard/dist/assets/{ActivityHeatmap-BLL6xjLG.js → ActivityHeatmap-Cd928Ptz.js} +1 -1
  2. package/dashboard/dist/assets/{Card-BB6LiYM8.js → Card--_I4rbLK.js} +1 -1
  3. package/dashboard/dist/assets/{ConfirmModal-CX5gv4tk.js → ConfirmModal-BJryA878.js} +1 -1
  4. package/dashboard/dist/assets/{DashboardPage-DMvTS8wX.js → DashboardPage-CikzR9-n.js} +1 -1
  5. package/dashboard/dist/assets/{DevicePage-vyJEBiMw.js → DevicePage-Cucg8IJm.js} +1 -1
  6. package/dashboard/dist/assets/{DialogTitle-R2g9r9PR.js → DialogTitle-CBT-8O9t.js} +1 -1
  7. package/dashboard/dist/assets/{FadeIn-CQUbHzNN.js → FadeIn-CJo3CY8P.js} +1 -1
  8. package/dashboard/dist/assets/{HeaderGithubStar-FwJX-pXB.js → HeaderGithubStar-DlIkA0ge.js} +1 -1
  9. package/dashboard/dist/assets/{IpCheckPage-D_-1u2LS.js → IpCheckPage-D11sDL_F.js} +1 -1
  10. package/dashboard/dist/assets/{LandingPage-C-lWMwXP.js → LandingPage-BR1-nUHp.js} +1 -1
  11. package/dashboard/dist/assets/{LeaderboardAvatar-DskcTB9C.js → LeaderboardAvatar-DbGYLk_s.js} +1 -1
  12. package/dashboard/dist/assets/{LeaderboardPage-BjS-vLBW.js → LeaderboardPage-BFPX67-5.js} +2 -2
  13. package/dashboard/dist/assets/{LeaderboardProfileModal-C6k4VVFS.js → LeaderboardProfileModal-BFCT2cD1.js} +1 -1
  14. package/dashboard/dist/assets/{LeaderboardProfilePage-o4fbDnzy.js → LeaderboardProfilePage-kYZ1Pk0J.js} +1 -1
  15. package/dashboard/dist/assets/{LimitsPage-qv3ovWi8.js → LimitsPage-wsAp4jGy.js} +1 -1
  16. package/dashboard/dist/assets/{LocalOnlyNotice-C1qyMDpn.js → LocalOnlyNotice-DH4MorD1.js} +1 -1
  17. package/dashboard/dist/assets/{LoginPage-BKNFuYEQ.js → LoginPage-BNXijxzk.js} +1 -1
  18. package/dashboard/dist/assets/{PopoverPopup-leUqNKuk.js → PopoverPopup-Dqz11HEi.js} +1 -1
  19. package/dashboard/dist/assets/{ResetPasswordPage-D5yP5dZa.js → ResetPasswordPage-CWvCPu5G.js} +1 -1
  20. package/dashboard/dist/assets/{Select-Bq8XjKo9.js → Select-B1n0PW4j.js} +1 -1
  21. package/dashboard/dist/assets/{SelectItemText-BUE4AAIY.js → SelectItemText-BYGw7q1R.js} +1 -1
  22. package/dashboard/dist/assets/{SettingsPage-DXTRXh8r.js → SettingsPage-CCwxdKVg.js} +1 -1
  23. package/dashboard/dist/assets/{SkillsPage-Cz17a9ep.js → SkillsPage-ds_IoVAo.js} +1 -1
  24. package/dashboard/dist/assets/{WidgetsPage-CSYCzAMh.js → WidgetsPage-DCI26iOU.js} +1 -1
  25. package/dashboard/dist/assets/{WrappedPage-BM2BvUUN.js → WrappedPage-CA9NmFM8.js} +1 -1
  26. package/dashboard/dist/assets/{agent-logos-DfysM5Ft.js → agent-logos-BASBNeBF.js} +1 -1
  27. package/dashboard/dist/assets/{arrow-up-right-KJ5nNRaR.js → arrow-up-right-DfeFj5sx.js} +1 -1
  28. package/dashboard/dist/assets/{download-DPMzNjm9.js → download-CAPdZzcq.js} +1 -1
  29. package/dashboard/dist/assets/{main-V7ji8RkP.js → main-QK2wZyt4.js} +2 -2
  30. package/dashboard/dist/assets/{use-limits-display-prefs-BhRZ2Vbm.js → use-limits-display-prefs-CC1SjRQA.js} +1 -1
  31. package/dashboard/dist/assets/{use-native-settings-CUwxJneK.js → use-native-settings-BeV60g8D.js} +1 -1
  32. package/dashboard/dist/assets/{use-ordered-list-Cg8uqKy8.js → use-ordered-list-DykwKTTq.js} +1 -1
  33. package/dashboard/dist/assets/{use-quality-per-dollar-pref-DFYl9bWd.js → use-quality-per-dollar-pref-VMVZhzOm.js} +1 -1
  34. package/dashboard/dist/assets/{use-usage-limits-TD09WmDE.js → use-usage-limits-CIwmIjBw.js} +1 -1
  35. package/dashboard/dist/assets/{useCurrency-mDQb_32d.js → useCurrency-Bm3doFX6.js} +1 -1
  36. package/dashboard/dist/assets/{useScrollLock-CP5GKZtC.js → useScrollLock-DecJNW0u.js} +1 -1
  37. package/dashboard/dist/index.html +1 -1
  38. package/dashboard/dist/share.html +1 -1
  39. package/package.json +1 -1
  40. package/src/commands/status.js +2 -2
  41. package/src/commands/sync.js +151 -70
  42. package/src/lib/install-resolver.js +62 -0
  43. package/src/lib/multi-install-parser.js +103 -0
  44. package/src/lib/pricing/seed-snapshot.json +1 -1
  45. package/src/lib/rollout.js +104 -40
  46. package/src/lib/wsl-probe.js +21 -0
@@ -0,0 +1,62 @@
1
+ const fssync = require("node:fs");
2
+ const wsl = require("./wsl-probe");
3
+
4
+ function resolveInstallPaths({ nativeValue, wslDir, wslValue } = {}, env = process.env, deps = {}) {
5
+ if (process.platform !== "win32") {
6
+ return { native: nativeValue ?? null, wsl: null };
7
+ }
8
+
9
+ const wslCandidate = wslValue !== undefined
10
+ ? (wsl.shouldProbeWsl(env) ? wslValue : null)
11
+ : (wslDir && wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(wslDir, { ...deps, env }) : null);
12
+ const nativeCandidate = wsl.shouldProbeNative(env) && nativeValue
13
+ ? pathExists(nativeValue, deps.existsSync) : null;
14
+
15
+ return wsl.resolveAllWin32Paths({ nativeValue: nativeCandidate, wslValue: wslCandidate, env, platform: "win32" });
16
+ }
17
+
18
+ function pathExists(p, existsSync) {
19
+ try { return (existsSync || fssync.existsSync)(p) ? p : null; } catch (_e) { return null; }
20
+ }
21
+
22
+ // Migrate a flat (single-install) cursor to { native, wsl } namespaces.
23
+ // `activeKeys` names the namespaces seeded with a copy of the flat state; the
24
+ // others start empty so their install's full history backfills on first parse.
25
+ // Leaving a namespace empty is only safe when its install was NEVER counted
26
+ // under the flat cursor — the flat state holds the per-session dedup maps, and
27
+ // an already-counted install re-parsed without them double-counts everything.
28
+ // Callers that cannot prove which install the flat cursor tracked must seed
29
+ // ALL namespaces (the default): bounded backfill loss, never a double count.
30
+ function ensureNamespacedCursors(cursors, providerName, activeKeys = ["native", "wsl"]) {
31
+ const state = cursors[providerName] && typeof cursors[providerName] === "object" ? cursors[providerName] : {};
32
+
33
+ if (state.native !== undefined || state.wsl !== undefined) {
34
+ return state;
35
+ }
36
+
37
+ const keys = Array.isArray(activeKeys) ? activeKeys : [activeKeys];
38
+ cursors[providerName] = { native: {}, wsl: {} };
39
+ if (Object.keys(state).length > 0) {
40
+ for (const key of keys) {
41
+ cursors[providerName][key] = JSON.parse(JSON.stringify(state));
42
+ }
43
+ }
44
+ return cursors[providerName];
45
+ }
46
+
47
+ function ensureFlatCursor(cursors, providerName, env) {
48
+ const state = cursors[providerName];
49
+ if (!state || typeof state !== "object") return;
50
+ if (state.native === undefined && state.wsl === undefined) return;
51
+
52
+ const mode = wsl.getWslMode(env || process.env);
53
+ const preferWsl = mode === "wsl-first" || mode === "wsl-only";
54
+ const merged = preferWsl ? { ...state.native, ...state.wsl } : { ...state.wsl, ...state.native };
55
+ cursors[providerName] = merged;
56
+ }
57
+
58
+ module.exports = {
59
+ resolveInstallPaths,
60
+ ensureNamespacedCursors,
61
+ ensureFlatCursor,
62
+ };
@@ -0,0 +1,103 @@
1
+ const wsl = require("./wsl-probe");
2
+ const { ensureNamespacedCursors, ensureFlatCursor } = require("./install-resolver");
3
+
4
+ function emptyResult() {
5
+ return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
6
+ }
7
+
8
+ async function multiInstallParse({ paths, parserFn, providerName, cursors, getParams, onProgress, detectInstall, ...shared }) {
9
+ const installKeys = Object.keys(paths).filter(k => paths[k]);
10
+ if (installKeys.length === 0) return emptyResult();
11
+ const env = shared.env || process.env;
12
+
13
+ if (installKeys.length === 1) {
14
+ ensureFlatCursor(cursors, providerName, env);
15
+ return await parserFn({
16
+ ...getParams(paths[installKeys[0]], installKeys[0]),
17
+ ...shared,
18
+ cursors,
19
+ onProgress,
20
+ });
21
+ }
22
+
23
+ const activeKeys = resolveMigrationSeedKeys({ paths, installKeys, providerName, cursors, detectInstall });
24
+ const ns = ensureNamespacedCursors(cursors, providerName, activeKeys);
25
+ let recordsProcessed = 0;
26
+ let eventsAggregated = 0;
27
+ let bucketsQueued = 0;
28
+
29
+ for (let i = 0; i < installKeys.length; i++) {
30
+ const key = installKeys[i];
31
+ cursors[providerName] = ns[key];
32
+ try {
33
+ const result = await parserFn({
34
+ ...getParams(paths[key], key), ...shared, cursors,
35
+ onProgress: wrapProgress(onProgress, key),
36
+ });
37
+ ns[key] = cursors[providerName];
38
+ recordsProcessed += result.recordsProcessed || 0;
39
+ eventsAggregated += result.eventsAggregated || 0;
40
+ bucketsQueued += result.bucketsQueued || 0;
41
+ } catch (parseErr) {
42
+ cursors[providerName] = ns;
43
+ throw parseErr;
44
+ }
45
+ }
46
+ cursors[providerName] = ns;
47
+
48
+ return { recordsProcessed, eventsAggregated, bucketsQueued };
49
+ }
50
+
51
+ // Decide which namespaces the one-time flat→namespaced cursor migration seeds
52
+ // with the flat state. `detectInstall(installPath, flatState, installKey)` is
53
+ // a provider-specific probe answering "does this install's DB contain the
54
+ // flat cursor's session ids?". Only when EXACTLY ONE install matches do we
55
+ // know who owned the flat cursor — the other namespace then starts empty so
56
+ // its full history backfills. No probe, no match, multiple matches, or a
57
+ // probe error all fall back to seeding every namespace: an install re-parsed
58
+ // without its dedup state would double-count its entire history, and a
59
+ // bounded backfill gap is the cheaper failure.
60
+ function resolveMigrationSeedKeys({ paths, installKeys, providerName, cursors, detectInstall }) {
61
+ if (typeof detectInstall !== "function") return installKeys;
62
+ const state = cursors[providerName] && typeof cursors[providerName] === "object" ? cursors[providerName] : {};
63
+ const isFlat = state.native === undefined && state.wsl === undefined;
64
+ if (!isFlat || Object.keys(state).length === 0) return installKeys;
65
+
66
+ const hits = [];
67
+ for (const key of installKeys) {
68
+ let owns = false;
69
+ try {
70
+ owns = detectInstall(paths[key], state, key) === true;
71
+ } catch (_e) { }
72
+ if (owns) hits.push(key);
73
+ }
74
+ return hits.length === 1 ? hits : installKeys;
75
+ }
76
+
77
+ function wrapProgress(onProgress, installKey) {
78
+ if (!onProgress) return undefined;
79
+ return (p) => onProgress({ ...p, install: installKey });
80
+ }
81
+
82
+ function mergeBothFileSources({ resolveFiles, env }) {
83
+ const isBoth = process.platform === "win32" && wsl.getWslMode(env) === "both";
84
+ if (!isBoth) {
85
+ const files = resolveFiles(env);
86
+ return files;
87
+ }
88
+
89
+ const nativeEnv = { ...env, TOKENTRACKER_WSL_MODE: "native-only" };
90
+ const wslEnv = { ...env, TOKENTRACKER_WSL_MODE: "wsl-only" };
91
+
92
+ const nativeFiles = resolveFiles(nativeEnv);
93
+ const wslFiles = resolveFiles(wslEnv);
94
+
95
+ const seen = new Set();
96
+ const merged = [];
97
+ for (const f of [...nativeFiles, ...wslFiles]) {
98
+ if (!seen.has(f)) { seen.add(f); merged.push(f); }
99
+ }
100
+ return merged;
101
+ }
102
+
103
+ module.exports = { multiInstallParse, emptyResult, mergeBothFileSources };