@pushary/agent-hooks 0.87.2 → 0.87.4

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 +94 -28
  3. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +81 -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-QBWYNVKY.js → chunk-BFBAHOGR.js} +1 -1
  42. package/dist/{chunk-7YJS2VTG.js → chunk-DMW54VWK.js} +12 -1
  43. package/dist/{chunk-3LKYBFWA.js → chunk-EJL3PJU7.js} +1 -1
  44. package/dist/chunk-GIRT7677.js +11 -0
  45. package/dist/{chunk-SJVLFIYP.js → chunk-H5PIPIKQ.js} +1 -1
  46. package/dist/{chunk-HAUTLCMA.js → chunk-ICERLAE2.js} +1 -1
  47. package/dist/{chunk-ZZE6FN7T.js → chunk-K42RXJPG.js} +52 -1
  48. package/dist/{chunk-Y3UTTFTZ.js → chunk-KBS53WOE.js} +1 -1
  49. package/dist/{chunk-A4I5JGCW.js → chunk-KFAH6KRE.js} +1 -1
  50. package/dist/{chunk-LT2VLPUK.js → chunk-KULUNLRO.js} +18 -2
  51. package/dist/{chunk-B3V2H6SA.js → chunk-NAPRGHZ3.js} +54 -13
  52. package/dist/{chunk-HJ44HIUW.js → chunk-OMA2OZRE.js} +11 -9
  53. package/dist/{chunk-ECEUMWBH.js → chunk-QLMX5LLV.js} +1 -1
  54. package/dist/{chunk-XW5CHAQX.js → chunk-QXZ2CKRK.js} +2 -2
  55. package/dist/{chunk-JAICP7BA.js → chunk-TD3M232T.js} +1 -1
  56. package/dist/{chunk-WLLBFDVY.js → chunk-TV3UFGT7.js} +1 -1
  57. package/dist/{chunk-7MBDFNUC.js → chunk-VIHRP25S.js} +1 -1
  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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  isGatingMoment,
4
4
  recordKeylessMoment
5
- } from "../chunk-7MBDFNUC.js";
5
+ } from "../chunk-VIHRP25S.js";
6
6
  import {
7
7
  readHookInput
8
8
  } from "../chunk-6CUUA7HO.js";
@@ -10,19 +10,19 @@ import {
10
10
  denyReasonFrom,
11
11
  isDeferAnswer
12
12
  } from "../chunk-YHG74UFF.js";
13
+ import {
14
+ canonicalJson
15
+ } from "../chunk-GIRT7677.js";
13
16
  import {
14
17
  CODEX_AGENT,
15
18
  codexAllow,
16
19
  codexDeny,
17
20
  codexPass,
18
- deriveActionBody,
19
- deriveBlocker,
20
- deriveToolTarget,
21
21
  describeApplyPatch,
22
- describeToolCall,
23
22
  fetchModeState,
24
23
  getPolicy,
25
24
  handlePostToolUse,
25
+ handleSessionEnd,
26
26
  handleStop,
27
27
  handleUserPrompt,
28
28
  permissionTimeoutDecision,
@@ -31,11 +31,10 @@ import {
31
31
  readLastPrompt,
32
32
  repoKeyFor,
33
33
  reportEvent,
34
- scopePathFor,
35
34
  toCodexWire,
36
35
  toPolicyLookup,
37
36
  toPolicyLookups
38
- } from "../chunk-V2XPF77V.js";
37
+ } from "../chunk-VOE4SYPR.js";
39
38
  import {
40
39
  DEFAULT_SESSION,
41
40
  askUser,
@@ -44,12 +43,19 @@ import {
44
43
  savePendingQuestion,
45
44
  sendNotification
46
45
  } from "../chunk-J3YDT4HM.js";
46
+ import {
47
+ deriveActionBody,
48
+ deriveBlocker,
49
+ deriveToolTarget,
50
+ describeToolCall,
51
+ scopePathFor
52
+ } from "../chunk-7J67NYK4.js";
47
53
  import {
48
54
  getMachineId
49
55
  } from "../chunk-RN3NOEJF.js";
50
56
  import "../chunk-BSZYIAZL.js";
51
57
  import "../chunk-SAF6HGAA.js";
52
- import "../chunk-Y3UTTFTZ.js";
58
+ import "../chunk-KBS53WOE.js";
53
59
  import "../chunk-7QLSKOSU.js";
54
60
  import "../chunk-KZERVKTD.js";
55
61
  import {
@@ -62,57 +68,107 @@ import {
62
68
  hookWaitClamped,
63
69
  hookWaitDeadline,
64
70
  resolveGate
65
- } from "../chunk-7YJS2VTG.js";
71
+ } from "../chunk-DMW54VWK.js";
66
72
 
67
73
  // bin/pushary-codex-hook.ts
68
- import { basename, join } from "path";
74
+ import { basename } from "path";
75
+
76
+ // src/codex-call-decision.ts
77
+ import { createHash } from "crypto";
78
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
69
79
  import { tmpdir, userInfo } from "os";
70
- import { existsSync, mkdirSync, statSync, unlinkSync, writeFileSync } from "fs";
71
- var START_MS = Date.now();
72
- var APPROVAL_TTL_MS = 10 * 60 * 1e3;
73
- var sanitizeId = (value) => value.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128);
80
+ import { join } from "path";
81
+ var CODEX_DECISION_TTL_MS = 60 * 1e3;
74
82
  var userScope = () => {
75
83
  try {
76
84
  const info = userInfo();
77
85
  const id = typeof info.uid === "number" && info.uid >= 0 ? String(info.uid) : info.username;
78
- return sanitizeId(id) || "default";
86
+ return id.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || "default";
79
87
  } catch {
80
88
  return "default";
81
89
  }
82
90
  };
83
- var APPROVAL_DIR = join(tmpdir(), `pushary-codex-approvals-${userScope()}`);
84
- var markApproved = (toolUseId) => {
85
- if (!toolUseId) return;
91
+ var decisionDir = () => join(tmpdir(), `pushary-codex-decisions-${userScope()}`);
92
+ var decisionFile = (key) => join(decisionDir(), key);
93
+ var PRESENTATION_ONLY_INPUT_KEYS = ["description"];
94
+ var identifyingToolInput = (toolInput) => {
95
+ if (!toolInput) return {};
96
+ const identifying = {};
97
+ for (const [key, value] of Object.entries(toolInput)) {
98
+ if (PRESENTATION_ONLY_INPUT_KEYS.includes(key)) continue;
99
+ identifying[key] = value;
100
+ }
101
+ return identifying;
102
+ };
103
+ var codexCallKey = (identity) => {
104
+ if (!identity.sessionId || !identity.turnId || !identity.toolName) return null;
105
+ const material = canonicalJson([
106
+ identity.sessionId,
107
+ identity.turnId,
108
+ identity.toolName,
109
+ identifyingToolInput(identity.toolInput)
110
+ ]);
111
+ return createHash("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
112
+ };
113
+ var parseDecision = (raw) => {
114
+ try {
115
+ const parsed = JSON.parse(raw);
116
+ if (parsed.kind === "approved") return { kind: "approved" };
117
+ if (parsed.kind === "handled") return { kind: "handled" };
118
+ if (parsed.kind === "denied") {
119
+ return { kind: "denied", reason: typeof parsed.reason === "string" ? parsed.reason : "" };
120
+ }
121
+ return null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ };
126
+ var sweepExpired = (dir, nowMs) => {
86
127
  try {
87
- if (!existsSync(APPROVAL_DIR)) mkdirSync(APPROVAL_DIR, { recursive: true, mode: 448 });
88
- writeFileSync(join(APPROVAL_DIR, sanitizeId(toolUseId)), "", { encoding: "utf-8", mode: 384 });
128
+ for (const name of readdirSync(dir)) {
129
+ const path = join(dir, name);
130
+ try {
131
+ if (nowMs - statSync(path).mtimeMs >= CODEX_DECISION_TTL_MS) rmSync(path, { force: true });
132
+ } catch {
133
+ continue;
134
+ }
135
+ }
89
136
  } catch {
137
+ return;
90
138
  }
91
139
  };
92
- var consumeApproval = (toolUseId) => {
93
- if (!toolUseId) return false;
94
- const path = join(APPROVAL_DIR, sanitizeId(toolUseId));
140
+ var recordCodexCallDecision = (key, decision) => {
141
+ if (!key) return;
95
142
  try {
96
- const fresh = Date.now() - statSync(path).mtimeMs < APPROVAL_TTL_MS;
97
- unlinkSync(path);
98
- return fresh;
143
+ const dir = decisionDir();
144
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
145
+ else sweepExpired(dir, Date.now());
146
+ writeFileSync(decisionFile(key), JSON.stringify(decision), { encoding: "utf-8", mode: 384 });
99
147
  } catch {
100
- return false;
148
+ return;
101
149
  }
102
150
  };
103
- var NOTIFY_TTL_MS = 60 * 1e3;
104
- var claimNotify = (toolUseId) => {
105
- if (!toolUseId) return true;
106
- const path = join(APPROVAL_DIR, `notify-${sanitizeId(toolUseId)}`);
151
+ var consumeCodexCallDecision = (key) => {
152
+ if (!key) return null;
153
+ const path = decisionFile(key);
107
154
  try {
108
- if (existsSync(path) && Date.now() - statSync(path).mtimeMs < NOTIFY_TTL_MS) return false;
109
- if (!existsSync(APPROVAL_DIR)) mkdirSync(APPROVAL_DIR, { recursive: true, mode: 448 });
110
- writeFileSync(path, "", { encoding: "utf-8", mode: 384 });
111
- return true;
155
+ const fresh = Date.now() - statSync(path).mtimeMs < CODEX_DECISION_TTL_MS;
156
+ const raw = readFileSync(path, "utf-8");
157
+ rmSync(path, { force: true });
158
+ return fresh ? parseDecision(raw) : null;
112
159
  } catch {
113
- return true;
160
+ return null;
114
161
  }
115
162
  };
163
+
164
+ // bin/pushary-codex-hook.ts
165
+ var callKeyFor = (input) => codexCallKey({
166
+ sessionId: input.session_id,
167
+ turnId: input.turn_id,
168
+ toolName: input.tool_name,
169
+ toolInput: input.tool_input
170
+ });
171
+ var START_MS = Date.now();
116
172
  var agentNameFor = (input) => `Codex - ${basename(input.cwd ?? process.cwd())}`;
117
173
  var describeFor = (input, lookup) => input.tool_name === "apply_patch" ? describeApplyPatch(input.tool_input?.command) ?? describeToolCall(lookup.tool, lookup.input, "hook") : describeToolCall(lookup.tool, lookup.input, "hook");
118
174
  var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
@@ -184,7 +240,14 @@ var codexGate = async (apiKey, input) => {
184
240
  });
185
241
  return { verdict, lookup };
186
242
  };
243
+ var fromRecordedDecision = (decision) => {
244
+ if (decision.kind === "approved") return codexAllow();
245
+ if (decision.kind === "denied") return codexDeny(decision.reason);
246
+ return codexPass();
247
+ };
187
248
  var decidePermissionRequest = async (input) => {
249
+ const recorded = consumeCodexCallDecision(callKeyFor(input));
250
+ if (recorded) return fromRecordedDecision(recorded);
188
251
  try {
189
252
  const apiKey = getApiKey();
190
253
  const { verdict, lookup } = await codexGate(apiKey, input);
@@ -195,7 +258,7 @@ var decidePermissionRequest = async (input) => {
195
258
  const toolPolicy = verdict.policy;
196
259
  if (toolPolicy.mode === "terminal_only") return codexPass();
197
260
  if (toolPolicy.mode === "notify_only") {
198
- if (claimNotify(input.tool_use_id)) await notifyApprovalNeeded(apiKey, input, lookup);
261
+ await notifyApprovalNeeded(apiKey, input, lookup);
199
262
  return codexPass();
200
263
  }
201
264
  const waitSeconds = toolPolicy.mode === "push_first" ? toolPolicy.pushFirstSeconds : effectiveWaitSeconds(toolPolicy.timeoutAction, toolPolicy.timeoutSeconds, "codex");
@@ -207,22 +270,24 @@ var decidePermissionRequest = async (input) => {
207
270
  }
208
271
  if (answer.answered) {
209
272
  if (isDeferAnswer(answer.value)) return codexPass();
210
- if (answer.value === "yes") {
211
- if (toolPolicy.mode === "push_only") markApproved(input.tool_use_id);
212
- return codexAllow();
213
- }
273
+ if (answer.value === "yes") return codexAllow();
214
274
  return codexDeny(denyReasonFrom(answer.value, answer.note));
215
275
  }
216
276
  savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
217
277
  if (toolPolicy.mode === "push_first") return codexPass();
218
278
  if (clampCut) return codexPass();
219
- const decision = permissionTimeoutDecision(toolPolicy.timeoutAction);
220
- if (decision.kind === "allow") markApproved(input.tool_use_id);
221
- return decision;
279
+ return permissionTimeoutDecision(toolPolicy.timeoutAction);
222
280
  } catch {
223
281
  return codexPass();
224
282
  }
225
283
  };
284
+ var recordPreToolUse = (key, outcome) => {
285
+ recordCodexCallDecision(
286
+ key,
287
+ outcome.kind === "deny" ? { kind: "denied", reason: outcome.reason } : { kind: "handled" }
288
+ );
289
+ return outcome;
290
+ };
226
291
  var decidePreToolUse = async (input) => {
227
292
  let apiKey;
228
293
  try {
@@ -249,30 +314,35 @@ var decidePreToolUse = async (input) => {
249
314
  if (verdict.kind === "deny") return codexDeny(verdict.reason);
250
315
  if (verdict.kind === "defer") return codexPass();
251
316
  const toolPolicy = verdict.policy;
317
+ const callKey = callKeyFor(input);
252
318
  if (toolPolicy.mode === "push_only") {
253
- if (consumeApproval(input.tool_use_id)) return codexPass();
254
319
  let pushed;
255
320
  try {
256
321
  pushed = await pushQuestion(apiKey, input, lookup, effectiveWaitSeconds(toolPolicy.timeoutAction, toolPolicy.timeoutSeconds, "codex"), "push_only");
257
322
  } catch {
258
- return preToolUseTimeoutDecision(toolPolicy.timeoutAction, "Push notification failed, denying per policy");
323
+ return recordPreToolUse(callKey, preToolUseTimeoutDecision(toolPolicy.timeoutAction, "Push notification failed, denying per policy"));
259
324
  }
260
325
  const { answer, correlationId, suppressed, clampCut } = pushed;
261
326
  if (suppressed) {
262
327
  await cancelQuestion(apiKey, correlationId).catch(() => {
263
328
  });
264
- return codexPass();
329
+ return recordPreToolUse(callKey, codexPass());
265
330
  }
266
331
  if (answer.answered) {
267
- if (isDeferAnswer(answer.value)) return codexPass();
268
- return answer.value === "yes" ? codexPass() : codexDeny(denyReasonFrom(answer.value, answer.note));
332
+ if (isDeferAnswer(answer.value)) return recordPreToolUse(callKey, codexPass());
333
+ if (answer.value === "yes") {
334
+ recordCodexCallDecision(callKey, { kind: "approved" });
335
+ return codexPass();
336
+ }
337
+ return recordPreToolUse(callKey, codexDeny(denyReasonFrom(answer.value, answer.note)));
269
338
  }
270
339
  savePendingQuestion(input.session_id || DEFAULT_SESSION, correlationId);
271
- if (clampCut) return codexPass();
272
- return preToolUseTimeoutDecision(toolPolicy.timeoutAction);
340
+ if (clampCut) return recordPreToolUse(callKey, codexPass());
341
+ return recordPreToolUse(callKey, preToolUseTimeoutDecision(toolPolicy.timeoutAction));
273
342
  }
274
343
  if (toolPolicy.mode === "notify_only") {
275
- if (claimNotify(input.tool_use_id)) await notifyApprovalNeeded(apiKey, input, lookup);
344
+ await notifyApprovalNeeded(apiKey, input, lookup);
345
+ recordCodexCallDecision(callKey, { kind: "handled" });
276
346
  return codexPass();
277
347
  }
278
348
  return codexPass();
@@ -336,6 +406,13 @@ var main = async () => {
336
406
  case "SessionStart":
337
407
  await reportSessionStart(input);
338
408
  break;
409
+ case "SessionEnd":
410
+ await handleSessionEnd({
411
+ cwd: input.cwd,
412
+ session_id: input.session_id,
413
+ reason: input.reason
414
+ }, CODEX_AGENT, { reconcile: false, reportAttempts: 1, reportTimeoutMs: 1200 });
415
+ break;
339
416
  default:
340
417
  break;
341
418
  }
@@ -1,23 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reportEvent
4
- } from "../chunk-V2XPF77V.js";
4
+ } from "../chunk-VOE4SYPR.js";
5
5
  import {
6
6
  askUser,
7
7
  waitForAnswer
8
8
  } from "../chunk-J3YDT4HM.js";
9
+ import "../chunk-7J67NYK4.js";
9
10
  import {
10
11
  getMachineId
11
12
  } from "../chunk-RN3NOEJF.js";
12
13
  import "../chunk-BSZYIAZL.js";
13
14
  import "../chunk-SAF6HGAA.js";
14
- import "../chunk-Y3UTTFTZ.js";
15
+ import "../chunk-KBS53WOE.js";
15
16
  import "../chunk-7QLSKOSU.js";
16
17
  import "../chunk-KZERVKTD.js";
17
18
  import {
18
19
  getApiKey
19
20
  } from "../chunk-2UMNXADU.js";
20
- import "../chunk-7YJS2VTG.js";
21
+ import "../chunk-DMW54VWK.js";
21
22
 
22
23
  // bin/pushary-codex.ts
23
24
  import { basename } from "path";
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  printKeyCheck
4
- } from "../chunk-JAICP7BA.js";
4
+ } from "../chunk-TD3M232T.js";
5
5
  import {
6
6
  confirmAppConnection,
7
7
  connectDevice,
8
8
  printConnectInstructions
9
- } from "../chunk-3LKYBFWA.js";
9
+ } from "../chunk-EJL3PJU7.js";
10
10
  import "../chunk-3EGEA4KH.js";
11
11
  import {
12
12
  checkApiKey
13
- } from "../chunk-A4I5JGCW.js";
13
+ } from "../chunk-KFAH6KRE.js";
14
14
  import {
15
15
  createIo
16
16
  } from "../chunk-5RUIFDTP.js";
17
17
  import {
18
18
  readKeySource
19
- } from "../chunk-OT4YD54Q.js";
19
+ } from "../chunk-YX6ZKCC5.js";
20
20
  import {
21
21
  parseFlags
22
22
  } from "../chunk-GMXKITVA.js";
@@ -29,7 +29,7 @@ import "../chunk-DINDL2CF.js";
29
29
  import "../chunk-6MTNS63X.js";
30
30
  import "../chunk-BSZYIAZL.js";
31
31
  import "../chunk-SAF6HGAA.js";
32
- import "../chunk-Y3UTTFTZ.js";
32
+ import "../chunk-KBS53WOE.js";
33
33
  import {
34
34
  UsageError
35
35
  } from "../chunk-7QLSKOSU.js";
@@ -37,7 +37,7 @@ import {
37
37
  EXIT
38
38
  } from "../chunk-KZERVKTD.js";
39
39
  import "../chunk-2UMNXADU.js";
40
- import "../chunk-7YJS2VTG.js";
40
+ import "../chunk-DMW54VWK.js";
41
41
 
42
42
  // bin/pushary-connect.ts
43
43
  exitOnHelpFlag("connect");
@@ -8,7 +8,7 @@ import {
8
8
  acquireDaemonLease,
9
9
  readDaemonStatus,
10
10
  requestDaemonHandoff
11
- } from "../chunk-SJVLFIYP.js";
11
+ } from "../chunk-H5PIPIKQ.js";
12
12
  import {
13
13
  CODEX_BRIDGE_CAPABILITY,
14
14
  CODEX_RESUME_CAPABILITY,
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  findLiveProcessBySpawnId,
19
19
  ownsLiveProcess
20
- } from "../chunk-QBWYNVKY.js";
20
+ } from "../chunk-BFBAHOGR.js";
21
21
  import {
22
22
  spawnClaude
23
23
  } from "../chunk-XHKBHWLX.js";
@@ -26,7 +26,8 @@ import {
26
26
  resolveGlobalPackageVersion,
27
27
  resolvePackageVersionFromBinary
28
28
  } from "../chunk-VT4IVERX.js";
29
- import "../chunk-B3V2H6SA.js";
29
+ import "../chunk-NAPRGHZ3.js";
30
+ import "../chunk-GIRT7677.js";
30
31
  import {
31
32
  getMachineId
32
33
  } from "../chunk-RN3NOEJF.js";
@@ -43,7 +44,7 @@ import {
43
44
  getApiKey,
44
45
  getBaseUrl
45
46
  } from "../chunk-2UMNXADU.js";
46
- import "../chunk-7YJS2VTG.js";
47
+ import "../chunk-DMW54VWK.js";
47
48
 
48
49
  // src/spawn-daemon.ts
49
50
  import { execFileSync, spawn } from "child_process";
@@ -2,12 +2,22 @@
2
2
  import {
3
3
  removeClaudeMcpServers,
4
4
  removePusharySettings
5
- } from "../chunk-5646VKR4.js";
5
+ } from "../chunk-2JYQAW4B.js";
6
+ import {
7
+ OPENCODE_PLUGIN_SIBLING,
8
+ OPENCODE_PLUGIN_SIBLING_CONTENTS,
9
+ hasForeignPlugins,
10
+ isOpenCodePluginOurs,
11
+ openCodeConfigFile,
12
+ openCodePluginDir,
13
+ openCodePluginFile,
14
+ removeOpenCodeMcpServer
15
+ } from "../chunk-ZGC3W23N.js";
6
16
  import {
7
17
  removeGeminiSettings
8
- } from "../chunk-ZZE6FN7T.js";
18
+ } from "../chunk-K42RXJPG.js";
9
19
  import "../chunk-ATBKJNWS.js";
10
- import "../chunk-M7RYIZX6.js";
20
+ import "../chunk-B7ZBHWOW.js";
11
21
  import "../chunk-VT4IVERX.js";
12
22
  import {
13
23
  claudeJson,
@@ -21,21 +31,23 @@ import {
21
31
  geminiSettings,
22
32
  removeCodexHooks,
23
33
  removeCodexSettings
24
- } from "../chunk-B3V2H6SA.js";
34
+ } from "../chunk-NAPRGHZ3.js";
35
+ import "../chunk-GIRT7677.js";
25
36
  import {
26
37
  exitOnHelpFlag
27
38
  } from "../chunk-SQOFGPNX.js";
28
39
  import "../chunk-GOEXZ5QJ.js";
29
40
  import "../chunk-DINDL2CF.js";
30
41
  import "../chunk-KZERVKTD.js";
31
- import "../chunk-7YJS2VTG.js";
42
+ import "../chunk-DMW54VWK.js";
32
43
 
33
44
  // bin/pushary-disconnect.ts
34
- import { readFileSync, writeFileSync } from "fs";
45
+ import { readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
35
46
  import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
36
47
 
37
48
  // src/disconnect.ts
38
- var DISCONNECTABLE_AGENTS = ["claude", "codex", "gemini", "cursor"];
49
+ import { dirname } from "path";
50
+ var DISCONNECTABLE_AGENTS = ["claude", "codex", "gemini", "cursor", "opencode"];
39
51
  var isDisconnectableAgent = (value) => DISCONNECTABLE_AGENTS.includes(value);
40
52
  var removeCursorHooks = (config) => {
41
53
  const hooks = config.hooks;
@@ -85,8 +97,24 @@ var disconnectPlan = (agent) => {
85
97
  { path: cursorUserHooks(), format: "json", remove: removeCursorHooks },
86
98
  { path: cursorUserMcp(), format: "json", remove: removeClaudeMcpServers }
87
99
  ];
100
+ // The plugin itself is a whole file of ours and comes out in
101
+ // `disconnectRemovals` below; the MCP entry is an ordinary edit to a config
102
+ // that is otherwise the person's own.
103
+ case "opencode":
104
+ return [{ path: openCodeConfigFile(), format: "json", remove: removeOpenCodeMcpServer }];
88
105
  }
89
106
  };
107
+ var disconnectRemovals = (agent) => {
108
+ if (agent !== "opencode") return [];
109
+ return [
110
+ { path: openCodePluginFile(), ownedBy: isOpenCodePluginOurs },
111
+ {
112
+ path: `${openCodePluginDir()}/${OPENCODE_PLUGIN_SIBLING}`,
113
+ ownedBy: (contents) => contents === OPENCODE_PLUGIN_SIBLING_CONTENTS,
114
+ onlyIfLast: true
115
+ }
116
+ ];
117
+ };
90
118
  var disconnectAgent = (agent, io) => {
91
119
  const changed = [];
92
120
  const untouched = [];
@@ -109,6 +137,27 @@ var disconnectAgent = (agent, io) => {
109
137
  untouched.push(edit.path);
110
138
  }
111
139
  }
140
+ for (const removal of disconnectRemovals(agent)) {
141
+ const raw = io.read(removal.path);
142
+ if (raw === null) continue;
143
+ if (!removal.ownedBy(raw)) {
144
+ untouched.push(removal.path);
145
+ continue;
146
+ }
147
+ if (removal.onlyIfLast) {
148
+ const entries = io.list?.(dirname(removal.path));
149
+ if (entries === void 0 || hasForeignPlugins(entries)) {
150
+ untouched.push(removal.path);
151
+ continue;
152
+ }
153
+ }
154
+ try {
155
+ io.remove?.(removal.path);
156
+ changed.push(removal.path);
157
+ } catch {
158
+ untouched.push(removal.path);
159
+ }
160
+ }
112
161
  return { agent, changed, untouched };
113
162
  };
114
163
 
@@ -130,7 +179,15 @@ var result = disconnectAgent(requested, {
130
179
  },
131
180
  write: (path, contents) => writeFileSync(path, contents, "utf-8"),
132
181
  parseToml: (raw) => parseTOML(raw),
133
- stringifyToml: (config) => stringifyTOML(config)
182
+ stringifyToml: (config) => stringifyTOML(config),
183
+ remove: (path) => rmSync(path, { force: true }),
184
+ list: (dir) => {
185
+ try {
186
+ return readdirSync(dir);
187
+ } catch {
188
+ return [];
189
+ }
190
+ }
134
191
  });
135
192
  if (result.changed.length === 0) {
136
193
  console.log(`${requested} was not connected.`);