@pushary/agent-hooks 0.95.8 → 0.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/bin/pushary-bell-hook.js +19 -21
  3. package/dist/bin/pushary-bell.js +6 -8
  4. package/dist/bin/pushary-claude.js +21 -23
  5. package/dist/bin/pushary-clean.js +19 -22
  6. package/dist/bin/pushary-codex-bridge.js +13 -14
  7. package/dist/bin/pushary-codex-hook.js +452 -104
  8. package/dist/bin/pushary-codex.js +8 -8
  9. package/dist/bin/pushary-connect.js +6 -6
  10. package/dist/bin/pushary-cowork.js +3 -3
  11. package/dist/bin/pushary-daemon.js +25 -27
  12. package/dist/bin/pushary-disconnect.js +6 -8
  13. package/dist/bin/pushary-doctor.js +28 -23
  14. package/dist/bin/pushary-elicitation-hook.js +19 -21
  15. package/dist/bin/pushary-gemini-bridge.js +11 -12
  16. package/dist/bin/pushary-gemini-hook.js +22 -24
  17. package/dist/bin/pushary-hook.js +24 -26
  18. package/dist/bin/pushary-login.js +7 -8
  19. package/dist/bin/pushary-logout.js +4 -4
  20. package/dist/bin/pushary-mode.js +3 -3
  21. package/dist/bin/pushary-notification-hook.js +21 -23
  22. package/dist/bin/pushary-opencode-hook.js +22 -24
  23. package/dist/bin/pushary-permission-denied-hook.js +28 -27
  24. package/dist/bin/pushary-permission-hook.js +24 -26
  25. package/dist/bin/pushary-post-hook.js +21 -23
  26. package/dist/bin/pushary-prompt-hook.js +21 -23
  27. package/dist/bin/pushary-session-end-hook.js +21 -23
  28. package/dist/bin/pushary-session-start-hook.js +24 -26
  29. package/dist/bin/pushary-setup.js +34 -38
  30. package/dist/bin/pushary-stats.js +2 -2
  31. package/dist/bin/pushary-status.js +10 -11
  32. package/dist/bin/pushary-stop-hook.js +21 -23
  33. package/dist/bin/pushary-stopfailure-hook.js +21 -23
  34. package/dist/bin/pushary-transcript-register.js +17 -19
  35. package/dist/bin/pushary-transcripts.js +5 -5
  36. package/dist/bin/pushary-upgrade.js +17 -20
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-RE25RJ5A.js → chunk-3SNTEHOH.js} +4 -4
  39. package/dist/{chunk-R3OZ5ISU.js → chunk-4AXWBCCO.js} +4 -4
  40. package/dist/{chunk-XEVOPOKT.js → chunk-5ODZEFP3.js} +1 -1
  41. package/dist/{chunk-NOVA7KZ2.js → chunk-67RNDRKL.js} +260 -32
  42. package/dist/{chunk-3YFJGKJQ.js → chunk-6VXZKOBB.js} +8 -8
  43. package/dist/{chunk-ATKGKAP6.js → chunk-6WJKYD3Q.js} +8 -8
  44. package/dist/{chunk-EMGGUO2Q.js → chunk-A4AIISZZ.js} +1 -1
  45. package/dist/{chunk-N6FAXQDD.js → chunk-AXSDMDJI.js} +1 -1
  46. package/dist/{chunk-XO3JR3OP.js → chunk-B3557BDL.js} +2 -2
  47. package/dist/{chunk-QJPYJVL3.js → chunk-BYH75I7Y.js} +1 -1
  48. package/dist/{chunk-VERXHIQF.js → chunk-C2FYSFTQ.js} +1 -1
  49. package/dist/{chunk-SKPXQRKN.js → chunk-CDSA3NTB.js} +1 -1
  50. package/dist/{chunk-HUGIKB5O.js → chunk-DGUMR5LZ.js} +6 -6
  51. package/dist/{chunk-54W7ELDQ.js → chunk-DNLFSRCF.js} +2 -2
  52. package/dist/{chunk-R6LBGKWQ.js → chunk-DOKFLZWL.js} +10 -14
  53. package/dist/{chunk-U5RIXR7I.js → chunk-EHQC3GWL.js} +1 -1
  54. package/dist/{chunk-QD73WATQ.js → chunk-FU5SQKAY.js} +15 -4
  55. package/dist/{chunk-TY6IJY6I.js → chunk-GGYH6LIE.js} +1 -1
  56. package/dist/{chunk-XZWUOCJZ.js → chunk-H4VNAOCG.js} +1 -1
  57. package/dist/{chunk-MC5WUOJJ.js → chunk-HUZW6SEO.js} +75 -1
  58. package/dist/{chunk-QV266EGS.js → chunk-IBRX7LRE.js} +8 -10
  59. package/dist/{chunk-UKMLSQXA.js → chunk-ICUBJNFL.js} +2 -2
  60. package/dist/{chunk-E47WV7BJ.js → chunk-IZKCA35M.js} +2 -2
  61. package/dist/{chunk-F4TMY6XI.js → chunk-K7MLXXPY.js} +2 -2
  62. package/dist/{chunk-Y6SCMXR6.js → chunk-KDPYXMUI.js} +2 -2
  63. package/dist/{chunk-XS33QBJA.js → chunk-LGXUGYUH.js} +50 -1
  64. package/dist/{chunk-I7LKI4WO.js → chunk-LYWTLNMG.js} +6 -8
  65. package/dist/chunk-OYTOLQ3N.js +15 -0
  66. package/dist/{chunk-XUTSZYYJ.js → chunk-QFAZ6QCT.js} +1 -4
  67. package/dist/{chunk-FRXZMCVA.js → chunk-ROTIPRM4.js} +1 -1
  68. package/dist/{chunk-BQL7UDIB.js → chunk-SW37HB27.js} +1 -1
  69. package/dist/{chunk-Z4JVEL7R.js → chunk-VCKQ45QA.js} +191 -2
  70. package/dist/{chunk-SDNTRS76.js → chunk-VDTC5DEM.js} +1 -1
  71. package/dist/{chunk-VMQU7YEJ.js → chunk-VF7YOT2W.js} +1 -1
  72. package/dist/{chunk-5GKNAQTG.js → chunk-VOC55PQV.js} +1 -1
  73. package/dist/{chunk-L7NOG547.js → chunk-VTUN7WSG.js} +1 -1
  74. package/dist/{chunk-GOPIMNVC.js → chunk-WIKEADOS.js} +1 -1
  75. package/dist/chunk-XDMHLQMH.js +202 -0
  76. package/dist/{chunk-4DUEIUUO.js → chunk-YRXVC4HL.js} +1 -1
  77. package/dist/{chunk-A22VQ5ZL.js → chunk-YX3U2CLP.js} +111 -55
  78. package/dist/src/index.js +11 -11
  79. package/package.json +1 -1
  80. package/dist/chunk-3AJIK5AO.js +0 -57
  81. package/dist/chunk-L4ZCFMWW.js +0 -177
  82. package/dist/chunk-VDI645ZX.js +0 -18
@@ -1,10 +1,14 @@
1
1
  import {
2
- defersToNativeMode
3
- } from "./chunk-VDI645ZX.js";
2
+ blockedActionFingerprint,
3
+ defersToNativeMode,
4
+ forgetBlockedActionHint,
5
+ hasBlockedActionHint,
6
+ recordBlockedActionHint
7
+ } from "./chunk-XDMHLQMH.js";
4
8
  import {
5
9
  isGatingMoment,
6
10
  recordKeylessMoment
7
- } from "./chunk-5GKNAQTG.js";
11
+ } from "./chunk-VOC55PQV.js";
8
12
  import {
9
13
  denyReasonFrom,
10
14
  isDeferAnswer
@@ -20,7 +24,7 @@ import {
20
24
  reportEvent,
21
25
  settleQuestionsForToolUse,
22
26
  throttlePass
23
- } from "./chunk-HUGIKB5O.js";
27
+ } from "./chunk-DGUMR5LZ.js";
24
28
  import {
25
29
  UNNAMED_ACTION,
26
30
  deriveAction,
@@ -30,37 +34,46 @@ import {
30
34
  describeToolCall,
31
35
  permissionDeniedAction,
32
36
  scopePathFor
33
- } from "./chunk-Z4JVEL7R.js";
37
+ } from "./chunk-VCKQ45QA.js";
34
38
  import {
35
39
  DECISION_STOP_REASON,
36
40
  askUser,
41
+ cancelQuestion,
42
+ claimBlockedAction,
37
43
  isDecisionStopped,
38
44
  isLocalQuestionHandoff,
45
+ pollForAnswer,
46
+ requestGate,
39
47
  sendNotification,
40
48
  waitForQuestion
41
- } from "./chunk-XS33QBJA.js";
49
+ } from "./chunk-LGXUGYUH.js";
42
50
  import {
43
51
  getMachineId
44
- } from "./chunk-E47WV7BJ.js";
52
+ } from "./chunk-IZKCA35M.js";
45
53
  import {
46
54
  getApiKey,
47
55
  getBaseUrl
48
56
  } from "./chunk-2UMNXADU.js";
49
57
  import {
58
+ GATE_BLOCK_REASON_MAX_LENGTH,
59
+ GATE_WIRE,
50
60
  buildDecisionEpisodeFeatures,
61
+ claudePermissionDeniedOutput,
51
62
  claudePermissionRequestOutput,
52
63
  claudePreToolUseOutput,
53
64
  effectiveWaitSeconds,
65
+ hookHoldWindowMs,
54
66
  hookWaitClamped,
55
67
  hookWaitDeadline,
56
68
  hookWritePath,
69
+ isBlockedActionRequest,
57
70
  isPermissionRequestOwnedByPreToolUse,
58
71
  isPusharyOwnTool,
59
72
  parseAgentQuestionAnswers,
60
73
  parseAgentQuestions,
61
74
  resolveGate,
62
75
  stoppedOutput
63
- } from "./chunk-MC5WUOJJ.js";
76
+ } from "./chunk-HUZW6SEO.js";
64
77
 
65
78
  // src/decision-episode.ts
66
79
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -89,7 +102,7 @@ import { basename, join } from "path";
89
102
  import { tmpdir } from "os";
90
103
  import { existsSync, writeFileSync } from "fs";
91
104
  var START_MS = Date.now();
92
- var allow = () => claudePreToolUseOutput("allow");
105
+ var allow = (reason) => claudePreToolUseOutput("allow", reason);
93
106
  var deny = (reason) => claudePreToolUseOutput("deny", reason);
94
107
  var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
95
108
  var ask = (reason) => claudePreToolUseOutput("ask", reason);
@@ -449,6 +462,45 @@ var claudeGateInput = (modeState, config, input) => {
449
462
  sessionId: input.session_id
450
463
  };
451
464
  };
465
+ var BLOCKED_ACTION_GATE_TIMEOUT_MS = 15e3;
466
+ var BLOCKED_ACTION_CLAIM_TIMEOUT_MS = 4e3;
467
+ var BLOCKED_ACTION_ALLOW_REASON = "Approved from Pushary after auto mode blocked it";
468
+ var blockReasonOf = (reason) => typeof reason === "string" ? reason.trim().slice(0, GATE_BLOCK_REASON_MAX_LENGTH).trim() || void 0 : void 0;
469
+ var blockedActionFingerprintFor = (input) => blockedActionFingerprint({
470
+ sessionId: input.session_id,
471
+ toolName: input.tool_name,
472
+ toolInput: input.tool_input,
473
+ cwd: input.cwd
474
+ });
475
+ var blockedActionGateRequest = (input, event) => ({
476
+ v: GATE_WIRE,
477
+ source: "claude",
478
+ event,
479
+ toolName: input.tool_name,
480
+ toolInputs: [input.tool_input ?? {}],
481
+ sessionId: input.session_id,
482
+ toolUseId: input.tool_use_id,
483
+ machineId: getMachineId(),
484
+ cwd: input.cwd,
485
+ repoKey: repoKeyFor(input.cwd),
486
+ permissionMode: input.permission_mode,
487
+ ...event === "PermissionDenied" ? { createDecision: true, localSurfaceAvailable: false, blockReason: blockReasonOf(input.reason) } : {}
488
+ });
489
+ var claimedBlockedActionRetry = async (apiKey, input) => {
490
+ const fingerprint = blockedActionFingerprintFor(input);
491
+ if (!fingerprint || !hasBlockedActionHint(fingerprint)) return false;
492
+ try {
493
+ const granted = await claimBlockedAction(
494
+ apiKey,
495
+ blockedActionGateRequest(input, "PreToolUse"),
496
+ BLOCKED_ACTION_CLAIM_TIMEOUT_MS
497
+ );
498
+ if (granted !== null) forgetBlockedActionHint(fingerprint);
499
+ return granted === true;
500
+ } catch {
501
+ return false;
502
+ }
503
+ };
452
504
  var handlePreToolUse = async (input) => {
453
505
  if (isPusharyOwnTool(input.tool_name)) return void 0;
454
506
  let apiKey;
@@ -469,6 +521,7 @@ var handlePreToolUse = async (input) => {
469
521
  if (input.tool_name === "ExitPlanMode") {
470
522
  return handleExitPlanMode(apiKey, input);
471
523
  }
524
+ if (await claimedBlockedActionRetry(apiKey, input)) return allow(BLOCKED_ACTION_ALLOW_REASON);
472
525
  if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
473
526
  return void 0;
474
527
  }
@@ -531,12 +584,6 @@ var handlePermissionRequest = async (input) => {
531
584
  return void 0;
532
585
  }
533
586
  };
534
- var PERMISSION_DENIED_WINDOW_MS = 5 * 60 * 1e3;
535
- var PERMISSION_DENIED_WAIT_SECONDS = 25;
536
- var automodeRetryEnabled = () => {
537
- const flag = process.env.PUSHARY_AUTOMODE_RETRY;
538
- return flag === "1" || flag === "true";
539
- };
540
587
  var reportPermissionDenied = (input) => reportEvent({
541
588
  event: "permission_denied",
542
589
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -548,9 +595,48 @@ var reportPermissionDenied = (input) => reportEvent({
548
595
  // Side report, not a command carrier. Without timeoutMs this takes the phone's
549
596
  // queued instruction and acknowledges it delivered, and this hook can print
550
597
  // only {retry}. 4s rather than the Notification path's 2.5s because
551
- // PermissionDenied is given a 60s budget, not 10s.
598
+ // PermissionDenied holds the agent for the full hook budget, not 10s.
552
599
  }, { timeoutMs: 4e3 }).catch(() => void 0);
553
- var handlePermissionDenied = async (input) => {
600
+ var isAffirmativeAnswer = (answer) => answer.answered && !isDecisionStopped(answer) && answer.value === "yes";
601
+ var PERMISSION_DENIED_NOTICE_WINDOW_MS = 5 * 60 * 1e3;
602
+ var blockedActionHoldEndsAt = (holdBudgetSeconds) => START_MS + hookHoldWindowMs("claude", holdBudgetSeconds);
603
+ var notifyPermissionDenied = async (apiKey, input) => {
604
+ const sessionKey = input.session_id || DEFAULT_SESSION;
605
+ if (!throttlePass(`permdenied:${sessionKey}:${input.tool_name}`, PERMISSION_DENIED_NOTICE_WINDOW_MS)) return;
606
+ const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
607
+ if (mode?.kill || mode?.mode === "terminal_only") return;
608
+ const projectName = basename(input.cwd ?? process.cwd());
609
+ await sendNotification(apiKey, {
610
+ title: `Auto mode blocked ${input.tool_name}`,
611
+ body: `Your agent was denied ${input.tool_name} in ${projectName}.`,
612
+ agentName: `Claude Code - ${projectName}`,
613
+ sessionId: input.session_id
614
+ }, 4e3).catch(() => void 0);
615
+ };
616
+ var isRetryOutput = (output) => {
617
+ if (!output) return false;
618
+ try {
619
+ const parsed = JSON.parse(output);
620
+ return parsed?.hookSpecificOutput?.hookEventName === "PermissionDenied" && parsed.hookSpecificOutput.retry === true;
621
+ } catch {
622
+ return false;
623
+ }
624
+ };
625
+ var retryOnce = (fingerprint) => recordBlockedActionHint(fingerprint) ? claudePermissionDeniedOutput("allow") : void 0;
626
+ var askAboutBlockedAction = async (apiKey, input, fingerprint, holdEndsAt) => {
627
+ const reply = await requestGate(apiKey, blockedActionGateRequest(input, "PermissionDenied"), BLOCKED_ACTION_GATE_TIMEOUT_MS);
628
+ if (!reply) return void 0;
629
+ if (reply.kind === "no_opinion") return isRetryOutput(reply.output) ? retryOnce(fingerprint) : void 0;
630
+ if (reply.kind !== "ask" || !reply.correlationId || reply.deadlineSeconds === void 0) return void 0;
631
+ if (reply.noDevices) {
632
+ await cancelQuestion(apiKey, reply.correlationId, AbortSignal.timeout(4e3)).catch(() => void 0);
633
+ return void 0;
634
+ }
635
+ const deadline = Math.min(hookWaitDeadline(START_MS, reply.deadlineSeconds, "claude", Date.now()), holdEndsAt);
636
+ const answer = await pollForAnswer(apiKey, reply.correlationId, deadline);
637
+ return isAffirmativeAnswer(answer) ? retryOnce(fingerprint) : void 0;
638
+ };
639
+ var handlePermissionDenied = async (input, holdBudgetSeconds) => {
554
640
  await reportPermissionDenied(input);
555
641
  if (isPusharyOwnTool(input.tool_name)) return void 0;
556
642
  let apiKey;
@@ -560,46 +646,16 @@ var handlePermissionDenied = async (input) => {
560
646
  return void 0;
561
647
  }
562
648
  try {
563
- const projectName = basename(input.cwd ?? process.cwd());
564
- const sessionKey = input.session_id || DEFAULT_SESSION;
565
- await settleQuestionsForToolUse(sessionKey, input.tool_use_id);
566
- if (!throttlePass(`permdenied:${sessionKey}:${input.tool_name}`, PERMISSION_DENIED_WINDOW_MS)) return void 0;
567
- const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
568
- if (mode?.kill || mode?.mode === "terminal_only") return void 0;
569
- if (!automodeRetryEnabled()) {
570
- await sendNotification(apiKey, {
571
- title: `Auto-mode blocked ${input.tool_name}`,
572
- body: `Your agent was denied ${input.tool_name} in ${projectName}.`,
573
- agentName: `Claude Code - ${projectName}`,
574
- sessionId: input.session_id
575
- }, 4e3).catch(() => {
576
- });
649
+ await settleQuestionsForToolUse(input.session_id || DEFAULT_SESSION, input.tool_use_id);
650
+ if (!isBlockedActionRequest({ source: "claude", event: "PermissionDenied", toolName: input.tool_name })) return void 0;
651
+ const fingerprint = blockedActionFingerprintFor(input);
652
+ if (!fingerprint) return void 0;
653
+ const holdEndsAt = blockedActionHoldEndsAt(holdBudgetSeconds);
654
+ if (holdEndsAt - Date.now() <= BLOCKED_ACTION_GATE_TIMEOUT_MS) {
655
+ await notifyPermissionDenied(apiKey, input);
577
656
  return void 0;
578
657
  }
579
- let result;
580
- try {
581
- result = await askUser(apiKey, {
582
- question: `Auto-mode blocked ${input.tool_name} in ${projectName}. Let the agent retry it?`,
583
- type: "confirm",
584
- context: "Your agent hit a permission wall in auto mode",
585
- agentName: `Claude Code - ${projectName}`,
586
- sessionId: input.session_id,
587
- machineId: getMachineId(),
588
- toolName: input.tool_name,
589
- // Suffixed because the PreToolUse gate may already have asked about this
590
- // same tool call. Without it, the retry prompt would collide with that
591
- // decision and hand back its answer instead of asking a new question.
592
- toolUseId: input.tool_use_id ? `${input.tool_use_id}#retry` : void 0
593
- });
594
- } catch {
595
- return void 0;
596
- }
597
- const deadline = hookWaitDeadline(START_MS, PERMISSION_DENIED_WAIT_SECONDS, "claude", Date.now());
598
- const answer = await waitForQuestion(apiKey, result, deadline);
599
- if (answer.answered && answer.value === "yes") {
600
- return { hookSpecificOutput: { hookEventName: "PermissionDenied", retry: true } };
601
- }
602
- return void 0;
658
+ return await askAboutBlockedAction(apiKey, input, fingerprint, holdEndsAt);
603
659
  } catch {
604
660
  return void 0;
605
661
  }
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-A22VQ5ZL.js";
4
- import "../chunk-VDI645ZX.js";
5
- import "../chunk-5GKNAQTG.js";
3
+ } from "../chunk-YX3U2CLP.js";
4
+ import "../chunk-XDMHLQMH.js";
5
+ import "../chunk-VOC55PQV.js";
6
6
  import "../chunk-YHG74UFF.js";
7
7
  import {
8
8
  fetchAccountPublicKey
@@ -15,28 +15,28 @@ import {
15
15
  handlePostToolUse,
16
16
  handleStop,
17
17
  reportEvent
18
- } from "../chunk-HUGIKB5O.js";
19
- import "../chunk-Z4JVEL7R.js";
20
- import "../chunk-XUTSZYYJ.js";
18
+ } from "../chunk-DGUMR5LZ.js";
19
+ import "../chunk-VCKQ45QA.js";
20
+ import "../chunk-QFAZ6QCT.js";
21
21
  import {
22
22
  askUser,
23
23
  cancelQuestion,
24
24
  waitForAnswer
25
- } from "../chunk-XS33QBJA.js";
26
- import "../chunk-E47WV7BJ.js";
27
- import "../chunk-NOVA7KZ2.js";
25
+ } from "../chunk-LGXUGYUH.js";
26
+ import "../chunk-IZKCA35M.js";
27
+ import "../chunk-67RNDRKL.js";
28
28
  import "../chunk-EYRGBYZG.js";
29
29
  import "../chunk-SAF6HGAA.js";
30
30
  import "../chunk-7QLSKOSU.js";
31
31
  import "../chunk-KZERVKTD.js";
32
- import "../chunk-L7NOG547.js";
32
+ import "../chunk-VTUN7WSG.js";
33
33
  import {
34
34
  getApiKey,
35
35
  getBaseUrl
36
36
  } from "../chunk-2UMNXADU.js";
37
37
  import {
38
38
  resolvePolicy
39
- } from "../chunk-MC5WUOJJ.js";
39
+ } from "../chunk-HUZW6SEO.js";
40
40
  export {
41
41
  askUser,
42
42
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.95.8",
3
+ "version": "0.97.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -1,57 +0,0 @@
1
- import {
2
- quoteIfNeeded
3
- } from "./chunk-L4ZCFMWW.js";
4
-
5
- // src/hook-command.ts
6
- var HOOK_SHELL = "/bin/sh -c";
7
- var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
8
- var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
9
- var CURSOR_NO_OPINION = { permission: "deny", user_message: "Command denied via Pushary.", agent_message: "Pushary hook is unavailable. Repair the integration before retrying." };
10
- var shellDoubleQuoted = (text) => `"${text.replace(/(["\\$`])/g, "\\$1")}"`;
11
- var CURSOR_NATIVE_PROMPT = { permission: "ask" };
12
- var CURSOR_NATIVE_PROMPT_EVENTS = ["beforeShellExecution", "beforeMCPExecution"];
13
- var hookEventIsOneOf = (events) => events.flatMap((event) => [`*\\"hook_event_name\\":\\"${event}\\"*`, `*\\"hook_event_name\\":\\ \\"${event}\\"*`]).join("|");
14
- var CURSOR_MISS = `{ E=$(cat); case "$E" in ${hookEventIsOneOf(CURSOR_NATIVE_PROMPT_EVENTS)}) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NATIVE_PROMPT))} ;; *) printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NO_OPINION))} ;; esac; exit 0; }`;
15
- var HOOK_GUARD_SCRIPTS = {
16
- native_binary: `${LOCATE}; exec "$B" "$@"`,
17
- node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B" "$@"`,
18
- node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0" "$@"`,
19
- cursor_gate_script: `[ -f "$0" ] || ${CURSOR_MISS}; command -v node >/dev/null 2>&1 || ${CURSOR_MISS}; exec node "$0" "$@"`
20
- };
21
- var BINARY_GUARDS = {
22
- native: "native_binary",
23
- node: "node_binary"
24
- };
25
- var guardKindOf = (target) => {
26
- switch (target.kind) {
27
- case "node":
28
- return "node_script";
29
- case "cursor_gate":
30
- return "cursor_gate_script";
31
- case "binary":
32
- return BINARY_GUARDS[target.runtime];
33
- }
34
- };
35
- var targetPath = (target) => target.kind === "binary" ? target.path : target.script;
36
- var runsThroughNode = (target) => target.kind !== "binary";
37
- var guardHookCommand = (target, platform = process.platform) => {
38
- const path = quoteIfNeeded(targetPath(target));
39
- if (platform === "win32") return runsThroughNode(target) ? `node ${path}` : path;
40
- return `${HOOK_SHELL} '${HOOK_GUARD_SCRIPTS[guardKindOf(target)]}' ${path}`;
41
- };
42
- var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
43
- var guardCursorGate = (script, platform) => guardHookCommand({ kind: "cursor_gate", script }, platform);
44
- var guardedArgv = (target, args = [], platform = process.platform) => {
45
- const path = targetPath(target);
46
- if (platform === "win32") {
47
- return runsThroughNode(target) ? ["node", path, ...args] : [path, ...args];
48
- }
49
- const [shell, ...shellArgs] = HOOK_SHELL.split(/\s+/);
50
- return [shell, ...shellArgs, HOOK_GUARD_SCRIPTS[guardKindOf(target)], path, ...args];
51
- };
52
-
53
- export {
54
- guardBinary,
55
- guardCursorGate,
56
- guardedArgv
57
- };
@@ -1,177 +0,0 @@
1
- // src/npm.ts
2
- import { execFileSync, execSync } from "child_process";
3
- import { existsSync, readFileSync, realpathSync } from "fs";
4
- import { dirname, isAbsolute, join, relative, resolve } from "path";
5
- var cleanNpmEnv = () => {
6
- const env = {};
7
- for (const [key, value] of Object.entries(process.env)) {
8
- if (key.toLowerCase().startsWith("npm_config_workspace")) continue;
9
- env[key] = value;
10
- }
11
- return env;
12
- };
13
- var npmErrorMessage = (err) => {
14
- const e = err;
15
- const text = [e?.stderr, e?.stdout].map((part) => part ? part.toString() : "").join("\n");
16
- const line = text.split("\n").map((l) => l.replace(/^npm error\s*/i, "").trim()).find((l) => l && !l.startsWith("A complete log") && !/^code\s/i.test(l));
17
- return line || e?.message || String(err);
18
- };
19
- var execNpm = (args, options = {}) => {
20
- return execSync(`npm ${args}`, {
21
- timeout: 12e4,
22
- stdio: "pipe",
23
- ...options,
24
- env: { ...cleanNpmEnv(), ...options.env ?? {} }
25
- });
26
- };
27
- var resolveGlobalBinary = (name) => {
28
- try {
29
- const prefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
30
- const candidates = process.platform === "win32" ? [join(prefix, `${name}.cmd`), join(prefix, name)] : [join(prefix, "bin", name)];
31
- return candidates.find(existsSync);
32
- } catch {
33
- return void 0;
34
- }
35
- };
36
- var resolvePathBinary = (name, platform = process.platform) => {
37
- try {
38
- return execFileSync(platform === "win32" ? "where" : "which", [name], {
39
- encoding: "utf8",
40
- stdio: "pipe",
41
- timeout: 5e3
42
- }).split(/\r?\n/, 1)[0]?.trim() || void 0;
43
- } catch {
44
- return void 0;
45
- }
46
- };
47
- var resolveBinary = (name) => resolveGlobalBinary(name) ?? resolvePathBinary(name);
48
- var resolvePackageVersionFromBinary = (binary, packageName) => {
49
- try {
50
- let dir = dirname(realpathSync(binary));
51
- const packagePath = packageName.split("/");
52
- for (let depth = 0; depth < 8; depth++) {
53
- for (const manifest of [
54
- join(dir, "package.json"),
55
- join(dir, "node_modules", ...packagePath, "package.json")
56
- ]) {
57
- try {
58
- const value = JSON.parse(readFileSync(manifest, "utf8"));
59
- if (value.name === packageName && typeof value.version === "string") return value.version;
60
- } catch {
61
- }
62
- }
63
- const parent = dirname(dir);
64
- if (parent === dir) break;
65
- dir = parent;
66
- }
67
- } catch {
68
- }
69
- return void 0;
70
- };
71
- var resolveGlobalPackageVersion = (name) => {
72
- try {
73
- const result = JSON.parse(execNpm("list -g --depth=0 --json --no-workspaces", { timeout: 5e3 }).toString());
74
- const value = result.dependencies?.[name]?.version;
75
- return typeof value === "string" ? value : void 0;
76
- } catch {
77
- return void 0;
78
- }
79
- };
80
- var localImportsPresent = (entry, packageDir, seen = /* @__PURE__ */ new Set()) => {
81
- if (seen.has(entry)) return true;
82
- seen.add(entry);
83
- try {
84
- const source = readFileSync(entry, "utf8");
85
- for (const match of source.matchAll(/\b(?:import|export)\s+(?:[^"'()]*?\s+from\s+)?["'](\.{1,2}\/[^"']+)["']|\bimport\s*\(\s*["'](\.{1,2}\/[^"']+)["']/g)) {
86
- const dependency = resolve(dirname(entry), match[1] ?? match[2]);
87
- const localPath = relative(packageDir, dependency);
88
- if (localPath.startsWith("..") || isAbsolute(localPath) || !existsSync(dependency)) return false;
89
- if (/\.[cm]?js$/.test(dependency) && !localImportsPresent(dependency, packageDir, seen)) return false;
90
- }
91
- return true;
92
- } catch {
93
- return false;
94
- }
95
- };
96
- var packageBinsPresent = (packageDir, prefix, platform = process.platform, requiredFiles = []) => {
97
- try {
98
- const manifest = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
99
- if (!manifest.bin || typeof manifest.bin !== "object" || Array.isArray(manifest.bin)) return false;
100
- const bins = Object.entries(manifest.bin);
101
- if (bins.length === 0) return false;
102
- if (!requiredFiles.every((path) => existsSync(join(packageDir, path)))) return false;
103
- const seen = /* @__PURE__ */ new Set();
104
- return bins.every(([name, target]) => {
105
- if (typeof target !== "string") return false;
106
- const shims = platform === "win32" ? [join(prefix, `${name}.cmd`), join(prefix, name)] : [join(prefix, "bin", name)];
107
- const entry = join(packageDir, target);
108
- return existsSync(entry) && shims.some(existsSync) && localImportsPresent(entry, packageDir, seen);
109
- });
110
- } catch {
111
- return false;
112
- }
113
- };
114
- var AGENT_HOOKS_RUNTIME_FILES = [
115
- "data/SKILL.md",
116
- "data/cursor-plugin/.cursor-plugin/plugin.json",
117
- "data/cursor-plugin/hooks/hooks.json",
118
- "data/cursor-plugin/mcp.json",
119
- "data/cursor-plugin/scripts/pushary-gate.mjs",
120
- "data/vscode-plugin/.claude-plugin/plugin.json",
121
- "data/vscode-plugin/hooks/hooks.json",
122
- "data/vscode-plugin/.mcp.json",
123
- "data/vscode-plugin/scripts/pushary-gate.mjs"
124
- ];
125
- var globalPackageBinsPresent = (name) => {
126
- try {
127
- const root = execNpm("root -g --no-workspaces", { timeout: 5e3 }).toString().trim();
128
- const prefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
129
- return packageBinsPresent(join(root, ...name.split("/")), prefix, process.platform, AGENT_HOOKS_RUNTIME_FILES);
130
- } catch {
131
- return false;
132
- }
133
- };
134
- var resolveGlobalBinDir = (probe) => {
135
- if (process.platform === "win32") return void 0;
136
- const resolved = resolveGlobalBinary(probe);
137
- return resolved ? resolved.slice(0, resolved.length - probe.length - 1) : void 0;
138
- };
139
- var quoteIfNeeded = (path) => /\s/.test(path) ? `"${path}"` : path;
140
- var needsGlobalInstall = (requested, installed, binaryPresent) => requested !== installed || !binaryPresent;
141
- var installState;
142
- var installGlobally = (options) => {
143
- if (installState) {
144
- return installState.then((result) => result === "installed" ? "cached" : result);
145
- }
146
- const started = (async () => {
147
- const pinned = process.env.PUSHARY_PIN_GLOBAL_VERSION !== "0";
148
- if (pinned && !needsGlobalInstall(
149
- options.version,
150
- resolveGlobalPackageVersion("@pushary/agent-hooks"),
151
- globalPackageBinsPresent("@pushary/agent-hooks")
152
- )) {
153
- return "skipped";
154
- }
155
- const spec = pinned && options.version && options.version !== "0.0.0" ? `@pushary/agent-hooks@${options.version}` : "@pushary/agent-hooks@latest";
156
- execNpm(`install -g --no-workspaces ${spec}`, { timeout: options.timeoutMs ?? 12e4 });
157
- return "installed";
158
- })();
159
- installState = started;
160
- started.catch(() => {
161
- if (installState === started) installState = void 0;
162
- });
163
- return started;
164
- };
165
-
166
- export {
167
- npmErrorMessage,
168
- execNpm,
169
- resolveGlobalBinary,
170
- resolvePathBinary,
171
- resolveBinary,
172
- resolvePackageVersionFromBinary,
173
- resolveGlobalPackageVersion,
174
- resolveGlobalBinDir,
175
- quoteIfNeeded,
176
- installGlobally
177
- };
@@ -1,18 +0,0 @@
1
- import {
2
- shouldDeferToNativeMode
3
- } from "./chunk-MC5WUOJJ.js";
4
-
5
- // src/native-defer.ts
6
- var nativeDeferOptions = () => {
7
- const respect = process.env.PUSHARY_RESPECT_PERMISSION_MODE;
8
- const bypass = process.env.PUSHARY_DEFER_BYPASS;
9
- return {
10
- respectPermissionMode: !(respect === "0" || respect === "false"),
11
- deferBypass: bypass === "1" || bypass === "true"
12
- };
13
- };
14
- var defersToNativeMode = (permissionMode, toolName) => shouldDeferToNativeMode(permissionMode, toolName, nativeDeferOptions());
15
-
16
- export {
17
- defersToNativeMode
18
- };