@pushary/agent-hooks 0.32.2 → 0.34.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-M6YUEVRV.js";
5
+ } from "../chunk-RQSJVS3L.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-P7B3U6DR.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-P7B3U6DR.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-P7B3U6DR.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-2AMV2BDM.js";
4
+ } from "../chunk-M3YN5ZNW.js";
5
5
  import "../chunk-KQYIHZ5E.js";
6
- import "../chunk-P7B3U6DR.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-P7B3U6DR.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-P7B3U6DR.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-P7B3U6DR.js";
4
+ } from "../chunk-FIBI2QZC.js";
5
5
  import "../chunk-DWED7BS3.js";
6
6
  import "../chunk-Z5PL3K7C.js";
7
7
  import "../chunk-NKXSILEW.js";
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ handleSessionStart
4
+ } from "../chunk-FIBI2QZC.js";
5
+ import "../chunk-DWED7BS3.js";
6
+ import "../chunk-Z5PL3K7C.js";
7
+ import "../chunk-NKXSILEW.js";
8
+
9
+ // bin/pushary-session-start-hook.ts
10
+ var main = async () => {
11
+ let rawInput = "";
12
+ for await (const chunk of process.stdin) {
13
+ rawInput += chunk;
14
+ }
15
+ try {
16
+ const input = rawInput.trim() ? JSON.parse(rawInput) : {};
17
+ await handleSessionStart(input);
18
+ } catch {
19
+ }
20
+ };
21
+ main();
@@ -3,7 +3,7 @@ import {
3
3
  addClaudeMcpServer,
4
4
  addPusharyHooks,
5
5
  addPusharyToolPermissions
6
- } from "../chunk-M6YUEVRV.js";
6
+ } from "../chunk-RQSJVS3L.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-P7B3U6DR.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-P7B3U6DR.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,15 +190,34 @@ 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
  };
200
+ var describeTodoProgress = (todos) => {
201
+ if (!Array.isArray(todos)) return "updated the plan";
202
+ const items = todos.filter((t) => !!t && typeof t === "object");
203
+ const total = items.length;
204
+ if (total === 0) return "updated the plan";
205
+ const done = items.filter((t) => t.status === "completed").length;
206
+ const current = items.find((t) => t.status === "in_progress");
207
+ const label = current ? [current.activeForm, current.content].find((v) => typeof v === "string" && v.trim().length > 0) : void 0;
208
+ if (done >= total) return `finished all ${total} tasks`;
209
+ if (label) return `${label.trim()} (${done}/${total} done)`;
210
+ return `${done}/${total} tasks done`;
211
+ };
197
212
  var eventPrefixes = {
198
213
  Bash: (input) => `ran: ${String(input.command ?? "")}`,
214
+ PowerShell: (input) => `ran: ${String(input.command ?? "")}`,
215
+ Monitor: (input) => `ran: ${String(input.command ?? "")}`,
199
216
  Write: (input) => `wrote: ${input.file_path ?? "unknown"}`,
200
217
  Edit: (input) => `edited: ${input.file_path ?? "unknown"}`,
201
- Read: (input) => `read: ${input.file_path ?? "unknown"}`
218
+ MultiEdit: (input) => `edited: ${input.file_path ?? "unknown"}`,
219
+ Read: (input) => `read: ${input.file_path ?? "unknown"}`,
220
+ TodoWrite: (input) => describeTodoProgress(input.todos)
202
221
  };
203
222
  var EVENT_ACTION_MAX = 120;
204
223
  var HOOK_FALLBACK_MAX = 200;
@@ -217,10 +236,10 @@ var deriveCommandHead = (command) => {
217
236
  return head ? head.slice(0, TOOL_TARGET_MAX_LENGTH) : void 0;
218
237
  };
219
238
  var deriveToolTarget = (toolName, toolInput) => {
220
- if (toolName === "Bash") {
239
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor") {
221
240
  return deriveCommandHead(toolInput.command);
222
241
  }
223
- if (toolName === "Edit" || toolName === "Write") {
242
+ if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
224
243
  const filePath = toolInput.file_path;
225
244
  if (typeof filePath !== "string") return void 0;
226
245
  const separator = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
@@ -246,16 +265,16 @@ var relativizeReceiptPath = (filePath, cwd) => {
246
265
  var deriveReceiptMeta = (toolName, toolInput, toolResult, cwd) => {
247
266
  try {
248
267
  const ok = !isToolResultError(toolResult);
249
- if (toolName === "Edit" || toolName === "Write") {
268
+ if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
250
269
  const filePath = toolInput.file_path;
251
270
  if (typeof filePath !== "string" || !filePath) return void 0;
252
271
  return {
253
- kind: toolName === "Edit" ? "edit" : "write",
272
+ kind: toolName === "Write" ? "write" : "edit",
254
273
  target: relativizeReceiptPath(filePath, cwd).slice(0, RECEIPT_TARGET_MAX_LENGTH),
255
274
  ok
256
275
  };
257
276
  }
258
- if (toolName === "Bash") {
277
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor") {
259
278
  const head = deriveCommandHead(toolInput.command);
260
279
  if (!head) return void 0;
261
280
  return {
@@ -281,7 +300,7 @@ var rawActionBody = (toolName, toolInput) => {
281
300
  + ${after ?? ""}`;
282
301
  }
283
302
  if (toolName === "Write" || toolName === "write_file") return firstString(toolInput.content);
284
- 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") {
285
304
  return firstString(toolInput.command);
286
305
  }
287
306
  return void 0;
@@ -919,6 +938,20 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
919
938
  return void 0;
920
939
  }
921
940
  };
941
+ var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
942
+ try {
943
+ const projectName = basename(input.cwd ?? process.cwd());
944
+ const action = input.source === "resume" ? "Session resumed" : "Session started";
945
+ await reportEvent({
946
+ event: "session_start",
947
+ agentType: agent.type,
948
+ agentName: `${agent.label} - ${projectName}`,
949
+ action,
950
+ sessionId: input.session_id
951
+ }, { maxAttempts: 1, timeoutMs: 800 });
952
+ } catch {
953
+ }
954
+ };
922
955
  var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set(["idle_prompt", "agent_needs_input"]);
923
956
  var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
924
957
  var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
@@ -990,5 +1023,6 @@ export {
990
1023
  handlePostToolUse,
991
1024
  handleUserPrompt,
992
1025
  handleStop,
1026
+ handleSessionStart,
993
1027
  handleNotification
994
1028
  };
@@ -20,7 +20,7 @@ import {
20
20
  savePendingQuestion,
21
21
  sendNotification,
22
22
  waitForAnswer
23
- } from "./chunk-P7B3U6DR.js";
23
+ } from "./chunk-FIBI2QZC.js";
24
24
  import {
25
25
  isGatingMoment,
26
26
  recordKeylessMoment
@@ -200,11 +200,22 @@ var keylessNoticeOnce = (sessionId) => {
200
200
  } catch {
201
201
  }
202
202
  };
203
- var DEFER_PERMISSION_MODES = /* @__PURE__ */ new Set(["bypassPermissions", "plan", "auto"]);
204
- var shouldDeferToNativeMode = (permissionMode) => {
203
+ var FULL_DEFER_MODES = /* @__PURE__ */ new Set(["plan", "auto", "dontAsk"]);
204
+ var ACCEPT_EDITS_DEFER_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
205
+ var nativeModeGatingDisabled = () => {
205
206
  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);
207
+ return flag === "0" || flag === "false";
208
+ };
209
+ var shouldDeferToNativeMode = (permissionMode, toolName) => {
210
+ if (nativeModeGatingDisabled()) return false;
211
+ if (typeof permissionMode !== "string") return false;
212
+ if (FULL_DEFER_MODES.has(permissionMode)) return true;
213
+ if (permissionMode === "acceptEdits" && ACCEPT_EDITS_DEFER_TOOLS.has(toolName)) return true;
214
+ if (permissionMode === "bypassPermissions") {
215
+ const flag = process.env.PUSHARY_DEFER_BYPASS;
216
+ return flag === "1" || flag === "true";
217
+ }
218
+ return false;
208
219
  };
209
220
  var handleKeyless = (input) => {
210
221
  try {
@@ -235,7 +246,7 @@ var handlePreToolUse = async (input) => {
235
246
  if (modeState.kill) {
236
247
  return deny("Stopped by user \u2014 this agent was halted from Pushary");
237
248
  }
238
- if (shouldDeferToNativeMode(input.permission_mode)) {
249
+ if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
239
250
  return void 0;
240
251
  }
241
252
  const toolPolicy = resolvePolicy(policy, input.tool_name, modeState.mode, input.tool_input);
@@ -20,7 +20,7 @@ var isPusharyHook = (entry) => {
20
20
  if (!Array.isArray(hooks)) return false;
21
21
  return hooks.some((hook) => {
22
22
  const command = String(asRecord(hook)?.command ?? "");
23
- 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");
23
+ 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");
24
24
  });
25
25
  };
26
26
  var addClaudeMcpServer = (config, apiKey) => {
@@ -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,
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",
81
+ matcher: `${SHELL_AND_FILE_TOOLS}|TodoWrite`,
81
82
  hooks: [{
82
83
  type: "command",
83
84
  command: resolve("pushary-post-hook"),
@@ -113,6 +114,16 @@ var addPusharyHooks = (settings, binDir) => {
113
114
  }]
114
115
  });
115
116
  hooks.Notification = notification;
117
+ const sessionStart = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).filter((entry) => !isPusharyHook(entry));
118
+ sessionStart.push({
119
+ matcher: "startup|resume|clear",
120
+ hooks: [{
121
+ type: "command",
122
+ command: resolve("pushary-session-start-hook"),
123
+ timeout: 10
124
+ }]
125
+ });
126
+ hooks.SessionStart = sessionStart;
116
127
  };
117
128
  var removePusharySettings = (settings) => {
118
129
  let changed = removeClaudeMcpServers(settings);
@@ -131,7 +142,7 @@ var removePusharySettings = (settings) => {
131
142
  }
132
143
  const hooks = asRecord(settings.hooks);
133
144
  if (hooks) {
134
- for (const key of ["PreToolUse", "PostToolUse", "Stop", "UserPromptSubmit", "Notification"]) {
145
+ for (const key of ["PreToolUse", "PostToolUse", "Stop", "UserPromptSubmit", "Notification", "SessionStart"]) {
135
146
  const entries = hooks[key];
136
147
  if (!Array.isArray(entries)) continue;
137
148
  const filtered = entries.filter((entry) => !isPusharyHook(entry));
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-2AMV2BDM.js";
3
+ } from "../chunk-M3YN5ZNW.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-P7B3U6DR.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.32.2",
3
+ "version": "0.34.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -48,6 +48,7 @@
48
48
  "pushary-stop-hook": "./dist/bin/pushary-stop-hook.js",
49
49
  "pushary-prompt-hook": "./dist/bin/pushary-prompt-hook.js",
50
50
  "pushary-notification-hook": "./dist/bin/pushary-notification-hook.js",
51
+ "pushary-session-start-hook": "./dist/bin/pushary-session-start-hook.js",
51
52
  "pushary-claude": "./dist/bin/pushary-claude.js",
52
53
  "pushary-codex": "./dist/bin/pushary-codex.js",
53
54
  "pushary-codex-hook": "./dist/bin/pushary-codex-hook.js",