pushary 1.8.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 (186) hide show
  1. package/CHANGELOG.md +1943 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/data/SKILL.md +591 -0
  5. package/data/cowork/SKILL.md +77 -0
  6. package/data/cursor-plugin/.cursor-plugin/plugin.json +25 -0
  7. package/data/cursor-plugin/CHANGELOG.md +70 -0
  8. package/data/cursor-plugin/CONTRIBUTING.md +40 -0
  9. package/data/cursor-plugin/LICENSE +21 -0
  10. package/data/cursor-plugin/README.md +113 -0
  11. package/data/cursor-plugin/SECURITY.md +33 -0
  12. package/data/cursor-plugin/assets/logo.png +0 -0
  13. package/data/cursor-plugin/commands/notify-when-done.md +14 -0
  14. package/data/cursor-plugin/commands/pushary-test.md +13 -0
  15. package/data/cursor-plugin/hooks/hooks.json +75 -0
  16. package/data/cursor-plugin/mcp.json +11 -0
  17. package/data/cursor-plugin/rules/pushary.mdc +40 -0
  18. package/data/cursor-plugin/scripts/pushary-gate.mjs +724 -0
  19. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +137 -0
  20. package/data/cursor-plugin/scripts/redaction.mjs +51 -0
  21. package/data/cursor-plugin/skills/pushary/SKILL.md +584 -0
  22. package/data/vscode-plugin/.claude-plugin/plugin.json +28 -0
  23. package/data/vscode-plugin/.mcp.json +11 -0
  24. package/data/vscode-plugin/CHANGELOG.md +49 -0
  25. package/data/vscode-plugin/CONTRIBUTING.md +40 -0
  26. package/data/vscode-plugin/LICENSE +21 -0
  27. package/data/vscode-plugin/README.md +128 -0
  28. package/data/vscode-plugin/SECURITY.md +56 -0
  29. package/data/vscode-plugin/assets/logo.png +0 -0
  30. package/data/vscode-plugin/commands/notify-when-done.md +14 -0
  31. package/data/vscode-plugin/commands/pushary-test.md +13 -0
  32. package/data/vscode-plugin/hooks/hooks.json +60 -0
  33. package/data/vscode-plugin/scripts/pushary-gate.mjs +762 -0
  34. package/data/vscode-plugin/scripts/redaction.mjs +51 -0
  35. package/data/vscode-plugin/skills/pushary/SKILL.md +584 -0
  36. package/dist/bin/pushary-bell-hook.d.ts +1 -0
  37. package/dist/bin/pushary-bell-hook.js +46 -0
  38. package/dist/bin/pushary-bell.d.ts +1 -0
  39. package/dist/bin/pushary-bell.js +146 -0
  40. package/dist/bin/pushary-claude.d.ts +1 -0
  41. package/dist/bin/pushary-claude.js +1916 -0
  42. package/dist/bin/pushary-clean.d.ts +1 -0
  43. package/dist/bin/pushary-clean.js +1300 -0
  44. package/dist/bin/pushary-codex-bridge.d.ts +1 -0
  45. package/dist/bin/pushary-codex-bridge.js +599 -0
  46. package/dist/bin/pushary-codex-hook.d.ts +1 -0
  47. package/dist/bin/pushary-codex-hook.js +886 -0
  48. package/dist/bin/pushary-codex.d.ts +1 -0
  49. package/dist/bin/pushary-codex.js +125 -0
  50. package/dist/bin/pushary-connect.d.ts +1 -0
  51. package/dist/bin/pushary-connect.js +100 -0
  52. package/dist/bin/pushary-cowork.d.ts +1 -0
  53. package/dist/bin/pushary-cowork.js +36 -0
  54. package/dist/bin/pushary-daemon-supervisor.d.ts +1 -0
  55. package/dist/bin/pushary-daemon-supervisor.js +27 -0
  56. package/dist/bin/pushary-daemon.d.ts +1 -0
  57. package/dist/bin/pushary-daemon.js +878 -0
  58. package/dist/bin/pushary-disconnect.d.ts +1 -0
  59. package/dist/bin/pushary-disconnect.js +264 -0
  60. package/dist/bin/pushary-doctor.d.ts +1 -0
  61. package/dist/bin/pushary-doctor.js +1433 -0
  62. package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
  63. package/dist/bin/pushary-elicitation-hook.js +149 -0
  64. package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
  65. package/dist/bin/pushary-gemini-bridge.js +339 -0
  66. package/dist/bin/pushary-gemini-hook.d.ts +1 -0
  67. package/dist/bin/pushary-gemini-hook.js +381 -0
  68. package/dist/bin/pushary-hook.d.ts +1 -0
  69. package/dist/bin/pushary-hook.js +95 -0
  70. package/dist/bin/pushary-login.d.ts +1 -0
  71. package/dist/bin/pushary-login.js +198 -0
  72. package/dist/bin/pushary-logout.d.ts +1 -0
  73. package/dist/bin/pushary-logout.js +147 -0
  74. package/dist/bin/pushary-mcp.d.ts +1 -0
  75. package/dist/bin/pushary-mcp.js +114 -0
  76. package/dist/bin/pushary-mode.d.ts +1 -0
  77. package/dist/bin/pushary-mode.js +130 -0
  78. package/dist/bin/pushary-notification-hook.d.ts +1 -0
  79. package/dist/bin/pushary-notification-hook.js +56 -0
  80. package/dist/bin/pushary-opencode-hook.d.ts +1 -0
  81. package/dist/bin/pushary-opencode-hook.js +338 -0
  82. package/dist/bin/pushary-permission-denied-hook.d.ts +1 -0
  83. package/dist/bin/pushary-permission-denied-hook.js +60 -0
  84. package/dist/bin/pushary-permission-hook.d.ts +1 -0
  85. package/dist/bin/pushary-permission-hook.js +65 -0
  86. package/dist/bin/pushary-post-hook.d.ts +1 -0
  87. package/dist/bin/pushary-post-hook.js +70 -0
  88. package/dist/bin/pushary-prompt-hook.d.ts +1 -0
  89. package/dist/bin/pushary-prompt-hook.js +61 -0
  90. package/dist/bin/pushary-session-end-hook.d.ts +1 -0
  91. package/dist/bin/pushary-session-end-hook.js +59 -0
  92. package/dist/bin/pushary-session-start-hook.d.ts +1 -0
  93. package/dist/bin/pushary-session-start-hook.js +72 -0
  94. package/dist/bin/pushary-setup.d.ts +1 -0
  95. package/dist/bin/pushary-setup.js +2660 -0
  96. package/dist/bin/pushary-stats.d.ts +1 -0
  97. package/dist/bin/pushary-stats.js +44 -0
  98. package/dist/bin/pushary-status.d.ts +1 -0
  99. package/dist/bin/pushary-status.js +262 -0
  100. package/dist/bin/pushary-stop-hook.d.ts +1 -0
  101. package/dist/bin/pushary-stop-hook.js +63 -0
  102. package/dist/bin/pushary-stopfailure-hook.d.ts +1 -0
  103. package/dist/bin/pushary-stopfailure-hook.js +56 -0
  104. package/dist/bin/pushary-suggestions.d.ts +1 -0
  105. package/dist/bin/pushary-suggestions.js +100 -0
  106. package/dist/bin/pushary-transcript-register.d.ts +1 -0
  107. package/dist/bin/pushary-transcript-register.js +40 -0
  108. package/dist/bin/pushary-transcripts.d.ts +1 -0
  109. package/dist/bin/pushary-transcripts.js +77 -0
  110. package/dist/bin/pushary-upgrade.d.ts +1 -0
  111. package/dist/bin/pushary-upgrade.js +599 -0
  112. package/dist/bin/pushary-wait.d.ts +1 -0
  113. package/dist/bin/pushary-wait.js +122 -0
  114. package/dist/bin/pushary.d.ts +1 -0
  115. package/dist/bin/pushary.js +74 -0
  116. package/dist/chunk-2ABTSGFT.js +173 -0
  117. package/dist/chunk-2OY3ZZ5N.js +124 -0
  118. package/dist/chunk-3EGEA4KH.js +44 -0
  119. package/dist/chunk-3EVPNIBE.js +185 -0
  120. package/dist/chunk-5OP5MQI7.js +45 -0
  121. package/dist/chunk-6J2JC2RT.js +149 -0
  122. package/dist/chunk-7QLSKOSU.js +19 -0
  123. package/dist/chunk-7Z7Q4GGS.js +88 -0
  124. package/dist/chunk-A2A2YTMG.js +835 -0
  125. package/dist/chunk-AHV4LNB5.js +377 -0
  126. package/dist/chunk-AXPCESYO.js +197 -0
  127. package/dist/chunk-C2WCPBF7.js +86 -0
  128. package/dist/chunk-CJVVKOSR.js +73 -0
  129. package/dist/chunk-CULUZTWQ.js +2117 -0
  130. package/dist/chunk-CVQYGMSR.js +142 -0
  131. package/dist/chunk-CVWG2N3Y.js +16 -0
  132. package/dist/chunk-D2FX3EPW.js +15 -0
  133. package/dist/chunk-DB3ONZB4.js +44 -0
  134. package/dist/chunk-DE4Y6TCC.js +160 -0
  135. package/dist/chunk-E4FJTUH4.js +249 -0
  136. package/dist/chunk-EPCXUP3P.js +41 -0
  137. package/dist/chunk-EX3GAPA4.js +44 -0
  138. package/dist/chunk-EYK3GSRH.js +143 -0
  139. package/dist/chunk-GMXKITVA.js +70 -0
  140. package/dist/chunk-GPEGOGRJ.js +49 -0
  141. package/dist/chunk-GU3EJCVV.js +43 -0
  142. package/dist/chunk-HQ6G3B5R.js +59 -0
  143. package/dist/chunk-HZVVNGEW.js +22 -0
  144. package/dist/chunk-IPVKH2ST.js +231 -0
  145. package/dist/chunk-J5TIV3J6.js +238 -0
  146. package/dist/chunk-JU6W3XAU.js +64 -0
  147. package/dist/chunk-JYG4B33G.js +227 -0
  148. package/dist/chunk-JZTHGAPA.js +503 -0
  149. package/dist/chunk-KGDWDSNL.js +102 -0
  150. package/dist/chunk-KVQBWI5T.js +99 -0
  151. package/dist/chunk-KZERVKTD.js +16 -0
  152. package/dist/chunk-LXQ5FBBD.js +47 -0
  153. package/dist/chunk-M5ICYGHT.js +138 -0
  154. package/dist/chunk-M5JDZ45W.js +943 -0
  155. package/dist/chunk-MDEPU45E.js +1226 -0
  156. package/dist/chunk-MPWXGONV.js +157 -0
  157. package/dist/chunk-N6IOSZOE.js +185 -0
  158. package/dist/chunk-NGDD6TXY.js +192 -0
  159. package/dist/chunk-NQTHFF4W.js +2830 -0
  160. package/dist/chunk-OKXS7WDZ.js +68 -0
  161. package/dist/chunk-OLQRRFBX.js +21 -0
  162. package/dist/chunk-P7WQEVWA.js +307 -0
  163. package/dist/chunk-POFBE6ZY.js +12 -0
  164. package/dist/chunk-PQ4K74WL.js +30 -0
  165. package/dist/chunk-PUZZELE6.js +37 -0
  166. package/dist/chunk-Q7E4I6UA.js +767 -0
  167. package/dist/chunk-QCGNGEGE.js +15 -0
  168. package/dist/chunk-R7437YI3.js +151 -0
  169. package/dist/chunk-RHR6NARM.js +305 -0
  170. package/dist/chunk-SAXBC4AZ.js +60 -0
  171. package/dist/chunk-SGZJ5LYH.js +76 -0
  172. package/dist/chunk-SIFVFP6T.js +70 -0
  173. package/dist/chunk-SLFABHW3.js +150 -0
  174. package/dist/chunk-T7E75EKA.js +67 -0
  175. package/dist/chunk-TY4JT7W3.js +38 -0
  176. package/dist/chunk-U4TMAANR.js +111 -0
  177. package/dist/chunk-UI36QSBI.js +177 -0
  178. package/dist/chunk-VQKRG3AC.js +339 -0
  179. package/dist/chunk-WE2J62NC.js +1131 -0
  180. package/dist/chunk-XHKBHWLX.js +14 -0
  181. package/dist/chunk-ZSZSSJCV.js +275 -0
  182. package/dist/chunk-ZVSKMPBZ.js +135 -0
  183. package/dist/reapply-CW3MA66V.js +34 -0
  184. package/dist/src/index.d.ts +204 -0
  185. package/dist/src/index.js +64 -0
  186. package/package.json +108 -0
@@ -0,0 +1,2830 @@
1
+ // ../contracts/src/claude-connector.ts
2
+ var CLAUDE_CONNECTOR_URL = "https://pushary.com/api/mcp/mcp";
3
+ var CLAUDE_CONNECTOR_GUIDE = "https://pushary.com/docs/agents/guides/claude-desktop";
4
+ var COWORK_CONNECTOR_LIMIT = "Pushary\u2019s Cowork connector is cooperative: Claude chooses when to ask. It does not intercept Claude\u2019s native permission prompts or start new Cowork tasks.";
5
+ var CLAUDE_CONNECTOR_STEPS = [
6
+ "In Claude, open Customize > Connectors > Add custom connector and paste the Pushary connector URL.",
7
+ "Leave OAuth Client ID and Secret empty. Sign in with the same Pushary account used by your phone and Mac app.",
8
+ "Enable Pushary in the Chat or Cowork conversation. Approve access to its tools if Claude asks.",
9
+ "For proactive Cowork use, install the Pushary Cowork plugin/skill or paste the standing instructions into Settings > Cowork.",
10
+ "Ask Claude to send the test question below, answer it from Pushary, and confirm Claude receives your answer."
11
+ ];
12
+ var CLAUDE_CONNECTOR_TEST = "Use Pushary ask_user to ask \u201CDid this setup question reach you?\u201D with type \u201Cconfirm\u201D. Use agentName \u201CClaude Cowork - Setup check\u201D in Cowork or \u201CClaude Chat - Setup check\u201D in Chat. Wait for my answer and tell me which answer you received. Do not run a command or change any files.";
13
+ var COWORK_STANDING_INSTRUCTIONS = `Pushary is connected as a custom connector. Use it proactively to reach me on my phone or Mac notch.
14
+
15
+ - When you need a decision or clarifying answer, call ask_user. Use type "confirm", "select", or "input" as appropriate. Ask once on one channel.
16
+ - Before a risky or irreversible action outside my existing authorization, call ask_user with type "confirm" and wait for approval. Silence is never consent.
17
+ - When a task finishes, call send_notification with context.type "task_complete" and a short summary. For an error you cannot resolve, use context.type "error"; use "info" for other updates.
18
+ - Only keep a reply channel open when a specific unresolved decision is required: call send_notification with context.askQuestion, then poll the returned linkedCorrelationId with wait_for_answer. Do not ask for optional feedback just to keep a finished turn alive.
19
+ - Follow handoffAction when present, otherwise nextAction. If ask_user times out on a live question, poll once with wait_for_answer. If still pending, cancel_question before asking in the current chat. If cancellation returns handoffAction "stop", stop. If cancellation returns false, poll once for 1 second and honor the answer that won the race. Expired, cancelled, missing, and unavailable questions are not new timeouts.
20
+ - Pass agentName as "Claude Cowork - <task name>" on every call, and reuse one opaque sessionId per task so parallel tasks stay separate. Never include credentials in these identifiers.
21
+ - Cancel a question when it is no longer needed. Once this turn ends, reopen Cowork for follow-up work; this connector cannot restart it.
22
+ - If Pushary tools are missing or disabled, report that once and continue within my authorization. Do not claim a notification or approval was delivered.`;
23
+
24
+ // ../contracts/src/agent-manifest.ts
25
+ var AGENT_HOOKS_SETUP_COMMAND = "npx @pushary/agent-hooks@latest setup";
26
+ var defaults = {
27
+ hasStoredIdentity: true,
28
+ hookSource: null,
29
+ policyAgent: null,
30
+ repoAware: false,
31
+ disconnectable: false,
32
+ managedProvider: false,
33
+ bridged: false,
34
+ enforcement: "cooperative",
35
+ connectVia: "Plain MCP",
36
+ setupCommand: null,
37
+ setupChoice: null,
38
+ setupGuide: "",
39
+ activationGuide: null,
40
+ enforcementDetail: "Cooperative. The agent chooses when to ask; no native permission interception.",
41
+ capabilityNote: "",
42
+ documented: true
43
+ };
44
+ var AGENTS = {
45
+ claude_code: {
46
+ ...defaults,
47
+ label: "Claude Code",
48
+ activationGuide: "/docs/agents/guides/claude-code#verify-a-real-approval",
49
+ hookSource: "claude",
50
+ bridged: true,
51
+ policyAgent: "claude_code",
52
+ repoAware: true,
53
+ disconnectable: true,
54
+ managedProvider: true,
55
+ enforcement: "enforced",
56
+ connectVia: "Native hooks + MCP",
57
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
58
+ enforcementDetail: "Shell, writes and edits, subject to Claude Code permission mode and explicit policy."
59
+ },
60
+ claude_chat: {
61
+ ...defaults,
62
+ label: "Claude Chat",
63
+ connectVia: "Custom MCP connector",
64
+ setupGuide: "[Claude.ai and Claude Desktop setup](/docs/agents/guides/claude-desktop)"
65
+ },
66
+ claude_cowork: {
67
+ ...defaults,
68
+ label: "Claude Cowork",
69
+ connectVia: "Custom MCP connector + optional Cowork skill",
70
+ setupGuide: "[Cowork setup](/docs/agents/guides/claude-desktop#use-it-in-cowork)",
71
+ enforcementDetail: COWORK_CONNECTOR_LIMIT
72
+ },
73
+ codex: {
74
+ ...defaults,
75
+ label: "Codex",
76
+ hookSource: "codex",
77
+ bridged: true,
78
+ policyAgent: "codex",
79
+ repoAware: true,
80
+ disconnectable: true,
81
+ managedProvider: true,
82
+ enforcement: "enforced",
83
+ connectVia: "Native hooks + MCP",
84
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
85
+ enforcementDetail: "Shell and apply_patch edits through native permission hooks; requires a compatible Codex runtime."
86
+ },
87
+ chatgpt: {
88
+ ...defaults,
89
+ label: "ChatGPT",
90
+ connectVia: "Custom MCP connector",
91
+ setupGuide: "[ChatGPT setup](/docs/agents/guides/chatgpt)"
92
+ },
93
+ gemini_cli: {
94
+ ...defaults,
95
+ label: "Gemini CLI",
96
+ hookSource: "gemini",
97
+ bridged: true,
98
+ policyAgent: "gemini_cli",
99
+ repoAware: true,
100
+ disconnectable: true,
101
+ managedProvider: true,
102
+ enforcement: "enforced",
103
+ connectVia: "Native hooks + MCP",
104
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
105
+ enforcementDetail: "Shell, writes and edits through native BeforeTool hooks."
106
+ },
107
+ hermes: {
108
+ ...defaults,
109
+ label: "Hermes",
110
+ policyAgent: "hermes",
111
+ enforcement: "enforced",
112
+ connectVia: "Native Python plugin + approval transport",
113
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
114
+ enforcementDetail: "Hermes dangerous-command approvals when the Pushary transport is selected; optional PUSHARY_GATE_TOOLS gates named tools.",
115
+ capabilityNote: "Uses the Python plugin, not a macOS bridge HookSource. CLI disconnect and managed-provider controls are not implemented for Hermes."
116
+ },
117
+ cursor: {
118
+ ...defaults,
119
+ label: "Cursor",
120
+ hookSource: "cursor",
121
+ bridged: true,
122
+ policyAgent: "cursor",
123
+ disconnectable: true,
124
+ enforcement: "enforced",
125
+ connectVia: "Editor plugin hooks + MCP",
126
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
127
+ enforcementDetail: "With installed hooks: shell, MCP execution and Write/Delete/Edit tool calls. Plain MCP remains cooperative."
128
+ },
129
+ vscode: {
130
+ ...defaults,
131
+ label: "VS Code",
132
+ hookSource: "vscode",
133
+ bridged: true,
134
+ policyAgent: "vscode",
135
+ enforcement: "enforced",
136
+ connectVia: "Agent plugin hooks + MCP",
137
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
138
+ enforcementDetail: "PreToolUse approval hooks from the installed agent plugin; the host decides which events it exposes.",
139
+ capabilityNote: "Has both a bridge HookSource and a per-agent policy profile. CLI disconnect and managed-provider controls are not implemented for VS Code."
140
+ },
141
+ opencode: {
142
+ ...defaults,
143
+ label: "OpenCode",
144
+ hookSource: "opencode",
145
+ bridged: true,
146
+ policyAgent: "opencode",
147
+ repoAware: true,
148
+ disconnectable: true,
149
+ enforcement: "enforced",
150
+ connectVia: "Generated plugin + MCP",
151
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
152
+ enforcementDetail: "Shell, writes, edits and web fetches when OpenCode itself asks for permission."
153
+ },
154
+ fx: {
155
+ ...defaults,
156
+ label: "fx",
157
+ connectVia: "Local MCP bridge",
158
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
159
+ capabilityNote: "fx has no hooks that other tools can register, so Pushary cannot answer fx's own approval prompts. Setup turns off fx's terminal question tool so fx's agent asks through Pushary. fx's full-access mode ignores that setting; there the agent is only instructed to ask through Pushary."
160
+ },
161
+ qwen_code: {
162
+ ...defaults,
163
+ label: "Qwen Code",
164
+ hookSource: "qwen",
165
+ bridged: true,
166
+ policyAgent: "qwen_code",
167
+ repoAware: true,
168
+ disconnectable: true,
169
+ enforcement: "enforced",
170
+ connectVia: "Native hooks + MCP",
171
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
172
+ enforcementDetail: "Shell, writes and edits through native PreToolUse hooks, matched on Qwen's own tool names and their Claude-shaped aliases."
173
+ },
174
+ codebuddy: {
175
+ ...defaults,
176
+ label: "CodeBuddy",
177
+ hookSource: "codebuddy",
178
+ bridged: true,
179
+ policyAgent: "codebuddy",
180
+ repoAware: true,
181
+ disconnectable: true,
182
+ enforcement: "enforced",
183
+ connectVia: "Native hooks + MCP",
184
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
185
+ enforcementDetail: "Shell, writes and edits through native PreToolUse hooks, plus PermissionRequest. CodeBuddy implements the Claude Code hook specification."
186
+ },
187
+ qoder: {
188
+ ...defaults,
189
+ label: "Qoder",
190
+ hookSource: "qoder",
191
+ bridged: true,
192
+ policyAgent: "qoder",
193
+ repoAware: true,
194
+ disconnectable: true,
195
+ enforcement: "enforced",
196
+ connectVia: "Native hooks + MCP",
197
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
198
+ enforcementDetail: "Shell, writes and edits through native PreToolUse hooks, plus PermissionRequest, over Qoder's superset of the Claude Code event list.",
199
+ capabilityNote: "Qoder documents no user-level instructions file, so the proactive-ask block is not installed. Hooks and MCP are unaffected. Like every agent in this family, the CLI wires it; the macOS app does not yet."
200
+ },
201
+ droid: {
202
+ ...defaults,
203
+ label: "Droid",
204
+ hookSource: "droid",
205
+ bridged: true,
206
+ policyAgent: "droid",
207
+ repoAware: true,
208
+ disconnectable: true,
209
+ enforcement: "enforced",
210
+ connectVia: "Native hooks",
211
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
212
+ enforcementDetail: "Execute, Create, Edit and ApplyPatch through native PreToolUse hooks in ~/.factory/hooks.json.",
213
+ capabilityNote: "Droid registers MCP servers through `droid mcp add` rather than a documented remote-server config shape, so setup writes hooks only. Add Pushary's MCP server yourself to let Droid send notifications and ask questions."
214
+ },
215
+ devin: {
216
+ ...defaults,
217
+ label: "Devin CLI",
218
+ hookSource: "devin",
219
+ bridged: true,
220
+ policyAgent: "devin",
221
+ repoAware: true,
222
+ disconnectable: true,
223
+ enforcement: "enforced",
224
+ connectVia: "Native hooks",
225
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
226
+ enforcementDetail: "exec, write and edit through native PreToolUse hooks, answered in Devin's own approve/block schema.",
227
+ capabilityNote: "Devin CLI answers PreToolUse with `{decision}` rather than a permission decision. Pushary blocks an unanswered approval because this adapter cannot request a native prompt reliably. Question and plan dialogs remain with Devin; this adapter does not render their answers. It also fires no Notification event."
228
+ },
229
+ windsurf: {
230
+ ...defaults,
231
+ label: "Windsurf",
232
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
233
+ setupChoice: "Other"
234
+ },
235
+ unknown: {
236
+ ...defaults,
237
+ label: "Agent",
238
+ documented: false
239
+ },
240
+ lovable: {
241
+ ...defaults,
242
+ label: "Lovable",
243
+ setupGuide: "[Lovable setup](/docs/agents/guides/lovable)"
244
+ },
245
+ mcp: {
246
+ ...defaults,
247
+ hasStoredIdentity: false,
248
+ label: "Any MCP client",
249
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
250
+ setupChoice: "Other"
251
+ },
252
+ http: {
253
+ ...defaults,
254
+ hasStoredIdentity: false,
255
+ label: "No-code and HTTP",
256
+ connectVia: "REST with a Bearer token",
257
+ setupGuide: "[Connect any agent](/docs/agents/connect-any-agent)"
258
+ }
259
+ };
260
+ var HOOK_SOURCES = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.hookSource === null ? [] : [agent.hookSource]));
261
+ var POLICY_AGENTS = [...Object.values(AGENTS).flatMap((agent) => agent.policyAgent === null ? [] : [agent.policyAgent]), "*"];
262
+ var AGENT_LABELS_BY_TYPE = Object.fromEntries(
263
+ Object.entries(AGENTS).flatMap(([id, agent]) => agent.hasStoredIdentity ? [[id, agent.label]] : [])
264
+ );
265
+ var DISCONNECTABLE_AGENTS = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.disconnectable ? [agent.hookSource] : []));
266
+ var REPO_AWARE_AGENTS = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.repoAware ? [agent.policyAgent] : []));
267
+ var MANAGED_PROVIDERS = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.managedProvider ? [agent.hookSource] : []));
268
+ var BRIDGE_HOOK_SOURCES = Object.freeze(Object.values(AGENTS).flatMap((agent) => agent.bridged ? [agent.hookSource] : []));
269
+
270
+ // ../contracts/src/agent-wiring.ts
271
+ var COMPATIBLE_AGENT_TYPES = ["qwen_code", "codebuddy", "qoder", "droid", "devin"];
272
+ var CANONICAL_PATH_KEYS = ["file_path", "path", "notebook_path"];
273
+ var CANONICAL_COMMAND_KEYS = ["command"];
274
+ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
275
+ var PATH_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit", "Read"]);
276
+ var PHONE_ANSWERED_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
277
+ var firstString = (record, keys) => {
278
+ for (const key of keys) {
279
+ const value = record[key];
280
+ if (typeof value === "string" && value.length > 0) return value;
281
+ }
282
+ return void 0;
283
+ };
284
+ var canonicalToolName = (profile, toolName) => profile.toolAliases[toolName] ?? toolName;
285
+ var toToolLookup = (profile, toolName, toolInput, resolvePath = (path) => path) => {
286
+ const tool = canonicalToolName(profile, toolName);
287
+ if (SHELL_TOOLS.has(tool)) {
288
+ const command = firstString(toolInput, [...profile.shellCommandKeys, ...CANONICAL_COMMAND_KEYS]);
289
+ if (!command) return { tool, input: toolInput, unreadable: true };
290
+ const context = Object.fromEntries(
291
+ profile.shellContextKeys.flatMap((key) => toolInput[key] === void 0 ? [] : [[key, toolInput[key]]])
292
+ );
293
+ return { tool, input: { command, ...context }, unreadable: false };
294
+ }
295
+ if (!PATH_TOOLS.has(tool)) return { tool, input: toolInput, unreadable: false };
296
+ const filePath = firstString(toolInput, [...profile.pathInputKeys, ...CANONICAL_PATH_KEYS]);
297
+ if (!filePath) return { tool, input: toolInput, unreadable: true };
298
+ return { tool, input: { ...toolInput, file_path: resolvePath(filePath) }, unreadable: false };
299
+ };
300
+ var renderPreToolUseVerdict = (profile, verdict) => {
301
+ if (profile.wire === "permission_decision") return verdict;
302
+ const { permissionDecision, permissionDecisionReason, updatedInput } = verdict.hookSpecificOutput;
303
+ if (permissionDecision === "ask") return { decision: "block", reason: permissionDecisionReason ?? "Pushary requires an explicit approval before this tool can run." };
304
+ if (updatedInput) return void 0;
305
+ const stop = verdict.continue === false ? { continue: false, ...verdict.stopReason ? { stopReason: verdict.stopReason } : {} } : {};
306
+ return {
307
+ decision: permissionDecision === "allow" ? "approve" : "block",
308
+ ...permissionDecisionReason ? { reason: permissionDecisionReason } : {},
309
+ ...stop
310
+ };
311
+ };
312
+ var CLAUDE_SHAPED_EVENTS = [
313
+ "PreToolUse",
314
+ "PostToolUse",
315
+ "Stop",
316
+ "UserPromptSubmit",
317
+ "Notification",
318
+ "SessionStart",
319
+ "SessionEnd",
320
+ "StopFailure",
321
+ "PermissionRequest",
322
+ "PermissionDenied",
323
+ "SubagentStart",
324
+ "SubagentStop",
325
+ "PreCompact",
326
+ "PostCompact",
327
+ "TeammateIdle",
328
+ "Elicitation"
329
+ ];
330
+ var without = (...absent) => CLAUDE_SHAPED_EVENTS.filter((event) => !absent.includes(event));
331
+ var QWEN_CODE = {
332
+ agentType: "qwen_code",
333
+ source: "qwen",
334
+ label: "Qwen Code",
335
+ binary: "qwen",
336
+ configFile: [".qwen", "settings.json"],
337
+ hooksKeyPath: ["hooks"],
338
+ toolAliases: {
339
+ run_shell_command: "Bash",
340
+ write_file: "Write",
341
+ edit: "Edit",
342
+ replace: "Edit",
343
+ read_file: "Read",
344
+ WriteFile: "Write",
345
+ ReadFile: "Read",
346
+ ask_user_question: "AskUserQuestion",
347
+ exit_plan_mode: "ExitPlanMode"
348
+ },
349
+ extraMatchers: ["mcp__.*"],
350
+ pathInputKeys: ["absolute_path"],
351
+ shellCommandKeys: [],
352
+ shellContextKeys: ["directory"],
353
+ events: without("TeammateIdle", "Elicitation", "PermissionDenied"),
354
+ wire: "permission_decision",
355
+ mcp: { shape: "gemini_http", file: [".qwen", "settings.json"], trusted: true },
356
+ instructions: { file: [".qwen", "QWEN.md"], label: "Qwen Code" },
357
+ permissionRuleFile: null
358
+ };
359
+ var CODEBUDDY = {
360
+ agentType: "codebuddy",
361
+ source: "codebuddy",
362
+ label: "CodeBuddy",
363
+ binary: "codebuddy",
364
+ configFile: [".codebuddy", "settings.json"],
365
+ hooksKeyPath: ["hooks"],
366
+ toolAliases: {},
367
+ extraMatchers: ["mcp__.*"],
368
+ pathInputKeys: [],
369
+ shellCommandKeys: [],
370
+ shellContextKeys: [],
371
+ events: without("Elicitation", "PermissionDenied"),
372
+ wire: "permission_decision",
373
+ mcp: { shape: "claude_http", file: [".codebuddy", ".mcp.json"], trusted: false },
374
+ instructions: { file: [".codebuddy", "CODEBUDDY.md"], label: "CodeBuddy" },
375
+ permissionRuleFile: [".codebuddy", "settings.json"]
376
+ };
377
+ var QODER = {
378
+ agentType: "qoder",
379
+ source: "qoder",
380
+ label: "Qoder",
381
+ binary: "qoder",
382
+ configFile: [".qoder", "settings.json"],
383
+ hooksKeyPath: ["hooks"],
384
+ toolAliases: {},
385
+ extraMatchers: ["mcp__.*"],
386
+ pathInputKeys: [],
387
+ shellCommandKeys: [],
388
+ shellContextKeys: [],
389
+ events: without("Elicitation", "PermissionDenied"),
390
+ wire: "permission_decision",
391
+ mcp: { shape: "claude_http", file: [".qoder", "settings.json"], trusted: true },
392
+ instructions: null,
393
+ permissionRuleFile: null
394
+ };
395
+ var DROID = {
396
+ agentType: "droid",
397
+ source: "droid",
398
+ label: "Droid",
399
+ binary: "droid",
400
+ configFile: [".factory", "hooks.json"],
401
+ hooksKeyPath: [],
402
+ toolAliases: {
403
+ Execute: "Bash",
404
+ Create: "Write",
405
+ ApplyPatch: "Edit",
406
+ FetchUrl: "WebFetch"
407
+ },
408
+ extraMatchers: ["mcp__.*"],
409
+ pathInputKeys: [],
410
+ shellCommandKeys: [],
411
+ shellContextKeys: [],
412
+ events: ["PreToolUse", "PostToolUse", "UserPromptSubmit", "Notification", "Stop", "SubagentStop", "PreCompact", "SessionStart", "SessionEnd"],
413
+ wire: "permission_decision",
414
+ mcp: null,
415
+ instructions: null,
416
+ permissionRuleFile: null
417
+ };
418
+ var DEVIN = {
419
+ agentType: "devin",
420
+ source: "devin",
421
+ label: "Devin CLI",
422
+ binary: "devin",
423
+ configFile: [".config", "devin", "config.json"],
424
+ hooksKeyPath: ["hooks"],
425
+ toolAliases: {
426
+ exec: "Bash",
427
+ write: "Write",
428
+ edit: "Edit",
429
+ apply_patch: "Edit",
430
+ notebook_edit: "NotebookEdit",
431
+ read: "Read",
432
+ webfetch: "WebFetch",
433
+ web_search: "WebSearch",
434
+ run_subagent: "Task"
435
+ },
436
+ extraMatchers: ["mcp__.*", "mcp_call_tool", "write_to_process"],
437
+ pathInputKeys: [],
438
+ shellCommandKeys: [],
439
+ shellContextKeys: [],
440
+ events: ["PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop", "SessionStart", "SessionEnd"],
441
+ wire: "legacy_decision",
442
+ mcp: null,
443
+ instructions: null,
444
+ permissionRuleFile: null
445
+ };
446
+ var HOOK_COMPATIBLE_PROFILES = Object.freeze([QWEN_CODE, CODEBUDDY, QODER, DROID, DEVIN]);
447
+ var hookCompatibleProfile = (agentType) => HOOK_COMPATIBLE_PROFILES.find((profile) => profile.agentType === agentType);
448
+ var hookCompatibleProfileBySource = (source) => HOOK_COMPATIBLE_PROFILES.find((profile) => profile.source === source);
449
+ var compatibleTargetFiles = (profile) => {
450
+ const seen = /* @__PURE__ */ new Map();
451
+ for (const file of [profile.configFile, profile.mcp?.file, profile.permissionRuleFile, profile.instructions?.file]) {
452
+ if (file) seen.set(file.join("/"), file);
453
+ }
454
+ return [...seen.values()];
455
+ };
456
+
457
+ // ../contracts/src/redaction.ts
458
+ var CREDENTIAL_NAME = String.raw`(?:authorization|secret|token|password|passwd|api[_-]?key|access[_-]?key|client[_-]?secret|private[_-]?key)["']?`;
459
+ var CREDENTIAL_KEY = String.raw`${CREDENTIAL_NAME}\s*[=:]\s*`;
460
+ var DISPLAYED_CREDENTIAL_KEY = String.raw`${CREDENTIAL_NAME}(?:[ \t]*:[ \t]*|[ \t]+=[ \t]*|=)`;
461
+ var SHELL_INERT_UNIT = String.raw`(?:[A-Za-z0-9_\-./+=:@%^!#]|\\[^\s'"])`;
462
+ var SHELL_INERT_SINGLE_QUOTED_UNIT = String.raw`[A-Za-z0-9_\-./+=:@%^!#\\]`;
463
+ var SHELL_INERT_URL_PASSWORD_UNIT = String.raw`(?:[A-Za-z0-9_\-.+=:%^!#]|\\[^\s'"@/])`;
464
+ var PEM_LINE_BREAK = String.raw`(?:\r?\n|(?:\\r)?\\n)`;
465
+ var credentialAssignmentRule = (key, doubleQuoted, singleQuoted, bare) => ({
466
+ pattern: new RegExp(String.raw`(${key})(?:(")${doubleQuoted}|(')${singleQuoted}|(["']?)${bare})`, "gi"),
467
+ replacement: "$1$2$3$4[redacted]$2$3"
468
+ });
469
+ var DISPLAYED_SECRET_SHAPES = {
470
+ privateKeyBody: String.raw`${PEM_LINE_BREAK}?(?:(?:Proc-Type|DEK-Info): [A-Za-z0-9,-]+${PEM_LINE_BREAK})*(?:[A-Za-z0-9+/=\r\n]|\\[rn])*?`,
471
+ urlPassword: `${SHELL_INERT_URL_PASSWORD_UNIT}+`,
472
+ authorizationValue: `${SHELL_INERT_UNIT}+`,
473
+ credentialAssignment: credentialAssignmentRule(
474
+ DISPLAYED_CREDENTIAL_KEY,
475
+ `${SHELL_INERT_UNIT}*"`,
476
+ `${SHELL_INERT_SINGLE_QUOTED_UNIT}*'`,
477
+ `${SHELL_INERT_UNIT}+`
478
+ )
479
+ };
480
+ var CUT_OFF_SECRET_SHAPES = {
481
+ privateKeyBody: String.raw`[\s\S]*?`,
482
+ urlPassword: String.raw`[^\s@/]+`,
483
+ authorizationValue: String.raw`\S+`,
484
+ credentialAssignment: credentialAssignmentRule(
485
+ CREDENTIAL_KEY,
486
+ String.raw`(?:\\[\s\S]|[^"\\])*(?:"|\\?$)`,
487
+ "[^']*(?:'|$)",
488
+ String.raw`\S+`
489
+ )
490
+ };
491
+ var secretRedactionRules = (shapes) => [
492
+ { pattern: /\bpk_[A-Za-z0-9_]+\.[A-Za-z0-9_]+/g, replacement: "[redacted]" },
493
+ { pattern: new RegExp(String.raw`-----BEGIN[A-Z0-9 ]*PRIVATE KEY-----${shapes.privateKeyBody}-----END[A-Z0-9 ]*PRIVATE KEY-----`, "g"), replacement: "[redacted key]" },
494
+ { pattern: /\bsk-[A-Za-z0-9_-]{16,}\b/g, replacement: "[redacted]" },
495
+ { pattern: /\b[spr]k_(?:live|test)_[A-Za-z0-9]{8,}\b/g, replacement: "[redacted]" },
496
+ { pattern: /\bwhsec_[A-Za-z0-9]{16,}\b/g, replacement: "[redacted]" },
497
+ { pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b/g, replacement: "[redacted]" },
498
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{22,}\b/g, replacement: "[redacted]" },
499
+ { pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g, replacement: "[redacted]" },
500
+ { pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, replacement: "[redacted]" },
501
+ { pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g, replacement: "[redacted]" },
502
+ { pattern: /\bAKIA[0-9A-Z]{16}\b/g, replacement: "[redacted]" },
503
+ { pattern: /\bnpm_[A-Za-z0-9]{36}\b/g, replacement: "[redacted]" },
504
+ { pattern: /\bxai-[A-Za-z0-9]{16,}\b/g, replacement: "[redacted]" },
505
+ { pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, replacement: "[redacted]" },
506
+ { pattern: new RegExp(String.raw`\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)${shapes.urlPassword}@`, "gi"), replacement: "$1[redacted]@" },
507
+ { pattern: /\bbearer\s+[A-Za-z0-9._~+/=-]+/gi, replacement: "bearer [redacted]" },
508
+ { pattern: new RegExp(String.raw`\bauthorization:\s*${shapes.authorizationValue}`, "gi"), replacement: "authorization: [redacted]" },
509
+ shapes.credentialAssignment
510
+ ];
511
+ var SECRET_REDACTION_RULES = secretRedactionRules(DISPLAYED_SECRET_SHAPES);
512
+ var CUT_OFF_TEXT_REDACTION_RULES = secretRedactionRules(CUT_OFF_SECRET_SHAPES);
513
+ var HIGH_ENTROPY_RULE = { pattern: /[A-Za-z0-9+/]{40,}={0,2}/g, replacement: "[redacted]" };
514
+ var applyRedactionRules = (rules, text) => rules.reduce((acc, rule) => acc.replace(rule.pattern, rule.replacement), text);
515
+ var redactSecrets = (text) => applyRedactionRules(SECRET_REDACTION_RULES, text);
516
+ var redactSecretsFromContent = (text) => applyRedactionRules(CUT_OFF_TEXT_REDACTION_RULES, text);
517
+ var redactSecretsDeep = (text) => redactSecrets(text).replace(HIGH_ENTROPY_RULE.pattern, HIGH_ENTROPY_RULE.replacement);
518
+
519
+ // ../contracts/src/degraded.ts
520
+ var DEGRADED_PREFIX = "[degraded]";
521
+ var degradedPayload = (subsystem, report) => ({
522
+ subsystem,
523
+ reason: report.reason,
524
+ fallback: report.fallback,
525
+ ...report.siteId ? { siteId: report.siteId } : {},
526
+ ...report.sessionId ? { sessionId: report.sessionId } : {},
527
+ ...report.event ? { event: report.event } : {}
528
+ });
529
+ var degradedLine = (subsystem, report) => `${DEGRADED_PREFIX} ${JSON.stringify(degradedPayload(subsystem, report))}`;
530
+
531
+ // ../contracts/src/setup-wire.ts
532
+ var PAIR_TTL_SECONDS = 900;
533
+ var SETUP_AGENT_CHOICES = [
534
+ "claude_code",
535
+ "codex",
536
+ "gemini_cli",
537
+ "hermes",
538
+ "cursor",
539
+ "vscode",
540
+ "opencode",
541
+ "fx",
542
+ ...COMPATIBLE_AGENT_TYPES,
543
+ "custom"
544
+ ];
545
+ var SETUP_CONNECT_MODES = ["app", "browser", "web", "none"];
546
+ var isSetupConnectMode = (value) => typeof value === "string" && SETUP_CONNECT_MODES.includes(value);
547
+
548
+ // ../contracts/src/observability.ts
549
+ var CLIENT_CONTEXT_HEADERS = {
550
+ surface: "x-pushary-surface",
551
+ clientVersion: "x-pushary-client-version",
552
+ machineId: "x-pushary-machine-id",
553
+ runId: "x-pushary-run-id",
554
+ platform: "x-pushary-platform"
555
+ };
556
+ var CLIENT_REPORT_PATH = "/api/agent/client-report";
557
+ var CLIENT_VERSION_PATTERN = /^\d+(?:\.\d+){1,3}(?:[-+][0-9A-Za-z.-]+)?$/;
558
+ var CONTEXT_ID_PATTERN = /^[A-Za-z0-9-]{8,64}$/;
559
+ var PLATFORM_PATTERN = /^[a-z0-9_.-]{1,32}$/;
560
+ var CONTEXT_VALUE_MAX_LENGTH = 64;
561
+ var isClientVersion = (value) => typeof value === "string" && value.length <= CONTEXT_VALUE_MAX_LENGTH && CLIENT_VERSION_PATTERN.test(value);
562
+ var isContextId = (value) => typeof value === "string" && CONTEXT_ID_PATTERN.test(value);
563
+ var isClientPlatform = (value) => typeof value === "string" && PLATFORM_PATTERN.test(value);
564
+ var clientContextHeaders = (source) => {
565
+ const headers = {
566
+ [CLIENT_CONTEXT_HEADERS.surface]: source.surface,
567
+ [CLIENT_CONTEXT_HEADERS.runId]: source.runId
568
+ };
569
+ if (isClientVersion(source.clientVersion)) headers[CLIENT_CONTEXT_HEADERS.clientVersion] = source.clientVersion;
570
+ if (isContextId(source.machineId)) headers[CLIENT_CONTEXT_HEADERS.machineId] = source.machineId;
571
+ if (isClientPlatform(source.platform)) headers[CLIENT_CONTEXT_HEADERS.platform] = source.platform;
572
+ return headers;
573
+ };
574
+
575
+ // ../contracts/src/index.ts
576
+ var KEY_SCOPES = ["full_access", "agent", "connector", "bound"];
577
+ var normalizeKeyScope = (value) => typeof value === "string" && KEY_SCOPES.includes(value) ? value : null;
578
+ var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
579
+ var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
580
+ var MAX_AGENT_QUESTIONS = 4;
581
+ var parseAgentQuestion = (raw) => {
582
+ const q = raw;
583
+ if (!q || typeof q.question !== "string") return void 0;
584
+ const options = Array.isArray(q.options) ? q.options.flatMap((option) => {
585
+ if (!option || typeof option !== "object") return [];
586
+ const value = option;
587
+ if (typeof value.label !== "string" || value.label.length === 0) return [];
588
+ return [{
589
+ label: value.label,
590
+ ...typeof value.description === "string" ? { description: value.description } : {}
591
+ }];
592
+ }) : [];
593
+ if (options.length < 2) return void 0;
594
+ return {
595
+ question: q.question,
596
+ ...typeof q.header === "string" ? { header: q.header } : {},
597
+ multiSelect: q.multiSelect === true,
598
+ ...typeof q.allowOther === "boolean" ? { allowOther: q.allowOther } : {},
599
+ options
600
+ };
601
+ };
602
+ var parseAgentQuestions = (toolInput) => {
603
+ const questions = toolInput.questions;
604
+ if (!Array.isArray(questions) || questions.length === 0) return void 0;
605
+ if (questions.length > MAX_AGENT_QUESTIONS) return void 0;
606
+ const parsed = [];
607
+ for (const raw of questions) {
608
+ const one = parseAgentQuestion(raw);
609
+ if (!one || parsed.some((question) => question.question === one.question)) return void 0;
610
+ parsed.push(one);
611
+ }
612
+ return parsed;
613
+ };
614
+ var parseAgentQuestionAnswers = (questions, value) => {
615
+ let parsed;
616
+ try {
617
+ parsed = JSON.parse(value);
618
+ } catch {
619
+ return null;
620
+ }
621
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
622
+ const answers = parsed;
623
+ if (Object.keys(answers).length !== questions.length) return null;
624
+ for (const question of questions) {
625
+ const answer = answers[question.question];
626
+ if (typeof answer !== "string" || answer.trim().length === 0) return null;
627
+ const labels = new Set(question.options.map((option) => option.label));
628
+ if (!question.multiSelect) {
629
+ if (question.allowOther === false && !labels.has(answer)) return null;
630
+ continue;
631
+ }
632
+ try {
633
+ const selected = JSON.parse(answer);
634
+ if (!Array.isArray(selected)) {
635
+ if (question.allowOther === false) return null;
636
+ continue;
637
+ }
638
+ if (selected.length === 0 || new Set(selected).size !== selected.length || !selected.every((label) => typeof label === "string" && labels.has(label))) return null;
639
+ } catch {
640
+ if (question.allowOther === false) return null;
641
+ }
642
+ }
643
+ return answers;
644
+ };
645
+ var INSTALL_SOURCES = [
646
+ /** `npx @pushary/agent-hooks@latest ...`, the documented one-liner. */
647
+ "npx",
648
+ /** A global or project npm install, invoked as `pushary`. */
649
+ "npm",
650
+ /** Claude Code plugin, from the marketplace. Detected via CLAUDE_PLUGIN_ROOT. */
651
+ "claude_plugin",
652
+ /** Cursor or VS Code plugin bundle. */
653
+ "editor_plugin",
654
+ /** Installed by the skills.sh wizard. */
655
+ "skills_sh",
656
+ /** An MCP client configured straight from the registry entry, no CLI involved. */
657
+ "mcp_registry",
658
+ /** One of the published framework adapters (ai-sdk, mastra, langgraph, ...). */
659
+ "framework_adapter",
660
+ /** The browser onboarding wizard. */
661
+ "dashboard",
662
+ /** The macOS app pairing a phone from its own onboarding. */
663
+ "mac_app",
664
+ /** The phone app's own sign-up, which provisions the agent site before any web step. */
665
+ "mobile_app",
666
+ /** The CLI's device-code sign-in page (`/cli/authorize`), which provisions a site before any web step. */
667
+ "cli_login",
668
+ /** Reported nothing, or reported something we do not recognise. */
669
+ "unknown"
670
+ ];
671
+ var isInstallSource = (value) => typeof value === "string" && INSTALL_SOURCES.includes(value);
672
+ var normalizeInstallSource = (value) => isInstallSource(value) ? value : "unknown";
673
+ var HOOK_BUDGETS = {
674
+ claude: { budgetSeconds: 600, guardSeconds: 10 },
675
+ codex: { budgetSeconds: 180, guardSeconds: 10 },
676
+ gemini: { budgetSeconds: 180, guardSeconds: 10 },
677
+ // The Cursor gate is a dependency-free .mjs that cannot import this module; it
678
+ // mirrors these as MAX_BLOCK_MS (45s) inside its 60s failClosed budget.
679
+ cursor: { budgetSeconds: 60, guardSeconds: 15 },
680
+ vscode: { budgetSeconds: 60, guardSeconds: 15 },
681
+ // OpenCode kills nothing and waits forever: `Permission.ask` blocks on a
682
+ // `Deferred` with no deadline of its own, so this budget is ours to choose
683
+ // rather than one we are told. Chosen to match Claude Code, because what it
684
+ // bounds is how long the person's tool call sits unanswered before Pushary
685
+ // stops asking and hands it back to the terminal.
686
+ opencode: { budgetSeconds: 120, guardSeconds: 10 },
687
+ // The hook-compatible agents take a per-hook `timeout` in seconds exactly as
688
+ // Claude Code does, and `compatibleHookEvents` writes Claude's own numbers into
689
+ // their config, so what we may wait for is what we registered for.
690
+ qwen: { budgetSeconds: 600, guardSeconds: 10 },
691
+ codebuddy: { budgetSeconds: 600, guardSeconds: 10 },
692
+ qoder: { budgetSeconds: 600, guardSeconds: 10 },
693
+ droid: { budgetSeconds: 600, guardSeconds: 10 },
694
+ devin: { budgetSeconds: 600, guardSeconds: 10 }
695
+ };
696
+ var hookMaxWaitSeconds = (agent) => Math.max(HOOK_BUDGETS[agent].budgetSeconds - HOOK_BUDGETS[agent].guardSeconds, 1);
697
+ var HOOK_HOLD_BUDGET_FLAG = "--hold-budget";
698
+ var HOOK_HOLD_BUDGET_ENV = "PUSHARY_HOLD_BUDGET_SECONDS";
699
+ var HOOK_BRIDGE_SOURCE_FLAG = "--source";
700
+ var HOOK_HOLD_BUDGET_SECONDS = /^[1-9]\d{0,5}$/;
701
+ var hookHoldBudgetSeconds = (text) => text !== void 0 && HOOK_HOLD_BUDGET_SECONDS.test(text) ? Number(text) : void 0;
702
+ var hookHoldBudgetArgument = (seconds) => `${HOOK_HOLD_BUDGET_FLAG}=${seconds}`;
703
+ var withHookHoldBudget = (command, holdBudgetSeconds) => holdBudgetSeconds === void 0 ? command : `${command} ${hookHoldBudgetArgument(holdBudgetSeconds)}`;
704
+ var registeredHookHoldBudget = (args) => {
705
+ const prefix = `${HOOK_HOLD_BUDGET_FLAG}=`;
706
+ const proofs = args.filter((arg) => arg.startsWith(prefix));
707
+ return proofs.length === 1 ? hookHoldBudgetSeconds(proofs[0].slice(prefix.length)) : void 0;
708
+ };
709
+ var hookCommandHoldBudget = (command) => registeredHookHoldBudget(command.trim().split(/\s+/));
710
+ var hookHoldBudgetProof = (args, env) => {
711
+ const handedDown = hookHoldBudgetSeconds(env[HOOK_HOLD_BUDGET_ENV]);
712
+ const registered = args.includes(HOOK_BRIDGE_SOURCE_FLAG) ? void 0 : registeredHookHoldBudget(args);
713
+ if (handedDown === void 0) return registered;
714
+ return registered === void 0 ? handedDown : Math.min(handedDown, registered);
715
+ };
716
+ var hookHoldWindowMs = (agent, holdBudgetSeconds) => holdBudgetSeconds === void 0 ? 0 : Math.max((Math.min(holdBudgetSeconds, HOOK_BUDGETS[agent].budgetSeconds) - HOOK_BUDGETS[agent].guardSeconds) * 1e3, 0);
717
+ var hookWaitDeadline = (startMs, policyWaitSeconds, agent, nowMs) => {
718
+ const maxWait = hookMaxWaitSeconds(agent);
719
+ return Math.min(
720
+ nowMs + Math.min(Math.max(policyWaitSeconds, 0), maxWait) * 1e3,
721
+ startMs + maxWait * 1e3
722
+ );
723
+ };
724
+ var hookWaitClamped = (startMs, policyWaitSeconds, agent, nowMs) => startMs + hookMaxWaitSeconds(agent) * 1e3 < nowMs + Math.max(policyWaitSeconds, 0) * 1e3;
725
+ var effectiveWaitSeconds = (timeoutAction, policySeconds, agent) => timeoutAction === "wait" ? hookMaxWaitSeconds(agent) : policySeconds;
726
+ var OPENCODE_APPROVAL_EVENT = "permission.ask";
727
+ var CURSOR_GATE_EVENTS = ["beforeShellExecution", "beforeMCPExecution", "preToolUse"];
728
+ var SUPPORT_EMAIL = "business@pushary.com";
729
+ var SUPPORT_DISCORD = "https://discord.gg/9v3VvUByrr";
730
+ var HOOK_LOCATOR_BINARY = "pushary-bridge";
731
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
732
+ var HOOK_BATCH_MAX_BYTES = 4 * 1024 * 1024;
733
+ var REPO_KEY_MAX_LENGTH = 200;
734
+ var WILDCARD_TOOL_PATTERN = "*";
735
+ var isRepoKeyMatch = (ruleRepoKey, currentRepoKey) => {
736
+ if (!ruleRepoKey) return true;
737
+ return ruleRepoKey === currentRepoKey;
738
+ };
739
+ var SCP_LIKE_REMOTE = /^[A-Za-z0-9._-]+@([A-Za-z0-9._-]+):(.+)$/;
740
+ var URL_LIKE_REMOTE = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/(.+)$/;
741
+ var NUMERIC_PORT = /:\d+$/;
742
+ var stripCredentials = (host) => {
743
+ const at = host.lastIndexOf("@");
744
+ return at === -1 ? host : host.slice(at + 1);
745
+ };
746
+ var normalizeRepoRemote = (remoteUrl) => {
747
+ const raw = remoteUrl.trim();
748
+ if (raw.length === 0) return void 0;
749
+ let hostAndPath;
750
+ const scp = SCP_LIKE_REMOTE.exec(raw);
751
+ if (scp) {
752
+ hostAndPath = `${scp[1]}/${scp[2]}`;
753
+ } else {
754
+ const url = URL_LIKE_REMOTE.exec(raw);
755
+ if (!url) return void 0;
756
+ if (/^file:/i.test(raw)) return void 0;
757
+ hostAndPath = url[1];
758
+ }
759
+ const slash = hostAndPath.indexOf("/");
760
+ if (slash <= 0) return void 0;
761
+ const host = stripCredentials(hostAndPath.slice(0, slash)).replace(NUMERIC_PORT, "");
762
+ const path = hostAndPath.slice(slash + 1).split("/").filter((segment) => segment.length > 0).join("/");
763
+ if (host.length === 0 || path.length === 0) return void 0;
764
+ const key = `${host}/${path}`.replace(/\.git$/i, "").toLowerCase();
765
+ return key.length > REPO_KEY_MAX_LENGTH ? key.slice(0, REPO_KEY_MAX_LENGTH) : key;
766
+ };
767
+ var localRepoKey = (directoryName) => {
768
+ const name = directoryName.trim().replace(/[/\\]+$/, "").toLowerCase();
769
+ if (name.length === 0 || name === "." || name === "..") return void 0;
770
+ const key = `local/${name}`;
771
+ return key.length > REPO_KEY_MAX_LENGTH ? key.slice(0, REPO_KEY_MAX_LENGTH) : key;
772
+ };
773
+ var MATCH_RANKS = ["none", "tool", "prefix", "exact"];
774
+ var matchRankWeight = (rank) => MATCH_RANKS.indexOf(rank);
775
+ var GLOB_MAX_WILDCARDS = 8;
776
+ var GLOB_MAX_ARG_LENGTH = 4096;
777
+ var GLOB_CACHE_MAX = 256;
778
+ var tokenizeGlob = (glob) => {
779
+ let stars = 0;
780
+ for (const char of glob) if (char === "*") stars += 1;
781
+ if (stars > GLOB_MAX_WILDCARDS) return void 0;
782
+ const tokens = [];
783
+ let literal = "";
784
+ const flush = () => {
785
+ if (literal.length > 0) {
786
+ tokens.push({ kind: "literal", value: literal });
787
+ literal = "";
788
+ }
789
+ };
790
+ for (let i = 0; i < glob.length; i += 1) {
791
+ if (glob[i] !== "*") {
792
+ literal += glob[i];
793
+ continue;
794
+ }
795
+ flush();
796
+ if (glob[i + 1] === "*") {
797
+ tokens.push({ kind: "globstar" });
798
+ i += 1;
799
+ continue;
800
+ }
801
+ tokens.push({ kind: "star" });
802
+ }
803
+ flush();
804
+ return tokens;
805
+ };
806
+ var SLASH = "/".charCodeAt(0);
807
+ var matchGlobTokens = (tokens, text) => {
808
+ const n = text.length;
809
+ let reachable = new Uint8Array(n + 1);
810
+ reachable[0] = 1;
811
+ for (const token of tokens) {
812
+ const next = new Uint8Array(n + 1);
813
+ if (token.kind === "literal") {
814
+ const { value } = token;
815
+ for (let p = 0; p <= n - value.length; p += 1) {
816
+ if (reachable[p] && text.startsWith(value, p)) next[p + value.length] = 1;
817
+ }
818
+ } else {
819
+ const spansSeparators = token.kind === "globstar";
820
+ let open = 0;
821
+ for (let p = 0; p <= n; p += 1) {
822
+ if (!spansSeparators && p > 0 && text.charCodeAt(p - 1) === SLASH) open = 0;
823
+ if (reachable[p]) open = 1;
824
+ if (open) next[p] = 1;
825
+ }
826
+ }
827
+ reachable = next;
828
+ }
829
+ return reachable[n] === 1;
830
+ };
831
+ var globCache = /* @__PURE__ */ new Map();
832
+ var compiledGlob = (glob) => {
833
+ const cached = globCache.get(glob);
834
+ if (cached !== void 0 || globCache.has(glob)) return cached;
835
+ const compiled = tokenizeGlob(glob);
836
+ if (globCache.size >= GLOB_CACHE_MAX) globCache.clear();
837
+ globCache.set(glob, compiled);
838
+ return compiled;
839
+ };
840
+ var PATTERN_INTENT = {
841
+ grant: "grant",
842
+ restrict: "restrict"
843
+ };
844
+ var SHELL_ESCAPE = "\\";
845
+ var SHELL_DOUBLE_QUOTE = '"';
846
+ var SHELL_SUBSTITUTION_SIGIL = "$";
847
+ var SHELL_GROUP_OPEN = "(";
848
+ var SHELL_QUOTES = /* @__PURE__ */ new Set(["'", SHELL_DOUBLE_QUOTE]);
849
+ var SHELL_SEGMENT_SEPARATORS = /* @__PURE__ */ new Set([";", "\n", "&", "|"]);
850
+ var SHELL_DOUBLED_SEPARATORS = /* @__PURE__ */ new Set(["&", "|"]);
851
+ var SHELL_UNDECOMPOSABLE = /* @__PURE__ */ new Set(["`", "<", ">"]);
852
+ var shellCommandSegments = (command) => {
853
+ const segments = [];
854
+ let current = "";
855
+ let quote = null;
856
+ for (let index = 0; index < command.length; index += 1) {
857
+ const char = command[index];
858
+ const next = command[index + 1];
859
+ if (quote !== null) {
860
+ if (char === SHELL_ESCAPE && quote === SHELL_DOUBLE_QUOTE && next !== void 0) {
861
+ current += char + next;
862
+ index += 1;
863
+ continue;
864
+ }
865
+ if (quote === SHELL_DOUBLE_QUOTE && (char === "`" || char === SHELL_SUBSTITUTION_SIGIL && next === SHELL_GROUP_OPEN)) return null;
866
+ if (char === quote) quote = null;
867
+ current += char;
868
+ continue;
869
+ }
870
+ if (char === SHELL_ESCAPE && next !== void 0) {
871
+ current += char + next;
872
+ index += 1;
873
+ continue;
874
+ }
875
+ if (SHELL_QUOTES.has(char)) {
876
+ quote = char;
877
+ current += char;
878
+ continue;
879
+ }
880
+ if (SHELL_UNDECOMPOSABLE.has(char)) return null;
881
+ if (char === SHELL_SUBSTITUTION_SIGIL && next === SHELL_GROUP_OPEN) return null;
882
+ if (SHELL_SEGMENT_SEPARATORS.has(char)) {
883
+ if (next === char && SHELL_DOUBLED_SEPARATORS.has(char)) index += 1;
884
+ segments.push(current);
885
+ current = "";
886
+ continue;
887
+ }
888
+ current += char;
889
+ }
890
+ if (quote !== null) return null;
891
+ segments.push(current);
892
+ const present = segments.map((segment) => segment.trim()).filter((segment) => segment.length > 0);
893
+ return present.length > 0 ? present : null;
894
+ };
895
+ var everySegmentMatches = (command, matches) => {
896
+ const segments = shellCommandSegments(command);
897
+ return segments !== null && segments.every(matches);
898
+ };
899
+ var anySegmentMatches = (command, matches) => (shellCommandSegments(command) ?? [command]).some(matches);
900
+ var shellSegmentsMatch = (command, intent, matches) => intent === PATTERN_INTENT.restrict ? anySegmentMatches(command, matches) : everySegmentMatches(command, matches);
901
+ var rankWholeOrSegments = (toolName, arg, intent, matches) => {
902
+ const matched = toolName in POLICY_COMMAND_KEYS ? shellSegmentsMatch(arg, intent, matches) : matches(arg);
903
+ return matched ? "prefix" : "none";
904
+ };
905
+ var matchToolPattern = (pattern, toolName, arg, intent = PATTERN_INTENT.grant) => {
906
+ const open = pattern.indexOf("(");
907
+ if (open === -1 || !pattern.endsWith(")")) {
908
+ return pattern === toolName ? "tool" : "none";
909
+ }
910
+ if (pattern.slice(0, open) !== toolName || arg === void 0) return "none";
911
+ const inner = pattern.slice(open + 1, -1);
912
+ if (inner.endsWith(":*")) {
913
+ const prefix = inner.slice(0, -2);
914
+ return rankWholeOrSegments(toolName, arg, intent, (segment) => segment.startsWith(prefix));
915
+ }
916
+ if (inner.includes("*")) {
917
+ if (arg.length > GLOB_MAX_ARG_LENGTH) return "none";
918
+ const compiled = compiledGlob(inner);
919
+ if (!compiled) return "none";
920
+ return rankWholeOrSegments(toolName, arg, intent, (segment) => matchGlobTokens(compiled, segment));
921
+ }
922
+ return arg === inner ? "exact" : "none";
923
+ };
924
+ var POLICY_PATH_KEYS = {
925
+ Read: "file_path",
926
+ Edit: "file_path",
927
+ Write: "file_path",
928
+ MultiEdit: "file_path",
929
+ NotebookEdit: "notebook_path",
930
+ LS: "path"
931
+ };
932
+ var POLICY_COMMAND_KEYS = {
933
+ Bash: "command",
934
+ PowerShell: "command",
935
+ Monitor: "command"
936
+ };
937
+ var POLICY_ARG_KEYS = { ...POLICY_COMMAND_KEYS, ...POLICY_PATH_KEYS };
938
+ var extractPolicyArg = (toolName, toolInput) => {
939
+ const key = POLICY_ARG_KEYS[toolName];
940
+ if (!key) return void 0;
941
+ const value = toolInput[key];
942
+ return typeof value === "string" ? value : void 0;
943
+ };
944
+ var normalizeSeparators = (value) => value.replace(/\\/g, "/");
945
+ var isAbsolutePath = (value) => value.startsWith("/") || /^[A-Za-z]:\//.test(value);
946
+ var relativeToCwd = (path, cwd) => {
947
+ const base = normalizeSeparators(cwd).replace(/\/+$/, "");
948
+ if (!base) return void 0;
949
+ const prefix = `${base}/`;
950
+ if (!path.startsWith(prefix)) return void 0;
951
+ const rest = path.slice(prefix.length);
952
+ return rest.length > 0 ? rest : void 0;
953
+ };
954
+ var policyArgForms = (toolName, toolInput, cwd) => {
955
+ const raw = extractPolicyArg(toolName, toolInput);
956
+ if (raw === void 0) return [];
957
+ if (!(toolName in POLICY_PATH_KEYS)) return [raw];
958
+ const forms = [raw];
959
+ const normalized = normalizeSeparators(raw);
960
+ if (normalized !== raw) forms.push(normalized);
961
+ if (cwd && isAbsolutePath(normalized)) {
962
+ const relative = relativeToCwd(normalized, cwd);
963
+ if (relative !== void 0) forms.push(relative);
964
+ }
965
+ return forms;
966
+ };
967
+ var matchToolPatternAny = (pattern, toolName, args, intent = PATTERN_INTENT.grant) => {
968
+ if (args.length === 0) return matchToolPattern(pattern, toolName);
969
+ let best = "none";
970
+ for (const arg of args) {
971
+ const rank = matchToolPattern(pattern, toolName, arg, intent);
972
+ if (matchRankWeight(rank) > matchRankWeight(best)) best = rank;
973
+ }
974
+ return best;
975
+ };
976
+ var SAFE_SHELL_COMMANDS = /* @__PURE__ */ new Set([
977
+ "ls",
978
+ "pwd",
979
+ "cd",
980
+ "cat",
981
+ "head",
982
+ "tail",
983
+ "wc",
984
+ "echo",
985
+ "printf",
986
+ "which",
987
+ "type",
988
+ "whoami",
989
+ "id",
990
+ "uname",
991
+ "arch",
992
+ "printenv",
993
+ "locale",
994
+ "tty",
995
+ "dirname",
996
+ "basename",
997
+ "realpath",
998
+ "readlink",
999
+ "stat",
1000
+ "cut",
1001
+ "nl",
1002
+ "tr",
1003
+ "comm",
1004
+ "diff",
1005
+ "cmp",
1006
+ "grep",
1007
+ "egrep",
1008
+ "fgrep",
1009
+ "jq",
1010
+ "cksum",
1011
+ "md5sum",
1012
+ "sha1sum",
1013
+ "sha256sum",
1014
+ "du",
1015
+ "df",
1016
+ "ps",
1017
+ "true"
1018
+ ]);
1019
+ var SAFE_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set([
1020
+ "status",
1021
+ "log",
1022
+ "diff",
1023
+ "show",
1024
+ "rev-parse",
1025
+ "describe",
1026
+ "blame",
1027
+ "shortlog",
1028
+ "ls-files",
1029
+ "ls-tree",
1030
+ "cat-file",
1031
+ "whatchanged",
1032
+ "rev-list",
1033
+ "name-rev",
1034
+ "for-each-ref",
1035
+ "var",
1036
+ "count-objects"
1037
+ ]);
1038
+ var GIT_WRITE_FLAGS = (token) => token === "--output" || token.startsWith("--output=");
1039
+ var DANGEROUS_FIND_ACTIONS = /* @__PURE__ */ new Set([
1040
+ "-exec",
1041
+ "-execdir",
1042
+ "-ok",
1043
+ "-okdir",
1044
+ "-delete",
1045
+ "-fprintf",
1046
+ "-fprint",
1047
+ "-fprint0",
1048
+ "-fls"
1049
+ ]);
1050
+ var SAFE_FIND_TOKENS = /* @__PURE__ */ new Set([
1051
+ "-name",
1052
+ "-iname",
1053
+ "-path",
1054
+ "-ipath",
1055
+ "-wholename",
1056
+ "-iwholename",
1057
+ "-lname",
1058
+ "-ilname",
1059
+ "-regex",
1060
+ "-iregex",
1061
+ "-type",
1062
+ "-xtype",
1063
+ "-maxdepth",
1064
+ "-mindepth",
1065
+ "-depth",
1066
+ "-mount",
1067
+ "-xdev",
1068
+ "-size",
1069
+ "-empty",
1070
+ "-perm",
1071
+ "-readable",
1072
+ "-writable",
1073
+ "-executable",
1074
+ "-mtime",
1075
+ "-mmin",
1076
+ "-atime",
1077
+ "-amin",
1078
+ "-ctime",
1079
+ "-cmin",
1080
+ "-newer",
1081
+ "-newermt",
1082
+ "-anewer",
1083
+ "-cnewer",
1084
+ "-user",
1085
+ "-uid",
1086
+ "-group",
1087
+ "-gid",
1088
+ "-nouser",
1089
+ "-nogroup",
1090
+ "-samefile",
1091
+ "-inum",
1092
+ "-links",
1093
+ "-print",
1094
+ "-print0",
1095
+ "-printf",
1096
+ "-ls",
1097
+ "-quit",
1098
+ "-prune",
1099
+ "-follow",
1100
+ "-noleaf",
1101
+ "-ignore_readdir_race",
1102
+ "-noignore_readdir_race",
1103
+ "-true",
1104
+ "-false",
1105
+ "-not",
1106
+ "-and",
1107
+ "-or",
1108
+ "-o",
1109
+ "-a",
1110
+ "-P",
1111
+ "-L",
1112
+ "-H",
1113
+ "-D",
1114
+ "-O"
1115
+ ]);
1116
+ var SAFE_REDIRECTION = /\s*(?:[0-9]*>&[0-9-]+|(?:[0-9]*|&)>>?\s*\/dev\/null)/g;
1117
+ var basenameOf = (token) => {
1118
+ const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
1119
+ return slash === -1 ? token : token.slice(slash + 1);
1120
+ };
1121
+ var TRUSTED_EXE_PREFIXES = [
1122
+ "/bin/",
1123
+ "/sbin/",
1124
+ "/usr/bin/",
1125
+ "/usr/sbin/",
1126
+ "/usr/local/bin/",
1127
+ "/opt/homebrew/bin/"
1128
+ ];
1129
+ var isTrustedExecutable = (first) => {
1130
+ if (!first.includes("/")) return true;
1131
+ if (first.includes("..")) return false;
1132
+ return TRUSTED_EXE_PREFIXES.some((prefix) => first.startsWith(prefix));
1133
+ };
1134
+ var tokenizeShellCommand = (command) => {
1135
+ const tokens = [];
1136
+ let current = "";
1137
+ let quote = null;
1138
+ let started = false;
1139
+ for (const ch of command) {
1140
+ if (quote) {
1141
+ if (ch === quote) quote = null;
1142
+ else current += ch;
1143
+ started = true;
1144
+ continue;
1145
+ }
1146
+ if (ch === '"' || ch === "'") {
1147
+ quote = ch;
1148
+ started = true;
1149
+ continue;
1150
+ }
1151
+ if (ch === " " || ch === " ") {
1152
+ if (started) {
1153
+ tokens.push(current);
1154
+ current = "";
1155
+ started = false;
1156
+ }
1157
+ continue;
1158
+ }
1159
+ current += ch;
1160
+ started = true;
1161
+ }
1162
+ if (quote) return null;
1163
+ if (started) tokens.push(current);
1164
+ return tokens;
1165
+ };
1166
+ var isSafeSimpleCommand = (segment) => {
1167
+ const tokens = tokenizeShellCommand(segment.trim());
1168
+ if (!tokens || tokens.length === 0) return false;
1169
+ const first = tokens[0];
1170
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(first) || first.includes("$")) return false;
1171
+ if (!isTrustedExecutable(first)) return false;
1172
+ const exe = basenameOf(first);
1173
+ if (exe === "git") {
1174
+ const sub = tokens[1];
1175
+ if (!sub || sub.startsWith("-")) return false;
1176
+ if (!SAFE_GIT_SUBCOMMANDS.has(sub)) return false;
1177
+ return !tokens.some(GIT_WRITE_FLAGS);
1178
+ }
1179
+ if (exe === "find") {
1180
+ for (let i = 1; i < tokens.length; i += 1) {
1181
+ const t = tokens[i];
1182
+ if (t === "(" || t === ")" || t === "!" || t === "\\(" || t === "\\)" || t === "\\!") continue;
1183
+ if (t.startsWith("-") && (DANGEROUS_FIND_ACTIONS.has(t) || !SAFE_FIND_TOKENS.has(t))) return false;
1184
+ }
1185
+ return true;
1186
+ }
1187
+ return SAFE_SHELL_COMMANDS.has(exe);
1188
+ };
1189
+ var isSafeReadOnlyCommand = (command) => {
1190
+ const trimmed = command.trim();
1191
+ if (!trimmed || trimmed.length > 2e3) return false;
1192
+ if (/[`\n\r{}]/.test(trimmed)) return false;
1193
+ if (/;|\|\|/.test(trimmed)) return false;
1194
+ if (/\\(?![()!])/.test(trimmed)) return false;
1195
+ if (/(?<!\\)[()]/.test(trimmed)) return false;
1196
+ const noRedir = trimmed.replace(SAFE_REDIRECTION, " ");
1197
+ if (/[<>]/.test(noRedir)) return false;
1198
+ if (noRedir.replace(/&&/g, " ").includes("&")) return false;
1199
+ for (const andPart of noRedir.split("&&")) {
1200
+ if (!andPart.trim()) return false;
1201
+ for (const segment of andPart.split("|")) {
1202
+ if (!isSafeSimpleCommand(segment)) return false;
1203
+ }
1204
+ }
1205
+ return true;
1206
+ };
1207
+ var isAutoApprove = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "approve";
1208
+ var isAutoDeny = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "deny";
1209
+ var verdictForPolicy = (policy) => {
1210
+ if (isAutoDeny(policy)) return "deny";
1211
+ if (isAutoApprove(policy)) return "allow";
1212
+ return "requires_human";
1213
+ };
1214
+ var policyIntent = (policy) => isAutoApprove(policy) ? PATTERN_INTENT.grant : PATTERN_INTENT.restrict;
1215
+ var selectGoverningRule = (policies, toolName, args, repoKey, sessionId) => {
1216
+ let best;
1217
+ let bestWeight = 0;
1218
+ let bestScoped = -1;
1219
+ let bestLength = -1;
1220
+ for (const candidate of policies) {
1221
+ if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
1222
+ if (candidate.sessionId && candidate.sessionId !== sessionId) continue;
1223
+ const rank = matchToolPatternAny(candidate.tool, toolName, args, policyIntent(candidate));
1224
+ if (rank === "none") continue;
1225
+ const weight = matchRankWeight(rank);
1226
+ const scoped = candidate.sessionId ? 2 : candidate.repoKey ? 1 : 0;
1227
+ const length = rank === "prefix" ? candidate.tool.length : -1;
1228
+ const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
1229
+ if (better) {
1230
+ best = { policy: candidate, rank };
1231
+ bestWeight = weight;
1232
+ bestScoped = scoped;
1233
+ bestLength = length;
1234
+ }
1235
+ }
1236
+ return best;
1237
+ };
1238
+ var ARG_PREFIX_TARGET_TOOLS = Object.keys(POLICY_COMMAND_KEYS);
1239
+ var FILE_TARGET_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
1240
+ var isCoveredByPolicy = (policyPatterns, toolName, toolTarget) => {
1241
+ const candidateWeight = matchRankWeight(toolTarget ? "prefix" : "tool");
1242
+ return policyPatterns.some(
1243
+ (pattern) => matchRankWeight(matchToolPattern(pattern, toolName, toolTarget ?? void 0, PATTERN_INTENT.restrict)) >= candidateWeight
1244
+ );
1245
+ };
1246
+ var COMMAND_RUNNERS = /* @__PURE__ */ new Set([
1247
+ "sh",
1248
+ "bash",
1249
+ "zsh",
1250
+ "dash",
1251
+ "ksh",
1252
+ "fish",
1253
+ "csh",
1254
+ "tcsh",
1255
+ "eval",
1256
+ "exec",
1257
+ "source",
1258
+ ".",
1259
+ "command",
1260
+ "builtin",
1261
+ "env",
1262
+ "xargs",
1263
+ "nohup",
1264
+ "time",
1265
+ "nice",
1266
+ "timeout",
1267
+ "watch",
1268
+ "parallel",
1269
+ "script",
1270
+ "su",
1271
+ "doas",
1272
+ "npx",
1273
+ "bunx",
1274
+ "pnpx",
1275
+ "uvx"
1276
+ ]);
1277
+ var INLINE_CODE_INTERPRETERS = /* @__PURE__ */ new Set([
1278
+ "python",
1279
+ "python2",
1280
+ "python3",
1281
+ "node",
1282
+ "deno",
1283
+ "ruby",
1284
+ "perl",
1285
+ "php",
1286
+ "lua",
1287
+ "osascript",
1288
+ "pwsh",
1289
+ "powershell"
1290
+ ]);
1291
+ var INLINE_CODE_LONG_FLAG = /^-{1,2}(?:eval|print|require|import|command|encodedcommand|enc|ec)(?:=|$)/i;
1292
+ var INLINE_CODE_SHORT_FLAG_LETTERS = /* @__PURE__ */ new Set(["c", "e", "E", "p", "r"]);
1293
+ var SHORT_FLAG_CLUSTER = /^-([A-Za-z]+)/;
1294
+ var INLINE_CODE_SUBCOMMAND = "eval";
1295
+ var STANDARD_INPUT_ARGUMENT = "-";
1296
+ var DESTRUCTIVE_ARGUMENTS_BY_COMMAND = /* @__PURE__ */ new Map([
1297
+ ["find", /* @__PURE__ */ new Set(["-delete", "-exec", "-execdir", "-ok", "-okdir"])],
1298
+ ["rsync", /* @__PURE__ */ new Set([
1299
+ "--del",
1300
+ "--delete",
1301
+ "--delete-before",
1302
+ "--delete-during",
1303
+ "--delete-delay",
1304
+ "--delete-after",
1305
+ "--delete-excluded",
1306
+ "--delete-missing-args",
1307
+ "--remove-source-files",
1308
+ "--remove-sent-files"
1309
+ ])],
1310
+ ["git checkout", /* @__PURE__ */ new Set(["-f", "--force", "--", "-B"])],
1311
+ ["git switch", /* @__PURE__ */ new Set(["-f", "--force", "--discard-changes", "-C", "--force-create"])],
1312
+ ["git branch", /* @__PURE__ */ new Set(["-f", "--force", "-D", "-M", "-C"])]
1313
+ ]);
1314
+ var DESTRUCTIVE_WORDS_IN_ANY_CASE_BY_COMMAND = /* @__PURE__ */ new Map([
1315
+ ["redis-cli", /* @__PURE__ */ new Set(["flushall", "flushdb"])]
1316
+ ]);
1317
+ var namesDestructiveWordInAnyCase = (name, texts) => {
1318
+ const words = DESTRUCTIVE_WORDS_IN_ANY_CASE_BY_COMMAND.get(name);
1319
+ return words !== void 0 && texts.some((text) => words.has(text.toLowerCase()));
1320
+ };
1321
+ var LONG_OPTION = /^--[A-Za-z]/;
1322
+ var namesDestructiveArgument = (text, destructiveArguments) => destructiveArguments.has(text) || LONG_OPTION.test(text) && [...destructiveArguments].some((argument) => argument.startsWith(text)) || [...SHORT_FLAG_CLUSTER.exec(text)?.[1] ?? ""].some((letter) => destructiveArguments.has(`-${letter}`));
1323
+ var RUNNER_SUBCOMMANDS = /* @__PURE__ */ new Set(["exec", "dlx"]);
1324
+ var SUBCOMMAND_CLIS = /* @__PURE__ */ new Set([
1325
+ "git",
1326
+ "docker",
1327
+ "podman",
1328
+ "kubectl",
1329
+ "helm",
1330
+ "terraform",
1331
+ "npm",
1332
+ "pnpm",
1333
+ "yarn",
1334
+ "bun",
1335
+ "cargo",
1336
+ "pip",
1337
+ "brew",
1338
+ "gh",
1339
+ "glab",
1340
+ "aws",
1341
+ "gcloud",
1342
+ "gsutil",
1343
+ "az",
1344
+ "doctl",
1345
+ "firebase",
1346
+ "vercel",
1347
+ "netlify",
1348
+ "fly",
1349
+ "flyctl",
1350
+ "heroku",
1351
+ "supabase",
1352
+ "wrangler",
1353
+ "railway"
1354
+ ]);
1355
+ var DESTRUCTIVE_SUBCOMMANDS = /* @__PURE__ */ new Set([
1356
+ "delete",
1357
+ "destroy",
1358
+ "remove",
1359
+ "rm",
1360
+ "rb",
1361
+ "rmi",
1362
+ "purge",
1363
+ "prune",
1364
+ "drop",
1365
+ "terminate",
1366
+ "uninstall",
1367
+ "wipe",
1368
+ "reset"
1369
+ ]);
1370
+ var DATA_LOSS_COMMAND_PATTERNS = [
1371
+ "Bash(git branch -D:*)",
1372
+ "Bash(git branch --delete:*)",
1373
+ "Bash(git checkout:*)",
1374
+ "Bash(git restore:*)",
1375
+ "Bash(git stash drop:*)",
1376
+ "Bash(git stash clear:*)",
1377
+ "Bash(git filter-branch:*)",
1378
+ "Bash(git update-ref -d:*)",
1379
+ "Bash(dd :*)",
1380
+ "Bash(shred:*)",
1381
+ "Bash(unlink:*)",
1382
+ "Bash(crontab:*)",
1383
+ "Bash(chmod -R:*)",
1384
+ "Bash(chown -R:*)",
1385
+ "Bash(kill:*)",
1386
+ "Bash(pkill:*)",
1387
+ "Bash(killall:*)",
1388
+ "Bash(launchctl:*)",
1389
+ "Bash(systemctl:*)",
1390
+ "Bash(diskutil:*)"
1391
+ ];
1392
+ var SHELL_ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
1393
+ var PLAIN_PROGRAM_WORD = /^[A-Za-z0-9_./+@:-]+$/;
1394
+ var SHELL_RESERVED_WORDS = /* @__PURE__ */ new Set([
1395
+ "if",
1396
+ "then",
1397
+ "elif",
1398
+ "else",
1399
+ "fi",
1400
+ "case",
1401
+ "esac",
1402
+ "for",
1403
+ "select",
1404
+ "while",
1405
+ "until",
1406
+ "do",
1407
+ "done",
1408
+ "function",
1409
+ "coproc",
1410
+ "!",
1411
+ "{",
1412
+ "}",
1413
+ "[[",
1414
+ "]]"
1415
+ ]);
1416
+ var HARMLESS_GLOBAL_FLAGS = /* @__PURE__ */ new Set(["--no-pager", "--version", "-v", "-V", "--help", "-h"]);
1417
+ var END_OF_OPTIONS = "--";
1418
+ var SUBCOMMAND_DEPTH = 3;
1419
+ var SHELL_COMMENT_MARK = "#";
1420
+ var SHELL_LINE_CONTINUATION = /\\\n/g;
1421
+ var SHELL_WORD_BREAK = /\s/;
1422
+ var DOUBLE_QUOTE_ESCAPABLE = /* @__PURE__ */ new Set([SHELL_DOUBLE_QUOTE, SHELL_ESCAPE, SHELL_SUBSTITUTION_SIGIL, "`"]);
1423
+ var shellWordsOf = (segment) => {
1424
+ const words = [];
1425
+ let text = "";
1426
+ let plain = true;
1427
+ let expands = false;
1428
+ let comment = false;
1429
+ let started = false;
1430
+ let quote = null;
1431
+ const finishWord = () => {
1432
+ if (started) words.push({ text, plain, expands, comment });
1433
+ text = "";
1434
+ plain = true;
1435
+ expands = false;
1436
+ comment = false;
1437
+ started = false;
1438
+ };
1439
+ for (let index = 0; index < segment.length; index += 1) {
1440
+ const char = segment[index];
1441
+ const next = segment[index + 1];
1442
+ if (quote === "'") {
1443
+ if (char === quote) quote = null;
1444
+ else text += char;
1445
+ continue;
1446
+ }
1447
+ if (char === SHELL_ESCAPE && next !== void 0 && (quote === null || DOUBLE_QUOTE_ESCAPABLE.has(next))) {
1448
+ text += next;
1449
+ plain = false;
1450
+ started = true;
1451
+ index += 1;
1452
+ continue;
1453
+ }
1454
+ if (quote === SHELL_DOUBLE_QUOTE) {
1455
+ if (char === quote) quote = null;
1456
+ else text += char;
1457
+ expands ||= char === SHELL_SUBSTITUTION_SIGIL;
1458
+ continue;
1459
+ }
1460
+ if (SHELL_QUOTES.has(char)) {
1461
+ quote = char;
1462
+ plain = false;
1463
+ started = true;
1464
+ continue;
1465
+ }
1466
+ if (SHELL_WORD_BREAK.test(char)) {
1467
+ finishWord();
1468
+ continue;
1469
+ }
1470
+ comment ||= !started && char === SHELL_COMMENT_MARK;
1471
+ expands ||= char === SHELL_SUBSTITUTION_SIGIL;
1472
+ text += char;
1473
+ started = true;
1474
+ }
1475
+ finishWord();
1476
+ return words;
1477
+ };
1478
+ var invocationOf = (segment) => {
1479
+ const [program, ...args] = shellWordsOf(segment);
1480
+ return { program, args };
1481
+ };
1482
+ var argsAfterHarmlessGlobalFlags = (args) => {
1483
+ const firstMeaningful = args.findIndex((arg) => !HARMLESS_GLOBAL_FLAGS.has(arg.text));
1484
+ return firstMeaningful === -1 ? [] : args.slice(firstMeaningful);
1485
+ };
1486
+ var subcommandWordsOf = (args) => {
1487
+ const endOfOptions = args.findIndex((arg) => arg.text === END_OF_OPTIONS);
1488
+ const options = endOfOptions === -1 ? args : args.slice(0, endOfOptions);
1489
+ return options.filter((arg) => !arg.text.startsWith("-")).slice(0, SUBCOMMAND_DEPTH);
1490
+ };
1491
+ var passesInlineCode = (arg) => {
1492
+ if (arg === INLINE_CODE_SUBCOMMAND || arg === STANDARD_INPUT_ARGUMENT || INLINE_CODE_LONG_FLAG.test(arg)) return true;
1493
+ if (arg.startsWith(END_OF_OPTIONS)) return false;
1494
+ const cluster = SHORT_FLAG_CLUSTER.exec(arg)?.[1] ?? "";
1495
+ return [...cluster].some((letter) => INLINE_CODE_SHORT_FLAG_LETTERS.has(letter));
1496
+ };
1497
+ var interpreterRunsUnreadableCode = (args) => args.some(passesInlineCode) || args.every((arg) => arg.startsWith("-"));
1498
+ var programHidesItsEffect = (program) => SHELL_RESERVED_WORDS.has(program.text) || !program.plain || program.expands || SHELL_ENV_ASSIGNMENT.test(program.text) || !PLAIN_PROGRAM_WORD.test(program.text) || COMMAND_RUNNERS.has(basenameOf(program.text));
1499
+ var destructiveArgumentsOf = (name, args) => DESTRUCTIVE_ARGUMENTS_BY_COMMAND.get(name) ?? DESTRUCTIVE_ARGUMENTS_BY_COMMAND.get(`${name} ${subcommandWordsOf(argsAfterHarmlessGlobalFlags(args))[0]?.text ?? ""}`);
1500
+ var argumentsHideTheEffect = (name, args) => {
1501
+ const texts = args.map((arg) => arg.text);
1502
+ if (INLINE_CODE_INTERPRETERS.has(name.replace(/\d+(?:\.\d+)*$/, "")) && interpreterRunsUnreadableCode(texts)) return true;
1503
+ const destructiveArguments = destructiveArgumentsOf(name, args);
1504
+ if (destructiveArguments && texts.some((text) => namesDestructiveArgument(text, destructiveArguments))) return true;
1505
+ if (namesDestructiveWordInAnyCase(name, texts)) return true;
1506
+ if (!SUBCOMMAND_CLIS.has(name)) return false;
1507
+ const meaningful = argsAfterHarmlessGlobalFlags(args);
1508
+ return (meaningful[0]?.text.startsWith("-") ?? false) || subcommandWordsOf(meaningful).some((word) => word.expands || RUNNER_SUBCOMMANDS.has(word.text));
1509
+ };
1510
+ var invocationHidesItsEffect = ({ program, args }) => program !== void 0 && ([program, ...args].some((word) => word.comment) || programHidesItsEffect(program) || argumentsHideTheEffect(basenameOf(program.text), args));
1511
+ var readableCommandSegments = (command) => shellCommandSegments(command.replace(SHELL_LINE_CONTINUATION, "").replace(SAFE_REDIRECTION, ""));
1512
+ var normalizedInvocation = ({ program, args }) => {
1513
+ if (program === void 0) return "";
1514
+ const name = basenameOf(program.text);
1515
+ const kept = SUBCOMMAND_CLIS.has(name) ? argsAfterHarmlessGlobalFlags(args) : args;
1516
+ return [name, ...kept.map((arg) => arg.text)].join(" ");
1517
+ };
1518
+ var normalizedCommand = (command) => {
1519
+ const segments = readableCommandSegments(command);
1520
+ return segments === null ? null : segments.map((segment) => normalizedInvocation(invocationOf(segment))).join("; ");
1521
+ };
1522
+ var subcommandDestroys = ({ program, args }) => program !== void 0 && SUBCOMMAND_CLIS.has(basenameOf(program.text)) && subcommandWordsOf(argsAfterHarmlessGlobalFlags(args)).some((word) => DESTRUCTIVE_SUBCOMMANDS.has(word.text));
1523
+ var segmentNeedsAPerson = (toolName, segment) => {
1524
+ const invocation = invocationOf(segment);
1525
+ const normalized = normalizedInvocation(invocation);
1526
+ return invocationHidesItsEffect(invocation) || subcommandDestroys(invocation) || classifyDecisionRisk(toolName, normalized) !== "standard" || isCoveredByPolicy(CAPABILITY_PATTERNS.destructive, toolName, normalized) || isCoveredByPolicy(CAPABILITY_PATTERNS.network, toolName, normalized) || isCoveredByPolicy(DATA_LOSS_COMMAND_PATTERNS, toolName, normalized);
1527
+ };
1528
+ var commandNeedsAPerson = (toolName, command) => {
1529
+ const segments = readableCommandSegments(command);
1530
+ return segments === null || classifyDecisionRisk(toolName, command) !== "standard" || segments.some((segment) => segmentNeedsAPerson(toolName, segment));
1531
+ };
1532
+ var withoutSessionToolGrants = (config) => ({
1533
+ ...config,
1534
+ policies: config.policies.filter((policy) => !isSessionToolGrant(policy))
1535
+ });
1536
+ var SESSION_TOOL_GRANT_TOOLS = /* @__PURE__ */ new Set(["Bash"]);
1537
+ var isSessionToolGrant = (policy) => Boolean(policy.sessionId) && SESSION_TOOL_GRANT_TOOLS.has(policy.tool) && isAutoApprove(policy);
1538
+ var API_KEY_PATTERN = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
1539
+ var isValidApiKey = (value) => API_KEY_PATTERN.test(value);
1540
+ var ACTION_BODY_MAX = 4e3;
1541
+ var DECISION_LINE_MAX = 500;
1542
+ var ASK_QUESTION_MAX = 500;
1543
+ var ASK_AGENT_NAME_MAX = 100;
1544
+ var TRUNCATION_MARK = "\u2026";
1545
+ var isHighSurrogate = (code) => code >= 55296 && code <= 56319;
1546
+ var truncateLine = (text, max, mark = TRUNCATION_MARK) => {
1547
+ if (text.length <= max) return text;
1548
+ const end = max - mark.length;
1549
+ const keep = isHighSurrogate(text.charCodeAt(end - 1)) ? end - 1 : end;
1550
+ return `${text.slice(0, keep)}${mark}`;
1551
+ };
1552
+ var APPROVAL_FATIGUE_FLAG_PATTERNS = [
1553
+ /\brm\s+-[a-z]*r/i,
1554
+ /\brmdir\b/i,
1555
+ /git\s+push[^\n]*(--force|--force-with-lease|\s-f\b)/i,
1556
+ /git\s+reset\s+--hard/i,
1557
+ /git\s+clean\s+-[a-z]*f/i,
1558
+ /\bdrop\s+(table|database|schema)\b/i,
1559
+ /\bdelete\s+from\b/i,
1560
+ /\btruncate\b/i,
1561
+ /\b(deploy|publish|release)\b/i,
1562
+ /\bnpm\s+publish\b/i,
1563
+ /\bsudo\b/i,
1564
+ /chmod\s+-?R?\s*777/i,
1565
+ /\bkubectl\s+delete\b/i,
1566
+ /\bterraform\s+(apply|destroy)\b/i,
1567
+ /\bdocker\s+system\s+prune\b/i
1568
+ ];
1569
+ var DELTA_STOPWORDS = /* @__PURE__ */ new Set([
1570
+ "the",
1571
+ "a",
1572
+ "an",
1573
+ "to",
1574
+ "of",
1575
+ "for",
1576
+ "and",
1577
+ "is",
1578
+ "in",
1579
+ "on",
1580
+ "with"
1581
+ ]);
1582
+ var HIGH_CONSEQUENCE_PATTERNS = [
1583
+ /(^|[\\/])\.env(\.|$|[\\/])/i,
1584
+ /secrets?/i,
1585
+ /credentials?/i,
1586
+ /(^|[\\/])deploy/i,
1587
+ /\.github[\\/]workflows/i,
1588
+ /package\.json/i
1589
+ ];
1590
+ var isCommandTool = (toolName) => {
1591
+ const name = (toolName ?? "").toLowerCase();
1592
+ return name === "bash" || name === "shell" || name === "run_shell_command" || name === "apply_patch" || name.includes("command");
1593
+ };
1594
+ var normalizeTarget = (toolName, toolTarget, actionBody) => {
1595
+ const command = isCommandTool(toolName);
1596
+ const target = toolTarget ?? "";
1597
+ const body = actionBody ?? "";
1598
+ const commandText = command ? body.length > 0 ? body : target : `${target}
1599
+ ${body}`;
1600
+ const path = command ? "" : target;
1601
+ const scanText = command ? commandText : `${path}
1602
+ ${body}`;
1603
+ const highConsequence = HIGH_CONSEQUENCE_PATTERNS.some((re) => re.test(scanText));
1604
+ return { isCommand: command, commandText, path, highConsequence };
1605
+ };
1606
+ var isDestructive = (toolName, commandText) => {
1607
+ const haystack = `${toolName ?? ""}
1608
+ ${commandText}`;
1609
+ return APPROVAL_FATIGUE_FLAG_PATTERNS.some((re) => re.test(haystack));
1610
+ };
1611
+ var addAlnumTokens = (source, set) => {
1612
+ for (const raw of source.toLowerCase().split(/[^a-z0-9]+/)) {
1613
+ if (raw) set.add(raw);
1614
+ }
1615
+ };
1616
+ var tokenize = (text) => {
1617
+ const set = /* @__PURE__ */ new Set();
1618
+ for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
1619
+ if (raw && !DELTA_STOPWORDS.has(raw)) set.add(raw);
1620
+ }
1621
+ return set;
1622
+ };
1623
+ var targetTokenSet = (target) => {
1624
+ const set = /* @__PURE__ */ new Set();
1625
+ if (target.isCommand) {
1626
+ const parts = target.commandText.trim().split(/\s+/).filter(Boolean);
1627
+ if (parts[0]) addAlnumTokens(basenameOf(parts[0]), set);
1628
+ if (parts[1]) addAlnumTokens(basenameOf(parts[1]), set);
1629
+ } else {
1630
+ addAlnumTokens(target.path, set);
1631
+ }
1632
+ return set;
1633
+ };
1634
+ var intersectionSize = (a, b) => {
1635
+ const [small, large] = a.size <= b.size ? [a, b] : [b, a];
1636
+ let count = 0;
1637
+ for (const token of small) if (large.has(token)) count += 1;
1638
+ return count;
1639
+ };
1640
+ var computeDeltaSignal = (input) => {
1641
+ const target = normalizeTarget(input.toolName, input.toolTarget, input.actionBody);
1642
+ const destructive = isDestructive(input.toolName, target.commandText);
1643
+ if (!input.intent || input.intent.trim().length === 0) {
1644
+ return { deltaSignal: "unclear", deltaScore: 0, remoteApprove: !destructive };
1645
+ }
1646
+ const intentTokens = tokenize(input.intent);
1647
+ const targetTokens = targetTokenSet(target);
1648
+ const overlap = intersectionSize(intentTokens, targetTokens);
1649
+ const zeroOverlap = overlap === 0 && targetTokens.size > 0;
1650
+ if (zeroOverlap && (destructive || target.highConsequence)) {
1651
+ return { deltaSignal: "mismatch", deltaScore: 1, remoteApprove: false };
1652
+ }
1653
+ if (zeroOverlap) {
1654
+ return { deltaSignal: "unclear", deltaScore: 0.5, remoteApprove: true };
1655
+ }
1656
+ return { deltaSignal: "match", deltaScore: 0, remoteApprove: !destructive };
1657
+ };
1658
+ var AUTHORIZATION_CANONICAL_VERSION = "pushary.authz.v1";
1659
+ var AUTHORIZATION_CANONICAL_FIXTURE = [
1660
+ {
1661
+ subject: { toolName: "refund.create" },
1662
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
1663
+ action:13:refund.create
1664
+ target:0:
1665
+ actor:0:
1666
+ environment:0:
1667
+ externalId:0:`
1668
+ },
1669
+ {
1670
+ subject: {
1671
+ toolName: "refund.create",
1672
+ toolTarget: "ord_991",
1673
+ actor: "user:u_44",
1674
+ environment: "production",
1675
+ externalId: "cust_7",
1676
+ parameters: { currency: "EUR", amount: 4800, expedited: true, rate: 1.5 }
1677
+ },
1678
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
1679
+ action:13:refund.create
1680
+ target:7:ord_991
1681
+ actor:9:user:u_44
1682
+ environment:10:production
1683
+ externalId:6:cust_7
1684
+ p:13:amount=n:4800
1685
+ p:14:currency=s:EUR
1686
+ p:16:expedited=b:true
1687
+ p:10:rate=n:1.5`
1688
+ },
1689
+ {
1690
+ // Parameter order is not part of the subject. The same facts written in a
1691
+ // different order are the same authorization.
1692
+ subject: { toolName: "refund.create", parameters: { b: 2, a: 1 } },
1693
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
1694
+ action:13:refund.create
1695
+ target:0:
1696
+ actor:0:
1697
+ environment:0:
1698
+ externalId:0:
1699
+ p:5:a=n:1
1700
+ p:5:b=n:2`
1701
+ },
1702
+ {
1703
+ // An integral float is the same number as the integer.
1704
+ subject: { toolName: "a", parameters: { n: 49 } },
1705
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
1706
+ action:1:a
1707
+ target:0:
1708
+ actor:0:
1709
+ environment:0:
1710
+ externalId:0:
1711
+ p:6:n=n:49`
1712
+ },
1713
+ {
1714
+ // Delimiters inside a value cannot forge another subject.
1715
+ subject: { toolName: "a", parameters: { "x\ny": "1" } },
1716
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
1717
+ action:1:a
1718
+ target:0:
1719
+ actor:0:
1720
+ environment:0:
1721
+ externalId:0:
1722
+ p:7:x
1723
+ y=s:1`
1724
+ },
1725
+ {
1726
+ // Every finite number, including the ones another language would render
1727
+ // differently. One runtime computes both sides of the comparison.
1728
+ subject: { toolName: "a", parameters: { small: 1e-7, big: 1e21 } },
1729
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
1730
+ action:1:a
1731
+ target:0:
1732
+ actor:0:
1733
+ environment:0:
1734
+ externalId:0:
1735
+ p:11:big=n:1e+21
1736
+ p:12:small=n:1e-7`
1737
+ }
1738
+ ];
1739
+ var AUTHORIZATION_PERMIT_TTL_SECONDS = 60 * 60;
1740
+ var ANSWER_SOURCES = [
1741
+ "dashboard",
1742
+ "mobile_app",
1743
+ "mobile_background",
1744
+ "decide_page",
1745
+ "live_page",
1746
+ "answer_link",
1747
+ "inbox",
1748
+ "slack",
1749
+ "mac_app",
1750
+ // Nobody. A decision opened on a workspace's sandbox site is settled by the
1751
+ // simulator instead of being delivered, and the ledger has to say so: a
1752
+ // sandbox row that looked like a human answer would be indistinguishable
1753
+ // evidence of a person who was never asked.
1754
+ "sandbox"
1755
+ ];
1756
+ var isAnswerSource = (value) => typeof value === "string" && ANSWER_SOURCES.includes(value);
1757
+ var SETTLED_OUTCOMES = ["approve", "deny"];
1758
+ var SETTLED_OUTCOME = {
1759
+ approve: "approve",
1760
+ deny: "deny"
1761
+ };
1762
+ var isSettledOutcome = (value) => typeof value === "string" && SETTLED_OUTCOMES.includes(value);
1763
+ var classifyDecisionRisk = (toolName, toolTarget) => {
1764
+ const parts = [toolName, toolTarget ?? ""];
1765
+ const matchesAny = (patterns) => parts.some((part) => patterns.some((re) => re.test(part)));
1766
+ if (matchesAny(APPROVAL_FATIGUE_FLAG_PATTERNS)) return "destructive";
1767
+ if (matchesAny(HIGH_CONSEQUENCE_PATTERNS)) return "elevated";
1768
+ return "standard";
1769
+ };
1770
+ var POLICY_CAPABILITIES = ["read", "edit", "shell", "network", "destructive"];
1771
+ var CAPABILITY_PATTERNS = {
1772
+ read: ["Read", "Grep", "Glob", "LS", "NotebookRead"],
1773
+ edit: ["Edit", "Write"],
1774
+ shell: ["Bash"],
1775
+ network: [
1776
+ "WebFetch",
1777
+ "Bash(curl:*)",
1778
+ "Bash(wget:*)",
1779
+ "Bash(nc:*)",
1780
+ "Bash(ssh:*)",
1781
+ "Bash(scp:*)",
1782
+ "Bash(npm install:*)",
1783
+ "Bash(npm i:*)",
1784
+ "Bash(pnpm add:*)",
1785
+ "Bash(yarn add:*)",
1786
+ "Bash(bun add:*)",
1787
+ "Bash(pip install:*)",
1788
+ "Bash(brew install:*)",
1789
+ "Bash(apt:*)",
1790
+ "Bash(apt-get:*)"
1791
+ ],
1792
+ destructive: [
1793
+ "Bash(rm:*)",
1794
+ "Bash(rmdir:*)",
1795
+ "Bash(sudo:*)",
1796
+ "Bash(git push:*)",
1797
+ "Bash(git reset --hard:*)",
1798
+ "Bash(git clean:*)",
1799
+ "Bash(npm publish:*)",
1800
+ "Bash(kubectl delete:*)",
1801
+ "Bash(terraform apply:*)",
1802
+ "Bash(terraform destroy:*)",
1803
+ "Bash(docker system prune:*)",
1804
+ "Bash(dropdb:*)",
1805
+ "Bash(mkfs:*)"
1806
+ ]
1807
+ };
1808
+ var DECISION_EPISODE_SCHEMA_VERSION = 1;
1809
+ var DECISION_FEATURE_EXTRACTOR = `pushary-hooks/schema-${DECISION_EPISODE_SCHEMA_VERSION}`;
1810
+ var DECISION_ORIGIN = {
1811
+ human: "human",
1812
+ autopilot: "autopilot",
1813
+ safeReadonly: "safe_readonly",
1814
+ policyTimeout: "policy_timeout",
1815
+ killSwitch: "kill_switch"
1816
+ };
1817
+ var EPISODE_CAPABILITIES = [...POLICY_CAPABILITIES, "other"];
1818
+ var NETWORK_COMMAND_PATTERNS = [
1819
+ /\bcurl\b/i,
1820
+ /\bwget\b/i,
1821
+ /\bnc\b/i,
1822
+ /\bssh\b/i,
1823
+ /\bscp\b/i,
1824
+ /\b(?:npm|pnpm|yarn|bun)\s+(?:install|i|add)\b/i,
1825
+ /\bpip\s+install\b/i,
1826
+ /\bbrew\s+install\b/i,
1827
+ /\bapt(?:-get)?\s+(?:install|update|upgrade)\b/i
1828
+ ];
1829
+ var SECRET_TARGET_PATTERNS = [
1830
+ /(^|[\\/])\.env(\.|$|[\\/])/i,
1831
+ /secrets?/i,
1832
+ /credentials?/i,
1833
+ /private[_-]?key/i,
1834
+ /\.pem$/i
1835
+ ];
1836
+ var READ_TOOL_NAMES = new Set(CAPABILITY_PATTERNS.read);
1837
+ var EDIT_TOOL_NAMES = new Set(CAPABILITY_PATTERNS.edit);
1838
+ var deriveEpisodeCapability = (toolName, commandText) => {
1839
+ if (READ_TOOL_NAMES.has(toolName)) return "read";
1840
+ if (EDIT_TOOL_NAMES.has(toolName)) return "edit";
1841
+ if (toolName === "WebFetch" || toolName === "WebSearch") return "network";
1842
+ if (isCommandTool(toolName)) {
1843
+ if (isDestructive(toolName, commandText)) return "destructive";
1844
+ if (NETWORK_COMMAND_PATTERNS.some((re) => re.test(commandText))) return "network";
1845
+ return "shell";
1846
+ }
1847
+ if (isDestructive(toolName, commandText)) return "destructive";
1848
+ return "other";
1849
+ };
1850
+ var deriveTargetClass = (path) => {
1851
+ const p = path.toLowerCase();
1852
+ if (!p) return "other";
1853
+ if (SECRET_TARGET_PATTERNS.some((re) => re.test(p))) return "secret";
1854
+ if (/(^|[\\/])(?:tests?|__tests__|spec)([\\/]|$)|\.(?:test|spec)\.[a-z0-9]+$/i.test(p)) return "test";
1855
+ if (/\.(?:md|mdx|txt|rst)$|(^|[\\/])docs?([\\/]|$)/i.test(p)) return "docs";
1856
+ if (/(^|[\\/])(?:package\.json|tsconfig[^\\/]*\.json|\.[a-z]+rc)$|\.(?:json|ya?ml|toml|ini|conf|cfg)$/i.test(p)) return "config";
1857
+ if (/\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|rb|php|c|cc|cpp|h|hpp|cs|swift|kt|scala|sh)$/i.test(p)) return "source";
1858
+ return "other";
1859
+ };
1860
+ var bucketArgCount = (n) => n <= 0 ? "none" : n <= 3 ? "few" : "many";
1861
+ var bucketLength = (n) => n < 40 ? "short" : n < 200 ? "medium" : "long";
1862
+ var deriveArgShape = (isCommand, commandText, path) => {
1863
+ const text = isCommand ? commandText : path;
1864
+ const tokenCount = isCommand ? text.trim().split(/\s+/).filter(Boolean).length : 0;
1865
+ return {
1866
+ hasPipe: isCommand && /(?<!\|)\|(?!\|)/.test(text),
1867
+ hasRedirect: isCommand && /(?<![0-9])[<>]/.test(text),
1868
+ hasChain: isCommand && /&&|\|\||;/.test(text),
1869
+ hasSudo: /\bsudo\b/i.test(text),
1870
+ hasGlob: isCommand && /[*?]/.test(text),
1871
+ hasSubshell: isCommand && /\$\(|`/.test(text),
1872
+ touchesSecretPath: SECRET_TARGET_PATTERNS.some((re) => re.test(text)),
1873
+ // The command word itself is not an argument, so subtract one.
1874
+ argCountBucket: bucketArgCount(isCommand ? Math.max(0, tokenCount - 1) : 0),
1875
+ lengthBucket: bucketLength(text.length)
1876
+ };
1877
+ };
1878
+ var deriveReversibility = (capability, riskClass) => {
1879
+ if (capability === "read") return "none";
1880
+ if (capability === "destructive" || riskClass === "destructive") return "irreversible";
1881
+ return "reversible";
1882
+ };
1883
+ var deriveBlastRadius = (capability, riskClass, targetClass) => {
1884
+ if (capability === "read") return "low";
1885
+ if (capability === "destructive" || riskClass === "destructive" || targetClass === "secret") return "high";
1886
+ if (capability === "network" || capability === "shell" || riskClass === "elevated" || targetClass === "config") return "medium";
1887
+ return "low";
1888
+ };
1889
+ var deriveLabelTier = (origin, decision) => {
1890
+ if (origin !== DECISION_ORIGIN.human) return "none";
1891
+ return isSettledOutcome(decision) ? "bronze" : "none";
1892
+ };
1893
+ var buildDecisionEpisodeFeatures = (input) => {
1894
+ const isCommand = isCommandTool(input.toolName);
1895
+ const raw = input.commandOrPath ?? input.toolTarget ?? "";
1896
+ const commandText = isCommand ? raw : "";
1897
+ const path = isCommand ? "" : raw;
1898
+ const capability = deriveEpisodeCapability(input.toolName, commandText);
1899
+ const riskClass = classifyDecisionRisk(input.toolName, input.toolTarget ?? raw);
1900
+ const targetClass = isCommand ? SECRET_TARGET_PATTERNS.some((re) => re.test(commandText)) ? "secret" : "other" : deriveTargetClass(path);
1901
+ const argShape = deriveArgShape(isCommand, commandText, path);
1902
+ const { deltaSignal } = computeDeltaSignal({
1903
+ intent: input.intent,
1904
+ toolName: input.toolName,
1905
+ toolTarget: input.toolTarget,
1906
+ actionBody: input.commandOrPath
1907
+ });
1908
+ return {
1909
+ schemaVersion: DECISION_EPISODE_SCHEMA_VERSION,
1910
+ extractorVersion: DECISION_FEATURE_EXTRACTOR,
1911
+ capability,
1912
+ toolFamily: input.toolName.trim().slice(0, 40),
1913
+ riskClass,
1914
+ targetClass,
1915
+ argShape,
1916
+ reversibility: deriveReversibility(capability, riskClass),
1917
+ blastRadius: deriveBlastRadius(capability, riskClass, targetClass),
1918
+ deltaSignal,
1919
+ preset: input.preset,
1920
+ // A policy artifact (usually canonical like `Bash(rm:*)`), but a user could
1921
+ // author a custom pattern, so scrub secrets defensively before it is stored.
1922
+ matchedPattern: input.matchedPattern ? redactSecretsDeep(input.matchedPattern).slice(0, 120) : void 0,
1923
+ policyMode: input.policyMode,
1924
+ requestedGate: input.requestedGate,
1925
+ timeoutSeconds: input.timeoutSeconds,
1926
+ timeoutAction: input.timeoutAction,
1927
+ decision: input.decision,
1928
+ origin: input.origin,
1929
+ latencyMs: input.latencyMs,
1930
+ labelTier: deriveLabelTier(input.origin, input.decision),
1931
+ agentType: input.agentType
1932
+ };
1933
+ };
1934
+ var matchesGlobPath = (glob, path) => {
1935
+ if (glob === path) return true;
1936
+ const compiled = compiledGlob(glob);
1937
+ return compiled ? matchGlobTokens(compiled, path) : false;
1938
+ };
1939
+ var SCOPE_PATH_MAX_LENGTH = 200;
1940
+ var evaluateScope = (contract, path) => {
1941
+ if (!contract) return { outcome: "no_contract" };
1942
+ if (typeof path !== "string" || path.length === 0) return { outcome: "in_scope" };
1943
+ for (const rule of contract.offLimitsPaths) {
1944
+ if (matchesGlobPath(rule, path)) return { outcome: "off_limits", path, rule };
1945
+ }
1946
+ if (contract.allowedPaths.length === 0) return { outcome: "in_scope" };
1947
+ for (const rule of contract.allowedPaths) {
1948
+ if (matchesGlobPath(rule, path)) return { outcome: "in_scope" };
1949
+ }
1950
+ return { outcome: "out_of_scope", path };
1951
+ };
1952
+ var scopeRequiresHuman = (verdict) => verdict.outcome === "out_of_scope" || verdict.outcome === "off_limits";
1953
+ var widenablePath = (path) => path.length > 0 && path.length <= SCOPE_PATH_MAX_LENGTH ? path : void 0;
1954
+ var scopeChangeReason = (verdict) => {
1955
+ if (verdict.outcome === "out_of_scope") {
1956
+ return `Outside the scope you approved: ${verdict.path}`;
1957
+ }
1958
+ if (verdict.outcome === "off_limits") {
1959
+ return `You marked this off limits: ${verdict.path} (${verdict.rule})`;
1960
+ }
1961
+ return void 0;
1962
+ };
1963
+ var isScopeContract = (value) => {
1964
+ if (!value || typeof value !== "object") return false;
1965
+ const c = value;
1966
+ const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
1967
+ if (c.promises !== void 0 && !strings(c.promises)) return false;
1968
+ return strings(c.allowedPaths) && strings(c.offLimitsPaths) && typeof c.doneWhen === "string";
1969
+ };
1970
+ var findBestRoute = (routes, toolName, args, repoKey) => {
1971
+ let best;
1972
+ let bestWeight = 0;
1973
+ let bestScoped = -1;
1974
+ let bestLength = -1;
1975
+ for (const candidate of routes) {
1976
+ if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
1977
+ const rank = matchToolPatternAny(candidate.toolPattern, toolName, args, PATTERN_INTENT.restrict);
1978
+ if (rank === "none") continue;
1979
+ const weight = matchRankWeight(rank);
1980
+ const scoped = candidate.repoKey ? 1 : 0;
1981
+ const length = rank === "prefix" ? candidate.toolPattern.length : -1;
1982
+ const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
1983
+ if (better) {
1984
+ best = candidate;
1985
+ bestWeight = weight;
1986
+ bestScoped = scoped;
1987
+ bestLength = length;
1988
+ }
1989
+ }
1990
+ return best ?? routes.find((r) => r.toolPattern === "*");
1991
+ };
1992
+ var autoApprove = (tool) => ({
1993
+ tool,
1994
+ timeoutSeconds: 0,
1995
+ timeoutAction: "approve",
1996
+ mode: "terminal_only",
1997
+ pushFirstSeconds: 0
1998
+ });
1999
+ var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessionId) => {
2000
+ const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
2001
+ const arg = args[0];
2002
+ const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
2003
+ const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
2004
+ if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
2005
+ return DECISION_ORIGIN.safeReadonly;
2006
+ }
2007
+ return DECISION_ORIGIN.autopilot;
2008
+ };
2009
+ var wildcardRuleOf = (pool) => {
2010
+ const wildcard = pool.find((policy) => policy.tool === WILDCARD_TOOL_PATTERN);
2011
+ return wildcard ? { policy: wildcard, rank: "wildcard" } : void 0;
2012
+ };
2013
+ var withSiteDelivery = (rule, siteMode) => isAutoApprove(rule.policy) || isAutoDeny(rule.policy) ? rule : { ...rule, policy: { ...rule.policy, mode: siteMode.mode, pushFirstSeconds: siteMode.pushFirstSeconds } };
2014
+ var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
2015
+ const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
2016
+ const arg = args[0];
2017
+ const specificRule = (pool) => pool.length === 0 ? void 0 : selectGoverningRule(pool, toolName, args, repoKey, sessionId);
2018
+ const written = config.policies.filter((p) => !p.origin);
2019
+ const presets = config.policies.filter((p) => p.origin === "preset");
2020
+ const builtins = config.policies.filter((p) => p.origin === "builtin");
2021
+ const siteMode = wildcardRuleOf(written);
2022
+ const presetRule = specificRule(presets);
2023
+ const match = specificRule(written) ?? (presetRule && siteMode ? withSiteDelivery(presetRule, siteMode.policy) : presetRule) ?? siteMode ?? wildcardRuleOf(presets) ?? specificRule(builtins) ?? wildcardRuleOf(builtins);
2024
+ let base = match?.policy ?? {
2025
+ tool: toolName,
2026
+ timeoutSeconds: config.defaultTimeoutSeconds,
2027
+ timeoutAction: config.defaultTimeoutAction,
2028
+ mode: config.defaultMode ?? "push_first",
2029
+ pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
2030
+ };
2031
+ const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
2032
+ const governedBySessionToolGrant = match?.rank === "tool" && isSessionToolGrant(match.policy);
2033
+ const governingMode = base.mode;
2034
+ const approvedByReadOnlyFloor = !governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg);
2035
+ if (governedBySessionToolGrant && !approvedByReadOnlyFloor && typeof arg === "string" && commandNeedsAPerson(toolName, arg)) {
2036
+ return resolvePolicy(withoutSessionToolGrants(config), toolName, modeOverride, toolInput, cwd, repoKey, sessionId);
2037
+ }
2038
+ if (approvedByReadOnlyFloor) {
2039
+ base = autoApprove(base.tool);
2040
+ }
2041
+ const wouldAutoApprove = isAutoApprove(base);
2042
+ if (!governedBySpecificRule && wouldAutoApprove && toolName === "Bash" && typeof arg === "string" && classifyDecisionRisk(toolName, arg) === "destructive") {
2043
+ base = {
2044
+ tool: base.tool,
2045
+ timeoutSeconds: config.defaultTimeoutSeconds,
2046
+ timeoutAction: "wait",
2047
+ mode: governingMode,
2048
+ pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
2049
+ };
2050
+ }
2051
+ const effectiveOverride = modeOverride ?? config.modeOverride;
2052
+ if (effectiveOverride) {
2053
+ return { ...base, mode: effectiveOverride };
2054
+ }
2055
+ return base;
2056
+ };
2057
+ var restraintRank = (policy) => {
2058
+ if (isAutoDeny(policy)) return 4;
2059
+ if (isAutoApprove(policy)) return 0;
2060
+ if (policy.mode === "push_only") return 3;
2061
+ return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
2062
+ };
2063
+ var strictestPolicy = (a, b) => restraintRank(b) > restraintRank(a) ? b : a;
2064
+ var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey, sessionId) => {
2065
+ if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey, sessionId);
2066
+ return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey, sessionId)).reduce(strictestPolicy);
2067
+ };
2068
+ var isModeStateDegraded = (state) => state?.degraded === true;
2069
+ var KILL_REASON = "Stopped by user, this agent was halted from Pushary";
2070
+ var GATE_ALLOW_SOURCE = {
2071
+ allowlist: "allowlist",
2072
+ policy: "policy"
2073
+ };
2074
+ var PUSHARY_TOOL_PREFIX = "mcp__pushary__";
2075
+ var isPusharyOwnTool = (toolName) => toolName.startsWith(PUSHARY_TOOL_PREFIX);
2076
+ var strictestScopeVerdict = (scope, paths) => {
2077
+ if (paths.length === 0) return evaluateScope(scope, void 0);
2078
+ let worst = { outcome: "in_scope" };
2079
+ for (const path of paths) {
2080
+ const verdict = evaluateScope(scope, path);
2081
+ if (verdict.outcome === "off_limits") return verdict;
2082
+ if (verdict.outcome === "out_of_scope") worst = verdict;
2083
+ }
2084
+ return worst;
2085
+ };
2086
+ var survivesDegraded = (config, toolName, toolInputs, cwd, repoKey, sessionId) => toolInputs.length === 1 && resolveAutoResolveOrigin(config, toolName, toolInputs[0], cwd, repoKey, sessionId) === "safe_readonly";
2087
+ var FULL_DEFER_MODES = /* @__PURE__ */ new Set(["plan", "auto", "dontAsk"]);
2088
+ var ACCEPT_EDITS_DEFER_TOOLS = /* @__PURE__ */ new Set([
2089
+ "Write",
2090
+ "Edit",
2091
+ "MultiEdit",
2092
+ "NotebookEdit"
2093
+ ]);
2094
+ var shouldDeferToNativeMode = (permissionMode, toolName, options = {}) => {
2095
+ if (options.respectPermissionMode === false) return false;
2096
+ if (typeof permissionMode !== "string") return false;
2097
+ if (FULL_DEFER_MODES.has(permissionMode)) return true;
2098
+ if (permissionMode === "acceptEdits" && ACCEPT_EDITS_DEFER_TOOLS.has(toolName)) return true;
2099
+ if (permissionMode === "bypassPermissions") return options.deferBypass === true;
2100
+ return false;
2101
+ };
2102
+ var AGENT_QUESTION_TOOL = "AskUserQuestion";
2103
+ var AGENT_QUESTION_STRATEGY = { native: "native_dialog", updatedInput: "updated_input", none: "not_a_question" };
2104
+ var UPDATED_INPUT_QUESTION_SOURCES = /* @__PURE__ */ new Set(["claude", "qoder", "codebuddy", "droid"]);
2105
+ function agentQuestionStrategy(source, event, toolName) {
2106
+ if (toolName !== AGENT_QUESTION_TOOL) return AGENT_QUESTION_STRATEGY.none;
2107
+ if (source !== null && event === "PreToolUse" && UPDATED_INPUT_QUESTION_SOURCES.has(source)) return AGENT_QUESTION_STRATEGY.updatedInput;
2108
+ return AGENT_QUESTION_STRATEGY.native;
2109
+ }
2110
+ var ASKED_IN_EVERY_MODE_TOOLS = [AGENT_QUESTION_TOOL, "ExitPlanMode"];
2111
+ var ASKED_IN_EVERY_MODE = new Set(ASKED_IN_EVERY_MODE_TOOLS);
2112
+ var isAskedInEveryMode = (toolName) => ASKED_IN_EVERY_MODE.has(toolName);
2113
+ var NATIVE_BLOCK_EVENTS = /* @__PURE__ */ new Set(["PermissionDenied"]);
2114
+ var isNativeBlockEvent = (event) => typeof event === "string" && NATIVE_BLOCK_EVENTS.has(event);
2115
+ var CODEX_RESCUE_TOOL_NAME = "Bash";
2116
+ var isBlockedActionEvent = ({ source, event, blockedBy }) => isNativeBlockEvent(event) || source === "codex" && event === "Stop" && blockedBy === "auto_review";
2117
+ var blockedActionRetryReachesGate = (source, toolName) => source === "claude" ? PRETOOLUSE_HANDLED_TOOLS.has(toolName) && !isAskedInEveryMode(toolName) : source === "codex" && toolName === CODEX_RESCUE_TOOL_NAME;
2118
+ var isBlockedActionRequest = (request) => isBlockedActionEvent(request) && blockedActionRetryReachesGate(request.source, request.toolName);
2119
+ var canonicalJsonText = (value) => {
2120
+ if (Array.isArray(value)) return `[${value.map(canonicalJsonText).join(",")}]`;
2121
+ if (value !== null && typeof value === "object") {
2122
+ const record = value;
2123
+ return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJsonText(record[key])}`).join(",")}}`;
2124
+ }
2125
+ return JSON.stringify(value) ?? "null";
2126
+ };
2127
+ var BLOCKED_ACTION_IDENTITY_VERSION = "blocked-action/v1";
2128
+ var CLAUDE_BLOCKED_ACTION_IDENTITY_VERSION = "blocked-action/v2";
2129
+ var BLOCKED_ACTION_GRANT_TTL_SECONDS = 120;
2130
+ var BLOCKED_ACTION_PRESENTATION_ONLY_KEYS = ["description"];
2131
+ var isPresentationOnlyInputKey = (key) => BLOCKED_ACTION_PRESENTATION_ONLY_KEYS.includes(key);
2132
+ var identifyingBlockedActionInput = (toolInput) => Object.fromEntries(Object.entries(toolInput).filter(([key]) => !isPresentationOnlyInputKey(key)));
2133
+ var blockedActionIdentity = (subject) => {
2134
+ const { sessionId, toolName, toolInput } = subject;
2135
+ return subject.source === "claude" ? canonicalJsonText([CLAUDE_BLOCKED_ACTION_IDENTITY_VERSION, sessionId, subject.cwd, toolName, identifyingBlockedActionInput(toolInput)]) : canonicalJsonText([BLOCKED_ACTION_IDENTITY_VERSION, sessionId, toolName, identifyingBlockedActionInput(toolInput)]);
2136
+ };
2137
+ var PRETOOLUSE_GATED_TOOLS = [
2138
+ "Bash",
2139
+ "Write",
2140
+ "Edit",
2141
+ "MultiEdit",
2142
+ "NotebookEdit",
2143
+ "PowerShell",
2144
+ "Monitor"
2145
+ ];
2146
+ var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, ...ASKED_IN_EVERY_MODE_TOOLS]);
2147
+ var isPermissionRequestOwnedByPreToolUse = (toolName, permissionMode, defers = shouldDeferToNativeMode) => PRETOOLUSE_HANDLED_TOOLS.has(toolName) && (isAskedInEveryMode(toolName) || !defers(permissionMode, toolName));
2148
+ var CLAUDE_PRETOOLUSE_MATCHER = [...PRETOOLUSE_HANDLED_TOOLS].join("|");
2149
+ var resolveGate = (input) => {
2150
+ const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
2151
+ if (isPusharyOwnTool(toolName)) {
2152
+ return {
2153
+ kind: "defer",
2154
+ policy: resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey, sessionId),
2155
+ reason: "own_tool"
2156
+ };
2157
+ }
2158
+ if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
2159
+ const policy = resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey, sessionId);
2160
+ const verdict = verdictForPolicy(policy);
2161
+ const autoApproves = verdict === "allow";
2162
+ const autoDenies = verdict === "deny";
2163
+ if (autoDenies) return { kind: "deny", policy, reason: `Denied by policy for ${policy.tool}` };
2164
+ if (isAskedInEveryMode(toolName)) return { kind: "gate", policy };
2165
+ if (isModeStateDegraded(modeState)) {
2166
+ if (autoApproves) {
2167
+ return survivesDegraded(config, toolName, toolInputs, cwd, repoKey, sessionId) ? { kind: "allow", policy, source: "allowlist" } : { kind: "defer", policy };
2168
+ }
2169
+ return { kind: "gate", policy };
2170
+ }
2171
+ const scopeVerdict = modeState.scope ? strictestScopeVerdict(modeState.scope, input.scopePaths ?? []) : { outcome: "no_contract" };
2172
+ if (autoApproves) {
2173
+ if (!scopeRequiresHuman(scopeVerdict)) {
2174
+ return {
2175
+ kind: "allow",
2176
+ policy,
2177
+ source: toolInputs.length === 1 && resolveAutoResolveOrigin(config, toolName, toolInputs[0], cwd, repoKey, sessionId) === "safe_readonly" ? "allowlist" : "policy"
2178
+ };
2179
+ }
2180
+ }
2181
+ const scopeReason = scopeChangeReason(scopeVerdict);
2182
+ return {
2183
+ kind: "gate",
2184
+ policy,
2185
+ scopeReason,
2186
+ scopePath: scopeReason && "path" in scopeVerdict ? widenablePath(scopeVerdict.path) : void 0
2187
+ };
2188
+ };
2189
+ var editAsksAtTheAgentsPrompt = (input, verdict) => verdict.kind === "gate" && !verdict.scopeReason && (input.modeState.mode ?? input.config.modeOverride ?? null) === null && !aChosenRuleGoverns(input) && verdict.policy.mode === "push_first" && ACCEPT_EDITS_DEFER_TOOLS.has(input.toolName);
2190
+ var claudePromptsBeforeAnEdit = (permissionMode) => permissionMode === "default";
2191
+ var MAX_AGENT_PERMISSION_RULES_PER_KIND = 256;
2192
+ var MAX_AGENT_PERMISSION_RULE_LENGTH = 512;
2193
+ var isAgentPermissionRule = (value) => typeof value === "string" && value.length > 0 && value.length <= MAX_AGENT_PERMISSION_RULE_LENGTH;
2194
+ var settingsPermissionList = (permissions, kind) => {
2195
+ const list = permissions[kind];
2196
+ return Array.isArray(list) ? list.filter(isAgentPermissionRule) : [];
2197
+ };
2198
+ var boundedUnion = (lists) => [...new Set(lists.flat())].slice(0, MAX_AGENT_PERMISSION_RULES_PER_KIND);
2199
+ var claudePermissionRulesFromSettings = (documents) => {
2200
+ const permissionBlocks = documents.flatMap((document) => isRecord(document) && isRecord(document.permissions) ? [document.permissions] : []);
2201
+ const collect = (kind) => boundedUnion(permissionBlocks.map((permissions) => settingsPermissionList(permissions, kind)));
2202
+ return { allow: collect("allow"), ask: collect("ask"), deny: collect("deny") };
2203
+ };
2204
+ var claudeConsultsItsAllowlist = (permissionMode) => permissionMode === "default" || permissionMode === "acceptEdits";
2205
+ var anyRuleRestricts = (rules, toolName, args) => rules.some((rule) => matchToolPatternAny(rule, toolName, args, PATTERN_INTENT.restrict) !== "none");
2206
+ var anyRuleGrants = (rules, toolName, args) => rules.some((rule) => matchToolPatternAny(rule, toolName, args, PATTERN_INTENT.grant) !== "none");
2207
+ var everyCommandSegmentGranted = (rules, toolName, command) => {
2208
+ const segments = readableCommandSegments(command);
2209
+ return segments !== null && segments.every((segment) => isSafeReadOnlyCommand(segment) || anyRuleGrants(rules, toolName, [segment]));
2210
+ };
2211
+ var restrictionFormsOf = (toolName, args) => {
2212
+ const command = toolName in POLICY_COMMAND_KEYS ? args[0] : void 0;
2213
+ const normalized = command === void 0 ? null : normalizedCommand(command);
2214
+ return normalized === null ? args : [...args, normalized];
2215
+ };
2216
+ var allowRulesGrant = (rules, toolName, args) => {
2217
+ const command = args[0];
2218
+ return toolName in POLICY_COMMAND_KEYS && command !== void 0 ? everyCommandSegmentGranted(rules, toolName, command) : anyRuleGrants(rules, toolName, args);
2219
+ };
2220
+ var inputNeedsAPerson = (toolName, toolInput) => {
2221
+ const arg = extractPolicyArg(toolName, toolInput);
2222
+ if (arg === void 0) return false;
2223
+ return toolName in POLICY_COMMAND_KEYS ? commandNeedsAPerson(toolName, arg) : classifyDecisionRisk(toolName, arg) !== "standard";
2224
+ };
2225
+ var agentRulesAllowEveryInput = (rules, toolName, toolInputs, cwd) => rules.allow.length > 0 && toolInputs.length > 0 && toolInputs.every((toolInput) => {
2226
+ const args = policyArgForms(toolName, toolInput, cwd);
2227
+ const restrictionForms = restrictionFormsOf(toolName, args);
2228
+ return allowRulesGrant(rules.allow, toolName, args) && !anyRuleRestricts(rules.ask, toolName, restrictionForms) && !anyRuleRestricts(rules.deny, toolName, restrictionForms) && !inputNeedsAPerson(toolName, toolInput);
2229
+ });
2230
+ var isDefaultRule = (policy) => policy.origin === "builtin" || policy.tool === WILDCARD_TOOL_PATTERN;
2231
+ var isApprovalRoutePattern = (value) => typeof value === "object" && value !== null && typeof value.toolPattern === "string" && ["string", "undefined"].includes(typeof value.repoKey);
2232
+ var approvalRoutesOf = (config) => {
2233
+ const routes = config.approvalRoutes ?? [];
2234
+ return Array.isArray(routes) && routes.every(isApprovalRoutePattern) ? routes : null;
2235
+ };
2236
+ var aChosenRuleGoverns = (input) => {
2237
+ const chosen = input.config.policies.filter((policy) => !isDefaultRule(policy));
2238
+ const routes = approvalRoutesOf(input.config);
2239
+ if (routes === null) return true;
2240
+ return input.toolInputs.some((toolInput) => {
2241
+ const args = policyArgForms(input.toolName, toolInput, input.cwd);
2242
+ return selectGoverningRule(chosen, input.toolName, args, input.repoKey, input.sessionId) !== void 0 || findBestRoute(routes, input.toolName, args, input.repoKey) !== void 0;
2243
+ });
2244
+ };
2245
+ var claudeAllowlistOwnsCall = (input, verdict, permissionMode, rules) => rules !== null && verdict.kind === "gate" && !verdict.scopeReason && !aChosenRuleGoverns(input) && !isAskedInEveryMode(input.toolName) && claudeConsultsItsAllowlist(permissionMode) && agentRulesAllowEveryInput(rules, input.toolName, input.toolInputs, input.cwd);
2246
+ var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
2247
+ var modeStateFromResponse = (data) => {
2248
+ const mode = data.override?.mode;
2249
+ return {
2250
+ mode: isApprovalMode(mode) ? mode : null,
2251
+ kill: data.kill === true,
2252
+ policyVersion: toPolicyVersion(data.policyVersion),
2253
+ relayUrl: typeof data.relayUrl === "string" && data.relayUrl.length > 0 ? data.relayUrl : null,
2254
+ trainingConsent: data.trainingConsent === true,
2255
+ // Validated rather than cast: a malformed contract must read as no contract,
2256
+ // never as one that silently matches nothing.
2257
+ scope: isScopeContract(data.scope) ? data.scope : null,
2258
+ degraded: false,
2259
+ askHandoff: data.askHandoff === true
2260
+ };
2261
+ };
2262
+ var GATE_WIRE = 1;
2263
+ var GATE_MAX_TOOL_INPUTS = 32;
2264
+ var GATE_BLOCK_REASON_MAX_LENGTH = 200;
2265
+ var wireVerdictFor = (verdict, deadlineSeconds) => {
2266
+ switch (verdict.kind) {
2267
+ case "kill":
2268
+ return { kind: "kill", reason: verdict.reason };
2269
+ case "allow":
2270
+ return { kind: "allow", source: verdict.source, policy: verdict.policy };
2271
+ case "deny":
2272
+ return { kind: "deny", reason: verdict.reason, policy: verdict.policy };
2273
+ case "defer":
2274
+ return { kind: "no_opinion", reason: verdict.reason === "own_tool" ? "not_gated" : "degraded" };
2275
+ case "gate":
2276
+ return {
2277
+ kind: "ask",
2278
+ policy: verdict.policy,
2279
+ deadlineSeconds,
2280
+ ...verdict.scopeReason ? { scopeReason: verdict.scopeReason } : {},
2281
+ ...verdict.scopePath ? { scopePath: verdict.scopePath } : {}
2282
+ };
2283
+ }
2284
+ };
2285
+ var machineDecisionFor = (verdict) => {
2286
+ switch (verdict.kind) {
2287
+ case "kill":
2288
+ return { origin: DECISION_ORIGIN.killSwitch, outcome: SETTLED_OUTCOME.deny };
2289
+ case "allow":
2290
+ return {
2291
+ origin: verdict.source === GATE_ALLOW_SOURCE.allowlist ? DECISION_ORIGIN.safeReadonly : DECISION_ORIGIN.autopilot,
2292
+ outcome: SETTLED_OUTCOME.approve
2293
+ };
2294
+ case "deny":
2295
+ return { origin: DECISION_ORIGIN.autopilot, outcome: SETTLED_OUTCOME.deny };
2296
+ case "ask":
2297
+ case "no_opinion":
2298
+ return null;
2299
+ }
2300
+ };
2301
+ var claudePermissionDeniedOutput = (decision) => ({
2302
+ hookSpecificOutput: {
2303
+ hookEventName: "PermissionDenied",
2304
+ retry: decision === "allow"
2305
+ }
2306
+ });
2307
+ var CODEX_RESCUE_COMMAND_MAX_LENGTH = 2e3;
2308
+ var CODEX_RESCUE_APPROVED = "I approved this command in Pushary after the auto-reviewer denied it. If it is denied again, stop and tell me.";
2309
+ var codexRescueRetryInstruction = (command) => command.length <= CODEX_RESCUE_COMMAND_MAX_LENGTH ? `Run it again now, exactly as before: ${command}` : `Run it again now, exactly as before. It is too long to repeat in full here and begins: ${command.slice(0, CODEX_RESCUE_COMMAND_MAX_LENGTH)}`;
2310
+ var codexStopRescueOutput = (decision, command) => decision === "allow" ? { decision: "block", reason: `${CODEX_RESCUE_APPROVED} ${codexRescueRetryInstruction(command)}` } : { continue: true };
2311
+ var stoppedOutput = (output, stopReason) => ({ ...output, continue: false, stopReason });
2312
+ var claudePreToolUseOutput = (decision, reason, updatedInput) => ({
2313
+ hookSpecificOutput: {
2314
+ hookEventName: "PreToolUse",
2315
+ permissionDecision: decision,
2316
+ ...reason ? { permissionDecisionReason: reason } : {},
2317
+ ...updatedInput ? { updatedInput } : {}
2318
+ }
2319
+ });
2320
+ var PENDING_COMMAND_PREFIX = "The user sent a new instruction from their phone via Pushary:";
2321
+ var pendingCommandEvents = (source) => {
2322
+ switch (source) {
2323
+ case "claude":
2324
+ return ["PostToolUse", "UserPromptSubmit", "Stop"];
2325
+ // Codex carries context on its activity events and NOT on Stop, whose output
2326
+ // schema has no context field at all. Draining for it at the turn boundary
2327
+ // was therefore right to render nothing, and wrong to conclude that Codex
2328
+ // could not be sent a message.
2329
+ case "codex":
2330
+ return ["PostToolUse", "PreToolUse", "UserPromptSubmit"];
2331
+ case "gemini":
2332
+ return ["AfterTool", "BeforeTool", "BeforeAgent"];
2333
+ case "cursor":
2334
+ return ["postToolUse"];
2335
+ case "vscode":
2336
+ return ["PostToolUse", "UserPromptSubmit"];
2337
+ // The prompt is appended through the TUI rather than through a hook return,
2338
+ // so there is nothing for this to print.
2339
+ case "opencode":
2340
+ return [];
2341
+ case "qwen":
2342
+ case "codebuddy":
2343
+ case "qoder":
2344
+ case "droid":
2345
+ case "devin":
2346
+ return compatiblePendingCommandEvents(source);
2347
+ }
2348
+ };
2349
+ var COMPATIBLE_PENDING_COMMAND_EVENTS = ["PostToolUse", "UserPromptSubmit"];
2350
+ var compatiblePendingCommandEvents = (source) => {
2351
+ const profile = hookCompatibleProfileBySource(source);
2352
+ if (!profile || profile.wire !== "permission_decision") return [];
2353
+ return COMPATIBLE_PENDING_COMMAND_EVENTS.filter((event) => profile.events.includes(event));
2354
+ };
2355
+ var claudePermissionRequestOutput = (decision, reason, updatedInput) => ({
2356
+ hookSpecificOutput: {
2357
+ hookEventName: "PermissionRequest",
2358
+ decision: decision === "allow" ? { behavior: "allow", ...updatedInput ? { updatedInput } : {} } : { behavior: "deny", ...reason ? { message: reason } : {} }
2359
+ }
2360
+ });
2361
+ var gateOutputFor = (source, event, decision, reason) => {
2362
+ let output;
2363
+ if (source === "claude" || source === "vscode") {
2364
+ if (event === "PreToolUse") {
2365
+ output = claudePreToolUseOutput(decision, reason);
2366
+ } else if (event === "PermissionRequest" && decision !== "ask") {
2367
+ output = claudePermissionRequestOutput(decision, reason);
2368
+ } else if (source === "claude" && event === "PermissionDenied" && decision !== "ask") {
2369
+ output = claudePermissionDeniedOutput(decision);
2370
+ }
2371
+ } else if (source === "codex") {
2372
+ if (event === "PermissionRequest" && decision !== "ask") {
2373
+ output = {
2374
+ hookSpecificOutput: {
2375
+ hookEventName: "PermissionRequest",
2376
+ decision: decision === "allow" ? { behavior: "allow" } : { behavior: "deny", message: reason ?? "Denied from Pushary" }
2377
+ }
2378
+ };
2379
+ } else if (event === "PreToolUse" && decision === "deny") {
2380
+ output = {
2381
+ hookSpecificOutput: {
2382
+ hookEventName: "PreToolUse",
2383
+ permissionDecision: "deny",
2384
+ permissionDecisionReason: reason ?? "Denied from Pushary"
2385
+ }
2386
+ };
2387
+ }
2388
+ } else if (source === "gemini" && event === "BeforeTool" && decision !== "ask") {
2389
+ output = decision === "allow" ? { decision: "allow" } : { decision: "deny", reason: reason ?? "Denied from Pushary" };
2390
+ } else if (source === "opencode" && event === OPENCODE_APPROVAL_EVENT) {
2391
+ output = decision === "deny" ? { status: "deny", reason: reason ?? "Denied from Pushary" } : { status: decision };
2392
+ } else if (source === "cursor" && CURSOR_GATE_EVENTS.includes(event)) {
2393
+ output = decision === "allow" ? { permission: "allow" } : decision === "ask" && event !== "preToolUse" ? { permission: "ask" } : {
2394
+ permission: "deny",
2395
+ user_message: "Command denied via Pushary.",
2396
+ agent_message: reason ?? "Denied from Pushary"
2397
+ };
2398
+ } else {
2399
+ const profile = hookCompatibleProfileBySource(source);
2400
+ if (profile?.events.includes(event)) {
2401
+ if (event === "PreToolUse") {
2402
+ output = renderPreToolUseVerdict(profile, claudePreToolUseOutput(decision, reason));
2403
+ } else if (event === "PermissionRequest" && decision !== "ask") {
2404
+ output = claudePermissionRequestOutput(decision, reason);
2405
+ }
2406
+ }
2407
+ }
2408
+ return output;
2409
+ };
2410
+ var ELICITATION_EVENTS = ["Elicitation", "ElicitationResult"];
2411
+ var claudeElicitationOutput = (event, action, content) => ({
2412
+ hookSpecificOutput: {
2413
+ hookEventName: event,
2414
+ action,
2415
+ // Only ever alongside an accept. A declined elicitation has nothing to carry,
2416
+ // and content beside a decline reads as an answer the person did not give.
2417
+ ...action === "accept" && content ? { content } : {}
2418
+ }
2419
+ });
2420
+ var renderElicitationOutput = (event, action, content) => ELICITATION_EVENTS.includes(event) ? JSON.stringify(claudeElicitationOutput(event, action, content)) : void 0;
2421
+ var ELICITATION_ANSWERABLE_MODE = "form";
2422
+ var ELICITATION_MIN_OPTIONS = 2;
2423
+ var ELICITATION_MAX_MULTI_QUESTIONS = 4;
2424
+ var ELICITATION_MAX_MULTI_OPTIONS = 4;
2425
+ var elicitationRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2426
+ var elicitationLabel = (name, property) => typeof property.title === "string" && property.title.trim() ? property.title.trim() : name;
2427
+ var elicitationEnum = (property) => {
2428
+ if (!Array.isArray(property.enum)) return null;
2429
+ const members = property.enum.filter((member) => typeof member === "string");
2430
+ return members.length === property.enum.length && members.length >= ELICITATION_MIN_OPTIONS ? members : null;
2431
+ };
2432
+ var readElicitationFields = (schema) => {
2433
+ const record = elicitationRecord(schema);
2434
+ const properties = elicitationRecord(record?.properties);
2435
+ if (!properties || record?.type !== "object") return null;
2436
+ if (Object.keys(record).some((key) => !["type", "properties", "required", "$schema"].includes(key))) return null;
2437
+ if (record.required !== void 0 && (!Array.isArray(record.required) || !record.required.every((name) => typeof name === "string" && Object.hasOwn(properties, name)))) return null;
2438
+ const entries = Object.entries(properties);
2439
+ if (entries.length === 0) return null;
2440
+ const fields = [];
2441
+ for (const [name, raw] of entries) {
2442
+ const property = elicitationRecord(raw);
2443
+ if (!property || !name) return null;
2444
+ if (Object.keys(property).some((key) => !["type", "title", "enum"].includes(key))) return null;
2445
+ if (property.title !== void 0 && typeof property.title !== "string") return null;
2446
+ const label = elicitationLabel(name, property);
2447
+ if ("enum" in property) {
2448
+ const options = elicitationEnum(property);
2449
+ if (property.type !== "string" || !options || options.some((option) => !option.trim()) || new Set(options).size !== options.length) return null;
2450
+ fields.push({ kind: "choice", name, label, options });
2451
+ continue;
2452
+ }
2453
+ switch (property.type) {
2454
+ case "string":
2455
+ fields.push({ kind: "text", name, label });
2456
+ break;
2457
+ case "number":
2458
+ fields.push({ kind: "number", name, label, integer: false });
2459
+ break;
2460
+ case "integer":
2461
+ fields.push({ kind: "number", name, label, integer: true });
2462
+ break;
2463
+ case "boolean":
2464
+ fields.push({ kind: "boolean", name, label });
2465
+ break;
2466
+ default:
2467
+ return null;
2468
+ }
2469
+ }
2470
+ return fields;
2471
+ };
2472
+ var elicitationQuestionText = (message, field, alone) => alone ? message : `${message} \u2014 ${field.label}`;
2473
+ var planElicitationAsk = (message, fields) => {
2474
+ if (fields.length === 1) {
2475
+ const field = fields[0];
2476
+ const question = elicitationQuestionText(message, field, true);
2477
+ switch (field.kind) {
2478
+ case "choice":
2479
+ return { kind: "select", field, question, options: field.options };
2480
+ case "boolean":
2481
+ return { kind: "confirm", field, question };
2482
+ case "text":
2483
+ case "number":
2484
+ return { kind: "input", field, question };
2485
+ }
2486
+ }
2487
+ if (fields.length > ELICITATION_MAX_MULTI_QUESTIONS) return null;
2488
+ if (!fields.every((field) => field.kind === "choice" && field.options.length <= ELICITATION_MAX_MULTI_OPTIONS)) return null;
2489
+ const questions = fields.map((field) => ({
2490
+ question: elicitationQuestionText(message, field, false),
2491
+ header: field.label.slice(0, 12),
2492
+ multiSelect: false,
2493
+ allowOther: false,
2494
+ options: field.options.map((label) => ({ label }))
2495
+ }));
2496
+ return new Set(questions.map((question) => question.question)).size === questions.length ? { kind: "questions", fields, questions } : null;
2497
+ };
2498
+ var ELICITATION_CONFIRMED = /* @__PURE__ */ new Set(["yes", "true", "approve", "approved", "allow"]);
2499
+ var ELICITATION_REFUSED = /* @__PURE__ */ new Set(["no", "false", "deny", "denied", "reject"]);
2500
+ var elicitationNumber = (raw, integer) => {
2501
+ const trimmed = raw.trim();
2502
+ if (!/^-?\d+(\.\d+)?$/.test(trimmed)) return null;
2503
+ const value = Number(trimmed);
2504
+ if (!Number.isFinite(value)) return null;
2505
+ return integer && !Number.isInteger(value) ? null : value;
2506
+ };
2507
+ var elicitationContent = (ask, answer) => {
2508
+ const value = answer.trim();
2509
+ if (!value) return null;
2510
+ if (ask.kind === "input") {
2511
+ if (ask.field.kind === "number") {
2512
+ const parsed = elicitationNumber(value, ask.field.integer);
2513
+ return parsed === null ? null : { [ask.field.name]: parsed };
2514
+ }
2515
+ return { [ask.field.name]: value };
2516
+ }
2517
+ if (ask.kind === "confirm") {
2518
+ const lowered = value.toLowerCase();
2519
+ if (ELICITATION_CONFIRMED.has(lowered)) return { [ask.field.name]: true };
2520
+ if (ELICITATION_REFUSED.has(lowered)) return { [ask.field.name]: false };
2521
+ return null;
2522
+ }
2523
+ if (ask.kind === "select") {
2524
+ return ask.options.includes(value) ? { [ask.field.name]: value } : null;
2525
+ }
2526
+ return null;
2527
+ };
2528
+ var elicitationContentFromAnswers = (ask, answers) => {
2529
+ const content = {};
2530
+ for (const [index, field] of ask.fields.entries()) {
2531
+ if (field.kind !== "choice") return null;
2532
+ const chosen = answers[ask.questions[index].question];
2533
+ if (typeof chosen !== "string" || !field.options.includes(chosen)) return null;
2534
+ content[field.name] = chosen;
2535
+ }
2536
+ return content;
2537
+ };
2538
+
2539
+ // ../contracts/src/hook-activation.ts
2540
+ var hookWritePath = (toolName, input) => toolName === "Write" && typeof input.file_path === "string" && input.file_path.startsWith("/") && input.file_path.length <= 4096 ? input.file_path : void 0;
2541
+ var HOOK_ACTIVATION_RESULTS = ["unverified", "approved_executed", "denied_observed", "unexpected_execution", "tool_failed"];
2542
+ var isHookActivationReport = (value) => {
2543
+ if (!value || typeof value !== "object") return false;
2544
+ const report = value;
2545
+ return typeof report.agentType === "string" && typeof report.machineId === "string" && typeof report.target === "string" && report.target.length > 0 && report.target.length <= 4096 && typeof report.truncated === "boolean" && Array.isArray(report.evidence) && report.evidence.length <= 20 && (!report.truncated || report.evidence.length === 0) && report.evidence.every((item) => {
2546
+ if (!item || typeof item !== "object") return false;
2547
+ const row = item;
2548
+ return ["correlationId", "sessionId", "toolUseId", "toolName", "createdAt"].every((key) => typeof row[key] === "string" && row[key].length > 0 && row[key].length <= 256) && (row.answerSource === null || isAnswerSource(row.answerSource)) && HOOK_ACTIVATION_RESULTS.includes(row.result) && Number.isFinite(Date.parse(row.createdAt)) && (row.occurredAt === null || typeof row.occurredAt === "string" && Number.isFinite(Date.parse(row.occurredAt))) && (row.observedAt === null || typeof row.observedAt === "string" && Number.isFinite(Date.parse(row.observedAt))) && (row.result === "unverified" || row.occurredAt !== null && row.observedAt !== null && row.producer !== null && isAnswerSource(row.answerSource) && row.answerSource !== "sandbox") && (row.producer === null || row.producer === "hook" || row.producer === "uplink");
2549
+ });
2550
+ };
2551
+ var hookActivationResult = (report) => {
2552
+ if (report?.evidence.some((row) => row.result === "unexpected_execution")) return "unexpected_execution";
2553
+ return report && !report.truncated && report.evidence.length === 1 ? report.evidence[0].result : "unverified";
2554
+ };
2555
+
2556
+ // src/cli-package-name.ts
2557
+ var CANONICAL_PACKAGE = "pushary";
2558
+ var LEGACY_PACKAGE = "@pushary/agent-hooks";
2559
+ var CLI_PACKAGE_NAMES = [CANONICAL_PACKAGE, LEGACY_PACKAGE];
2560
+ var isCliPackageName = (value) => typeof value === "string" && CLI_PACKAGE_NAMES.some((name) => name === value);
2561
+ var cliPackagePathSegments = (name) => name.split("/");
2562
+
2563
+ // src/global-package.ts
2564
+ import { posix, win32 } from "path";
2565
+ var OWNERSHIP_PROBE_COMMAND = "pushary-hook";
2566
+ var pathApiFor = (platform) => platform === "win32" ? win32 : posix;
2567
+ var POSIX_GLOBAL_ROOT_SEGMENTS = ["lib", "node_modules"];
2568
+ var UPGRADE_ENTRY_SEGMENTS = ["dist", "bin", "pushary-upgrade.js"];
2569
+ var globalRootFor = (prefix, platform) => platform === "win32" ? win32.join(prefix, "node_modules") : posix.join(prefix, ...POSIX_GLOBAL_ROOT_SEGMENTS);
2570
+ var globalPackageFolder = (root, name, platform) => pathApiFor(platform).join(root, ...cliPackagePathSegments(name));
2571
+ var globalUpgradeEntry = (prefix, name, platform) => pathApiFor(platform).join(globalPackageFolder(globalRootFor(prefix, platform), name, platform), ...UPGRADE_ENTRY_SEGMENTS);
2572
+ var isInsideFolder = (target, folder, platform) => {
2573
+ const pathApi = pathApiFor(platform);
2574
+ const relativePath = pathApi.relative(folder, target);
2575
+ return relativePath !== "" && !relativePath.startsWith("..") && !pathApi.isAbsolute(relativePath);
2576
+ };
2577
+ var linkPointsInto = (evidence, targets, name) => evidence.globalRoots.some((root) => {
2578
+ const folder = globalPackageFolder(root, name, evidence.platform);
2579
+ return targets.some((target) => isInsideFolder(target, folder, evidence.platform));
2580
+ });
2581
+ var ownerFromInstalled = (installed, whenNone) => {
2582
+ const [only, ...others] = installed;
2583
+ if (only === void 0) return whenNone;
2584
+ if (others.length > 0) return { kind: "unknown", reason: "both_installed" };
2585
+ return { kind: "owned", name: only };
2586
+ };
2587
+ var decideCommandOwner = (evidence) => {
2588
+ const { link } = evidence;
2589
+ if (link.kind === "resolved") {
2590
+ const name = CLI_PACKAGE_NAMES.find((candidate) => linkPointsInto(evidence, link.targets, candidate));
2591
+ return name === void 0 ? { kind: "unknown", reason: "foreign_command" } : { kind: "owned", name };
2592
+ }
2593
+ const whenNone = link.kind === "absent" ? { kind: "none" } : { kind: "unknown", reason: "unreadable_command" };
2594
+ return ownerFromInstalled(evidence.installed, whenNone);
2595
+ };
2596
+ var CMD_SHIM_TARGET = /"%(?:~dp0|dp0%)\\([^"]+?)"\s+%[*]/;
2597
+ var cmdShimTarget = (shimDir, contents) => {
2598
+ const relativeTarget = CMD_SHIM_TARGET.exec(contents)?.[1];
2599
+ return relativeTarget === void 0 ? void 0 : win32.resolve(shimDir, relativeTarget);
2600
+ };
2601
+ var launchedPackage = (ownName) => ownName ?? LEGACY_PACKAGE;
2602
+ var globalInstallTarget = (owner, ownName) => {
2603
+ switch (owner.kind) {
2604
+ case "owned":
2605
+ return owner.name;
2606
+ case "none":
2607
+ return launchedPackage(ownName);
2608
+ case "unknown":
2609
+ return LEGACY_PACKAGE;
2610
+ }
2611
+ };
2612
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2613
+ var declaresOwnershipProbe = (bin) => isRecord2(bin) && typeof bin[OWNERSHIP_PROBE_COMMAND] === "string";
2614
+ var cliManifestName = (manifest) => {
2615
+ if (!isRecord2(manifest) || !isCliPackageName(manifest.name) || !declaresOwnershipProbe(manifest.bin)) return void 0;
2616
+ return manifest.name;
2617
+ };
2618
+ var classifyGlobalFolder = (name, read) => {
2619
+ if (read.kind === "unreadable") return { kind: "unseen" };
2620
+ const { manifest } = read;
2621
+ if (!isRecord2(manifest) || cliManifestName(manifest) !== name) return { kind: "foreign" };
2622
+ return { kind: "ours", installed: { name, version: typeof manifest.version === "string" ? manifest.version : void 0 } };
2623
+ };
2624
+ var classifyGlobalInstall = (installed, owner) => {
2625
+ const [only, ...others] = installed;
2626
+ if (only === void 0) return { kind: "absent" };
2627
+ if (others.length === 0) return { kind: "single", installed: only };
2628
+ return { kind: "both", installed, owner };
2629
+ };
2630
+ var isUninstallable = (evidence) => evidence.listing !== "absent" && evidence.folder !== "foreign";
2631
+ var globalPackagesToUninstall = (evidence) => evidence.filter(isUninstallable).map((entry) => entry.name);
2632
+ var commandsPackage = (install) => {
2633
+ if (install.kind === "single") return install.installed;
2634
+ if (install.kind !== "both" || install.owner.kind !== "owned") return void 0;
2635
+ const ownerName = install.owner.name;
2636
+ return install.installed.find((installed) => installed.name === ownerName);
2637
+ };
2638
+
2639
+ // src/cli/version.ts
2640
+ import { readFileSync } from "fs";
2641
+ import { dirname, join, parse } from "path";
2642
+ import { fileURLToPath } from "url";
2643
+ var UNKNOWN_VERSION = "0.0.0";
2644
+ var cliManifestNameAt = (manifestPath) => {
2645
+ try {
2646
+ return cliManifestName(JSON.parse(readFileSync(manifestPath, "utf-8")));
2647
+ } catch {
2648
+ return void 0;
2649
+ }
2650
+ };
2651
+ var findOwnPackage = (start) => {
2652
+ const { root } = parse(start);
2653
+ let dir = start;
2654
+ while (true) {
2655
+ const name = cliManifestNameAt(join(dir, "package.json"));
2656
+ if (name !== void 0) return { root: dir, name };
2657
+ if (dir === root) return void 0;
2658
+ const parent = dirname(dir);
2659
+ if (parent === dir) return void 0;
2660
+ dir = parent;
2661
+ }
2662
+ };
2663
+ var runningPackage = () => {
2664
+ try {
2665
+ return findOwnPackage(dirname(fileURLToPath(import.meta.url)));
2666
+ } catch {
2667
+ return void 0;
2668
+ }
2669
+ };
2670
+ var getPackageVersion = () => {
2671
+ const root = ownPackageRoot();
2672
+ return (root === void 0 ? void 0 : packageVersionAt(root)) ?? UNKNOWN_VERSION;
2673
+ };
2674
+ var ownPackageRoot = () => runningPackage()?.root;
2675
+ var ownPackageName = () => runningPackage()?.name;
2676
+ var packageVersionAt = (packageRoot) => {
2677
+ try {
2678
+ const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf-8"));
2679
+ const version = manifest && typeof manifest === "object" && "version" in manifest ? manifest.version : void 0;
2680
+ return typeof version === "string" ? version : void 0;
2681
+ } catch {
2682
+ return void 0;
2683
+ }
2684
+ };
2685
+ var isNewerVersion = (candidate, current) => {
2686
+ const a = candidate.split(".").map((part) => parseInt(part, 10) || 0);
2687
+ const b = current.split(".").map((part) => parseInt(part, 10) || 0);
2688
+ for (let i = 0; i < 3; i++) {
2689
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
2690
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
2691
+ }
2692
+ return false;
2693
+ };
2694
+ var RELEASE_VERSION = /^\d+\.\d+\.\d+$/;
2695
+ var isReleaseVersion = (value) => RELEASE_VERSION.test(value);
2696
+ var majorOf = (version) => parseInt(version.split(".")[0] ?? "", 10) || 0;
2697
+ var isSameMajorVersion = (candidate, current) => majorOf(candidate) === majorOf(current);
2698
+
2699
+ export {
2700
+ CLAUDE_CONNECTOR_URL,
2701
+ CLAUDE_CONNECTOR_GUIDE,
2702
+ COWORK_CONNECTOR_LIMIT,
2703
+ CLAUDE_CONNECTOR_STEPS,
2704
+ CLAUDE_CONNECTOR_TEST,
2705
+ COWORK_STANDING_INSTRUCTIONS,
2706
+ AGENTS,
2707
+ HOOK_SOURCES,
2708
+ AGENT_LABELS_BY_TYPE,
2709
+ DISCONNECTABLE_AGENTS,
2710
+ REPO_AWARE_AGENTS,
2711
+ MANAGED_PROVIDERS,
2712
+ PHONE_ANSWERED_TOOLS,
2713
+ canonicalToolName,
2714
+ toToolLookup,
2715
+ renderPreToolUseVerdict,
2716
+ HOOK_COMPATIBLE_PROFILES,
2717
+ hookCompatibleProfile,
2718
+ hookCompatibleProfileBySource,
2719
+ compatibleTargetFiles,
2720
+ redactSecrets,
2721
+ redactSecretsFromContent,
2722
+ redactSecretsDeep,
2723
+ degradedLine,
2724
+ hookWritePath,
2725
+ isHookActivationReport,
2726
+ hookActivationResult,
2727
+ PAIR_TTL_SECONDS,
2728
+ SETUP_AGENT_CHOICES,
2729
+ SETUP_CONNECT_MODES,
2730
+ isSetupConnectMode,
2731
+ CLIENT_REPORT_PATH,
2732
+ clientContextHeaders,
2733
+ normalizeKeyScope,
2734
+ parseAgentQuestion,
2735
+ parseAgentQuestions,
2736
+ parseAgentQuestionAnswers,
2737
+ normalizeInstallSource,
2738
+ HOOK_BUDGETS,
2739
+ HOOK_HOLD_BUDGET_ENV,
2740
+ withHookHoldBudget,
2741
+ hookCommandHoldBudget,
2742
+ hookHoldBudgetProof,
2743
+ hookHoldWindowMs,
2744
+ hookWaitDeadline,
2745
+ hookWaitClamped,
2746
+ effectiveWaitSeconds,
2747
+ OPENCODE_APPROVAL_EVENT,
2748
+ CURSOR_GATE_EVENTS,
2749
+ SUPPORT_EMAIL,
2750
+ SUPPORT_DISCORD,
2751
+ HOOK_LOCATOR_BINARY,
2752
+ normalizeRepoRemote,
2753
+ localRepoKey,
2754
+ isSafeReadOnlyCommand,
2755
+ isAutoApprove,
2756
+ FILE_TARGET_TOOLS,
2757
+ isValidApiKey,
2758
+ ACTION_BODY_MAX,
2759
+ DECISION_LINE_MAX,
2760
+ ASK_QUESTION_MAX,
2761
+ ASK_AGENT_NAME_MAX,
2762
+ truncateLine,
2763
+ isAnswerSource,
2764
+ buildDecisionEpisodeFeatures,
2765
+ resolveAutoResolveOrigin,
2766
+ resolvePolicy,
2767
+ KILL_REASON,
2768
+ isPusharyOwnTool,
2769
+ shouldDeferToNativeMode,
2770
+ AGENT_QUESTION_STRATEGY,
2771
+ agentQuestionStrategy,
2772
+ CODEX_RESCUE_TOOL_NAME,
2773
+ isBlockedActionRequest,
2774
+ BLOCKED_ACTION_GRANT_TTL_SECONDS,
2775
+ blockedActionIdentity,
2776
+ PRETOOLUSE_GATED_TOOLS,
2777
+ isPermissionRequestOwnedByPreToolUse,
2778
+ CLAUDE_PRETOOLUSE_MATCHER,
2779
+ resolveGate,
2780
+ editAsksAtTheAgentsPrompt,
2781
+ claudePromptsBeforeAnEdit,
2782
+ claudePermissionRulesFromSettings,
2783
+ claudeConsultsItsAllowlist,
2784
+ agentRulesAllowEveryInput,
2785
+ claudeAllowlistOwnsCall,
2786
+ modeStateFromResponse,
2787
+ GATE_WIRE,
2788
+ GATE_MAX_TOOL_INPUTS,
2789
+ GATE_BLOCK_REASON_MAX_LENGTH,
2790
+ wireVerdictFor,
2791
+ machineDecisionFor,
2792
+ claudePermissionDeniedOutput,
2793
+ codexStopRescueOutput,
2794
+ stoppedOutput,
2795
+ claudePreToolUseOutput,
2796
+ PENDING_COMMAND_PREFIX,
2797
+ pendingCommandEvents,
2798
+ claudePermissionRequestOutput,
2799
+ gateOutputFor,
2800
+ renderElicitationOutput,
2801
+ ELICITATION_ANSWERABLE_MODE,
2802
+ readElicitationFields,
2803
+ planElicitationAsk,
2804
+ elicitationContent,
2805
+ elicitationContentFromAnswers,
2806
+ CANONICAL_PACKAGE,
2807
+ LEGACY_PACKAGE,
2808
+ CLI_PACKAGE_NAMES,
2809
+ cliPackagePathSegments,
2810
+ OWNERSHIP_PROBE_COMMAND,
2811
+ POSIX_GLOBAL_ROOT_SEGMENTS,
2812
+ globalRootFor,
2813
+ globalPackageFolder,
2814
+ globalUpgradeEntry,
2815
+ decideCommandOwner,
2816
+ cmdShimTarget,
2817
+ launchedPackage,
2818
+ globalInstallTarget,
2819
+ classifyGlobalFolder,
2820
+ classifyGlobalInstall,
2821
+ globalPackagesToUninstall,
2822
+ commandsPackage,
2823
+ getPackageVersion,
2824
+ ownPackageRoot,
2825
+ ownPackageName,
2826
+ packageVersionAt,
2827
+ isNewerVersion,
2828
+ isReleaseVersion,
2829
+ isSameMajorVersion
2830
+ };