tokentracker-cli 0.79.7 → 0.79.9
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.
- package/dashboard/dist/assets/{AchievementBadge-BgLKRfg6.js → AchievementBadge-B1WOPlu0.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DAZPKi69.js → AchievementsPage-9kHEQicS.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-Bpck20e3.js → AchievementsSection-Dw05PYPr.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-Di0rpmTQ.js → BadgeDetailModal-DaWgiijJ.js} +1 -1
- package/dashboard/dist/assets/{Card-CiVg1n9y.js → Card-DYhrLLM4.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-Dhw6SxU-.js → ClawdAnimated-BLy2LUKz.js} +1 -1
- package/dashboard/dist/assets/CommunityStatsModal-CWYqfht3.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-DD1Ri-dh.js → ConfirmModal-DcE2C24Y.js} +1 -1
- package/dashboard/dist/assets/{Controls-BQxSK041.js → Controls-CI7qw0-b.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-CAlgaBa7.js +19 -0
- package/dashboard/dist/assets/{DevicePage-BgByRtlZ.js → DevicePage-DinOyaeN.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-DigAXf6w.js → DialogClose-CjfSDrLo.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DWs-KIUA.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-Du1uOZMr.js → DialogTitle-f7gNmCca.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-Da6jgqZ_.js → FadeIn-D_5w0Qh0.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-Cqqy7vB7.js → HeaderGithubStar-DTZyNKyL.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DkTAw_dO.js → IpCheckPage-D8CViF98.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Dvb9s-kJ.js → LandingPage-CqN5KEjV.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-Ch7SLsZ_.js → LeaderboardAvatar-CwUVO4wn.js} +1 -1
- package/dashboard/dist/assets/LeaderboardPage-B12HJ9LA.js +2 -0
- package/dashboard/dist/assets/LeaderboardProfileModal-DFzPpsnV.js +73 -0
- package/dashboard/dist/assets/{LeaderboardProfilePage-DRautoWL.js → LeaderboardProfilePage-Z0ZYpNFd.js} +2 -2
- package/dashboard/dist/assets/{LimitsPage-Bwc3XCCg.js → LimitsPage-BpwioIa-.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-DftNXQsE.js → LocalOnlyNotice-Covi6zdt.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CffpNmm-.js → LoginPage-CRqbhwCx.js} +1 -1
- package/dashboard/dist/assets/{PetPage-C_lq7mMA.js → PetPage-BY_yObVj.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-B7L6V9OS.js → PopoverPopup-B2dE3Ary.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-P-VatRkZ.js → ResetPasswordPage-C8IEJbVc.js} +1 -1
- package/dashboard/dist/assets/{Select-NpNBO_zW.js → Select-BmRLYSqA.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DEqB9ndy.js → SelectItemText-FC46bk8b.js} +1 -1
- package/dashboard/dist/assets/SettingsPage-DipzOQVg.js +1 -0
- package/dashboard/dist/assets/{SkillsPage-Dy_sojV5.js → SkillsPage-CziypUTf.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CzbeMPoq.js → WidgetsPage-CJegJhVR.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-C9sMo5ha.js → WrappedPage-DcMiKvzf.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-D95HrgpM.js → agent-logos-S7bF1LwW.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-jaA_F8VA.js → arrow-up-right-D0yeL56y.js} +1 -1
- package/dashboard/dist/assets/check-Dn9BLYEf.js +1 -0
- package/dashboard/dist/assets/{copy-BUsG8Dxq.js → copy-D-z9ZtIu.js} +1 -1
- package/dashboard/dist/assets/{download-B89jns_I.js → download-Dwxnd3QG.js} +1 -1
- package/dashboard/dist/assets/{flame-DYJfKTkq.js → flame-Bdqk9A75.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-Ck4iJNuB.js → format-tokens-DNVcCRwm.js} +1 -1
- package/dashboard/dist/assets/{info-DgaOnN42.js → info-CMLT6oUI.js} +1 -1
- package/dashboard/dist/assets/{link-2-l6SkcxP3.js → link-2-BmiHu726.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-BTZ18vzb.js → loader-circle-CXVUQtzm.js} +1 -1
- package/dashboard/dist/assets/main-B9m79io-.js +1249 -0
- package/dashboard/dist/assets/provider-display-BoAesdke.js +42 -0
- package/dashboard/dist/assets/{trash-2-DDKBUuG2.js → trash-2-C9Rh2DAL.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-KM2l89gG.js → use-community-stats-aZwYxrXR.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-1RM6rjBg.js → use-limits-display-prefs-tJKR83RT.js} +1 -1
- package/dashboard/dist/assets/use-native-settings-PjfJPK5v.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-B3ExZ03c.js → use-ordered-list-D1QFmZUY.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-oapCFOoT.js → use-quality-per-dollar-pref-4N3Su4wC.js} +1 -1
- package/dashboard/dist/assets/{use-transform-Cd2VlHK0.js → use-transform-D9nurInL.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-BMwpdXJP.js → use-usage-limits-DapTpi_6.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-BA2GiiJp.js → useCurrency-B0DH9ZZA.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-CBHwgGF7.js → useScrollLock-DijV6DYS.js} +1 -1
- package/dashboard/dist/assets/useTokenFormat-nxdG6gi9.js +1 -0
- package/dashboard/dist/assets/{zap-C3aLlz8b.js → zap-BVfhR3Ck.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/init.js +78 -12
- package/src/commands/serve.js +65 -4
- package/src/commands/status.js +2 -1
- package/src/commands/sync.js +10 -1
- package/src/commands/uninstall.js +10 -2
- package/src/lib/claude-config.js +69 -28
- package/src/lib/diagnostics.js +2 -2
- package/src/lib/local-api.js +8 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/dashboard/dist/assets/CommunityStatsModal-DosBW4lR.js +0 -1
- package/dashboard/dist/assets/DashboardPage-B7F7CLG5.js +0 -19
- package/dashboard/dist/assets/DialogDescription-CS0HEX8l.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-BbPfFFuF.js +0 -2
- package/dashboard/dist/assets/LeaderboardProfileModal-PFZaQLMG.js +0 -73
- package/dashboard/dist/assets/SettingsPage-uCZ0OdF3.js +0 -1
- package/dashboard/dist/assets/check-ShRr2Grp.js +0 -1
- package/dashboard/dist/assets/main-BVPlyJJu.js +0 -1245
- package/dashboard/dist/assets/provider-display-DuQlae7O.js +0 -42
- package/dashboard/dist/assets/use-native-settings-S7AyC9Ou.js +0 -1
package/src/commands/serve.js
CHANGED
|
@@ -22,6 +22,7 @@ const WSL_DEFAULT_PORT = 7681;
|
|
|
22
22
|
const DEFAULT_MAX_PORT_ATTEMPTS = 20;
|
|
23
23
|
const NPM_PACKAGE_NAME = "tokentracker-cli";
|
|
24
24
|
const LOCAL_BIND_HOST = "127.0.0.1";
|
|
25
|
+
const NATIVE_BACKGROUND_SYNC_INTERVAL_MS = 60_000;
|
|
25
26
|
const STATIC_ASSET_EXTENSIONS = new Set([
|
|
26
27
|
".css",
|
|
27
28
|
".gif",
|
|
@@ -71,11 +72,13 @@ async function cmdServe(argv) {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
try {
|
|
74
|
-
const { installLocalTrackerApp,
|
|
75
|
+
const { installLocalTrackerApp, repairRuntimeIntegrations } = require("./init");
|
|
75
76
|
await installLocalTrackerApp({ appDir: path.join(trackerDir, "app") });
|
|
76
|
-
const repairResult = await
|
|
77
|
-
|
|
78
|
-
process.stdout.write(
|
|
77
|
+
const repairResult = await repairRuntimeIntegrations({ trackerDir, binDir, safeMode: true });
|
|
78
|
+
for (const warning of repairResult?.warnings || []) {
|
|
79
|
+
process.stdout.write(
|
|
80
|
+
`Runtime integration repair warning (${warning.integration}): ${warning.error}\n`,
|
|
81
|
+
);
|
|
79
82
|
}
|
|
80
83
|
} catch (e) {
|
|
81
84
|
process.stdout.write(`Runtime refresh warning: ${e?.message || e}\n`);
|
|
@@ -217,6 +220,18 @@ async function cmdServe(argv) {
|
|
|
217
220
|
|
|
218
221
|
await maybeShowStarCta({ trackerDir });
|
|
219
222
|
|
|
223
|
+
// Native desktop apps keep this server alive even when the dashboard window
|
|
224
|
+
// is closed. Provider hooks are the fast path, but they can be removed by a
|
|
225
|
+
// tool update or skipped by a provider. Scan every local source once a
|
|
226
|
+
// minute as a bounded fallback so the queue cannot remain stale indefinitely.
|
|
227
|
+
// `--no-sync` still skips the blocking startup sync; this periodic pass is
|
|
228
|
+
// native-shell-only and uses the lightweight local mode (no cloud upload,
|
|
229
|
+
// Cursor network request, or deep Codex archive scan).
|
|
230
|
+
const nativeBackgroundSync = startNativeBackgroundSync({
|
|
231
|
+
onError: (e) =>
|
|
232
|
+
process.stdout.write(`Background local sync warning: ${e?.message || e}\n`),
|
|
233
|
+
});
|
|
234
|
+
|
|
220
235
|
// Anonymous daily heartbeat (see src/lib/telemetry.js for the privacy
|
|
221
236
|
// contract). Fire-and-forget at startup, then re-checked every 6 hours so
|
|
222
237
|
// long-lived embedded-app servers still count on later days; the shared
|
|
@@ -238,6 +253,7 @@ async function cmdServe(argv) {
|
|
|
238
253
|
// 5. Graceful shutdown
|
|
239
254
|
const shutdown = () => {
|
|
240
255
|
process.stdout.write("\nShutting down...\n");
|
|
256
|
+
nativeBackgroundSync?.stop();
|
|
241
257
|
server.close(() => process.exit(0));
|
|
242
258
|
setTimeout(() => process.exit(0), 3000);
|
|
243
259
|
};
|
|
@@ -248,6 +264,49 @@ async function cmdServe(argv) {
|
|
|
248
264
|
await new Promise(() => {});
|
|
249
265
|
}
|
|
250
266
|
|
|
267
|
+
function startNativeBackgroundSync({
|
|
268
|
+
appShell = process.env.TOKENTRACKER_APP_SHELL,
|
|
269
|
+
intervalMs = NATIVE_BACKGROUND_SYNC_INTERVAL_MS,
|
|
270
|
+
runSync,
|
|
271
|
+
setIntervalFn = setInterval,
|
|
272
|
+
clearIntervalFn = clearInterval,
|
|
273
|
+
onError = () => {},
|
|
274
|
+
} = {}) {
|
|
275
|
+
const normalizedShell = String(appShell || "").trim().toLowerCase();
|
|
276
|
+
if (normalizedShell !== "macos" && normalizedShell !== "windows") return null;
|
|
277
|
+
|
|
278
|
+
const sync = runSync || (async (args) => {
|
|
279
|
+
const { cmdSync } = require("./sync");
|
|
280
|
+
await cmdSync(args);
|
|
281
|
+
});
|
|
282
|
+
let inFlight = null;
|
|
283
|
+
const run = () => {
|
|
284
|
+
if (inFlight) return inFlight;
|
|
285
|
+
const pending = Promise.resolve()
|
|
286
|
+
.then(() => sync(["--auto", "--background", "--all-local-sources"]))
|
|
287
|
+
.catch((error) => {
|
|
288
|
+
try { onError(error); } catch (_e) {}
|
|
289
|
+
})
|
|
290
|
+
.finally(() => {
|
|
291
|
+
if (inFlight === pending) inFlight = null;
|
|
292
|
+
});
|
|
293
|
+
inFlight = pending;
|
|
294
|
+
return pending;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const timer = setIntervalFn(() => {
|
|
298
|
+
void run();
|
|
299
|
+
}, intervalMs);
|
|
300
|
+
timer?.unref?.();
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
run,
|
|
304
|
+
stop() {
|
|
305
|
+
clearIntervalFn(timer);
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
251
310
|
function findPidOnPort(port) {
|
|
252
311
|
try {
|
|
253
312
|
const out = cp.execFileSync("lsof", ["-ti", `tcp:${port}`], { encoding: "utf8", timeout: 5000 });
|
|
@@ -455,4 +514,6 @@ module.exports = {
|
|
|
455
514
|
isRunningUnderWsl,
|
|
456
515
|
resolveDefaultPort,
|
|
457
516
|
shouldServeSpaFallback,
|
|
517
|
+
startNativeBackgroundSync,
|
|
518
|
+
NATIVE_BACKGROUND_SYNC_INTERVAL_MS,
|
|
458
519
|
};
|
package/src/commands/status.js
CHANGED
|
@@ -8,6 +8,7 @@ const { readJson } = require("../lib/fs");
|
|
|
8
8
|
const { readCodexNotify, readEveryCodeNotify } = require("../lib/codex-config");
|
|
9
9
|
const {
|
|
10
10
|
isClaudeHookConfigured,
|
|
11
|
+
areClaudeUsageHooksConfigured,
|
|
11
12
|
buildClaudeHookCommand,
|
|
12
13
|
buildHookCommand,
|
|
13
14
|
} = require("../lib/claude-config");
|
|
@@ -155,7 +156,7 @@ async function cmdStatus(argv = []) {
|
|
|
155
156
|
const everyCodeNotify = await readEveryCodeNotify(codeConfigPath);
|
|
156
157
|
const everyCodeConfigured =
|
|
157
158
|
Array.isArray(everyCodeNotify) && everyCodeNotify.length > 0;
|
|
158
|
-
const claudeHookConfigured = await
|
|
159
|
+
const claudeHookConfigured = await areClaudeUsageHooksConfigured({
|
|
159
160
|
settingsPath: claudeSettingsPath,
|
|
160
161
|
hookCommand: claudeHookCommand,
|
|
161
162
|
});
|
package/src/commands/sync.js
CHANGED
|
@@ -330,11 +330,18 @@ async function cmdSync(argv) {
|
|
|
330
330
|
|
|
331
331
|
const autoSourceScope = resolveAutoSourceScope(opts);
|
|
332
332
|
const isBackgroundLightweightSync = opts.auto && opts.background && !opts.drain;
|
|
333
|
+
const isBackgroundAllLocalSync = isBackgroundLightweightSync && opts.allLocalSources;
|
|
333
334
|
const isFullSourceScan = !autoSourceScope && !isBackgroundLightweightSync;
|
|
334
335
|
const sourceAllowed = (...sources) => {
|
|
335
336
|
if (isBackgroundLightweightSync) {
|
|
336
337
|
if (autoSourceScope) {
|
|
337
|
-
return
|
|
338
|
+
return (
|
|
339
|
+
(isBackgroundAllLocalSync || BACKGROUND_AUTO_SYNC_SOURCES.has(autoSourceScope)) &&
|
|
340
|
+
sources.includes(autoSourceScope)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
if (isBackgroundAllLocalSync) {
|
|
344
|
+
return sources.some((source) => AUTO_SYNC_SOURCES.has(source));
|
|
338
345
|
}
|
|
339
346
|
return sources.some((source) => BACKGROUND_AUTO_SYNC_SOURCES.has(source));
|
|
340
347
|
}
|
|
@@ -1998,6 +2005,7 @@ function parseArgs(argv) {
|
|
|
1998
2005
|
source: null,
|
|
1999
2006
|
drain: false,
|
|
2000
2007
|
background: false,
|
|
2008
|
+
allLocalSources: false,
|
|
2001
2009
|
repairGrok: false,
|
|
2002
2010
|
};
|
|
2003
2011
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -2013,6 +2021,7 @@ function parseArgs(argv) {
|
|
|
2013
2021
|
else if (a.startsWith("--source=")) out.source = normalizeSyncSource(a.slice("--source=".length));
|
|
2014
2022
|
else if (a === "--drain") out.drain = true;
|
|
2015
2023
|
else if (a === "--background" || a === "--lightweight") out.background = true;
|
|
2024
|
+
else if (a === "--all-local-sources") out.allLocalSources = true;
|
|
2016
2025
|
else if (a === "--repair-grok") out.repairGrok = true;
|
|
2017
2026
|
else throw new Error(`Unknown option: ${a}`);
|
|
2018
2027
|
}
|
|
@@ -3,7 +3,12 @@ const path = require("node:path");
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
4
|
|
|
5
5
|
const { restoreCodexNotify, restoreEveryCodeNotify } = require("../lib/codex-config");
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
removeClaudeHook,
|
|
8
|
+
removeClaudeUsageHooks,
|
|
9
|
+
buildClaudeHookCommand,
|
|
10
|
+
buildHookCommand,
|
|
11
|
+
} = require("../lib/claude-config");
|
|
7
12
|
const {
|
|
8
13
|
resolveGeminiConfigDir,
|
|
9
14
|
resolveGeminiSettingsPath,
|
|
@@ -64,7 +69,10 @@ async function cmdUninstall(argv) {
|
|
|
64
69
|
})
|
|
65
70
|
: { restored: false, skippedReason: "config-missing" };
|
|
66
71
|
const claudeRemove = claudeConfigExists
|
|
67
|
-
? await
|
|
72
|
+
? await removeClaudeUsageHooks({
|
|
73
|
+
settingsPath: claudeSettingsPath,
|
|
74
|
+
hookCommand: claudeHookCommand,
|
|
75
|
+
})
|
|
68
76
|
: { removed: false, skippedReason: "config-missing" };
|
|
69
77
|
const codebuddyRemove = codebuddyConfigExists
|
|
70
78
|
? await removeClaudeHook({
|
package/src/lib/claude-config.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
|
41
|
-
if (entries.length === 0) return { removed: false, skippedReason: "hook-missing" };
|
|
42
|
-
|
|
59
|
+
const nextHooks = { ...hooks };
|
|
43
60
|
let removed = false;
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
package/src/lib/diagnostics.js
CHANGED
|
@@ -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 {
|
|
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
|
|
84
|
+
const claudeHookConfigured = await areClaudeUsageHooksConfigured({
|
|
85
85
|
settingsPath: claudeConfigPath,
|
|
86
86
|
hookCommand: claudeHookCommand,
|
|
87
87
|
});
|
package/src/lib/local-api.js
CHANGED
|
@@ -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 },
|
|
@@ -1693,6 +1694,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1693
1694
|
const auto = body.auto === true && !drain;
|
|
1694
1695
|
const background =
|
|
1695
1696
|
auto && (body.background === true || body.lightweight === true);
|
|
1697
|
+
const allLocalSources = background && body.allLocalSources === true;
|
|
1696
1698
|
if (typeof body.deviceToken === "string" && body.deviceToken.trim()) {
|
|
1697
1699
|
extraEnv.TOKENTRACKER_DEVICE_TOKEN = body.deviceToken.trim();
|
|
1698
1700
|
}
|
|
@@ -1724,7 +1726,12 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1724
1726
|
extraEnv.TOKENTRACKER_DEVICE_TOKEN = issuedToken;
|
|
1725
1727
|
}
|
|
1726
1728
|
}
|
|
1727
|
-
const result = await runSyncCommand(extraEnv, {
|
|
1729
|
+
const result = await runSyncCommand(extraEnv, {
|
|
1730
|
+
drain,
|
|
1731
|
+
auto,
|
|
1732
|
+
background,
|
|
1733
|
+
allLocalSources,
|
|
1734
|
+
});
|
|
1728
1735
|
try {
|
|
1729
1736
|
const { resetUsageLimitsCache } = require("./usage-limits");
|
|
1730
1737
|
resetUsageLimitsCache();
|