tokentracker-cli 0.79.8 → 0.80.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 (77) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-Ctu3c_8t.js → AchievementBadge-D9jOMhlD.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-B3kBTHDB.js → AchievementsPage-DuswZa4g.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-CAnTXGpC.js → AchievementsSection-BJPeE1Uq.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-oTUXEQk9.js → BadgeDetailModal-nNPIsy6i.js} +1 -1
  5. package/dashboard/dist/assets/{Card-BHt5f5cD.js → Card-VENq8eEp.js} +1 -1
  6. package/dashboard/dist/assets/{ClawdAnimated-ChxXY3l0.js → ClawdAnimated-Cq7TW0vv.js} +1 -1
  7. package/dashboard/dist/assets/{CommunityStatsModal-CVLlItP1.js → CommunityStatsModal-y2VftQuq.js} +1 -1
  8. package/dashboard/dist/assets/{ConfirmModal-ClrILpb3.js → ConfirmModal-CWgnJ4hm.js} +1 -1
  9. package/dashboard/dist/assets/{Controls-D_XBBpIh.js → Controls-BUQ_iZZq.js} +1 -1
  10. package/dashboard/dist/assets/DashboardPage-WkiygMmq.js +19 -0
  11. package/dashboard/dist/assets/{DevicePage-J7DW7yeX.js → DevicePage-Dh3XDtNx.js} +1 -1
  12. package/dashboard/dist/assets/{DialogClose-B1r9gzUF.js → DialogClose-KTFU_B3F.js} +1 -1
  13. package/dashboard/dist/assets/DialogDescription-DIra288n.js +1 -0
  14. package/dashboard/dist/assets/{DialogTitle-BL2puDA7.js → DialogTitle-bza2pIek.js} +1 -1
  15. package/dashboard/dist/assets/{FadeIn-Cd-YT4oT.js → FadeIn-Dm3FVNfr.js} +1 -1
  16. package/dashboard/dist/assets/{HeaderGithubStar-BAtl4a-K.js → HeaderGithubStar-CbSElzLt.js} +1 -1
  17. package/dashboard/dist/assets/{IpCheckPage-BXK_cYN2.js → IpCheckPage-BDON7VCU.js} +1 -1
  18. package/dashboard/dist/assets/{LandingPage-C_16AbpF.js → LandingPage-Ck3CwZrt.js} +1 -1
  19. package/dashboard/dist/assets/{LeaderboardAvatar-B0vUuCYU.js → LeaderboardAvatar-GVUy0Ynq.js} +1 -1
  20. package/dashboard/dist/assets/{LeaderboardPage-Duhdaq09.js → LeaderboardPage-C19Rkt44.js} +2 -2
  21. package/dashboard/dist/assets/{LeaderboardProfileModal-CFFyHRTB.js → LeaderboardProfileModal-QcEq_o6E.js} +3 -3
  22. package/dashboard/dist/assets/{LeaderboardProfilePage-CjTelV-4.js → LeaderboardProfilePage-pFhI6292.js} +1 -1
  23. package/dashboard/dist/assets/{LimitsPage-DIcXRgSv.js → LimitsPage-ChV3Qezd.js} +1 -1
  24. package/dashboard/dist/assets/{LocalOnlyNotice-DC7wXWds.js → LocalOnlyNotice-ALvpjznF.js} +1 -1
  25. package/dashboard/dist/assets/{LoginPage--8hYT3aq.js → LoginPage-CL-QrJxU.js} +1 -1
  26. package/dashboard/dist/assets/{PetPage-ByWUW7-V.js → PetPage-DSuvdGgd.js} +1 -1
  27. package/dashboard/dist/assets/{PopoverPopup-g_v1fmhH.js → PopoverPopup-jSDQ87Bi.js} +1 -1
  28. package/dashboard/dist/assets/{ResetPasswordPage-BfunstIi.js → ResetPasswordPage-Dk2IDAJq.js} +1 -1
  29. package/dashboard/dist/assets/{Select-C4ShPt1t.js → Select-Cz6WTj7D.js} +1 -1
  30. package/dashboard/dist/assets/{SelectItemText-gw_TdvY6.js → SelectItemText-BZyRjWCM.js} +1 -1
  31. package/dashboard/dist/assets/{SettingsPage-BFCFrDuA.js → SettingsPage-CANz8HMM.js} +1 -1
  32. package/dashboard/dist/assets/{SkillsPage-BNQDySeK.js → SkillsPage-WZkQG_83.js} +1 -1
  33. package/dashboard/dist/assets/{WidgetsPage-B3bHP3Vk.js → WidgetsPage-BEyJ62NV.js} +1 -1
  34. package/dashboard/dist/assets/{WrappedPage-6amxWnWT.js → WrappedPage-Ck_dOx_w.js} +1 -1
  35. package/dashboard/dist/assets/{agent-logos-DPIQzYms.js → agent-logos-DpjHiAbZ.js} +1 -1
  36. package/dashboard/dist/assets/{arrow-up-right-CKcE2mif.js → arrow-up-right-C4iYPBtK.js} +1 -1
  37. package/dashboard/dist/assets/check-BkPUxPZV.js +1 -0
  38. package/dashboard/dist/assets/{copy-vi-V2M_I.js → copy-CHJ-38K6.js} +1 -1
  39. package/dashboard/dist/assets/{download-BH0Spg15.js → download-BSeG8u17.js} +1 -1
  40. package/dashboard/dist/assets/{flame-C3jUAnqA.js → flame-DeHwKcE-.js} +1 -1
  41. package/dashboard/dist/assets/{format-tokens-tnfJT09G.js → format-tokens-B-MJYJyp.js} +1 -1
  42. package/dashboard/dist/assets/{info-CGCZ5JvP.js → info-DA_0xr_o.js} +1 -1
  43. package/dashboard/dist/assets/{link-2-CXULWwZY.js → link-2-noUH83Lc.js} +1 -1
  44. package/dashboard/dist/assets/{loader-circle-B324hSa3.js → loader-circle-wL7KZtm0.js} +1 -1
  45. package/dashboard/dist/assets/{main-vGUuyRq-.js → main-BHfJDFcu.js} +20 -20
  46. package/dashboard/dist/assets/{provider-display-CeoAhoCb.js → provider-display-DxIClOGM.js} +1 -1
  47. package/dashboard/dist/assets/{trash-2-L9vxbDeP.js → trash-2-B8TPUmxZ.js} +1 -1
  48. package/dashboard/dist/assets/{use-community-stats-DLE30Lh0.js → use-community-stats-CXD3MRRE.js} +1 -1
  49. package/dashboard/dist/assets/{use-limits-display-prefs-BxN--PEW.js → use-limits-display-prefs-C2pJVYTn.js} +1 -1
  50. package/dashboard/dist/assets/{use-native-settings-Dn5gWQkJ.js → use-native-settings-BeOBLEZa.js} +1 -1
  51. package/dashboard/dist/assets/{use-ordered-list-DYoz2NJ9.js → use-ordered-list-kAtW8pJm.js} +1 -1
  52. package/dashboard/dist/assets/{use-quality-per-dollar-pref-JXorBQsT.js → use-quality-per-dollar-pref-CFZUZM3S.js} +1 -1
  53. package/dashboard/dist/assets/{use-transform-BTTF5vPC.js → use-transform-B9YwpyKi.js} +1 -1
  54. package/dashboard/dist/assets/{use-usage-limits-BUemSWQ9.js → use-usage-limits-DSX5iKPE.js} +1 -1
  55. package/dashboard/dist/assets/{useCurrency-Cxv4RR3U.js → useCurrency-CKg5K4Nj.js} +1 -1
  56. package/dashboard/dist/assets/{useScrollLock-DTPnHKfF.js → useScrollLock-DyyY_Vyt.js} +1 -1
  57. package/dashboard/dist/assets/useTokenFormat-FfTjEH_m.js +1 -0
  58. package/dashboard/dist/assets/{zap-CG8zuY9S.js → zap-B69x9NCT.js} +1 -1
  59. package/dashboard/dist/index.html +1 -1
  60. package/dashboard/dist/share.html +1 -1
  61. package/package.json +1 -1
  62. package/src/commands/init.js +89 -12
  63. package/src/commands/serve.js +65 -4
  64. package/src/commands/status.js +2 -1
  65. package/src/commands/sync.js +170 -7
  66. package/src/commands/uninstall.js +10 -2
  67. package/src/lib/claude-config.js +69 -28
  68. package/src/lib/diagnostics.js +2 -2
  69. package/src/lib/local-api.js +19 -6
  70. package/src/lib/openclaw-session-plugin.js +13 -0
  71. package/src/lib/pricing/seed-snapshot.json +1 -1
  72. package/src/lib/rollout.js +63 -2
  73. package/src/lib/usage-limits.js +9 -5
  74. package/dashboard/dist/assets/DashboardPage-CizTJox2.js +0 -19
  75. package/dashboard/dist/assets/DialogDescription-Bd9-kaCS.js +0 -1
  76. package/dashboard/dist/assets/check-B5V4_ezt.js +0 -1
  77. package/dashboard/dist/assets/useTokenFormat-DMAb2ec3.js +0 -1
@@ -4,56 +4,74 @@ const path = require("node:path");
4
4
  const { ensureDir, readJson, writeJson } = require("./fs");
5
5
 
6
6
  const DEFAULT_EVENT = "SessionEnd";
7
+ const CLAUDE_USAGE_EVENTS = ["Stop", DEFAULT_EVENT];
7
8
 
8
9
  async function upsertClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVENT }) {
10
+ return upsertClaudeHooks({ settingsPath, hookCommand, events: [event] });
11
+ }
12
+
13
+ async function upsertClaudeUsageHooks({ settingsPath, hookCommand }) {
14
+ return upsertClaudeHooks({ settingsPath, hookCommand, events: CLAUDE_USAGE_EVENTS });
15
+ }
16
+
17
+ async function upsertClaudeHooks({ settingsPath, hookCommand, events }) {
9
18
  const existing = await readJson(settingsPath);
10
19
  const settings = normalizeSettings(existing);
11
20
  const hooks = normalizeHooks(settings.hooks);
12
- const entries = normalizeEntries(hooks[event]);
13
-
14
- const normalized = normalizeEntriesForCommand(entries, hookCommand);
15
- if (normalized.changed) {
16
- const nextHooks = { ...hooks, [event]: normalized.entries };
17
- const nextSettings = { ...settings, hooks: nextHooks };
18
- const backupPath = await writeClaudeSettings({ settingsPath, settings: nextSettings });
19
- return { changed: true, backupPath };
20
- }
21
+ const nextHooks = { ...hooks };
22
+ let changed = false;
21
23
 
22
- if (hasHook(entries, hookCommand)) {
23
- return { changed: false, backupPath: null };
24
+ for (const event of normalizeEventList(events)) {
25
+ const entries = normalizeEntries(hooks[event]);
26
+ const normalized = normalizeEntriesForCommand(entries, hookCommand);
27
+ let nextEntries = normalized.entries;
28
+ if (!hasHook(nextEntries, hookCommand)) {
29
+ nextEntries = nextEntries.concat([{ hooks: [{ type: "command", command: hookCommand }] }]);
30
+ changed = true;
31
+ }
32
+ if (normalized.changed) changed = true;
33
+ if (normalized.changed || nextEntries.length !== entries.length) {
34
+ nextHooks[event] = nextEntries;
35
+ }
24
36
  }
25
37
 
26
- const nextEntries = entries.concat([{ hooks: [{ type: "command", command: hookCommand }] }]);
27
- const nextHooks = { ...hooks, [event]: nextEntries };
28
- const nextSettings = { ...settings, hooks: nextHooks };
38
+ if (!changed) return { changed: false, backupPath: null };
29
39
 
40
+ const nextSettings = { ...settings, hooks: nextHooks };
30
41
  const backupPath = await writeClaudeSettings({ settingsPath, settings: nextSettings });
31
42
  return { changed: true, backupPath };
32
43
  }
33
44
 
34
45
  async function removeClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVENT }) {
46
+ return removeClaudeHooks({ settingsPath, hookCommand, events: [event] });
47
+ }
48
+
49
+ async function removeClaudeUsageHooks({ settingsPath, hookCommand }) {
50
+ return removeClaudeHooks({ settingsPath, hookCommand, events: CLAUDE_USAGE_EVENTS });
51
+ }
52
+
53
+ async function removeClaudeHooks({ settingsPath, hookCommand, events }) {
35
54
  const existing = await readJson(settingsPath);
36
55
  if (!existing) return { removed: false, skippedReason: "settings-missing" };
37
56
 
38
57
  const settings = normalizeSettings(existing);
39
58
  const hooks = normalizeHooks(settings.hooks);
40
- const entries = normalizeEntries(hooks[event]);
41
- if (entries.length === 0) return { removed: false, skippedReason: "hook-missing" };
42
-
59
+ const nextHooks = { ...hooks };
43
60
  let removed = false;
44
- const nextEntries = [];
45
- for (const entry of entries) {
46
- const res = stripHookFromEntry(entry, hookCommand);
47
- if (res.removed) removed = true;
48
- if (res.entry) nextEntries.push(res.entry);
61
+ for (const event of normalizeEventList(events)) {
62
+ const entries = normalizeEntries(hooks[event]);
63
+ const nextEntries = [];
64
+ for (const entry of entries) {
65
+ const res = stripHookFromEntry(entry, hookCommand);
66
+ if (res.removed) removed = true;
67
+ if (res.entry) nextEntries.push(res.entry);
68
+ }
69
+ if (nextEntries.length > 0) nextHooks[event] = nextEntries;
70
+ else delete nextHooks[event];
49
71
  }
50
72
 
51
73
  if (!removed) return { removed: false, skippedReason: "hook-missing" };
52
74
 
53
- const nextHooks = { ...hooks };
54
- if (nextEntries.length > 0) nextHooks[event] = nextEntries;
55
- else delete nextHooks[event];
56
-
57
75
  const nextSettings = { ...settings };
58
76
  if (Object.keys(nextHooks).length > 0) nextSettings.hooks = nextHooks;
59
77
  else delete nextSettings.hooks;
@@ -63,12 +81,26 @@ async function removeClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVE
63
81
  }
64
82
 
65
83
  async function isClaudeHookConfigured({ settingsPath, hookCommand, event = DEFAULT_EVENT }) {
84
+ return areClaudeHooksConfigured({ settingsPath, hookCommand, events: [event] });
85
+ }
86
+
87
+ async function areClaudeUsageHooksConfigured({ settingsPath, hookCommand }) {
88
+ return areClaudeHooksConfigured({
89
+ settingsPath,
90
+ hookCommand,
91
+ events: CLAUDE_USAGE_EVENTS,
92
+ });
93
+ }
94
+
95
+ async function areClaudeHooksConfigured({ settingsPath, hookCommand, events }) {
66
96
  const settings = await readJson(settingsPath);
67
97
  if (!settings || typeof settings !== "object") return false;
68
98
  const hooks = settings.hooks;
69
99
  if (!hooks || typeof hooks !== "object") return false;
70
- const entries = normalizeEntries(hooks[event]);
71
- return hasHook(entries, hookCommand);
100
+ return normalizeEventList(events).every((event) => {
101
+ const entries = normalizeEntries(hooks[event]);
102
+ return hasHook(entries, hookCommand);
103
+ });
72
104
  }
73
105
 
74
106
  // Generic Session-hook command builder. CodeBuddy CLI is a Claude-Code fork
@@ -96,6 +128,11 @@ function normalizeEntries(raw) {
96
128
  return Array.isArray(raw) ? raw.slice() : [];
97
129
  }
98
130
 
131
+ function normalizeEventList(events) {
132
+ const values = Array.isArray(events) ? events : [];
133
+ return Array.from(new Set(values.filter((event) => typeof event === "string" && event)));
134
+ }
135
+
99
136
  function normalizeCommand(cmd) {
100
137
  if (Array.isArray(cmd)) return cmd.map((v) => String(v)).join("\u0000");
101
138
  if (typeof cmd === "string") return cmd.trim();
@@ -193,9 +230,13 @@ async function writeClaudeSettings({ settingsPath, settings }) {
193
230
  }
194
231
 
195
232
  module.exports = {
233
+ CLAUDE_USAGE_EVENTS,
196
234
  upsertClaudeHook,
235
+ upsertClaudeUsageHooks,
197
236
  removeClaudeHook,
237
+ removeClaudeUsageHooks,
198
238
  isClaudeHookConfigured,
239
+ areClaudeUsageHooksConfigured,
199
240
  buildClaudeHookCommand,
200
241
  buildHookCommand,
201
242
  // Aliases for callers that want a name unbiased toward Claude (the schema
@@ -4,7 +4,7 @@ const fs = require("node:fs/promises");
4
4
 
5
5
  const { readJson } = require("./fs");
6
6
  const { readCodexNotify, readEveryCodeNotify } = require("./codex-config");
7
- const { isClaudeHookConfigured, buildClaudeHookCommand } = require("./claude-config");
7
+ const { areClaudeUsageHooksConfigured, buildClaudeHookCommand } = require("./claude-config");
8
8
  const {
9
9
  resolveGeminiConfigDir,
10
10
  resolveGeminiSettingsPath,
@@ -81,7 +81,7 @@ async function collectTrackerDiagnostics({
81
81
  ? everyCodeNotifyRaw.map((v) => redactValue(v, home))
82
82
  : null;
83
83
  const claudeHookCommand = buildClaudeHookCommand(path.join(binDir, "notify.cjs"));
84
- const claudeHookConfigured = await isClaudeHookConfigured({
84
+ const claudeHookConfigured = await areClaudeUsageHooksConfigured({
85
85
  settingsPath: claudeConfigPath,
86
86
  hookCommand: claudeHookCommand,
87
87
  });
@@ -685,6 +685,7 @@ function runSyncCommand(extraEnv = {}, opts = {}) {
685
685
  const args = [TRACKER_BIN, "sync"];
686
686
  if (opts.auto === true) args.push("--auto");
687
687
  if (opts.background === true) args.push("--background");
688
+ if (opts.allLocalSources === true) args.push("--all-local-sources");
688
689
  if (opts.drain === true) args.push("--drain");
689
690
  const child = spawn(process.execPath, args, {
690
691
  env: { ...process.env, ...extraEnv },
@@ -1053,15 +1054,17 @@ function createLocalApiHandler({ queuePath }) {
1053
1054
  // sync is on. Cloud sync is a dashboard (WebView) preference persisted in
1054
1055
  // localStorage; the dashboard mirrors it here via POST
1055
1056
  // /functions/tokentracker-cloud-sync-pref so the auth-unaware popover can key
1056
- // off the same flag. Defaults OFF, exactly like the dashboard toggle.
1057
+ // off the same flag. Defaults ON, exactly like the dashboard toggle — every
1058
+ // consumer additionally requires a relayed refresh token, so the default only
1059
+ // takes effect for signed-in users; an explicit {enabled:false} still wins.
1057
1060
  const cloudSyncPrefPath = path.join(trackerDataDir, "cloud-sync-pref.json");
1058
1061
  let cloudSyncPrefCache;
1059
1062
  function getCloudSyncPref() {
1060
1063
  if (cloudSyncPrefCache === undefined) {
1061
1064
  try {
1062
- cloudSyncPrefCache = JSON.parse(fs.readFileSync(cloudSyncPrefPath, "utf8"))?.enabled === true;
1065
+ cloudSyncPrefCache = JSON.parse(fs.readFileSync(cloudSyncPrefPath, "utf8"))?.enabled !== false;
1063
1066
  } catch {
1064
- cloudSyncPrefCache = false;
1067
+ cloudSyncPrefCache = true;
1065
1068
  }
1066
1069
  }
1067
1070
  return cloudSyncPrefCache;
@@ -1693,6 +1696,7 @@ function createLocalApiHandler({ queuePath }) {
1693
1696
  const auto = body.auto === true && !drain;
1694
1697
  const background =
1695
1698
  auto && (body.background === true || body.lightweight === true);
1699
+ const allLocalSources = background && body.allLocalSources === true;
1696
1700
  if (typeof body.deviceToken === "string" && body.deviceToken.trim()) {
1697
1701
  extraEnv.TOKENTRACKER_DEVICE_TOKEN = body.deviceToken.trim();
1698
1702
  }
@@ -1724,7 +1728,12 @@ function createLocalApiHandler({ queuePath }) {
1724
1728
  extraEnv.TOKENTRACKER_DEVICE_TOKEN = issuedToken;
1725
1729
  }
1726
1730
  }
1727
- const result = await runSyncCommand(extraEnv, { drain, auto, background });
1731
+ const result = await runSyncCommand(extraEnv, {
1732
+ drain,
1733
+ auto,
1734
+ background,
1735
+ allLocalSources,
1736
+ });
1728
1737
  try {
1729
1738
  const { resetUsageLimitsCache } = require("./usage-limits");
1730
1739
  resetUsageLimitsCache();
@@ -2572,14 +2581,18 @@ function createLocalApiHandler({ queuePath }) {
2572
2581
  if (p === "/functions/tokentracker-usage-limits") {
2573
2582
  const { getUsageLimits, resetUsageLimitsCache } = require("./usage-limits");
2574
2583
  try {
2575
- const forceRefresh = url.searchParams.get("refresh");
2576
- if (forceRefresh === "1" || forceRefresh === "true") {
2584
+ const refreshParam = url.searchParams.get("refresh");
2585
+ const forceRefresh = refreshParam === "1" || refreshParam === "true";
2586
+ if (forceRefresh) {
2577
2587
  resetUsageLimitsCache();
2578
2588
  }
2579
2589
  const data = await getUsageLimits({
2580
2590
  home: os.homedir(),
2581
2591
  env: process.env,
2582
2592
  platform: process.platform,
2593
+ // Punches through the Claude disk fresh-cache (but not the 429
2594
+ // cooldown) — an explicit user refresh should hit upstream.
2595
+ forceRefresh,
2583
2596
  });
2584
2597
  json(res, data);
2585
2598
  } catch (e) {
@@ -312,6 +312,19 @@ async function removeOpenclawSessionPluginConfig({
312
312
  }
313
313
 
314
314
  function runOpenclawCli(args, env = process.env) {
315
+ // Test-only escape hatch: skip spawning the real `openclaw` CLI. Behaves like
316
+ // the CLI being absent (which is the CI environment anyway), so callers take
317
+ // their graceful skip path. Avoids a ~2s `plugins install --link` per cmdInit
318
+ // on dev machines that happen to have openclaw installed.
319
+ if ((env && env.TOKENTRACKER_SKIP_OPENCLAW_CLI) === "1") {
320
+ return {
321
+ code: 1,
322
+ skippedReason: "openclaw-cli-missing",
323
+ error: "skipped via TOKENTRACKER_SKIP_OPENCLAW_CLI",
324
+ stdout: "",
325
+ stderr: "",
326
+ };
327
+ }
315
328
  let res;
316
329
  try {
317
330
  res = cp.spawnSync("openclaw", args, {