@pushary/agent-hooks 0.29.0 → 0.30.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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  removeClaudeMcpServers,
4
4
  removePusharySettings
5
- } from "../chunk-QNEYHDKR.js";
5
+ } from "../chunk-H3LQRYMW.js";
6
6
  import {
7
7
  removeCodexHooks,
8
8
  removeGeminiSettings,
@@ -10,13 +10,14 @@ import {
10
10
  } from "../chunk-XY6OKUQ4.js";
11
11
  import {
12
12
  execNpm
13
- } from "../chunk-RSHN2AQ7.js";
13
+ } from "../chunk-J7JWI3KU.js";
14
14
  import "../chunk-Z5PL3K7C.js";
15
15
 
16
16
  // bin/pushary-clean.ts
17
- import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
17
+ import { existsSync, readFileSync, writeFileSync, rmSync, readdirSync } from "fs";
18
18
  import { join } from "path";
19
- import { homedir } from "os";
19
+ import { homedir, tmpdir } from "os";
20
+ import { execSync } from "child_process";
20
21
  import { confirm } from "@inquirer/prompts";
21
22
  import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
22
23
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
@@ -31,7 +32,25 @@ var CLAUDE_JSON = join(homedir(), ".claude.json");
31
32
  var SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
32
33
  var CURSOR_MCP = join(".cursor", "mcp.json");
33
34
  var CURSOR_PLUGIN_DIR = join(homedir(), ".cursor", "plugins", "local", "pushary");
35
+ var CURSOR_USER_HOOKS = join(homedir(), ".cursor", "hooks.json");
36
+ var PUSHARY_DIR = join(homedir(), ".pushary");
34
37
  var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
38
+ var resolveHermesPython = () => {
39
+ try {
40
+ const launcher = execSync("command -v hermes", { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).trim();
41
+ if (launcher) {
42
+ const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
43
+ if (shebang.startsWith("#!")) {
44
+ const interpreter = shebang.slice(2).trim().split(/\s+/)[0];
45
+ if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
46
+ }
47
+ }
48
+ } catch {
49
+ }
50
+ const venvPython = join(homedir(), ".hermes", "hermes-agent", "venv", "bin", "python3");
51
+ if (existsSync(venvPython)) return venvPython;
52
+ return null;
53
+ };
35
54
  var readJson = (path) => {
36
55
  try {
37
56
  return JSON.parse(readFileSync(path, "utf-8"));
@@ -61,7 +80,12 @@ var main = async () => {
61
80
  console.log(` ${bold("Pushary Clean")}`);
62
81
  console.log(` ${dim("Removes all Pushary configuration")}`);
63
82
  console.log();
64
- const proceed = await confirm({ message: "Remove all Pushary configuration?", default: false });
83
+ const assumeYes = process.argv.includes("--yes") || process.argv.includes("-y");
84
+ if (!assumeYes && !process.stdin.isTTY) {
85
+ console.log(` ${yellow("!")} Non-interactive shell. Re-run with --yes to confirm removal.`);
86
+ process.exit(1);
87
+ }
88
+ const proceed = assumeYes || await confirm({ message: "Remove all Pushary configuration?", default: false });
65
89
  if (!proceed) {
66
90
  console.log(` ${dim("Cancelled.")}`);
67
91
  process.exit(0);
@@ -99,6 +123,23 @@ var main = async () => {
99
123
  } else {
100
124
  console.log(` ${skip} Cursor plugin ${dim("(not installed)")}`);
101
125
  }
126
+ const cursorHooks = readJson(CURSOR_USER_HOOKS);
127
+ if (cursorHooks) {
128
+ const hooks = cursorHooks.hooks ?? {};
129
+ const existing = Array.isArray(hooks.beforeShellExecution) ? hooks.beforeShellExecution : [];
130
+ const others = existing.filter((h) => !String(h.command ?? "").includes("pushary-gate"));
131
+ if (others.length !== existing.length) {
132
+ if (others.length === 0) delete hooks.beforeShellExecution;
133
+ else hooks.beforeShellExecution = others;
134
+ cursorHooks.hooks = hooks;
135
+ writeJson(CURSOR_USER_HOOKS, cursorHooks);
136
+ console.log(` ${check} Cursor gate ${dim("(removed from ~/.cursor/hooks.json)")}`);
137
+ } else {
138
+ console.log(` ${skip} Cursor gate ${dim("(no pushary entries)")}`);
139
+ }
140
+ } else {
141
+ console.log(` ${skip} Cursor gate ${dim("(no hooks.json)")}`);
142
+ }
102
143
  if (existsSync(SKILL_DIR)) {
103
144
  rmSync(SKILL_DIR, { recursive: true });
104
145
  console.log(` ${check} Skill directory ${dim("(removed)")}`);
@@ -170,6 +211,24 @@ var main = async () => {
170
211
  } else {
171
212
  console.log(` ${skip} Gemini GEMINI.md ${dim("(no pushary block)")}`);
172
213
  }
214
+ const hermesPython = resolveHermesPython();
215
+ if (hermesPython) {
216
+ const snippet = 'from hermes_cli.config import load_config, save_config; c = load_config(); p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}; e = p.get("enabled") if isinstance(p.get("enabled"), list) else []; p["enabled"] = [x for x in e if x != "pushary"]; c["plugins"] = p; a = c.get("agent") if isinstance(c.get("agent"), dict) else {}; d = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []; a["disabled_toolsets"] = [x for x in d if x != "clarify"]; c["agent"] = a; save_config(c)';
217
+ try {
218
+ execSync(`"${hermesPython}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 });
219
+ console.log(` ${check} Hermes config ${dim("(plugin disabled, clarify toolset restored)")}`);
220
+ } catch {
221
+ console.log(` ${skip} Hermes config ${dim("(could not update config.yaml)")}`);
222
+ }
223
+ try {
224
+ execSync(`"${hermesPython}" -m pip uninstall -y hermes-plugin-pushary`, { stdio: "pipe", timeout: 6e4 });
225
+ console.log(` ${check} Hermes plugin ${dim("(pip uninstalled)")}`);
226
+ } catch {
227
+ console.log(` ${skip} Hermes plugin ${dim("(not installed)")}`);
228
+ }
229
+ } else {
230
+ console.log(` ${skip} Hermes ${dim("(not found)")}`);
231
+ }
173
232
  for (const shellFile of SHELL_FILES) {
174
233
  try {
175
234
  const content = readFileSync(shellFile, "utf-8");
@@ -181,6 +240,25 @@ var main = async () => {
181
240
  } catch {
182
241
  }
183
242
  }
243
+ if (existsSync(PUSHARY_DIR)) {
244
+ rmSync(PUSHARY_DIR, { recursive: true });
245
+ console.log(` ${check} Local state ${dim("(~/.pushary removed: stored API key, ledger)")}`);
246
+ } else {
247
+ console.log(` ${skip} Local state ${dim("(~/.pushary not found)")}`);
248
+ }
249
+ try {
250
+ let swept = 0;
251
+ for (const f of readdirSync(tmpdir())) {
252
+ if (!f.startsWith("pushary-")) continue;
253
+ try {
254
+ rmSync(join(tmpdir(), f), { recursive: true, force: true });
255
+ swept++;
256
+ } catch {
257
+ }
258
+ }
259
+ if (swept > 0) console.log(` ${check} Temp state ${dim(`(${swept} pushary entries swept)`)}`);
260
+ } catch {
261
+ }
184
262
  try {
185
263
  execNpm("uninstall -g --no-workspaces @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
186
264
  console.log(` ${check} Global package ${dim("(uninstalled)")}`);
@@ -3,6 +3,10 @@ import {
3
3
  denyReasonFrom,
4
4
  isDeferAnswer
5
5
  } from "../chunk-KQYIHZ5E.js";
6
+ import {
7
+ isGatingMoment,
8
+ recordKeylessMoment
9
+ } from "../chunk-R5AJNXZS.js";
6
10
  import {
7
11
  CODEX_AGENT,
8
12
  DEFAULT_SESSION,
@@ -201,8 +205,25 @@ var decidePermissionRequest = async (input) => {
201
205
  }
202
206
  };
203
207
  var decidePreToolUse = async (input) => {
208
+ let apiKey;
209
+ try {
210
+ apiKey = getApiKey();
211
+ } catch {
212
+ try {
213
+ const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
214
+ if (isGatingMoment(lookup.tool, lookup.input)) {
215
+ recordKeylessMoment({
216
+ ts: Date.now(),
217
+ tool: lookup.tool,
218
+ project: basename(input.cwd ?? process.cwd()),
219
+ sessionId: input.session_id
220
+ });
221
+ }
222
+ } catch {
223
+ }
224
+ return codexPass();
225
+ }
204
226
  try {
205
- const apiKey = getApiKey();
206
227
  const modeState = await fetchModeState(apiKey, input.session_id);
207
228
  if (modeState.kill) return codexDeny(KILL_REASON);
208
229
  const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
@@ -14,7 +14,10 @@ import {
14
14
  } from "../chunk-XY6OKUQ4.js";
15
15
  import {
16
16
  execNpm
17
- } from "../chunk-RSHN2AQ7.js";
17
+ } from "../chunk-J7JWI3KU.js";
18
+ import {
19
+ readLedgerSummary
20
+ } from "../chunk-R5AJNXZS.js";
18
21
  import {
19
22
  callMcpTool,
20
23
  sendMcpRequest
@@ -317,12 +320,29 @@ var main = async () => {
317
320
  globalVersion = "";
318
321
  }
319
322
  check(!!globalVersion, "Global package installed", globalVersion || "not found");
323
+ if (globalVersion) {
324
+ try {
325
+ const res = await fetch("https://registry.npmjs.org/@pushary/agent-hooks/latest", {
326
+ signal: AbortSignal.timeout(5e3)
327
+ });
328
+ const latest = res.ok ? (await res.json()).version ?? "" : "";
329
+ if (latest) {
330
+ const upToDate = latest === globalVersion;
331
+ check(upToDate, "Global package up to date", upToDate ? globalVersion : `${globalVersion} installed, ${latest} available \u2014 run npx @pushary/agent-hooks@latest upgrade`);
332
+ }
333
+ } catch {
334
+ }
335
+ }
320
336
  console.log();
321
337
  console.log(` ${dim("Connectivity")}`);
322
338
  if (!apiKey) {
323
339
  check(false, "MCP server reachable", "skipped \u2014 no API key");
324
340
  check(false, "API key valid", "skipped");
325
341
  check(false, "MCP handshake", "skipped");
342
+ const keylessSummary = readLedgerSummary(7);
343
+ if (keylessSummary.count > 0) {
344
+ check(false, "Keyless approval moments (7d)", `${keylessSummary.count} counted locally \u2014 npx @pushary/agent-hooks@latest stats`);
345
+ }
326
346
  } else {
327
347
  let sessionId = "";
328
348
  try {
@@ -379,7 +399,7 @@ var main = async () => {
379
399
  const msg = err instanceof Error ? err.message : "network error";
380
400
  check(false, "Push notification sent", msg);
381
401
  }
382
- const testQuestion = await confirm({ message: "Test question roundtrip? (sends a push notification)", default: false });
402
+ const testQuestion = process.stdin.isTTY ? await confirm({ message: "Test question roundtrip? (sends a push notification)", default: false }) : false;
383
403
  if (testQuestion) {
384
404
  console.log();
385
405
  console.log(` ${dim("Question Roundtrip")}`);
@@ -418,7 +438,8 @@ var main = async () => {
418
438
  if (apiKey) {
419
439
  try {
420
440
  const res = await fetch(`${getBaseUrl()}/api/mcp/policy`, {
421
- headers: { Authorization: `Bearer ${apiKey}` }
441
+ headers: { Authorization: `Bearer ${apiKey}` },
442
+ signal: AbortSignal.timeout(1e4)
422
443
  });
423
444
  if (res.ok) {
424
445
  const policy = await res.json();
@@ -453,4 +474,7 @@ var main = async () => {
453
474
  }
454
475
  console.log();
455
476
  };
456
- main();
477
+ main().catch((err) => {
478
+ console.error(` doctor failed: ${err instanceof Error ? err.message : String(err)}`);
479
+ process.exit(1);
480
+ });
@@ -3,6 +3,10 @@ import {
3
3
  denyReasonFrom,
4
4
  isDeferAnswer
5
5
  } from "../chunk-KQYIHZ5E.js";
6
+ import {
7
+ isGatingMoment,
8
+ recordKeylessMoment
9
+ } from "../chunk-R5AJNXZS.js";
6
10
  import {
7
11
  DEFAULT_SESSION,
8
12
  askUser,
@@ -181,8 +185,25 @@ var handlePushFirst = async (apiKey, input, lookup, pushFirstSeconds) => {
181
185
  return geminiPass();
182
186
  };
183
187
  var decideBeforeTool = async (input) => {
188
+ let apiKey;
189
+ try {
190
+ apiKey = getApiKey();
191
+ } catch {
192
+ try {
193
+ const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {});
194
+ if (isGatingMoment(lookup.tool, lookup.input)) {
195
+ recordKeylessMoment({
196
+ ts: Date.now(),
197
+ tool: lookup.tool,
198
+ project: basename(input.cwd ?? process.cwd()),
199
+ sessionId: input.session_id
200
+ });
201
+ }
202
+ } catch {
203
+ }
204
+ return geminiPass();
205
+ }
184
206
  try {
185
- const apiKey = getApiKey();
186
207
  const modeState = await fetchModeState(apiKey, input.session_id);
187
208
  if (modeState.kill) return geminiDeny(KILL_REASON);
188
209
  const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {});
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-GPLKEAFG.js";
5
- import "../chunk-WLF3NCHS.js";
4
+ } from "../chunk-CY5YP34L.js";
6
5
  import "../chunk-KQYIHZ5E.js";
6
+ import "../chunk-R5AJNXZS.js";
7
7
  import "../chunk-IAOXM7X5.js";
8
8
  import "../chunk-DWED7BS3.js";
9
9
  import "../chunk-Z5PL3K7C.js";
@@ -3,7 +3,7 @@ import {
3
3
  addClaudeMcpServer,
4
4
  addPusharyHooks,
5
5
  addPusharyToolPermissions
6
- } from "../chunk-QNEYHDKR.js";
6
+ } from "../chunk-H3LQRYMW.js";
7
7
  import {
8
8
  GEMINI_HOOK_BINARY,
9
9
  addCodexHookTrust,
@@ -17,8 +17,11 @@ import {
17
17
  } from "../chunk-XY6OKUQ4.js";
18
18
  import {
19
19
  execNpm,
20
- npmErrorMessage
21
- } from "../chunk-RSHN2AQ7.js";
20
+ npmErrorMessage,
21
+ quoteIfNeeded,
22
+ resolveGlobalBinDir,
23
+ resolveGlobalBinary
24
+ } from "../chunk-J7JWI3KU.js";
22
25
  import {
23
26
  reportEvent
24
27
  } from "../chunk-IAOXM7X5.js";
@@ -463,13 +466,7 @@ var setupClaudeCode = async (apiKey) => {
463
466
  });
464
467
  await installGlobally();
465
468
  await spinner("Adding hooks (PreToolUse, PostToolUse, UserPromptSubmit, Stop)", async () => {
466
- let binDir;
467
- try {
468
- binDir = join(execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim(), "bin");
469
- } catch {
470
- binDir = void 0;
471
- }
472
- addPusharyHooks(settings, binDir);
469
+ addPusharyHooks(settings, resolveGlobalBinDir("pushary-hook"));
473
470
  });
474
471
  await spinner(`Writing ${CLAUDE_SETTINGS}`, async () => {
475
472
  writeJson(CLAUDE_SETTINGS, settings);
@@ -591,9 +588,7 @@ var removeCodexNotifyEntry = (codexConfig) => {
591
588
  writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
592
589
  };
593
590
  var addCodexNotifyEntry = (codexConfig) => {
594
- const globalPrefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
595
- const pusharyCodexPath = join(globalPrefix, "bin", "pushary-codex");
596
- if (!existsSync(pusharyCodexPath)) throw new Error("pushary-codex not found at " + pusharyCodexPath);
591
+ const pusharyCodexPath = resolveGlobalBinary("pushary-codex") ?? "pushary-codex";
597
592
  let raw = "";
598
593
  try {
599
594
  raw = readFileSync(codexConfig, "utf-8");
@@ -633,10 +628,8 @@ var setupCodex = async (apiKey) => {
633
628
  const trustAuto = codexTrustAutoSupported(codexVersion);
634
629
  let trusted = false;
635
630
  if (hooksSupported) {
636
- const globalPrefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
637
- const hookCommand = join(globalPrefix, "bin", "pushary-codex-hook");
631
+ const hookCommand = quoteIfNeeded(resolveGlobalBinary("pushary-codex-hook") ?? "pushary-codex-hook");
638
632
  await spinner("Adding native hooks (~/.codex/hooks.json)", async () => {
639
- if (!existsSync(hookCommand)) throw new Error("pushary-codex-hook not found at " + hookCommand);
640
633
  const hooksConfig = readJson(CODEX_HOOKS_JSON);
641
634
  addCodexHooks(hooksConfig, hookCommand);
642
635
  writeJson(CODEX_HOOKS_JSON, hooksConfig);
@@ -822,13 +815,7 @@ var setupGemini = async (apiKey) => {
822
815
  addGeminiMcpServer(settings, apiKey);
823
816
  });
824
817
  await spinner("Adding hooks (BeforeTool, AfterTool, BeforeAgent, SessionStart, SessionEnd)", async () => {
825
- let hookCommand = GEMINI_HOOK_BINARY;
826
- try {
827
- const candidate = join(execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim(), "bin", GEMINI_HOOK_BINARY);
828
- if (existsSync(candidate)) hookCommand = candidate;
829
- } catch {
830
- }
831
- addGeminiHooks(settings, hookCommand);
818
+ addGeminiHooks(settings, quoteIfNeeded(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
832
819
  });
833
820
  await spinner(`Writing ${GEMINI_SETTINGS}`, async () => {
834
821
  writeJson(GEMINI_SETTINGS, settings);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readLedgerSummary
4
- } from "../chunk-WLF3NCHS.js";
4
+ } from "../chunk-R5AJNXZS.js";
5
5
  import "../chunk-Z5PL3K7C.js";
6
6
  import {
7
7
  getApiKey
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  execNpm,
4
4
  npmErrorMessage
5
- } from "../chunk-RSHN2AQ7.js";
5
+ } from "../chunk-J7JWI3KU.js";
6
6
 
7
7
  // bin/pushary-upgrade.ts
8
8
  var getInstalledVersion = () => {
@@ -25,7 +25,7 @@ Pushary Agent Hooks
25
25
  Commands:
26
26
  setup Configure Claude Code, Codex, Gemini CLI, Hermes, or Cursor with Pushary
27
27
  doctor Verify your Pushary installation is working
28
- clean Remove all Pushary configuration
28
+ clean Remove all Pushary configuration (--yes for non-interactive)
29
29
  mode Switch approval mode (push_only, push_first, terminal_only)
30
30
  wait Show or set the "wait for your phone" ladder (pushary wait 45)
31
31
  stats Show the approval moments your agents hit while not connected
@@ -1,11 +1,11 @@
1
- import {
2
- isGatingMoment,
3
- recordKeylessMoment
4
- } from "./chunk-WLF3NCHS.js";
5
1
  import {
6
2
  denyReasonFrom,
7
3
  isDeferAnswer
8
4
  } from "./chunk-KQYIHZ5E.js";
5
+ import {
6
+ isGatingMoment,
7
+ recordKeylessMoment
8
+ } from "./chunk-R5AJNXZS.js";
9
9
  import {
10
10
  DEFAULT_SESSION,
11
11
  askUser,
@@ -60,7 +60,10 @@ var addPusharyToolPermissions = (settings) => {
60
60
  permissions.allow = filtered;
61
61
  };
62
62
  var addPusharyHooks = (settings, binDir) => {
63
- const resolve = (name) => binDir ? join(binDir, name) : name;
63
+ const resolve = (name) => {
64
+ const path = binDir ? join(binDir, name) : name;
65
+ return /\s/.test(path) ? `"${path}"` : path;
66
+ };
64
67
  const hooks = ensureRecord(settings, "hooks");
65
68
  const preToolUse = (Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : []).filter((entry) => !isPusharyHook(entry));
66
69
  preToolUse.push({
@@ -1,5 +1,7 @@
1
1
  // src/npm.ts
2
2
  import { execSync } from "child_process";
3
+ import { existsSync } from "fs";
4
+ import { join } from "path";
3
5
  var cleanNpmEnv = () => {
4
6
  const env = {};
5
7
  for (const [key, value] of Object.entries(process.env)) {
@@ -22,8 +24,26 @@ var execNpm = (args, options = {}) => {
22
24
  env: { ...cleanNpmEnv(), ...options.env ?? {} }
23
25
  });
24
26
  };
27
+ var resolveGlobalBinary = (name) => {
28
+ try {
29
+ const prefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
30
+ const candidates = process.platform === "win32" ? [join(prefix, `${name}.cmd`), join(prefix, name)] : [join(prefix, "bin", name)];
31
+ return candidates.find(existsSync);
32
+ } catch {
33
+ return void 0;
34
+ }
35
+ };
36
+ var resolveGlobalBinDir = (probe) => {
37
+ if (process.platform === "win32") return void 0;
38
+ const resolved = resolveGlobalBinary(probe);
39
+ return resolved ? resolved.slice(0, resolved.length - probe.length - 1) : void 0;
40
+ };
41
+ var quoteIfNeeded = (path) => /\s/.test(path) ? `"${path}"` : path;
25
42
 
26
43
  export {
27
44
  npmErrorMessage,
28
- execNpm
45
+ execNpm,
46
+ resolveGlobalBinary,
47
+ resolveGlobalBinDir,
48
+ quoteIfNeeded
29
49
  };
@@ -30,26 +30,28 @@ var recordKeylessMoment = (entry) => {
30
30
  var readLedgerSummary = (days = 7) => {
31
31
  const sinceMs = Date.now() - days * 864e5;
32
32
  const summary = { count: 0, sessions: 0, byTool: {}, sinceMs };
33
- let raw;
34
- try {
35
- raw = readFileSync(ledgerPath(), "utf-8");
36
- } catch {
37
- return summary;
38
- }
39
33
  const sessions = /* @__PURE__ */ new Set();
40
- for (const line of raw.split("\n")) {
41
- if (!line.trim()) continue;
42
- let entry;
34
+ for (const path of [`${ledgerPath()}.1`, ledgerPath()]) {
35
+ let raw;
43
36
  try {
44
- entry = JSON.parse(line);
37
+ raw = readFileSync(path, "utf-8");
45
38
  } catch {
46
39
  continue;
47
40
  }
48
- if (typeof entry.ts !== "number" || entry.ts < sinceMs) continue;
49
- summary.count += 1;
50
- const tool = typeof entry.tool === "string" && entry.tool ? entry.tool : "Other";
51
- summary.byTool[tool] = (summary.byTool[tool] ?? 0) + 1;
52
- sessions.add(entry.sessionId || `${entry.project ?? "unknown"}:no-session`);
41
+ for (const line of raw.split("\n")) {
42
+ if (!line.trim()) continue;
43
+ let entry;
44
+ try {
45
+ entry = JSON.parse(line);
46
+ } catch {
47
+ continue;
48
+ }
49
+ if (typeof entry.ts !== "number" || entry.ts < sinceMs) continue;
50
+ summary.count += 1;
51
+ const tool = typeof entry.tool === "string" && entry.tool ? entry.tool : "Other";
52
+ summary.byTool[tool] = (summary.byTool[tool] ?? 0) + 1;
53
+ sessions.add(entry.sessionId || `${entry.project ?? "unknown"}:no-session`);
54
+ }
53
55
  }
54
56
  summary.sessions = sessions.size;
55
57
  return summary;
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-GPLKEAFG.js";
4
- import "../chunk-WLF3NCHS.js";
3
+ } from "../chunk-CY5YP34L.js";
5
4
  import "../chunk-KQYIHZ5E.js";
5
+ import "../chunk-R5AJNXZS.js";
6
6
  import {
7
7
  askUser,
8
8
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",