@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.
- package/data/cursor-plugin/README.md +5 -1
- package/data/cursor-plugin/mcp.json +1 -1
- package/data/cursor-plugin/scripts/pushary-gate.mjs +28 -2
- package/dist/bin/pushary-clean.js +1 -1
- package/dist/bin/pushary-codex-hook.js +29 -19
- package/dist/bin/pushary-codex.js +3 -5
- package/dist/bin/pushary-doctor.js +2 -2
- package/dist/bin/pushary-gemini-hook.js +25 -14
- package/dist/bin/pushary-hook.js +4 -4
- package/dist/bin/pushary-post-hook.js +2 -3
- package/dist/bin/pushary-prompt-hook.js +2 -3
- package/dist/bin/pushary-setup.js +24 -2
- package/dist/bin/pushary-stop-hook.js +2 -3
- package/dist/{chunk-FXYTRZHC.js → chunk-FGZZRCOG.js} +27 -10
- package/dist/{chunk-BHAMEPOP.js → chunk-JPRRYC5X.js} +5 -0
- package/dist/{chunk-N7VXDBQU.js → chunk-KQYIHZ5E.js} +3 -1
- package/dist/chunk-M4GDBGXF.js +824 -0
- package/dist/{chunk-USUCPCUC.js → chunk-WPVL6VIT.js} +5 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +8 -10
- package/package.json +1 -1
- package/dist/chunk-22CV7V7A.js +0 -38
- package/dist/chunk-2BE2IPMO.js +0 -315
- package/dist/chunk-2CRLBMOX.js +0 -244
- package/dist/chunk-2HMNOZPY.js +0 -109
- package/dist/chunk-2I6DLXJN.js +0 -219
- package/dist/chunk-2Q6XPZ4Q.js +0 -395
- package/dist/chunk-3MIR7ODJ.js +0 -112
- package/dist/chunk-3QZESA66.js +0 -222
- package/dist/chunk-4Z4MB37G.js +0 -96
- package/dist/chunk-5GFUI5N6.js +0 -132
- package/dist/chunk-5JEDLXEC.js +0 -99
- package/dist/chunk-5QHQVXWN.js +0 -174
- package/dist/chunk-6A3WCJO2.js +0 -416
- package/dist/chunk-6QWFMVCD.js +0 -328
- package/dist/chunk-6SRXMZAN.js +0 -99
- package/dist/chunk-7PTU7TGE.js +0 -96
- package/dist/chunk-AB4KX4XT.js +0 -109
- package/dist/chunk-AC4UYAGX.js +0 -136
- package/dist/chunk-ACE77TKQ.js +0 -328
- package/dist/chunk-BYNYYZWW.js +0 -412
- package/dist/chunk-C5TFTNHG.js +0 -244
- package/dist/chunk-CH53PBQN.js +0 -265
- package/dist/chunk-DF3BM6BF.js +0 -195
- package/dist/chunk-DKZVKEOW.js +0 -175
- package/dist/chunk-EMPL27ZV.js +0 -96
- package/dist/chunk-EQJXMEIR.js +0 -175
- package/dist/chunk-GTJGZBTQ.js +0 -328
- package/dist/chunk-HJMFYDWY.js +0 -222
- package/dist/chunk-HRQEECB6.js +0 -315
- package/dist/chunk-HWEMAAOY.js +0 -370
- package/dist/chunk-IBWCHA5M.js +0 -10
- package/dist/chunk-KTM4ZQ3W.js +0 -96
- package/dist/chunk-KTP2EPVB.js +0 -27
- package/dist/chunk-KYARP7KP.js +0 -97
- package/dist/chunk-M2N5DYWN.js +0 -247
- package/dist/chunk-M5F2L4KF.js +0 -177
- package/dist/chunk-M6S3M6E3.js +0 -244
- package/dist/chunk-O6A5RHWY.js +0 -122
- package/dist/chunk-ODUXELPM.js +0 -219
- package/dist/chunk-OF5WIOYS.js +0 -129
- package/dist/chunk-OP4QAN23.js +0 -99
- package/dist/chunk-P4JH2Q7Z.js +0 -102
- package/dist/chunk-PMD5JSV3.js +0 -242
- package/dist/chunk-PSSHNBEL.js +0 -177
- package/dist/chunk-QEPRH7JB.js +0 -315
- package/dist/chunk-QRXWPZKN.js +0 -308
- package/dist/chunk-QY4L6XHN.js +0 -384
- package/dist/chunk-RNWPCELY.js +0 -176
- package/dist/chunk-RSHN2AQ7.js +0 -29
- package/dist/chunk-RZLVE57X.js +0 -38
- package/dist/chunk-SDCIKREA.js +0 -241
- package/dist/chunk-SDREQWNI.js +0 -175
- package/dist/chunk-SH26ZOHU.js +0 -159
- package/dist/chunk-TRLBBLSS.js +0 -176
- package/dist/chunk-U6OXZPYC.js +0 -384
- package/dist/chunk-UIBTFZUD.js +0 -314
- package/dist/chunk-V4GLWPD7.js +0 -174
- package/dist/chunk-VC6U3QGF.js +0 -142
- package/dist/chunk-VIST7ACL.js +0 -88
- package/dist/chunk-VUNL35KE.js +0 -16
- package/dist/chunk-VWBNI4SC.js +0 -174
- package/dist/chunk-W5KRWUNE.js +0 -262
- package/dist/chunk-WCGKLHCL.js +0 -154
- package/dist/chunk-WNXGIEX7.js +0 -219
- package/dist/chunk-XPVSZIA3.js +0 -315
- package/dist/chunk-Y633ZIJF.js +0 -384
- package/dist/chunk-YCL4GUFR.js +0 -222
- package/dist/chunk-YTMKB44I.js +0 -220
- package/dist/chunk-ZWBS3T7Q.js +0 -244
- 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 |
|
|
@@ -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)
|
|
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)
|
|
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
|
|
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
denyReasonFrom
|
|
4
|
-
|
|
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-
|
|
33
|
-
import
|
|
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-
|
|
10
|
-
import "../chunk-
|
|
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-
|
|
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("
|
|
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
|
-
|
|
5
|
-
|
|
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-
|
|
24
|
-
import
|
|
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);
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
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-
|
|
5
|
-
import "../chunk-
|
|
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-
|
|
5
|
-
import "../chunk-
|
|
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-
|
|
18
|
+
} from "../chunk-JPRRYC5X.js";
|
|
18
19
|
import {
|
|
19
20
|
isValidApiKey
|
|
20
|
-
} from "../chunk-
|
|
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-
|
|
5
|
-
import "../chunk-
|
|
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
|
-
|
|
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-
|
|
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
|
};
|