@pushary/agent-hooks 0.25.0 → 0.27.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/scripts/pushary-gate.mjs +21 -1
- package/dist/bin/pushary-clean.js +3 -2
- package/dist/bin/pushary-codex-hook.js +37 -12
- package/dist/bin/pushary-codex.js +2 -1
- package/dist/bin/pushary-doctor.js +52 -10
- package/dist/bin/pushary-gemini-hook.js +36 -11
- package/dist/bin/pushary-hook.js +3 -2
- package/dist/bin/pushary-mode.js +5 -3
- package/dist/bin/pushary-post-hook.js +8 -2
- package/dist/bin/pushary-prompt-hook.js +8 -2
- package/dist/bin/pushary-setup.js +11 -21
- package/dist/bin/pushary-stop-hook.js +2 -1
- package/dist/bin/pushary-wait.d.ts +1 -0
- package/dist/bin/pushary-wait.js +86 -0
- package/dist/bin/pushary.js +4 -0
- package/dist/{chunk-WPVL6VIT.js → chunk-FDR5WYT7.js} +50 -2
- package/dist/chunk-IIENZE2J.js +15 -0
- package/dist/{chunk-5MA3CPZB.js → chunk-MVNOP5IZ.js} +5 -1
- package/dist/{chunk-GYPPVD3P.js → chunk-TUILTFLS.js} +74 -184
- package/dist/{chunk-O2QL6U6G.js → chunk-VHD3GMZ3.js} +40 -3
- package/dist/{chunk-JPRRYC5X.js → chunk-WXWHRXDK.js} +32 -3
- package/dist/src/index.d.ts +7 -1
- package/dist/src/index.js +3 -2
- package/package.json +4 -3
- 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-3LC4YUMA.js +0 -194
- 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-5LTQNC7R.js +0 -824
- 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-BHAMEPOP.js +0 -323
- 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-FGZZRCOG.js +0 -194
- package/dist/chunk-FXYTRZHC.js +0 -177
- 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-M4GDBGXF.js +0 -824
- package/dist/chunk-M5F2L4KF.js +0 -177
- package/dist/chunk-M6S3M6E3.js +0 -244
- package/dist/chunk-N7VXDBQU.js +0 -6
- 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-USUCPCUC.js +0 -156
- 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
|
@@ -295,7 +295,19 @@ const handlePushOnly = async (apiKey, command, project, ident, timeoutSeconds, t
|
|
|
295
295
|
}
|
|
296
296
|
if (!asked?.correlationId) return ask()
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
// Keyboard bypass: at the keyboard, so Cursor's own prompt handles it now.
|
|
299
|
+
if (asked.suppressed) {
|
|
300
|
+
await callTool(apiKey, 'cancel_question', { correlationId: asked.correlationId }).catch(() => {})
|
|
301
|
+
return ask('You are at the keyboard — approve here.')
|
|
302
|
+
}
|
|
303
|
+
// No reachable device: apply the configured fallback immediately.
|
|
304
|
+
if (asked.noDevices) {
|
|
305
|
+
return fromTimeoutAction(timeoutAction, 'No device connected to approve on; denied per your Pushary policy.')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// `wait` holds the full block window for the phone (fromTimeoutAction maps it
|
|
309
|
+
// to ask, so on timeout it hands to Cursor's prompt, never auto-denies).
|
|
310
|
+
const realMs = timeoutAction === 'wait' ? MAX_BLOCK_MS : Math.max(timeoutSeconds, 1) * 1000
|
|
299
311
|
const cap = Math.min(realMs, MAX_BLOCK_MS)
|
|
300
312
|
const answer = await pollForAnswer(apiKey, asked.correlationId, Date.now() + cap)
|
|
301
313
|
if (answer.answered) {
|
|
@@ -319,6 +331,14 @@ const handlePushFirst = async (apiKey, command, project, ident, pushFirstSeconds
|
|
|
319
331
|
}
|
|
320
332
|
if (!asked?.correlationId) return ask()
|
|
321
333
|
|
|
334
|
+
// Keyboard bypass / no reachable device: push_first already falls to Cursor's
|
|
335
|
+
// prompt, so do it now rather than racing a push nobody will see.
|
|
336
|
+
if (asked.suppressed) {
|
|
337
|
+
await callTool(apiKey, 'cancel_question', { correlationId: asked.correlationId }).catch(() => {})
|
|
338
|
+
return ask('You are at the keyboard — approve here.')
|
|
339
|
+
}
|
|
340
|
+
if (asked.noDevices) return ask('No device connected — approve here.')
|
|
341
|
+
|
|
322
342
|
const cap = Math.min(Math.max(pushFirstSeconds, 1) * 1000, MAX_BLOCK_MS)
|
|
323
343
|
const answer = await pollForAnswer(apiKey, asked.correlationId, Date.now() + cap)
|
|
324
344
|
if (answer.answered) {
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
removeClaudeMcpServers,
|
|
4
4
|
removePusharySettings
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-MVNOP5IZ.js";
|
|
6
6
|
import {
|
|
7
7
|
execNpm,
|
|
8
8
|
removeCodexHooks,
|
|
9
9
|
removeGeminiSettings,
|
|
10
10
|
removeInstructionBlock
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-WXWHRXDK.js";
|
|
12
|
+
import "../chunk-FDR5WYT7.js";
|
|
12
13
|
|
|
13
14
|
// bin/pushary-clean.ts
|
|
14
15
|
import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
} from "../chunk-KQYIHZ5E.js";
|
|
6
6
|
import {
|
|
7
7
|
CODEX_AGENT,
|
|
8
|
-
DECISION_LINE_MAX,
|
|
9
8
|
DEFAULT_SESSION,
|
|
10
9
|
askUser,
|
|
10
|
+
cancelQuestion,
|
|
11
11
|
codexAllow,
|
|
12
12
|
codexDeny,
|
|
13
13
|
codexPass,
|
|
@@ -32,8 +32,14 @@ import {
|
|
|
32
32
|
toCodexWire,
|
|
33
33
|
toPolicyLookup,
|
|
34
34
|
waitForAnswer
|
|
35
|
-
} from "../chunk-
|
|
35
|
+
} from "../chunk-TUILTFLS.js";
|
|
36
36
|
import "../chunk-DWED7BS3.js";
|
|
37
|
+
import {
|
|
38
|
+
DECISION_LINE_MAX,
|
|
39
|
+
effectiveWaitSeconds,
|
|
40
|
+
hookWaitClamped,
|
|
41
|
+
hookWaitDeadline
|
|
42
|
+
} from "../chunk-FDR5WYT7.js";
|
|
37
43
|
import {
|
|
38
44
|
getApiKey
|
|
39
45
|
} from "../chunk-NKXSILEW.js";
|
|
@@ -43,7 +49,6 @@ import { basename, join } from "path";
|
|
|
43
49
|
import { tmpdir, userInfo } from "os";
|
|
44
50
|
import { existsSync, mkdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
45
51
|
var KILL_REASON = "Stopped by user: this agent was halted from Pushary";
|
|
46
|
-
var MAX_WAIT_SECONDS = 170;
|
|
47
52
|
var START_MS = Date.now();
|
|
48
53
|
var APPROVAL_TTL_MS = 10 * 60 * 1e3;
|
|
49
54
|
var sanitizeId = (value) => value.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128);
|
|
@@ -126,12 +131,20 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
|
|
|
126
131
|
blocker: deriveBlocker(mode),
|
|
127
132
|
actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {})
|
|
128
133
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
if (result.suppressed || result.noDevices) {
|
|
135
|
+
return {
|
|
136
|
+
answer: { answered: false },
|
|
137
|
+
correlationId: result.correlationId,
|
|
138
|
+
suppressed: !!result.suppressed,
|
|
139
|
+
noDevices: !!result.noDevices,
|
|
140
|
+
clampCut: false
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
const deadline = hookWaitDeadline(START_MS, waitSeconds, "codex", now);
|
|
145
|
+
const clampCut = hookWaitClamped(START_MS, waitSeconds, "codex", now);
|
|
133
146
|
const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
|
|
134
|
-
return { answer, correlationId: result.correlationId };
|
|
147
|
+
return { answer, correlationId: result.correlationId, suppressed: false, noDevices: false, clampCut };
|
|
135
148
|
};
|
|
136
149
|
var notifyApprovalNeeded = async (apiKey, input, lookup) => {
|
|
137
150
|
try {
|
|
@@ -162,8 +175,13 @@ var decidePermissionRequest = async (input) => {
|
|
|
162
175
|
if (claimNotify(input.tool_use_id)) await notifyApprovalNeeded(apiKey, input, lookup);
|
|
163
176
|
return codexPass();
|
|
164
177
|
}
|
|
165
|
-
const waitSeconds = toolPolicy.mode === "push_first" ? toolPolicy.pushFirstSeconds : toolPolicy.timeoutSeconds;
|
|
166
|
-
const { answer, correlationId } = await pushQuestion(apiKey, input, lookup, waitSeconds, toolPolicy.mode);
|
|
178
|
+
const waitSeconds = toolPolicy.mode === "push_first" ? toolPolicy.pushFirstSeconds : effectiveWaitSeconds(toolPolicy.timeoutAction, toolPolicy.timeoutSeconds, "codex");
|
|
179
|
+
const { answer, correlationId, suppressed, clampCut } = await pushQuestion(apiKey, input, lookup, waitSeconds, toolPolicy.mode);
|
|
180
|
+
if (suppressed) {
|
|
181
|
+
await cancelQuestion(apiKey, correlationId).catch(() => {
|
|
182
|
+
});
|
|
183
|
+
return codexPass();
|
|
184
|
+
}
|
|
167
185
|
if (answer.answered) {
|
|
168
186
|
if (isDeferAnswer(answer.value)) return codexPass();
|
|
169
187
|
if (answer.value === "yes") {
|
|
@@ -174,6 +192,7 @@ var decidePermissionRequest = async (input) => {
|
|
|
174
192
|
}
|
|
175
193
|
savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
|
|
176
194
|
if (toolPolicy.mode === "push_first") return codexPass();
|
|
195
|
+
if (clampCut) return codexPass();
|
|
177
196
|
const decision = permissionTimeoutDecision(toolPolicy.timeoutAction);
|
|
178
197
|
if (decision.kind === "allow") markApproved(input.tool_use_id);
|
|
179
198
|
return decision;
|
|
@@ -197,16 +216,22 @@ var decidePreToolUse = async (input) => {
|
|
|
197
216
|
if (consumeApproval(input.tool_use_id)) return codexPass();
|
|
198
217
|
let pushed;
|
|
199
218
|
try {
|
|
200
|
-
pushed = await pushQuestion(apiKey, input, lookup, toolPolicy.timeoutSeconds, "push_only");
|
|
219
|
+
pushed = await pushQuestion(apiKey, input, lookup, effectiveWaitSeconds(toolPolicy.timeoutAction, toolPolicy.timeoutSeconds, "codex"), "push_only");
|
|
201
220
|
} catch {
|
|
202
221
|
return preToolUseTimeoutDecision(toolPolicy.timeoutAction, "Push notification failed, denying per policy");
|
|
203
222
|
}
|
|
204
|
-
const { answer, correlationId } = pushed;
|
|
223
|
+
const { answer, correlationId, suppressed, clampCut } = pushed;
|
|
224
|
+
if (suppressed) {
|
|
225
|
+
await cancelQuestion(apiKey, correlationId).catch(() => {
|
|
226
|
+
});
|
|
227
|
+
return codexPass();
|
|
228
|
+
}
|
|
205
229
|
if (answer.answered) {
|
|
206
230
|
if (isDeferAnswer(answer.value)) return codexPass();
|
|
207
231
|
return answer.value === "yes" ? codexPass() : codexDeny(denyReasonFrom(answer.value));
|
|
208
232
|
}
|
|
209
233
|
savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
|
|
234
|
+
if (clampCut) return codexPass();
|
|
210
235
|
return preToolUseTimeoutDecision(toolPolicy.timeoutAction);
|
|
211
236
|
}
|
|
212
237
|
if (toolPolicy.mode === "notify_only") {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
describeWaitLadder
|
|
4
|
+
} from "../chunk-IIENZE2J.js";
|
|
2
5
|
import {
|
|
3
6
|
GEMINI_HOOK_BINARY,
|
|
4
7
|
execNpm,
|
|
@@ -7,13 +10,17 @@ import {
|
|
|
7
10
|
hasInstructionBlock,
|
|
8
11
|
missingCodexHookEvents,
|
|
9
12
|
missingGeminiHookEvents,
|
|
13
|
+
readCodexMcpAuth,
|
|
10
14
|
untrustedCodexHookEvents
|
|
11
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-WXWHRXDK.js";
|
|
12
16
|
import {
|
|
13
17
|
callMcpTool,
|
|
14
18
|
sendMcpRequest
|
|
15
19
|
} from "../chunk-DWED7BS3.js";
|
|
16
|
-
import "../chunk-
|
|
20
|
+
import "../chunk-FDR5WYT7.js";
|
|
21
|
+
import {
|
|
22
|
+
getBaseUrl
|
|
23
|
+
} from "../chunk-NKXSILEW.js";
|
|
17
24
|
|
|
18
25
|
// bin/pushary-doctor.ts
|
|
19
26
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -171,22 +178,33 @@ var main = async () => {
|
|
|
171
178
|
}
|
|
172
179
|
const codexMcp = codexParsed.mcp_servers?.pushary;
|
|
173
180
|
const codexTransport = !!codexMcp && (typeof codexMcp.url === "string" || typeof codexMcp.command === "string");
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
check(codexMcpOk, "Codex: MCP server configured", codexMcpOk ? void 0 : !codexMcp ? "no [mcp_servers.pushary] \u2014 re-run setup" : !codexTransport ? "entry has no url \u2014 `codex mcp add` likely failed, re-run setup" : "entry has no bearer_token/_env_var \u2014 re-run setup");
|
|
181
|
+
const codexAuth = readCodexMcpAuth(codexParsed);
|
|
182
|
+
const codexMcpOk = !!codexMcp && codexTransport && !!codexAuth;
|
|
183
|
+
check(codexMcpOk, "Codex: MCP server configured", codexMcpOk ? void 0 : !codexMcp ? "no [mcp_servers.pushary] \u2014 re-run setup" : !codexTransport ? "entry has no url \u2014 re-run setup" : "entry has no Authorization header or bearer token \u2014 re-run setup");
|
|
178
184
|
if (codexMcp) {
|
|
179
185
|
const hasAutoApprove = codexMcp.default_tools_approval_mode === "approve";
|
|
180
186
|
check(hasAutoApprove, "Codex: tools auto-allowed", hasAutoApprove ? "default_tools_approval_mode = approve" : "missing \u2014 MCP calls will prompt for approval");
|
|
181
187
|
if (codexMcp.tools && typeof codexMcp.tools === "object") {
|
|
182
188
|
console.log(` ${warn} Codex: per-tool approval overrides detected ${dim("(redundant with default_tools_approval_mode)")}`);
|
|
183
189
|
}
|
|
184
|
-
if (
|
|
185
|
-
const
|
|
186
|
-
check(
|
|
190
|
+
if (codexAuth?.method === "embedded") {
|
|
191
|
+
const keyMatches = !apiKey || codexAuth.key === apiKey;
|
|
192
|
+
check(keyMatches, "Codex: MCP key embedded (config.toml)", keyMatches ? "no shell export needed" : "differs from your active key \u2014 re-run setup");
|
|
193
|
+
} else if (codexAuth?.method === "env") {
|
|
194
|
+
const envValue = process.env[codexAuth.envVar]?.trim();
|
|
195
|
+
const exportedInProfile = SHELL_FILES.some((f) => {
|
|
196
|
+
try {
|
|
197
|
+
return readFileSync(f, "utf-8").includes(`export ${codexAuth.envVar}=`);
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
check(!!envValue, `Codex: MCP key available in shell ($${codexAuth.envVar})`, envValue ? "set \u2014 re-run setup to embed the key and drop the shell dependency" : exportedInProfile ? "exported in your profile but not in this shell \u2014 restart the terminal, or re-run setup to embed the key in config.toml" : "not set \u2014 Codex reads its key from your shell; re-run setup to embed the key in config.toml");
|
|
187
203
|
if (envValue && apiKey && envValue !== apiKey) {
|
|
188
|
-
console.log(` ${warn} Codex: $${
|
|
204
|
+
console.log(` ${warn} Codex: $${codexAuth.envVar} differs from your active key ${dim("(rotate or re-run setup so hooks and MCP match)")}`);
|
|
189
205
|
}
|
|
206
|
+
} else if (codexAuth?.method === "legacy-bearer") {
|
|
207
|
+
console.log(` ${warn} Codex: legacy bearer_token entry ${dim("(re-run setup to embed the key as an Authorization header)")}`);
|
|
190
208
|
}
|
|
191
209
|
}
|
|
192
210
|
const codexNotifyPath = codexConfig.match(/["']([^"']*pushary-codex[^"']*)["']/)?.[1] ?? null;
|
|
@@ -395,6 +413,30 @@ var main = async () => {
|
|
|
395
413
|
}
|
|
396
414
|
}
|
|
397
415
|
}
|
|
416
|
+
if (apiKey) {
|
|
417
|
+
try {
|
|
418
|
+
const res = await fetch(`${getBaseUrl()}/api/mcp/policy`, {
|
|
419
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
420
|
+
});
|
|
421
|
+
if (res.ok) {
|
|
422
|
+
const policy = await res.json();
|
|
423
|
+
console.log();
|
|
424
|
+
console.log(` ${dim("Wait Ladder")} ${dim("(what happens when you don't answer)")}`);
|
|
425
|
+
const wildcard = policy.policies?.find((p) => p.tool === "*");
|
|
426
|
+
const defaultWindow = policy.defaultPushFirstSeconds ?? 20;
|
|
427
|
+
console.log(` ${dim("\xB7")} default: ${cyan(`${defaultWindow}s`)} ${dim("\u2014")} ${wildcard ? describeWaitLadder(wildcard) : `ping your phone ${defaultWindow}s, then ask in the terminal`}`);
|
|
428
|
+
if (policy.modeOverride) {
|
|
429
|
+
console.log(` ${warn} mode override active: ${cyan(policy.modeOverride)} ${dim("(pushary mode status)")}`);
|
|
430
|
+
}
|
|
431
|
+
const overrides = (policy.policies ?? []).filter((p) => p.tool !== "*").slice(0, 4);
|
|
432
|
+
for (const p of overrides) {
|
|
433
|
+
console.log(` ${dim("\xB7")} ${p.tool}: ${describeWaitLadder(p)}`);
|
|
434
|
+
}
|
|
435
|
+
console.log(` ${dim("Tune it:")} ${cyan("pushary wait <seconds>")} ${dim("\xB7 full ladder:")} ${cyan("pushary wait status")}`);
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
}
|
|
439
|
+
}
|
|
398
440
|
console.log();
|
|
399
441
|
const failed = results.filter((r) => !r.passed);
|
|
400
442
|
if (failed.length === 0) {
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
isDeferAnswer
|
|
5
5
|
} from "../chunk-KQYIHZ5E.js";
|
|
6
6
|
import {
|
|
7
|
-
DECISION_LINE_MAX,
|
|
8
7
|
DEFAULT_SESSION,
|
|
9
8
|
askUser,
|
|
9
|
+
cancelQuestion,
|
|
10
10
|
deriveActionBody,
|
|
11
11
|
deriveBlocker,
|
|
12
12
|
deriveToolTarget,
|
|
@@ -24,8 +24,14 @@ import {
|
|
|
24
24
|
savePendingQuestion,
|
|
25
25
|
sendNotification,
|
|
26
26
|
waitForAnswer
|
|
27
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-TUILTFLS.js";
|
|
28
28
|
import "../chunk-DWED7BS3.js";
|
|
29
|
+
import {
|
|
30
|
+
DECISION_LINE_MAX,
|
|
31
|
+
effectiveWaitSeconds,
|
|
32
|
+
hookWaitClamped,
|
|
33
|
+
hookWaitDeadline
|
|
34
|
+
} from "../chunk-FDR5WYT7.js";
|
|
29
35
|
import {
|
|
30
36
|
getApiKey
|
|
31
37
|
} from "../chunk-NKXSILEW.js";
|
|
@@ -68,7 +74,6 @@ var geminiTimeoutDecision = (timeoutAction, denyReason = "No response within tim
|
|
|
68
74
|
|
|
69
75
|
// bin/pushary-gemini-hook.ts
|
|
70
76
|
var KILL_REASON = "Stopped by user: this agent was halted from Pushary";
|
|
71
|
-
var MAX_WAIT_SECONDS = 170;
|
|
72
77
|
var START_MS = Date.now();
|
|
73
78
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
74
79
|
var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
|
|
@@ -106,12 +111,20 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode, pollInterval
|
|
|
106
111
|
blocker: deriveBlocker(mode),
|
|
107
112
|
actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {})
|
|
108
113
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
if (result.suppressed || result.noDevices) {
|
|
115
|
+
return {
|
|
116
|
+
answer: { answered: false },
|
|
117
|
+
correlationId: result.correlationId,
|
|
118
|
+
suppressed: !!result.suppressed,
|
|
119
|
+
noDevices: !!result.noDevices,
|
|
120
|
+
clampCut: false
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const deadline = hookWaitDeadline(START_MS, waitSeconds, "gemini", now);
|
|
125
|
+
const clampCut = hookWaitClamped(START_MS, waitSeconds, "gemini", now);
|
|
113
126
|
const answer = await pollForAnswer(apiKey, result.correlationId, deadline, pollInterval);
|
|
114
|
-
return { answer, correlationId: result.correlationId };
|
|
127
|
+
return { answer, correlationId: result.correlationId, suppressed: false, noDevices: false, clampCut };
|
|
115
128
|
};
|
|
116
129
|
var notifyApprovalNeeded = async (apiKey, input, lookup) => {
|
|
117
130
|
try {
|
|
@@ -128,16 +141,22 @@ var notifyApprovalNeeded = async (apiKey, input, lookup) => {
|
|
|
128
141
|
var handlePushOnly = async (apiKey, input, lookup, timeoutSeconds, timeoutAction) => {
|
|
129
142
|
let pushed;
|
|
130
143
|
try {
|
|
131
|
-
pushed = await pushQuestion(apiKey, input, lookup, timeoutSeconds, "push_only");
|
|
144
|
+
pushed = await pushQuestion(apiKey, input, lookup, effectiveWaitSeconds(timeoutAction, timeoutSeconds, "gemini"), "push_only");
|
|
132
145
|
} catch {
|
|
133
146
|
return geminiTimeoutDecision(timeoutAction, "Push notification failed, denying per policy");
|
|
134
147
|
}
|
|
135
|
-
const { answer, correlationId } = pushed;
|
|
148
|
+
const { answer, correlationId, suppressed, clampCut } = pushed;
|
|
149
|
+
if (suppressed) {
|
|
150
|
+
await cancelQuestion(apiKey, correlationId).catch(() => {
|
|
151
|
+
});
|
|
152
|
+
return geminiPass();
|
|
153
|
+
}
|
|
136
154
|
if (answer.answered) {
|
|
137
155
|
if (isDeferAnswer(answer.value)) return geminiPass();
|
|
138
156
|
return answer.value === "yes" ? geminiAllow() : geminiDeny(denyReasonFrom(answer.value));
|
|
139
157
|
}
|
|
140
158
|
savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
|
|
159
|
+
if (clampCut) return geminiPass();
|
|
141
160
|
return geminiTimeoutDecision(timeoutAction);
|
|
142
161
|
};
|
|
143
162
|
var handlePushFirst = async (apiKey, input, lookup, pushFirstSeconds) => {
|
|
@@ -147,7 +166,13 @@ var handlePushFirst = async (apiKey, input, lookup, pushFirstSeconds) => {
|
|
|
147
166
|
} catch {
|
|
148
167
|
return geminiPass();
|
|
149
168
|
}
|
|
150
|
-
const { answer, correlationId } = pushed;
|
|
169
|
+
const { answer, correlationId, suppressed, noDevices } = pushed;
|
|
170
|
+
if (suppressed) {
|
|
171
|
+
await cancelQuestion(apiKey, correlationId).catch(() => {
|
|
172
|
+
});
|
|
173
|
+
return geminiPass();
|
|
174
|
+
}
|
|
175
|
+
if (noDevices) return geminiPass();
|
|
151
176
|
if (answer.answered) {
|
|
152
177
|
if (isDeferAnswer(answer.value)) return geminiPass();
|
|
153
178
|
return answer.value === "yes" ? geminiAllow() : geminiDeny(denyReasonFrom(answer.value));
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-VHD3GMZ3.js";
|
|
5
5
|
import "../chunk-KQYIHZ5E.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-TUILTFLS.js";
|
|
7
7
|
import "../chunk-DWED7BS3.js";
|
|
8
|
+
import "../chunk-FDR5WYT7.js";
|
|
8
9
|
import "../chunk-NKXSILEW.js";
|
|
9
10
|
|
|
10
11
|
// bin/pushary-hook.ts
|
package/dist/bin/pushary-mode.js
CHANGED
|
@@ -19,9 +19,11 @@ var parseDuration = (value) => {
|
|
|
19
19
|
var main = async () => {
|
|
20
20
|
const apiKey = getApiKey();
|
|
21
21
|
const baseUrl = getBaseUrl();
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
22
|
+
const rest = process.argv.slice(2);
|
|
23
|
+
const args = rest[0] === "mode" ? rest.slice(1) : rest;
|
|
24
|
+
const mode = args[0];
|
|
25
|
+
const forFlag = args[1];
|
|
26
|
+
const forValue = args[2];
|
|
25
27
|
const headers = {
|
|
26
28
|
"Authorization": `Bearer ${apiKey}`,
|
|
27
29
|
"Content-Type": "application/json"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePostToolUse
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-TUILTFLS.js";
|
|
5
5
|
import "../chunk-DWED7BS3.js";
|
|
6
|
+
import "../chunk-FDR5WYT7.js";
|
|
6
7
|
import "../chunk-NKXSILEW.js";
|
|
7
8
|
|
|
8
9
|
// bin/pushary-post-hook.ts
|
|
@@ -16,7 +17,12 @@ var main = async () => {
|
|
|
16
17
|
}
|
|
17
18
|
try {
|
|
18
19
|
const input = JSON.parse(rawInput);
|
|
19
|
-
await handlePostToolUse(input);
|
|
20
|
+
const additionalContext = await handlePostToolUse(input);
|
|
21
|
+
if (additionalContext) {
|
|
22
|
+
process.stdout.write(JSON.stringify({
|
|
23
|
+
hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext }
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
20
26
|
} catch {
|
|
21
27
|
}
|
|
22
28
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleUserPrompt
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-TUILTFLS.js";
|
|
5
5
|
import "../chunk-DWED7BS3.js";
|
|
6
|
+
import "../chunk-FDR5WYT7.js";
|
|
6
7
|
import "../chunk-NKXSILEW.js";
|
|
7
8
|
|
|
8
9
|
// bin/pushary-prompt-hook.ts
|
|
@@ -16,7 +17,12 @@ var main = async () => {
|
|
|
16
17
|
}
|
|
17
18
|
try {
|
|
18
19
|
const input = JSON.parse(rawInput);
|
|
19
|
-
await handleUserPrompt(input);
|
|
20
|
+
const additionalContext = await handleUserPrompt(input);
|
|
21
|
+
if (additionalContext) {
|
|
22
|
+
process.stdout.write(JSON.stringify({
|
|
23
|
+
hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext }
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
20
26
|
} catch {
|
|
21
27
|
}
|
|
22
28
|
};
|
|
@@ -3,11 +3,12 @@ import {
|
|
|
3
3
|
addClaudeMcpServer,
|
|
4
4
|
addPusharyHooks,
|
|
5
5
|
addPusharyToolPermissions
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-MVNOP5IZ.js";
|
|
7
7
|
import {
|
|
8
8
|
GEMINI_HOOK_BINARY,
|
|
9
9
|
addCodexHookTrust,
|
|
10
10
|
addCodexHooks,
|
|
11
|
+
addCodexMcpServer,
|
|
11
12
|
addGeminiHooks,
|
|
12
13
|
addGeminiMcpServer,
|
|
13
14
|
execNpm,
|
|
@@ -15,12 +16,14 @@ import {
|
|
|
15
16
|
renderAgentInstructions,
|
|
16
17
|
renderProjectAgentInstructions,
|
|
17
18
|
writeInstructionBlock
|
|
18
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-WXWHRXDK.js";
|
|
19
20
|
import {
|
|
20
|
-
isValidApiKey,
|
|
21
21
|
reportEvent
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-TUILTFLS.js";
|
|
23
23
|
import "../chunk-DWED7BS3.js";
|
|
24
|
+
import {
|
|
25
|
+
isValidApiKey
|
|
26
|
+
} from "../chunk-FDR5WYT7.js";
|
|
24
27
|
import "../chunk-NKXSILEW.js";
|
|
25
28
|
|
|
26
29
|
// bin/pushary-setup.ts
|
|
@@ -602,7 +605,7 @@ var addCodexNotifyEntry = (codexConfig) => {
|
|
|
602
605
|
writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
|
|
603
606
|
}
|
|
604
607
|
};
|
|
605
|
-
var setupCodex = async (
|
|
608
|
+
var setupCodex = async (apiKey) => {
|
|
606
609
|
console.log(`
|
|
607
610
|
${bold2("Setting up Codex")}
|
|
608
611
|
`);
|
|
@@ -613,28 +616,14 @@ var setupCodex = async (_apiKey) => {
|
|
|
613
616
|
}
|
|
614
617
|
await installGlobally();
|
|
615
618
|
const codexConfig = join(homedir(), ".codex", "config.toml");
|
|
616
|
-
await spinner("Adding Pushary MCP server
|
|
617
|
-
try {
|
|
618
|
-
execSync(
|
|
619
|
-
"codex mcp add pushary --url https://pushary.com/api/mcp/mcp --bearer-token-env-var PUSHARY_API_KEY",
|
|
620
|
-
{ stdio: "ignore", timeout: 15e3 }
|
|
621
|
-
);
|
|
622
|
-
} catch {
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
await spinner("Auto-allowing all Pushary tools", async () => {
|
|
619
|
+
await spinner("Adding Pushary MCP server (key embedded, auto-allowed)", async () => {
|
|
626
620
|
let raw = "";
|
|
627
621
|
try {
|
|
628
622
|
raw = readFileSync(codexConfig, "utf-8");
|
|
629
623
|
} catch {
|
|
630
624
|
}
|
|
631
625
|
const config = raw ? parseTOML(raw) : {};
|
|
632
|
-
|
|
633
|
-
const pushary = mcpServers.pushary ?? {};
|
|
634
|
-
pushary.default_tools_approval_mode = "approve";
|
|
635
|
-
delete pushary.tools;
|
|
636
|
-
mcpServers.pushary = pushary;
|
|
637
|
-
config.mcp_servers = mcpServers;
|
|
626
|
+
addCodexMcpServer(config, apiKey);
|
|
638
627
|
writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
|
|
639
628
|
});
|
|
640
629
|
const codexVersion = readCodexVersion();
|
|
@@ -681,6 +670,7 @@ var setupCodex = async (_apiKey) => {
|
|
|
681
670
|
console.log();
|
|
682
671
|
console.log(` ${dim2("What this configured:")}`);
|
|
683
672
|
console.log(` ${dim2("\u2022")} MCP server: Codex can send notifications and ask questions`);
|
|
673
|
+
console.log(` ${dim2("\u2022")} API key embedded in config.toml: works without a shell reload or export`);
|
|
684
674
|
console.log(` ${dim2("\u2022")} Proactive instructions: ~/.codex/AGENTS.md tells Codex to ask via push, not the terminal`);
|
|
685
675
|
console.log(` ${dim2("\u2022")} Auto-allowed tools: no permission prompts for Pushary MCP calls`);
|
|
686
676
|
if (hooksSupported) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
describeWaitLadder
|
|
4
|
+
} from "../chunk-IIENZE2J.js";
|
|
5
|
+
import {
|
|
6
|
+
getApiKey,
|
|
7
|
+
getBaseUrl
|
|
8
|
+
} from "../chunk-NKXSILEW.js";
|
|
9
|
+
|
|
10
|
+
// bin/pushary-wait.ts
|
|
11
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
12
|
+
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
13
|
+
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
14
|
+
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
15
|
+
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
16
|
+
var MAX_WINDOW = 300;
|
|
17
|
+
var printLadder = (data) => {
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(` ${bold("Wait ladder")} ${dim("(what happens when you don't answer)")}`);
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(` Default push window: ${cyan(`${data.defaultPushFirstSeconds}s`)} ${dim("(catch-all tools)")}`);
|
|
22
|
+
if (data.modeOverride) {
|
|
23
|
+
console.log(` ${yellow("!")} Active mode override: ${cyan(data.modeOverride)} ${dim("(pushary mode status)")}`);
|
|
24
|
+
}
|
|
25
|
+
console.log();
|
|
26
|
+
const rows = [...data.policies].sort((a, b) => a.tool === "*" ? -1 : b.tool === "*" ? 1 : a.tool.localeCompare(b.tool));
|
|
27
|
+
const width = Math.max(...rows.map((p) => p.tool.length), 4);
|
|
28
|
+
for (const p of rows) {
|
|
29
|
+
const label = p.tool === "*" ? "default (*)" : p.tool;
|
|
30
|
+
console.log(` ${label.padEnd(width + 2)} ${dim("\xB7")} ${describeWaitLadder(p)}`);
|
|
31
|
+
}
|
|
32
|
+
console.log();
|
|
33
|
+
console.log(` ${dim("Set the default window:")} ${cyan("pushary wait <seconds>")}`);
|
|
34
|
+
console.log(` ${dim("Reset it:")} ${cyan("pushary wait clear")}`);
|
|
35
|
+
console.log(` ${dim("Per-tool rules live in the dashboard under Agent \u2192 Policies.")}`);
|
|
36
|
+
};
|
|
37
|
+
var fetchPolicy = async (baseUrl, headers) => {
|
|
38
|
+
const res = await fetch(`${baseUrl}/api/mcp/policy`, { headers });
|
|
39
|
+
if (!res.ok) throw new Error(`policy fetch failed (${res.status})`);
|
|
40
|
+
return res.json();
|
|
41
|
+
};
|
|
42
|
+
var setWindow = async (baseUrl, headers, seconds) => {
|
|
43
|
+
const res = await fetch(`${baseUrl}/api/mcp/policy`, {
|
|
44
|
+
method: "PATCH",
|
|
45
|
+
headers,
|
|
46
|
+
body: JSON.stringify({ defaultPushFirstSeconds: seconds })
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const err = await res.json().catch(() => ({}));
|
|
50
|
+
throw new Error(err.error ?? res.statusText);
|
|
51
|
+
}
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
return data.defaultPushFirstSeconds;
|
|
54
|
+
};
|
|
55
|
+
var main = async () => {
|
|
56
|
+
const apiKey = getApiKey();
|
|
57
|
+
const baseUrl = getBaseUrl();
|
|
58
|
+
const headers = {
|
|
59
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
60
|
+
"Content-Type": "application/json"
|
|
61
|
+
};
|
|
62
|
+
const rest = process.argv.slice(2);
|
|
63
|
+
const args = rest[0] === "wait" ? rest.slice(1) : rest;
|
|
64
|
+
const arg = args[0];
|
|
65
|
+
if (!arg || arg === "status") {
|
|
66
|
+
printLadder(await fetchPolicy(baseUrl, headers));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (arg === "clear" || arg === "reset") {
|
|
70
|
+
const value2 = await setWindow(baseUrl, headers, 20);
|
|
71
|
+
console.log(` ${green("\u2713")} Default push window reset to ${cyan(`${value2}s`)}`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const seconds = Number(arg);
|
|
75
|
+
if (!Number.isFinite(seconds) || seconds < 0 || seconds > MAX_WINDOW) {
|
|
76
|
+
console.log(` ${yellow("!")} Invalid window: ${arg} ${dim(`(use 0-${MAX_WINDOW} seconds, or "status" / "clear")`)}`);
|
|
77
|
+
console.log(` ${dim("Usage: pushary wait <seconds> | status | clear")}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const value = await setWindow(baseUrl, headers, Math.trunc(seconds));
|
|
81
|
+
console.log(` ${green("\u2713")} Default push window set to ${cyan(`${value}s`)} ${dim("\u2014 ping your phone that long, then the terminal takes over")}`);
|
|
82
|
+
};
|
|
83
|
+
main().catch((err) => {
|
|
84
|
+
console.error(` ${yellow("!")} ${err instanceof Error ? err.message : err}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
package/dist/bin/pushary.js
CHANGED
|
@@ -12,6 +12,8 @@ if (command === "setup") {
|
|
|
12
12
|
await import("./pushary-doctor.js");
|
|
13
13
|
} else if (command === "mode") {
|
|
14
14
|
await import("./pushary-mode.js");
|
|
15
|
+
} else if (command === "wait") {
|
|
16
|
+
await import("./pushary-wait.js");
|
|
15
17
|
} else {
|
|
16
18
|
console.log(`
|
|
17
19
|
Pushary Agent Hooks
|
|
@@ -21,6 +23,7 @@ Commands:
|
|
|
21
23
|
doctor Verify your Pushary installation is working
|
|
22
24
|
clean Remove all Pushary configuration
|
|
23
25
|
mode Switch approval mode (push_only, push_first, terminal_only)
|
|
26
|
+
wait Show or set the "wait for your phone" ladder (pushary wait 45)
|
|
24
27
|
hook Run as a PreToolUse hook (reads stdin, writes stdout)
|
|
25
28
|
|
|
26
29
|
Setup options:
|
|
@@ -34,5 +37,6 @@ Usage:
|
|
|
34
37
|
npx @pushary/agent-hooks@latest setup --key pk_xxx.xxx --skip-phone
|
|
35
38
|
npx @pushary/agent-hooks@latest doctor
|
|
36
39
|
npx @pushary/agent-hooks@latest mode push_only --for 30m
|
|
40
|
+
npx @pushary/agent-hooks@latest wait 45
|
|
37
41
|
`);
|
|
38
42
|
}
|