pushary 1.9.44 → 1.9.46

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 (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/bin/pushary-bell-hook.js +25 -26
  4. package/dist/bin/pushary-bell.js +9 -9
  5. package/dist/bin/pushary-claude.js +32 -34
  6. package/dist/bin/pushary-clean.js +30 -31
  7. package/dist/bin/pushary-codex-bridge.js +22 -690
  8. package/dist/bin/pushary-codex-hook.js +32 -34
  9. package/dist/bin/pushary-codex-session.js +439 -42
  10. package/dist/bin/pushary-codex.js +12 -13
  11. package/dist/bin/pushary-connect.js +24 -25
  12. package/dist/bin/pushary-cowork.js +5 -5
  13. package/dist/bin/pushary-daemon.js +42 -43
  14. package/dist/bin/pushary-disconnect.js +24 -25
  15. package/dist/bin/pushary-doctor.js +35 -36
  16. package/dist/bin/pushary-elicitation-hook.js +25 -26
  17. package/dist/bin/pushary-gemini-bridge.js +22 -22
  18. package/dist/bin/pushary-gemini-hook.js +29 -31
  19. package/dist/bin/pushary-hook.js +30 -31
  20. package/dist/bin/pushary-login.js +13 -13
  21. package/dist/bin/pushary-logout.js +24 -25
  22. package/dist/bin/pushary-mcp.js +9 -9
  23. package/dist/bin/pushary-mode.js +12 -12
  24. package/dist/bin/pushary-notification-hook.js +24 -25
  25. package/dist/bin/pushary-opencode-hook.js +28 -30
  26. package/dist/bin/pushary-permission-denied-hook.js +29 -30
  27. package/dist/bin/pushary-permission-hook.js +28 -29
  28. package/dist/bin/pushary-post-hook.js +24 -25
  29. package/dist/bin/pushary-prompt-hook.js +24 -25
  30. package/dist/bin/pushary-session-end-hook.js +24 -25
  31. package/dist/bin/pushary-session-start-hook.js +29 -30
  32. package/dist/bin/pushary-setup.js +37 -38
  33. package/dist/bin/pushary-stats.js +5 -5
  34. package/dist/bin/pushary-status.js +27 -28
  35. package/dist/bin/pushary-stop-hook.js +24 -25
  36. package/dist/bin/pushary-stopfailure-hook.js +24 -25
  37. package/dist/bin/pushary-suggestions.js +12 -12
  38. package/dist/bin/pushary-transcript-register.js +17 -17
  39. package/dist/bin/pushary-transcripts.js +7 -7
  40. package/dist/bin/pushary-upgrade.js +31 -32
  41. package/dist/bin/pushary-wait.js +12 -12
  42. package/dist/bin/pushary.js +6 -6
  43. package/dist/{chunk-KYLY6UMN.js → chunk-2IFTZZ5K.js} +1 -1
  44. package/dist/{chunk-L2ZM7LXN.js → chunk-2PTJHEX4.js} +2 -2
  45. package/dist/{chunk-3IRC3XFG.js → chunk-2UABJ75E.js} +2 -2
  46. package/dist/{chunk-MRQJCIFO.js → chunk-2VNHP2OE.js} +1 -1
  47. package/dist/{chunk-TKTS3SIM.js → chunk-2X3PPRNC.js} +3 -3
  48. package/dist/{chunk-E76QCZIA.js → chunk-2ZD53EQW.js} +2 -2
  49. package/dist/{chunk-SI2NC6UU.js → chunk-35TUEZ5E.js} +3 -3
  50. package/dist/{chunk-7UM4NCUI.js → chunk-5NXD6ZHQ.js} +2 -2
  51. package/dist/{chunk-42CVAOC5.js → chunk-5O6GB64K.js} +2 -2
  52. package/dist/{chunk-OWQOMGBF.js → chunk-7XSIUY5F.js} +1 -1
  53. package/dist/{chunk-4WBAHVBK.js → chunk-BKJP2MQN.js} +6 -6
  54. package/dist/{chunk-W2TA4EVZ.js → chunk-CA6F44OF.js} +7 -7
  55. package/dist/{chunk-DD6IBKEJ.js → chunk-CJKEU4EZ.js} +4 -4
  56. package/dist/{chunk-JNU3LV47.js → chunk-EDSXF7NV.js} +25 -4
  57. package/dist/{chunk-75M7NFFC.js → chunk-EKDSF3IX.js} +1 -1
  58. package/dist/{chunk-PQAFH4DF.js → chunk-ELLN4XAF.js} +2 -2
  59. package/dist/{chunk-T4CKP47U.js → chunk-EP4MD6TH.js} +5 -5
  60. package/dist/{chunk-4JHEZ2PV.js → chunk-EW52AGOV.js} +4 -4
  61. package/dist/chunk-FMDGUCER.js +866 -0
  62. package/dist/{chunk-53UZN7JW.js → chunk-FQW7AV6U.js} +3 -3
  63. package/dist/{chunk-XPQDBTW4.js → chunk-FQWI6XQG.js} +1 -1
  64. package/dist/{chunk-MIBCRQWK.js → chunk-G227SH2O.js} +2 -2
  65. package/dist/{chunk-OR6KJOHE.js → chunk-GG6C4CHH.js} +2 -2
  66. package/dist/{chunk-2NVXFWQY.js → chunk-GQZF6DPL.js} +1 -1
  67. package/dist/{chunk-FI2F5KZJ.js → chunk-HTQPDODV.js} +4 -4
  68. package/dist/{chunk-SWDPCDLT.js → chunk-HX34VBPE.js} +4 -4
  69. package/dist/{chunk-OZ7MLVDM.js → chunk-I5EQ5NJD.js} +8 -8
  70. package/dist/{chunk-IWHDNMM3.js → chunk-JPGY5LSF.js} +5 -5
  71. package/dist/{chunk-S2ALW446.js → chunk-KJNWVAS6.js} +3 -3
  72. package/dist/{chunk-TJZUQRMH.js → chunk-LA4S2KIM.js} +3 -3
  73. package/dist/{chunk-RGBTKOCC.js → chunk-LEMQA4HM.js} +5 -5
  74. package/dist/{chunk-UC4PNLMK.js → chunk-LNO25UX4.js} +6 -6
  75. package/dist/{chunk-CESLE4UN.js → chunk-MBB4XJIN.js} +7 -7
  76. package/dist/{chunk-UJXLVH66.js → chunk-MOUUDZOZ.js} +1 -1
  77. package/dist/{chunk-6EV3B5CD.js → chunk-NO3ADUD4.js} +1 -1
  78. package/dist/{chunk-KILK6SWZ.js → chunk-NULG2ZUG.js} +4 -4
  79. package/dist/{chunk-CAJCCQ7N.js → chunk-NWZZTHWF.js} +1 -1
  80. package/dist/{chunk-FKDZU6YX.js → chunk-OI6YNNI5.js} +4 -4
  81. package/dist/{chunk-CEZTYTBN.js → chunk-ON2WCBIN.js} +11 -13
  82. package/dist/{chunk-ALY7ZBNB.js → chunk-P2FHZ65U.js} +1 -1
  83. package/dist/{chunk-UY5E2UUW.js → chunk-PGULTXDJ.js} +5 -5
  84. package/dist/{chunk-GRAPJKDK.js → chunk-PZPYL65I.js} +1 -1
  85. package/dist/{chunk-FYNMFSIZ.js → chunk-SEKQAYVP.js} +2 -2
  86. package/dist/{chunk-GBCR2FH2.js → chunk-T623UG5T.js} +3 -3
  87. package/dist/{chunk-OSMNLNPF.js → chunk-UCRG5QGN.js} +1 -1
  88. package/dist/{chunk-GOEIW2VH.js → chunk-UGXRDY3O.js} +182 -39
  89. package/dist/{chunk-PGHN5D4N.js → chunk-UI3WDOV3.js} +2 -2
  90. package/dist/{chunk-WMO2XVG4.js → chunk-ULEX43F7.js} +1 -1
  91. package/dist/{chunk-LEHBWBCG.js → chunk-UP2RNIHL.js} +4 -4
  92. package/dist/{chunk-UZVF6MP2.js → chunk-VTF7LKDB.js} +5 -5
  93. package/dist/{chunk-M2L7625S.js → chunk-W7MXYDGP.js} +2 -2
  94. package/dist/{chunk-T3KMJI4W.js → chunk-WN3MDHAM.js} +2 -2
  95. package/dist/{chunk-BKK3JCTD.js → chunk-WYD4PYTL.js} +12 -14
  96. package/dist/{chunk-TWATOPHK.js → chunk-XAMFOUXA.js} +2 -2
  97. package/dist/{chunk-WBD7HZEZ.js → chunk-ZGGSQTCO.js} +1 -1
  98. package/dist/{chunk-HV4TEKNP.js → chunk-ZYS4UHEC.js} +4 -4
  99. package/dist/{global-upgrade-QSSDYZCZ.js → global-upgrade-A7EADTOL.js} +6 -6
  100. package/dist/{paths-55DDFLY4.js → paths-UIPHTZH4.js} +2 -2
  101. package/dist/{reapply-MEU66ALO.js → reapply-JAT4DS6G.js} +21 -22
  102. package/dist/src/index.js +19 -20
  103. package/dist/{upgrade-lock-YLXAU4AD.js → upgrade-lock-YUSXU5I5.js} +3 -3
  104. package/package.json +1 -1
  105. package/dist/chunk-5ZSBQHCM.js +0 -161
  106. package/dist/chunk-SVCT5D7K.js +0 -196
@@ -1,36 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  exitOnHelpFlag
4
- } from "../chunk-OR6KJOHE.js";
4
+ } from "../chunk-GG6C4CHH.js";
5
5
  import {
6
- CodexNativeApprovals
7
- } from "../chunk-SVCT5D7K.js";
8
- import "../chunk-PQAFH4DF.js";
6
+ CodexNativeApprovals,
7
+ answerCodexElicitation,
8
+ answerCodexQuestions,
9
+ claimOwnedQuestion,
10
+ closesOwnedQuestion,
11
+ codexQuestionOwner,
12
+ planCodexElicitation,
13
+ reportOwnedQuestion
14
+ } from "../chunk-FMDGUCER.js";
15
+ import "../chunk-ELLN4XAF.js";
9
16
  import {
10
17
  resolveProviderBinary
11
- } from "../chunk-L2ZM7LXN.js";
18
+ } from "../chunk-2PTJHEX4.js";
12
19
  import "../chunk-RIVNNBEK.js";
13
- import "../chunk-75M7NFFC.js";
14
- import "../chunk-MRQJCIFO.js";
20
+ import "../chunk-UI3WDOV3.js";
21
+ import "../chunk-EKDSF3IX.js";
22
+ import "../chunk-2VNHP2OE.js";
23
+ import "../chunk-XHKBHWLX.js";
15
24
  import "../chunk-6ICPEJY5.js";
16
- import "../chunk-GOEIW2VH.js";
25
+ import {
26
+ writeDegraded
27
+ } from "../chunk-UGXRDY3O.js";
17
28
  import "../chunk-POFBE6ZY.js";
18
- import "../chunk-53UZN7JW.js";
19
- import "../chunk-TKTS3SIM.js";
20
- import "../chunk-S2ALW446.js";
29
+ import "../chunk-FQW7AV6U.js";
30
+ import "../chunk-2X3PPRNC.js";
31
+ import "../chunk-KJNWVAS6.js";
21
32
  import {
22
33
  getMachineId
23
- } from "../chunk-WMO2XVG4.js";
34
+ } from "../chunk-ULEX43F7.js";
24
35
  import "../chunk-7QLSKOSU.js";
25
36
  import "../chunk-KZERVKTD.js";
26
- import "../chunk-4JHEZ2PV.js";
37
+ import "../chunk-EW52AGOV.js";
27
38
  import "../chunk-AQFEP65J.js";
28
39
  import {
29
40
  getApiKey,
30
41
  getBaseUrl
31
- } from "../chunk-OWQOMGBF.js";
32
- import "../chunk-T3KMJI4W.js";
33
- import "../chunk-JNU3LV47.js";
42
+ } from "../chunk-7XSIUY5F.js";
43
+ import "../chunk-WN3MDHAM.js";
44
+ import {
45
+ CODEX_NATIVE_APPROVAL_METHODS,
46
+ canonicalJsonText
47
+ } from "../chunk-EDSXF7NV.js";
34
48
 
35
49
  // src/codex-session-launch.ts
36
50
  import { spawn as spawn2 } from "child_process";
@@ -44,6 +58,95 @@ import { createServer } from "http";
44
58
  import { tmpdir } from "os";
45
59
  import { join } from "path";
46
60
  import WebSocket, { WebSocketServer } from "ws";
61
+
62
+ // src/codex-shared-questions.ts
63
+ import { createHash, randomUUID } from "crypto";
64
+ var CodexSharedQuestions = class {
65
+ constructor(options) {
66
+ this.options = options;
67
+ }
68
+ generation = randomUUID();
69
+ pending = /* @__PURE__ */ new Map();
70
+ retired = /* @__PURE__ */ new Set();
71
+ tasks = /* @__PURE__ */ new Set();
72
+ closed = false;
73
+ handle(message, threadId) {
74
+ if (this.closed || !message.method) return;
75
+ const params = message.params ?? {};
76
+ if (message.id === void 0) {
77
+ for (const [id2, pending2] of this.pending) {
78
+ const closed = pending2.dispatch ? closesOwnedQuestion(pending2.dispatch.owner, message.method, params) : message.method === "serverRequest/resolved" && params.requestId === id2 || params.threadId === pending2.threadId && ["thread/closed", "thread/archived", "turn/completed"].includes(message.method);
79
+ if (closed) {
80
+ pending2.controller.abort();
81
+ this.pending.delete(id2);
82
+ this.retired.add(id2);
83
+ if (pending2.dispatch) {
84
+ const task2 = reportOwnedQuestion(this.options, pending2.dispatch, "request_closed").finally(() => this.tasks.delete(task2));
85
+ this.tasks.add(task2);
86
+ }
87
+ }
88
+ }
89
+ return;
90
+ }
91
+ const id = message.id;
92
+ if (typeof id !== "string" && !(typeof id === "number" && Number.isSafeInteger(id))) return;
93
+ if (this.retired.has(id)) return;
94
+ const subjectHash = createHash("sha256").update(canonicalJsonText(params)).digest("hex");
95
+ const existing = this.pending.get(id);
96
+ if (existing) {
97
+ if (existing.subjectHash !== subjectHash) {
98
+ existing.controller.abort();
99
+ this.pending.delete(id);
100
+ this.retired.add(id);
101
+ }
102
+ return;
103
+ }
104
+ if (this.pending.size >= 8) return;
105
+ const isQuestion = message.method === "item/tool/requestUserInput";
106
+ if (!isQuestion && message.method !== "mcpServer/elicitation/request") return;
107
+ const owner = isQuestion ? codexQuestionOwner(params, id, { machineId: this.options.machineId, generation: this.generation, threadId }) : null;
108
+ if (isQuestion ? !owner : !planCodexElicitation(params, threadId)) return;
109
+ const duration = params.autoResolutionMs;
110
+ if (duration != null && (typeof duration !== "number" || !Number.isFinite(duration) || duration < 0)) return;
111
+ const dispatch = owner ? { owner, correlationIds: [], deadline: typeof duration === "number" ? Date.now() + duration : null } : void 0;
112
+ const pending = { controller: new AbortController(), dispatch, threadId, subjectHash };
113
+ this.pending.set(id, pending);
114
+ const task = this.answer(id, params, pending).finally(() => this.tasks.delete(task));
115
+ this.tasks.add(task);
116
+ }
117
+ async answer(id, params, pending) {
118
+ const signal = AbortSignal.any([this.options.signal, pending.controller.signal]);
119
+ const options = { ...this.options, shared: true, signal };
120
+ const dispatch = pending.dispatch;
121
+ try {
122
+ const result = dispatch ? await answerCodexQuestions(options, params, signal, "request_user_input", dispatch) : await answerCodexElicitation(options, params, pending.threadId, signal);
123
+ if (signal.aborted || this.closed || "action" in result && result.action !== "accept") return;
124
+ if (dispatch) await claimOwnedQuestion(options, dispatch);
125
+ if (signal.aborted || this.closed || this.pending.get(id) !== pending) return;
126
+ if (dispatch?.deadline != null && Date.now() >= dispatch.deadline) throw new Error("Question expired");
127
+ this.options.reply(id, result);
128
+ if (dispatch) await reportOwnedQuestion(options, dispatch, "reply_queued");
129
+ } catch {
130
+ if (!signal.aborted) writeDegraded("questions", { reason: "shared_codex_question_unavailable", fallback: "native client", event: "question" });
131
+ } finally {
132
+ if (dispatch && (!dispatch.claimId || signal.aborted)) await reportOwnedQuestion(options, dispatch, "unavailable");
133
+ if (!dispatch?.claimId || signal.aborted) {
134
+ this.pending.delete(id);
135
+ this.retired.add(id);
136
+ }
137
+ }
138
+ }
139
+ async close() {
140
+ this.closed = true;
141
+ for (const pending of this.pending.values()) pending.controller.abort();
142
+ await Promise.allSettled(this.tasks);
143
+ await Promise.allSettled([...this.pending.values()].flatMap((pending) => pending.dispatch ? [reportOwnedQuestion(this.options, pending.dispatch, "unavailable")] : []));
144
+ this.pending.clear();
145
+ }
146
+ };
147
+
148
+ // src/codex-shared-session.ts
149
+ var GROUP_HAS_NOTHING_LEFT_TO_SIGNAL = /* @__PURE__ */ new Set(["ESRCH", "EPERM"]);
47
150
  var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
48
151
  var requestId = (value) => typeof value === "string" || typeof value === "number" && Number.isSafeInteger(value);
49
152
  var MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
@@ -66,14 +169,14 @@ var startCodexSharedSession = async (options) => {
66
169
  detached: true
67
170
  });
68
171
  child.stderr.resume();
69
- const exited = new Promise((resolve2) => {
172
+ const exited = new Promise((resolve3) => {
70
173
  child.once("exit", () => {
71
174
  controller.abort(new Error("Codex app-server stopped"));
72
- resolve2();
175
+ resolve3();
73
176
  });
74
177
  child.once("error", () => {
75
178
  controller.abort(new Error("Could not start Codex app-server"));
76
- resolve2();
179
+ resolve3();
77
180
  });
78
181
  });
79
182
  let terminal = null;
@@ -84,16 +187,20 @@ var startCodexSharedSession = async (options) => {
84
187
  if (signal.aborted || child.stdin.destroyed) throw new Error("Codex connection closed");
85
188
  child.stdin.write(JSON.stringify(message) + "\n");
86
189
  };
87
- const approvals = new CodexNativeApprovals({ ...options, signal, shared: true, reply: (id, result) => {
190
+ const reply = (id, result) => {
88
191
  if (!unanswered.delete(id)) throw new Error("Native request already answered");
89
192
  write({ id, result });
90
- } });
193
+ };
194
+ const approvals = new CodexNativeApprovals({ ...options, signal, shared: true, reply });
195
+ const questions = new CodexSharedQuestions({ ...options, signal, reply });
91
196
  const observeNative = (message) => {
92
197
  if (typeof message.method !== "string") return;
93
198
  if (requestId(message.id)) unanswered.add(message.id);
94
199
  if (!record(message.params)) return;
95
200
  if (message.method === "serverRequest/resolved" && requestId(message.params.requestId)) unanswered.delete(message.params.requestId);
96
- approvals.handle({ id: message.id, method: message.method, params: message.params }, typeof message.params.threadId === "string" ? message.params.threadId : null);
201
+ const event = { id: message.id, method: message.method, params: message.params };
202
+ const threadId = typeof message.params.threadId === "string" ? message.params.threadId : null;
203
+ if (!approvals.handle(event, threadId)) questions.handle(event, threadId);
97
204
  };
98
205
  child.stdout.setEncoding("utf8");
99
206
  child.stdout.on("data", (chunk) => {
@@ -135,6 +242,7 @@ var startCodexSharedSession = async (options) => {
135
242
  if (requestId(message.id) && !("method" in message)) {
136
243
  if (!unanswered.delete(message.id)) return;
137
244
  approvals.handle({ method: "serverRequest/resolved", params: { requestId: message.id } }, null);
245
+ questions.handle({ method: "serverRequest/resolved", params: { requestId: message.id } }, null);
138
246
  }
139
247
  write(message);
140
248
  } catch {
@@ -148,14 +256,14 @@ var startCodexSharedSession = async (options) => {
148
256
  process.kill(-child.pid, signal2);
149
257
  return true;
150
258
  } catch (error) {
151
- if (record(error) && error.code === "ESRCH") return false;
259
+ if (record(error) && typeof error.code === "string" && GROUP_HAS_NOTHING_LEFT_TO_SIGNAL.has(error.code)) return false;
152
260
  throw error;
153
261
  }
154
262
  };
155
263
  const stopServer = async () => {
156
264
  signalServerGroup("SIGTERM");
157
265
  const deadline = Date.now() + 2e3;
158
- while (signalServerGroup(0) && Date.now() < deadline) await new Promise((resolve2) => setTimeout(resolve2, 25));
266
+ while (signalServerGroup(0) && Date.now() < deadline) await new Promise((resolve3) => setTimeout(resolve3, 25));
159
267
  if (signalServerGroup(0)) signalServerGroup("SIGKILL");
160
268
  await exited;
161
269
  };
@@ -167,7 +275,7 @@ var startCodexSharedSession = async (options) => {
167
275
  websocket.close();
168
276
  http.close();
169
277
  try {
170
- await Promise.all([approvals.close(), stopServer()]);
278
+ await Promise.all([approvals.close(), questions.close(), stopServer()]);
171
279
  } finally {
172
280
  await rm(directory, { recursive: true, force: true });
173
281
  }
@@ -178,9 +286,9 @@ var startCodexSharedSession = async (options) => {
178
286
  void close();
179
287
  }, { once: true });
180
288
  try {
181
- await new Promise((resolve2, reject) => {
289
+ await new Promise((resolve3, reject) => {
182
290
  http.once("error", reject);
183
- http.listen(socketPath, resolve2);
291
+ http.listen(socketPath, resolve3);
184
292
  });
185
293
  signal.throwIfAborted();
186
294
  return { endpoint: "unix://" + socketPath, signal, close };
@@ -200,7 +308,7 @@ var launchCodexSession = async (options) => {
200
308
  if (!(await stat(options.cwd)).isDirectory()) throw new Error("Codex needs a project directory");
201
309
  const session = await startCodexSharedSession(options);
202
310
  try {
203
- process.stderr.write("[pushary] Managed Codex session. Supported native permissions can reach Pushary; other prompts stay in this terminal.\n");
311
+ process.stderr.write("[pushary] Managed Codex session. Supported questions and native permissions can reach Pushary; other prompts stay in this terminal.\n");
204
312
  const tui = spawn2(options.binary, ["--remote", session.endpoint], {
205
313
  cwd: options.cwd,
206
314
  env: { ...options.env ?? process.env, PUSHARY_CODEX_BRIDGE: "1" },
@@ -212,9 +320,9 @@ var launchCodexSession = async (options) => {
212
320
  session.signal.addEventListener("abort", stop, { once: true });
213
321
  if (session.signal.aborted) stop();
214
322
  try {
215
- return await new Promise((resolve2, reject) => {
323
+ return await new Promise((resolve3, reject) => {
216
324
  tui.once("error", reject);
217
- tui.once("close", (code) => resolve2(code ?? 1));
325
+ tui.once("close", (code) => resolve3(code ?? 1));
218
326
  });
219
327
  } finally {
220
328
  session.signal.removeEventListener("abort", stop);
@@ -224,25 +332,314 @@ var launchCodexSession = async (options) => {
224
332
  }
225
333
  };
226
334
 
335
+ // src/codex-desktop-launch.ts
336
+ import { spawn as spawn4, execFileSync } from "child_process";
337
+ import { existsSync } from "fs";
338
+ import { mkdtemp as mkdtemp2, readFile, rm as rm2, writeFile, access } from "fs/promises";
339
+ import { constants } from "fs";
340
+ import { tmpdir as tmpdir2 } from "os";
341
+ import { join as join2, resolve as resolve2 } from "path";
342
+
343
+ // src/codex-desktop-proxy.ts
344
+ import { spawn as spawn3 } from "child_process";
345
+ import { isAbsolute } from "path";
346
+ import { PassThrough, Transform } from "stream";
347
+ var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
348
+ var requestId2 = (value) => typeof value === "string" && value.length > 0 || typeof value === "number" && Number.isSafeInteger(value);
349
+ var THREAD_METHODS = ["thread/start", "thread/resume", "thread/fork", "thread/read"];
350
+ var MAX_MESSAGE_BYTES2 = 16 * 1024 * 1024;
351
+ var MAX_CONTEXTS = 1024;
352
+ var CodexDesktopApprovals = class {
353
+ threads = /* @__PURE__ */ new Map();
354
+ requests = /* @__PURE__ */ new Map();
355
+ answered = /* @__PURE__ */ new Map();
356
+ approvals;
357
+ closing;
358
+ constructor(options) {
359
+ this.approvals = new CodexNativeApprovals({ ...options, cwd: "/", shared: true, reply: (id, result) => {
360
+ if (this.closing || this.answered.get(id) !== false) throw new Error("Desktop already resolved this permission");
361
+ this.answered.set(id, true);
362
+ options.reply({ id, result });
363
+ } });
364
+ }
365
+ client(message) {
366
+ if (requestId2(message.id) && !("method" in message)) {
367
+ if (this.answered.get(message.id) === true) return false;
368
+ if (this.answered.has(message.id)) {
369
+ this.answered.set(message.id, true);
370
+ this.approvals.handle({ method: "serverRequest/resolved", params: { requestId: message.id } }, null);
371
+ }
372
+ }
373
+ if (this.closing) return true;
374
+ if (requestId2(message.id) && typeof message.method === "string" && (THREAD_METHODS.includes(message.method) || message.method === "turn/start")) {
375
+ if (this.requests.size >= MAX_CONTEXTS) {
376
+ void this.close();
377
+ return true;
378
+ }
379
+ this.requests.set(message.id, message);
380
+ const p = message.params;
381
+ if (message.method === "turn/start" && record2(p) && typeof p.threadId === "string" && p.cwd != null) this.forgetThread(p.threadId);
382
+ }
383
+ return true;
384
+ }
385
+ server(message) {
386
+ if (this.closing) return;
387
+ if (requestId2(message.id) && !("method" in message)) {
388
+ const request = this.requests.get(message.id);
389
+ this.requests.delete(message.id);
390
+ if (request && record2(message.result)) {
391
+ this.rememberThread(message.result.thread);
392
+ const p2 = request.params;
393
+ if (request.method === "turn/start" && record2(p2)) this.rememberThread({ id: p2.threadId, cwd: p2.cwd });
394
+ }
395
+ return;
396
+ }
397
+ if (typeof message.method !== "string" || !record2(message.params)) return;
398
+ const p = message.params;
399
+ if (message.method === "thread/started") this.rememberThread(p.thread);
400
+ if (message.method === "thread/closed" || message.method === "thread/archived") {
401
+ if (typeof p.threadId === "string") this.forgetThread(p.threadId);
402
+ }
403
+ if (message.method === "serverRequest/resolved" && requestId2(p.requestId) && this.answered.has(p.requestId)) this.answered.set(p.requestId, true);
404
+ const threadId = typeof p.threadId === "string" ? p.threadId : null;
405
+ const cwd = threadId ? this.threads.get(threadId) : void 0;
406
+ if (message.id !== void 0) {
407
+ if (!cwd || !requestId2(message.id) || !CODEX_NATIVE_APPROVAL_METHODS.some((method) => method === message.method)) return;
408
+ if (this.answered.has(message.id)) return;
409
+ if (this.answered.size >= 1e4) {
410
+ void this.close();
411
+ return;
412
+ }
413
+ this.answered.set(message.id, false);
414
+ }
415
+ this.approvals.handle({ id: message.id, method: message.method, params: p }, threadId, cwd);
416
+ }
417
+ close() {
418
+ if (!this.closing) this.closing = this.approvals.close();
419
+ return this.closing;
420
+ }
421
+ forgetThread(id) {
422
+ this.threads.delete(id);
423
+ this.approvals.handle({ method: "thread/closed", params: { threadId: id } }, id);
424
+ }
425
+ rememberThread(thread) {
426
+ if (!record2(thread) || typeof thread.id !== "string" || typeof thread.cwd !== "string" || !isAbsolute(thread.cwd)) return;
427
+ if (this.threads.has(thread.id) && this.threads.get(thread.id) !== thread.cwd) this.forgetThread(thread.id);
428
+ if (this.threads.size >= MAX_CONTEXTS && !this.threads.has(thread.id)) return;
429
+ this.threads.set(thread.id, thread.cwd);
430
+ }
431
+ };
432
+ var codexProtocolLines = (observe, unavailable) => {
433
+ let buffered = Buffer.alloc(0), passthrough = false;
434
+ const forward = (stream, line) => {
435
+ try {
436
+ const message = JSON.parse(line.toString("utf8"));
437
+ if (!record2(message)) throw new Error("Unknown Codex protocol message");
438
+ if (observe(message)) stream.push(line);
439
+ } catch {
440
+ unavailable();
441
+ passthrough = true;
442
+ stream.push(line);
443
+ }
444
+ };
445
+ return new Transform({
446
+ transform(chunk, _encoding, callback) {
447
+ if (passthrough) {
448
+ this.push(chunk);
449
+ callback();
450
+ return;
451
+ }
452
+ buffered = Buffer.concat([buffered, chunk]);
453
+ if (buffered.length > MAX_MESSAGE_BYTES2) {
454
+ unavailable();
455
+ passthrough = true;
456
+ this.push(buffered);
457
+ buffered = Buffer.alloc(0);
458
+ callback();
459
+ return;
460
+ }
461
+ let end;
462
+ while ((end = buffered.indexOf(10)) >= 0) {
463
+ const line = buffered.subarray(0, end + 1);
464
+ buffered = buffered.subarray(end + 1);
465
+ if (passthrough || !line.toString("utf8").trim()) this.push(line);
466
+ else forward(this, line);
467
+ }
468
+ callback();
469
+ },
470
+ flush(callback) {
471
+ if (buffered.length) {
472
+ if (passthrough) this.push(buffered);
473
+ else forward(this, buffered);
474
+ }
475
+ callback();
476
+ }
477
+ });
478
+ };
479
+ var runCodexDesktopProxy = async (binary, args2, readAccount) => {
480
+ const stdioServer = args2[0] === "app-server" && !args2.some((arg) => arg === "--listen" || arg.startsWith("--listen="));
481
+ const child = spawn3(binary, [...args2], {
482
+ stdio: ["pipe", "pipe", "inherit"],
483
+ detached: true,
484
+ env: { ...process.env, CODEX_CLI_PATH: binary }
485
+ });
486
+ const account = stdioServer ? readAccount() : null;
487
+ const abort = new AbortController();
488
+ let approvals;
489
+ let timer;
490
+ let killTimer;
491
+ const unavailable = () => {
492
+ abort.abort();
493
+ clearInterval(timer);
494
+ void approvals?.close();
495
+ process.stderr.write("[pushary] Desktop forwarding unavailable; answer permissions in Codex.\n");
496
+ };
497
+ if (account && stdioServer) {
498
+ approvals = new CodexDesktopApprovals({ ...account, signal: abort.signal, reply: (message) => {
499
+ if (child.stdin.destroyed) throw new Error("Codex engine stopped");
500
+ child.stdin.write(JSON.stringify(message) + "\n");
501
+ } });
502
+ timer = setInterval(() => {
503
+ const current = readAccount();
504
+ if (!current || current.apiKey !== account.apiKey || current.baseUrl !== account.baseUrl || current.machineId !== account.machineId) unavailable();
505
+ }, 1e3);
506
+ }
507
+ const signalChild = (signal) => {
508
+ if (!child.pid) return;
509
+ try {
510
+ process.kill(-child.pid, signal);
511
+ } catch (error) {
512
+ if (!record2(error) || error.code !== "ESRCH") throw error;
513
+ }
514
+ };
515
+ const stop = () => {
516
+ if (killTimer) return;
517
+ abort.abort();
518
+ signalChild("SIGTERM");
519
+ killTimer = setTimeout(() => signalChild("SIGKILL"), 2e3);
520
+ };
521
+ const input = stdioServer ? codexProtocolLines((message) => approvals?.client(message) ?? true, unavailable) : new PassThrough();
522
+ const output = stdioServer ? codexProtocolLines((message) => {
523
+ approvals?.server(message);
524
+ return true;
525
+ }, unavailable) : new PassThrough();
526
+ process.stdin.pipe(input).pipe(child.stdin);
527
+ child.stdout.pipe(output).pipe(process.stdout, { end: false });
528
+ if (stdioServer) process.stdin.once("end", stop);
529
+ for (const stream of [process.stdin, input, child.stdin, child.stdout, output, process.stdout]) stream.on("error", stop);
530
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, stop);
531
+ try {
532
+ return await new Promise((resolve3, reject) => {
533
+ child.once("error", reject);
534
+ child.once("close", (code) => resolve3(code ?? 1));
535
+ });
536
+ } finally {
537
+ abort.abort();
538
+ clearInterval(timer);
539
+ clearTimeout(killTimer);
540
+ signalChild("SIGKILL");
541
+ process.stdin.unpipe(input);
542
+ child.stdout.unpipe(output);
543
+ output.unpipe(process.stdout);
544
+ process.stdin.pause();
545
+ process.stdin.off("end", stop);
546
+ for (const stream of [process.stdin, input, child.stdin, child.stdout, output, process.stdout]) stream.off("error", stop);
547
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.off(signal, stop);
548
+ await approvals?.close();
549
+ }
550
+ };
551
+
552
+ // src/codex-desktop-launch.ts
553
+ var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
554
+ var quote = (value) => "'" + value.replace(/'/g, "'\\''") + "'";
555
+ var TESTED_ENGINE_VERSION = "0.162.0-alpha.2";
556
+ var desktopEngineFromManifest = (app, manifest) => {
557
+ if (!record3(manifest) || manifest.layoutVersion !== 1 || manifest.entrypoint !== "bin/codex" || manifest.version !== TESTED_ENGINE_VERSION) {
558
+ throw new Error(`Experimental Desktop forwarding requires bundled Codex ${TESTED_ENGINE_VERSION}. Launch Codex normally for this version.`);
559
+ }
560
+ return join2(app, "Contents/Resources/codex-cli/bin/codex");
561
+ };
562
+ var writeDesktopProxy = async (entry, app, directory) => {
563
+ const shim = join2(directory, "codex");
564
+ await writeFile(shim, "#!/bin/sh\nexec " + [...entry, "--desktop-proxy", app, "--"].map(quote).join(" ") + ' "$@"\n', { mode: 448, flag: "wx" });
565
+ return shim;
566
+ };
567
+ var desktopEngine = async (app) => {
568
+ const manifest = JSON.parse(await readFile(join2(app, "Contents/Resources/codex-cli/codex-package.json"), "utf8"));
569
+ const engine = desktopEngineFromManifest(app, manifest);
570
+ await access(engine, constants.X_OK);
571
+ return engine;
572
+ };
573
+ var desktopExecutable = (app) => {
574
+ const plist = join2(app, "Contents/Info.plist");
575
+ const read = (key) => execFileSync("/usr/bin/plutil", ["-extract", key, "raw", plist], { encoding: "utf8" }).trim();
576
+ if (read("CFBundleIdentifier") !== "com.openai.codex") throw new Error("Choose the Codex Desktop application");
577
+ const name = read("CFBundleExecutable");
578
+ if (!name || name.includes("/") || name === "." || name === "..") throw new Error("Invalid Desktop executable");
579
+ try {
580
+ execFileSync("/usr/bin/pgrep", ["-x", name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")], { stdio: "ignore" });
581
+ } catch (error) {
582
+ if (record3(error) && error.status === 1) return join2(app, "Contents/MacOS", name);
583
+ throw error;
584
+ }
585
+ throw new Error("Codex Desktop is already running. Finish active work and quit it yourself, then retry. Pushary will not restart it.");
586
+ };
587
+ var codexDesktopCommand = async (args2, entry, readAccount) => {
588
+ if (args2[0] !== "--desktop" && args2[0] !== "--desktop-proxy") return null;
589
+ if (process.platform !== "darwin") throw new Error("Experimental Codex Desktop forwarding currently requires macOS");
590
+ if (args2[0] === "--desktop-proxy") {
591
+ if (!args2[1] || args2[2] !== "--") throw new Error("Invalid Desktop proxy invocation");
592
+ return runCodexDesktopProxy(await desktopEngine(args2[1]), args2.slice(3), readAccount);
593
+ }
594
+ if (args2.length > 2) throw new Error("Usage: pushary codex --desktop [path/to/Codex.app]");
595
+ if (!readAccount()) throw new Error("Connect Pushary before starting Desktop forwarding");
596
+ const app = resolve2(args2[1] ?? ["/Applications/ChatGPT.app", "/Applications/Codex.app"].find((candidate) => existsSync(join2(candidate, "Contents/Resources/codex-cli/codex-package.json"))) ?? "/Applications/Codex.app");
597
+ await desktopEngine(app);
598
+ const executable = desktopExecutable(app);
599
+ const directory = await mkdtemp2(join2(tmpdir2(), "pushary-codex-desktop-"));
600
+ try {
601
+ const shim = await writeDesktopProxy(entry, app, directory);
602
+ process.stderr.write("[pushary] Experimental Desktop launch. Supported approvals can reach Pushary; other prompts stay in Codex. Quit and open Codex normally to disable.\n");
603
+ const child = spawn4(executable, [], { stdio: "ignore", detached: true, env: { ...process.env, CODEX_CLI_PATH: shim } });
604
+ return await new Promise((resolve3, reject) => {
605
+ child.once("error", reject);
606
+ child.once("close", (code) => resolve3(code ?? 1));
607
+ });
608
+ } finally {
609
+ await rm2(directory, { recursive: true, force: true });
610
+ }
611
+ };
612
+
227
613
  // bin/pushary-codex-session.ts
228
- exitOnHelpFlag("codex");
229
614
  var args = process.argv.slice(2);
230
615
  if (args[0] === "codex") args.shift();
616
+ if (args[0] !== "--desktop-proxy") exitOnHelpFlag("codex");
617
+ var desktopAccount = () => {
618
+ try {
619
+ return { apiKey: getApiKey(), baseUrl: getBaseUrl(), machineId: getMachineId() };
620
+ } catch {
621
+ return null;
622
+ }
623
+ };
231
624
  {
232
625
  const abort = new AbortController();
233
626
  const stop = () => abort.abort();
234
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, stop);
627
+ if (!args[0]?.startsWith("--desktop")) for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, stop);
235
628
  try {
236
- const binary = resolveProviderBinary("codex");
237
- if (!binary) throw new Error("Install Codex first, then run pushary codex");
238
- process.exitCode = await launchCodexSession({
239
- binary,
240
- cwd: codexSessionDirectory(args, process.cwd()),
241
- apiKey: getApiKey(),
242
- baseUrl: getBaseUrl(),
243
- machineId: getMachineId(),
244
- signal: abort.signal
245
- });
629
+ const desktop = await codexDesktopCommand(args, [process.execPath, ...process.execArgv, process.argv[1], "codex"], desktopAccount);
630
+ if (desktop !== null) process.exitCode = desktop;
631
+ else {
632
+ const binary = resolveProviderBinary("codex");
633
+ if (!binary) throw new Error("Install Codex first, then run pushary codex");
634
+ process.exitCode = await launchCodexSession({
635
+ binary,
636
+ cwd: codexSessionDirectory(args, process.cwd()),
637
+ apiKey: getApiKey(),
638
+ baseUrl: getBaseUrl(),
639
+ machineId: getMachineId(),
640
+ signal: abort.signal
641
+ });
642
+ }
246
643
  } catch (error) {
247
644
  process.stderr.write(`[pushary] ${error instanceof Error ? error.message : "Codex session failed"}
248
645
  `);
@@ -1,33 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reportEvent
4
- } from "../chunk-BKK3JCTD.js";
5
- import "../chunk-CAJCCQ7N.js";
4
+ } from "../chunk-WYD4PYTL.js";
5
+ import "../chunk-NWZZTHWF.js";
6
+ import "../chunk-W7MXYDGP.js";
6
7
  import {
7
8
  askUser,
8
9
  isDecisionStopped,
9
10
  waitForAnswer
10
- } from "../chunk-5ZSBQHCM.js";
11
- import "../chunk-M2L7625S.js";
12
- import "../chunk-GOEIW2VH.js";
11
+ } from "../chunk-UGXRDY3O.js";
13
12
  import "../chunk-POFBE6ZY.js";
14
- import "../chunk-53UZN7JW.js";
15
- import "../chunk-TKTS3SIM.js";
16
- import "../chunk-S2ALW446.js";
13
+ import "../chunk-FQW7AV6U.js";
14
+ import "../chunk-2X3PPRNC.js";
15
+ import "../chunk-KJNWVAS6.js";
17
16
  import {
18
17
  getMachineId
19
- } from "../chunk-WMO2XVG4.js";
18
+ } from "../chunk-ULEX43F7.js";
20
19
  import "../chunk-7QLSKOSU.js";
21
20
  import "../chunk-KZERVKTD.js";
22
- import "../chunk-4JHEZ2PV.js";
21
+ import "../chunk-EW52AGOV.js";
23
22
  import "../chunk-AQFEP65J.js";
24
23
  import {
25
24
  getApiKey
26
- } from "../chunk-OWQOMGBF.js";
25
+ } from "../chunk-7XSIUY5F.js";
27
26
  import {
28
27
  NPX_LATEST_COMMAND
29
- } from "../chunk-T3KMJI4W.js";
30
- import "../chunk-JNU3LV47.js";
28
+ } from "../chunk-WN3MDHAM.js";
29
+ import "../chunk-EDSXF7NV.js";
31
30
 
32
31
  // bin/pushary-codex.ts
33
32
  import { basename } from "path";