@pushary/agent-hooks 0.95.8 → 0.98.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 (82) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/bin/pushary-bell-hook.js +19 -21
  3. package/dist/bin/pushary-bell.js +6 -8
  4. package/dist/bin/pushary-claude.js +21 -23
  5. package/dist/bin/pushary-clean.js +19 -22
  6. package/dist/bin/pushary-codex-bridge.js +13 -14
  7. package/dist/bin/pushary-codex-hook.js +452 -104
  8. package/dist/bin/pushary-codex.js +8 -8
  9. package/dist/bin/pushary-connect.js +6 -6
  10. package/dist/bin/pushary-cowork.js +3 -3
  11. package/dist/bin/pushary-daemon.js +25 -27
  12. package/dist/bin/pushary-disconnect.js +6 -8
  13. package/dist/bin/pushary-doctor.js +28 -23
  14. package/dist/bin/pushary-elicitation-hook.js +19 -21
  15. package/dist/bin/pushary-gemini-bridge.js +11 -12
  16. package/dist/bin/pushary-gemini-hook.js +22 -24
  17. package/dist/bin/pushary-hook.js +24 -26
  18. package/dist/bin/pushary-login.js +7 -8
  19. package/dist/bin/pushary-logout.js +4 -4
  20. package/dist/bin/pushary-mode.js +3 -3
  21. package/dist/bin/pushary-notification-hook.js +21 -23
  22. package/dist/bin/pushary-opencode-hook.js +22 -24
  23. package/dist/bin/pushary-permission-denied-hook.js +28 -27
  24. package/dist/bin/pushary-permission-hook.js +24 -26
  25. package/dist/bin/pushary-post-hook.js +21 -23
  26. package/dist/bin/pushary-prompt-hook.js +21 -23
  27. package/dist/bin/pushary-session-end-hook.js +21 -23
  28. package/dist/bin/pushary-session-start-hook.js +24 -26
  29. package/dist/bin/pushary-setup.js +45 -40
  30. package/dist/bin/pushary-stats.js +2 -2
  31. package/dist/bin/pushary-status.js +10 -11
  32. package/dist/bin/pushary-stop-hook.js +21 -23
  33. package/dist/bin/pushary-stopfailure-hook.js +21 -23
  34. package/dist/bin/pushary-transcript-register.js +17 -19
  35. package/dist/bin/pushary-transcripts.js +5 -5
  36. package/dist/bin/pushary-upgrade.js +17 -20
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-GOPIMNVC.js → chunk-2GLOHBYB.js} +1 -1
  39. package/dist/{chunk-BQL7UDIB.js → chunk-3RVCQDC4.js} +1 -1
  40. package/dist/chunk-53QCTSWY.js +202 -0
  41. package/dist/{chunk-3YFJGKJQ.js → chunk-5B5QV3KC.js} +8 -8
  42. package/dist/{chunk-FRXZMCVA.js → chunk-5KLAETSU.js} +1 -1
  43. package/dist/{chunk-SDNTRS76.js → chunk-6C6MS57P.js} +1 -1
  44. package/dist/{chunk-A22VQ5ZL.js → chunk-6CYB77VD.js} +111 -55
  45. package/dist/{chunk-QD73WATQ.js → chunk-6LRGZJFB.js} +21 -21
  46. package/dist/{chunk-Z4JVEL7R.js → chunk-A76GNRIK.js} +191 -2
  47. package/dist/{chunk-RE25RJ5A.js → chunk-B35OCFXH.js} +4 -4
  48. package/dist/{chunk-UKMLSQXA.js → chunk-CP34FI4B.js} +2 -2
  49. package/dist/{chunk-I7LKI4WO.js → chunk-DOD3A6EU.js} +6 -8
  50. package/dist/{chunk-N6FAXQDD.js → chunk-FAJ4R7IB.js} +1 -1
  51. package/dist/{chunk-U5RIXR7I.js → chunk-HB2KB7QL.js} +1 -1
  52. package/dist/{chunk-QV266EGS.js → chunk-HFQ7WSHZ.js} +11 -14
  53. package/dist/{chunk-QJPYJVL3.js → chunk-IRLTW6KF.js} +1 -1
  54. package/dist/{chunk-Y6SCMXR6.js → chunk-J44RPHUZ.js} +2 -2
  55. package/dist/{chunk-R6LBGKWQ.js → chunk-JQU6PMH4.js} +17 -15
  56. package/dist/{chunk-4DUEIUUO.js → chunk-K6CH5NVW.js} +1 -1
  57. package/dist/{chunk-XZWUOCJZ.js → chunk-LBNAGGLV.js} +1 -1
  58. package/dist/{chunk-XS33QBJA.js → chunk-LGXUGYUH.js} +50 -1
  59. package/dist/{chunk-XO3JR3OP.js → chunk-LX4NNYII.js} +2 -2
  60. package/dist/{chunk-XEVOPOKT.js → chunk-M4TVEMLH.js} +1 -1
  61. package/dist/{chunk-TY6IJY6I.js → chunk-NIXVMMJM.js} +1 -1
  62. package/dist/{chunk-VERXHIQF.js → chunk-NQGYPTJQ.js} +1 -1
  63. package/dist/{chunk-EMGGUO2Q.js → chunk-OBYC6SN4.js} +1 -1
  64. package/dist/{chunk-XUTSZYYJ.js → chunk-OPR2BPD4.js} +1 -4
  65. package/dist/{chunk-MC5WUOJJ.js → chunk-OUAE3GKO.js} +75 -1
  66. package/dist/{chunk-ATKGKAP6.js → chunk-QXIF6R4P.js} +16 -13
  67. package/dist/chunk-SI7GYBQP.js +15 -0
  68. package/dist/{chunk-VMQU7YEJ.js → chunk-SJLGOVC6.js} +1 -1
  69. package/dist/{chunk-54W7ELDQ.js → chunk-TZHDKRMG.js} +2 -2
  70. package/dist/{chunk-F4TMY6XI.js → chunk-V47GITZV.js} +3 -3
  71. package/dist/{chunk-L7NOG547.js → chunk-VZD2GEKB.js} +1 -1
  72. package/dist/{chunk-R3OZ5ISU.js → chunk-W2MG2P46.js} +4 -4
  73. package/dist/{chunk-E47WV7BJ.js → chunk-WU6LSXJ6.js} +2 -2
  74. package/dist/{chunk-NOVA7KZ2.js → chunk-XL3RKJYQ.js} +308 -35
  75. package/dist/{chunk-5GKNAQTG.js → chunk-XOOWC62T.js} +1 -1
  76. package/dist/{chunk-SKPXQRKN.js → chunk-XPPXOKIG.js} +1 -1
  77. package/dist/{chunk-HUGIKB5O.js → chunk-YJDRHQLG.js} +6 -6
  78. package/dist/src/index.js +11 -11
  79. package/package.json +1 -1
  80. package/dist/chunk-3AJIK5AO.js +0 -57
  81. package/dist/chunk-L4ZCFMWW.js +0 -177
  82. package/dist/chunk-VDI645ZX.js +0 -18
@@ -1,7 +1,222 @@
1
1
  import {
2
2
  HOOK_BUDGETS,
3
- HOOK_LOCATOR_BINARY
4
- } from "./chunk-MC5WUOJJ.js";
3
+ HOOK_LOCATOR_BINARY,
4
+ withHookHoldBudget
5
+ } from "./chunk-OUAE3GKO.js";
6
+
7
+ // src/npm.ts
8
+ import { execFileSync, execSync } from "child_process";
9
+ import { existsSync, readFileSync, realpathSync } from "fs";
10
+ import { dirname, isAbsolute, join, relative, resolve } from "path";
11
+ var cleanNpmEnv = () => {
12
+ const env = {};
13
+ for (const [key, value] of Object.entries(process.env)) {
14
+ if (key.toLowerCase().startsWith("npm_config_workspace")) continue;
15
+ env[key] = value;
16
+ }
17
+ return env;
18
+ };
19
+ var npmErrorMessage = (err) => {
20
+ const e = err;
21
+ const text = [e?.stderr, e?.stdout].map((part) => part ? part.toString() : "").join("\n");
22
+ const line = text.split("\n").map((l) => l.replace(/^npm error\s*/i, "").trim()).find((l) => l && !l.startsWith("A complete log") && !/^code\s/i.test(l));
23
+ return line || e?.message || String(err);
24
+ };
25
+ var execNpm = (args, options = {}) => {
26
+ return execSync(`npm ${args}`, {
27
+ timeout: 12e4,
28
+ stdio: "pipe",
29
+ ...options,
30
+ env: { ...cleanNpmEnv(), ...options.env ?? {} }
31
+ });
32
+ };
33
+ var resolveGlobalBinary = (name) => {
34
+ try {
35
+ const prefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
36
+ const candidates = process.platform === "win32" ? [join(prefix, `${name}.cmd`), join(prefix, name)] : [join(prefix, "bin", name)];
37
+ return candidates.find(existsSync);
38
+ } catch {
39
+ return void 0;
40
+ }
41
+ };
42
+ var resolvePathBinary = (name, platform = process.platform) => {
43
+ try {
44
+ return execFileSync(platform === "win32" ? "where" : "which", [name], {
45
+ encoding: "utf8",
46
+ stdio: "pipe",
47
+ timeout: 5e3
48
+ }).split(/\r?\n/, 1)[0]?.trim() || void 0;
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ };
53
+ var resolveBinary = (name) => resolveGlobalBinary(name) ?? resolvePathBinary(name);
54
+ var resolvePackageVersionFromBinary = (binary, packageName) => {
55
+ try {
56
+ let dir = dirname(realpathSync(binary));
57
+ const packagePath = packageName.split("/");
58
+ for (let depth = 0; depth < 8; depth++) {
59
+ for (const manifest of [
60
+ join(dir, "package.json"),
61
+ join(dir, "node_modules", ...packagePath, "package.json")
62
+ ]) {
63
+ try {
64
+ const value = JSON.parse(readFileSync(manifest, "utf8"));
65
+ if (value.name === packageName && typeof value.version === "string") return value.version;
66
+ } catch {
67
+ }
68
+ }
69
+ const parent = dirname(dir);
70
+ if (parent === dir) break;
71
+ dir = parent;
72
+ }
73
+ } catch {
74
+ }
75
+ return void 0;
76
+ };
77
+ var resolveGlobalPackageVersion = (name) => {
78
+ try {
79
+ const result = JSON.parse(execNpm("list -g --depth=0 --json --no-workspaces", { timeout: 5e3 }).toString());
80
+ const value = result.dependencies?.[name]?.version;
81
+ return typeof value === "string" ? value : void 0;
82
+ } catch {
83
+ return void 0;
84
+ }
85
+ };
86
+ var localImportsPresent = (entry, packageDir, seen = /* @__PURE__ */ new Set()) => {
87
+ if (seen.has(entry)) return true;
88
+ seen.add(entry);
89
+ try {
90
+ const source = readFileSync(entry, "utf8");
91
+ for (const match of source.matchAll(/\b(?:import|export)\s+(?:[^"'()]*?\s+from\s+)?["'](\.{1,2}\/[^"']+)["']|\bimport\s*\(\s*["'](\.{1,2}\/[^"']+)["']/g)) {
92
+ const dependency = resolve(dirname(entry), match[1] ?? match[2]);
93
+ const localPath = relative(packageDir, dependency);
94
+ if (localPath.startsWith("..") || isAbsolute(localPath) || !existsSync(dependency)) return false;
95
+ if (/\.[cm]?js$/.test(dependency) && !localImportsPresent(dependency, packageDir, seen)) return false;
96
+ }
97
+ return true;
98
+ } catch {
99
+ return false;
100
+ }
101
+ };
102
+ var packageBinsPresent = (packageDir, prefix, platform = process.platform, requiredFiles = []) => {
103
+ try {
104
+ const manifest = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
105
+ if (!manifest.bin || typeof manifest.bin !== "object" || Array.isArray(manifest.bin)) return false;
106
+ const bins = Object.entries(manifest.bin);
107
+ if (bins.length === 0) return false;
108
+ if (!requiredFiles.every((path) => existsSync(join(packageDir, path)))) return false;
109
+ const seen = /* @__PURE__ */ new Set();
110
+ return bins.every(([name, target]) => {
111
+ if (typeof target !== "string") return false;
112
+ const shims = platform === "win32" ? [join(prefix, `${name}.cmd`), join(prefix, name)] : [join(prefix, "bin", name)];
113
+ const entry = join(packageDir, target);
114
+ return existsSync(entry) && shims.some(existsSync) && localImportsPresent(entry, packageDir, seen);
115
+ });
116
+ } catch {
117
+ return false;
118
+ }
119
+ };
120
+ var AGENT_HOOKS_RUNTIME_FILES = [
121
+ "data/SKILL.md",
122
+ "data/cursor-plugin/.cursor-plugin/plugin.json",
123
+ "data/cursor-plugin/hooks/hooks.json",
124
+ "data/cursor-plugin/mcp.json",
125
+ "data/cursor-plugin/scripts/pushary-gate.mjs",
126
+ "data/vscode-plugin/.claude-plugin/plugin.json",
127
+ "data/vscode-plugin/hooks/hooks.json",
128
+ "data/vscode-plugin/.mcp.json",
129
+ "data/vscode-plugin/scripts/pushary-gate.mjs"
130
+ ];
131
+ var globalPackageBinsPresent = (name) => {
132
+ try {
133
+ const root = execNpm("root -g --no-workspaces", { timeout: 5e3 }).toString().trim();
134
+ const prefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
135
+ return packageBinsPresent(join(root, ...name.split("/")), prefix, process.platform, AGENT_HOOKS_RUNTIME_FILES);
136
+ } catch {
137
+ return false;
138
+ }
139
+ };
140
+ var resolveGlobalBinDir = (probe) => {
141
+ if (process.platform === "win32") return void 0;
142
+ const resolved = resolveGlobalBinary(probe);
143
+ return resolved ? resolved.slice(0, resolved.length - probe.length - 1) : void 0;
144
+ };
145
+ var quoteIfNeeded = (path) => /\s/.test(path) ? `"${path}"` : path;
146
+ var needsGlobalInstall = (requested, installed, binaryPresent) => requested !== installed || !binaryPresent;
147
+ var installState;
148
+ var installGlobally = (options) => {
149
+ if (installState) {
150
+ return installState.then((result) => result === "installed" ? "cached" : result);
151
+ }
152
+ const started = (async () => {
153
+ const pinned = process.env.PUSHARY_PIN_GLOBAL_VERSION !== "0";
154
+ if (pinned && !needsGlobalInstall(
155
+ options.version,
156
+ resolveGlobalPackageVersion("@pushary/agent-hooks"),
157
+ globalPackageBinsPresent("@pushary/agent-hooks")
158
+ )) {
159
+ return "skipped";
160
+ }
161
+ const spec = pinned && options.version && options.version !== "0.0.0" ? `@pushary/agent-hooks@${options.version}` : "@pushary/agent-hooks@latest";
162
+ execNpm(`install -g --no-workspaces ${spec}`, { timeout: options.timeoutMs ?? 12e4 });
163
+ return "installed";
164
+ })();
165
+ installState = started;
166
+ started.catch(() => {
167
+ if (installState === started) installState = void 0;
168
+ });
169
+ return started;
170
+ };
171
+
172
+ // src/hook-command.ts
173
+ var HOOK_SHELL = "/bin/sh -c";
174
+ var holdBudgetOf = (hook) => hook.holds ? hook.timeout : void 0;
175
+ var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
176
+ var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
177
+ var CURSOR_NO_OPINION = { permission: "deny", user_message: "Command denied via Pushary.", agent_message: "Pushary hook is unavailable. Repair the integration before retrying." };
178
+ var shellDoubleQuoted = (text) => `"${text.replace(/(["\\$`])/g, "\\$1")}"`;
179
+ var CURSOR_NATIVE_PROMPT = { permission: "ask" };
180
+ var CURSOR_NATIVE_PROMPT_EVENTS = ["beforeShellExecution", "beforeMCPExecution"];
181
+ var hookEventIsOneOf = (events) => events.flatMap((event) => [`*\\"hook_event_name\\":\\"${event}\\"*`, `*\\"hook_event_name\\":\\ \\"${event}\\"*`]).join("|");
182
+ var CURSOR_MISS = `{ E=$(cat); case "$E" in ${hookEventIsOneOf(CURSOR_NATIVE_PROMPT_EVENTS)}) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NATIVE_PROMPT))} ;; *) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NO_OPINION))} ;; esac; exit 0; }`;
183
+ var HOOK_GUARD_SCRIPTS = {
184
+ native_binary: `${LOCATE}; exec "$B" "$@"`,
185
+ node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B" "$@"`,
186
+ node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0" "$@"`,
187
+ cursor_gate_script: `[ -f "$0" ] || ${CURSOR_MISS}; command -v node >/dev/null 2>&1 || ${CURSOR_MISS}; exec node "$0" "$@"`
188
+ };
189
+ var BINARY_GUARDS = {
190
+ native: "native_binary",
191
+ node: "node_binary"
192
+ };
193
+ var guardKindOf = (target) => {
194
+ switch (target.kind) {
195
+ case "node":
196
+ return "node_script";
197
+ case "cursor_gate":
198
+ return "cursor_gate_script";
199
+ case "binary":
200
+ return BINARY_GUARDS[target.runtime];
201
+ }
202
+ };
203
+ var targetPath = (target) => target.kind === "binary" ? target.path : target.script;
204
+ var runsThroughNode = (target) => target.kind !== "binary";
205
+ var guardHookCommand = (target, platform = process.platform) => {
206
+ const path = quoteIfNeeded(targetPath(target));
207
+ if (platform === "win32") return runsThroughNode(target) ? `node ${path}` : path;
208
+ return `${HOOK_SHELL} '${HOOK_GUARD_SCRIPTS[guardKindOf(target)]}' ${path}`;
209
+ };
210
+ var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
211
+ var guardCursorGate = (script, platform) => guardHookCommand({ kind: "cursor_gate", script }, platform);
212
+ var guardedArgv = (target, args = [], platform = process.platform) => {
213
+ const path = targetPath(target);
214
+ if (platform === "win32") {
215
+ return runsThroughNode(target) ? ["node", path, ...args] : [path, ...args];
216
+ }
217
+ const [shell, ...shellArgs] = HOOK_SHELL.split(/\s+/);
218
+ return [shell, ...shellArgs, HOOK_GUARD_SCRIPTS[guardKindOf(target)], path, ...args];
219
+ };
5
220
 
6
221
  // src/removal-scope.ts
7
222
  var namesAppLocator = (command) => command.includes(HOOK_LOCATOR_BINARY);
@@ -10,7 +225,24 @@ var isProtectedByScope = (command, scope) => scope?.keepAppWiring === true && na
10
225
  // src/codex-config.ts
11
226
  import { createHash } from "crypto";
12
227
  import { homedir } from "os";
13
- import { join } from "path";
228
+ import { join as join2 } from "path";
229
+
230
+ // src/hook-entries.ts
231
+ var registerHookEntry = (existing, isOurs, entry) => {
232
+ const rewritten = [];
233
+ let placed = false;
234
+ for (const item of existing) {
235
+ if (!isOurs(item)) {
236
+ rewritten.push(item);
237
+ continue;
238
+ }
239
+ if (placed) continue;
240
+ rewritten.push(entry);
241
+ placed = true;
242
+ }
243
+ if (!placed) rewritten.push(entry);
244
+ return rewritten;
245
+ };
14
246
 
15
247
  // src/canonical-json.ts
16
248
  var canonicalJson = (value) => {
@@ -23,17 +255,17 @@ var canonicalJson = (value) => {
23
255
  // src/codex-config.ts
24
256
  var CODEX_HOOK_BINARY = "pushary-codex-hook";
25
257
  var codexHome = () => codexHomeFrom(homedir());
26
- var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join(home, ".codex");
27
- var codexConfigToml = () => join(codexHome(), "config.toml");
28
- var codexHooksJson = () => join(codexHome(), "hooks.json");
29
- var codexAgentsMd = () => join(codexHome(), "AGENTS.md");
30
- var codexSkillDir = () => join(codexHome(), "skills", "pushary");
258
+ var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join2(home, ".codex");
259
+ var codexConfigToml = () => join2(codexHome(), "config.toml");
260
+ var codexHooksJson = () => join2(codexHome(), "hooks.json");
261
+ var codexAgentsMd = () => join2(codexHome(), "AGENTS.md");
262
+ var codexSkillDir = () => join2(codexHome(), "skills", "pushary");
31
263
  var CODEX_HOOK_EVENTS = [
32
264
  { event: "PermissionRequest", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
33
265
  { event: "PreToolUse", matcher: "Bash|apply_patch|mcp__.*", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
34
266
  { event: "PostToolUse", matcher: "Bash|apply_patch|mcp__.*", delivery: "telemetry", timeout: 10 },
35
267
  { event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
36
- { event: "Stop", delivery: "telemetry", timeout: 10 },
268
+ { event: "Stop", delivery: "rescue", timeout: HOOK_BUDGETS.codex.budgetSeconds, holds: true },
37
269
  // `clear` alongside startup and resume, matching what we already do on Claude
38
270
  // Code. Codex names all three in its own SessionStart sources, and without it a
39
271
  // session the person started by clearing is one we never see begin.
@@ -110,17 +342,15 @@ var addCodexHooks = (config, command) => {
110
342
  const hooks = ensureRecord(config, "hooks");
111
343
  for (const definition of CODEX_HOOK_EVENTS) {
112
344
  const existing = Array.isArray(hooks[definition.event]) ? hooks[definition.event] : [];
113
- const entries = existing.filter((entry) => !isPusharyCodexHook(entry));
114
- entries.push({
345
+ hooks[definition.event] = registerHookEntry(existing, isPusharyCodexHook, {
115
346
  ...definition.matcher ? { matcher: definition.matcher } : {},
116
347
  hooks: [{
117
348
  type: "command",
118
- command,
349
+ command: withHookHoldBudget(command, holdBudgetOf(definition)),
119
350
  timeout: definition.timeout,
120
351
  ...definition.statusMessage ? { statusMessage: definition.statusMessage } : {}
121
352
  }]
122
353
  });
123
- hooks[definition.event] = entries;
124
354
  }
125
355
  };
126
356
  var removeCodexHooks = (config, scope) => {
@@ -159,7 +389,7 @@ var missingCodexHookEvents = (config) => {
159
389
  }).map((definition) => definition.event);
160
390
  };
161
391
  var codexHookTrustHash = (definition, command) => {
162
- const hook = { command, timeout: definition.timeout, type: "command" };
392
+ const hook = { command: withHookHoldBudget(command, holdBudgetOf(definition)), timeout: definition.timeout, type: "command" };
163
393
  if (definition.statusMessage) hook.statusMessage = definition.statusMessage;
164
394
  const entry = { hooks: [hook] };
165
395
  if (definition.matcher) entry.matcher = definition.matcher;
@@ -206,6 +436,33 @@ var codexHookPositions = (hooksConfig) => {
206
436
  return positions;
207
437
  };
208
438
  var codexHookStateKey = (hooksJsonPath, event, position) => `${hooksJsonPath}:${CODEX_EVENT_KEY[event]}:${position[0]}:${position[1]}`;
439
+ var codexVacatedIndexes = (hooksConfig) => {
440
+ const hooks = asRecord(hooksConfig?.hooks);
441
+ const vacated = {};
442
+ for (const definition of CODEX_HOOK_EVENTS) {
443
+ const entries = hooks?.[definition.event];
444
+ if (!Array.isArray(entries)) continue;
445
+ const ours = entries.flatMap((entry, index) => isPusharyCodexHook(entry) ? [index] : []);
446
+ if (ours.length > 1) vacated[definition.event] = ours[1];
447
+ }
448
+ return vacated;
449
+ };
450
+ var dropVacatedCodexHookTrust = (config, hooksJsonPath, vacated) => {
451
+ const state = asRecord(asRecord(config.hooks)?.state);
452
+ if (!state) return false;
453
+ let changed = false;
454
+ for (const [event, from] of Object.entries(vacated)) {
455
+ const prefix = `${hooksJsonPath}:${CODEX_EVENT_KEY[event] ?? codexEventKeyOf(event)}:`;
456
+ for (const key of Object.keys(state)) {
457
+ if (!key.startsWith(prefix)) continue;
458
+ const entryIndex = Number(key.slice(prefix.length).split(":")[0]);
459
+ if (!Number.isInteger(entryIndex) || entryIndex < from) continue;
460
+ delete state[key];
461
+ changed = true;
462
+ }
463
+ }
464
+ return changed;
465
+ };
209
466
  var captureCodexHookTrust = (config, hooksJsonPath, positions) => {
210
467
  const state = asRecord(asRecord(config.hooks)?.state);
211
468
  if (!state) return {};
@@ -308,29 +565,43 @@ var staleTrustKeys = (state, hooksJsonPath, event, currentKey, ourHash) => {
308
565
 
309
566
  // src/setup/paths.ts
310
567
  import { homedir as homedir2 } from "os";
311
- import { join as join2 } from "path";
312
- var claudeSettings = () => join2(homedir2(), ".claude", "settings.json");
313
- var claudeJson = () => join2(homedir2(), ".claude.json");
314
- var claudeSkillDir = () => join2(homedir2(), ".claude", "skills", "pushary");
315
- var cursorPluginDir = () => join2(homedir2(), ".cursor", "plugins", "local", "pushary");
316
- var cursorUserHooks = () => join2(homedir2(), ".cursor", "hooks.json");
317
- var cursorUserMcp = () => join2(homedir2(), ".cursor", "mcp.json");
318
- var vscodePluginDir = () => join2(homedir2(), ".pushary", "plugins", "vscode");
319
- var vscodePluginMcp = () => join2(vscodePluginDir(), ".mcp.json");
320
- var claudeSettingsLocal = () => join2(homedir2(), ".claude", "settings.local.json");
321
- var pusharyDir = () => join2(homedir2(), ".pushary");
322
- var claudeMcpReceipt = () => join2(pusharyDir(), "setup", "claude-mcp.json");
323
- var bridgeAgentKey = () => join2(pusharyDir(), "bridge-auth", "agent-key.json");
324
- var claudeRegistrationReceipt = () => join2(pusharyDir(), "setup", "claude-registration.json");
325
- var hermesConfigReceipt = () => join2(pusharyDir(), "setup", "hermes-config.json");
326
- var hookTakeoverReceipt = (agent) => join2(pusharyDir(), "setup", `hook-takeover-${agent}.json`);
327
- var codexSkillDir2 = () => join2(codexHome(), "skills", "pushary");
328
- var codexAgentsMd2 = () => join2(codexHome(), "AGENTS.md");
329
- var geminiSettings = () => join2(homedir2(), ".gemini", "settings.json");
330
- var geminiMd = () => join2(homedir2(), ".gemini", "GEMINI.md");
331
- var pusharyConfigFile = () => join2(homedir2(), ".pushary", "config.json");
568
+ import { join as join3 } from "path";
569
+ var claudeSettings = () => join3(homedir2(), ".claude", "settings.json");
570
+ var claudeJson = () => join3(homedir2(), ".claude.json");
571
+ var claudeSkillDir = () => join3(homedir2(), ".claude", "skills", "pushary");
572
+ var cursorPluginDir = () => join3(homedir2(), ".cursor", "plugins", "local", "pushary");
573
+ var cursorUserHooks = () => join3(homedir2(), ".cursor", "hooks.json");
574
+ var cursorUserMcp = () => join3(homedir2(), ".cursor", "mcp.json");
575
+ var vscodePluginDir = () => join3(homedir2(), ".pushary", "plugins", "vscode");
576
+ var vscodePluginMcp = () => join3(vscodePluginDir(), ".mcp.json");
577
+ var claudeSettingsLocal = () => join3(homedir2(), ".claude", "settings.local.json");
578
+ var pusharyDir = () => join3(homedir2(), ".pushary");
579
+ var claudeMcpReceipt = () => join3(pusharyDir(), "setup", "claude-mcp.json");
580
+ var bridgeAgentKey = () => join3(pusharyDir(), "bridge-auth", "agent-key.json");
581
+ var claudeRegistrationReceipt = () => join3(pusharyDir(), "setup", "claude-registration.json");
582
+ var hermesConfigReceipt = () => join3(pusharyDir(), "setup", "hermes-config.json");
583
+ var hookTakeoverReceipt = (agent) => join3(pusharyDir(), "setup", `hook-takeover-${agent}.json`);
584
+ var codexSkillDir2 = () => join3(codexHome(), "skills", "pushary");
585
+ var codexAgentsMd2 = () => join3(codexHome(), "AGENTS.md");
586
+ var geminiSettings = () => join3(homedir2(), ".gemini", "settings.json");
587
+ var geminiMd = () => join3(homedir2(), ".gemini", "GEMINI.md");
588
+ var pusharyConfigFile = () => join3(homedir2(), ".pushary", "config.json");
332
589
 
333
590
  export {
591
+ npmErrorMessage,
592
+ execNpm,
593
+ resolveGlobalBinary,
594
+ resolvePathBinary,
595
+ resolveBinary,
596
+ resolvePackageVersionFromBinary,
597
+ resolveGlobalPackageVersion,
598
+ resolveGlobalBinDir,
599
+ installGlobally,
600
+ holdBudgetOf,
601
+ guardBinary,
602
+ guardCursorGate,
603
+ guardedArgv,
604
+ registerHookEntry,
334
605
  isProtectedByScope,
335
606
  canonicalJson,
336
607
  CODEX_HOOK_BINARY,
@@ -349,6 +620,8 @@ export {
349
620
  missingCodexHookEvents,
350
621
  codexTrustSchemeAgrees,
351
622
  codexHookPositions,
623
+ codexVacatedIndexes,
624
+ dropVacatedCodexHookTrust,
352
625
  captureCodexHookTrust,
353
626
  restoreCodexHookTrust,
354
627
  codexTrustGaps,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-MC5WUOJJ.js";
3
+ } from "./chunk-OUAE3GKO.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  redactSecrets
3
- } from "./chunk-MC5WUOJJ.js";
3
+ } from "./chunk-OUAE3GKO.js";
4
4
 
5
5
  // src/crypto.ts
6
6
  import nacl from "tweetnacl";
@@ -18,26 +18,26 @@ import {
18
18
  summarizeStop,
19
19
  taskTitleFrom,
20
20
  toolResultErrorText
21
- } from "./chunk-Z4JVEL7R.js";
21
+ } from "./chunk-A76GNRIK.js";
22
22
  import {
23
23
  NOTIFICATION_PUSH_TYPES,
24
24
  POSTTOOLUSE_MIRROR_ONLY_TOOLS
25
- } from "./chunk-XUTSZYYJ.js";
25
+ } from "./chunk-OPR2BPD4.js";
26
26
  import {
27
27
  cancelQuestion,
28
28
  isPolicyConfig,
29
29
  sendNotification,
30
30
  waitForAnswer
31
- } from "./chunk-XS33QBJA.js";
31
+ } from "./chunk-LGXUGYUH.js";
32
32
  import {
33
33
  getMachineId
34
- } from "./chunk-E47WV7BJ.js";
34
+ } from "./chunk-WU6LSXJ6.js";
35
35
  import {
36
36
  withRetry
37
37
  } from "./chunk-EYRGBYZG.js";
38
38
  import {
39
39
  reportedInstallSource
40
- } from "./chunk-L7NOG547.js";
40
+ } from "./chunk-VZD2GEKB.js";
41
41
  import {
42
42
  getApiKey,
43
43
  getBaseUrl
@@ -54,7 +54,7 @@ import {
54
54
  pendingCommandEvents,
55
55
  resolveAutoResolveOrigin,
56
56
  resolvePolicy
57
- } from "./chunk-MC5WUOJJ.js";
57
+ } from "./chunk-OUAE3GKO.js";
58
58
 
59
59
  // src/policy.ts
60
60
  import { createHash } from "crypto";
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-A22VQ5ZL.js";
4
- import "../chunk-VDI645ZX.js";
5
- import "../chunk-5GKNAQTG.js";
3
+ } from "../chunk-6CYB77VD.js";
4
+ import "../chunk-53QCTSWY.js";
5
+ import "../chunk-XOOWC62T.js";
6
6
  import "../chunk-YHG74UFF.js";
7
7
  import {
8
8
  fetchAccountPublicKey
@@ -15,28 +15,28 @@ import {
15
15
  handlePostToolUse,
16
16
  handleStop,
17
17
  reportEvent
18
- } from "../chunk-HUGIKB5O.js";
19
- import "../chunk-Z4JVEL7R.js";
20
- import "../chunk-XUTSZYYJ.js";
18
+ } from "../chunk-YJDRHQLG.js";
19
+ import "../chunk-A76GNRIK.js";
20
+ import "../chunk-OPR2BPD4.js";
21
21
  import {
22
22
  askUser,
23
23
  cancelQuestion,
24
24
  waitForAnswer
25
- } from "../chunk-XS33QBJA.js";
26
- import "../chunk-E47WV7BJ.js";
27
- import "../chunk-NOVA7KZ2.js";
25
+ } from "../chunk-LGXUGYUH.js";
26
+ import "../chunk-WU6LSXJ6.js";
27
+ import "../chunk-XL3RKJYQ.js";
28
28
  import "../chunk-EYRGBYZG.js";
29
29
  import "../chunk-SAF6HGAA.js";
30
30
  import "../chunk-7QLSKOSU.js";
31
31
  import "../chunk-KZERVKTD.js";
32
- import "../chunk-L7NOG547.js";
32
+ import "../chunk-VZD2GEKB.js";
33
33
  import {
34
34
  getApiKey,
35
35
  getBaseUrl
36
36
  } from "../chunk-2UMNXADU.js";
37
37
  import {
38
38
  resolvePolicy
39
- } from "../chunk-MC5WUOJJ.js";
39
+ } from "../chunk-OUAE3GKO.js";
40
40
  export {
41
41
  askUser,
42
42
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.95.8",
3
+ "version": "0.98.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -1,57 +0,0 @@
1
- import {
2
- quoteIfNeeded
3
- } from "./chunk-L4ZCFMWW.js";
4
-
5
- // src/hook-command.ts
6
- var HOOK_SHELL = "/bin/sh -c";
7
- var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
8
- var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
9
- var CURSOR_NO_OPINION = { permission: "deny", user_message: "Command denied via Pushary.", agent_message: "Pushary hook is unavailable. Repair the integration before retrying." };
10
- var shellDoubleQuoted = (text) => `"${text.replace(/(["\\$`])/g, "\\$1")}"`;
11
- var CURSOR_NATIVE_PROMPT = { permission: "ask" };
12
- var CURSOR_NATIVE_PROMPT_EVENTS = ["beforeShellExecution", "beforeMCPExecution"];
13
- var hookEventIsOneOf = (events) => events.flatMap((event) => [`*\\"hook_event_name\\":\\"${event}\\"*`, `*\\"hook_event_name\\":\\ \\"${event}\\"*`]).join("|");
14
- var CURSOR_MISS = `{ E=$(cat); case "$E" in ${hookEventIsOneOf(CURSOR_NATIVE_PROMPT_EVENTS)}) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NATIVE_PROMPT))} ;; *) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NO_OPINION))} ;; esac; exit 0; }`;
15
- var HOOK_GUARD_SCRIPTS = {
16
- native_binary: `${LOCATE}; exec "$B" "$@"`,
17
- node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B" "$@"`,
18
- node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0" "$@"`,
19
- cursor_gate_script: `[ -f "$0" ] || ${CURSOR_MISS}; command -v node >/dev/null 2>&1 || ${CURSOR_MISS}; exec node "$0" "$@"`
20
- };
21
- var BINARY_GUARDS = {
22
- native: "native_binary",
23
- node: "node_binary"
24
- };
25
- var guardKindOf = (target) => {
26
- switch (target.kind) {
27
- case "node":
28
- return "node_script";
29
- case "cursor_gate":
30
- return "cursor_gate_script";
31
- case "binary":
32
- return BINARY_GUARDS[target.runtime];
33
- }
34
- };
35
- var targetPath = (target) => target.kind === "binary" ? target.path : target.script;
36
- var runsThroughNode = (target) => target.kind !== "binary";
37
- var guardHookCommand = (target, platform = process.platform) => {
38
- const path = quoteIfNeeded(targetPath(target));
39
- if (platform === "win32") return runsThroughNode(target) ? `node ${path}` : path;
40
- return `${HOOK_SHELL} '${HOOK_GUARD_SCRIPTS[guardKindOf(target)]}' ${path}`;
41
- };
42
- var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
43
- var guardCursorGate = (script, platform) => guardHookCommand({ kind: "cursor_gate", script }, platform);
44
- var guardedArgv = (target, args = [], platform = process.platform) => {
45
- const path = targetPath(target);
46
- if (platform === "win32") {
47
- return runsThroughNode(target) ? ["node", path, ...args] : [path, ...args];
48
- }
49
- const [shell, ...shellArgs] = HOOK_SHELL.split(/\s+/);
50
- return [shell, ...shellArgs, HOOK_GUARD_SCRIPTS[guardKindOf(target)], path, ...args];
51
- };
52
-
53
- export {
54
- guardBinary,
55
- guardCursorGate,
56
- guardedArgv
57
- };