tokentracker-cli 0.64.3 → 0.65.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 (56) hide show
  1. package/README.de.md +476 -0
  2. package/README.ja.md +1 -1
  3. package/README.ko.md +1 -1
  4. package/README.md +1 -1
  5. package/README.zh-CN.md +1 -1
  6. package/dashboard/dist/assets/{ActivityHeatmap-BSA4KPkJ.js → ActivityHeatmap-N0MiuKiD.js} +1 -1
  7. package/dashboard/dist/assets/{Card-BniOg7K8.js → Card-C-UUcB6K.js} +1 -1
  8. package/dashboard/dist/assets/{ConfirmModal-CxThrcXK.js → ConfirmModal-Ck7qQwqO.js} +1 -1
  9. package/dashboard/dist/assets/{DashboardPage-DygQTlzO.js → DashboardPage-BdZPqztW.js} +2 -2
  10. package/dashboard/dist/assets/{DevicePage-BfRGbds7.js → DevicePage-Wr-LIowe.js} +1 -1
  11. package/dashboard/dist/assets/{DialogTitle-Cd0Nw0hW.js → DialogTitle-BNVIjz-M.js} +1 -1
  12. package/dashboard/dist/assets/{FadeIn-DxDHmb9-.js → FadeIn-D8XF2ui8.js} +1 -1
  13. package/dashboard/dist/assets/{HeaderGithubStar-2aQXPupt.js → HeaderGithubStar-Dv3PacNv.js} +1 -1
  14. package/dashboard/dist/assets/{IpCheckPage-m_W_l5Xa.js → IpCheckPage-DfDXn5N1.js} +1 -1
  15. package/dashboard/dist/assets/LandingPage-QlEpH3fR.js +4415 -0
  16. package/dashboard/dist/assets/{LeaderboardAvatar-BmpmwHwD.js → LeaderboardAvatar-CEV0WCgO.js} +1 -1
  17. package/dashboard/dist/assets/{LeaderboardPage-B6wWBsUW.js → LeaderboardPage-Bte3fdxj.js} +2 -2
  18. package/dashboard/dist/assets/{LeaderboardProfileModal-CLlbxbZQ.js → LeaderboardProfileModal-DBSiwRmn.js} +1 -1
  19. package/dashboard/dist/assets/{LeaderboardProfilePage-en0u_bG-.js → LeaderboardProfilePage-DL1xrz2u.js} +1 -1
  20. package/dashboard/dist/assets/LimitsPage-CU71xh0N.js +3 -0
  21. package/dashboard/dist/assets/{LocalOnlyNotice-DNHw_KVs.js → LocalOnlyNotice-B0O7VftN.js} +1 -1
  22. package/dashboard/dist/assets/{LoginPage-D38KpNMd.js → LoginPage-R_gddHQq.js} +1 -1
  23. package/dashboard/dist/assets/{PopoverPopup-C4Ndfprc.js → PopoverPopup-Dt3vUklH.js} +1 -1
  24. package/dashboard/dist/assets/{ResetPasswordPage-DYF1Iz64.js → ResetPasswordPage-C9OkcYCJ.js} +1 -1
  25. package/dashboard/dist/assets/{Select-6qFjabMe.js → Select-hvb85wtR.js} +1 -1
  26. package/dashboard/dist/assets/{SelectItemText-B234IuCW.js → SelectItemText-BkSrV5-F.js} +1 -1
  27. package/dashboard/dist/assets/SettingsPage-D6NSX-54.js +1 -0
  28. package/dashboard/dist/assets/{SkillsPage-37lQlleG.js → SkillsPage-CjrVT_6o.js} +1 -1
  29. package/dashboard/dist/assets/{WidgetsPage-D9ANC2dF.js → WidgetsPage-BA7CYY1i.js} +1 -1
  30. package/dashboard/dist/assets/{WrappedPage-hjymf4DR.js → WrappedPage-BxxXLdaK.js} +1 -1
  31. package/dashboard/dist/assets/{agent-logos-B-NxiwlS.js → agent-logos-DW_otOmk.js} +1 -1
  32. package/dashboard/dist/assets/{arrow-up-right-B_iC7jeO.js → arrow-up-right-BNZE25jN.js} +1 -1
  33. package/dashboard/dist/assets/{download-DSBqpgG5.js → download-B3dZN1Qo.js} +1 -1
  34. package/dashboard/dist/assets/{main-CGx8BJ4O.css → main-BHwig9Um.css} +1 -1
  35. package/dashboard/dist/assets/{main-DEwjlMQP.js → main-XJt43zJ1.js} +21 -20
  36. package/dashboard/dist/assets/{use-limits-display-prefs-OhLCvWa8.js → use-limits-display-prefs-CILTJ0ib.js} +1 -1
  37. package/dashboard/dist/assets/{use-native-settings-BOQcPSo9.js → use-native-settings-CMJ5wI4Y.js} +1 -1
  38. package/dashboard/dist/assets/{use-ordered-list-DKOLCb2L.js → use-ordered-list-Bvt0pvNt.js} +1 -1
  39. package/dashboard/dist/assets/{use-quality-per-dollar-pref-EzQkuFZg.js → use-quality-per-dollar-pref-D634C_GV.js} +1 -1
  40. package/dashboard/dist/assets/{use-usage-limits-DD8zzK6Q.js → use-usage-limits-DU_6J0Y3.js} +1 -1
  41. package/dashboard/dist/assets/{useCurrency-D1ldsRAh.js → useCurrency-DInjo-qq.js} +1 -1
  42. package/dashboard/dist/assets/{useScrollLock-eUzPJJ4w.js → useScrollLock-DTTYcDSy.js} +1 -1
  43. package/dashboard/dist/index.html +2 -2
  44. package/dashboard/dist/share.html +2 -2
  45. package/package.json +1 -1
  46. package/src/commands/init.js +3 -2
  47. package/src/commands/status.js +26 -17
  48. package/src/commands/sync.js +672 -98
  49. package/src/lib/openclaw-session-plugin.js +2 -2
  50. package/src/lib/pricing/seed-snapshot.json +1 -1
  51. package/src/lib/rollout.js +602 -178
  52. package/src/lib/usage-limits.js +49 -1
  53. package/src/lib/wsl-probe.js +181 -0
  54. package/dashboard/dist/assets/LandingPage-BkusMtg4.js +0 -4411
  55. package/dashboard/dist/assets/LimitsPage-CZXzDyPl.js +0 -3
  56. package/dashboard/dist/assets/SettingsPage-C-2m2LzE.js +0 -1
@@ -144,6 +144,34 @@ function formatClaudeRateLimitMessage(retryAfterSec) {
144
144
  return "Claude API rate limited (429) — retry shortly.";
145
145
  }
146
146
 
147
+ // Model-scoped weekly windows (e.g. Fable) arrive only in the newer generic `limits`
148
+ // array (`kind: "weekly_scoped"` + `scope.model.display_name`) — the legacy top-level
149
+ // fields (`seven_day_opus` etc.) stay null for them. Extract them into
150
+ // `weekly_scoped: [{ label, utilization, resets_at }]`. Entries that duplicate a
151
+ // populated legacy field (an "Opus" scoped entry alongside `seven_day_opus`) are
152
+ // dropped so the same window never renders twice.
153
+ function extractClaudeScopedWeekly(body) {
154
+ if (!Array.isArray(body?.limits)) return null;
155
+ const out = [];
156
+ for (const entry of body.limits) {
157
+ if (!entry || typeof entry !== "object" || entry.kind !== "weekly_scoped") continue;
158
+ const model = entry.scope?.model;
159
+ const label = typeof model?.display_name === "string" && model.display_name.trim()
160
+ ? model.display_name.trim()
161
+ : typeof model?.id === "string" && model.id.trim() ? model.id.trim() : null;
162
+ if (!label) continue;
163
+ if (body.seven_day_opus && label.toLowerCase() === "opus") continue;
164
+ const utilization = Number(entry.percent);
165
+ if (!Number.isFinite(utilization)) continue;
166
+ out.push({
167
+ label,
168
+ utilization,
169
+ resets_at: typeof entry.resets_at === "string" ? entry.resets_at : null,
170
+ });
171
+ }
172
+ return out.length > 0 ? out : null;
173
+ }
174
+
147
175
  async function fetchClaudeUsageLimits(accessToken, { fetchImpl = fetch, maxAttempts = 3 } = {}) {
148
176
  const url = "https://api.anthropic.com/api/oauth/usage";
149
177
  const headers = {
@@ -178,6 +206,7 @@ async function fetchClaudeUsageLimits(accessToken, { fetchImpl = fetch, maxAttem
178
206
  five_hour: body.five_hour ?? null,
179
207
  seven_day: body.seven_day ?? null,
180
208
  seven_day_opus: body.seven_day_opus ?? null,
209
+ weekly_scoped: extractClaudeScopedWeekly(body),
181
210
  extra_usage: body.extra_usage ?? null,
182
211
  };
183
212
  }
@@ -1848,7 +1877,21 @@ function isClaudeCacheWindowUsable(window, { nowMs } = {}) {
1848
1877
  }
1849
1878
 
1850
1879
  function hasClaudeWindow(limits) {
1851
- return Boolean(limits?.five_hour || limits?.seven_day || limits?.seven_day_opus);
1880
+ return Boolean(
1881
+ limits?.five_hour || limits?.seven_day || limits?.seven_day_opus ||
1882
+ (Array.isArray(limits?.weekly_scoped) && limits.weekly_scoped.length > 0),
1883
+ );
1884
+ }
1885
+
1886
+ // Cached scoped windows are filtered per-entry like the fixed windows: a scoped
1887
+ // window whose reset has passed is stale data, not a usable fallback.
1888
+ function normalizeClaudeCachedScopedWeekly(raw, { nowMs } = {}) {
1889
+ if (!Array.isArray(raw)) return null;
1890
+ const usable = raw.filter(
1891
+ (w) => w && typeof w.label === "string" && Number.isFinite(Number(w.utilization)) &&
1892
+ isClaudeCacheWindowUsable(w, { nowMs }),
1893
+ );
1894
+ return usable.length > 0 ? usable : null;
1852
1895
  }
1853
1896
 
1854
1897
  function normalizeClaudeCachedLimits(
@@ -1870,6 +1913,7 @@ function normalizeClaudeCachedLimits(
1870
1913
  five_hour: isClaudeCacheWindowUsable(raw?.five_hour, { nowMs }) ? raw.five_hour : null,
1871
1914
  seven_day: isClaudeCacheWindowUsable(raw?.seven_day, { nowMs }) ? raw.seven_day : null,
1872
1915
  seven_day_opus: isClaudeCacheWindowUsable(raw?.seven_day_opus, { nowMs }) ? raw.seven_day_opus : null,
1916
+ weekly_scoped: normalizeClaudeCachedScopedWeekly(raw?.weekly_scoped, { nowMs }),
1873
1917
  extra_usage: raw?.extra_usage ?? null,
1874
1918
  stale,
1875
1919
  cached_at: raw.cached_at,
@@ -1909,6 +1953,9 @@ function writeClaudeLimitsCache(limits, { home, nowMs = Date.now() } = {}) {
1909
1953
  five_hour: limits.five_hour || null,
1910
1954
  seven_day: limits.seven_day || null,
1911
1955
  seven_day_opus: limits.seven_day_opus || null,
1956
+ weekly_scoped: Array.isArray(limits.weekly_scoped) && limits.weekly_scoped.length > 0
1957
+ ? limits.weekly_scoped
1958
+ : null,
1912
1959
  extra_usage: limits.extra_usage || null,
1913
1960
  cached_at: new Date(nowMs).toISOString(),
1914
1961
  },
@@ -2646,6 +2693,7 @@ async function fetchUsageLimitsUncached({
2646
2693
  five_hour: claudeResult.value.five_hour,
2647
2694
  seven_day: claudeResult.value.seven_day,
2648
2695
  seven_day_opus: claudeResult.value.seven_day_opus,
2696
+ weekly_scoped: claudeResult.value.weekly_scoped,
2649
2697
  extra_usage: claudeResult.value.extra_usage,
2650
2698
  };
2651
2699
  writeClaudeLimitsCache(claude, { home, nowMs });
@@ -0,0 +1,181 @@
1
+ const fssync = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ const DEFAULT_EXEC_OPTS = { timeout: 15000, windowsHide: true, maxBuffer: 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] };
5
+
6
+ let _cachedDistros = null;
7
+ const _cachedWslUsers = new Map();
8
+
9
+ function resetWslProbeCache() {
10
+ _cachedDistros = null;
11
+ _cachedWslUsers.clear();
12
+ }
13
+
14
+ const WSL_MODES = new Set([
15
+ "wsl-first",
16
+ "native-first",
17
+ "wsl-only",
18
+ "native-only",
19
+ ]);
20
+
21
+ function defaultRunWsl(args, { utf16 = false } = {}) {
22
+ const { execFileSync } = require("node:child_process");
23
+ const buf = execFileSync("wsl.exe", args, DEFAULT_EXEC_OPTS);
24
+ return utf16 ? buf.toString("utf16le") : buf.toString("utf8");
25
+ }
26
+
27
+ function parseWslListVerbose(raw) {
28
+ if (typeof raw !== "string") return [];
29
+ const distros = [];
30
+ for (const line of raw.split(/\r?\n/)) {
31
+ const clean = line.replace(/\0/g, "").replace(/\uFEFF/g, "").trim();
32
+ if (!clean) continue;
33
+ const cells = clean.split(/\s+/);
34
+ let isDefault = false;
35
+ let idx = 0;
36
+ if (cells[0] === "*") {
37
+ isDefault = true;
38
+ idx = 1;
39
+ }
40
+ const name = cells[idx];
41
+ if (!name || name === "NAME") continue;
42
+ const version = parseInt(cells[cells.length - 1], 10);
43
+ distros.push({ name, version: Number.isFinite(version) ? version : null, isDefault });
44
+ }
45
+ return distros;
46
+ }
47
+
48
+ function probeWslDistros(deps = {}) {
49
+ const hasDeps = Object.keys(deps).length > 0;
50
+ if (!hasDeps && _cachedDistros) return _cachedDistros;
51
+ const runWsl = deps.runWsl || defaultRunWsl;
52
+ let raw;
53
+ try {
54
+ raw = runWsl(["-l", "-v"], { utf16: true });
55
+ } catch (_e) {
56
+ if (!hasDeps) _cachedDistros = [];
57
+ return [];
58
+ }
59
+ const distros = parseWslListVerbose(raw);
60
+ const sorted = distros.sort((a, b) => (b.isDefault ? 1 : 0) - (a.isDefault ? 1 : 0));
61
+ if (!hasDeps) _cachedDistros = sorted;
62
+ return sorted;
63
+ }
64
+
65
+ function normalizeWslMode(value) {
66
+ return String(value ?? "")
67
+ .trim()
68
+ .toLowerCase()
69
+ .replaceAll("_", "-");
70
+ }
71
+
72
+ function getWslMode(env = process.env) {
73
+ const raw = normalizeWslMode(env.TOKENTRACKER_WSL_MODE);
74
+ return WSL_MODES.has(raw) ? raw : "wsl-first";
75
+ }
76
+
77
+ function isInvalidWslMode(env = process.env) {
78
+ const value = env.TOKENTRACKER_WSL_MODE;
79
+ if (value == null || String(value).trim() === "") return false;
80
+ return !WSL_MODES.has(normalizeWslMode(value));
81
+ }
82
+
83
+ function shouldProbeWsl(env = process.env) {
84
+ return getWslMode(env) !== "native-only";
85
+ }
86
+
87
+ function shouldProbeNative(env = process.env) {
88
+ return getWslMode(env) !== "wsl-only";
89
+ }
90
+
91
+ function pickWin32Path({
92
+ wslValue,
93
+ nativeValue,
94
+ env = process.env,
95
+ platform = process.platform,
96
+ }) {
97
+ if (platform !== "win32") return null;
98
+
99
+ const mode = getWslMode(env);
100
+
101
+ if (mode === "wsl-only") return wslValue ?? null;
102
+ if (mode === "native-only") return nativeValue ?? null;
103
+ if (mode === "native-first") return nativeValue ?? wslValue ?? null;
104
+
105
+ return wslValue ?? nativeValue ?? null;
106
+ }
107
+
108
+ function lookupWslUser(distroName, runWsl, useCache) {
109
+ if (useCache && _cachedWslUsers.has(distroName)) {
110
+ return _cachedWslUsers.get(distroName);
111
+ }
112
+ let user = "";
113
+ try {
114
+ user = String(runWsl(["-d", distroName, "-e", "whoami"], { utf16: false }) || "").trim();
115
+ } catch (_e) { }
116
+ if (useCache) _cachedWslUsers.set(distroName, user);
117
+ return user;
118
+ }
119
+
120
+ function discoverWslHome(providerDir, deps = {}) {
121
+ if (!shouldProbeWsl(deps.env)) return null;
122
+
123
+ const runWsl = deps.runWsl || defaultRunWsl;
124
+ const existsSync = deps.existsSync || fssync.existsSync;
125
+ const distros = deps.runWsl ? probeWslDistros({ runWsl: deps.runWsl }) : probeWslDistros();
126
+ const useCache = !deps.runWsl;
127
+ for (const distro of distros) {
128
+ const user = lookupWslUser(distro.name, runWsl, useCache);
129
+ if (!user) continue;
130
+ const roots = distro.version === 1
131
+ ? ["\\\\wsl.localhost\\", "\\\\wsl$\\"]
132
+ : ["\\\\wsl$\\", "\\\\wsl.localhost\\"];
133
+ for (const root of roots) {
134
+ const candidate = `${root}${distro.name}\\home\\${user}\\${providerDir}`;
135
+ try {
136
+ if (existsSync(candidate)) return candidate;
137
+ } catch (_e) { }
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+
143
+ function isUncPath(p) {
144
+ return typeof p === "string" && (p.startsWith("\\\\") || p.startsWith("//"));
145
+ }
146
+
147
+ function snapshotSqliteDb(dbPath) {
148
+ const tmpRoot = fssync.mkdtempSync(
149
+ path.join(require("node:os").tmpdir(), "tokentracker-wsl-snap-"),
150
+ );
151
+ const target = path.join(tmpRoot, path.basename(dbPath));
152
+ fssync.copyFileSync(dbPath, target);
153
+ for (const suffix of ["-wal", "-shm", "-journal"]) {
154
+ const src = dbPath + suffix;
155
+ try {
156
+ if (fssync.existsSync(src)) fssync.copyFileSync(src, target + suffix);
157
+ } catch (_e) { }
158
+ }
159
+ return {
160
+ path: target,
161
+ cleanup() {
162
+ try { fssync.rmSync(tmpRoot, { recursive: true, force: true }); } catch (_e) { }
163
+ },
164
+ };
165
+ }
166
+
167
+ module.exports = {
168
+ defaultRunWsl,
169
+ parseWslListVerbose,
170
+ probeWslDistros,
171
+ resetWslProbeCache,
172
+ discoverWslHome,
173
+ isUncPath,
174
+ snapshotSqliteDb,
175
+ getWslMode,
176
+ isInvalidWslMode,
177
+ shouldProbeWsl,
178
+ shouldProbeNative,
179
+ pickWin32Path,
180
+ normalizeWslMode,
181
+ };