pushary 1.9.49 → 1.9.51

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 (120) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +0 -1
  3. package/dist/api-PPNFRSJC.js +55 -0
  4. package/dist/bin/pushary-bell-hook.js +28 -25
  5. package/dist/bin/pushary-bell.js +13 -11
  6. package/dist/bin/pushary-claude.js +36 -32
  7. package/dist/bin/pushary-clean.js +68 -42
  8. package/dist/bin/pushary-codex-bridge.js +23 -20
  9. package/dist/bin/pushary-codex-hook.js +35 -31
  10. package/dist/bin/pushary-codex-session.js +17 -616
  11. package/dist/bin/pushary-codex.js +15 -12
  12. package/dist/bin/pushary-connect.js +31 -28
  13. package/dist/bin/pushary-cowork.js +7 -6
  14. package/dist/bin/pushary-daemon.js +55 -52
  15. package/dist/bin/pushary-disconnect.js +29 -26
  16. package/dist/bin/pushary-doctor.js +57 -52
  17. package/dist/bin/pushary-elicitation-hook.js +28 -25
  18. package/dist/bin/pushary-gemini-bridge.js +17 -15
  19. package/dist/bin/pushary-gemini-hook.js +32 -28
  20. package/dist/bin/pushary-hook.js +34 -31
  21. package/dist/bin/pushary-login.js +19 -16
  22. package/dist/bin/pushary-logout.js +30 -27
  23. package/dist/bin/pushary-mcp.js +11 -9
  24. package/dist/bin/pushary-mode.js +16 -14
  25. package/dist/bin/pushary-notification-hook.js +27 -24
  26. package/dist/bin/pushary-opencode-hook.js +31 -27
  27. package/dist/bin/pushary-permission-denied-hook.js +32 -29
  28. package/dist/bin/pushary-permission-hook.js +31 -28
  29. package/dist/bin/pushary-post-hook.js +27 -24
  30. package/dist/bin/pushary-prompt-hook.js +27 -24
  31. package/dist/bin/pushary-session-end-hook.js +27 -24
  32. package/dist/bin/pushary-session-start-hook.js +32 -29
  33. package/dist/bin/pushary-setup.js +145 -82
  34. package/dist/bin/pushary-stats.js +8 -7
  35. package/dist/bin/pushary-status.js +35 -32
  36. package/dist/bin/pushary-stop-hook.js +27 -24
  37. package/dist/bin/pushary-stopfailure-hook.js +27 -24
  38. package/dist/bin/pushary-suggestions.js +16 -14
  39. package/dist/bin/pushary-transcript-register.js +19 -17
  40. package/dist/bin/pushary-transcripts.js +10 -9
  41. package/dist/bin/pushary-upgrade.js +46 -42
  42. package/dist/bin/pushary-wait.js +16 -14
  43. package/dist/bin/pushary.js +10 -8
  44. package/dist/{chunk-IF2UPWD2.js → chunk-26MUPAKS.js} +1 -1
  45. package/dist/{chunk-AWMMABVB.js → chunk-2QPHUCXU.js} +49 -135
  46. package/dist/{chunk-WEY5ZHDB.js → chunk-2YSCVT6Q.js} +5 -5
  47. package/dist/chunk-36NGFHE4.js +72 -0
  48. package/dist/{chunk-25SYEAW4.js → chunk-3ICRY72Q.js} +3 -3
  49. package/dist/{chunk-LZYJMSOY.js → chunk-3OLEGCMU.js} +3 -3
  50. package/dist/{chunk-B7LV6W7U.js → chunk-3RPN7AAT.js} +1 -1
  51. package/dist/{chunk-VCZN7NAB.js → chunk-3YAOJEHG.js} +7 -5
  52. package/dist/{chunk-TGCIFEDE.js → chunk-43NXSBNZ.js} +13 -11
  53. package/dist/{chunk-Y74IJ2IO.js → chunk-4JM2CK2A.js} +4 -4
  54. package/dist/{chunk-KEAIIVW3.js → chunk-4QQEUJL4.js} +2 -2
  55. package/dist/{chunk-XNZB274I.js → chunk-5463UUO2.js} +1 -1
  56. package/dist/{chunk-ZMYLVFXC.js → chunk-5CRQCIHR.js} +14 -9
  57. package/dist/{chunk-KIO2MZQC.js → chunk-6QPRQVQM.js} +5 -3
  58. package/dist/{chunk-4PCJ6DYK.js → chunk-6SWMKF5E.js} +2 -2
  59. package/dist/{chunk-Y7WD72J7.js → chunk-7CLLWDCP.js} +1 -1
  60. package/dist/{chunk-TYTIDEBW.js → chunk-7DBTOPOT.js} +5 -5
  61. package/dist/chunk-7YAVKENV.js +271 -0
  62. package/dist/{chunk-34AQAQKW.js → chunk-AA2K3H7Y.js} +4 -4
  63. package/dist/{chunk-DH4BWFU7.js → chunk-AZHTI5JX.js} +5 -5
  64. package/dist/{chunk-PA54A437.js → chunk-BUXDR6US.js} +1 -1
  65. package/dist/{chunk-VUS6QGND.js → chunk-CQ5JGRML.js} +5 -1
  66. package/dist/{chunk-YVO4HIHX.js → chunk-DT5CACWZ.js} +12 -10
  67. package/dist/{chunk-LLRYP5TR.js → chunk-DT5Y55FI.js} +5 -5
  68. package/dist/{chunk-ZTTCYHOM.js → chunk-DUMZORSL.js} +8 -6
  69. package/dist/{chunk-XTVEW2NG.js → chunk-EGKD4S5H.js} +1 -1
  70. package/dist/{chunk-FPKQGWAF.js → chunk-EJCPHLY2.js} +2 -2
  71. package/dist/{chunk-YTYXFA5M.js → chunk-FI73DGNB.js} +1 -1
  72. package/dist/{chunk-JGNYCRB2.js → chunk-FV34HT35.js} +1 -1
  73. package/dist/{chunk-KK2DIUVV.js → chunk-GCN73COS.js} +1 -1
  74. package/dist/{chunk-SE4JFMLW.js → chunk-GTSI6NG6.js} +8 -6
  75. package/dist/chunk-JBT3ZSJS.js +92 -0
  76. package/dist/chunk-JZDRR7BL.js +427 -0
  77. package/dist/{chunk-KBGKWB5T.js → chunk-K3BIMGP4.js} +4 -2
  78. package/dist/{chunk-LY3OJFBW.js → chunk-KQBXPGXO.js} +2 -1
  79. package/dist/{chunk-OR5F63C2.js → chunk-LF3TLC7B.js} +2 -168
  80. package/dist/{chunk-QYBL4EHD.js → chunk-LXRQLDGW.js} +11 -11
  81. package/dist/{chunk-ZOKMGBPR.js → chunk-MC4VY5LK.js} +16 -5
  82. package/dist/{chunk-OW4DNSQ6.js → chunk-NSY2V7XU.js} +1 -1
  83. package/dist/{chunk-X3RCIOIH.js → chunk-ONCWYACA.js} +1 -1
  84. package/dist/{chunk-EP6S6VY3.js → chunk-PMJEN6CX.js} +6 -6
  85. package/dist/{chunk-K73CBZKW.js → chunk-PTNSIGLB.js} +4 -4
  86. package/dist/chunk-PVDSN6TB.js +729 -0
  87. package/dist/chunk-QTOLNRET.js +169 -0
  88. package/dist/{chunk-APASKZ2I.js → chunk-RLADRNPV.js} +5 -3
  89. package/dist/{chunk-BOVSUH4N.js → chunk-S6RNS4UL.js} +2 -2
  90. package/dist/{chunk-34RXPM5U.js → chunk-SRXBOMSR.js} +3 -3
  91. package/dist/{chunk-HXRH7ZCK.js → chunk-TQ6AGJVH.js} +2 -2
  92. package/dist/{chunk-SALUTWPX.js → chunk-UKX5WIB5.js} +4 -4
  93. package/dist/{chunk-ABMEBN2R.js → chunk-UR7XX2DV.js} +6 -4
  94. package/dist/{chunk-BXK6ZJVJ.js → chunk-UVT6B6XN.js} +2 -2
  95. package/dist/{chunk-5Q2FRMPC.js → chunk-VD2OAF7Q.js} +4 -4
  96. package/dist/{chunk-XH3F66VD.js → chunk-VYBH6D4X.js} +3 -3
  97. package/dist/{chunk-UCF3EZZL.js → chunk-VZRCM362.js} +13 -13
  98. package/dist/{chunk-7RMSB4A2.js → chunk-W26Q2DTD.js} +14 -12
  99. package/dist/{chunk-OWQHE2VV.js → chunk-WVSM3G4C.js} +19 -4
  100. package/dist/{chunk-GDDXGBTM.js → chunk-X5GHIFST.js} +2 -2
  101. package/dist/chunk-XCEN33UP.js +7 -0
  102. package/dist/{chunk-UHVD7BSV.js → chunk-XQHPHJHR.js} +6 -6
  103. package/dist/{chunk-OZLGTMIY.js → chunk-XSUJ2PQY.js} +2 -2
  104. package/dist/{chunk-ZYYQJSIO.js → chunk-YQDZ7ZIL.js} +44 -462
  105. package/dist/{chunk-PXUNPXOX.js → chunk-ZOP3I2GQ.js} +3 -3
  106. package/dist/{chunk-WLT6J3SB.js → chunk-ZXGDBCIH.js} +2 -2
  107. package/dist/codex-app-cards-switch-ZQRT7MXB.js +52 -0
  108. package/dist/codex-desktop-launch-X7UKQ4VR.js +27 -0
  109. package/dist/codex-session-launch-IX33LOQZ.js +331 -0
  110. package/dist/config-HX6ZNOIL.js +14 -0
  111. package/dist/{global-upgrade-AIUSS2YH.js → global-upgrade-DGP2E2NE.js} +8 -6
  112. package/dist/identity-YISWVJ2A.js +15 -0
  113. package/dist/live-capabilities-44KFBOD4.js +53 -0
  114. package/dist/{paths-RO34TYNM.js → paths-VLGX4GJA.js} +4 -2
  115. package/dist/{reapply-KTNPP4XE.js → reapply-35F3WDTB.js} +24 -21
  116. package/dist/src/index.d.ts +3 -3
  117. package/dist/src/index.js +22 -19
  118. package/dist/{upgrade-lock-5UAGXIW7.js → upgrade-lock-LMO3DSSU.js} +5 -3
  119. package/dist/usage-ZC6BKIE3.js +10 -0
  120. package/package.json +1 -1
@@ -0,0 +1,729 @@
1
+ import {
2
+ processGroupHasNothingLeftToSignal
3
+ } from "./chunk-XCEN33UP.js";
4
+ import {
5
+ CODEX_APP_LAUNCH_VARIABLE,
6
+ CODEX_ENGINE_DIRECTORY,
7
+ CODEX_ENGINE_ENTRYPOINT,
8
+ CODEX_ENGINE_MANIFEST,
9
+ CODEX_ENGINE_PENDING_VARIABLE,
10
+ codexEngineEnvironment,
11
+ codexEngineInside,
12
+ launcherIsGone,
13
+ launcherWaitingOnThisProcess,
14
+ tellTheLauncherTheEngineStarted
15
+ } from "./chunk-36NGFHE4.js";
16
+
17
+ // src/codex-desktop-launch.ts
18
+ import { spawn as spawn2, execFileSync } from "child_process";
19
+ import { existsSync } from "fs";
20
+ import { mkdtemp, readFile, rm, writeFile, access } from "fs/promises";
21
+ import { constants } from "fs";
22
+ import { tmpdir } from "os";
23
+ import { join, resolve } from "path";
24
+
25
+ // src/codex-desktop-proxy.ts
26
+ import { spawn } from "child_process";
27
+ import { Transform } from "stream";
28
+
29
+ // src/codex-app-card.ts
30
+ var CODEX_APP_CARD_METHOD = "mcpServer/elicitation/request";
31
+ var CODEX_REQUEST_RESOLVED_METHOD = "serverRequest/resolved";
32
+ var COMPUTER_USE_SERVER = "cua_repl";
33
+ var COMPUTER_USE_CONNECTOR = "computer-use";
34
+ var TOOL_CALL_APPROVAL = "mcp_tool_call";
35
+ var CONVERSATION_GRANT = "session";
36
+ var FORM_MODES = ["form", "openai/form", "openaiForm"];
37
+ var APP_PARAMETER = "app";
38
+ var MAX_IDENTIFIER_LENGTH = 200;
39
+ var MAX_APP_NAME_LENGTH = 100;
40
+ var MAX_CARDS_ANSWERED_ON_THE_PHONE = 64;
41
+ var APP_LINES_A_LATE_REPLY_CAN_FOLLOW = 256;
42
+ var ONE_PRINTABLE_LINE = /^[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+$/u;
43
+ var APP_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
44
+ var RISK_LEVELS = ["low", "medium", "high"];
45
+ var PHONE_ANSWER_YES = "yes";
46
+ var PHONE_ANSWER_NO = "no";
47
+ var ALLOW_THIS_CONVERSATION = { action: "accept", content: {}, _meta: { persist: CONVERSATION_GRANT } };
48
+ var DENY = { action: "decline", content: null, _meta: null };
49
+ var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
50
+ var isRequestId = (value) => typeof value === "string" && value.length > 0 && value.length <= MAX_IDENTIFIER_LENGTH || typeof value === "number" && Number.isSafeInteger(value);
51
+ var isIdentifier = (value) => typeof value === "string" && value.length > 0 && value.length <= MAX_IDENTIFIER_LENGTH;
52
+ var asksForNothingToFillIn = (schema) => isRecord(schema) && isRecord(schema.properties) && Object.keys(schema.properties).length === 0;
53
+ var offersAConversationGrant = (persist) => Array.isArray(persist) && persist.includes(CONVERSATION_GRANT);
54
+ var isTheAppParameter = (entry) => isRecord(entry) && entry.name === APP_PARAMETER;
55
+ var appNameShownBy = (meta) => {
56
+ const shown = Array.isArray(meta.tool_params_display) ? meta.tool_params_display.find(isTheAppParameter) : void 0;
57
+ const name = typeof shown?.value === "string" ? shown.value.trim() : "";
58
+ return name.length > 0 && name.length <= MAX_APP_NAME_LENGTH && ONE_PRINTABLE_LINE.test(name) ? name : null;
59
+ };
60
+ var appIdentifierIn = (meta) => {
61
+ const identifier = isRecord(meta.tool_params) ? meta.tool_params[APP_PARAMETER] : void 0;
62
+ return isIdentifier(identifier) && APP_IDENTIFIER.test(identifier) ? identifier : null;
63
+ };
64
+ var riskIn = (meta) => RISK_LEVELS.find((level) => level === meta.riskLevel) ?? null;
65
+ var wordingCodexShowsFor = (appName) => `Allow Computer Use to use "${appName}"?`;
66
+ var codexAppCardOf = (message) => {
67
+ if (!isRecord(message) || message.method !== CODEX_APP_CARD_METHOD || !isRequestId(message.id)) return null;
68
+ const params = message.params;
69
+ if (!isRecord(params) || !isIdentifier(params.threadId) || params.serverName !== COMPUTER_USE_SERVER) return null;
70
+ if (typeof params.mode !== "string" || !FORM_MODES.includes(params.mode)) return null;
71
+ if (!asksForNothingToFillIn(params.requestedSchema)) return null;
72
+ const meta = params._meta;
73
+ if (!isRecord(meta) || meta.connector_id !== COMPUTER_USE_CONNECTOR || meta.codex_approval_kind !== TOOL_CALL_APPROVAL) return null;
74
+ if (!offersAConversationGrant(meta.persist)) return null;
75
+ const appName = appNameShownBy(meta);
76
+ if (appName === null || params.message !== wordingCodexShowsFor(appName)) return null;
77
+ return {
78
+ requestId: message.id,
79
+ threadId: params.threadId,
80
+ appName,
81
+ appIdentifier: appIdentifierIn(meta),
82
+ risk: riskIn(meta)
83
+ };
84
+ };
85
+ var comesFromComputerUse = (message) => isRecord(message) && message.method === CODEX_APP_CARD_METHOD && isRecord(message.params) && message.params.serverName === COMPUTER_USE_SERVER;
86
+ var codexRequestResolvedBy = (message) => {
87
+ if (!isRecord(message) || message.method !== CODEX_REQUEST_RESOLVED_METHOD || !isRecord(message.params)) return null;
88
+ return isRequestId(message.params.requestId) ? message.params.requestId : null;
89
+ };
90
+ var codexRequestAnsweredBy = (message) => isRecord(message) && !("method" in message) && isRequestId(message.id) ? message.id : null;
91
+ var codexAppCardQuestion = (card) => `Allow Codex to use ${card.appName} for this conversation?`;
92
+ var codexAppCardContext = (card) => [
93
+ card.appIdentifier ? `App: ${card.appName} (${card.appIdentifier})` : `App: ${card.appName}`,
94
+ ...card.risk ? [`Risk Codex reports: ${card.risk}`] : [],
95
+ "Codex is showing the same question on your Mac. Yes allows this app for this conversation only."
96
+ ].join("\n");
97
+ var codexAppCardDecisionOf = (phoneAnswer) => {
98
+ if (phoneAnswer === PHONE_ANSWER_YES) return "allow";
99
+ if (phoneAnswer === PHONE_ANSWER_NO) return "deny";
100
+ return null;
101
+ };
102
+ var codexAppCardReply = (requestId, decision) => ({
103
+ id: requestId,
104
+ result: decision === "allow" ? ALLOW_THIS_CONVERSATION : DENY
105
+ });
106
+ var FORWARD_UNRELATED_REPLY = { kind: "forward", phoneQuestionIsMoot: false };
107
+ var FORWARD_THE_APPS_ANSWER = { kind: "forward", phoneQuestionIsMoot: true };
108
+ var DROP_THE_LATE_REPLY = { kind: "drop" };
109
+ var codexRequestKey = (requestId) => JSON.stringify(requestId);
110
+ var CodexAppCardLedger = class {
111
+ waiting = /* @__PURE__ */ new Set();
112
+ answeredOnThePhone = /* @__PURE__ */ new Map();
113
+ get tracksNothing() {
114
+ return this.waiting.size === 0 && this.answeredOnThePhone.size === 0;
115
+ }
116
+ open(requestId) {
117
+ const key = codexRequestKey(requestId);
118
+ if (this.waiting.has(key) || this.answeredOnThePhone.has(key)) return false;
119
+ this.waiting.add(key);
120
+ return true;
121
+ }
122
+ isWaiting(requestId) {
123
+ return this.waiting.has(codexRequestKey(requestId));
124
+ }
125
+ settleOnThePhone(requestId) {
126
+ const key = codexRequestKey(requestId);
127
+ if (!this.waiting.delete(key)) return false;
128
+ this.answeredOnThePhone.set(key, { appLinesLeft: APP_LINES_A_LATE_REPLY_CAN_FOLLOW, engineConfirmed: false });
129
+ if (this.answeredOnThePhone.size > MAX_CARDS_ANSWERED_ON_THE_PHONE) this.forgetTheOldestAnswer();
130
+ return true;
131
+ }
132
+ leaveWithTheApp(requestId) {
133
+ return this.waiting.delete(codexRequestKey(requestId));
134
+ }
135
+ engineResolved(requestId) {
136
+ const key = codexRequestKey(requestId);
137
+ if (this.waiting.delete(key)) return "phone_question_is_moot";
138
+ const answer = this.answeredOnThePhone.get(key);
139
+ if (!answer) return "unrelated";
140
+ this.answeredOnThePhone.set(key, { ...answer, engineConfirmed: true });
141
+ return "confirmed_the_phone_answer";
142
+ }
143
+ appReplied(requestId) {
144
+ const key = codexRequestKey(requestId);
145
+ const answer = this.answeredOnThePhone.get(key);
146
+ if (answer) {
147
+ this.answeredOnThePhone.delete(key);
148
+ return answer.engineConfirmed ? DROP_THE_LATE_REPLY : FORWARD_UNRELATED_REPLY;
149
+ }
150
+ return this.waiting.delete(key) ? FORWARD_THE_APPS_ANSWER : FORWARD_UNRELATED_REPLY;
151
+ }
152
+ appSentALine() {
153
+ for (const [key, answer] of this.answeredOnThePhone) {
154
+ if (answer.appLinesLeft <= 1) this.answeredOnThePhone.delete(key);
155
+ else this.answeredOnThePhone.set(key, { ...answer, appLinesLeft: answer.appLinesLeft - 1 });
156
+ }
157
+ }
158
+ forgetEverything() {
159
+ this.waiting.clear();
160
+ this.answeredOnThePhone.clear();
161
+ }
162
+ forgetTheOldestAnswer() {
163
+ const oldest = this.answeredOnThePhone.keys().next();
164
+ if (!oldest.done) this.answeredOnThePhone.delete(oldest.value);
165
+ }
166
+ };
167
+
168
+ // src/codex-desktop-proxy.ts
169
+ var APP_SERVER_COMMAND = "app-server";
170
+ var CONFIG_FLAGS = ["-c", "--config"];
171
+ var CONFIG_FLAG_PREFIXES = ["-c=", "--config="];
172
+ var LISTEN_FLAG = "--listen";
173
+ var LISTEN_FLAG_PREFIX = "--listen=";
174
+ var STDIO_LISTENER = "stdio://";
175
+ var NEWLINE = 10;
176
+ var MAX_WATCHED_LINE_BYTES = 256 * 1024;
177
+ var MAX_HELD_LINE_BYTES = 64 * 1024;
178
+ var CARD_MARKER = Buffer.from(`"${CODEX_APP_CARD_METHOD}"`);
179
+ var RESOLVED_MARKER = Buffer.from(`"${CODEX_REQUEST_RESOLVED_METHOD}"`);
180
+ var CODEX_APP_AGENT_NAME = "Codex app";
181
+ var ASK_TIMEOUT_MS = 15e3;
182
+ var MAX_CONSECUTIVE_WAIT_FAILURES = 5;
183
+ var WITHDRAW_TIMEOUT_MS = 4e3;
184
+ var CLOSE_GRACE_MS = 1500;
185
+ var ENGINE_KILL_GRACE_MS = 2e3;
186
+ var ENGINE_EXIT_GRACE_MS = 5e3;
187
+ var QUESTION_IN_AN_UNKNOWN_SHAPE = "Codex asked about Computer Use in a way this version of Pushary does not know, so the question stays in Codex.";
188
+ var LAUNCHER_CHECK_MS = 2e3;
189
+ var USUAL_TIMING = { answerWindowMs: 10 * 6e4, longPollMs: 25e3, pauseBetweenPollsMs: 2e3 };
190
+ var subcommandIndexOf = (args) => {
191
+ let index = 0;
192
+ while (index < args.length) {
193
+ const argument = args[index];
194
+ if (CONFIG_FLAGS.includes(argument)) {
195
+ index += 2;
196
+ continue;
197
+ }
198
+ if (CONFIG_FLAG_PREFIXES.some((prefix) => argument.startsWith(prefix))) {
199
+ index += 1;
200
+ continue;
201
+ }
202
+ return argument.startsWith("-") ? -1 : index;
203
+ }
204
+ return -1;
205
+ };
206
+ var listenersIn = (args) => args.flatMap((argument, index) => {
207
+ if (argument === LISTEN_FLAG) return [args[index + 1] ?? ""];
208
+ return argument.startsWith(LISTEN_FLAG_PREFIX) ? [argument.slice(LISTEN_FLAG_PREFIX.length)] : [];
209
+ });
210
+ var startsAStdioAppServer = (args) => {
211
+ const subcommandAt = subcommandIndexOf(args);
212
+ if (subcommandAt < 0 || args[subcommandAt] !== APP_SERVER_COMMAND) return false;
213
+ return listenersIn(args.slice(subcommandAt + 1)).every((listener) => listener === STDIO_LISTENER);
214
+ };
215
+ var BoundedLineReader = class {
216
+ constructor(limit, onLine) {
217
+ this.limit = limit;
218
+ this.onLine = onLine;
219
+ }
220
+ pieces = [];
221
+ bytes = 0;
222
+ overLimit = false;
223
+ read(chunk) {
224
+ let start = 0;
225
+ while (start < chunk.length) {
226
+ const newlineAt = chunk.indexOf(NEWLINE, start);
227
+ const piece = chunk.subarray(start, newlineAt < 0 ? chunk.length : newlineAt);
228
+ this.keep(piece);
229
+ if (newlineAt < 0) return;
230
+ this.finishLine();
231
+ start = newlineAt + 1;
232
+ }
233
+ }
234
+ keep(piece) {
235
+ if (this.overLimit || piece.length === 0) return;
236
+ if (this.bytes + piece.length > this.limit) {
237
+ this.overLimit = true;
238
+ this.pieces = [];
239
+ this.bytes = 0;
240
+ return;
241
+ }
242
+ this.pieces.push(piece);
243
+ this.bytes += piece.length;
244
+ }
245
+ finishLine() {
246
+ const line = this.overLimit || this.bytes === 0 ? null : this.pieces.length === 1 ? this.pieces[0] : Buffer.concat(this.pieces, this.bytes);
247
+ this.pieces = [];
248
+ this.bytes = 0;
249
+ this.overLimit = false;
250
+ if (line) this.onLine(line);
251
+ }
252
+ };
253
+ var AppToEngineGate = class extends Transform {
254
+ constructor(keeps) {
255
+ super();
256
+ this.keeps = keeps;
257
+ }
258
+ held = [];
259
+ heldBytes = 0;
260
+ passingALongLine = false;
261
+ waitingForALineEnd = [];
262
+ writeBetweenLines(line) {
263
+ if (this.destroyed || this.readableEnded) throw new Error("Codex engine input is closed");
264
+ if (this.passingALongLine) this.waitingForALineEnd.push(line);
265
+ else this.push(line);
266
+ }
267
+ _transform(chunk, _encoding, callback) {
268
+ let start = 0;
269
+ while (start < chunk.length) {
270
+ const newlineAt = chunk.indexOf(NEWLINE, start);
271
+ const end = newlineAt < 0 ? chunk.length : newlineAt + 1;
272
+ this.accept(chunk.subarray(start, end), newlineAt >= 0);
273
+ start = end;
274
+ }
275
+ callback();
276
+ }
277
+ _flush(callback) {
278
+ if (this.heldBytes > 0) this.push(Buffer.concat(this.held, this.heldBytes));
279
+ this.held = [];
280
+ this.heldBytes = 0;
281
+ callback();
282
+ }
283
+ accept(piece, endsALine) {
284
+ if (this.passingALongLine) {
285
+ this.push(piece);
286
+ if (endsALine) this.finishTheLongLine();
287
+ return;
288
+ }
289
+ this.held.push(piece);
290
+ this.heldBytes += piece.length;
291
+ if (endsALine) {
292
+ const line = this.held.length === 1 ? piece : Buffer.concat(this.held, this.heldBytes);
293
+ this.held = [];
294
+ this.heldBytes = 0;
295
+ if (this.keeps(line)) this.push(line);
296
+ return;
297
+ }
298
+ if (this.heldBytes <= MAX_HELD_LINE_BYTES) return;
299
+ this.push(Buffer.concat(this.held, this.heldBytes));
300
+ this.held = [];
301
+ this.heldBytes = 0;
302
+ this.passingALongLine = true;
303
+ }
304
+ finishTheLongLine() {
305
+ this.passingALongLine = false;
306
+ for (const line of this.waitingForALineEnd.splice(0)) this.push(line);
307
+ }
308
+ };
309
+ var parsedLine = (line) => {
310
+ try {
311
+ return JSON.parse(line.toString("utf8"));
312
+ } catch {
313
+ return null;
314
+ }
315
+ };
316
+ var isTheSameAccount = (current, asked) => current !== null && current.apiKey === asked.apiKey && current.baseUrl === asked.baseUrl && current.machineId === asked.machineId;
317
+ var reachesAPersonAwayFromCodex = (phone, asked) => !phone.isAwayPaused(asked) && !phone.isDecisionStopped(asked) && !phone.isLocalQuestionHandoff({ status: asked.status, noDevices: asked.noDevices });
318
+ var pauseUnlessStopped = (ms, signal) => new Promise((resolve2) => {
319
+ if (signal.aborted) {
320
+ resolve2();
321
+ return;
322
+ }
323
+ const finish = () => {
324
+ clearTimeout(timer);
325
+ signal.removeEventListener("abort", finish);
326
+ resolve2();
327
+ };
328
+ const timer = setTimeout(finish, ms);
329
+ signal.addEventListener("abort", finish, { once: true });
330
+ });
331
+ var CodexAppCards = class {
332
+ constructor(options) {
333
+ this.options = options;
334
+ this.timing = options.timing ?? USUAL_TIMING;
335
+ }
336
+ ledger = new CodexAppCardLedger();
337
+ questions = /* @__PURE__ */ new Map();
338
+ phone;
339
+ stoodDown = false;
340
+ saidAQuestionWasUnknown = false;
341
+ timing;
342
+ standDown() {
343
+ this.stoodDown = true;
344
+ this.ledger.forgetEverything();
345
+ void this.close();
346
+ }
347
+ fromTheEngine(line) {
348
+ if (this.stoodDown) return;
349
+ try {
350
+ if (line.includes(CARD_MARKER)) this.questionShown(parsedLine(line));
351
+ else if (line.includes(RESOLVED_MARKER)) this.requestResolved(codexRequestResolvedBy(parsedLine(line)));
352
+ } catch (error) {
353
+ this.options.report(`Codex app card was left with Codex: ${error instanceof Error ? error.message : "unreadable"}`);
354
+ }
355
+ }
356
+ keepsFromTheApp(line) {
357
+ try {
358
+ if (this.stoodDown || this.ledger.tracksNothing) return true;
359
+ const answered = codexRequestAnsweredBy(parsedLine(line));
360
+ if (answered === null) {
361
+ this.ledger.appSentALine();
362
+ return true;
363
+ }
364
+ const handling = this.ledger.appReplied(answered);
365
+ this.ledger.appSentALine();
366
+ if (handling.kind === "drop") return false;
367
+ if (handling.phoneQuestionIsMoot) this.withdraw(answered);
368
+ return true;
369
+ } catch {
370
+ return true;
371
+ }
372
+ }
373
+ async close() {
374
+ const outstanding = [...this.questions.values()];
375
+ this.questions.clear();
376
+ for (const question of outstanding) question.waiting.abort();
377
+ await Promise.allSettled(outstanding.map((question) => this.cancel(question)));
378
+ }
379
+ thePhone() {
380
+ this.phone ??= import("./api-PPNFRSJC.js");
381
+ return this.phone;
382
+ }
383
+ questionShown(message) {
384
+ const card = codexAppCardOf(message);
385
+ if (card) {
386
+ if (this.ledger.open(card.requestId)) void this.askThePhone(card);
387
+ return;
388
+ }
389
+ if (this.saidAQuestionWasUnknown || !comesFromComputerUse(message)) return;
390
+ this.saidAQuestionWasUnknown = true;
391
+ this.options.report(QUESTION_IN_AN_UNKNOWN_SHAPE);
392
+ }
393
+ requestResolved(requestId) {
394
+ if (requestId !== null && this.ledger.engineResolved(requestId) === "phone_question_is_moot") this.withdraw(requestId);
395
+ }
396
+ async askThePhone(card) {
397
+ try {
398
+ const account = await this.options.readAccount();
399
+ if (!account) {
400
+ this.ledger.leaveWithTheApp(card.requestId);
401
+ return;
402
+ }
403
+ const phone = await this.thePhone();
404
+ if (!this.ledger.isWaiting(card.requestId)) return;
405
+ const answerableUntil = Date.now() + this.timing.answerWindowMs;
406
+ const asked = await phone.askUser(account.apiKey, {
407
+ question: codexAppCardQuestion(card),
408
+ type: "confirm",
409
+ context: codexAppCardContext(card),
410
+ agentName: CODEX_APP_AGENT_NAME,
411
+ sessionId: card.threadId,
412
+ machineId: account.machineId,
413
+ waitEndsAt: new Date(answerableUntil).toISOString()
414
+ }, ASK_TIMEOUT_MS, account.baseUrl);
415
+ const question = { account, correlationId: asked.correlationId, waiting: new AbortController(), answerableUntil };
416
+ if (!this.ledger.isWaiting(card.requestId)) {
417
+ await this.cancel(question);
418
+ return;
419
+ }
420
+ if (asked.answered) {
421
+ await this.settle(card, question, asked.value);
422
+ return;
423
+ }
424
+ if (!reachesAPersonAwayFromCodex(phone, asked)) {
425
+ this.ledger.leaveWithTheApp(card.requestId);
426
+ await this.cancel(question);
427
+ return;
428
+ }
429
+ this.questions.set(codexRequestKey(card.requestId), question);
430
+ await this.waitForThePhone(phone, card, question);
431
+ } catch (error) {
432
+ if (this.ledger.leaveWithTheApp(card.requestId)) {
433
+ this.options.report(`Could not ask about ${card.appName} through Pushary; answer in Codex. ${error instanceof Error ? error.message.split("\n")[0] : ""}`.trim());
434
+ }
435
+ }
436
+ }
437
+ async waitForThePhone(phone, card, question) {
438
+ const { account, correlationId } = question;
439
+ const stopped = AbortSignal.any([this.options.signal, question.waiting.signal]);
440
+ let failures = 0;
441
+ while (this.ledger.isWaiting(card.requestId) && !stopped.aborted && failures < MAX_CONSECUTIVE_WAIT_FAILURES) {
442
+ const timeLeft = question.answerableUntil - Date.now();
443
+ if (timeLeft <= 0) break;
444
+ let answer;
445
+ try {
446
+ answer = await phone.waitForAnswer(account.apiKey, correlationId, Math.min(this.timing.longPollMs, timeLeft), stopped, account.baseUrl);
447
+ failures = 0;
448
+ } catch {
449
+ failures += 1;
450
+ await pauseUnlessStopped(this.timing.pauseBetweenPollsMs, stopped);
451
+ continue;
452
+ }
453
+ if (!this.ledger.isWaiting(card.requestId)) return;
454
+ if (answer.answered) {
455
+ await this.settle(card, question, answer.value);
456
+ return;
457
+ }
458
+ if (phone.isDecisionStopped(answer) || answer.status === "expired") {
459
+ this.questions.delete(codexRequestKey(card.requestId));
460
+ this.ledger.leaveWithTheApp(card.requestId);
461
+ return;
462
+ }
463
+ await pauseUnlessStopped(this.timing.pauseBetweenPollsMs, stopped);
464
+ }
465
+ if (this.ledger.leaveWithTheApp(card.requestId)) this.withdraw(card.requestId);
466
+ }
467
+ async settle(card, question, phoneAnswer) {
468
+ this.questions.delete(codexRequestKey(card.requestId));
469
+ const decision = codexAppCardDecisionOf(phoneAnswer);
470
+ if (this.stoodDown) return;
471
+ if (decision === null || !isTheSameAccount(await this.options.readAccount(), question.account)) {
472
+ this.ledger.leaveWithTheApp(card.requestId);
473
+ return;
474
+ }
475
+ if (this.ledger.settleOnThePhone(card.requestId)) this.options.reply(codexAppCardReply(card.requestId, decision));
476
+ }
477
+ withdraw(requestId) {
478
+ const key = codexRequestKey(requestId);
479
+ const question = this.questions.get(key);
480
+ if (!question) return;
481
+ this.questions.delete(key);
482
+ question.waiting.abort();
483
+ void this.cancel(question);
484
+ }
485
+ async cancel(question) {
486
+ try {
487
+ const phone = await this.thePhone();
488
+ await phone.cancelQuestion(question.account.apiKey, question.correlationId, AbortSignal.timeout(WITHDRAW_TIMEOUT_MS), question.account.baseUrl);
489
+ } catch {
490
+ return;
491
+ }
492
+ }
493
+ };
494
+ var watchingTheEngine = (cards) => {
495
+ const lines = new BoundedLineReader(MAX_WATCHED_LINE_BYTES, (line) => cards.fromTheEngine(line));
496
+ return new Transform({
497
+ transform(chunk, _encoding, callback) {
498
+ lines.read(chunk);
499
+ callback(null, chunk);
500
+ }
501
+ });
502
+ };
503
+ var reportOnStderr = (line) => {
504
+ process.stderr.write(`[pushary] ${line}
505
+ `);
506
+ };
507
+ var withinTheCloseGrace = (closing) => Promise.race([closing, new Promise((resolve2) => setTimeout(resolve2, CLOSE_GRACE_MS).unref())]);
508
+ var runCodexDesktopProxy = async (binary, args, readAccount, options = { watchesForCards: true }) => {
509
+ const watches = options.watchesForCards && startsAStdioAppServer(args);
510
+ const child = spawn(binary, [...args], {
511
+ stdio: watches ? ["pipe", "pipe", "inherit"] : "inherit",
512
+ detached: true,
513
+ env: codexEngineEnvironment(binary)
514
+ });
515
+ child.once("spawn", tellTheLauncherTheEngineStarted);
516
+ const abort = new AbortController();
517
+ let killTimer;
518
+ let lingerTimer;
519
+ const signalChild = (signal) => {
520
+ if (!child.pid) return;
521
+ try {
522
+ process.kill(-child.pid, signal);
523
+ } catch (error) {
524
+ if (!processGroupHasNothingLeftToSignal(error)) throw error;
525
+ }
526
+ };
527
+ const stop = () => {
528
+ if (killTimer) return;
529
+ abort.abort();
530
+ signalChild("SIGTERM");
531
+ killTimer = setTimeout(() => signalChild("SIGKILL"), ENGINE_KILL_GRACE_MS);
532
+ };
533
+ const stopIfTheEngineLingers = () => {
534
+ lingerTimer ??= setTimeout(stop, ENGINE_EXIT_GRACE_MS);
535
+ };
536
+ const launcher = launcherWaitingOnThisProcess();
537
+ const launcherCheck = launcher === null ? void 0 : setInterval(() => {
538
+ if (launcherIsGone(launcher)) stop();
539
+ }, LAUNCHER_CHECK_MS);
540
+ launcherCheck?.unref();
541
+ let cards;
542
+ const streams = [];
543
+ const keepCopyingWithoutCards = (error) => {
544
+ cards?.standDown();
545
+ reportOnStderr(`Codex app cards stopped for this session after an unexpected error; Codex keeps working. ${error instanceof Error ? error.message.split("\n")[0] : ""}`.trim());
546
+ };
547
+ if (watches && child.stdin && child.stdout) {
548
+ const gate = new AppToEngineGate((line) => cards?.keepsFromTheApp(line) ?? true);
549
+ cards = new CodexAppCards({
550
+ readAccount,
551
+ reply: (reply) => gate.writeBetweenLines(Buffer.from(`${JSON.stringify(reply)}
552
+ `)),
553
+ signal: abort.signal,
554
+ report: reportOnStderr
555
+ });
556
+ const watcher = watchingTheEngine(cards);
557
+ streams.push(process.stdin, child.stdin, child.stdout, process.stdout, gate, watcher);
558
+ process.stdin.pipe(gate).pipe(child.stdin);
559
+ child.stdout.pipe(watcher).pipe(process.stdout, { end: false });
560
+ process.stdin.once("end", stopIfTheEngineLingers);
561
+ process.on("uncaughtException", keepCopyingWithoutCards);
562
+ process.on("unhandledRejection", keepCopyingWithoutCards);
563
+ }
564
+ for (const stream of streams) stream.on("error", stop);
565
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, stop);
566
+ try {
567
+ return await new Promise((resolve2, reject) => {
568
+ child.once("error", reject);
569
+ child.once("close", (code) => resolve2(code ?? 1));
570
+ });
571
+ } finally {
572
+ abort.abort();
573
+ clearTimeout(killTimer);
574
+ clearTimeout(lingerTimer);
575
+ clearInterval(launcherCheck);
576
+ signalChild("SIGKILL");
577
+ if (watches) {
578
+ process.stdin.unpipe();
579
+ child.stdout?.unpipe();
580
+ process.stdin.pause();
581
+ process.stdin.off("end", stopIfTheEngineLingers);
582
+ }
583
+ process.off("uncaughtException", keepCopyingWithoutCards);
584
+ process.off("unhandledRejection", keepCopyingWithoutCards);
585
+ for (const stream of streams) stream.off("error", stop);
586
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.off(signal, stop);
587
+ if (cards) await withinTheCloseGrace(cards.close());
588
+ }
589
+ };
590
+
591
+ // src/codex-desktop-launch.ts
592
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
593
+ var QUOTE = "'";
594
+ var QUOTE_INSIDE_QUOTES = "'\\''";
595
+ var shellQuoted = (value) => QUOTE + value.replaceAll(QUOTE, QUOTE_INSIDE_QUOTES) + QUOTE;
596
+ var shellUnquoted = (quoted) => quoted.length >= 2 && quoted.startsWith(QUOTE) && quoted.endsWith(QUOTE) ? quoted.slice(1, -1).replaceAll(QUOTE_INSIDE_QUOTES, QUOTE) : null;
597
+ var KNOWN_ENGINE_LAYOUT = 1;
598
+ var ENGINE_COMMAND_THE_PROXY_WATCHES = "app-server";
599
+ var INSTALLED_CODEX_APPS = ["/Applications/ChatGPT.app", "/Applications/Codex.app"];
600
+ var LAUNCHCTL = "/bin/launchctl";
601
+ var APP_CARDS_FLAG = "--app-cards";
602
+ var LAUNCHER_INTERPRETER = "#!/bin/sh";
603
+ var ENGINE_ASSIGNMENT = "ENGINE=";
604
+ var RUN_THE_ENGINE = `${CODEX_APP_LAUNCH_VARIABLE}="$ENGINE" exec "$ENGINE" "$@"`;
605
+ var desktopEngineFromManifest = (app, manifest) => {
606
+ if (!record(manifest) || manifest.layoutVersion !== KNOWN_ENGINE_LAYOUT || manifest.entrypoint !== CODEX_ENGINE_ENTRYPOINT || typeof manifest.version !== "string") {
607
+ throw new Error("This Codex app keeps its engine somewhere Pushary does not know. Launch Codex normally for this version.");
608
+ }
609
+ return codexEngineInside(app);
610
+ };
611
+ var isAbsolutePath = (value) => value.startsWith("/");
612
+ var everyProgramFileIsThere = (entry) => [`[ -x ${shellQuoted(entry[0] ?? "")} ]`, ...entry.slice(1).filter(isAbsolutePath).map((path) => `[ -e ${shellQuoted(path)} ]`)].join(" && ");
613
+ var TURN_THIS_LAUNCH_OFF = [
614
+ ` if [ "$(${LAUNCHCTL} getenv ${CODEX_APP_LAUNCH_VARIABLE})" = "$0" ]; then ${LAUNCHCTL} unsetenv ${CODEX_APP_LAUNCH_VARIABLE}; fi`
615
+ ];
616
+ var desktopProxyScript = (entry, app, engine, options = { turnsItselfOffWithoutTheEngine: false }) => {
617
+ const throughTheProxy = [...entry, "--desktop-proxy", app, "--"].map(shellQuoted).join(" ") + ' "$@"';
618
+ return [
619
+ LAUNCHER_INTERPRETER,
620
+ `${ENGINE_ASSIGNMENT}${shellQuoted(engine)}`,
621
+ 'if [ ! -x "$ENGINE" ]; then',
622
+ ...options.turnsItselfOffWithoutTheEngine ? TURN_THIS_LAUNCH_OFF : [],
623
+ ' echo "[pushary] The Codex engine is not at $ENGINE, so Pushary stepped aside. Open Codex again." >&2',
624
+ " exit 127",
625
+ "fi",
626
+ `if ${everyProgramFileIsThere(entry)}; then`,
627
+ ' for argument in "$@"; do',
628
+ ` if [ "$argument" = ${shellQuoted(ENGINE_COMMAND_THE_PROXY_WATCHES)} ]; then`,
629
+ ' for left in "$0".pending.*; do',
630
+ ' [ -e "$left" ] && ! kill -0 "${left##*.}" 2>/dev/null && rm -f "$left"',
631
+ " done",
632
+ ' PENDING="$0.pending.$$"',
633
+ ` if ! { printf '' > "$PENDING"; } 2>/dev/null; then break; fi`,
634
+ ` ${CODEX_ENGINE_PENDING_VARIABLE}="$PENDING" ${throughTheProxy}`,
635
+ " STATUS=$?",
636
+ ' if [ ! -e "$PENDING" ]; then exit "$STATUS"; fi',
637
+ ' rm -f "$PENDING"',
638
+ ' echo "[pushary] Pushary did not start, so Codex runs without it." >&2',
639
+ " break",
640
+ " fi",
641
+ " done",
642
+ "fi",
643
+ RUN_THE_ENGINE,
644
+ ""
645
+ ].join("\n");
646
+ };
647
+ var codexPassThroughScript = (engine) => [LAUNCHER_INTERPRETER, `${ENGINE_ASSIGNMENT}${shellQuoted(engine)}`, RUN_THE_ENGINE, ""].join("\n");
648
+ var codexEngineBehindLauncher = (script) => {
649
+ const [interpreter, assignment] = script.split("\n", 2);
650
+ if (interpreter !== LAUNCHER_INTERPRETER || !assignment?.startsWith(ENGINE_ASSIGNMENT) || !script.trimEnd().endsWith(RUN_THE_ENGINE)) return null;
651
+ return shellUnquoted(assignment.slice(ENGINE_ASSIGNMENT.length));
652
+ };
653
+ var writeDesktopProxy = async (entry, app, engine, directory) => {
654
+ const shim = join(directory, "codex");
655
+ await writeFile(shim, desktopProxyScript(entry, app, engine), { mode: 448, flag: "wx" });
656
+ return shim;
657
+ };
658
+ var desktopEngine = async (app) => {
659
+ const manifest = JSON.parse(await readFile(join(app, CODEX_ENGINE_DIRECTORY, CODEX_ENGINE_MANIFEST), "utf8"));
660
+ const engine = desktopEngineFromManifest(app, manifest);
661
+ await access(engine, constants.X_OK);
662
+ return engine;
663
+ };
664
+ var engineWhateverTheLayout = async (app) => {
665
+ const engine = codexEngineInside(app);
666
+ await access(engine, constants.X_OK);
667
+ try {
668
+ return { engine, layoutIsKnown: await desktopEngine(app) === engine };
669
+ } catch {
670
+ return { engine, layoutIsKnown: false };
671
+ }
672
+ };
673
+ var installedCodexApp = (chosen) => resolve(chosen ?? INSTALLED_CODEX_APPS.find((candidate) => existsSync(join(candidate, CODEX_ENGINE_DIRECTORY, CODEX_ENGINE_MANIFEST))) ?? INSTALLED_CODEX_APPS[1]);
674
+ var desktopExecutable = (app) => {
675
+ const plist = join(app, "Contents/Info.plist");
676
+ const read = (key) => execFileSync("/usr/bin/plutil", ["-extract", key, "raw", plist], { encoding: "utf8" }).trim();
677
+ if (read("CFBundleIdentifier") !== "com.openai.codex") throw new Error("Choose the Codex Desktop application");
678
+ const name = read("CFBundleExecutable");
679
+ if (!name || name.includes("/") || name === "." || name === "..") throw new Error("Invalid Desktop executable");
680
+ try {
681
+ execFileSync("/usr/bin/pgrep", ["-x", name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")], { stdio: "ignore" });
682
+ } catch (error) {
683
+ if (record(error) && error.status === 1) return join(app, "Contents/MacOS", name);
684
+ throw error;
685
+ }
686
+ throw new Error("Codex Desktop is already running. Finish active work and quit it yourself, then retry. Pushary will not restart it.");
687
+ };
688
+ var codexDesktopCommand = async (args, entry, readAccount) => {
689
+ if (args[0] !== "--desktop" && args[0] !== "--desktop-proxy" && args[0] !== APP_CARDS_FLAG) return null;
690
+ if (process.platform !== "darwin") throw new Error("Experimental Codex Desktop forwarding currently requires macOS");
691
+ if (args[0] === "--desktop-proxy") {
692
+ if (!args[1] || args[2] !== "--") throw new Error("Invalid Desktop proxy invocation");
693
+ const { engine: engine2, layoutIsKnown } = await engineWhateverTheLayout(args[1]);
694
+ return runCodexDesktopProxy(engine2, args.slice(3), readAccount, { watchesForCards: layoutIsKnown });
695
+ }
696
+ if (args[0] === APP_CARDS_FLAG) {
697
+ const { codexAppCardsCommandOnThisMac } = await import("./codex-app-cards-switch-ZQRT7MXB.js");
698
+ return codexAppCardsCommandOnThisMac(args.slice(1), entry, readAccount);
699
+ }
700
+ if (args.length > 2) throw new Error("Usage: pushary codex --desktop [path/to/Codex.app]");
701
+ if (!await readAccount()) throw new Error("Connect Pushary before starting Desktop forwarding");
702
+ const app = installedCodexApp(args[1]);
703
+ const engine = await desktopEngine(app);
704
+ const executable = desktopExecutable(app);
705
+ const directory = await mkdtemp(join(tmpdir(), "pushary-codex-desktop-"));
706
+ try {
707
+ const shim = await writeDesktopProxy(entry, app, engine, directory);
708
+ process.stderr.write("[pushary] Experimental Codex app launch. When Codex asks to use an app on this Mac, the question also reaches Pushary; every other prompt stays as it is. Quit and open Codex normally to turn this off.\n");
709
+ const child = spawn2(executable, [], { stdio: "ignore", detached: true, env: { ...process.env, [CODEX_APP_LAUNCH_VARIABLE]: shim } });
710
+ return await new Promise((resolve2, reject) => {
711
+ child.once("error", reject);
712
+ child.once("close", (code) => resolve2(code ?? 1));
713
+ });
714
+ } finally {
715
+ await rm(directory, { recursive: true, force: true });
716
+ }
717
+ };
718
+
719
+ export {
720
+ shellQuoted,
721
+ desktopEngineFromManifest,
722
+ desktopProxyScript,
723
+ codexPassThroughScript,
724
+ codexEngineBehindLauncher,
725
+ writeDesktopProxy,
726
+ desktopEngine,
727
+ installedCodexApp,
728
+ codexDesktopCommand
729
+ };