@pushary/agent-hooks 0.87.2 → 0.87.3
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/hooks/hooks.json +7 -0
- package/data/cursor-plugin/scripts/pushary-gate.mjs +85 -28
- package/data/cursor-plugin/scripts/pushary-gate.test.mjs +42 -1
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +7 -6
- package/dist/bin/pushary-claude.js +14 -11
- package/dist/bin/pushary-clean.js +36 -20
- package/dist/bin/pushary-codex-bridge.js +6 -5
- package/dist/bin/pushary-codex-hook.js +129 -52
- package/dist/bin/pushary-codex.js +4 -3
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-daemon.js +5 -4
- package/dist/bin/pushary-disconnect.js +65 -8
- package/dist/bin/pushary-doctor.js +79 -18
- package/dist/bin/pushary-elicitation-hook.js +1 -1
- package/dist/bin/pushary-gemini-bridge.js +6 -5
- package/dist/bin/pushary-gemini-hook.js +12 -10
- package/dist/bin/pushary-hook.js +6 -5
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +5 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +4 -3
- package/dist/bin/pushary-opencode-hook.d.ts +1 -0
- package/dist/bin/pushary-opencode-hook.js +329 -0
- package/dist/bin/pushary-permission-denied-hook.js +6 -5
- package/dist/bin/pushary-permission-hook.js +6 -5
- package/dist/bin/pushary-post-hook.js +4 -3
- package/dist/bin/pushary-prompt-hook.js +4 -3
- package/dist/bin/pushary-session-end-hook.js +4 -3
- package/dist/bin/pushary-session-start-hook.js +4 -3
- package/dist/bin/pushary-setup.js +238 -84
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +5 -5
- package/dist/bin/pushary-stop-hook.js +4 -3
- package/dist/bin/pushary-stopfailure-hook.js +4 -3
- package/dist/bin/pushary-upgrade.js +7 -6
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-5646VKR4.js → chunk-2JYQAW4B.js} +5 -53
- package/dist/chunk-7J67NYK4.js +287 -0
- package/dist/{chunk-M7RYIZX6.js → chunk-B7ZBHWOW.js} +10 -1
- package/dist/{chunk-7YJS2VTG.js → chunk-DMW54VWK.js} +12 -1
- package/dist/{chunk-LT2VLPUK.js → chunk-DPUOW6NS.js} +18 -2
- package/dist/{chunk-3LKYBFWA.js → chunk-EJL3PJU7.js} +1 -1
- package/dist/chunk-GIRT7677.js +11 -0
- package/dist/{chunk-HAUTLCMA.js → chunk-ICERLAE2.js} +1 -1
- package/dist/{chunk-ZZE6FN7T.js → chunk-K42RXJPG.js} +52 -1
- package/dist/{chunk-Y3UTTFTZ.js → chunk-KBS53WOE.js} +1 -1
- package/dist/{chunk-SJVLFIYP.js → chunk-KCAFCGGY.js} +1 -1
- package/dist/{chunk-A4I5JGCW.js → chunk-KFAH6KRE.js} +1 -1
- package/dist/{chunk-QBWYNVKY.js → chunk-LGVHYFIB.js} +1 -1
- package/dist/{chunk-HJ44HIUW.js → chunk-OMA2OZRE.js} +11 -9
- package/dist/{chunk-ECEUMWBH.js → chunk-QLMX5LLV.js} +1 -1
- package/dist/{chunk-XW5CHAQX.js → chunk-TAYHEJFF.js} +2 -2
- package/dist/{chunk-JAICP7BA.js → chunk-TD3M232T.js} +1 -1
- package/dist/{chunk-WLLBFDVY.js → chunk-TV3UFGT7.js} +1 -1
- package/dist/{chunk-7MBDFNUC.js → chunk-VIHRP25S.js} +1 -1
- package/dist/{chunk-B3V2H6SA.js → chunk-VL5ZNK6V.js} +43 -12
- package/dist/{chunk-V2XPF77V.js → chunk-VOE4SYPR.js} +42 -279
- package/dist/{chunk-OT4YD54Q.js → chunk-YX6ZKCC5.js} +1 -1
- package/dist/{chunk-2T3GX7WG.js → chunk-YYTLTEGU.js} +1 -1
- package/dist/chunk-ZGC3W23N.js +371 -0
- package/dist/src/index.js +6 -5
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Pushary gate — Cursor `beforeShellExecution`
|
|
2
|
+
// Pushary gate — Cursor `beforeShellExecution` and `beforeMCPExecution` hooks.
|
|
3
3
|
//
|
|
4
4
|
// Routes risky shell commands through your Pushary permission policy before they
|
|
5
5
|
// run. Which commands reach this gate is the `matcher` in ../hooks/hooks.json; what
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
// Self-contained: no dependencies, uses the global fetch (Node 18+).
|
|
17
17
|
//
|
|
18
18
|
// Contract (https://cursor.com/docs/hooks):
|
|
19
|
-
// stdin : { "
|
|
19
|
+
// stdin : { "hook_event_name": string, "cwd": string, "conversation_id": string, ... }
|
|
20
|
+
// beforeShellExecution adds { "command": string }
|
|
21
|
+
// beforeMCPExecution adds { "tool_name", "tool_input", "mcp_server_name" }
|
|
20
22
|
// stdout : { "permission": "allow" | "deny" | "ask", "user_message"?, "agent_message"? }
|
|
21
23
|
//
|
|
22
24
|
// Failure model: every handled path writes a decision and exits 0. Network/parse
|
|
@@ -245,12 +247,12 @@ const callTool = async (apiKey, name, args) => {
|
|
|
245
247
|
const GATE_TIMEOUT_MS = 5000
|
|
246
248
|
|
|
247
249
|
/**
|
|
248
|
-
* The verdict for one
|
|
250
|
+
* The verdict for one request, or null if we could not get one.
|
|
249
251
|
*
|
|
250
252
|
* Null is not a denial and not an approval: every failure here hands the call
|
|
251
253
|
* back to Cursor's own prompt, exactly as if this gate were not installed.
|
|
252
254
|
*/
|
|
253
|
-
const decide = async (apiKey,
|
|
255
|
+
const decide = async (apiKey, request, cwd, sessionId) => {
|
|
254
256
|
try {
|
|
255
257
|
const response = await fetch(`${BASE_URL}/api/agent/gate`, {
|
|
256
258
|
method: 'POST',
|
|
@@ -258,8 +260,8 @@ const decide = async (apiKey, command, cwd, sessionId) => {
|
|
|
258
260
|
body: JSON.stringify({
|
|
259
261
|
v: 1,
|
|
260
262
|
source: 'cursor',
|
|
261
|
-
toolName:
|
|
262
|
-
toolInputs:
|
|
263
|
+
toolName: request.toolName,
|
|
264
|
+
toolInputs: request.toolInputs,
|
|
263
265
|
cwd,
|
|
264
266
|
repoKey: deriveRepoKey(cwd),
|
|
265
267
|
sessionId,
|
|
@@ -341,16 +343,16 @@ const deriveActionBody = (command) => capActionBody(redactSecretsDeep(command))
|
|
|
341
343
|
// lock screen ungated.
|
|
342
344
|
const commandHead = (command) => command.trim().split(/\s+/).slice(0, 2).join(' ').slice(0, 120)
|
|
343
345
|
|
|
344
|
-
const askArgs = (
|
|
345
|
-
question:
|
|
346
|
+
const askArgs = (request, project, ident) => ({
|
|
347
|
+
question: `${request.prompt}\n\n${redactSecrets(request.display)}`,
|
|
346
348
|
type: 'confirm',
|
|
347
|
-
context: `Cursor agent wants to
|
|
349
|
+
context: `Cursor agent wants to ${request.verb} in ${project}`,
|
|
348
350
|
agentName: ident.agentName,
|
|
349
351
|
sessionId: ident.sessionId,
|
|
350
352
|
machineId: ident.machineId,
|
|
351
|
-
toolName:
|
|
352
|
-
toolTarget:
|
|
353
|
-
actionBody: deriveActionBody(
|
|
353
|
+
toolName: request.toolName,
|
|
354
|
+
toolTarget: request.toolTarget,
|
|
355
|
+
actionBody: deriveActionBody(request.display),
|
|
354
356
|
wait: false,
|
|
355
357
|
})
|
|
356
358
|
|
|
@@ -374,13 +376,68 @@ const pollForAnswer = async (apiKey, correlationId, deadlineMs) => {
|
|
|
374
376
|
const fromTimeoutAction = (action, deniedReason) =>
|
|
375
377
|
action === 'approve' ? ALLOW : action === 'deny' ? deny(deniedReason) : ask()
|
|
376
378
|
|
|
377
|
-
const
|
|
379
|
+
const denialFor = (noun) =>
|
|
380
|
+
`The user denied this ${noun} via a Pushary push approval. Do not run it — propose an alternative or ask how to proceed.`
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* What this hook is being asked about, in the one shape the rest of the gate
|
|
384
|
+
* reads.
|
|
385
|
+
*
|
|
386
|
+
* Cursor sends a different payload per event and the gate used to know only one
|
|
387
|
+
* of them: it read `input.command`, called everything "Bash", and any event that
|
|
388
|
+
* was not a shell execution fell out at the empty-command guard as `ask`. That
|
|
389
|
+
* was correct while `beforeShellExecution` was the only thing registered.
|
|
390
|
+
*
|
|
391
|
+
* Returns null when there is nothing to gate, which the caller answers with
|
|
392
|
+
* Cursor's own prompt.
|
|
393
|
+
*/
|
|
394
|
+
const describeRequest = (input) => {
|
|
395
|
+
const event = typeof input.hook_event_name === 'string' ? input.hook_event_name : ''
|
|
396
|
+
|
|
397
|
+
if (event === 'beforeMCPExecution') {
|
|
398
|
+
const tool = typeof input.tool_name === 'string' ? input.tool_name.trim() : ''
|
|
399
|
+
if (!tool) return null
|
|
400
|
+
const server = typeof input.mcp_server_name === 'string' ? input.mcp_server_name.trim() : ''
|
|
401
|
+
// `mcp__<server>__<tool>`, the same spelling Claude Code uses and the one
|
|
402
|
+
// `validateToolPattern` accepts, so a policy someone wrote once matches the
|
|
403
|
+
// same tool whichever agent called it. That portability is the only reason
|
|
404
|
+
// the policy engine is server-side.
|
|
405
|
+
const toolName = server ? `mcp__${server}__${tool}` : `mcp__${tool}`
|
|
406
|
+
const params = typeof input.tool_input === 'string'
|
|
407
|
+
? input.tool_input
|
|
408
|
+
: input.tool_input === undefined ? '' : JSON.stringify(input.tool_input)
|
|
409
|
+
return {
|
|
410
|
+
toolName,
|
|
411
|
+
toolInputs: [{ tool: toolName, params }],
|
|
412
|
+
display: params ? `${toolName}\n${params}` : toolName,
|
|
413
|
+
toolTarget: toolName.slice(0, 120),
|
|
414
|
+
prompt: 'Allow this tool call?',
|
|
415
|
+
verb: 'call this tool',
|
|
416
|
+
denied: denialFor('tool call'),
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Shell is also the fallback for a payload with no event name: a `command` is
|
|
421
|
+
// what `beforeShellExecution` has always been recognised by, and an older
|
|
422
|
+
// Cursor that omits `hook_event_name` must keep working exactly as before.
|
|
423
|
+
const command = typeof input.command === 'string' ? input.command.trim() : ''
|
|
424
|
+
if (!command || (event && event !== 'beforeShellExecution')) return null
|
|
425
|
+
return {
|
|
426
|
+
toolName: 'Bash',
|
|
427
|
+
toolInputs: [{ command }],
|
|
428
|
+
display: command,
|
|
429
|
+
toolTarget: commandHead(command),
|
|
430
|
+
prompt: 'Allow this command?',
|
|
431
|
+
verb: 'run this',
|
|
432
|
+
denied: denialFor('command'),
|
|
433
|
+
}
|
|
434
|
+
}
|
|
378
435
|
|
|
379
436
|
// push_only: wait up to the policy timeout, then apply the timeout action.
|
|
380
|
-
const handlePushOnly = async (apiKey,
|
|
437
|
+
const handlePushOnly = async (apiKey, request, project, ident, timeoutSeconds, timeoutAction) => {
|
|
381
438
|
let asked
|
|
382
439
|
try {
|
|
383
|
-
asked = await withRetry(() => callTool(apiKey, 'ask_user', askArgs(
|
|
440
|
+
asked = await withRetry(() => callTool(apiKey, 'ask_user', askArgs(request, project, ident)), 3)
|
|
384
441
|
} catch {
|
|
385
442
|
return fromTimeoutAction(timeoutAction, 'Push notification failed; denied per your Pushary policy.')
|
|
386
443
|
}
|
|
@@ -389,7 +446,7 @@ const handlePushOnly = async (apiKey, command, project, ident, timeoutSeconds, t
|
|
|
389
446
|
const realMs = Math.max(timeoutSeconds, 1) * 1000
|
|
390
447
|
const cap = Math.min(realMs, MAX_BLOCK_MS)
|
|
391
448
|
const answer = await pollForAnswer(apiKey, asked.correlationId, Date.now() + cap)
|
|
392
|
-
if (answer.answered) return answer.value === 'yes' ? ALLOW : deny(
|
|
449
|
+
if (answer.answered) return answer.value === 'yes' ? ALLOW : deny(request.denied)
|
|
393
450
|
|
|
394
451
|
// If Cursor's hook limit cut us off before the configured timeout, hand off to
|
|
395
452
|
// Cursor's own prompt rather than misapplying the policy's timeout action.
|
|
@@ -398,10 +455,10 @@ const handlePushOnly = async (apiKey, command, project, ident, timeoutSeconds, t
|
|
|
398
455
|
}
|
|
399
456
|
|
|
400
457
|
// push_first: race the push for a short window, then fall back to Cursor's prompt.
|
|
401
|
-
const handlePushFirst = async (apiKey,
|
|
458
|
+
const handlePushFirst = async (apiKey, request, project, ident, pushFirstSeconds) => {
|
|
402
459
|
let asked
|
|
403
460
|
try {
|
|
404
|
-
asked = await withRetry(() => callTool(apiKey, 'ask_user', askArgs(
|
|
461
|
+
asked = await withRetry(() => callTool(apiKey, 'ask_user', askArgs(request, project, ident)), 3)
|
|
405
462
|
} catch {
|
|
406
463
|
return ask()
|
|
407
464
|
}
|
|
@@ -409,16 +466,16 @@ const handlePushFirst = async (apiKey, command, project, ident, pushFirstSeconds
|
|
|
409
466
|
|
|
410
467
|
const cap = Math.min(Math.max(pushFirstSeconds, 1) * 1000, MAX_BLOCK_MS)
|
|
411
468
|
const answer = await pollForAnswer(apiKey, asked.correlationId, Date.now() + cap)
|
|
412
|
-
if (answer.answered) return answer.value === 'yes' ? ALLOW : deny(
|
|
469
|
+
if (answer.answered) return answer.value === 'yes' ? ALLOW : deny(request.denied)
|
|
413
470
|
return ask('Sent to your phone via Pushary — you can also approve here.')
|
|
414
471
|
}
|
|
415
472
|
|
|
416
473
|
// notify_only: fire an awareness notification, let Cursor's prompt decide.
|
|
417
|
-
const handleNotifyOnly = async (apiKey,
|
|
474
|
+
const handleNotifyOnly = async (apiKey, request, project, ident) => {
|
|
418
475
|
try {
|
|
419
476
|
await callTool(apiKey, 'send_notification', {
|
|
420
477
|
title: 'Agent needs approval',
|
|
421
|
-
body: redactSecrets(
|
|
478
|
+
body: redactSecrets(request.display).slice(0, 180),
|
|
422
479
|
agentName: ident.agentName,
|
|
423
480
|
sessionId: ident.sessionId,
|
|
424
481
|
machineId: ident.machineId,
|
|
@@ -436,8 +493,8 @@ const main = async () => {
|
|
|
436
493
|
return respond(ask())
|
|
437
494
|
}
|
|
438
495
|
|
|
439
|
-
const
|
|
440
|
-
if (!
|
|
496
|
+
const request = describeRequest(input)
|
|
497
|
+
if (!request) return respond(ask())
|
|
441
498
|
|
|
442
499
|
const apiKey = resolveApiKey()
|
|
443
500
|
if (!apiKey) {
|
|
@@ -451,7 +508,7 @@ const main = async () => {
|
|
|
451
508
|
const ident = { agentName: `Cursor - ${project}`, sessionId, machineId: getMachineId() }
|
|
452
509
|
|
|
453
510
|
try {
|
|
454
|
-
const verdict = await decide(apiKey,
|
|
511
|
+
const verdict = await decide(apiKey, request, input.cwd, sessionId)
|
|
455
512
|
|
|
456
513
|
// No verdict, or one that says nothing: Cursor's own prompt decides, exactly
|
|
457
514
|
// as if this gate were not installed. Never a forced denial on an outage.
|
|
@@ -467,12 +524,12 @@ const main = async () => {
|
|
|
467
524
|
case 'terminal_only':
|
|
468
525
|
return respond(ask())
|
|
469
526
|
case 'notify_only':
|
|
470
|
-
return respond(await handleNotifyOnly(apiKey,
|
|
527
|
+
return respond(await handleNotifyOnly(apiKey, request, project, ident))
|
|
471
528
|
case 'push_only':
|
|
472
|
-
return respond(await handlePushOnly(apiKey,
|
|
529
|
+
return respond(await handlePushOnly(apiKey, request, project, ident, tool.timeoutSeconds, tool.timeoutAction))
|
|
473
530
|
case 'push_first':
|
|
474
531
|
default:
|
|
475
|
-
return respond(await handlePushFirst(apiKey,
|
|
532
|
+
return respond(await handlePushFirst(apiKey, request, project, ident, tool.pushFirstSeconds))
|
|
476
533
|
}
|
|
477
534
|
} catch (error) {
|
|
478
535
|
process.stderr.write(`[pushary-gate] ${error?.message ?? error}\n`)
|
|
@@ -482,7 +539,7 @@ const main = async () => {
|
|
|
482
539
|
|
|
483
540
|
// Exported for scripts/pushary-gate.test.mjs. main() only runs when the gate is
|
|
484
541
|
// executed directly, so importing this file for a test does not read stdin.
|
|
485
|
-
export { normalizeRepoRemote, deriveRepoKey }
|
|
542
|
+
export { normalizeRepoRemote, deriveRepoKey, describeRequest }
|
|
486
543
|
|
|
487
544
|
const isDirectRun = process.argv[1] && import.meta.url === `file://${process.argv[1]}`
|
|
488
545
|
if (!isDirectRun) {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import assert from 'node:assert/strict'
|
|
16
16
|
import { describe, it } from 'node:test'
|
|
17
|
-
import { normalizeRepoRemote } from './pushary-gate.mjs'
|
|
17
|
+
import { describeRequest, normalizeRepoRemote } from './pushary-gate.mjs'
|
|
18
18
|
|
|
19
19
|
describe('repo identity', () => {
|
|
20
20
|
it('collapses the forms of one remote', () => {
|
|
@@ -33,3 +33,44 @@ describe('repo identity', () => {
|
|
|
33
33
|
for (const r of ['', '../rel', 'file:///Users/dev/repo']) assert.equal(normalizeRepoRemote(r), undefined, r)
|
|
34
34
|
})
|
|
35
35
|
})
|
|
36
|
+
|
|
37
|
+
// The gate answers two Cursor events now, and they arrive on the same stdin with
|
|
38
|
+
// different fields. What each one turns into is the only thing standing between
|
|
39
|
+
// an MCP call and being gated as if it were a shell command.
|
|
40
|
+
describe('what the gate was asked about', () => {
|
|
41
|
+
it('names an MCP call the way every other agent names one', () => {
|
|
42
|
+
const request = describeRequest({
|
|
43
|
+
hook_event_name: 'beforeMCPExecution',
|
|
44
|
+
tool_name: 'create_issue',
|
|
45
|
+
mcp_server_name: 'github',
|
|
46
|
+
tool_input: '{"title":"x"}',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// `mcp__<server>__<tool>` is what `validateToolPattern` accepts and what
|
|
50
|
+
// Claude Code sends, so one policy rule covers the tool on both agents. A
|
|
51
|
+
// Cursor-only spelling would silently miss every rule a user already wrote.
|
|
52
|
+
assert.equal(request.toolName, 'mcp__github__create_issue')
|
|
53
|
+
assert.deepEqual(request.toolInputs, [{ tool: 'mcp__github__create_issue', params: '{"title":"x"}' }])
|
|
54
|
+
assert.match(request.display, /create_issue/)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('still reads a shell command, including from a payload with no event name', () => {
|
|
58
|
+
const named = describeRequest({ hook_event_name: 'beforeShellExecution', command: ' rm -rf /tmp/x ' })
|
|
59
|
+
assert.equal(named.toolName, 'Bash')
|
|
60
|
+
assert.equal(named.toolTarget, 'rm -rf')
|
|
61
|
+
|
|
62
|
+
// An older Cursor that omits `hook_event_name` must keep working exactly as
|
|
63
|
+
// it did when shell was the only registered event.
|
|
64
|
+
assert.equal(describeRequest({ command: 'git push' }).toolName, 'Bash')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('declines an event it does not gate rather than calling it Bash', () => {
|
|
68
|
+
// beforeReadFile is registered by nobody here on purpose: its response
|
|
69
|
+
// schema has no `ask`, so the no-opinion path this gate takes on every error
|
|
70
|
+
// has nothing to return. If one ever arrives, it must not be mistaken for a
|
|
71
|
+
// shell command carrying no command.
|
|
72
|
+
assert.equal(describeRequest({ hook_event_name: 'beforeReadFile', file_path: '/etc/passwd' }), null)
|
|
73
|
+
assert.equal(describeRequest({ hook_event_name: 'beforeMCPExecution' }), null)
|
|
74
|
+
assert.equal(describeRequest({}), null)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleBell
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-YYTLTEGU.js";
|
|
5
5
|
import {
|
|
6
6
|
readHookInput
|
|
7
7
|
} from "../chunk-6CUUA7HO.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-YX6ZKCC5.js";
|
|
9
9
|
import "../chunk-BC3VCZ3E.js";
|
|
10
10
|
import "../chunk-6MTNS63X.js";
|
|
11
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-KBS53WOE.js";
|
|
12
12
|
import "../chunk-2UMNXADU.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-DMW54VWK.js";
|
|
14
14
|
|
|
15
15
|
// bin/pushary-bell-hook.ts
|
|
16
16
|
var main = async () => {
|
package/dist/bin/pushary-bell.js
CHANGED
|
@@ -5,18 +5,19 @@ import {
|
|
|
5
5
|
liveAgentCount,
|
|
6
6
|
ring,
|
|
7
7
|
upgradeThreshold
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-YYTLTEGU.js";
|
|
9
9
|
import {
|
|
10
10
|
guardBinary
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-B7ZBHWOW.js";
|
|
12
12
|
import "../chunk-VT4IVERX.js";
|
|
13
13
|
import {
|
|
14
14
|
claudeSettings
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-VL5ZNK6V.js";
|
|
16
|
+
import "../chunk-GIRT7677.js";
|
|
16
17
|
import {
|
|
17
18
|
createIo
|
|
18
19
|
} from "../chunk-5RUIFDTP.js";
|
|
19
|
-
import "../chunk-
|
|
20
|
+
import "../chunk-YX6ZKCC5.js";
|
|
20
21
|
import {
|
|
21
22
|
parseFlags
|
|
22
23
|
} from "../chunk-GMXKITVA.js";
|
|
@@ -30,13 +31,13 @@ import {
|
|
|
30
31
|
readJsonSafe,
|
|
31
32
|
writeJsonAtomic
|
|
32
33
|
} from "../chunk-6MTNS63X.js";
|
|
33
|
-
import "../chunk-
|
|
34
|
+
import "../chunk-KBS53WOE.js";
|
|
34
35
|
import "../chunk-7QLSKOSU.js";
|
|
35
36
|
import {
|
|
36
37
|
EXIT
|
|
37
38
|
} from "../chunk-KZERVKTD.js";
|
|
38
39
|
import "../chunk-2UMNXADU.js";
|
|
39
|
-
import "../chunk-
|
|
40
|
+
import "../chunk-DMW54VWK.js";
|
|
40
41
|
|
|
41
42
|
// src/bell/install.ts
|
|
42
43
|
import { join } from "path";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ensureDaemonRunning
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-KCAFCGGY.js";
|
|
5
5
|
import {
|
|
6
6
|
buildTranscriptRecords,
|
|
7
7
|
decodePublicKey,
|
|
@@ -9,34 +9,37 @@ import {
|
|
|
9
9
|
generateSessionKey,
|
|
10
10
|
uploadTranscriptRecords,
|
|
11
11
|
wrapSessionKey
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-QLMX5LLV.js";
|
|
13
13
|
import {
|
|
14
14
|
startProcessOwnership,
|
|
15
15
|
withSpawnProcessOwnership
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-LGVHYFIB.js";
|
|
17
17
|
import {
|
|
18
18
|
needsShell,
|
|
19
19
|
spawnClaude
|
|
20
20
|
} from "../chunk-XHKBHWLX.js";
|
|
21
21
|
import "../chunk-VT4IVERX.js";
|
|
22
|
-
import "../chunk-
|
|
22
|
+
import "../chunk-VL5ZNK6V.js";
|
|
23
23
|
import {
|
|
24
24
|
isDeferAnswer
|
|
25
25
|
} from "../chunk-YHG74UFF.js";
|
|
26
|
+
import "../chunk-GIRT7677.js";
|
|
26
27
|
import {
|
|
27
|
-
deriveToolTarget,
|
|
28
|
-
describeToolCall,
|
|
29
28
|
fetchModeState,
|
|
30
29
|
getPolicy,
|
|
31
30
|
repoKeyFor,
|
|
32
|
-
reportEvent
|
|
33
|
-
|
|
34
|
-
} from "../chunk-V2XPF77V.js";
|
|
31
|
+
reportEvent
|
|
32
|
+
} from "../chunk-VOE4SYPR.js";
|
|
35
33
|
import {
|
|
36
34
|
askUser,
|
|
37
35
|
cancelQuestion,
|
|
38
36
|
waitForAnswer
|
|
39
37
|
} from "../chunk-J3YDT4HM.js";
|
|
38
|
+
import {
|
|
39
|
+
deriveToolTarget,
|
|
40
|
+
describeToolCall,
|
|
41
|
+
scopePathFor
|
|
42
|
+
} from "../chunk-7J67NYK4.js";
|
|
40
43
|
import {
|
|
41
44
|
getMachineId
|
|
42
45
|
} from "../chunk-RN3NOEJF.js";
|
|
@@ -44,7 +47,7 @@ import "../chunk-DINDL2CF.js";
|
|
|
44
47
|
import "../chunk-6MTNS63X.js";
|
|
45
48
|
import "../chunk-BSZYIAZL.js";
|
|
46
49
|
import "../chunk-SAF6HGAA.js";
|
|
47
|
-
import "../chunk-
|
|
50
|
+
import "../chunk-KBS53WOE.js";
|
|
48
51
|
import "../chunk-7QLSKOSU.js";
|
|
49
52
|
import "../chunk-KZERVKTD.js";
|
|
50
53
|
import {
|
|
@@ -54,7 +57,7 @@ import {
|
|
|
54
57
|
import {
|
|
55
58
|
KILL_REASON,
|
|
56
59
|
resolveGate
|
|
57
|
-
} from "../chunk-
|
|
60
|
+
} from "../chunk-DMW54VWK.js";
|
|
58
61
|
|
|
59
62
|
// src/wrapper/claudeBinary.ts
|
|
60
63
|
import { statSync } from "fs";
|
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
import {
|
|
3
3
|
removeClaudeMcpServers,
|
|
4
4
|
removePusharySettings
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-2JYQAW4B.js";
|
|
6
6
|
import {
|
|
7
|
+
CURSOR_GATE_EVENTS,
|
|
7
8
|
removeInstructionBlock,
|
|
8
9
|
shortenHome,
|
|
9
10
|
unregisterPluginLocation,
|
|
10
11
|
vscodeSettingsTargets
|
|
11
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-DPUOW6NS.js";
|
|
13
|
+
import "../chunk-ZGC3W23N.js";
|
|
12
14
|
import {
|
|
13
15
|
removeGeminiSettings
|
|
14
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-K42RXJPG.js";
|
|
15
17
|
import "../chunk-ATBKJNWS.js";
|
|
16
|
-
import "../chunk-
|
|
18
|
+
import "../chunk-B7ZBHWOW.js";
|
|
17
19
|
import {
|
|
18
20
|
execNpm
|
|
19
21
|
} from "../chunk-VT4IVERX.js";
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
claudeSkillDir,
|
|
25
27
|
codexAgentsMd,
|
|
26
28
|
codexConfigToml,
|
|
29
|
+
codexHookPositions,
|
|
27
30
|
codexHooksJson,
|
|
28
31
|
codexSkillDir,
|
|
29
32
|
cursorPluginDir,
|
|
@@ -32,9 +35,12 @@ import {
|
|
|
32
35
|
geminiMd,
|
|
33
36
|
geminiSettings,
|
|
34
37
|
pusharyDir,
|
|
38
|
+
removeCodexHookTrust,
|
|
35
39
|
removeCodexHooks,
|
|
36
40
|
vscodePluginDir
|
|
37
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-VL5ZNK6V.js";
|
|
42
|
+
import "../chunk-GIRT7677.js";
|
|
43
|
+
import "../chunk-7J67NYK4.js";
|
|
38
44
|
import {
|
|
39
45
|
rejectUnknownFlags
|
|
40
46
|
} from "../chunk-GMXKITVA.js";
|
|
@@ -50,7 +56,7 @@ import "../chunk-7QLSKOSU.js";
|
|
|
50
56
|
import {
|
|
51
57
|
EXIT
|
|
52
58
|
} from "../chunk-KZERVKTD.js";
|
|
53
|
-
import "../chunk-
|
|
59
|
+
import "../chunk-DMW54VWK.js";
|
|
54
60
|
|
|
55
61
|
// bin/pushary-clean.ts
|
|
56
62
|
import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
|
|
@@ -157,11 +163,16 @@ var removeCursorUserHook = () => {
|
|
|
157
163
|
}
|
|
158
164
|
if (!parsed || typeof parsed !== "object") return "failed";
|
|
159
165
|
const hooks = parsed.hooks ?? {};
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
let removedAny = false;
|
|
167
|
+
for (const event of CURSOR_GATE_EVENTS) {
|
|
168
|
+
const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
169
|
+
const others = existing.filter((h) => !String(h.command ?? "").includes("pushary-gate"));
|
|
170
|
+
if (others.length === existing.length) continue;
|
|
171
|
+
removedAny = true;
|
|
172
|
+
if (others.length === 0) delete hooks[event];
|
|
173
|
+
else hooks[event] = others;
|
|
174
|
+
}
|
|
175
|
+
if (!removedAny) return "absent";
|
|
165
176
|
parsed.hooks = hooks;
|
|
166
177
|
try {
|
|
167
178
|
writeJson(CURSOR_USER_HOOKS, parsed);
|
|
@@ -171,10 +182,12 @@ var removeCursorUserHook = () => {
|
|
|
171
182
|
if (dryRun) return "removed";
|
|
172
183
|
try {
|
|
173
184
|
const after = JSON.parse(readFileSync(CURSOR_USER_HOOKS, "utf-8"));
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
const afterHooks = after?.hooks ?? {};
|
|
186
|
+
const stillThere = CURSOR_GATE_EVENTS.some((event) => {
|
|
187
|
+
const entries = afterHooks[event];
|
|
188
|
+
return Array.isArray(entries) && entries.some((h) => String(h?.command ?? "").includes("pushary-gate"));
|
|
189
|
+
});
|
|
190
|
+
if (stillThere) return "failed";
|
|
178
191
|
} catch {
|
|
179
192
|
return "failed";
|
|
180
193
|
}
|
|
@@ -297,11 +310,15 @@ var main = async () => {
|
|
|
297
310
|
console.log(` ${skip} Skill directory ${dim("(not found)")}`);
|
|
298
311
|
}
|
|
299
312
|
const codexConfig = codexConfigToml();
|
|
313
|
+
const codexHooksJson2 = codexHooksJson();
|
|
314
|
+
const codexHooksData = readJson(codexHooksJson2);
|
|
315
|
+
const codexPositions = codexHookPositions(codexHooksData ?? void 0);
|
|
300
316
|
try {
|
|
301
317
|
const config = readFileSync(codexConfig, "utf-8");
|
|
318
|
+
const parsed = parseTOML(config);
|
|
319
|
+
const trustRemoved = removeCodexHookTrust(parsed, codexHooksJson2, codexPositions);
|
|
302
320
|
const hadPushary = config.includes("pushary");
|
|
303
|
-
if (hadPushary) {
|
|
304
|
-
const parsed = parseTOML(config);
|
|
321
|
+
if (hadPushary || trustRemoved) {
|
|
305
322
|
const mcpServers = parsed.mcp_servers ?? {};
|
|
306
323
|
delete mcpServers.pushary;
|
|
307
324
|
if (Object.keys(mcpServers).length === 0) delete parsed.mcp_servers;
|
|
@@ -311,15 +328,14 @@ var main = async () => {
|
|
|
311
328
|
if (parsed.notify.length === 0) delete parsed.notify;
|
|
312
329
|
}
|
|
313
330
|
guardedWrite(codexConfig, stringifyTOML(parsed));
|
|
314
|
-
|
|
331
|
+
const what = trustRemoved ? "cleaned, hook trust removed" : "cleaned";
|
|
332
|
+
console.log(` ${check} Codex config ${dim(did(what))}`);
|
|
315
333
|
} else {
|
|
316
334
|
console.log(` ${skip} Codex config ${dim("(no pushary entries)")}`);
|
|
317
335
|
}
|
|
318
336
|
} catch {
|
|
319
337
|
console.log(` ${skip} Codex config ${dim("(not found)")}`);
|
|
320
338
|
}
|
|
321
|
-
const codexHooksJson2 = codexHooksJson();
|
|
322
|
-
const codexHooksData = readJson(codexHooksJson2);
|
|
323
339
|
if (codexHooksData) {
|
|
324
340
|
if (removeCodexHooks(codexHooksData)) {
|
|
325
341
|
writeJson(codexHooksJson2, codexHooksData);
|
|
@@ -6,19 +6,20 @@ import {
|
|
|
6
6
|
postLiveSession,
|
|
7
7
|
postLiveSessionUntilAccepted,
|
|
8
8
|
startLiveSessionHeartbeat
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-TV3UFGT7.js";
|
|
10
|
+
import "../chunk-QLMX5LLV.js";
|
|
11
11
|
import {
|
|
12
12
|
startProcessOwnership,
|
|
13
13
|
withSpawnProcessOwnership
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-LGVHYFIB.js";
|
|
15
15
|
import {
|
|
16
16
|
spawnClaude
|
|
17
17
|
} from "../chunk-XHKBHWLX.js";
|
|
18
18
|
import {
|
|
19
19
|
resolveBinary
|
|
20
20
|
} from "../chunk-VT4IVERX.js";
|
|
21
|
-
import "../chunk-
|
|
21
|
+
import "../chunk-VL5ZNK6V.js";
|
|
22
|
+
import "../chunk-GIRT7677.js";
|
|
22
23
|
import {
|
|
23
24
|
getMachineId
|
|
24
25
|
} from "../chunk-RN3NOEJF.js";
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
getApiKey,
|
|
28
29
|
getBaseUrl
|
|
29
30
|
} from "../chunk-2UMNXADU.js";
|
|
30
|
-
import "../chunk-
|
|
31
|
+
import "../chunk-DMW54VWK.js";
|
|
31
32
|
|
|
32
33
|
// bin/pushary-codex-bridge.ts
|
|
33
34
|
import { existsSync } from "fs";
|