@pushary/agent-hooks 0.84.0 → 0.85.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 (56) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +24 -0
  3. package/dist/bin/pushary-bell-hook.js +5 -3
  4. package/dist/bin/pushary-bell.js +8 -7
  5. package/dist/bin/pushary-claude.js +29 -20
  6. package/dist/bin/pushary-clean.js +6 -5
  7. package/dist/bin/pushary-codex-bridge.js +8 -7
  8. package/dist/bin/pushary-codex-hook.js +11 -14
  9. package/dist/bin/pushary-codex.js +1 -1
  10. package/dist/bin/pushary-connect.js +5 -5
  11. package/dist/bin/pushary-daemon.js +36 -18
  12. package/dist/bin/pushary-disconnect.d.ts +1 -0
  13. package/dist/bin/pushary-disconnect.js +125 -0
  14. package/dist/bin/pushary-doctor.js +9 -9
  15. package/dist/bin/pushary-gemini-bridge.js +8 -7
  16. package/dist/bin/pushary-gemini-hook.js +12 -15
  17. package/dist/bin/pushary-hook.js +13 -20
  18. package/dist/bin/pushary-login.js +2 -2
  19. package/dist/bin/pushary-logout.js +3 -3
  20. package/dist/bin/pushary-mode.js +2 -2
  21. package/dist/bin/pushary-notification-hook.js +6 -9
  22. package/dist/bin/pushary-permission-denied-hook.js +11 -9
  23. package/dist/bin/pushary-permission-hook.js +11 -9
  24. package/dist/bin/pushary-post-hook.js +6 -9
  25. package/dist/bin/pushary-prompt-hook.js +6 -9
  26. package/dist/bin/pushary-session-end-hook.js +6 -6
  27. package/dist/bin/pushary-session-start-hook.js +6 -6
  28. package/dist/bin/pushary-setup.js +23 -24
  29. package/dist/bin/pushary-stats.js +2 -2
  30. package/dist/bin/pushary-status.js +2 -2
  31. package/dist/bin/pushary-stop-hook.js +6 -6
  32. package/dist/bin/pushary-stopfailure-hook.js +6 -6
  33. package/dist/bin/pushary-suggestions.js +2 -2
  34. package/dist/bin/pushary-upgrade.js +13 -8
  35. package/dist/bin/pushary-wait.js +2 -2
  36. package/dist/bin/pushary.js +2 -1
  37. package/dist/{chunk-HZNOAJGB.js → chunk-4QPOMJUB.js} +1 -1
  38. package/dist/chunk-64PXDATC.js +187 -0
  39. package/dist/chunk-6CUUA7HO.js +59 -0
  40. package/dist/{chunk-UXWEE3QI.js → chunk-ADISVXZL.js} +1 -1
  41. package/dist/chunk-AXRATIS6.js +70 -0
  42. package/dist/{chunk-YJCMI5GF.js → chunk-ERYKGH4J.js} +11 -4
  43. package/dist/{chunk-OPMSMF5R.js → chunk-EXMUM226.js} +21 -8
  44. package/dist/{chunk-NWYGW7HY.js → chunk-GCKAWHFP.js} +9 -2
  45. package/dist/{chunk-QNBA5OXQ.js → chunk-GFM3STWO.js} +3 -3
  46. package/dist/{chunk-MGJBC6DD.js → chunk-GOEXZ5QJ.js} +1 -0
  47. package/dist/{chunk-37AD5K7W.js → chunk-GXIFADGD.js} +1 -1
  48. package/dist/{chunk-SEAHGHCE.js → chunk-HVZIHTOO.js} +33 -0
  49. package/dist/chunk-PWTKKQQI.js +27 -0
  50. package/dist/{chunk-J4KFVOCQ.js → chunk-SQOFGPNX.js} +1 -1
  51. package/dist/{chunk-CCH775Y6.js → chunk-WEAQQSHQ.js} +1 -1
  52. package/dist/{chunk-EVJ6ULHN.js → chunk-YKW6JCWI.js} +68 -29
  53. package/dist/src/index.d.ts +1 -1
  54. package/dist/src/index.js +6 -4
  55. package/package.json +3 -2
  56. package/dist/chunk-UKSQWTBH.js +0 -208
@@ -5,7 +5,7 @@ import {
5
5
  generateSessionKey,
6
6
  uploadTranscriptRecords,
7
7
  wrapSessionKey
8
- } from "./chunk-NWYGW7HY.js";
8
+ } from "./chunk-GCKAWHFP.js";
9
9
 
10
10
  // src/live-session.ts
11
11
  var MAX_TRANSCRIPT_QUEUE = 100;
@@ -72,8 +72,10 @@ var LiveTranscriptRelay = class {
72
72
  sessionKey = null;
73
73
  wrappedKey = null;
74
74
  keyUploaded = false;
75
+ keyRejected = false;
75
76
  flushing = null;
76
77
  add(value) {
78
+ if (this.keyRejected) return;
77
79
  const raw = JSON.stringify(value);
78
80
  if (raw.length > MAX_TRANSCRIPT_TEXT) return;
79
81
  if (this.queue.length === MAX_TRANSCRIPT_QUEUE) this.queue.shift();
@@ -106,14 +108,19 @@ var LiveTranscriptRelay = class {
106
108
  this.queue.splice(0, count);
107
109
  return true;
108
110
  }
109
- const uploaded = await uploadTranscriptRecords(this.options.apiKey, {
111
+ const result = await uploadTranscriptRecords(this.options.apiKey, {
110
112
  sessionId: this.sessionId,
111
113
  machineId: this.options.machineId,
112
114
  dataEncryptionKey: this.keyUploaded ? void 0 : this.wrappedKey ?? void 0,
113
115
  records
114
116
  }, this.options.baseUrl);
115
- if (uploaded) {
116
- this.keyUploaded = true;
117
+ if (result.keyConflict) {
118
+ this.keyRejected = true;
119
+ this.queue.length = 0;
120
+ return false;
121
+ }
122
+ if (result.ok) {
123
+ this.keyUploaded ||= result.keyRegistered;
117
124
  this.queue.splice(0, count);
118
125
  return true;
119
126
  }
@@ -60,7 +60,7 @@ var CACHE_TTL_MS = 60 * 1e3;
60
60
  var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
61
61
  var policyRequestFor = (agentType) => ({
62
62
  agent: agentType,
63
- repoAware: agentType === "claude_code"
63
+ repoAware: ["claude_code", "codex", "gemini_cli"].includes(agentType)
64
64
  });
65
65
  var policyCacheFile = (apiKey, agent, repoAware = false) => {
66
66
  const parts = [apiKey, agent, repoAware ? "repoAware" : void 0].filter(Boolean);
@@ -196,9 +196,10 @@ var readLastGoodMode = (apiKey) => {
196
196
  return null;
197
197
  }
198
198
  };
199
- var writeLastGoodMode = (apiKey, state) => {
199
+ var writeLastGoodMode = (apiKey, state, sessionId) => {
200
200
  try {
201
- writeFileSync(modeStateCacheFile(apiKey), JSON.stringify({ state, at: Date.now() }), { encoding: "utf-8", mode: 384 });
201
+ const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
202
+ writeFileSync(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
202
203
  } catch {
203
204
  }
204
205
  };
@@ -220,7 +221,18 @@ var degradedFallback = (apiKey, authoritative = false) => {
220
221
  return unknownMode();
221
222
  };
222
223
  var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
223
- var fetchModeState = async (apiKey, sessionId) => {
224
+ var MODE_REUSE_MS = CACHE_TTL_MS;
225
+ var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
226
+ const last = readLastGoodMode(apiKey);
227
+ if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
228
+ if (Date.now() - last.at >= maxAgeMs) return null;
229
+ return { ...last.state, degraded: false };
230
+ };
231
+ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
232
+ if (reuseWithinMs > 0) {
233
+ const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
234
+ if (reusable) return reusable;
235
+ }
224
236
  try {
225
237
  const baseUrl = getBaseUrl();
226
238
  const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
@@ -244,7 +256,7 @@ var fetchModeState = async (apiKey, sessionId) => {
244
256
  scope: isScopeContract(data.scope) ? data.scope : null,
245
257
  degraded: false
246
258
  };
247
- writeLastGoodMode(apiKey, state);
259
+ writeLastGoodMode(apiKey, state, sessionId);
248
260
  return state;
249
261
  } catch {
250
262
  return degradedFallback(apiKey);
@@ -1053,7 +1065,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1053
1065
  const isError = isToolResultError(toolResult);
1054
1066
  const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
1055
1067
  const sessionKey = input.session_id || DEFAULT_SESSION;
1056
- const liveMode = await fetchModeState(getApiKey(), input.session_id);
1068
+ const liveMode = await fetchModeState(getApiKey(), input.session_id, MODE_REUSE_MS);
1057
1069
  const late = await reconcilePendingQuestions(sessionKey);
1058
1070
  let additionalContext;
1059
1071
  if (late.length > 0 && agent.type === CLAUDE_CODE_AGENT.type) {
@@ -1239,7 +1251,7 @@ var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
1239
1251
  } catch {
1240
1252
  return;
1241
1253
  }
1242
- const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
1254
+ const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
1243
1255
  if (mode?.kill || mode?.mode === "terminal_only") return;
1244
1256
  await sendNotification(apiKey, {
1245
1257
  title: `${agent.label} is waiting`,
@@ -1302,7 +1314,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1302
1314
  return;
1303
1315
  }
1304
1316
  const projectName = basename2(input.cwd ?? process.cwd());
1305
- const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
1317
+ const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
1306
1318
  if (mode?.kill || mode?.mode === "terminal_only") return;
1307
1319
  const reason = describeStopFailure(input.error);
1308
1320
  await sendNotification(apiKey, {
@@ -1318,6 +1330,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1318
1330
  };
1319
1331
 
1320
1332
  export {
1333
+ policyRequestFor,
1321
1334
  getPolicy,
1322
1335
  resolveAutoResolveOrigin,
1323
1336
  resolvePolicy,
@@ -101,9 +101,16 @@ var uploadTranscriptRecords = async (apiKey, params, baseUrl = getBaseUrl()) =>
101
101
  body: JSON.stringify(params),
102
102
  signal: AbortSignal.timeout(15e3)
103
103
  });
104
- return res.ok;
104
+ const body = await res.json().catch(() => null);
105
+ const keyRegistered = body?.keyRegistered === true;
106
+ const keyConflict = body?.keyConflict === true;
107
+ return {
108
+ ok: res.ok && !keyConflict && (params.dataEncryptionKey === void 0 || keyRegistered),
109
+ keyRegistered,
110
+ keyConflict
111
+ };
105
112
  } catch {
106
- return false;
113
+ return { ok: false, keyRegistered: false, keyConflict: false };
107
114
  }
108
115
  };
109
116
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PRETOOLUSE_HANDLED_TOOLS
3
- } from "./chunk-UKSQWTBH.js";
3
+ } from "./chunk-64PXDATC.js";
4
4
  import {
5
5
  isGatingMoment,
6
6
  recordKeylessMoment
@@ -9,7 +9,7 @@ import {
9
9
  denyReasonFrom,
10
10
  isDeferAnswer,
11
11
  resolveGate
12
- } from "./chunk-37AD5K7W.js";
12
+ } from "./chunk-GXIFADGD.js";
13
13
  import {
14
14
  DEFAULT_SESSION,
15
15
  askUser,
@@ -29,7 +29,7 @@ import {
29
29
  sendNotification,
30
30
  throttlePass,
31
31
  waitForAnswer
32
- } from "./chunk-OPMSMF5R.js";
32
+ } from "./chunk-EXMUM226.js";
33
33
  import {
34
34
  getMachineId
35
35
  } from "./chunk-RN3NOEJF.js";
@@ -53,6 +53,7 @@ var COMMAND_LIST = [
53
53
  ]
54
54
  },
55
55
  { name: "doctor", module: "pushary-doctor", summary: "Verify your Pushary installation is working" },
56
+ { name: "disconnect", module: "pushary-disconnect", summary: "Turn off one agent (claude|codex|gemini|cursor)" },
56
57
  { name: "clean", module: "pushary-clean", summary: "Remove all Pushary configuration (--yes for non-interactive)" },
57
58
  { name: "mode", module: "pushary-mode", summary: "Switch approval mode (push_only, push_first, terminal_only)" },
58
59
  { name: "wait", module: "pushary-wait", summary: 'Show or set the "wait for your phone" ladder (pushary wait 45)' },
@@ -2,7 +2,7 @@ import {
2
2
  isModeStateDegraded,
3
3
  resolveAutoResolveOrigin,
4
4
  resolvePolicyAcross
5
- } from "./chunk-OPMSMF5R.js";
5
+ } from "./chunk-EXMUM226.js";
6
6
  import {
7
7
  evaluateScope,
8
8
  scopeChangeReason,
@@ -139,6 +139,38 @@ var untrustedCodexHookEvents = (config, hooksJsonPath, command) => {
139
139
  return stored !== codexHookTrustHash(definition, command);
140
140
  }).map((definition) => definition.event);
141
141
  };
142
+ var removeCodexMcpServer = (config) => {
143
+ const servers = asRecord(config.mcp_servers);
144
+ if (!servers?.pushary) return false;
145
+ delete servers.pushary;
146
+ if (Object.keys(servers).length === 0) delete config.mcp_servers;
147
+ return true;
148
+ };
149
+ var removeCodexHookTrust = (config, hooksJsonPath) => {
150
+ const state = asRecord(asRecord(config.hooks)?.state);
151
+ if (!state) return false;
152
+ let changed = false;
153
+ for (const definition of CODEX_HOOK_EVENTS) {
154
+ const key = codexHookStateKey(hooksJsonPath, definition.event);
155
+ if (key in state) {
156
+ delete state[key];
157
+ changed = true;
158
+ }
159
+ }
160
+ if (changed && Object.keys(state).length === 0) {
161
+ const hooks = asRecord(config.hooks);
162
+ if (hooks) {
163
+ delete hooks.state;
164
+ if (Object.keys(hooks).length === 0) delete config.hooks;
165
+ }
166
+ }
167
+ return changed;
168
+ };
169
+ var removeCodexSettings = (config, hooksJsonPath) => {
170
+ const mcp = removeCodexMcpServer(config);
171
+ const trust = removeCodexHookTrust(config, hooksJsonPath);
172
+ return mcp || trust;
173
+ };
142
174
  var addCodexHookTrust = (config, hooksJsonPath, command) => {
143
175
  const hooks = ensureRecord(config, "hooks");
144
176
  const state = ensureRecord(hooks, "state");
@@ -183,6 +215,7 @@ export {
183
215
  hasCodexHooks,
184
216
  missingCodexHookEvents,
185
217
  untrustedCodexHookEvents,
218
+ removeCodexSettings,
186
219
  addCodexHookTrust,
187
220
  claudeSettings,
188
221
  claudeJson,
@@ -0,0 +1,27 @@
1
+ import {
2
+ quoteIfNeeded
3
+ } from "./chunk-VT4IVERX.js";
4
+
5
+ // src/hook-command.ts
6
+ var 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 RESOLVE = {
10
+ native: `${LOCATE}; exec "$B"`,
11
+ node: `${LOCATE}; ${NODE_PRESENT}; exec "$B"`
12
+ };
13
+ var guardHookCommand = (target, platform = process.platform) => {
14
+ if (target.kind === "node") {
15
+ const script = quoteIfNeeded(target.script);
16
+ return platform === "win32" ? `node ${script}` : `${SHELL} '[ -f "$0" ] || exit 0; command -v node >/dev/null 2>&1 || exit 0; exec node "$0"' ${script}`;
17
+ }
18
+ const path = quoteIfNeeded(target.path);
19
+ return platform === "win32" ? path : `${SHELL} '${RESOLVE[target.runtime]}' ${path}`;
20
+ };
21
+ var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
22
+ var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
23
+
24
+ export {
25
+ guardBinary,
26
+ guardNodeScript
27
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  renderCommandHelp
3
- } from "./chunk-MGJBC6DD.js";
3
+ } from "./chunk-GOEXZ5QJ.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VT4IVERX.js";
8
8
  import {
9
9
  pusharyDir
10
- } from "./chunk-SEAHGHCE.js";
10
+ } from "./chunk-HVZIHTOO.js";
11
11
  import {
12
12
  getMachineId
13
13
  } from "./chunk-RN3NOEJF.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-SEAHGHCE.js";
3
+ } from "./chunk-HVZIHTOO.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-6MTNS63X.js";
@@ -91,7 +91,11 @@ var startProcessOwnership = (identity, options = {}) => {
91
91
  proof = { ...identity, port: address.port, token };
92
92
  mkdirSync(dir, { recursive: true, mode: 448 });
93
93
  pruneProofs(dir);
94
- writeFileSync(path, JSON.stringify(proof), { encoding: "utf8", mode: 384 });
94
+ writeFileSync(path, JSON.stringify(proof), {
95
+ encoding: "utf8",
96
+ mode: 384,
97
+ flag: options.exclusive ? "wx" : "w"
98
+ });
95
99
  resolve();
96
100
  } catch {
97
101
  server.close();
@@ -113,41 +117,76 @@ var startProcessOwnership = (identity, options = {}) => {
113
117
  }
114
118
  };
115
119
  };
120
+ var withSpawnProcessOwnership = async (spawnId, run, options = {}) => {
121
+ if (!spawnId) return run();
122
+ const sessionId = `spawn:${spawnId}`;
123
+ const ownership = startProcessOwnership({
124
+ sessionId,
125
+ pid: process.pid,
126
+ spawnId
127
+ }, { ...options, exclusive: true });
128
+ await ownership.ready;
129
+ if (!await ownsLiveProcess({ sessionId, pid: process.pid, spawnId }, options)) {
130
+ ownership.stop();
131
+ throw new Error("Could not establish spawned process ownership");
132
+ }
133
+ try {
134
+ return await run();
135
+ } finally {
136
+ ownership.stop();
137
+ }
138
+ };
116
139
  var ownsLiveProcess = async (request, options = {}) => {
117
140
  const stored = readJsonSafe(proofPath(ownershipDir(options), request.sessionId));
118
141
  if (stored.kind !== "ok" || !isProof(stored.value)) return false;
119
142
  const proof = stored.value;
120
143
  if (proof.sessionId !== request.sessionId || proof.pid !== request.pid) return false;
121
144
  if (request.spawnId && proof.spawnId !== request.spawnId) return false;
122
- return new Promise((resolve) => {
123
- let settled = false;
124
- let raw = "";
125
- const socket = createConnection({ host: "127.0.0.1", port: proof.port });
126
- const finish = (owned) => {
127
- if (settled) return;
128
- settled = true;
129
- socket.destroy();
130
- resolve(owned);
131
- };
132
- socket.setTimeout(options.connectTimeoutMs ?? 500, () => finish(false));
133
- socket.on("error", () => finish(false));
134
- socket.on("connect", () => socket.write(proof.token));
135
- socket.on("data", (chunk) => {
136
- raw += String(chunk);
137
- if (raw.length > MAX_PROOF_BYTES) finish(false);
138
- });
139
- socket.on("end", () => {
140
- try {
141
- const live = JSON.parse(raw);
142
- finish(isProof(live) && live.token === proof.token && live.sessionId === proof.sessionId && live.pid === proof.pid && live.spawnId === proof.spawnId);
143
- } catch {
144
- finish(false);
145
- }
146
- });
147
- });
145
+ return verifyProof(proof, options);
148
146
  };
147
+ var findLiveProcessBySpawnId = async (spawnId, options = {}) => {
148
+ try {
149
+ const dir = ownershipDir(options);
150
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
151
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
152
+ const stored = readJsonSafe(join(dir, entry.name));
153
+ if (stored.kind !== "ok" || !isProof(stored.value) || stored.value.spawnId !== spawnId) continue;
154
+ if (await verifyProof(stored.value, options)) return stored.value.pid;
155
+ }
156
+ } catch {
157
+ }
158
+ return null;
159
+ };
160
+ var verifyProof = (proof, options) => new Promise((resolve) => {
161
+ let settled = false;
162
+ let raw = "";
163
+ const socket = createConnection({ host: "127.0.0.1", port: proof.port });
164
+ const finish = (owned) => {
165
+ if (settled) return;
166
+ settled = true;
167
+ socket.destroy();
168
+ resolve(owned);
169
+ };
170
+ socket.setTimeout(options.connectTimeoutMs ?? 500, () => finish(false));
171
+ socket.on("error", () => finish(false));
172
+ socket.on("connect", () => socket.write(proof.token));
173
+ socket.on("data", (chunk) => {
174
+ raw += String(chunk);
175
+ if (raw.length > MAX_PROOF_BYTES) finish(false);
176
+ });
177
+ socket.on("end", () => {
178
+ try {
179
+ const live = JSON.parse(raw);
180
+ finish(isProof(live) && live.token === proof.token && live.sessionId === proof.sessionId && live.pid === proof.pid && live.spawnId === proof.spawnId);
181
+ } catch {
182
+ finish(false);
183
+ }
184
+ });
185
+ });
149
186
 
150
187
  export {
151
188
  startProcessOwnership,
152
- ownsLiveProcess
189
+ withSpawnProcessOwnership,
190
+ ownsLiveProcess,
191
+ findLiveProcessBySpawnId
153
192
  };
@@ -39,7 +39,7 @@ interface ModeState {
39
39
  readonly scope?: ScopeContract | null;
40
40
  readonly degraded?: boolean;
41
41
  }
42
- declare const fetchModeState: (apiKey: string, sessionId?: string) => Promise<ModeState>;
42
+ declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
43
43
  declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
44
44
 
45
45
  interface UsageReport {
package/dist/src/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-QNBA5OXQ.js";
4
- import "../chunk-UKSQWTBH.js";
3
+ } from "../chunk-GFM3STWO.js";
4
+ import "../chunk-64PXDATC.js";
5
+ import "../chunk-PWTKKQQI.js";
6
+ import "../chunk-VT4IVERX.js";
5
7
  import "../chunk-NJ7JT26G.js";
6
- import "../chunk-37AD5K7W.js";
8
+ import "../chunk-GXIFADGD.js";
7
9
  import {
8
10
  askUser,
9
11
  cancelQuestion,
@@ -16,7 +18,7 @@ import {
16
18
  reportEvent,
17
19
  resolvePolicy,
18
20
  waitForAnswer
19
- } from "../chunk-OPMSMF5R.js";
21
+ } from "../chunk-EXMUM226.js";
20
22
  import "../chunk-RN3NOEJF.js";
21
23
  import "../chunk-BSZYIAZL.js";
22
24
  import "../chunk-SAF6HGAA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.84.0",
3
+ "version": "0.85.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -68,7 +68,8 @@
68
68
  "pushary-suggestions": "./dist/bin/pushary-suggestions.js",
69
69
  "pushary-upgrade": "./dist/bin/pushary-upgrade.js",
70
70
  "pushary-bell": "./dist/bin/pushary-bell.js",
71
- "pushary-bell-hook": "./dist/bin/pushary-bell-hook.js"
71
+ "pushary-bell-hook": "./dist/bin/pushary-bell-hook.js",
72
+ "pushary-disconnect": "./dist/bin/pushary-disconnect.js"
72
73
  },
73
74
  "files": [
74
75
  "dist",
@@ -1,208 +0,0 @@
1
- import {
2
- HOOK_BUDGETS
3
- } from "./chunk-MQMPG5X4.js";
4
-
5
- // src/claude-config.ts
6
- import { join } from "path";
7
- var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
8
- var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
9
- var PRETOOLUSE_GATED_TOOLS = ["Bash", "Write", "Edit", "MultiEdit", "NotebookEdit", "PowerShell", "Monitor"];
10
- var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, "AskUserQuestion"]);
11
- var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
12
- var ensureRecord = (target, key) => {
13
- const existing = asRecord(target[key]);
14
- if (existing) return existing;
15
- const created = {};
16
- target[key] = created;
17
- return created;
18
- };
19
- var isPusharyPermission = (rule) => typeof rule === "string" && (rule.includes("pushary") || rule.includes("MCP(pushary"));
20
- var isPusharyHook = (entry) => {
21
- const hooks = asRecord(entry)?.hooks;
22
- if (!Array.isArray(hooks)) return false;
23
- return hooks.some((hook) => {
24
- const command = String(asRecord(hook)?.command ?? "");
25
- return command.includes("pushary-hook") || command.includes("pushary-post-hook") || command.includes("pushary-stop-hook") || command.includes("pushary-prompt-hook") || command.includes("pushary-notification-hook") || command.includes("pushary-session-start-hook") || command.includes("pushary-session-end-hook") || command.includes("pushary-stopfailure-hook") || command.includes("pushary-permission-hook") || command.includes("pushary-permission-denied-hook");
26
- });
27
- };
28
- var addClaudeMcpServer = (config, apiKey) => {
29
- const mcpServers = ensureRecord(config, "mcpServers");
30
- mcpServers.pushary = {
31
- type: "http",
32
- url: PUSHARY_MCP_URL,
33
- headers: { Authorization: `Bearer ${apiKey}` }
34
- };
35
- };
36
- var removeClaudeMcpServers = (config) => {
37
- let changed = false;
38
- const removeFrom = (target) => {
39
- const mcpServers = asRecord(target.mcpServers);
40
- if (!mcpServers?.pushary) return;
41
- delete mcpServers.pushary;
42
- if (Object.keys(mcpServers).length === 0) delete target.mcpServers;
43
- changed = true;
44
- };
45
- removeFrom(config);
46
- const projects = asRecord(config.projects);
47
- if (projects) {
48
- for (const project of Object.values(projects)) {
49
- const projectConfig = asRecord(project);
50
- if (projectConfig) removeFrom(projectConfig);
51
- }
52
- }
53
- return changed;
54
- };
55
- var addPusharyToolPermissions = (settings) => {
56
- const permissions = ensureRecord(settings, "permissions");
57
- const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
58
- const filtered = allow.filter((rule) => !isPusharyPermission(rule));
59
- if (!filtered.includes(PUSHARY_PERMISSION_RULE)) {
60
- filtered.push(PUSHARY_PERMISSION_RULE);
61
- }
62
- permissions.allow = filtered;
63
- };
64
- var addPusharyHooks = (settings, binDir) => {
65
- const resolve = (name) => {
66
- const path = binDir ? join(binDir, name) : name;
67
- return /\s/.test(path) ? `"${path}"` : path;
68
- };
69
- const hooks = ensureRecord(settings, "hooks");
70
- const SHELL_AND_FILE_TOOLS = PRETOOLUSE_GATED_TOOLS.join("|");
71
- const preToolUse = (Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : []).filter((entry) => !isPusharyHook(entry));
72
- preToolUse.push({
73
- matcher: `${SHELL_AND_FILE_TOOLS}|AskUserQuestion`,
74
- hooks: [{
75
- type: "command",
76
- command: resolve("pushary-hook"),
77
- timeout: HOOK_BUDGETS.claude.budgetSeconds
78
- }]
79
- });
80
- hooks.PreToolUse = preToolUse;
81
- const postToolUse = (Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : []).filter((entry) => !isPusharyHook(entry));
82
- postToolUse.push({
83
- matcher: `${SHELL_AND_FILE_TOOLS}|TodoWrite`,
84
- hooks: [{
85
- type: "command",
86
- command: resolve("pushary-post-hook"),
87
- timeout: 10
88
- }]
89
- });
90
- hooks.PostToolUse = postToolUse;
91
- const stop = (Array.isArray(hooks.Stop) ? hooks.Stop : []).filter((entry) => !isPusharyHook(entry));
92
- stop.push({
93
- hooks: [{
94
- type: "command",
95
- command: resolve("pushary-stop-hook"),
96
- timeout: 10
97
- }]
98
- });
99
- hooks.Stop = stop;
100
- const userPromptSubmit = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).filter((entry) => !isPusharyHook(entry));
101
- userPromptSubmit.push({
102
- hooks: [{
103
- type: "command",
104
- command: resolve("pushary-prompt-hook"),
105
- timeout: 10
106
- }]
107
- });
108
- hooks.UserPromptSubmit = userPromptSubmit;
109
- const notification = (Array.isArray(hooks.Notification) ? hooks.Notification : []).filter((entry) => !isPusharyHook(entry));
110
- notification.push({
111
- matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed",
112
- hooks: [{
113
- type: "command",
114
- command: resolve("pushary-notification-hook"),
115
- timeout: 10
116
- }]
117
- });
118
- hooks.Notification = notification;
119
- const sessionStart = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).filter((entry) => !isPusharyHook(entry));
120
- sessionStart.push({
121
- matcher: "startup|resume|clear",
122
- hooks: [{
123
- type: "command",
124
- command: resolve("pushary-session-start-hook"),
125
- timeout: 10
126
- }]
127
- });
128
- hooks.SessionStart = sessionStart;
129
- const sessionEnd = (Array.isArray(hooks.SessionEnd) ? hooks.SessionEnd : []).filter((entry) => !isPusharyHook(entry));
130
- sessionEnd.push({
131
- hooks: [{
132
- type: "command",
133
- command: resolve("pushary-session-end-hook"),
134
- timeout: 10
135
- }]
136
- });
137
- hooks.SessionEnd = sessionEnd;
138
- const stopFailure = (Array.isArray(hooks.StopFailure) ? hooks.StopFailure : []).filter((entry) => !isPusharyHook(entry));
139
- stopFailure.push({
140
- hooks: [{
141
- type: "command",
142
- command: resolve("pushary-stopfailure-hook"),
143
- timeout: 10
144
- }]
145
- });
146
- hooks.StopFailure = stopFailure;
147
- const permissionRequest = (Array.isArray(hooks.PermissionRequest) ? hooks.PermissionRequest : []).filter((entry) => !isPusharyHook(entry));
148
- permissionRequest.push({
149
- hooks: [{
150
- type: "command",
151
- command: resolve("pushary-permission-hook"),
152
- timeout: HOOK_BUDGETS.claude.budgetSeconds
153
- }]
154
- });
155
- hooks.PermissionRequest = permissionRequest;
156
- const permissionDenied = (Array.isArray(hooks.PermissionDenied) ? hooks.PermissionDenied : []).filter((entry) => !isPusharyHook(entry));
157
- permissionDenied.push({
158
- hooks: [{
159
- type: "command",
160
- command: resolve("pushary-permission-denied-hook"),
161
- timeout: 60
162
- }]
163
- });
164
- hooks.PermissionDenied = permissionDenied;
165
- };
166
- var removePusharySettings = (settings) => {
167
- let changed = removeClaudeMcpServers(settings);
168
- const permissions = asRecord(settings.permissions);
169
- if (permissions && Array.isArray(permissions.allow)) {
170
- const filtered = permissions.allow.filter((rule) => !isPusharyPermission(rule));
171
- if (filtered.length !== permissions.allow.length) {
172
- if (filtered.length === 0) {
173
- delete permissions.allow;
174
- } else {
175
- permissions.allow = filtered;
176
- }
177
- if (Object.keys(permissions).length === 0) delete settings.permissions;
178
- changed = true;
179
- }
180
- }
181
- const hooks = asRecord(settings.hooks);
182
- if (hooks) {
183
- for (const key of ["PreToolUse", "PostToolUse", "Stop", "StopFailure", "UserPromptSubmit", "Notification", "SessionStart", "SessionEnd", "PermissionRequest", "PermissionDenied"]) {
184
- const entries = hooks[key];
185
- if (!Array.isArray(entries)) continue;
186
- const filtered = entries.filter((entry) => !isPusharyHook(entry));
187
- if (filtered.length !== entries.length) {
188
- if (filtered.length === 0) {
189
- delete hooks[key];
190
- } else {
191
- hooks[key] = filtered;
192
- }
193
- changed = true;
194
- }
195
- }
196
- if (Object.keys(hooks).length === 0) delete settings.hooks;
197
- }
198
- return changed;
199
- };
200
-
201
- export {
202
- PRETOOLUSE_HANDLED_TOOLS,
203
- addClaudeMcpServer,
204
- removeClaudeMcpServers,
205
- addPusharyToolPermissions,
206
- addPusharyHooks,
207
- removePusharySettings
208
- };