@pushary/agent-hooks 0.87.2 → 0.87.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/data/cursor-plugin/hooks/hooks.json +7 -0
  2. package/data/cursor-plugin/scripts/pushary-gate.mjs +85 -28
  3. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +42 -1
  4. package/dist/bin/pushary-bell-hook.js +4 -4
  5. package/dist/bin/pushary-bell.js +7 -6
  6. package/dist/bin/pushary-claude.js +14 -11
  7. package/dist/bin/pushary-clean.js +36 -20
  8. package/dist/bin/pushary-codex-bridge.js +6 -5
  9. package/dist/bin/pushary-codex-hook.js +129 -52
  10. package/dist/bin/pushary-codex.js +4 -3
  11. package/dist/bin/pushary-connect.js +6 -6
  12. package/dist/bin/pushary-daemon.js +5 -4
  13. package/dist/bin/pushary-disconnect.js +65 -8
  14. package/dist/bin/pushary-doctor.js +79 -18
  15. package/dist/bin/pushary-elicitation-hook.js +1 -1
  16. package/dist/bin/pushary-gemini-bridge.js +6 -5
  17. package/dist/bin/pushary-gemini-hook.js +12 -10
  18. package/dist/bin/pushary-hook.js +6 -5
  19. package/dist/bin/pushary-login.js +4 -4
  20. package/dist/bin/pushary-logout.js +5 -4
  21. package/dist/bin/pushary-mode.js +3 -3
  22. package/dist/bin/pushary-notification-hook.js +4 -3
  23. package/dist/bin/pushary-opencode-hook.d.ts +1 -0
  24. package/dist/bin/pushary-opencode-hook.js +329 -0
  25. package/dist/bin/pushary-permission-denied-hook.js +6 -5
  26. package/dist/bin/pushary-permission-hook.js +6 -5
  27. package/dist/bin/pushary-post-hook.js +4 -3
  28. package/dist/bin/pushary-prompt-hook.js +4 -3
  29. package/dist/bin/pushary-session-end-hook.js +4 -3
  30. package/dist/bin/pushary-session-start-hook.js +4 -3
  31. package/dist/bin/pushary-setup.js +238 -84
  32. package/dist/bin/pushary-stats.js +2 -2
  33. package/dist/bin/pushary-status.js +5 -5
  34. package/dist/bin/pushary-stop-hook.js +4 -3
  35. package/dist/bin/pushary-stopfailure-hook.js +4 -3
  36. package/dist/bin/pushary-upgrade.js +7 -6
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-5646VKR4.js → chunk-2JYQAW4B.js} +5 -53
  39. package/dist/chunk-7J67NYK4.js +287 -0
  40. package/dist/{chunk-M7RYIZX6.js → chunk-B7ZBHWOW.js} +10 -1
  41. package/dist/{chunk-7YJS2VTG.js → chunk-DMW54VWK.js} +12 -1
  42. package/dist/{chunk-LT2VLPUK.js → chunk-DPUOW6NS.js} +18 -2
  43. package/dist/{chunk-3LKYBFWA.js → chunk-EJL3PJU7.js} +1 -1
  44. package/dist/chunk-GIRT7677.js +11 -0
  45. package/dist/{chunk-HAUTLCMA.js → chunk-ICERLAE2.js} +1 -1
  46. package/dist/{chunk-ZZE6FN7T.js → chunk-K42RXJPG.js} +52 -1
  47. package/dist/{chunk-Y3UTTFTZ.js → chunk-KBS53WOE.js} +1 -1
  48. package/dist/{chunk-SJVLFIYP.js → chunk-KCAFCGGY.js} +1 -1
  49. package/dist/{chunk-A4I5JGCW.js → chunk-KFAH6KRE.js} +1 -1
  50. package/dist/{chunk-QBWYNVKY.js → chunk-LGVHYFIB.js} +1 -1
  51. package/dist/{chunk-HJ44HIUW.js → chunk-OMA2OZRE.js} +11 -9
  52. package/dist/{chunk-ECEUMWBH.js → chunk-QLMX5LLV.js} +1 -1
  53. package/dist/{chunk-XW5CHAQX.js → chunk-TAYHEJFF.js} +2 -2
  54. package/dist/{chunk-JAICP7BA.js → chunk-TD3M232T.js} +1 -1
  55. package/dist/{chunk-WLLBFDVY.js → chunk-TV3UFGT7.js} +1 -1
  56. package/dist/{chunk-7MBDFNUC.js → chunk-VIHRP25S.js} +1 -1
  57. package/dist/{chunk-B3V2H6SA.js → chunk-VL5ZNK6V.js} +43 -12
  58. package/dist/{chunk-V2XPF77V.js → chunk-VOE4SYPR.js} +42 -279
  59. package/dist/{chunk-OT4YD54Q.js → chunk-YX6ZKCC5.js} +1 -1
  60. package/dist/{chunk-2T3GX7WG.js → chunk-YYTLTEGU.js} +1 -1
  61. package/dist/chunk-ZGC3W23N.js +371 -0
  62. package/dist/src/index.js +6 -5
  63. package/package.json +2 -1
@@ -0,0 +1,371 @@
1
+ import {
2
+ HOOK_BUDGETS,
3
+ OPENCODE_APPROVAL_EVENT
4
+ } from "./chunk-DMW54VWK.js";
5
+
6
+ // src/opencode-plugin.ts
7
+ var OPENCODE_PLUGIN_MARKER = "pushary-opencode-plugin w1";
8
+ var OPENCODE_PLUGIN_PLACEHOLDER = "__PUSHARY_COMMAND__";
9
+ var OPENCODE_PLUGIN_FILE = "pushary.js";
10
+ var OPENCODE_PLUGIN_DIR = "plugins";
11
+ var OPENCODE_PLUGIN_SIBLING = "package.json";
12
+ var OPENCODE_PLUGIN_SIBLING_CONTENTS = `${JSON.stringify({ type: "module" }, null, 2)}
13
+ `;
14
+ var OPENCODE_ASKED_EVENTS = ["permission.asked", "permission.updated"];
15
+ var OPENCODE_REPLIED_EVENT = "permission.replied";
16
+ var OPENCODE_IDLE_EVENT = "session.idle";
17
+ var approvalTimeoutMs = () => (HOOK_BUDGETS.opencode.budgetSeconds - HOOK_BUDGETS.opencode.guardSeconds) * 1e3;
18
+ var openCodePluginSource = () => `// Pushary for OpenCode. Generated by @pushary/agent-hooks \u2014 do not edit.
19
+ // ${OPENCODE_PLUGIN_MARKER}
20
+ //
21
+ // OpenCode finds a plugin by globbing {plugin,plugins}/*.{ts,js} under its config
22
+ // directory and importing it, so this file is the whole install.
23
+ //
24
+ // It decides nothing. Each hook builds the payload OpenCode handed it, writes it
25
+ // to the Pushary hook command on stdin, and reads a verdict back. The policy
26
+ // engine, the phone, the wait and the settlement all live behind that command.
27
+ //
28
+ // Approvals ride the event stream rather than the "permission.ask" hook, which is
29
+ // declared in @opencode-ai/plugin and called by nothing. Permission.ask publishes
30
+ // "permission.asked" and blocks on a Deferred that only a reply resolves, so this
31
+ // answers that reply on the person's behalf when Pushary has a verdict.
32
+ //
33
+ // Failure is silence. A missing command, a crash, a timeout, unreadable bytes and
34
+ // an unparseable reply all leave OpenCode's own prompt exactly as it was. The
35
+ // worst this file may do to somebody is nothing at all.
36
+
37
+ import { spawn } from "node:child_process"
38
+
39
+ const COMMAND = ${OPENCODE_PLUGIN_PLACEHOLDER}
40
+ const APPROVAL_TIMEOUT_MS = ${approvalTimeoutMs()}
41
+ const TELEMETRY_TIMEOUT_MS = 10000
42
+ const MAX_REPLY_BYTES = 65536
43
+ // One entry per call in flight. Bounded because a long session must not grow them
44
+ // without limit, and evicted oldest-first because whatever is still waiting on an
45
+ // answer is the newest thing in here, never the oldest.
46
+ const MAX_TRACKED = 256
47
+
48
+ const text = (value) => (typeof value === "string" && value.length > 0 ? value : undefined)
49
+
50
+ const remember = (map, key, value) => {
51
+ if (!key || value === undefined) return
52
+ if (map.size >= MAX_TRACKED) map.delete(map.keys().next().value)
53
+ map.set(key, value)
54
+ }
55
+
56
+ const run = (payload, timeoutMs, onSpawn) =>
57
+ new Promise((resolve) => {
58
+ let settled = false
59
+ const finish = (value) => {
60
+ if (settled) return
61
+ settled = true
62
+ clearTimeout(timer)
63
+ resolve(value)
64
+ }
65
+
66
+ let child
67
+ try {
68
+ child = spawn(COMMAND[0], COMMAND.slice(1), { stdio: ["pipe", "pipe", "ignore"] })
69
+ } catch {
70
+ return finish(null)
71
+ }
72
+ if (onSpawn) onSpawn(child)
73
+
74
+ const timer = setTimeout(() => {
75
+ try {
76
+ child.kill("SIGKILL")
77
+ } catch {}
78
+ finish(null)
79
+ }, timeoutMs)
80
+ if (typeof timer.unref === "function") timer.unref()
81
+
82
+ let out = ""
83
+ child.stdout.on("data", (chunk) => {
84
+ if (out.length < MAX_REPLY_BYTES) out += chunk
85
+ })
86
+ child.on("error", () => finish(null))
87
+ child.on("close", () => {
88
+ const body = out.trim()
89
+ if (!body) return finish(null)
90
+ try {
91
+ finish(JSON.parse(body))
92
+ } catch {
93
+ finish(null)
94
+ }
95
+ })
96
+
97
+ try {
98
+ child.stdin.on("error", () => {})
99
+ child.stdin.end(JSON.stringify(payload))
100
+ } catch {
101
+ finish(null)
102
+ }
103
+ })
104
+
105
+ // Telemetry is fire and forget: the agent has already acted and is only being
106
+ // observed, so nothing here may hold up the next turn.
107
+ const tell = (payload) => {
108
+ void run(payload, TELEMETRY_TIMEOUT_MS)
109
+ }
110
+
111
+ // What OpenCode gates on, and what it hands over with it. Verified against its own
112
+ // tools: bash asks with { command }, write and edit both ask under "edit" with
113
+ // { filepath, diff }, webfetch with { url }, and a shell command reaching outside
114
+ // the worktree asks a second time under "external_directory".
115
+ const inputFrom = (permission, metadata) => {
116
+ const meta = metadata && typeof metadata === "object" ? metadata : {}
117
+ const command = text(meta.command)
118
+ if (command) return { command }
119
+ const filepath = text(meta.filepath) ?? text(meta.file_path)
120
+ // The diff rides along, because it is what the person is actually approving.
121
+ // OpenCode computes it before raising the permission and hands it over; without
122
+ // it the phone shows a filename and nothing to judge.
123
+ if (filepath) return { file_path: filepath, ...(text(meta.diff) ? { diff: meta.diff } : {}) }
124
+ const url = text(meta.url)
125
+ if (url) return { url }
126
+ return { permission }
127
+ }
128
+
129
+ // Two spellings of one request: the runtime publishes PermissionV1.Request
130
+ // ({ permission, patterns }), the generated SDK types say Permission ({ type,
131
+ // pattern }). Whichever arrives, the id and the session are what we need.
132
+ const askedFrom = (properties) => {
133
+ if (!properties || typeof properties !== "object") return null
134
+ const id = text(properties.id)
135
+ const sessionID = text(properties.sessionID)
136
+ if (!id || !sessionID) return null
137
+ return {
138
+ id,
139
+ sessionID,
140
+ permission: text(properties.permission) ?? text(properties.type) ?? "unknown",
141
+ metadata: properties.metadata,
142
+ title: text(properties.title),
143
+ callID: text(properties.callID) ?? text(properties.tool && properties.tool.callID),
144
+ }
145
+ }
146
+
147
+ const repliedID = (properties) => {
148
+ if (!properties || typeof properties !== "object") return undefined
149
+ return text(properties.requestID) ?? text(properties.permissionID) ?? text(properties.id)
150
+ }
151
+
152
+ export const PusharyPlugin = async ({ client, directory, worktree }) => {
153
+ const cwd = worktree || directory || process.cwd()
154
+ // OpenCode raises a permission under a coarse key \u2014 "edit" covers both write and
155
+ // edit \u2014 and names the actual tool only on tool.execute.before. Keeping the
156
+ // pairing is what lets a rule written for Write mean Write here too.
157
+ const tools = new Map()
158
+ // Every gate still waiting. Held so a person answering in the terminal can end
159
+ // ours: whoever answers first wins, and the loser stops asking.
160
+ const waiting = new Map()
161
+
162
+ const base = (sessionID) => ({ session_id: sessionID ?? "", cwd })
163
+
164
+ // Replying is the whole approval. Two shapes, because the client handed to a
165
+ // plugin is generated: v1 exposes postSessionIdPermissionsPermissionId against
166
+ // /session/{id}/permissions/{permissionID}, v2 exposes permission.reply against
167
+ // /permission/{requestID}/reply. Both routes are live in the server today.
168
+ const respond = async (asked, allow) => {
169
+ const legacy = client && typeof client.postSessionIdPermissionsPermissionId === "function"
170
+ ? () => client.postSessionIdPermissionsPermissionId({
171
+ path: { id: asked.sessionID, permissionID: asked.id },
172
+ body: { response: allow ? "once" : "reject" },
173
+ })
174
+ : null
175
+ const current = client && client.permission && typeof client.permission.reply === "function"
176
+ ? () => client.permission.reply({
177
+ path: { requestID: asked.id },
178
+ body: { reply: allow ? "once" : "reject" },
179
+ })
180
+ : null
181
+
182
+ for (const attempt of [legacy, current]) {
183
+ if (!attempt) continue
184
+ try {
185
+ await attempt()
186
+ return
187
+ } catch {
188
+ // The person answered first, or this client does not serve that route.
189
+ // Either way the next shape is the only thing left to try.
190
+ }
191
+ }
192
+ }
193
+
194
+ const gate = async (asked) => {
195
+ const reply = await run({
196
+ ...base(asked.sessionID),
197
+ hook_event_name: "${OPENCODE_APPROVAL_EVENT}",
198
+ tool_name: tools.get(asked.callID) ?? asked.permission,
199
+ tool_input: inputFrom(asked.permission, asked.metadata),
200
+ permission_id: asked.id,
201
+ permission_type: asked.permission,
202
+ title: asked.title,
203
+ }, APPROVAL_TIMEOUT_MS, (child) => remember(waiting, asked.id, child))
204
+
205
+ // Gone means the terminal already answered while we were asking. Replying now
206
+ // would be answering a question nobody is holding open any more.
207
+ if (!waiting.delete(asked.id)) return
208
+
209
+ const status = reply && typeof reply === "object" ? reply.status : undefined
210
+ if (status !== "allow" && status !== "deny") return
211
+ await respond(asked, status === "allow")
212
+ }
213
+
214
+ const withdraw = (id) => {
215
+ const child = id && waiting.get(id)
216
+ if (!child) return
217
+ waiting.delete(id)
218
+ // SIGTERM rather than SIGKILL: the hook cancels its outstanding question on
219
+ // the way out, so the phone stops showing a decision the terminal has taken.
220
+ try {
221
+ child.kill("SIGTERM")
222
+ } catch {}
223
+ }
224
+
225
+ return {
226
+ "tool.execute.before": async (input) => {
227
+ remember(tools, input && input.callID, input && input.tool)
228
+ },
229
+
230
+ "tool.execute.after": async (input, output) => {
231
+ const call = input && input.callID
232
+ if (call) tools.delete(call)
233
+ tell({
234
+ ...base(input && input.sessionID),
235
+ hook_event_name: "tool.execute.after",
236
+ tool_name: text(input && input.tool) ?? "unknown",
237
+ tool_input: input && input.args && typeof input.args === "object" ? input.args : {},
238
+ tool_result: { title: text(output && output.title), output: text(output && output.output) },
239
+ })
240
+ },
241
+
242
+ "chat.message": async (input, output) => {
243
+ const parts = output && Array.isArray(output.parts) ? output.parts : []
244
+ const prompt = parts
245
+ .filter((part) => part && part.type === "text" && typeof part.text === "string")
246
+ .map((part) => part.text)
247
+ .join("\\n")
248
+ .trim()
249
+ if (!prompt) return
250
+ tell({ ...base(input && input.sessionID), hook_event_name: "chat.message", prompt })
251
+ },
252
+
253
+ event: async ({ event }) => {
254
+ // Never allowed to throw: the host calls this hook without awaiting it, so a
255
+ // rejection here is an unhandled rejection in somebody's editor.
256
+ try {
257
+ const type = event && event.type
258
+ const properties = event && event.properties
259
+ if (${JSON.stringify(OPENCODE_ASKED_EVENTS)}.includes(type)) {
260
+ const asked = askedFrom(properties)
261
+ if (asked) void gate(asked)
262
+ return
263
+ }
264
+ if (type === ${JSON.stringify(OPENCODE_REPLIED_EVENT)}) {
265
+ withdraw(repliedID(properties))
266
+ return
267
+ }
268
+ // session.idle is the turn boundary \u2014 OpenCode's equivalent of Claude's
269
+ // Stop, not of its SessionEnd. Nothing else on the stream is relayed: the
270
+ // receipts already arrive through the hooks above, and reporting the same
271
+ // moment twice would double every row on the account.
272
+ if (type === ${JSON.stringify(OPENCODE_IDLE_EVENT)}) {
273
+ tell({
274
+ ...base(properties && properties.sessionID),
275
+ hook_event_name: ${JSON.stringify(OPENCODE_IDLE_EVENT)},
276
+ })
277
+ }
278
+ } catch {}
279
+ },
280
+
281
+ dispose: async () => {
282
+ for (const id of Array.from(waiting.keys())) withdraw(id)
283
+ tools.clear()
284
+ },
285
+ }
286
+ }
287
+ `;
288
+ var openCodePluginModule = () => ({
289
+ marker: OPENCODE_PLUGIN_MARKER,
290
+ source: openCodePluginSource(),
291
+ placeholder: OPENCODE_PLUGIN_PLACEHOLDER,
292
+ sibling: {
293
+ path: OPENCODE_PLUGIN_SIBLING,
294
+ contents: OPENCODE_PLUGIN_SIBLING_CONTENTS
295
+ }
296
+ });
297
+ var renderOpenCodePlugin = (module, argv) => module.source.replace(module.placeholder, JSON.stringify(argv));
298
+ var isOpenCodePluginOurs = (contents) => contents.includes(OPENCODE_PLUGIN_MARKER);
299
+
300
+ // src/opencode-config.ts
301
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
302
+ import { homedir } from "os";
303
+ import { join } from "path";
304
+ var OPENCODE_HOOK_BINARY = "pushary-opencode-hook";
305
+ var openCodeConfigDirFrom = (home, env = process.env) => env.OPENCODE_CONFIG_DIR?.trim() || join(env.XDG_CONFIG_HOME?.trim() || join(home, ".config"), "opencode");
306
+ var openCodeConfigDir = () => openCodeConfigDirFrom(homedir());
307
+ var OPENCODE_PLUGIN_PATH = `${OPENCODE_PLUGIN_DIR}/${OPENCODE_PLUGIN_FILE}`;
308
+ var openCodePluginDir = () => join(openCodeConfigDir(), OPENCODE_PLUGIN_DIR);
309
+ var openCodePluginFile = () => join(openCodePluginDir(), OPENCODE_PLUGIN_FILE);
310
+ var OPENCODE_HOOK_EVENTS = [
311
+ { event: OPENCODE_APPROVAL_EVENT, delivery: "approval", timeout: HOOK_BUDGETS.opencode.budgetSeconds },
312
+ { event: "tool.execute.after", delivery: "telemetry", timeout: 10 },
313
+ { event: "chat.message", delivery: "telemetry", timeout: 10 },
314
+ { event: OPENCODE_IDLE_EVENT, delivery: "telemetry", timeout: 10 }
315
+ ];
316
+ var hasForeignPlugins = (entries) => entries.some((entry) => entry !== OPENCODE_PLUGIN_FILE && /\.(ts|js)$/.test(entry));
317
+ var installOpenCodePlugin = (argv, dir = openCodePluginDir()) => {
318
+ mkdirSync(dir, { recursive: true });
319
+ const plugin = join(dir, OPENCODE_PLUGIN_FILE);
320
+ writeFileSync(plugin, renderOpenCodePlugin(openCodePluginModule(), argv), "utf-8");
321
+ const sibling = join(dir, OPENCODE_PLUGIN_SIBLING);
322
+ if (existsSync(sibling)) return { plugin, wroteSibling: false };
323
+ writeFileSync(sibling, OPENCODE_PLUGIN_SIBLING_CONTENTS, "utf-8");
324
+ return { plugin, wroteSibling: true };
325
+ };
326
+ var openCodeWired = (dir = openCodePluginDir()) => {
327
+ try {
328
+ return isOpenCodePluginOurs(readFileSync(join(dir, OPENCODE_PLUGIN_FILE), "utf-8"));
329
+ } catch {
330
+ return false;
331
+ }
332
+ };
333
+ var OPENCODE_MCP_URL = "https://pushary.com/api/mcp/mcp";
334
+ var OPENCODE_CONFIG_FILE = "opencode.json";
335
+ var openCodeConfigFile = () => join(openCodeConfigDir(), OPENCODE_CONFIG_FILE);
336
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
337
+ var addOpenCodeMcpServer = (config, apiKey) => {
338
+ const servers = asRecord(config.mcp) ?? {};
339
+ servers.pushary = {
340
+ type: "remote",
341
+ url: OPENCODE_MCP_URL,
342
+ enabled: true,
343
+ headers: { Authorization: `Bearer ${apiKey}` }
344
+ };
345
+ config.mcp = servers;
346
+ };
347
+ var removeOpenCodeMcpServer = (config) => {
348
+ const servers = asRecord(config.mcp);
349
+ if (!servers || !("pushary" in servers)) return false;
350
+ delete servers.pushary;
351
+ if (Object.keys(servers).length === 0) delete config.mcp;
352
+ return true;
353
+ };
354
+
355
+ export {
356
+ OPENCODE_PLUGIN_PLACEHOLDER,
357
+ OPENCODE_PLUGIN_SIBLING,
358
+ OPENCODE_PLUGIN_SIBLING_CONTENTS,
359
+ isOpenCodePluginOurs,
360
+ OPENCODE_HOOK_BINARY,
361
+ openCodeConfigDirFrom,
362
+ openCodeConfigDir,
363
+ openCodePluginDir,
364
+ openCodePluginFile,
365
+ hasForeignPlugins,
366
+ installOpenCodePlugin,
367
+ openCodeWired,
368
+ openCodeConfigFile,
369
+ addOpenCodeMcpServer,
370
+ removeOpenCodeMcpServer
371
+ };
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-HJ44HIUW.js";
3
+ } from "../chunk-OMA2OZRE.js";
4
4
  import "../chunk-ATBKJNWS.js";
5
- import "../chunk-7MBDFNUC.js";
5
+ import "../chunk-VIHRP25S.js";
6
6
  import "../chunk-YHG74UFF.js";
7
7
  import {
8
8
  fetchModeOverride,
@@ -12,16 +12,17 @@ import {
12
12
  handlePostToolUse,
13
13
  handleStop,
14
14
  reportEvent
15
- } from "../chunk-V2XPF77V.js";
15
+ } from "../chunk-VOE4SYPR.js";
16
16
  import {
17
17
  askUser,
18
18
  cancelQuestion,
19
19
  waitForAnswer
20
20
  } from "../chunk-J3YDT4HM.js";
21
+ import "../chunk-7J67NYK4.js";
21
22
  import "../chunk-RN3NOEJF.js";
22
23
  import "../chunk-BSZYIAZL.js";
23
24
  import "../chunk-SAF6HGAA.js";
24
- import "../chunk-Y3UTTFTZ.js";
25
+ import "../chunk-KBS53WOE.js";
25
26
  import "../chunk-7QLSKOSU.js";
26
27
  import "../chunk-KZERVKTD.js";
27
28
  import {
@@ -30,7 +31,7 @@ import {
30
31
  } from "../chunk-2UMNXADU.js";
31
32
  import {
32
33
  resolvePolicy
33
- } from "../chunk-7YJS2VTG.js";
34
+ } from "../chunk-DMW54VWK.js";
34
35
  export {
35
36
  askUser,
36
37
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.87.2",
3
+ "version": "0.87.3",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -56,6 +56,7 @@
56
56
  "pushary-codex-hook": "./dist/bin/pushary-codex-hook.js",
57
57
  "pushary-gemini-hook": "./dist/bin/pushary-gemini-hook.js",
58
58
  "pushary-gemini-bridge": "./dist/bin/pushary-gemini-bridge.js",
59
+ "pushary-opencode-hook": "./dist/bin/pushary-opencode-hook.js",
59
60
  "pushary-setup": "./dist/bin/pushary-setup.js",
60
61
  "pushary-clean": "./dist/bin/pushary-clean.js",
61
62
  "pushary-status": "./dist/bin/pushary-status.js",