@pushary/agent-hooks 1.9.7 → 1.9.9

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 (111) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +3 -3
  3. package/data/cursor-plugin/README.md +3 -7
  4. package/data/vscode-plugin/README.md +1 -1
  5. package/dist/bin/pushary-bell-hook.js +22 -24
  6. package/dist/bin/pushary-bell.js +13 -13
  7. package/dist/bin/pushary-claude.js +28 -30
  8. package/dist/bin/pushary-clean.js +113 -94
  9. package/dist/bin/pushary-codex-bridge.js +20 -20
  10. package/dist/bin/pushary-codex-hook.js +36 -38
  11. package/dist/bin/pushary-codex.js +13 -13
  12. package/dist/bin/pushary-connect.js +48 -22
  13. package/dist/bin/pushary-cowork.js +5 -5
  14. package/dist/bin/pushary-daemon.js +50 -40
  15. package/dist/bin/pushary-disconnect.js +188 -56
  16. package/dist/bin/pushary-doctor.js +105 -85
  17. package/dist/bin/pushary-elicitation-hook.js +25 -27
  18. package/dist/bin/pushary-gemini-bridge.js +13 -13
  19. package/dist/bin/pushary-gemini-hook.js +31 -33
  20. package/dist/bin/pushary-hook.js +36 -39
  21. package/dist/bin/pushary-login.js +12 -12
  22. package/dist/bin/pushary-logout.js +105 -42
  23. package/dist/bin/pushary-mcp.js +9 -9
  24. package/dist/bin/pushary-mode.js +28 -31
  25. package/dist/bin/pushary-notification-hook.js +29 -32
  26. package/dist/bin/pushary-opencode-hook.js +30 -32
  27. package/dist/bin/pushary-permission-denied-hook.js +30 -33
  28. package/dist/bin/pushary-permission-hook.js +33 -36
  29. package/dist/bin/pushary-post-hook.js +30 -33
  30. package/dist/bin/pushary-prompt-hook.js +29 -32
  31. package/dist/bin/pushary-session-end-hook.js +29 -32
  32. package/dist/bin/pushary-session-start-hook.js +33 -36
  33. package/dist/bin/pushary-setup.js +168 -160
  34. package/dist/bin/pushary-stats.js +5 -5
  35. package/dist/bin/pushary-status.js +65 -19
  36. package/dist/bin/pushary-stop-hook.js +29 -32
  37. package/dist/bin/pushary-stopfailure-hook.js +29 -32
  38. package/dist/bin/pushary-suggestions.js +19 -21
  39. package/dist/bin/pushary-transcript-register.js +18 -20
  40. package/dist/bin/pushary-transcripts.js +7 -7
  41. package/dist/bin/pushary-upgrade.js +50 -26
  42. package/dist/bin/pushary-wait.js +23 -19
  43. package/dist/bin/pushary.js +15 -14
  44. package/dist/{chunk-4FK6RZVJ.js → chunk-462EQA6K.js} +1 -1
  45. package/dist/{chunk-STQRUH3S.js → chunk-5DCDQ5VW.js} +59 -15
  46. package/dist/{chunk-JXISQ7DT.js → chunk-5N5L6DNN.js} +3 -3
  47. package/dist/{chunk-QR6VE5ME.js → chunk-5RGA636G.js} +79 -17
  48. package/dist/{chunk-L76HTPU6.js → chunk-6AYSKVLL.js} +3 -3
  49. package/dist/{chunk-NIBFSARF.js → chunk-6EBGOHDJ.js} +33 -13
  50. package/dist/{chunk-QQ4HBXZC.js → chunk-6W5M3MSE.js} +84 -673
  51. package/dist/{chunk-R3YW55MY.js → chunk-AI7RMFVV.js} +29 -26
  52. package/dist/{chunk-AUUXS44H.js → chunk-APP43AW7.js} +1 -1
  53. package/dist/{chunk-RQGODFQN.js → chunk-AZOKPWVI.js} +1 -1
  54. package/dist/{chunk-3GNUXXDZ.js → chunk-B5NMSP3V.js} +2 -2
  55. package/dist/{chunk-4D2AYNSE.js → chunk-BNSKVIAW.js} +2 -2
  56. package/dist/chunk-BYLKCZ2G.js +367 -0
  57. package/dist/{chunk-X5NXPCIC.js → chunk-BZLHTMT6.js} +89 -7
  58. package/dist/{chunk-BTQGAXZ3.js → chunk-COQ64C4F.js} +10 -10
  59. package/dist/{chunk-N4EUSDKX.js → chunk-DCCSULXK.js} +14 -14
  60. package/dist/{chunk-JWKBDSPF.js → chunk-DP3WX7FW.js} +2 -2
  61. package/dist/chunk-E5HOHIUV.js +52 -0
  62. package/dist/{chunk-GU3EJCVV.js → chunk-EA5NWDK7.js} +4 -0
  63. package/dist/chunk-EIAEUG7G.js +46 -0
  64. package/dist/{chunk-EB4XGVBG.js → chunk-EUW7JL2K.js} +0 -32
  65. package/dist/{chunk-T7Y5EADL.js → chunk-F7R3SZ4Q.js} +2 -2
  66. package/dist/{chunk-H37L7TT3.js → chunk-G7RIDXAT.js} +2 -2
  67. package/dist/{chunk-W2KI5KPD.js → chunk-H5T27W7M.js} +2 -2
  68. package/dist/chunk-HBWYMRR5.js +94 -0
  69. package/dist/{chunk-CBQACHKY.js → chunk-IIVZZMSO.js} +1 -1
  70. package/dist/{chunk-Z6HPS67T.js → chunk-IRHABAQT.js} +1 -1
  71. package/dist/{chunk-QTO6HEZD.js → chunk-K545B734.js} +2 -2
  72. package/dist/{chunk-KYO45G5L.js → chunk-KOWFRUIT.js} +1 -1
  73. package/dist/{chunk-QFEM7R6T.js → chunk-KQFTBQ66.js} +11 -11
  74. package/dist/chunk-LENBKYRB.js +50 -0
  75. package/dist/{chunk-JCWZ23MJ.js → chunk-M3STIAGU.js} +3 -3
  76. package/dist/{chunk-6DJTDHBI.js → chunk-MD4SVNT6.js} +1 -1
  77. package/dist/{chunk-7264S5HZ.js → chunk-MLGIVVSG.js} +1 -1
  78. package/dist/{chunk-H7MMU64M.js → chunk-NGKUXZID.js} +2 -2
  79. package/dist/{chunk-JWQZSYCK.js → chunk-NUVGDBZC.js} +1 -1
  80. package/dist/{chunk-AQ55ZSSS.js → chunk-OAYWO5NP.js} +1 -1
  81. package/dist/chunk-Q6C6ILUS.js +49 -0
  82. package/dist/{chunk-MBK7N46B.js → chunk-QIOIO7VN.js} +6 -6
  83. package/dist/{chunk-KFKL3FHS.js → chunk-QRXPWW3M.js} +4 -3
  84. package/dist/{chunk-7OIWWZAD.js → chunk-QTIUJZRX.js} +3 -3
  85. package/dist/{chunk-XWAYAO3V.js → chunk-R5RIVOPL.js} +2 -2
  86. package/dist/{chunk-ADSQDYYG.js → chunk-REU2VQPH.js} +54 -5
  87. package/dist/{chunk-GKI6LFAV.js → chunk-RQ32ADJP.js} +2 -2
  88. package/dist/{chunk-VXRXFU6Q.js → chunk-SZ4Q6ZVS.js} +14 -7
  89. package/dist/{chunk-KT5E2RLS.js → chunk-T6ZN4TEF.js} +1 -1
  90. package/dist/{chunk-UFVGSEWH.js → chunk-TO3PS646.js} +2 -2
  91. package/dist/chunk-UAP5DS6P.js +599 -0
  92. package/dist/{chunk-MZPXVH4A.js → chunk-UF6RBP5V.js} +204 -28
  93. package/dist/{chunk-AVNKVF7N.js → chunk-UPIZSCYD.js} +4 -4
  94. package/dist/{chunk-3PVDOCFI.js → chunk-VHPFNLRS.js} +5 -5
  95. package/dist/{chunk-HPRZJKRO.js → chunk-VIIHC42X.js} +1 -1
  96. package/dist/{chunk-D6S5EKXA.js → chunk-WRPYCIV2.js} +8 -12
  97. package/dist/{chunk-ZUTU43FT.js → chunk-Z23ZBYDW.js} +6 -4
  98. package/dist/{chunk-SBLPHUEB.js → chunk-ZGADIZDS.js} +8 -6
  99. package/dist/{global-upgrade-E2HUEB4B.js → global-upgrade-H272QOGD.js} +6 -6
  100. package/dist/{paths-QVTEGNZV.js → paths-HMVOTFUN.js} +2 -2
  101. package/dist/reapply-Q5NHDTVU.js +55 -0
  102. package/dist/src/index.d.ts +1 -0
  103. package/dist/src/index.js +19 -20
  104. package/dist/{upgrade-lock-JCIDZMHF.js → upgrade-lock-L3GHFIRW.js} +3 -3
  105. package/package.json +1 -1
  106. package/dist/chunk-4YO4T5FV.js +0 -149
  107. package/dist/chunk-ANEAA37E.js +0 -197
  108. package/dist/chunk-MNWRUGMH.js +0 -86
  109. package/dist/chunk-RQSOEB4S.js +0 -44
  110. package/dist/chunk-SD3272LS.js +0 -143
  111. package/dist/reapply-5QVKM6LA.js +0 -38
@@ -1,21 +1,21 @@
1
- import {
2
- activeHookSource,
3
- activeIdentity
4
- } from "./chunk-MZPXVH4A.js";
5
1
  import {
6
2
  defersToNativeMode,
7
3
  reportMachineDenial
8
- } from "./chunk-3GNUXXDZ.js";
4
+ } from "./chunk-B5NMSP3V.js";
9
5
  import {
10
6
  isGatingMoment,
11
7
  recordKeylessMoment
12
- } from "./chunk-CBQACHKY.js";
8
+ } from "./chunk-IIVZZMSO.js";
13
9
  import {
14
10
  announceSubscriptionRequired,
15
11
  denyReasonFrom,
16
12
  isDeferAnswer,
17
13
  oncePerSession
18
14
  } from "./chunk-5OP5MQI7.js";
15
+ import {
16
+ activeHookSource,
17
+ activeIdentity
18
+ } from "./chunk-UF6RBP5V.js";
19
19
  import {
20
20
  UNNAMED_ACTION,
21
21
  approvalAwaitingRetry,
@@ -39,13 +39,11 @@ import {
39
39
  reportEvent,
40
40
  scopePathFor,
41
41
  settleQuestionsForToolUse
42
- } from "./chunk-QFEM7R6T.js";
42
+ } from "./chunk-KQFTBQ66.js";
43
43
  import {
44
44
  DEFAULT_SESSION
45
45
  } from "./chunk-N6IOSZOE.js";
46
46
  import {
47
- DECISION_STOP_REASON,
48
- UNVERIFIABLE_APPROVAL_REASON,
49
47
  askUser,
50
48
  cancelQuestion,
51
49
  claimBlockedAction,
@@ -55,24 +53,28 @@ import {
55
53
  requestGate,
56
54
  sendNotification,
57
55
  waitForQuestion
58
- } from "./chunk-D6S5EKXA.js";
56
+ } from "./chunk-WRPYCIV2.js";
59
57
  import {
60
58
  SubscriptionRequiredError
61
- } from "./chunk-L76HTPU6.js";
59
+ } from "./chunk-6AYSKVLL.js";
62
60
  import {
63
61
  getMachineId
64
- } from "./chunk-KYO45G5L.js";
62
+ } from "./chunk-KOWFRUIT.js";
65
63
  import {
66
64
  getApiKey,
67
65
  getBaseUrl
68
- } from "./chunk-Z6HPS67T.js";
66
+ } from "./chunk-IRHABAQT.js";
69
67
  import {
70
68
  NPX_LATEST_COMMAND
71
- } from "./chunk-STQRUH3S.js";
69
+ } from "./chunk-5DCDQ5VW.js";
72
70
  import {
73
71
  AGENT_QUESTION_STRATEGY,
72
+ DECISION_STOP_REASON,
74
73
  GATE_BLOCK_REASON_MAX_LENGTH,
75
74
  GATE_WIRE,
75
+ PLAN_DECLINED_REASON,
76
+ UNVERIFIABLE_APPROVAL_REASON,
77
+ WITHDRAWN_APPROVAL_REASON,
76
78
  agentQuestionStrategy,
77
79
  agentRulesAllowEveryInput,
78
80
  buildDecisionEpisodeFeatures,
@@ -82,8 +84,9 @@ import {
82
84
  claudePermissionRequestOutput,
83
85
  claudePermissionRulesFromSettings,
84
86
  claudePreToolUseOutput,
85
- claudePromptsBeforeAnEdit,
86
- editAsksAtTheAgentsPrompt,
87
+ claudeStoppedPermissionRequestOutput,
88
+ claudeStoppedPreToolUseOutput,
89
+ editGoesToTheAgentsPrompt,
87
90
  effectiveWaitSeconds,
88
91
  hookHoldWindowMs,
89
92
  hookWaitClamped,
@@ -94,9 +97,9 @@ import {
94
97
  isPusharyOwnTool,
95
98
  parseAgentQuestionAnswers,
96
99
  parseAgentQuestions,
97
- resolveGate,
98
- stoppedOutput
99
- } from "./chunk-QR6VE5ME.js";
100
+ questionHandedToTheTerminal,
101
+ resolveGate
102
+ } from "./chunk-5RGA636G.js";
100
103
 
101
104
  // src/claude-permission-rules.ts
102
105
  import { readFileSync, statSync } from "fs";
@@ -174,7 +177,7 @@ var allow = (reason) => claudePreToolUseOutput("allow", reason);
174
177
  var deny = (reason) => claudePreToolUseOutput("deny", reason);
175
178
  var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
176
179
  var ask = (reason) => claudePreToolUseOutput("ask", reason);
177
- var halted = (reason) => stoppedOutput(deny(reason), reason);
180
+ var halted = (reason) => claudeStoppedPreToolUseOutput(reason);
178
181
  var outcomeOfFailedAsk = (error, sessionId, failure) => {
179
182
  if (!(error instanceof SubscriptionRequiredError)) return failure;
180
183
  announceSubscriptionRequired(error.upgradeUrl, sessionId);
@@ -218,7 +221,7 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
218
221
  return answer.value === "yes" ? allow() : deny(denyReasonFrom(answer.value, answer.note));
219
222
  }
220
223
  if (timeoutAction === "wait" || clampCut || deadline < requestedDeadline) {
221
- return ask("The phone question was withdrawn. Approve in the current client.");
224
+ return ask(WITHDRAWN_APPROVAL_REASON);
222
225
  }
223
226
  switch (timeoutAction) {
224
227
  case "approve":
@@ -261,7 +264,7 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
261
264
  signals.humanDecision = true;
262
265
  return answer.value === "yes" ? allow() : deny(denyReasonFrom(answer.value, answer.note));
263
266
  }
264
- return ask("The phone question was withdrawn. Approve in the current client.");
267
+ return ask(WITHDRAWN_APPROVAL_REASON);
265
268
  };
266
269
  var handleNotifyOnly = async (apiKey, description, projectName, sessionId, machineId) => {
267
270
  try {
@@ -448,7 +451,7 @@ var askQuestionByQuestion = async (apiKey, input, parsed, projectName, deadline)
448
451
  return answers;
449
452
  };
450
453
  var handleAskUserQuestion = async (apiKey, input, modeState) => {
451
- if (modeState.askHandoff) return void 0;
454
+ if (questionHandedToTheTerminal(input.tool_name, modeState)) return void 0;
452
455
  const parsed = parseQuestions(input.tool_input ?? {});
453
456
  if (!parsed) return void 0;
454
457
  const projectName = basename(input.cwd ?? process.cwd());
@@ -494,7 +497,7 @@ var handleExitPlanMode = async (apiKey, input) => {
494
497
  return void 0;
495
498
  }
496
499
  if (value === PLAN_APPROVE) return allow();
497
- return deny("Plan declined from the phone. Ask what to change before coding.");
500
+ return deny(PLAN_DECLINED_REASON);
498
501
  };
499
502
  var handleKeyless = (input) => {
500
503
  try {
@@ -615,7 +618,7 @@ var handlePreToolUse = async (input, canonical) => {
615
618
  return void 0;
616
619
  }
617
620
  if (claudeAllowlistOwnsCall(gateInput, verdict, input.permission_mode, claudePermissionRulesFor(input))) return void 0;
618
- if (claudePromptsBeforeAnEdit(input.permission_mode) && editAsksAtTheAgentsPrompt(gateInput, verdict)) return handleTerminalOnly();
621
+ if (editGoesToTheAgentsPrompt({ source: ACTIVE_SOURCE, event: "PreToolUse", permissionMode: input.permission_mode }, gateInput, verdict)) return handleTerminalOnly();
619
622
  switch (verdict.kind) {
620
623
  case "allow":
621
624
  return allow();
@@ -635,7 +638,7 @@ var handlePreToolUse = async (input, canonical) => {
635
638
  };
636
639
  var permReqAllow = (updatedInput) => claudePermissionRequestOutput("allow", void 0, updatedInput);
637
640
  var permReqDeny = (message) => claudePermissionRequestOutput("deny", message);
638
- var permReqHalted = (reason) => stoppedOutput(permReqDeny(reason), reason);
641
+ var permReqHalted = (reason) => claudeStoppedPermissionRequestOutput(reason);
639
642
  var toPermissionRequestOutput = (hookOutput) => {
640
643
  if (!hookOutput) return void 0;
641
644
  const d = hookOutput.hookSpecificOutput;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  redactSecretsFromContent
3
- } from "./chunk-QR6VE5ME.js";
3
+ } from "./chunk-5RGA636G.js";
4
4
 
5
5
  // src/crypto.ts
6
6
  import nacl from "tweetnacl";
@@ -2,7 +2,7 @@ import {
2
2
  autoUpdateLock,
3
3
  installPackageGlobally,
4
4
  npmErrorMessage
5
- } from "./chunk-ZUTU43FT.js";
5
+ } from "./chunk-Z23ZBYDW.js";
6
6
  import {
7
7
  writeJsonAtomic
8
8
  } from "./chunk-T7E75EKA.js";
@@ -5,7 +5,7 @@ import {
5
5
  permissionDeniedAction,
6
6
  releaseThrottle,
7
7
  reportEvent
8
- } from "./chunk-QFEM7R6T.js";
8
+ } from "./chunk-KQFTBQ66.js";
9
9
  import {
10
10
  DEFAULT_SESSION
11
11
  } from "./chunk-N6IOSZOE.js";
@@ -13,7 +13,7 @@ import {
13
13
  machineDecisionFor,
14
14
  shouldDeferToNativeMode,
15
15
  wireVerdictFor
16
- } from "./chunk-QR6VE5ME.js";
16
+ } from "./chunk-5RGA636G.js";
17
17
 
18
18
  // src/machine-decision-audit.ts
19
19
  import { basename } from "path";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  configFilePath
3
- } from "./chunk-Z6HPS67T.js";
3
+ } from "./chunk-IRHABAQT.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-QR6VE5ME.js";
6
+ } from "./chunk-5RGA636G.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -0,0 +1,367 @@
1
+ import {
2
+ CLAUDE_HOOK_EVENTS,
3
+ GEMINI_HOOK_EVENTS,
4
+ hasGeminiHooks
5
+ } from "./chunk-M3STIAGU.js";
6
+ import {
7
+ AGENT_CHOICES
8
+ } from "./chunk-ZGADIZDS.js";
9
+ import {
10
+ BELL_HOOK_BINARY
11
+ } from "./chunk-MLGIVVSG.js";
12
+ import {
13
+ CODEX_HOOK_EVENTS,
14
+ hasCodexHooks,
15
+ holdBudgetOf
16
+ } from "./chunk-Z23ZBYDW.js";
17
+ import {
18
+ CURSOR_GATE_EVENTS,
19
+ HOOK_LOCATOR_BINARY,
20
+ hookCommandHoldBudget
21
+ } from "./chunk-5RGA636G.js";
22
+
23
+ // src/diagnostics/bridge.ts
24
+ import { execFileSync } from "child_process";
25
+ import { existsSync, readFileSync } from "fs";
26
+ import { homedir } from "os";
27
+ import { dirname, join } from "path";
28
+ var BRIDGE_BUNDLE_IDENTIFIER = "com.pushary.app";
29
+ var BRIDGE_HELPER_SUFFIX = "/Contents/Helpers/pushary-bridge";
30
+ var BRIDGE_BUNDLE_ROOTS = ["/Applications/Pushary.app", "$HOME/Applications/Pushary.app"];
31
+ var expandHome = (path, home) => path.replaceAll("$HOME", home);
32
+ var parseBridgeStub = (contents, stubPath) => {
33
+ const helperSuffix = contents.match(/^H=(\S+)$/m)?.[1] ?? BRIDGE_HELPER_SUFFIX;
34
+ const cachePath = contents.match(/^C="([^"\n]+)"$/m)?.[1] ?? join(dirname(stubPath), ".bridge-cache");
35
+ const rootsLine = contents.match(/^for P in ((?:"[^"\n]*"\s*)+); do$/m)?.[1];
36
+ const bundleRoots = rootsLine ? [...rootsLine.matchAll(/"([^"]*)"/g)].map((match) => match[1]) : BRIDGE_BUNDLE_ROOTS;
37
+ const bundleIdentifier = contents.match(/kMDItemCFBundleIdentifier == "([^"]+)"/)?.[1] ?? BRIDGE_BUNDLE_IDENTIFIER;
38
+ return { helperSuffix, cachePath, bundleRoots, bundleIdentifier };
39
+ };
40
+ var resolveBridgeStub = (stubPath, probe) => {
41
+ const contents = probe.readFile(stubPath);
42
+ if (contents === null) return { kind: "no-stub", stub: stubPath };
43
+ const shape = parseBridgeStub(contents, stubPath);
44
+ const helperIn = (bundle) => `${bundle}${shape.helperSuffix}`;
45
+ const looked = [];
46
+ for (const root of shape.bundleRoots.map((root2) => expandHome(root2, probe.home))) {
47
+ looked.push(root);
48
+ if (probe.exists(helperIn(root))) return { kind: "bundle", helper: helperIn(root), via: "root" };
49
+ }
50
+ const cached = shape.cachePath ? probe.readFile(shape.cachePath)?.split("\n")[0]?.trim() : void 0;
51
+ if (cached) {
52
+ looked.push(cached);
53
+ if (probe.exists(helperIn(cached))) return { kind: "bundle", helper: helperIn(cached), via: "cache" };
54
+ }
55
+ for (const found of probe.mdfind(shape.bundleIdentifier)) {
56
+ looked.push(found);
57
+ if (probe.exists(helperIn(found))) return { kind: "bundle", helper: helperIn(found), via: "spotlight" };
58
+ }
59
+ return { kind: "missing", stub: stubPath, looked };
60
+ };
61
+ var BRIDGE_GONE_MESSAGE = "the Mac app is gone but its hooks remain; run pushary setup or reinstall the app";
62
+ var describeBridgeResolution = (resolution) => {
63
+ switch (resolution.kind) {
64
+ case "bundle":
65
+ return `${resolution.helper} (${resolution.via === "root" ? "installed" : resolution.via === "cache" ? "cached path" : "found by Spotlight"})`;
66
+ case "missing":
67
+ return BRIDGE_GONE_MESSAGE;
68
+ case "no-stub":
69
+ return `${resolution.stub} is not there; ${BRIDGE_GONE_MESSAGE}`;
70
+ }
71
+ };
72
+ var isBridgeLocator = (executable) => executable !== null && executable.split(/[/\\]/).pop() === HOOK_LOCATOR_BINARY;
73
+ var spotlight = (bundleIdentifier) => {
74
+ if (process.platform !== "darwin") return [];
75
+ try {
76
+ return execFileSync("/usr/bin/mdfind", [`kMDItemCFBundleIdentifier == "${bundleIdentifier}"`], {
77
+ encoding: "utf-8",
78
+ timeout: 5e3,
79
+ stdio: ["ignore", "pipe", "ignore"]
80
+ }).split("\n").map((line) => line.trim()).filter((line) => line !== "");
81
+ } catch {
82
+ return [];
83
+ }
84
+ };
85
+ var readOrNull = (path) => {
86
+ try {
87
+ return readFileSync(path, "utf-8");
88
+ } catch {
89
+ return null;
90
+ }
91
+ };
92
+ var liveBridgeProbe = () => ({
93
+ exists: existsSync,
94
+ readFile: readOrNull,
95
+ mdfind: spotlight,
96
+ home: homedir()
97
+ });
98
+
99
+ // src/diagnostics/wiring.ts
100
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
101
+ var CLAUDE_HOOK_BINARY = /pushary-[a-z-]*hook/;
102
+ var EXACT_HOOK_BINARY = /^pushary-[a-z-]*(hook|gate)$/;
103
+ var pusharyHookExecutable = (command) => {
104
+ const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
105
+ for (const raw of tokens.reverse()) {
106
+ const token = raw[0] === '"' && raw.at(-1) === '"' || raw[0] === "'" && raw.at(-1) === "'" ? raw.slice(1, -1) : raw;
107
+ const name = token.split(/[/\\]/).pop()?.replace(/\.(cmd|exe|mjs)$/i, "") ?? "";
108
+ if (name === HOOK_LOCATOR_BINARY || EXACT_HOOK_BINARY.test(name)) return token;
109
+ }
110
+ return null;
111
+ };
112
+ var claudeWired = ({ claudeJson, settings, skillExists }) => {
113
+ if (record(record(claudeJson)?.mcpServers)?.pushary) return true;
114
+ const s = record(settings);
115
+ if (s) {
116
+ const hooks = JSON.stringify(s.hooks ?? {});
117
+ if (CLAUDE_HOOK_BINARY.test(hooks) || hooks.includes(HOOK_LOCATOR_BINARY)) return true;
118
+ const allow = record(s.permissions)?.allow;
119
+ if (Array.isArray(allow) && allow.some((rule) => String(rule).includes("mcp__pushary__"))) return true;
120
+ }
121
+ return skillExists;
122
+ };
123
+ var codexWired = ({ config, hooks, skillExists }) => {
124
+ if (record(record(config)?.mcp_servers)?.pushary) return true;
125
+ const h = record(hooks);
126
+ if (h && hasCodexHooks(h)) return true;
127
+ return skillExists;
128
+ };
129
+ var geminiWired = ({ settings }) => {
130
+ const s = record(settings);
131
+ if (!s) return false;
132
+ if (record(s.mcpServers)?.pushary) return true;
133
+ return hasGeminiHooks(s);
134
+ };
135
+ var cursorWired = ({ userHooks, mcp, pluginDirExists }) => {
136
+ if (pluginDirExists) return true;
137
+ if (record(record(mcp)?.mcpServers)?.pushary) return true;
138
+ const hooks = record(record(userHooks)?.hooks);
139
+ if (!hooks) return false;
140
+ return Object.values(hooks).some((entries) => Array.isArray(entries) && entries.some((entry) => pusharyHookExecutable(String(record(entry)?.command ?? "")) !== null));
141
+ };
142
+ var cursorMcpAuthorization = (pluginMcp, userMcp) => {
143
+ const pluginServers = record(record(pluginMcp)?.mcpServers);
144
+ const usePlugin = pluginServers !== null && Object.hasOwn(pluginServers, "pushary");
145
+ const servers = usePlugin ? pluginServers : record(record(userMcp)?.mcpServers);
146
+ const headers = record(record(servers?.pushary)?.headers);
147
+ return {
148
+ authorization: typeof headers?.Authorization === "string" ? headers.Authorization : null,
149
+ source: usePlugin ? "plugin mcp.json" : "~/.cursor/mcp.json"
150
+ };
151
+ };
152
+ var LONG_HOLD_SECONDS = 600;
153
+ var toSeconds = (timeout, unit) => {
154
+ if (typeof timeout !== "number" || !Number.isFinite(timeout)) return null;
155
+ return unit === "milliseconds" ? timeout / 1e3 : timeout;
156
+ };
157
+ var registeredHooks = (entries, shape, unit) => {
158
+ if (!Array.isArray(entries)) return [];
159
+ return entries.flatMap((entry) => {
160
+ const container = record(entry);
161
+ if (!container) return [];
162
+ const hooks = shape === "nested" ? Array.isArray(container.hooks) ? container.hooks : [] : [container];
163
+ return hooks.flatMap((hook) => {
164
+ const command = record(hook)?.command;
165
+ if (typeof command !== "string" || command.trim() === "") return [];
166
+ return [{ command, timeoutSeconds: toSeconds(record(hook)?.timeout, unit) }];
167
+ });
168
+ });
169
+ };
170
+ var holdersOnEvent = (event, hooks) => {
171
+ if (!hooks.some((hook) => pusharyHookExecutable(hook.command) !== null)) return [];
172
+ return hooks.filter((hook) => pusharyHookExecutable(hook.command) === null).map((hook) => ({ event, command: hook.command, timeoutSeconds: hook.timeoutSeconds }));
173
+ };
174
+ var otherHolders = (hooks, events, shape, unit) => {
175
+ const table = record(hooks);
176
+ if (!table) return [];
177
+ return events.flatMap((event) => holdersOnEvent(event, registeredHooks(table[event], shape, unit)));
178
+ };
179
+ var approvalEvents = (table) => table.filter((hook) => hook.delivery === "approval").map((hook) => hook.event);
180
+ var CLAUDE_GATE_EVENTS = approvalEvents(CLAUDE_HOOK_EVENTS);
181
+ var CODEX_GATE_EVENTS = approvalEvents(CODEX_HOOK_EVENTS);
182
+ var GEMINI_GATE_EVENTS = approvalEvents(GEMINI_HOOK_EVENTS);
183
+ var claudeOtherHolders = (settings) => otherHolders(record(settings)?.hooks, CLAUDE_GATE_EVENTS, "nested", "seconds");
184
+ var codexOtherHolders = (hooks) => otherHolders(record(hooks)?.hooks, CODEX_GATE_EVENTS, "nested", "seconds");
185
+ var geminiOtherHolders = (settings) => otherHolders(record(settings)?.hooks, GEMINI_GATE_EVENTS, "nested", "milliseconds");
186
+ var cursorOtherHolders = (userHooks) => otherHolders(record(userHooks)?.hooks, CURSOR_GATE_EVENTS, "flat", "seconds");
187
+ var holdEvents = (table) => table.flatMap((hook) => {
188
+ const seconds = holdBudgetOf(hook);
189
+ return seconds === void 0 ? [] : [{ event: hook.event, seconds }];
190
+ });
191
+ var unprovenHolds = (hooks, events) => {
192
+ const table = record(hooks);
193
+ if (!table) return [];
194
+ return events.filter(({ event, seconds }) => registeredHooks(table[event], "nested", "seconds").some((hook) => pusharyHookExecutable(hook.command) !== null && hookCommandHoldBudget(hook.command) !== seconds)).map(({ event }) => event);
195
+ };
196
+ var claudeUnprovenHolds = (settings) => unprovenHolds(record(settings)?.hooks, holdEvents(CLAUDE_HOOK_EVENTS));
197
+ var codexUnprovenHolds = (hooks) => unprovenHolds(record(hooks)?.hooks, holdEvents(CODEX_HOOK_EVENTS));
198
+ var holdsLong = (holder) => holder.timeoutSeconds !== null && holder.timeoutSeconds > LONG_HOLD_SECONDS;
199
+ var SHELLS = /* @__PURE__ */ new Set(["/bin/sh", "/bin/bash", "/bin/zsh", "/usr/bin/env"]);
200
+ var holderExecutable = (command) => {
201
+ const pathLike = command.match(/[^\s"']*[/\\][^\s"']*/g) ?? [];
202
+ const target = pathLike.find((token) => !SHELLS.has(token));
203
+ return target ?? command.trim().split(/\s+/)[0] ?? command;
204
+ };
205
+ var describeHoldTimeout = (holder) => {
206
+ const seconds = holder.timeoutSeconds;
207
+ if (seconds === null) return "no timeout set, so the agent default applies";
208
+ if (seconds >= 3600 && seconds % 3600 === 0) return `${seconds / 3600}h timeout`;
209
+ if (seconds >= 60 && seconds % 60 === 0) return `${seconds / 60}m timeout`;
210
+ return `${seconds}s timeout`;
211
+ };
212
+ var describeOtherHolder = (holder) => `another hook also registered on ${holder.event}: ${holderExecutable(holder.command)} (${describeHoldTimeout(holder)})`;
213
+ var describeHoldSentence = (agentLabel) => `${agentLabel} waits for every hook on an event, so the terminal cannot move until that one returns, however fast your phone answered.`;
214
+
215
+ // src/setup/hook-ownership.ts
216
+ var commandsIn = (value) => {
217
+ if (Array.isArray(value)) return value.flatMap(commandsIn);
218
+ if (!value || typeof value !== "object") return [];
219
+ return Object.entries(value).flatMap(([key, item]) => key === "command" && typeof item === "string" ? [item] : commandsIn(item));
220
+ };
221
+ var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
222
+ var hasPaidPusharyCommand = (value) => commandsIn(value).some((command) => {
223
+ const executable = pusharyHookExecutable(command);
224
+ const name = executable?.split(/[/\\]/).pop()?.replace(/\.(cmd|exe|mjs)$/i, "");
225
+ return executable !== null && !isBridgeLocator(executable) && name !== BELL_HOOK_BINARY;
226
+ });
227
+ var hasAppCommand = (value) => commandsIn(value).some((command) => {
228
+ const executable = pusharyHookExecutable(command);
229
+ return executable !== null && isBridgeLocator(executable);
230
+ });
231
+ var mcpBearerKey = (document, containerKey, headersKey) => {
232
+ const servers = isRecord(document?.[containerKey]) ? document[containerKey] : null;
233
+ const pushary = isRecord(servers?.pushary) ? servers.pushary : null;
234
+ const headers = isRecord(pushary?.[headersKey]) ? pushary[headersKey] : null;
235
+ const authorization = headers?.Authorization;
236
+ const match = typeof authorization === "string" ? authorization.trim().match(/^Bearer\s+(\S+)$/) : null;
237
+ return match?.[1] ?? null;
238
+ };
239
+ var captureMcpServer = (document, containerKey) => {
240
+ const container = document[containerKey];
241
+ const servers = isRecord(container) ? container : null;
242
+ if (!servers && Object.hasOwn(document, containerKey)) {
243
+ return { present: false, containerValue: structuredClone(container) };
244
+ }
245
+ return servers && Object.hasOwn(servers, "pushary") ? { present: true, value: structuredClone(servers.pushary) } : { present: false };
246
+ };
247
+ var restoreMcpServer = (document, containerKey, snapshot) => {
248
+ const before = JSON.stringify(document);
249
+ const existing = isRecord(document[containerKey]) ? document[containerKey] : null;
250
+ if (snapshot.present) {
251
+ const servers = existing ?? {};
252
+ servers.pushary = structuredClone(snapshot.value);
253
+ document[containerKey] = servers;
254
+ } else if (Object.hasOwn(snapshot, "containerValue")) {
255
+ document[containerKey] = structuredClone(snapshot.containerValue);
256
+ } else if (existing && Object.hasOwn(existing, "pushary")) {
257
+ delete existing.pushary;
258
+ if (Object.keys(existing).length === 0) delete document[containerKey];
259
+ }
260
+ return JSON.stringify(document) !== before;
261
+ };
262
+ var captureHookTakeover = (agent, document) => {
263
+ const hooks = isRecord(document?.hooks) ? document.hooks : null;
264
+ if (!hooks) return null;
265
+ const captured = {};
266
+ for (const [event, entries] of Object.entries(hooks)) {
267
+ if (!Array.isArray(entries)) continue;
268
+ const appEntries = entries.flatMap((value, index) => isRecord(value) && hasAppCommand(value) ? [{ index, value: structuredClone(value) }] : []);
269
+ if (appEntries.length > 0) captured[event] = appEntries;
270
+ }
271
+ return Object.keys(captured).length > 0 ? { version: 1, agent, hooks: captured } : null;
272
+ };
273
+ var isHookTakeoverReceipt = (value) => {
274
+ if (!isRecord(value) || value.version !== 1 || typeof value.agent !== "string" || !AGENT_CHOICES.includes(value.agent) || !isRecord(value.hooks)) return false;
275
+ const expectsMcp = ["claude_code", "codex", "gemini_cli", "vscode"].includes(value.agent);
276
+ if (value.mcpServer !== void 0 !== expectsMcp) return false;
277
+ if (value.codexTrust !== void 0 !== (value.agent === "codex")) return false;
278
+ if (value.codexTrust !== void 0 && !isRecord(value.codexTrust)) return false;
279
+ if (value.mcpServer !== void 0 && (!isRecord(value.mcpServer) || typeof value.mcpServer.present !== "boolean" || value.mcpServer.present && !Object.hasOwn(value.mcpServer, "value"))) return false;
280
+ return Object.values(value.hooks).every((entries) => Array.isArray(entries) && entries.length > 0 && entries.every((item) => isRecord(item) && Number.isInteger(item.index) && item.index >= 0 && isRecord(item.value)));
281
+ };
282
+ var restoreHookTakeover = (document, receipt) => {
283
+ const before = JSON.stringify(document);
284
+ const appOwnsNow = hookOwnerOf(document) === "app";
285
+ const hooks = isRecord(document.hooks) ? document.hooks : {};
286
+ for (const [event, value] of Object.entries(hooks)) {
287
+ if (!Array.isArray(value)) continue;
288
+ const kept = value.filter((entry) => !hasPaidPusharyCommand(entry));
289
+ if (kept.length === 0) delete hooks[event];
290
+ else hooks[event] = kept;
291
+ }
292
+ if (!appOwnsNow) {
293
+ for (const [event, snapshots] of Object.entries(receipt.hooks)) {
294
+ const entries = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
295
+ for (const snapshot of snapshots) {
296
+ entries.splice(Math.min(snapshot.index, entries.length), 0, structuredClone(snapshot.value));
297
+ }
298
+ hooks[event] = entries;
299
+ }
300
+ }
301
+ document.hooks = hooks;
302
+ return JSON.stringify(document) !== before;
303
+ };
304
+ var hookOwnerOf = (document, appAvailable = true, hooksKeyPath = ["hooks"]) => {
305
+ const hooks = hooksKeyPath.reduce(
306
+ (node, key) => node && typeof node === "object" ? node[key] : void 0,
307
+ document
308
+ );
309
+ return hookOwnerOfCommands(commandsIn(hooks), appAvailable);
310
+ };
311
+ var hookOwnerOfCommands = (commands, appAvailable = true) => {
312
+ const executables = commands.map(pusharyHookExecutable).filter((executable) => executable !== null);
313
+ if (appAvailable && executables.some(isBridgeLocator)) return "app";
314
+ if (executables.some((executable) => !isBridgeLocator(executable))) return "cli";
315
+ return "none";
316
+ };
317
+ var planHooks = (context) => {
318
+ if (context.owner !== "app") return "install";
319
+ if (context.takeOverHooks) return "install";
320
+ return "keep";
321
+ };
322
+ var describeAppOwnership = (agentName) => `The Pushary Mac app owns ${agentName}'s hooks.`;
323
+ var KEEP_HINT = "Kept with the app's account. Pass --take-over-hooks to have the CLI own them instead.";
324
+ var OWNER_LABELS = {
325
+ app: "the Pushary Mac app",
326
+ cli: "the CLI"
327
+ };
328
+ var describeHookOwnership = (entries, names) => {
329
+ const owned = entries.filter((entry) => entry.owner !== "none");
330
+ if (owned.length === 0) return null;
331
+ const parts = owned.map((entry) => `${names[entry.agent]} by ${OWNER_LABELS[entry.owner]}${entry.kept ? " (kept)" : ""}`);
332
+ return `Hooks: ${parts.join(", ")}.`;
333
+ };
334
+
335
+ export {
336
+ resolveBridgeStub,
337
+ describeBridgeResolution,
338
+ isBridgeLocator,
339
+ liveBridgeProbe,
340
+ pusharyHookExecutable,
341
+ claudeWired,
342
+ codexWired,
343
+ geminiWired,
344
+ cursorWired,
345
+ cursorMcpAuthorization,
346
+ claudeOtherHolders,
347
+ codexOtherHolders,
348
+ geminiOtherHolders,
349
+ cursorOtherHolders,
350
+ claudeUnprovenHolds,
351
+ codexUnprovenHolds,
352
+ holdsLong,
353
+ describeOtherHolder,
354
+ describeHoldSentence,
355
+ mcpBearerKey,
356
+ captureMcpServer,
357
+ restoreMcpServer,
358
+ captureHookTakeover,
359
+ isHookTakeoverReceipt,
360
+ restoreHookTakeover,
361
+ hookOwnerOf,
362
+ hookOwnerOfCommands,
363
+ planHooks,
364
+ describeAppOwnership,
365
+ KEEP_HINT,
366
+ describeHookOwnership
367
+ };