@pushary/agent-hooks 0.85.0 → 0.85.1

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 (64) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/data/cursor-plugin/CHANGELOG.md +21 -0
  3. package/data/cursor-plugin/scripts/pushary-gate.mjs +54 -222
  4. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +11 -106
  5. package/data/vscode-plugin/CHANGELOG.md +26 -0
  6. package/data/vscode-plugin/scripts/pushary-gate.mjs +66 -74
  7. package/dist/bin/pushary-bell-hook.js +4 -4
  8. package/dist/bin/pushary-bell.js +6 -6
  9. package/dist/bin/pushary-claude.js +12 -11
  10. package/dist/bin/pushary-clean.js +6 -6
  11. package/dist/bin/pushary-codex-bridge.js +5 -5
  12. package/dist/bin/pushary-codex-hook.js +9 -9
  13. package/dist/bin/pushary-codex.js +3 -3
  14. package/dist/bin/pushary-connect.js +6 -6
  15. package/dist/bin/pushary-daemon.js +4 -4
  16. package/dist/bin/pushary-disconnect.js +5 -5
  17. package/dist/bin/pushary-doctor.js +11 -11
  18. package/dist/bin/pushary-gemini-bridge.js +5 -5
  19. package/dist/bin/pushary-gemini-hook.js +9 -9
  20. package/dist/bin/pushary-hook.js +8 -8
  21. package/dist/bin/pushary-login.js +4 -4
  22. package/dist/bin/pushary-logout.js +4 -4
  23. package/dist/bin/pushary-mode.js +3 -3
  24. package/dist/bin/pushary-notification-hook.js +3 -3
  25. package/dist/bin/pushary-permission-denied-hook.js +8 -8
  26. package/dist/bin/pushary-permission-hook.js +8 -8
  27. package/dist/bin/pushary-post-hook.js +3 -3
  28. package/dist/bin/pushary-prompt-hook.js +3 -3
  29. package/dist/bin/pushary-session-end-hook.js +3 -3
  30. package/dist/bin/pushary-session-start-hook.js +3 -3
  31. package/dist/bin/pushary-setup.js +14 -14
  32. package/dist/bin/pushary-stats.js +2 -2
  33. package/dist/bin/pushary-status.js +5 -5
  34. package/dist/bin/pushary-stop-hook.js +3 -3
  35. package/dist/bin/pushary-stopfailure-hook.js +3 -3
  36. package/dist/bin/pushary-upgrade.js +6 -6
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-YKW6JCWI.js → chunk-55656H4Q.js} +1 -1
  39. package/dist/{chunk-6LC4WSCL.js → chunk-BZKTCOZQ.js} +1 -1
  40. package/dist/{chunk-K7CSRGKZ.js → chunk-CIVG746V.js} +1 -1
  41. package/dist/{chunk-FBGBJXVC.js → chunk-DNSIVHIA.js} +1 -1
  42. package/dist/{chunk-437T77TY.js → chunk-ES3FAX5X.js} +1 -1
  43. package/dist/{chunk-AXRATIS6.js → chunk-EZPRY2ZY.js} +1 -1
  44. package/dist/{chunk-MQMPG5X4.js → chunk-G3LXA2ZP.js} +207 -10
  45. package/dist/{chunk-64PXDATC.js → chunk-GM54KC62.js} +17 -17
  46. package/dist/chunk-HSGEEBBL.js +37 -0
  47. package/dist/{chunk-ADISVXZL.js → chunk-IGV4KL7B.js} +1 -1
  48. package/dist/{chunk-GCKAWHFP.js → chunk-JDQY47OW.js} +1 -1
  49. package/dist/{chunk-7PZPJSWI.js → chunk-KTKIZEFT.js} +1 -1
  50. package/dist/{chunk-GFM3STWO.js → chunk-LB6RSCLL.js} +12 -10
  51. package/dist/{chunk-DOXSWT4Q.js → chunk-OZKRF7MS.js} +6 -6
  52. package/dist/{chunk-HVZIHTOO.js → chunk-PHXEADSL.js} +7 -7
  53. package/dist/{chunk-F52SIIDG.js → chunk-QDKJTPAT.js} +1 -1
  54. package/dist/{chunk-NJ7JT26G.js → chunk-VGADRFG6.js} +1 -1
  55. package/dist/{chunk-4QPOMJUB.js → chunk-VKJUVPTJ.js} +2 -2
  56. package/dist/{chunk-ERYKGH4J.js → chunk-XIR6N66V.js} +1 -1
  57. package/dist/{chunk-WEAQQSHQ.js → chunk-XLM6COJA.js} +1 -1
  58. package/dist/{chunk-EXMUM226.js → chunk-Y7BALRVB.js} +54 -127
  59. package/dist/chunk-YHG74UFF.js +12 -0
  60. package/dist/src/index.d.ts +6 -21
  61. package/dist/src/index.js +10 -9
  62. package/package.json +4 -2
  63. package/dist/chunk-GXIFADGD.js +0 -66
  64. package/dist/chunk-PWTKKQQI.js +0 -27
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-SAF6HGAA.js";
11
11
  import {
12
12
  reportedInstallSource
13
- } from "./chunk-437T77TY.js";
13
+ } from "./chunk-ES3FAX5X.js";
14
14
  import {
15
15
  getApiKey,
16
16
  getBaseUrl
@@ -18,19 +18,15 @@ import {
18
18
  import {
19
19
  ACTION_BODY_MAX,
20
20
  DECISION_LINE_MAX,
21
- classifyDecisionRisk,
22
- isApprovalMode,
23
21
  isAutoApprove,
24
- isAutoDeny,
25
- isSafeReadOnlyCommand,
26
- isScopeContract,
27
22
  localRepoKey,
23
+ modeStateFromResponse,
28
24
  normalizeRepoRemote,
29
- policyArgForms,
30
25
  redactSecrets,
31
26
  redactSecretsDeep,
32
- selectGoverningRule
33
- } from "./chunk-MQMPG5X4.js";
27
+ resolveAutoResolveOrigin,
28
+ resolvePolicy
29
+ } from "./chunk-G3LXA2ZP.js";
34
30
 
35
31
  // src/policy.ts
36
32
  import { createHash } from "crypto";
@@ -120,66 +116,6 @@ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessio
120
116
  throw new Error("Failed to fetch policy and no cached policy available");
121
117
  }
122
118
  };
123
- var autoApprove = (tool) => ({
124
- tool,
125
- timeoutSeconds: 0,
126
- timeoutAction: "approve",
127
- mode: "terminal_only",
128
- pushFirstSeconds: 0
129
- });
130
- var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessionId) => {
131
- const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
132
- const arg = args[0];
133
- const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
134
- const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
135
- if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
136
- return "safe_readonly";
137
- }
138
- return "policy_timeout";
139
- };
140
- var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
141
- const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
142
- const arg = args[0];
143
- const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
144
- let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
145
- tool: toolName,
146
- timeoutSeconds: config.defaultTimeoutSeconds,
147
- timeoutAction: config.defaultTimeoutAction,
148
- mode: config.defaultMode ?? "push_first",
149
- pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
150
- };
151
- const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
152
- if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
153
- base = autoApprove(base.tool);
154
- }
155
- const wouldAutoApprove = isAutoApprove(base);
156
- if (!governedBySpecificRule && wouldAutoApprove && toolName === "Bash" && typeof arg === "string" && classifyDecisionRisk(toolName, arg) === "destructive") {
157
- base = {
158
- tool: base.tool,
159
- timeoutSeconds: config.defaultTimeoutSeconds,
160
- timeoutAction: "wait",
161
- mode: "push_first",
162
- pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
163
- };
164
- }
165
- const effectiveOverride = modeOverride ?? config.modeOverride;
166
- if (effectiveOverride) {
167
- return { ...base, mode: effectiveOverride };
168
- }
169
- return base;
170
- };
171
- var restraintRank = (policy) => {
172
- if (isAutoDeny(policy)) return 4;
173
- if (isAutoApprove(policy)) return 0;
174
- if (policy.mode === "push_only") return 3;
175
- return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
176
- };
177
- var strictestPolicy = (a, b) => restraintRank(b) > restraintRank(a) ? b : a;
178
- var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey, sessionId) => {
179
- if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey, sessionId);
180
- return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey, sessionId)).reduce(strictestPolicy);
181
- };
182
- var isModeStateDegraded = (state) => state?.degraded === true;
183
119
  var MODE_GRACE_MS = 60 * 1e3;
184
120
  var modeStateCacheFile = (apiKey) => {
185
121
  const hash = createHash("sha256").update(`mode:${apiKey}`).digest("hex").slice(0, 12);
@@ -220,7 +156,6 @@ var degradedFallback = (apiKey, authoritative = false) => {
220
156
  }
221
157
  return unknownMode();
222
158
  };
223
- var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
224
159
  var MODE_REUSE_MS = CACHE_TTL_MS;
225
160
  var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
226
161
  const last = readLastGoodMode(apiKey);
@@ -243,19 +178,7 @@ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
243
178
  if (!response.ok) {
244
179
  return degradedFallback(apiKey, response.status === 401 || response.status === 403);
245
180
  }
246
- const data = await response.json();
247
- const mode = data.override?.mode;
248
- const state = {
249
- mode: isApprovalMode(mode) ? mode : null,
250
- kill: data.kill === true,
251
- policyVersion: toPolicyVersion(data.policyVersion),
252
- relayUrl: typeof data.relayUrl === "string" && data.relayUrl.length > 0 ? data.relayUrl : null,
253
- trainingConsent: data.trainingConsent === true,
254
- // Validated rather than cast: a malformed contract must read as no contract,
255
- // never as one that silently matches nothing.
256
- scope: isScopeContract(data.scope) ? data.scope : null,
257
- degraded: false
258
- };
181
+ const state = modeStateFromResponse(await response.json());
259
182
  writeLastGoodMode(apiKey, state, sessionId);
260
183
  return state;
261
184
  } catch {
@@ -265,9 +188,11 @@ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
265
188
  var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
266
189
 
267
190
  // src/api.ts
191
+ import { randomUUID } from "crypto";
268
192
  installProxyDispatcher();
269
193
  var askUser = async (apiKey, params, timeoutMs = 15e3) => {
270
- const result = await callMcpTool(apiKey, "ask_user", { ...params, wait: false }, { maxRetries: 3, timeoutMs });
194
+ const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
195
+ const result = await callMcpTool(apiKey, "ask_user", { ...identified, wait: false }, { maxRetries: 3, timeoutMs });
271
196
  if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
272
197
  return result;
273
198
  };
@@ -286,7 +211,7 @@ var sendNotification = async (apiKey, params, timeoutMs = 15e3) => {
286
211
  await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: 3, timeoutMs });
287
212
  };
288
213
 
289
- // src/describe.ts
214
+ // ../hook-mapping/src/describe.ts
290
215
  import { isAbsolute, relative } from "path";
291
216
  var hookPrefixes = {
292
217
  Bash: (input) => `bash: ${input.command ?? "(no command)"}`,
@@ -455,6 +380,36 @@ var deriveBlocker = (mode, reason) => {
455
380
  return text.slice(0, DECISION_LINE_MAX);
456
381
  };
457
382
 
383
+ // ../hook-mapping/src/map.ts
384
+ import { basename } from "path";
385
+ var TASK_TITLE_MAX_LENGTH = 120;
386
+ var STOP_SUMMARY_MAX_LENGTH = 200;
387
+ var SESSION_END_ACTIONS = {
388
+ clear: "Session cleared",
389
+ logout: "Logged out",
390
+ resume: "Session suspended",
391
+ prompt_input_exit: "Session ended",
392
+ bypass_permissions_disabled: "Session ended",
393
+ other: "Session ended"
394
+ };
395
+ var countArray = (value) => Array.isArray(value) ? value.length : 0;
396
+ var summarizeFinalMessage = (message) => {
397
+ if (typeof message !== "string") return "Session ended";
398
+ const collapsed = message.replace(/\s+/g, " ").trim();
399
+ if (!collapsed) return "Session ended";
400
+ return collapsed.slice(0, STOP_SUMMARY_MAX_LENGTH);
401
+ };
402
+ var summarizeStop = (input) => {
403
+ const running = countArray(input.background_tasks);
404
+ if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
405
+ const crons = countArray(input.session_crons);
406
+ if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
407
+ return summarizeFinalMessage(input.last_assistant_message);
408
+ };
409
+ var sessionStartAction = (source) => source === "resume" ? "Session resumed" : "Session started";
410
+ var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACTIONS[reason] || "Session ended";
411
+ var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
412
+
458
413
  // src/pending.ts
459
414
  import { join as join2 } from "path";
460
415
  import { tmpdir as tmpdir2 } from "os";
@@ -503,7 +458,7 @@ var removePendingSession = (sessionId) => {
503
458
 
504
459
  // src/repo.ts
505
460
  import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
506
- import { basename, dirname, isAbsolute as isAbsolute2, join as join3, resolve } from "path";
461
+ import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join3, resolve } from "path";
507
462
  var MAX_PARENT_WALK = 64;
508
463
  var GIT_FILE_PREFIX = "gitdir:";
509
464
  var findGitDir = (startDir) => {
@@ -562,9 +517,9 @@ var deriveRepoKey = (cwd) => {
562
517
  const normalized = remote ? normalizeRepoRemote(remote) : void 0;
563
518
  if (normalized) return normalized;
564
519
  const root = gitDir.endsWith(".git") ? dirname(gitDir) : dir;
565
- return localRepoKey(basename(root));
520
+ return localRepoKey(basename2(root));
566
521
  }
567
- return localRepoKey(basename(resolve(dir)));
522
+ return localRepoKey(basename2(resolve(dir)));
568
523
  } catch {
569
524
  return void 0;
570
525
  }
@@ -869,7 +824,7 @@ var throttlePass = (key, windowMs) => {
869
824
  };
870
825
 
871
826
  // src/events.ts
872
- import { basename as basename2, join as join6 } from "path";
827
+ import { basename as basename3, join as join6 } from "path";
873
828
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
874
829
  import { tmpdir as tmpdir5 } from "os";
875
830
  var INTENT_DIR = join6(tmpdir5(), "pushary-intent");
@@ -1058,7 +1013,7 @@ var reportEvent = async (event, options = {}) => {
1058
1013
  var NON_GATED_MIRROR_TOOLS = /* @__PURE__ */ new Set(["TodoWrite"]);
1059
1014
  var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1060
1015
  try {
1061
- const projectName = basename2(input.cwd ?? process.cwd());
1016
+ const projectName = basename3(input.cwd ?? process.cwd());
1062
1017
  const action = describeToolCall(input.tool_name, input.tool_input, "event");
1063
1018
  const lookup = toPolicyLookup(input.tool_name, input.tool_input);
1064
1019
  const toolResult = input.tool_response ?? input.tool_result;
@@ -1110,13 +1065,12 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1110
1065
  return void 0;
1111
1066
  }
1112
1067
  };
1113
- var TASK_TITLE_MAX_LENGTH = 120;
1114
1068
  var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
1115
1069
  try {
1116
- const projectName = basename2(input.cwd ?? process.cwd());
1070
+ const projectName = basename3(input.cwd ?? process.cwd());
1117
1071
  const sessionKey = input.session_id || DEFAULT_SESSION;
1118
1072
  const titlesEnabled = process.env.PUSHARY_TASK_TITLES !== "off";
1119
- const taskTitle = titlesEnabled ? input.prompt?.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
1073
+ const taskTitle = titlesEnabled ? taskTitleFrom(input.prompt) : void 0;
1120
1074
  if (input.prompt) saveLastPrompt(sessionKey, input.prompt);
1121
1075
  const late = await reconcilePendingQuestions(sessionKey);
1122
1076
  let additionalContext;
@@ -1141,24 +1095,9 @@ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
1141
1095
  return void 0;
1142
1096
  }
1143
1097
  };
1144
- var STOP_SUMMARY_MAX_LENGTH = 200;
1145
- var summarizeFinalMessage = (message) => {
1146
- if (typeof message !== "string") return "Session ended";
1147
- const collapsed = message.replace(/\s+/g, " ").trim();
1148
- if (!collapsed) return "Session ended";
1149
- return collapsed.slice(0, STOP_SUMMARY_MAX_LENGTH);
1150
- };
1151
- var countArray = (value) => Array.isArray(value) ? value.length : 0;
1152
- var summarizeStop = (input) => {
1153
- const running = countArray(input.background_tasks);
1154
- if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
1155
- const crons = countArray(input.session_crons);
1156
- if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
1157
- return summarizeFinalMessage(input.last_assistant_message);
1158
- };
1159
1098
  var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
1160
1099
  try {
1161
- const projectName = basename2(input.cwd ?? process.cwd());
1100
+ const projectName = basename3(input.cwd ?? process.cwd());
1162
1101
  const sessionKey = input.session_id || DEFAULT_SESSION;
1163
1102
  const late = await reconcilePendingQuestions(sessionKey);
1164
1103
  const tasks = [
@@ -1194,8 +1133,8 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
1194
1133
  };
1195
1134
  var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
1196
1135
  try {
1197
- const projectName = basename2(input.cwd ?? process.cwd());
1198
- const action = input.source === "resume" ? "Session resumed" : "Session started";
1136
+ const projectName = basename3(input.cwd ?? process.cwd());
1137
+ const action = sessionStartAction(input.source);
1199
1138
  await reportEvent({
1200
1139
  event: "session_start",
1201
1140
  agentType: agent.type,
@@ -1206,18 +1145,10 @@ var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
1206
1145
  } catch {
1207
1146
  }
1208
1147
  };
1209
- var SESSION_END_ACTIONS = {
1210
- clear: "Session cleared",
1211
- logout: "Logged out",
1212
- resume: "Session suspended",
1213
- prompt_input_exit: "Session ended",
1214
- bypass_permissions_disabled: "Session ended",
1215
- other: "Session ended"
1216
- };
1217
1148
  var handleSessionEnd = async (input, agent = CLAUDE_CODE_AGENT) => {
1218
1149
  try {
1219
- const projectName = basename2(input.cwd ?? process.cwd());
1220
- const action = typeof input.reason === "string" && SESSION_END_ACTIONS[input.reason] || "Session ended";
1150
+ const projectName = basename3(input.cwd ?? process.cwd());
1151
+ const action = sessionEndAction(input.reason);
1221
1152
  const sessionKey = input.session_id || DEFAULT_SESSION;
1222
1153
  const late = await reconcilePendingQuestions(sessionKey);
1223
1154
  if (late.length > 0) {
@@ -1240,7 +1171,7 @@ var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set(["idle_prompt", "agent_nee
1240
1171
  var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
1241
1172
  var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
1242
1173
  try {
1243
- const projectName = basename2(input.cwd ?? process.cwd());
1174
+ const projectName = basename3(input.cwd ?? process.cwd());
1244
1175
  const notifType = input.notification_type ?? input.type ?? "";
1245
1176
  const sessionKey = input.session_id || DEFAULT_SESSION;
1246
1177
  if (NOTIFICATION_PUSH_TYPES.has(notifType)) {
@@ -1313,7 +1244,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1313
1244
  } catch {
1314
1245
  return;
1315
1246
  }
1316
- const projectName = basename2(input.cwd ?? process.cwd());
1247
+ const projectName = basename3(input.cwd ?? process.cwd());
1317
1248
  const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
1318
1249
  if (mode?.kill || mode?.mode === "terminal_only") return;
1319
1250
  const reason = describeStopFailure(input.error);
@@ -1332,10 +1263,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1332
1263
  export {
1333
1264
  policyRequestFor,
1334
1265
  getPolicy,
1335
- resolveAutoResolveOrigin,
1336
- resolvePolicy,
1337
- resolvePolicyAcross,
1338
- isModeStateDegraded,
1339
1266
  fetchModeState,
1340
1267
  fetchModeOverride,
1341
1268
  askUser,
@@ -0,0 +1,12 @@
1
+ // src/answer.ts
2
+ var denyReasonFrom = (value, note) => {
3
+ const trimmed = note?.trim();
4
+ if (trimmed) return `Denied from your phone: ${trimmed}`;
5
+ return value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
6
+ };
7
+ var isDeferAnswer = (value) => value === "defer";
8
+
9
+ export {
10
+ denyReasonFrom,
11
+ isDeferAnswer
12
+ };
@@ -1,5 +1,6 @@
1
- import { PolicyConfig, ApprovalMode, ToolPolicy, ScopeContract, AgentQuestion } from '@pushary/contracts';
2
- export { ApprovalMode, PolicyConfig, ToolPolicy } from '@pushary/contracts';
1
+ import { ReceiptMeta } from '@pushary/hook-mapping';
2
+ import { PolicyConfig, ApprovalMode, ModeState, AutoResolveOrigin, AgentQuestion } from '@pushary/contracts';
3
+ export { ApprovalMode, ModeState, PolicyConfig, ToolPolicy, resolvePolicy } from '@pushary/contracts';
3
4
 
4
5
  interface ToolInput {
5
6
  tool_name: string;
@@ -20,25 +21,8 @@ interface HookOutput {
20
21
  }
21
22
  declare const handlePreToolUse: (input: ToolInput) => Promise<HookOutput | undefined>;
22
23
 
23
- type ReceiptKind = 'edit' | 'write' | 'bash' | 'commit';
24
- interface ReceiptMeta {
25
- kind: ReceiptKind;
26
- target: string;
27
- ok: boolean;
28
- }
29
-
30
24
  declare const getPolicy: (apiKey: string, expectedVersion?: string | null, agent?: string, repoAware?: boolean, sessionId?: string) => Promise<PolicyConfig>;
31
- type AutoResolveOrigin = 'safe_readonly' | 'policy_timeout';
32
- declare const resolvePolicy: (config: PolicyConfig, toolName: string, modeOverride?: ApprovalMode | null, toolInput?: Record<string, unknown>, cwd?: string, repoKey?: string, sessionId?: string) => ToolPolicy;
33
- interface ModeState {
34
- readonly mode: ApprovalMode | null;
35
- readonly kill: boolean;
36
- readonly policyVersion: string | null;
37
- readonly relayUrl: string | null;
38
- readonly trainingConsent?: boolean;
39
- readonly scope?: ScopeContract | null;
40
- readonly degraded?: boolean;
41
- }
25
+
42
26
  declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
43
27
  declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
44
28
 
@@ -142,6 +126,7 @@ interface AskUserParams {
142
126
  actionBody?: string;
143
127
  scopePath?: string;
144
128
  toolUseId?: string;
129
+ requestId?: string;
145
130
  }
146
131
  interface AskUserResponse {
147
132
  correlationId: string;
@@ -166,4 +151,4 @@ declare const cancelQuestion: (apiKey: string, correlationId: string) => Promise
166
151
  declare const getApiKey: () => string;
167
152
  declare const getBaseUrl: () => string;
168
153
 
169
- export { type ModeState, askUser, cancelQuestion, fetchModeOverride, fetchModeState, getApiKey, getBaseUrl, getPolicy, handleNotification, handlePostToolUse, handlePreToolUse, handleStop, reportEvent, resolvePolicy, waitForAnswer };
154
+ export { askUser, cancelQuestion, fetchModeOverride, fetchModeState, getApiKey, getBaseUrl, getPolicy, handleNotification, handlePostToolUse, handlePreToolUse, handleStop, reportEvent, waitForAnswer };
package/dist/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-GFM3STWO.js";
4
- import "../chunk-64PXDATC.js";
5
- import "../chunk-PWTKKQQI.js";
3
+ } from "../chunk-LB6RSCLL.js";
4
+ import "../chunk-GM54KC62.js";
5
+ import "../chunk-HSGEEBBL.js";
6
6
  import "../chunk-VT4IVERX.js";
7
- import "../chunk-NJ7JT26G.js";
8
- import "../chunk-GXIFADGD.js";
7
+ import "../chunk-VGADRFG6.js";
8
+ import "../chunk-YHG74UFF.js";
9
9
  import {
10
10
  askUser,
11
11
  cancelQuestion,
@@ -16,20 +16,21 @@ import {
16
16
  handlePostToolUse,
17
17
  handleStop,
18
18
  reportEvent,
19
- resolvePolicy,
20
19
  waitForAnswer
21
- } from "../chunk-EXMUM226.js";
20
+ } from "../chunk-Y7BALRVB.js";
22
21
  import "../chunk-RN3NOEJF.js";
23
22
  import "../chunk-BSZYIAZL.js";
24
23
  import "../chunk-SAF6HGAA.js";
25
- import "../chunk-437T77TY.js";
24
+ import "../chunk-ES3FAX5X.js";
26
25
  import "../chunk-7QLSKOSU.js";
27
26
  import "../chunk-KZERVKTD.js";
28
27
  import {
29
28
  getApiKey,
30
29
  getBaseUrl
31
30
  } from "../chunk-2UMNXADU.js";
32
- import "../chunk-MQMPG5X4.js";
31
+ import {
32
+ resolvePolicy
33
+ } from "../chunk-G3LXA2ZP.js";
33
34
  export {
34
35
  askUser,
35
36
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.85.0",
3
+ "version": "0.85.1",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -80,7 +80,8 @@
80
80
  "build": "node scripts/bundle-plugin.mjs && tsup",
81
81
  "dev": "tsup --watch",
82
82
  "test": "node ../../scripts/ci/run-tests.mjs src bin",
83
- "typecheck": "tsc --noEmit"
83
+ "typecheck": "tsc --noEmit",
84
+ "gen:hooks-spec": "bun scripts/generate-hooks-spec.ts"
84
85
  },
85
86
  "dependencies": {
86
87
  "@inquirer/prompts": "^8.4.2",
@@ -91,6 +92,7 @@
91
92
  },
92
93
  "devDependencies": {
93
94
  "@pushary/contracts": "workspace:*",
95
+ "@pushary/hook-mapping": "workspace:*",
94
96
  "tsup": "^8.0.0",
95
97
  "typescript": "^5.0.0"
96
98
  }
@@ -1,66 +0,0 @@
1
- import {
2
- isModeStateDegraded,
3
- resolveAutoResolveOrigin,
4
- resolvePolicyAcross
5
- } from "./chunk-EXMUM226.js";
6
- import {
7
- evaluateScope,
8
- scopeChangeReason,
9
- scopeRequiresHuman,
10
- verdictForPolicy
11
- } from "./chunk-MQMPG5X4.js";
12
-
13
- // src/answer.ts
14
- var denyReasonFrom = (value, note) => {
15
- const trimmed = note?.trim();
16
- if (trimmed) return `Denied from your phone: ${trimmed}`;
17
- return value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
18
- };
19
- var isDeferAnswer = (value) => value === "defer";
20
-
21
- // src/gate.ts
22
- var KILL_REASON = "Stopped by user, this agent was halted from Pushary";
23
- var strictestScopeVerdict = (scope, paths) => {
24
- if (paths.length === 0) return evaluateScope(scope, void 0);
25
- let worst = { outcome: "in_scope" };
26
- for (const path of paths) {
27
- const verdict = evaluateScope(scope, path);
28
- if (verdict.outcome === "off_limits") return verdict;
29
- if (verdict.outcome === "out_of_scope") worst = verdict;
30
- }
31
- return worst;
32
- };
33
- var survivesDegraded = (config, toolName, toolInputs, cwd, repoKey, sessionId) => toolInputs.length === 1 && resolveAutoResolveOrigin(config, toolName, toolInputs[0], cwd, repoKey, sessionId) === "safe_readonly";
34
- var resolveGate = (input) => {
35
- const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
36
- if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
37
- const policy = resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey, sessionId);
38
- const verdict = verdictForPolicy(policy);
39
- const autoApproves = verdict === "allow";
40
- const autoDenies = verdict === "deny";
41
- if (autoDenies) return { kind: "deny", policy, reason: `Denied by policy for ${policy.tool}` };
42
- if (isModeStateDegraded(modeState)) {
43
- if (autoApproves) {
44
- return survivesDegraded(config, toolName, toolInputs, cwd, repoKey, sessionId) ? { kind: "allow", policy } : { kind: "defer", policy };
45
- }
46
- return { kind: "gate", policy };
47
- }
48
- const scopeVerdict = modeState.scope ? strictestScopeVerdict(modeState.scope, input.scopePaths ?? []) : { outcome: "no_contract" };
49
- if (autoApproves) {
50
- if (!scopeRequiresHuman(scopeVerdict)) return { kind: "allow", policy };
51
- }
52
- const scopeReason = scopeChangeReason(scopeVerdict);
53
- return {
54
- kind: "gate",
55
- policy,
56
- scopeReason,
57
- scopePath: scopeReason && "path" in scopeVerdict ? scopeVerdict.path : void 0
58
- };
59
- };
60
-
61
- export {
62
- denyReasonFrom,
63
- isDeferAnswer,
64
- KILL_REASON,
65
- resolveGate
66
- };
@@ -1,27 +0,0 @@
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
- };