@pushary/agent-hooks 0.33.0 → 0.35.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-AV2F2ZTZ.js";
5
+ } from "../chunk-ZJICOAAN.js";
6
6
  import {
7
7
  removeCodexHooks,
8
8
  removeGeminiSettings,
@@ -32,7 +32,7 @@ import {
32
32
  toCodexWire,
33
33
  toPolicyLookup,
34
34
  waitForAnswer
35
- } from "../chunk-OCPDWNG2.js";
35
+ } from "../chunk-FIBI2QZC.js";
36
36
  import {
37
37
  isGatingMoment,
38
38
  recordKeylessMoment
@@ -4,7 +4,7 @@ import {
4
4
  getMachineId,
5
5
  reportEvent,
6
6
  waitForAnswer
7
- } from "../chunk-OCPDWNG2.js";
7
+ } from "../chunk-FIBI2QZC.js";
8
8
  import "../chunk-DWED7BS3.js";
9
9
  import "../chunk-Z5PL3K7C.js";
10
10
  import {
@@ -24,7 +24,7 @@ import {
24
24
  savePendingQuestion,
25
25
  sendNotification,
26
26
  waitForAnswer
27
- } from "../chunk-OCPDWNG2.js";
27
+ } from "../chunk-FIBI2QZC.js";
28
28
  import {
29
29
  isGatingMoment,
30
30
  recordKeylessMoment
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-UU3ETDK4.js";
4
+ } from "../chunk-JE3WQW63.js";
5
5
  import "../chunk-KQYIHZ5E.js";
6
- import "../chunk-OCPDWNG2.js";
6
+ import "../chunk-FIBI2QZC.js";
7
7
  import "../chunk-R5AJNXZS.js";
8
8
  import "../chunk-DWED7BS3.js";
9
9
  import "../chunk-Z5PL3K7C.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleNotification
4
- } from "../chunk-OCPDWNG2.js";
4
+ } from "../chunk-FIBI2QZC.js";
5
5
  import "../chunk-DWED7BS3.js";
6
6
  import "../chunk-Z5PL3K7C.js";
7
7
  import "../chunk-NKXSILEW.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-OCPDWNG2.js";
4
+ } from "../chunk-FIBI2QZC.js";
5
5
  import "../chunk-DWED7BS3.js";
6
6
  import "../chunk-Z5PL3K7C.js";
7
7
  import "../chunk-NKXSILEW.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleUserPrompt
4
- } from "../chunk-OCPDWNG2.js";
4
+ } from "../chunk-FIBI2QZC.js";
5
5
  import "../chunk-DWED7BS3.js";
6
6
  import "../chunk-Z5PL3K7C.js";
7
7
  import "../chunk-NKXSILEW.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleSessionStart
4
- } from "../chunk-OCPDWNG2.js";
4
+ } from "../chunk-FIBI2QZC.js";
5
5
  import "../chunk-DWED7BS3.js";
6
6
  import "../chunk-Z5PL3K7C.js";
7
7
  import "../chunk-NKXSILEW.js";
@@ -3,7 +3,7 @@ import {
3
3
  addClaudeMcpServer,
4
4
  addPusharyHooks,
5
5
  addPusharyToolPermissions
6
- } from "../chunk-AV2F2ZTZ.js";
6
+ } from "../chunk-ZJICOAAN.js";
7
7
  import {
8
8
  GEMINI_HOOK_BINARY,
9
9
  addCodexHookTrust,
@@ -24,7 +24,7 @@ import {
24
24
  } from "../chunk-J7JWI3KU.js";
25
25
  import {
26
26
  reportEvent
27
- } from "../chunk-OCPDWNG2.js";
27
+ } from "../chunk-FIBI2QZC.js";
28
28
  import "../chunk-DWED7BS3.js";
29
29
  import {
30
30
  isValidApiKey
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-OCPDWNG2.js";
4
+ } from "../chunk-FIBI2QZC.js";
5
5
  import "../chunk-DWED7BS3.js";
6
6
  import "../chunk-Z5PL3K7C.js";
7
7
  import "../chunk-NKXSILEW.js";
@@ -190,8 +190,11 @@ var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
190
190
  import { isAbsolute, relative } from "path";
191
191
  var hookPrefixes = {
192
192
  Bash: (input) => `bash: ${input.command ?? "(no command)"}`,
193
+ PowerShell: (input) => `powershell: ${input.command ?? "(no command)"}`,
194
+ Monitor: (input) => `monitor: ${input.command ?? "(no command)"}`,
193
195
  Write: (input) => `write file: ${input.file_path ?? "(unknown path)"}`,
194
196
  Edit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
197
+ MultiEdit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
195
198
  Read: (input) => `read file: ${input.file_path ?? "(unknown path)"}`
196
199
  };
197
200
  var describeTodoProgress = (todos) => {
@@ -208,8 +211,11 @@ var describeTodoProgress = (todos) => {
208
211
  };
209
212
  var eventPrefixes = {
210
213
  Bash: (input) => `ran: ${String(input.command ?? "")}`,
214
+ PowerShell: (input) => `ran: ${String(input.command ?? "")}`,
215
+ Monitor: (input) => `ran: ${String(input.command ?? "")}`,
211
216
  Write: (input) => `wrote: ${input.file_path ?? "unknown"}`,
212
217
  Edit: (input) => `edited: ${input.file_path ?? "unknown"}`,
218
+ MultiEdit: (input) => `edited: ${input.file_path ?? "unknown"}`,
213
219
  Read: (input) => `read: ${input.file_path ?? "unknown"}`,
214
220
  TodoWrite: (input) => describeTodoProgress(input.todos)
215
221
  };
@@ -230,10 +236,10 @@ var deriveCommandHead = (command) => {
230
236
  return head ? head.slice(0, TOOL_TARGET_MAX_LENGTH) : void 0;
231
237
  };
232
238
  var deriveToolTarget = (toolName, toolInput) => {
233
- if (toolName === "Bash") {
239
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor") {
234
240
  return deriveCommandHead(toolInput.command);
235
241
  }
236
- if (toolName === "Edit" || toolName === "Write") {
242
+ if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
237
243
  const filePath = toolInput.file_path;
238
244
  if (typeof filePath !== "string") return void 0;
239
245
  const separator = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
@@ -259,16 +265,16 @@ var relativizeReceiptPath = (filePath, cwd) => {
259
265
  var deriveReceiptMeta = (toolName, toolInput, toolResult, cwd) => {
260
266
  try {
261
267
  const ok = !isToolResultError(toolResult);
262
- if (toolName === "Edit" || toolName === "Write") {
268
+ if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
263
269
  const filePath = toolInput.file_path;
264
270
  if (typeof filePath !== "string" || !filePath) return void 0;
265
271
  return {
266
- kind: toolName === "Edit" ? "edit" : "write",
272
+ kind: toolName === "Write" ? "write" : "edit",
267
273
  target: relativizeReceiptPath(filePath, cwd).slice(0, RECEIPT_TARGET_MAX_LENGTH),
268
274
  ok
269
275
  };
270
276
  }
271
- if (toolName === "Bash") {
277
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor") {
272
278
  const head = deriveCommandHead(toolInput.command);
273
279
  if (!head) return void 0;
274
280
  return {
@@ -294,7 +300,7 @@ var rawActionBody = (toolName, toolInput) => {
294
300
  + ${after ?? ""}`;
295
301
  }
296
302
  if (toolName === "Write" || toolName === "write_file") return firstString(toolInput.content);
297
- if (toolName === "Bash" || toolName === "run_shell_command" || toolName === "shell" || toolName === "apply_patch") {
303
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor" || toolName === "run_shell_command" || toolName === "shell" || toolName === "apply_patch") {
298
304
  return firstString(toolInput.command);
299
305
  }
300
306
  return void 0;
@@ -20,7 +20,7 @@ import {
20
20
  savePendingQuestion,
21
21
  sendNotification,
22
22
  waitForAnswer
23
- } from "./chunk-OCPDWNG2.js";
23
+ } from "./chunk-FIBI2QZC.js";
24
24
  import {
25
25
  isGatingMoment,
26
26
  recordKeylessMoment
@@ -53,6 +53,13 @@ var deny = (reason) => ({
53
53
  permissionDecisionReason: reason
54
54
  }
55
55
  });
56
+ var allowWithInput = (updatedInput) => ({
57
+ hookSpecificOutput: {
58
+ hookEventName: "PreToolUse",
59
+ permissionDecision: "allow",
60
+ updatedInput
61
+ }
62
+ });
56
63
  var ask = (reason) => ({
57
64
  hookSpecificOutput: {
58
65
  hookEventName: "PreToolUse",
@@ -200,11 +207,64 @@ var keylessNoticeOnce = (sessionId) => {
200
207
  } catch {
201
208
  }
202
209
  };
203
- var DEFER_PERMISSION_MODES = /* @__PURE__ */ new Set(["bypassPermissions", "plan", "auto"]);
204
- var shouldDeferToNativeMode = (permissionMode) => {
210
+ var FULL_DEFER_MODES = /* @__PURE__ */ new Set(["plan", "auto", "dontAsk"]);
211
+ var ACCEPT_EDITS_DEFER_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
212
+ var nativeModeGatingDisabled = () => {
205
213
  const flag = process.env.PUSHARY_RESPECT_PERMISSION_MODE;
206
- if (flag !== "1" && flag !== "true") return false;
207
- return typeof permissionMode === "string" && DEFER_PERMISSION_MODES.has(permissionMode);
214
+ return flag === "0" || flag === "false";
215
+ };
216
+ var shouldDeferToNativeMode = (permissionMode, toolName) => {
217
+ if (nativeModeGatingDisabled()) return false;
218
+ if (typeof permissionMode !== "string") return false;
219
+ if (FULL_DEFER_MODES.has(permissionMode)) return true;
220
+ if (permissionMode === "acceptEdits" && ACCEPT_EDITS_DEFER_TOOLS.has(toolName)) return true;
221
+ if (permissionMode === "bypassPermissions") {
222
+ const flag = process.env.PUSHARY_DEFER_BYPASS;
223
+ return flag === "1" || flag === "true";
224
+ }
225
+ return false;
226
+ };
227
+ var parseSingleQuestion = (toolInput) => {
228
+ const questions = toolInput.questions;
229
+ if (!Array.isArray(questions) || questions.length !== 1) return void 0;
230
+ const q = questions[0];
231
+ if (!q || typeof q.question !== "string" || q.multiSelect === true) return void 0;
232
+ const labels = Array.isArray(q.options) ? q.options.map((o) => o && typeof o === "object" ? o.label : void 0).filter((l) => typeof l === "string" && l.length > 0) : [];
233
+ if (labels.length < 2) return void 0;
234
+ return { question: q.question, header: typeof q.header === "string" ? q.header : void 0, labels };
235
+ };
236
+ var handleAskUserQuestion = async (apiKey, input) => {
237
+ const parsed = parseSingleQuestion(input.tool_input ?? {});
238
+ if (!parsed) return void 0;
239
+ const projectName = basename(input.cwd ?? process.cwd());
240
+ let result;
241
+ try {
242
+ result = await askUser(apiKey, {
243
+ question: parsed.question,
244
+ type: "select",
245
+ options: [...parsed.labels],
246
+ context: parsed.header ? `${parsed.header}: your agent is asking in ${projectName}` : `Your agent is asking in ${projectName}`,
247
+ agentName: `Claude Code - ${projectName}`,
248
+ sessionId: input.session_id,
249
+ machineId: getMachineId(),
250
+ toolName: "AskUserQuestion"
251
+ });
252
+ } catch {
253
+ return void 0;
254
+ }
255
+ if (result.suppressed || result.noDevices) {
256
+ await cancelQuestion(apiKey, result.correlationId).catch(() => {
257
+ });
258
+ return void 0;
259
+ }
260
+ const deadline = hookWaitDeadline(START_MS, effectiveWaitSeconds("wait", 0, "claude"), "claude", Date.now());
261
+ const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
262
+ const value = answer.answered ? (answer.value ?? "").trim() : "";
263
+ if (!value || isDeferAnswer(answer.value)) {
264
+ savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
265
+ return void 0;
266
+ }
267
+ return allowWithInput({ ...input.tool_input ?? {}, answers: { [parsed.question]: value } });
208
268
  };
209
269
  var handleKeyless = (input) => {
210
270
  try {
@@ -235,7 +295,10 @@ var handlePreToolUse = async (input) => {
235
295
  if (modeState.kill) {
236
296
  return deny("Stopped by user \u2014 this agent was halted from Pushary");
237
297
  }
238
- if (shouldDeferToNativeMode(input.permission_mode)) {
298
+ if (input.tool_name === "AskUserQuestion") {
299
+ return handleAskUserQuestion(apiKey, input);
300
+ }
301
+ if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
239
302
  return void 0;
240
303
  }
241
304
  const toolPolicy = resolvePolicy(policy, input.tool_name, modeState.mode, input.tool_input);
@@ -65,9 +65,10 @@ var addPusharyHooks = (settings, binDir) => {
65
65
  return /\s/.test(path) ? `"${path}"` : path;
66
66
  };
67
67
  const hooks = ensureRecord(settings, "hooks");
68
+ const SHELL_AND_FILE_TOOLS = "Bash|Write|Edit|MultiEdit|NotebookEdit|PowerShell|Monitor";
68
69
  const preToolUse = (Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : []).filter((entry) => !isPusharyHook(entry));
69
70
  preToolUse.push({
70
- matcher: "Bash|Write|Edit|NotebookEdit",
71
+ matcher: `${SHELL_AND_FILE_TOOLS}|AskUserQuestion`,
71
72
  hooks: [{
72
73
  type: "command",
73
74
  command: resolve("pushary-hook"),
@@ -77,7 +78,7 @@ var addPusharyHooks = (settings, binDir) => {
77
78
  hooks.PreToolUse = preToolUse;
78
79
  const postToolUse = (Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : []).filter((entry) => !isPusharyHook(entry));
79
80
  postToolUse.push({
80
- matcher: "Bash|Write|Edit|NotebookEdit|TodoWrite",
81
+ matcher: `${SHELL_AND_FILE_TOOLS}|TodoWrite`,
81
82
  hooks: [{
82
83
  type: "command",
83
84
  command: resolve("pushary-post-hook"),
@@ -14,6 +14,7 @@ interface HookOutput {
14
14
  hookEventName: 'PreToolUse';
15
15
  permissionDecision: 'allow' | 'deny' | 'ask';
16
16
  permissionDecisionReason?: string;
17
+ updatedInput?: Record<string, unknown>;
17
18
  };
18
19
  }
19
20
  declare const handlePreToolUse: (input: ToolInput) => Promise<HookOutput | undefined>;
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-UU3ETDK4.js";
3
+ } from "../chunk-JE3WQW63.js";
4
4
  import "../chunk-KQYIHZ5E.js";
5
5
  import {
6
6
  askUser,
@@ -14,7 +14,7 @@ import {
14
14
  reportEvent,
15
15
  resolvePolicy,
16
16
  waitForAnswer
17
- } from "../chunk-OCPDWNG2.js";
17
+ } from "../chunk-FIBI2QZC.js";
18
18
  import "../chunk-R5AJNXZS.js";
19
19
  import "../chunk-DWED7BS3.js";
20
20
  import "../chunk-Z5PL3K7C.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.33.0",
3
+ "version": "0.35.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",