@pushary/agent-hooks 0.21.1 → 0.23.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 (91) hide show
  1. package/data/cursor-plugin/README.md +5 -1
  2. package/data/cursor-plugin/mcp.json +1 -1
  3. package/data/cursor-plugin/scripts/pushary-gate.mjs +28 -2
  4. package/dist/bin/pushary-clean.js +1 -1
  5. package/dist/bin/pushary-codex-hook.js +29 -19
  6. package/dist/bin/pushary-codex.js +3 -5
  7. package/dist/bin/pushary-doctor.js +2 -2
  8. package/dist/bin/pushary-gemini-hook.js +25 -14
  9. package/dist/bin/pushary-hook.js +4 -4
  10. package/dist/bin/pushary-post-hook.js +2 -3
  11. package/dist/bin/pushary-prompt-hook.js +2 -3
  12. package/dist/bin/pushary-setup.js +24 -2
  13. package/dist/bin/pushary-stop-hook.js +2 -3
  14. package/dist/{chunk-FXYTRZHC.js → chunk-FGZZRCOG.js} +27 -10
  15. package/dist/{chunk-BHAMEPOP.js → chunk-JPRRYC5X.js} +5 -0
  16. package/dist/{chunk-N7VXDBQU.js → chunk-KQYIHZ5E.js} +3 -1
  17. package/dist/chunk-M4GDBGXF.js +824 -0
  18. package/dist/{chunk-USUCPCUC.js → chunk-WPVL6VIT.js} +5 -1
  19. package/dist/src/index.d.ts +4 -0
  20. package/dist/src/index.js +8 -10
  21. package/package.json +1 -1
  22. package/dist/chunk-22CV7V7A.js +0 -38
  23. package/dist/chunk-2BE2IPMO.js +0 -315
  24. package/dist/chunk-2CRLBMOX.js +0 -244
  25. package/dist/chunk-2HMNOZPY.js +0 -109
  26. package/dist/chunk-2I6DLXJN.js +0 -219
  27. package/dist/chunk-2Q6XPZ4Q.js +0 -395
  28. package/dist/chunk-3MIR7ODJ.js +0 -112
  29. package/dist/chunk-3QZESA66.js +0 -222
  30. package/dist/chunk-4Z4MB37G.js +0 -96
  31. package/dist/chunk-5GFUI5N6.js +0 -132
  32. package/dist/chunk-5JEDLXEC.js +0 -99
  33. package/dist/chunk-5QHQVXWN.js +0 -174
  34. package/dist/chunk-6A3WCJO2.js +0 -416
  35. package/dist/chunk-6QWFMVCD.js +0 -328
  36. package/dist/chunk-6SRXMZAN.js +0 -99
  37. package/dist/chunk-7PTU7TGE.js +0 -96
  38. package/dist/chunk-AB4KX4XT.js +0 -109
  39. package/dist/chunk-AC4UYAGX.js +0 -136
  40. package/dist/chunk-ACE77TKQ.js +0 -328
  41. package/dist/chunk-BYNYYZWW.js +0 -412
  42. package/dist/chunk-C5TFTNHG.js +0 -244
  43. package/dist/chunk-CH53PBQN.js +0 -265
  44. package/dist/chunk-DF3BM6BF.js +0 -195
  45. package/dist/chunk-DKZVKEOW.js +0 -175
  46. package/dist/chunk-EMPL27ZV.js +0 -96
  47. package/dist/chunk-EQJXMEIR.js +0 -175
  48. package/dist/chunk-GTJGZBTQ.js +0 -328
  49. package/dist/chunk-HJMFYDWY.js +0 -222
  50. package/dist/chunk-HRQEECB6.js +0 -315
  51. package/dist/chunk-HWEMAAOY.js +0 -370
  52. package/dist/chunk-IBWCHA5M.js +0 -10
  53. package/dist/chunk-KTM4ZQ3W.js +0 -96
  54. package/dist/chunk-KTP2EPVB.js +0 -27
  55. package/dist/chunk-KYARP7KP.js +0 -97
  56. package/dist/chunk-M2N5DYWN.js +0 -247
  57. package/dist/chunk-M5F2L4KF.js +0 -177
  58. package/dist/chunk-M6S3M6E3.js +0 -244
  59. package/dist/chunk-O6A5RHWY.js +0 -122
  60. package/dist/chunk-ODUXELPM.js +0 -219
  61. package/dist/chunk-OF5WIOYS.js +0 -129
  62. package/dist/chunk-OP4QAN23.js +0 -99
  63. package/dist/chunk-P4JH2Q7Z.js +0 -102
  64. package/dist/chunk-PMD5JSV3.js +0 -242
  65. package/dist/chunk-PSSHNBEL.js +0 -177
  66. package/dist/chunk-QEPRH7JB.js +0 -315
  67. package/dist/chunk-QRXWPZKN.js +0 -308
  68. package/dist/chunk-QY4L6XHN.js +0 -384
  69. package/dist/chunk-RNWPCELY.js +0 -176
  70. package/dist/chunk-RSHN2AQ7.js +0 -29
  71. package/dist/chunk-RZLVE57X.js +0 -38
  72. package/dist/chunk-SDCIKREA.js +0 -241
  73. package/dist/chunk-SDREQWNI.js +0 -175
  74. package/dist/chunk-SH26ZOHU.js +0 -159
  75. package/dist/chunk-TRLBBLSS.js +0 -176
  76. package/dist/chunk-U6OXZPYC.js +0 -384
  77. package/dist/chunk-UIBTFZUD.js +0 -314
  78. package/dist/chunk-V4GLWPD7.js +0 -174
  79. package/dist/chunk-VC6U3QGF.js +0 -142
  80. package/dist/chunk-VIST7ACL.js +0 -88
  81. package/dist/chunk-VUNL35KE.js +0 -16
  82. package/dist/chunk-VWBNI4SC.js +0 -174
  83. package/dist/chunk-W5KRWUNE.js +0 -262
  84. package/dist/chunk-WCGKLHCL.js +0 -154
  85. package/dist/chunk-WNXGIEX7.js +0 -219
  86. package/dist/chunk-XPVSZIA3.js +0 -315
  87. package/dist/chunk-Y633ZIJF.js +0 -384
  88. package/dist/chunk-YCL4GUFR.js +0 -222
  89. package/dist/chunk-YTMKB44I.js +0 -220
  90. package/dist/chunk-ZWBS3T7Q.js +0 -244
  91. package/dist/pushary-mode-T7XOPI6Z.js +0 -83
@@ -48,7 +48,7 @@ Add this to `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` for ever
48
48
  "pushary": {
49
49
  "type": "http",
50
50
  "url": "https://pushary.com/api/mcp/mcp",
51
- "headers": { "Authorization": "Bearer ${PUSHARY_API_KEY}" }
51
+ "headers": { "Authorization": "Bearer ${env:PUSHARY_API_KEY}" }
52
52
  }
53
53
  }
54
54
  }
@@ -67,6 +67,10 @@ source ~/.zshrc
67
67
 
68
68
  Install the Pushary app on your phone (or turn on web push) so the agent can reach you.
69
69
 
70
+ ## Troubleshooting
71
+
72
+ macOS GUI apps do not read `.zshrc`. If Settings > MCP shows pushary in red after you set the env var, launch Cursor from a terminal where `PUSHARY_API_KEY` is exported, or set the variable system-wide so GUI apps see it.
73
+
70
74
  ## What is in the plugin
71
75
 
72
76
  | Part | File | What it does |
@@ -4,7 +4,7 @@
4
4
  "type": "http",
5
5
  "url": "https://pushary.com/api/mcp/mcp",
6
6
  "headers": {
7
- "Authorization": "Bearer ${PUSHARY_API_KEY}"
7
+ "Authorization": "Bearer ${env:PUSHARY_API_KEY}"
8
8
  }
9
9
  }
10
10
  }
@@ -231,6 +231,25 @@ const fetchModeState = async (apiKey, sessionId) => {
231
231
  }
232
232
  }
233
233
 
234
+ // ── action body capture + redaction (inlined mirror of describe.ts, since this
235
+ // dependency-free hook cannot import the workspace) ──────────────────────────────
236
+ const ACTION_BODY_MAX = 4000
237
+ const ACTION_BODY_TRUNCATION_MARKER = '\n… [truncated]'
238
+ const REDACTION_RULES = [
239
+ [/\bsk-[A-Za-z0-9]{20,}\b/g, '[redacted]'],
240
+ [/\bpk_(?:live|test)_[A-Za-z0-9]+\b/g, '[redacted]'],
241
+ [/\brk_[A-Za-z0-9]+\b/g, '[redacted]'],
242
+ [/\bAKIA[0-9A-Z]{16}\b/g, '[redacted]'],
243
+ [/\bbearer\s+[A-Za-z0-9._-]+/gi, 'bearer [redacted]'],
244
+ [/\bauthorization:\s*\S+/gi, 'authorization: [redacted]'],
245
+ [/((?:secret|token|password|passwd|api[_-]?key|private[_-]?key)\s*[=:]\s*)(\S+)/gi, '$1[redacted]'],
246
+ [/[A-Za-z0-9+/]{40,}={0,2}/g, '[redacted]'],
247
+ ]
248
+ const redactSecrets = (text) => REDACTION_RULES.reduce((acc, [pattern, replacement]) => acc.replace(pattern, replacement), text)
249
+ const capActionBody = (text) =>
250
+ text.length <= ACTION_BODY_MAX ? text : `${text.slice(0, ACTION_BODY_MAX - ACTION_BODY_TRUNCATION_MARKER.length)}${ACTION_BODY_TRUNCATION_MARKER}`
251
+ const deriveActionBody = (command) => capActionBody(redactSecrets(command))
252
+
234
253
  // ── ask / wait ───────────────────────────────────────────────────────────────
235
254
  const askArgs = (command, project, ident) => ({
236
255
  question: `Allow this command?\n\n${command}`,
@@ -240,6 +259,7 @@ const askArgs = (command, project, ident) => ({
240
259
  sessionId: ident.sessionId,
241
260
  machineId: ident.machineId,
242
261
  toolName: 'Bash',
262
+ actionBody: deriveActionBody(command),
243
263
  wait: false,
244
264
  })
245
265
 
@@ -278,7 +298,10 @@ const handlePushOnly = async (apiKey, command, project, ident, timeoutSeconds, t
278
298
  const realMs = Math.max(timeoutSeconds, 1) * 1000
279
299
  const cap = Math.min(realMs, MAX_BLOCK_MS)
280
300
  const answer = await pollForAnswer(apiKey, asked.correlationId, Date.now() + cap)
281
- if (answer.answered) return answer.value === 'yes' ? ALLOW : deny(DENIED)
301
+ if (answer.answered) {
302
+ if (answer.value === 'defer') return ask()
303
+ return answer.value === 'yes' ? ALLOW : deny(DENIED)
304
+ }
282
305
 
283
306
  // If Cursor's hook limit cut us off before the configured timeout, hand off to
284
307
  // Cursor's own prompt rather than misapplying the policy's timeout action.
@@ -298,7 +321,10 @@ const handlePushFirst = async (apiKey, command, project, ident, pushFirstSeconds
298
321
 
299
322
  const cap = Math.min(Math.max(pushFirstSeconds, 1) * 1000, MAX_BLOCK_MS)
300
323
  const answer = await pollForAnswer(apiKey, asked.correlationId, Date.now() + cap)
301
- if (answer.answered) return answer.value === 'yes' ? ALLOW : deny(DENIED)
324
+ if (answer.answered) {
325
+ if (answer.value === 'defer') return ask()
326
+ return answer.value === 'yes' ? ALLOW : deny(DENIED)
327
+ }
302
328
  return ask('Sent to your phone via Pushary — you can also approve here.')
303
329
  }
304
330
 
@@ -8,7 +8,7 @@ import {
8
8
  removeCodexHooks,
9
9
  removeGeminiSettings,
10
10
  removeInstructionBlock
11
- } from "../chunk-BHAMEPOP.js";
11
+ } from "../chunk-JPRRYC5X.js";
12
12
 
13
13
  // bin/pushary-clean.ts
14
14
  import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
@@ -1,36 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- denyReasonFrom
4
- } from "../chunk-N7VXDBQU.js";
3
+ denyReasonFrom,
4
+ isDeferAnswer
5
+ } from "../chunk-KQYIHZ5E.js";
5
6
  import {
6
7
  CODEX_AGENT,
8
+ DEFAULT_SESSION,
9
+ askUser,
7
10
  codexAllow,
8
11
  codexDeny,
9
12
  codexPass,
13
+ deriveActionBody,
14
+ deriveBlocker,
15
+ deriveToolTarget,
10
16
  describeApplyPatch,
17
+ describeToolCall,
18
+ fetchModeState,
19
+ getMachineId,
20
+ getPolicy,
11
21
  handlePostToolUse,
12
22
  handleStop,
13
23
  handleUserPrompt,
14
24
  permissionTimeoutDecision,
15
25
  preToolUseTimeoutDecision,
26
+ readLastPrompt,
16
27
  reportEvent,
17
- toCodexWire,
18
- toPolicyLookup
19
- } from "../chunk-BYNYYZWW.js";
20
- import {
21
- DEFAULT_SESSION,
22
- askUser,
23
- deriveToolTarget,
24
- describeToolCall,
25
- fetchModeState,
26
- getMachineId,
27
- getPolicy,
28
28
  resolvePolicy,
29
29
  savePendingQuestion,
30
30
  sendNotification,
31
+ toCodexWire,
32
+ toPolicyLookup,
31
33
  waitForAnswer
32
- } from "../chunk-6QWFMVCD.js";
33
- import "../chunk-USUCPCUC.js";
34
+ } from "../chunk-M4GDBGXF.js";
35
+ import {
36
+ DECISION_LINE_MAX
37
+ } from "../chunk-WPVL6VIT.js";
34
38
  import "../chunk-DWED7BS3.js";
35
39
  import {
36
40
  getApiKey
@@ -107,7 +111,7 @@ var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3
107
111
  };
108
112
  var agentNameFor = (input) => `Codex - ${basename(input.cwd ?? process.cwd())}`;
109
113
  var describeFor = (input, lookup) => input.tool_name === "apply_patch" ? describeApplyPatch(input.tool_input?.command) ?? describeToolCall(lookup.tool, lookup.input, "hook") : describeToolCall(lookup.tool, lookup.input, "hook");
110
- var pushQuestion = async (apiKey, input, lookup, waitSeconds) => {
114
+ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
111
115
  const projectName = basename(input.cwd ?? process.cwd());
112
116
  const description = describeFor(input, lookup);
113
117
  const result = await askUser(apiKey, {
@@ -118,7 +122,11 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds) => {
118
122
  sessionId: input.session_id,
119
123
  machineId: getMachineId(),
120
124
  toolName: lookup.tool,
121
- toolTarget: deriveToolTarget(lookup.tool, lookup.input)
125
+ toolTarget: deriveToolTarget(lookup.tool, lookup.input),
126
+ intent: readLastPrompt(input.session_id || DEFAULT_SESSION),
127
+ action: description.slice(0, DECISION_LINE_MAX),
128
+ blocker: deriveBlocker(mode),
129
+ actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {})
122
130
  });
123
131
  const deadline = Math.min(
124
132
  Date.now() + Math.min(waitSeconds, MAX_WAIT_SECONDS) * 1e3,
@@ -157,8 +165,9 @@ var decidePermissionRequest = async (input) => {
157
165
  return codexPass();
158
166
  }
159
167
  const waitSeconds = toolPolicy.mode === "push_first" ? toolPolicy.pushFirstSeconds : toolPolicy.timeoutSeconds;
160
- const { answer, correlationId } = await pushQuestion(apiKey, input, lookup, waitSeconds);
168
+ const { answer, correlationId } = await pushQuestion(apiKey, input, lookup, waitSeconds, toolPolicy.mode);
161
169
  if (answer.answered) {
170
+ if (isDeferAnswer(answer.value)) return codexPass();
162
171
  if (answer.value === "yes") {
163
172
  if (toolPolicy.mode === "push_only") markApproved(input.tool_use_id);
164
173
  return codexAllow();
@@ -190,12 +199,13 @@ var decidePreToolUse = async (input) => {
190
199
  if (consumeApproval(input.tool_use_id)) return codexPass();
191
200
  let pushed;
192
201
  try {
193
- pushed = await pushQuestion(apiKey, input, lookup, toolPolicy.timeoutSeconds);
202
+ pushed = await pushQuestion(apiKey, input, lookup, toolPolicy.timeoutSeconds, "push_only");
194
203
  } catch {
195
204
  return preToolUseTimeoutDecision(toolPolicy.timeoutAction, "Push notification failed, denying per policy");
196
205
  }
197
206
  const { answer, correlationId } = pushed;
198
207
  if (answer.answered) {
208
+ if (isDeferAnswer(answer.value)) return codexPass();
199
209
  return answer.value === "yes" ? codexPass() : codexDeny(denyReasonFrom(answer.value));
200
210
  }
201
211
  savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
@@ -1,13 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- reportEvent
4
- } from "../chunk-BYNYYZWW.js";
5
2
  import {
6
3
  askUser,
7
4
  getMachineId,
5
+ reportEvent,
8
6
  waitForAnswer
9
- } from "../chunk-6QWFMVCD.js";
10
- import "../chunk-USUCPCUC.js";
7
+ } from "../chunk-M4GDBGXF.js";
8
+ import "../chunk-WPVL6VIT.js";
11
9
  import "../chunk-DWED7BS3.js";
12
10
  import {
13
11
  getApiKey
@@ -8,7 +8,7 @@ import {
8
8
  missingCodexHookEvents,
9
9
  missingGeminiHookEvents,
10
10
  untrustedCodexHookEvents
11
- } from "../chunk-BHAMEPOP.js";
11
+ } from "../chunk-JPRRYC5X.js";
12
12
  import {
13
13
  callMcpTool,
14
14
  sendMcpRequest
@@ -283,7 +283,7 @@ var main = async () => {
283
283
  const cursorAuth = cursorServers.pushary?.headers?.Authorization;
284
284
  if (!cursorAuth) {
285
285
  check(false, "Cursor: API key linked", "no Authorization in plugin mcp.json \u2014 re-run setup");
286
- } else if (cursorAuth.includes("${PUSHARY_API_KEY}")) {
286
+ } else if (cursorAuth.includes("PUSHARY_API_KEY")) {
287
287
  check(false, "Cursor: API key linked", "plugin relies on $PUSHARY_API_KEY; re-run setup to embed the key so the gate works when Cursor is launched from the GUI");
288
288
  } else {
289
289
  check(true, "Cursor: API key linked", "embedded in plugin mcp.json");
@@ -1,27 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- denyReasonFrom
4
- } from "../chunk-N7VXDBQU.js";
5
- import {
6
- handlePostToolUse,
7
- handleStop,
8
- handleUserPrompt,
9
- reportEvent
10
- } from "../chunk-BYNYYZWW.js";
3
+ denyReasonFrom,
4
+ isDeferAnswer
5
+ } from "../chunk-KQYIHZ5E.js";
11
6
  import {
12
7
  DEFAULT_SESSION,
13
8
  askUser,
9
+ deriveActionBody,
10
+ deriveBlocker,
14
11
  deriveToolTarget,
15
12
  describeToolCall,
16
13
  fetchModeState,
17
14
  getMachineId,
18
15
  getPolicy,
16
+ handlePostToolUse,
17
+ handleStop,
18
+ handleUserPrompt,
19
+ readLastPrompt,
20
+ readLastUserPrompt,
21
+ reportEvent,
19
22
  resolvePolicy,
20
23
  savePendingQuestion,
21
24
  sendNotification,
22
25
  waitForAnswer
23
- } from "../chunk-6QWFMVCD.js";
24
- import "../chunk-USUCPCUC.js";
26
+ } from "../chunk-M4GDBGXF.js";
27
+ import {
28
+ DECISION_LINE_MAX
29
+ } from "../chunk-WPVL6VIT.js";
25
30
  import "../chunk-DWED7BS3.js";
26
31
  import {
27
32
  getApiKey
@@ -86,7 +91,7 @@ var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3
86
91
  return { answered: false };
87
92
  };
88
93
  var agentNameFor = (input) => `${GEMINI_AGENT.label} - ${basename(input.cwd ?? process.cwd())}`;
89
- var pushQuestion = async (apiKey, input, lookup, waitSeconds, pollInterval = 2e3) => {
94
+ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode, pollInterval = 2e3) => {
90
95
  const projectName = basename(input.cwd ?? process.cwd());
91
96
  const description = describeToolCall(lookup.tool, lookup.input, "hook");
92
97
  const result = await askUser(apiKey, {
@@ -97,7 +102,11 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds, pollInterval = 2e3
97
102
  sessionId: input.session_id,
98
103
  machineId: getMachineId(),
99
104
  toolName: lookup.tool,
100
- toolTarget: deriveToolTarget(lookup.tool, lookup.input)
105
+ toolTarget: deriveToolTarget(lookup.tool, lookup.input),
106
+ intent: readLastPrompt(input.session_id || DEFAULT_SESSION) ?? (input.transcript_path ? readLastUserPrompt(input.transcript_path) : void 0),
107
+ action: description.slice(0, DECISION_LINE_MAX),
108
+ blocker: deriveBlocker(mode),
109
+ actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {})
101
110
  });
102
111
  const deadline = Math.min(
103
112
  Date.now() + Math.min(waitSeconds, MAX_WAIT_SECONDS) * 1e3,
@@ -121,12 +130,13 @@ var notifyApprovalNeeded = async (apiKey, input, lookup) => {
121
130
  var handlePushOnly = async (apiKey, input, lookup, timeoutSeconds, timeoutAction) => {
122
131
  let pushed;
123
132
  try {
124
- pushed = await pushQuestion(apiKey, input, lookup, timeoutSeconds);
133
+ pushed = await pushQuestion(apiKey, input, lookup, timeoutSeconds, "push_only");
125
134
  } catch {
126
135
  return geminiTimeoutDecision(timeoutAction, "Push notification failed, denying per policy");
127
136
  }
128
137
  const { answer, correlationId } = pushed;
129
138
  if (answer.answered) {
139
+ if (isDeferAnswer(answer.value)) return geminiPass();
130
140
  return answer.value === "yes" ? geminiAllow() : geminiDeny(denyReasonFrom(answer.value));
131
141
  }
132
142
  savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
@@ -135,12 +145,13 @@ var handlePushOnly = async (apiKey, input, lookup, timeoutSeconds, timeoutAction
135
145
  var handlePushFirst = async (apiKey, input, lookup, pushFirstSeconds) => {
136
146
  let pushed;
137
147
  try {
138
- pushed = await pushQuestion(apiKey, input, lookup, pushFirstSeconds, 1500);
148
+ pushed = await pushQuestion(apiKey, input, lookup, pushFirstSeconds, "push_first", 1500);
139
149
  } catch {
140
150
  return geminiPass();
141
151
  }
142
152
  const { answer, correlationId } = pushed;
143
153
  if (answer.answered) {
154
+ if (isDeferAnswer(answer.value)) return geminiPass();
144
155
  return answer.value === "yes" ? geminiAllow() : geminiDeny(denyReasonFrom(answer.value));
145
156
  }
146
157
  savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-M5F2L4KF.js";
5
- import "../chunk-N7VXDBQU.js";
6
- import "../chunk-6QWFMVCD.js";
7
- import "../chunk-USUCPCUC.js";
4
+ } from "../chunk-FGZZRCOG.js";
5
+ import "../chunk-KQYIHZ5E.js";
6
+ import "../chunk-M4GDBGXF.js";
7
+ import "../chunk-WPVL6VIT.js";
8
8
  import "../chunk-DWED7BS3.js";
9
9
  import "../chunk-NKXSILEW.js";
10
10
 
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-BYNYYZWW.js";
5
- import "../chunk-6QWFMVCD.js";
6
- import "../chunk-USUCPCUC.js";
4
+ } from "../chunk-M4GDBGXF.js";
5
+ import "../chunk-WPVL6VIT.js";
7
6
  import "../chunk-DWED7BS3.js";
8
7
  import "../chunk-NKXSILEW.js";
9
8
 
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleUserPrompt
4
- } from "../chunk-BYNYYZWW.js";
5
- import "../chunk-6QWFMVCD.js";
6
- import "../chunk-USUCPCUC.js";
4
+ } from "../chunk-M4GDBGXF.js";
5
+ import "../chunk-WPVL6VIT.js";
7
6
  import "../chunk-DWED7BS3.js";
8
7
  import "../chunk-NKXSILEW.js";
9
8
 
@@ -13,11 +13,12 @@ import {
13
13
  execNpm,
14
14
  npmErrorMessage,
15
15
  renderAgentInstructions,
16
+ renderProjectAgentInstructions,
16
17
  writeInstructionBlock
17
- } from "../chunk-BHAMEPOP.js";
18
+ } from "../chunk-JPRRYC5X.js";
18
19
  import {
19
20
  isValidApiKey
20
- } from "../chunk-USUCPCUC.js";
21
+ } from "../chunk-WPVL6VIT.js";
21
22
 
22
23
  // bin/pushary-setup.ts
23
24
  import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync, chmodSync } from "fs";
@@ -857,6 +858,26 @@ var AGENT_SETUP = {
857
858
  cursor: setupCursor,
858
859
  custom: setupCustom
859
860
  };
861
+ var PROJECT_INSTRUCTION_TARGETS = {
862
+ claude_code: { file: "CLAUDE.md", label: "Claude Code" },
863
+ codex: { file: "AGENTS.md", label: "Codex" }
864
+ };
865
+ var offerProjectInstructions = async (agents) => {
866
+ const targets = agents.map((agent) => PROJECT_INSTRUCTION_TARGETS[agent]).filter((target) => target !== void 0);
867
+ if (targets.length === 0 || !process.stdout.isTTY) return;
868
+ console.log();
869
+ const wanted = await confirm({
870
+ message: "Add Pushary instructions to this project's CLAUDE.md/AGENTS.md so your whole team's agents can use phone approvals?",
871
+ default: false
872
+ });
873
+ if (!wanted) return;
874
+ for (const target of targets) {
875
+ await spinner(`Writing managed block to ${join(process.cwd(), target.file)}`, async () => {
876
+ writeInstructionBlock(join(process.cwd(), target.file), renderProjectAgentInstructions(target.label));
877
+ }, { optional: true });
878
+ }
879
+ console.log(` ${dim2("Commit the file to share it. Teammates without a key fall back to the terminal.")}`);
880
+ };
860
881
  var main = async () => {
861
882
  const version = getPackageVersion();
862
883
  console.log();
@@ -952,6 +973,7 @@ var main = async () => {
952
973
  console.log();
953
974
  console.log(` ${yellow2("!")} Failed: ${failed.join(", ")} ${dim2("(others completed successfully)")}`);
954
975
  }
976
+ await offerProjectInstructions(agents);
955
977
  }
956
978
  if (connectMode !== "app") {
957
979
  const skipPhone = process.argv.includes("--skip-phone");
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-BYNYYZWW.js";
5
- import "../chunk-6QWFMVCD.js";
6
- import "../chunk-USUCPCUC.js";
4
+ } from "../chunk-M4GDBGXF.js";
5
+ import "../chunk-WPVL6VIT.js";
7
6
  import "../chunk-DWED7BS3.js";
8
7
  import "../chunk-NKXSILEW.js";
9
8
 
@@ -1,19 +1,25 @@
1
1
  import {
2
- denyReasonFrom
3
- } from "./chunk-N7VXDBQU.js";
2
+ denyReasonFrom,
3
+ isDeferAnswer
4
+ } from "./chunk-KQYIHZ5E.js";
4
5
  import {
5
6
  DEFAULT_SESSION,
6
7
  askUser,
8
+ deriveAction,
9
+ deriveActionBody,
10
+ deriveBlocker,
7
11
  deriveToolTarget,
8
12
  describeToolCall,
9
13
  fetchModeState,
10
14
  getMachineId,
11
15
  getPolicy,
16
+ readLastPrompt,
17
+ readLastUserPrompt,
12
18
  resolvePolicy,
13
19
  savePendingQuestion,
14
20
  sendNotification,
15
21
  waitForAnswer
16
- } from "./chunk-ACE77TKQ.js";
22
+ } from "./chunk-M4GDBGXF.js";
17
23
  import {
18
24
  getApiKey
19
25
  } from "./chunk-NKXSILEW.js";
@@ -58,7 +64,7 @@ var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3
58
64
  }
59
65
  return { answered: false };
60
66
  };
61
- var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, timeoutAction, sessionId, machineId, toolName, toolTarget) => {
67
+ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, timeoutAction, sessionId, machineId, toolName, toolTarget, decision) => {
62
68
  let result;
63
69
  try {
64
70
  result = await askUser(apiKey, {
@@ -69,7 +75,8 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
69
75
  sessionId,
70
76
  machineId,
71
77
  toolName,
72
- toolTarget
78
+ toolTarget,
79
+ ...decision
73
80
  });
74
81
  } catch {
75
82
  switch (timeoutAction) {
@@ -84,6 +91,7 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
84
91
  const deadline = Date.now() + timeoutSeconds * 1e3;
85
92
  const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
86
93
  if (answer.answered) {
94
+ if (isDeferAnswer(answer.value)) return ask("Handling on your machine");
87
95
  return answer.value === "yes" ? allow() : deny(denyReasonFrom(answer.value));
88
96
  }
89
97
  switch (timeoutAction) {
@@ -98,7 +106,7 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
98
106
  var handleTerminalOnly = () => {
99
107
  return ask();
100
108
  };
101
- var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds, sessionId, machineId, toolName, toolTarget) => {
109
+ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds, sessionId, machineId, toolName, toolTarget, decision) => {
102
110
  let result;
103
111
  try {
104
112
  result = await askUser(apiKey, {
@@ -109,7 +117,8 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
109
117
  sessionId,
110
118
  machineId,
111
119
  toolName,
112
- toolTarget
120
+ toolTarget,
121
+ ...decision
113
122
  });
114
123
  } catch {
115
124
  return ask("Push notification failed, asking in terminal");
@@ -117,6 +126,7 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
117
126
  const deadline = Date.now() + pushFirstSeconds * 1e3;
118
127
  const answer = await pollForAnswer(apiKey, result.correlationId, deadline, 1500);
119
128
  if (answer.answered) {
129
+ if (isDeferAnswer(answer.value)) return ask("Handling on your machine");
120
130
  return answer.value === "yes" ? allow() : deny(denyReasonFrom(answer.value));
121
131
  }
122
132
  savePendingQuestion(sessionId || DEFAULT_SESSION, result.correlationId);
@@ -155,17 +165,24 @@ var handlePreToolUse = async (input) => {
155
165
  const sessionId = input.session_id;
156
166
  const machineId = getMachineId();
157
167
  const toolTarget = deriveToolTarget(input.tool_name, input.tool_input);
168
+ const intent = readLastPrompt(sessionId ?? DEFAULT_SESSION) ?? (input.transcript_path ? readLastUserPrompt(input.transcript_path) : void 0);
169
+ const decision = {
170
+ intent,
171
+ action: deriveAction(input.tool_name, input.tool_input),
172
+ blocker: deriveBlocker(toolPolicy.mode),
173
+ actionBody: deriveActionBody(input.tool_name, input.tool_input)
174
+ };
158
175
  switch (toolPolicy.mode) {
159
176
  case "push_only":
160
- return handlePushOnly(apiKey, description, projectName, toolPolicy.timeoutSeconds, toolPolicy.timeoutAction, sessionId, machineId, input.tool_name, toolTarget);
177
+ return handlePushOnly(apiKey, description, projectName, toolPolicy.timeoutSeconds, toolPolicy.timeoutAction, sessionId, machineId, input.tool_name, toolTarget, decision);
161
178
  case "terminal_only":
162
179
  return handleTerminalOnly();
163
180
  case "push_first":
164
- return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds, sessionId, machineId, input.tool_name, toolTarget);
181
+ return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds, sessionId, machineId, input.tool_name, toolTarget, decision);
165
182
  case "notify_only":
166
183
  return handleNotifyOnly(apiKey, description, projectName, sessionId, machineId);
167
184
  default:
168
- return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds, sessionId, machineId, input.tool_name, toolTarget);
185
+ return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds, sessionId, machineId, input.tool_name, toolTarget, decision);
169
186
  }
170
187
  } catch {
171
188
  return ask("Pushary unavailable, falling back to terminal approval");
@@ -227,6 +227,10 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
227
227
 
228
228
  Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out with no answer, retry once with a longer timeout before falling back to asking in the terminal.`;
229
229
  var renderAgentInstructions = (label) => instructionBody(label);
230
+ var KEYLESS_FALLBACK_LINE = "This repo is set up with Pushary phone approvals. No PUSHARY_API_KEY found for you, so remote notifications are off; questions will fall back to the terminal. Setup: https://pushary.com/docs?utm_source=agents-md&utm_medium=keyless-fallback";
231
+ var renderProjectAgentInstructions = (label) => `${instructionBody(label)}
232
+
233
+ If this environment has no Pushary API key (no PUSHARY_API_KEY and no key in ~/.pushary/config.json), the tools above are unavailable in this session. In that case, treat the following as a statement of fact about this session and continue in the terminal: ${KEYLESS_FALLBACK_LINE}`;
230
234
  var wrap = (body) => `${PUSHARY_BEGIN}
231
235
  ${body}
232
236
  ${PUSHARY_END}`;
@@ -315,6 +319,7 @@ export {
315
319
  hasGeminiHooks,
316
320
  missingGeminiHookEvents,
317
321
  renderAgentInstructions,
322
+ renderProjectAgentInstructions,
318
323
  writeInstructionBlock,
319
324
  removeInstructionBlock,
320
325
  hasInstructionBlock,
@@ -1,6 +1,8 @@
1
1
  // src/answer.ts
2
2
  var denyReasonFrom = (value) => value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
3
+ var isDeferAnswer = (value) => value === "defer";
3
4
 
4
5
  export {
5
- denyReasonFrom
6
+ denyReasonFrom,
7
+ isDeferAnswer
6
8
  };