@pushary/agent-hooks 0.26.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 +32 -2
- 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 +6 -4
- 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-FDR5WYT7.js +208 -0
- package/dist/chunk-IIENZE2J.js +15 -0
- package/dist/{chunk-5MA3CPZB.js → chunk-MVNOP5IZ.js} +5 -1
- package/dist/{chunk-A7DQCA2N.js → chunk-TUILTFLS.js} +74 -184
- package/dist/{chunk-SA4BDS7T.js → chunk-VHD3GMZ3.js} +40 -3
- package/dist/{chunk-SLX4AONG.js → chunk-WXWHRXDK.js} +7 -3
- package/dist/src/index.d.ts +7 -1
- package/dist/src/index.js +3 -2
- package/package.json +4 -3
|
@@ -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,
|
|
@@ -9,12 +12,15 @@ import {
|
|
|
9
12
|
missingGeminiHookEvents,
|
|
10
13
|
readCodexMcpAuth,
|
|
11
14
|
untrustedCodexHookEvents
|
|
12
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-WXWHRXDK.js";
|
|
13
16
|
import {
|
|
14
17
|
callMcpTool,
|
|
15
18
|
sendMcpRequest
|
|
16
19
|
} from "../chunk-DWED7BS3.js";
|
|
17
|
-
import "../chunk-
|
|
20
|
+
import "../chunk-FDR5WYT7.js";
|
|
21
|
+
import {
|
|
22
|
+
getBaseUrl
|
|
23
|
+
} from "../chunk-NKXSILEW.js";
|
|
18
24
|
|
|
19
25
|
// bin/pushary-doctor.ts
|
|
20
26
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -407,6 +413,30 @@ var main = async () => {
|
|
|
407
413
|
}
|
|
408
414
|
}
|
|
409
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
|
+
}
|
|
410
440
|
console.log();
|
|
411
441
|
const failed = results.filter((r) => !r.passed);
|
|
412
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,7 +3,7 @@ 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,
|
|
@@ -16,12 +16,14 @@ import {
|
|
|
16
16
|
renderAgentInstructions,
|
|
17
17
|
renderProjectAgentInstructions,
|
|
18
18
|
writeInstructionBlock
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-WXWHRXDK.js";
|
|
20
20
|
import {
|
|
21
|
-
isValidApiKey,
|
|
22
21
|
reportEvent
|
|
23
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-TUILTFLS.js";
|
|
24
23
|
import "../chunk-DWED7BS3.js";
|
|
24
|
+
import {
|
|
25
|
+
isValidApiKey
|
|
26
|
+
} from "../chunk-FDR5WYT7.js";
|
|
25
27
|
import "../chunk-NKXSILEW.js";
|
|
26
28
|
|
|
27
29
|
// bin/pushary-setup.ts
|
|
@@ -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
|
}
|