agent-embassy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/CONTRIBUTING.md +122 -0
  3. package/LICENSE +21 -0
  4. package/README.md +185 -0
  5. package/README.zh-CN.md +185 -0
  6. package/SECURITY.md +224 -0
  7. package/assets/live-dashboard/app.css +1610 -0
  8. package/assets/mark-seal.svg +7 -0
  9. package/assets/mark.svg +7 -0
  10. package/assets/social-preview.png +0 -0
  11. package/assets/vendor/react/LICENSE +21 -0
  12. package/assets/vendor/react/react-dom.production.min.js +267 -0
  13. package/assets/vendor/react/react.production.min.js +31 -0
  14. package/dist/src/errors.d.ts +5 -0
  15. package/dist/src/errors.js +11 -0
  16. package/dist/src/errors.js.map +1 -0
  17. package/dist/src/gateway/claude-compatibility-scratch.d.ts +18 -0
  18. package/dist/src/gateway/claude-compatibility-scratch.js +158 -0
  19. package/dist/src/gateway/claude-compatibility-scratch.js.map +1 -0
  20. package/dist/src/gateway/claude-helper-client.d.ts +43 -0
  21. package/dist/src/gateway/claude-helper-client.js +212 -0
  22. package/dist/src/gateway/claude-helper-client.js.map +1 -0
  23. package/dist/src/gateway/claude-helper-protocol.d.ts +172 -0
  24. package/dist/src/gateway/claude-helper-protocol.js +373 -0
  25. package/dist/src/gateway/claude-helper-protocol.js.map +1 -0
  26. package/dist/src/gateway/claude-helper-supervisor.d.ts +74 -0
  27. package/dist/src/gateway/claude-helper-supervisor.js +497 -0
  28. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -0
  29. package/dist/src/gateway/claude-helper.d.ts +1 -0
  30. package/dist/src/gateway/claude-helper.js +269 -0
  31. package/dist/src/gateway/claude-helper.js.map +1 -0
  32. package/dist/src/gateway/claude-peer.d.ts +273 -0
  33. package/dist/src/gateway/claude-peer.js +2359 -0
  34. package/dist/src/gateway/claude-peer.js.map +1 -0
  35. package/dist/src/gateway/claude-runtime.d.ts +40 -0
  36. package/dist/src/gateway/claude-runtime.js +302 -0
  37. package/dist/src/gateway/claude-runtime.js.map +1 -0
  38. package/dist/src/gateway/cli-copy.d.ts +8 -0
  39. package/dist/src/gateway/cli-copy.en.d.ts +13 -0
  40. package/dist/src/gateway/cli-copy.en.js +43 -0
  41. package/dist/src/gateway/cli-copy.en.js.map +1 -0
  42. package/dist/src/gateway/cli-copy.js +19 -0
  43. package/dist/src/gateway/cli-copy.js.map +1 -0
  44. package/dist/src/gateway/cli-copy.zh-CN.d.ts +13 -0
  45. package/dist/src/gateway/cli-copy.zh-CN.js +43 -0
  46. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -0
  47. package/dist/src/gateway/cli.d.ts +44 -0
  48. package/dist/src/gateway/cli.js +969 -0
  49. package/dist/src/gateway/cli.js.map +1 -0
  50. package/dist/src/gateway/codex-app-server.d.ts +310 -0
  51. package/dist/src/gateway/codex-app-server.js +1746 -0
  52. package/dist/src/gateway/codex-app-server.js.map +1 -0
  53. package/dist/src/gateway/codex-local-transport.d.ts +81 -0
  54. package/dist/src/gateway/codex-local-transport.js +617 -0
  55. package/dist/src/gateway/codex-local-transport.js.map +1 -0
  56. package/dist/src/gateway/codex-registration-generation.d.ts +5 -0
  57. package/dist/src/gateway/codex-registration-generation.js +19 -0
  58. package/dist/src/gateway/codex-registration-generation.js.map +1 -0
  59. package/dist/src/gateway/codex-registration-succession.d.ts +209 -0
  60. package/dist/src/gateway/codex-registration-succession.js +588 -0
  61. package/dist/src/gateway/codex-registration-succession.js.map +1 -0
  62. package/dist/src/gateway/compatibility.d.ts +70 -0
  63. package/dist/src/gateway/compatibility.js +252 -0
  64. package/dist/src/gateway/compatibility.js.map +1 -0
  65. package/dist/src/gateway/config.d.ts +24 -0
  66. package/dist/src/gateway/config.js +130 -0
  67. package/dist/src/gateway/config.js.map +1 -0
  68. package/dist/src/gateway/control.d.ts +308 -0
  69. package/dist/src/gateway/control.js +1573 -0
  70. package/dist/src/gateway/control.js.map +1 -0
  71. package/dist/src/gateway/dashboard-copy.d.ts +7 -0
  72. package/dist/src/gateway/dashboard-copy.en.d.ts +506 -0
  73. package/dist/src/gateway/dashboard-copy.en.js +507 -0
  74. package/dist/src/gateway/dashboard-copy.en.js.map +1 -0
  75. package/dist/src/gateway/dashboard-copy.js +516 -0
  76. package/dist/src/gateway/dashboard-copy.js.map +1 -0
  77. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +506 -0
  78. package/dist/src/gateway/dashboard-copy.zh-CN.js +507 -0
  79. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -0
  80. package/dist/src/gateway/dashboard-model.d.ts +207 -0
  81. package/dist/src/gateway/dashboard-model.js +720 -0
  82. package/dist/src/gateway/dashboard-model.js.map +1 -0
  83. package/dist/src/gateway/dashboard.d.ts +26 -0
  84. package/dist/src/gateway/dashboard.js +941 -0
  85. package/dist/src/gateway/dashboard.js.map +1 -0
  86. package/dist/src/gateway/delivery-machine.d.ts +235 -0
  87. package/dist/src/gateway/delivery-machine.js +540 -0
  88. package/dist/src/gateway/delivery-machine.js.map +1 -0
  89. package/dist/src/gateway/instance-lease.d.ts +21 -0
  90. package/dist/src/gateway/instance-lease.js +549 -0
  91. package/dist/src/gateway/instance-lease.js.map +1 -0
  92. package/dist/src/gateway/live-dashboard-app/app.js +2914 -0
  93. package/dist/src/gateway/live-dashboard-assets.d.ts +10 -0
  94. package/dist/src/gateway/live-dashboard-assets.js +73 -0
  95. package/dist/src/gateway/live-dashboard-assets.js.map +1 -0
  96. package/dist/src/gateway/live-dashboard-bootstrap.d.ts +36 -0
  97. package/dist/src/gateway/live-dashboard-bootstrap.js +176 -0
  98. package/dist/src/gateway/live-dashboard-bootstrap.js.map +1 -0
  99. package/dist/src/gateway/live-dashboard-command.d.ts +54 -0
  100. package/dist/src/gateway/live-dashboard-command.js +324 -0
  101. package/dist/src/gateway/live-dashboard-command.js.map +1 -0
  102. package/dist/src/gateway/live-dashboard-http.d.ts +41 -0
  103. package/dist/src/gateway/live-dashboard-http.js +445 -0
  104. package/dist/src/gateway/live-dashboard-http.js.map +1 -0
  105. package/dist/src/gateway/live-dashboard-protocol.d.ts +38 -0
  106. package/dist/src/gateway/live-dashboard-protocol.js +156 -0
  107. package/dist/src/gateway/live-dashboard-protocol.js.map +1 -0
  108. package/dist/src/gateway/live-dashboard-server.d.ts +33 -0
  109. package/dist/src/gateway/live-dashboard-server.js +130 -0
  110. package/dist/src/gateway/live-dashboard-server.js.map +1 -0
  111. package/dist/src/gateway/live-dashboard-stream.d.ts +46 -0
  112. package/dist/src/gateway/live-dashboard-stream.js +233 -0
  113. package/dist/src/gateway/live-dashboard-stream.js.map +1 -0
  114. package/dist/src/gateway/live-dashboard.d.ts +31 -0
  115. package/dist/src/gateway/live-dashboard.js +190 -0
  116. package/dist/src/gateway/live-dashboard.js.map +1 -0
  117. package/dist/src/gateway/locale.d.ts +4 -0
  118. package/dist/src/gateway/locale.js +10 -0
  119. package/dist/src/gateway/locale.js.map +1 -0
  120. package/dist/src/gateway/progress-watch-machine.d.ts +97 -0
  121. package/dist/src/gateway/progress-watch-machine.js +172 -0
  122. package/dist/src/gateway/progress-watch-machine.js.map +1 -0
  123. package/dist/src/gateway/providers.d.ts +287 -0
  124. package/dist/src/gateway/providers.js +2451 -0
  125. package/dist/src/gateway/providers.js.map +1 -0
  126. package/dist/src/gateway/server.d.ts +57 -0
  127. package/dist/src/gateway/server.js +264 -0
  128. package/dist/src/gateway/server.js.map +1 -0
  129. package/dist/src/gateway/service.d.ts +449 -0
  130. package/dist/src/gateway/service.js +4831 -0
  131. package/dist/src/gateway/service.js.map +1 -0
  132. package/dist/src/gateway/store.d.ts +340 -0
  133. package/dist/src/gateway/store.js +4047 -0
  134. package/dist/src/gateway/store.js.map +1 -0
  135. package/dist/src/gateway/types.d.ts +514 -0
  136. package/dist/src/gateway/types.js +414 -0
  137. package/dist/src/gateway/types.js.map +1 -0
  138. package/dist/src/mutex.d.ts +4 -0
  139. package/dist/src/mutex.js +23 -0
  140. package/dist/src/mutex.js.map +1 -0
  141. package/docs/DESIGN.md +134 -0
  142. package/docs/GATEWAY-ARCHITECTURE.md +858 -0
  143. package/package.json +80 -0
  144. package/skills/embassy-peer/SKILL.md +198 -0
  145. package/skills/embassy-peer/agents/openai.yaml +4 -0
@@ -0,0 +1,969 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Local gateway foreground launcher and metadata-only control client.
4
+ *
5
+ * Provider identities and message bodies are accepted only from inherited
6
+ * process state and stdin respectively. They are never reflected in output or
7
+ * error text. Only `serve` owns the long-lived provider capabilities and
8
+ * private control server; every other command is a bounded client operation.
9
+ */
10
+ import { realpathSync } from "node:fs";
11
+ import { lstat, realpath } from "node:fs/promises";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { BridgeError } from "../errors.js";
15
+ import { getCliCopy, } from "./cli-copy.js";
16
+ import { GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS, GATEWAY_CONTROL_MAX_MESSAGE_BYTES, GATEWAY_CONTROL_MAX_RESPONSE_BYTES, GATEWAY_CONTROL_PROTOCOL_VERSION, GatewayControlTransportError, isClaudeSessionSelector, isGatewayAlias, isGatewayConversationId, isGatewayDeliveryToken, isGatewayHostId, isGatewayReplyAddress, sendGatewayControlRequest, } from "./control.js";
17
+ import { loadGatewayConfig } from "./config.js";
18
+ import { isDashboardLocale, } from "./locale.js";
19
+ import { runLiveDashboardCommand, } from "./live-dashboard-command.js";
20
+ import { runGatewayServer, } from "./server.js";
21
+ import { PROGRESS_WATCH_DEFAULT_IDLE_MS } from "./progress-watch-machine.js";
22
+ import { isCompatibilityCertificationReport, isCompatibilityCheckReport, } from "./compatibility.js";
23
+ const THREAD_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
24
+ const DEFAULT_HOST_ID = "this-mac";
25
+ const CLI_MAX_OUTPUT_BYTES = GATEWAY_CONTROL_MAX_RESPONSE_BYTES;
26
+ const DELIVERY_POLL_INTERVAL_MS = 250;
27
+ const DELIVERY_POLL_MIN_REQUEST_TIMEOUT_MS = 50;
28
+ export const EMBASSY_VERSION = "1.0.0";
29
+ const DEFAULT_CLI_LOCALE = "en";
30
+ export const gatewayCliCommands = [
31
+ "serve",
32
+ "health",
33
+ "status",
34
+ "compat-check",
35
+ "compat-certify",
36
+ "delivery-status",
37
+ "wait-delivery",
38
+ "untrack",
39
+ "refresh-dashboard",
40
+ "dashboard",
41
+ "register-codex",
42
+ "unregister-codex",
43
+ "select-claude",
44
+ "unselect-claude",
45
+ "pair",
46
+ "unpair",
47
+ "send-to-claude",
48
+ "send-to-codex",
49
+ "reply",
50
+ ];
51
+ export const gatewayCliExitCodes = Object.freeze({
52
+ ok: 0,
53
+ invalidInput: 2,
54
+ rejected: 3,
55
+ unavailable: 4,
56
+ ambiguous: 5,
57
+ failure: 6,
58
+ claudeCertificationFailed: 7,
59
+ codexCertificationFailed: 8,
60
+ multipleCertificationsFailed: 9,
61
+ });
62
+ const COMPAT_CERTIFY_CONTROL_TIMEOUT_MS = 90_000;
63
+ class CliFault extends Error {
64
+ code;
65
+ retryable;
66
+ hint;
67
+ kind;
68
+ constructor(code, retryable = false, hint, kind) {
69
+ super("The gateway client rejected the request.");
70
+ this.name = "CliFault";
71
+ this.code = code;
72
+ this.retryable = retryable;
73
+ this.hint = hint;
74
+ this.kind = kind;
75
+ }
76
+ }
77
+ function isCommand(value) {
78
+ return (value !== undefined &&
79
+ gatewayCliCommands.includes(value));
80
+ }
81
+ function parseOptions(args, valueNames, flagNames = []) {
82
+ const values = new Set(valueNames);
83
+ const flags = new Set(flagNames);
84
+ const parsed = Object.create(null);
85
+ for (let index = 0; index < args.length; index += 1) {
86
+ const token = args[index];
87
+ if (token === undefined || !token.startsWith("--") || token.length <= 2) {
88
+ throw new CliFault("INVALID_ARGUMENTS");
89
+ }
90
+ const name = token.slice(2);
91
+ if (Object.hasOwn(parsed, name)) {
92
+ throw new CliFault("INVALID_ARGUMENTS");
93
+ }
94
+ if (flags.has(name)) {
95
+ parsed[name] = true;
96
+ continue;
97
+ }
98
+ if (!values.has(name)) {
99
+ throw new CliFault("INVALID_ARGUMENTS");
100
+ }
101
+ const value = args[index + 1];
102
+ if (value === undefined || value.startsWith("--") || value.length === 0) {
103
+ throw new CliFault("INVALID_ARGUMENTS");
104
+ }
105
+ parsed[name] = value;
106
+ index += 1;
107
+ }
108
+ return parsed;
109
+ }
110
+ function environmentLocale(env) {
111
+ const value = env.EMBASSY_LOCALE;
112
+ if (value === undefined || value.length === 0)
113
+ return DEFAULT_CLI_LOCALE;
114
+ if (!isDashboardLocale(value))
115
+ throw new CliFault("INVALID_ARGUMENTS");
116
+ return value;
117
+ }
118
+ function resolveCommonCliOptions(args, env) {
119
+ const stripped = [];
120
+ let locale;
121
+ let sawLocale = false;
122
+ for (let index = 0; index < args.length; index += 1) {
123
+ const token = args[index];
124
+ if (token !== "--lang") {
125
+ if (token !== undefined)
126
+ stripped.push(token);
127
+ continue;
128
+ }
129
+ if (sawLocale)
130
+ throw new CliFault("INVALID_ARGUMENTS");
131
+ sawLocale = true;
132
+ const value = args[index + 1];
133
+ if (value === undefined || value.startsWith("--") || value.length === 0) {
134
+ throw new CliFault("INVALID_ARGUMENTS");
135
+ }
136
+ if (!isDashboardLocale(value)) {
137
+ throw new CliFault("INVALID_ARGUMENTS");
138
+ }
139
+ locale = value;
140
+ index += 1;
141
+ }
142
+ return {
143
+ args: stripped,
144
+ // Resolve the environment only when no valid explicit flag exists. This
145
+ // lets an exact flag intentionally override even a malformed environment.
146
+ locale: locale ?? environmentLocale(env),
147
+ };
148
+ }
149
+ function fallbackCliLocale(args, env) {
150
+ const localeFlagIndices = [];
151
+ for (let index = 0; index < args.length; index += 1) {
152
+ if (args[index] === "--lang")
153
+ localeFlagIndices.push(index);
154
+ }
155
+ if (localeFlagIndices.length === 1) {
156
+ const value = args[(localeFlagIndices[0] ?? -1) + 1];
157
+ if (isDashboardLocale(value))
158
+ return value;
159
+ }
160
+ return isDashboardLocale(env.EMBASSY_LOCALE)
161
+ ? env.EMBASSY_LOCALE
162
+ : DEFAULT_CLI_LOCALE;
163
+ }
164
+ function fixedStderr(locale, kind) {
165
+ return `[embassy] ${getCliCopy(locale)[`error.${kind}`]}\n`;
166
+ }
167
+ function requireString(options, name) {
168
+ const value = options[name];
169
+ if (typeof value !== "string")
170
+ throw new CliFault("INVALID_ARGUMENTS");
171
+ return value;
172
+ }
173
+ function assertExactOptionCount(options, minimum, maximum = minimum) {
174
+ const count = Object.keys(options).length;
175
+ if (count < minimum || count > maximum) {
176
+ throw new CliFault("INVALID_ARGUMENTS");
177
+ }
178
+ }
179
+ function requireAlias(options, name) {
180
+ const alias = requireString(options, name);
181
+ if (!isGatewayAlias(alias))
182
+ throw new CliFault("INVALID_ARGUMENTS");
183
+ return alias;
184
+ }
185
+ function requireCodexAlias(options, name) {
186
+ const alias = requireAlias(options, name);
187
+ if (!alias.startsWith("codex-"))
188
+ throw new CliFault("INVALID_ARGUMENTS");
189
+ return alias;
190
+ }
191
+ function gatewayAliasHost(alias) {
192
+ return alias.slice(alias.lastIndexOf("@") + 1);
193
+ }
194
+ function requireClaudeSelector(options, name) {
195
+ const selector = requireString(options, name);
196
+ if (!isClaudeSessionSelector(selector)) {
197
+ throw new CliFault("INVALID_ARGUMENTS");
198
+ }
199
+ return selector;
200
+ }
201
+ function trackIdleMinutes(options) {
202
+ const tracking = options.track === true;
203
+ const raw = options["idle-minutes"];
204
+ if (raw !== undefined && !tracking) {
205
+ throw new CliFault("INVALID_ARGUMENTS");
206
+ }
207
+ if (!tracking)
208
+ return undefined;
209
+ if (raw === undefined)
210
+ return PROGRESS_WATCH_DEFAULT_IDLE_MS / 60_000;
211
+ if (typeof raw !== "string" ||
212
+ !/^[1-9][0-9]{0,3}$/.test(raw)) {
213
+ throw new CliFault("INVALID_ARGUMENTS");
214
+ }
215
+ const minutes = Number(raw);
216
+ if (!Number.isSafeInteger(minutes) || minutes > 24 * 60) {
217
+ throw new CliFault("INVALID_ARGUMENTS");
218
+ }
219
+ return minutes;
220
+ }
221
+ function requireDeliveryToken(options, name) {
222
+ const token = requireString(options, name);
223
+ if (!isGatewayDeliveryToken(token)) {
224
+ throw new CliFault("INVALID_ARGUMENTS");
225
+ }
226
+ return token;
227
+ }
228
+ function requireCodexThreadId(env) {
229
+ const threadId = env.CODEX_THREAD_ID;
230
+ if (typeof threadId !== "string" || !THREAD_ID_PATTERN.test(threadId)) {
231
+ throw new CliFault("CODEX_IDENTITY_REQUIRED");
232
+ }
233
+ return threadId.toLowerCase();
234
+ }
235
+ function hasInheritedIdentity(value) {
236
+ return typeof value === "string" && value.length > 0;
237
+ }
238
+ function requireExclusiveCodexThreadId(env) {
239
+ if (hasInheritedIdentity(env.CLAUDE_CODE_MESSAGING_SOCKET)) {
240
+ throw new CliFault("CALLER_IDENTITY_CONFLICT");
241
+ }
242
+ return requireCodexThreadId(env);
243
+ }
244
+ function optionalCodexThreadId(env) {
245
+ const threadId = env.CODEX_THREAD_ID;
246
+ if (threadId === undefined || threadId.length === 0)
247
+ return undefined;
248
+ if (!THREAD_ID_PATTERN.test(threadId)) {
249
+ throw new CliFault("CODEX_IDENTITY_REQUIRED");
250
+ }
251
+ return threadId.toLowerCase();
252
+ }
253
+ function optionalClaudeReplyAddress(env) {
254
+ const socketPath = env.CLAUDE_CODE_MESSAGING_SOCKET;
255
+ if (socketPath === undefined || socketPath.length === 0)
256
+ return undefined;
257
+ // Claude Code 2.1.225 exports the raw socket path. The peer protocol's
258
+ // transient callback capability is the same path with an explicit UDS
259
+ // scheme; form it only in memory and leave generation validation to the
260
+ // broker's pinned Claude adapter.
261
+ if (socketPath.includes("\0") ||
262
+ !path.isAbsolute(socketPath) ||
263
+ path.resolve(socketPath) !== socketPath) {
264
+ throw new CliFault("CLAUDE_IDENTITY_INVALID");
265
+ }
266
+ const replyAddress = `uds:${socketPath}`;
267
+ if (!isGatewayReplyAddress(replyAddress)) {
268
+ throw new CliFault("CLAUDE_IDENTITY_INVALID");
269
+ }
270
+ return replyAddress;
271
+ }
272
+ function requireClaudeReplyAddress(env) {
273
+ const replyAddress = optionalClaudeReplyAddress(env);
274
+ if (replyAddress === undefined) {
275
+ throw new CliFault("CLAUDE_IDENTITY_REQUIRED");
276
+ }
277
+ return replyAddress;
278
+ }
279
+ function requireExclusiveClaudeReplyAddress(env) {
280
+ if (hasInheritedIdentity(env.CODEX_THREAD_ID)) {
281
+ throw new CliFault("CALLER_IDENTITY_CONFLICT");
282
+ }
283
+ return requireClaudeReplyAddress(env);
284
+ }
285
+ async function readMessageBody(stdin) {
286
+ const chunks = [];
287
+ let length = 0;
288
+ for await (const chunk of stdin) {
289
+ if (typeof chunk !== "string" && !Buffer.isBuffer(chunk)) {
290
+ throw new CliFault("INVALID_MESSAGE_INPUT");
291
+ }
292
+ const buffer = Buffer.isBuffer(chunk)
293
+ ? Buffer.from(chunk)
294
+ : Buffer.from(chunk, "utf8");
295
+ length += buffer.length;
296
+ if (length > GATEWAY_CONTROL_MAX_MESSAGE_BYTES) {
297
+ throw new CliFault("MESSAGE_TOO_LARGE");
298
+ }
299
+ chunks.push(buffer);
300
+ }
301
+ let text;
302
+ try {
303
+ text = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, length));
304
+ }
305
+ catch {
306
+ throw new CliFault("INVALID_MESSAGE_INPUT");
307
+ }
308
+ if (text.trim().length === 0 || text.includes("\0")) {
309
+ throw new CliFault("MESSAGE_REQUIRED");
310
+ }
311
+ return text;
312
+ }
313
+ function emptyParams(args) {
314
+ if (args.length !== 0)
315
+ throw new CliFault("INVALID_ARGUMENTS");
316
+ return {};
317
+ }
318
+ function parseServeInboundMode(args) {
319
+ const options = parseOptions(args, ["inbound"]);
320
+ if (Object.keys(options).length === 0)
321
+ return "paired";
322
+ assertExactOptionCount(options, 1);
323
+ if (options.inbound !== "open")
324
+ throw new CliFault("INVALID_ARGUMENTS");
325
+ return "open";
326
+ }
327
+ function validateLiveDashboardArgs(args) {
328
+ if (args.length === 0) {
329
+ throw new CliFault("INVALID_ARGUMENTS", false, "hint.dashboardLiveRequired");
330
+ }
331
+ const options = parseOptions(args, [], ["live"]);
332
+ assertExactOptionCount(options, 1);
333
+ if (options.live !== true)
334
+ throw new CliFault("INVALID_ARGUMENTS");
335
+ }
336
+ async function buildRequest(command, args, env, stdin) {
337
+ switch (command) {
338
+ case "serve":
339
+ case "dashboard":
340
+ throw new CliFault("INVALID_ARGUMENTS");
341
+ case "health":
342
+ return {
343
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
344
+ method: "health",
345
+ params: emptyParams(args),
346
+ };
347
+ case "status":
348
+ return {
349
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
350
+ method: "list_snapshot",
351
+ params: emptyParams(args),
352
+ };
353
+ case "compat-check":
354
+ return {
355
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
356
+ method: "compat_check",
357
+ params: emptyParams(args),
358
+ };
359
+ case "compat-certify": {
360
+ const options = parseOptions(args, ["codex"], ["with-turn"]);
361
+ assertExactOptionCount(options, 0, 2);
362
+ const codexAlias = options.codex === undefined
363
+ ? undefined
364
+ : requireCodexAlias(options, "codex");
365
+ return {
366
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
367
+ method: "compat_certify",
368
+ params: {
369
+ withTurn: options["with-turn"] === true,
370
+ ...(codexAlias === undefined ? {} : { codexAlias }),
371
+ },
372
+ };
373
+ }
374
+ case "delivery-status":
375
+ case "wait-delivery": {
376
+ const options = parseOptions(args, ["token"]);
377
+ assertExactOptionCount(options, 1);
378
+ return {
379
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
380
+ method: "delivery_status",
381
+ params: { token: requireDeliveryToken(options, "token") },
382
+ };
383
+ }
384
+ case "untrack": {
385
+ const options = parseOptions(args, ["conversation"]);
386
+ assertExactOptionCount(options, 1);
387
+ const conversationId = requireString(options, "conversation");
388
+ if (!isGatewayConversationId(conversationId)) {
389
+ throw new CliFault("INVALID_ARGUMENTS");
390
+ }
391
+ return {
392
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
393
+ method: "untrack",
394
+ params: { conversationId },
395
+ };
396
+ }
397
+ case "refresh-dashboard":
398
+ return {
399
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
400
+ method: "refresh_dashboard",
401
+ params: emptyParams(args),
402
+ };
403
+ case "register-codex": {
404
+ const options = parseOptions(args, ["alias", "host", "succeeds"]);
405
+ const alias = requireCodexAlias(options, "alias");
406
+ const succeedsAlias = options.succeeds === undefined
407
+ ? undefined
408
+ : requireCodexAlias(options, "succeeds");
409
+ if (succeedsAlias === undefined) {
410
+ assertExactOptionCount(options, 1, 2);
411
+ }
412
+ else {
413
+ assertExactOptionCount(options, 2);
414
+ if (options.host !== undefined) {
415
+ throw new CliFault("INVALID_ARGUMENTS");
416
+ }
417
+ }
418
+ const host = succeedsAlias === undefined
419
+ ? (options.host ?? DEFAULT_HOST_ID)
420
+ : gatewayAliasHost(alias);
421
+ if (typeof host !== "string" ||
422
+ !isGatewayHostId(host) ||
423
+ !alias.endsWith(`@${host}`) ||
424
+ (succeedsAlias !== undefined &&
425
+ (succeedsAlias === alias ||
426
+ gatewayAliasHost(succeedsAlias) !== host))) {
427
+ throw new CliFault("INVALID_ARGUMENTS");
428
+ }
429
+ return {
430
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
431
+ method: "register_codex",
432
+ params: {
433
+ alias,
434
+ threadId: requireExclusiveCodexThreadId(env),
435
+ hostId: host,
436
+ busyPolicy: "queue",
437
+ ...(succeedsAlias === undefined ? {} : { succeedsAlias }),
438
+ },
439
+ };
440
+ }
441
+ case "unregister-codex": {
442
+ const options = parseOptions(args, ["alias"]);
443
+ assertExactOptionCount(options, 1);
444
+ return {
445
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
446
+ method: "unregister_codex",
447
+ params: {
448
+ alias: requireCodexAlias(options, "alias"),
449
+ threadId: requireExclusiveCodexThreadId(env),
450
+ },
451
+ };
452
+ }
453
+ case "select-claude":
454
+ case "unselect-claude": {
455
+ const options = parseOptions(args, ["alias", "session"]);
456
+ assertExactOptionCount(options, 1);
457
+ const selector = options.alias === undefined
458
+ ? requireClaudeSelector(options, "session")
459
+ : requireClaudeSelector(options, "alias");
460
+ const codexThreadId = optionalCodexThreadId(env);
461
+ return {
462
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
463
+ method: command === "select-claude" ? "select_claude" : "unselect_claude",
464
+ params: {
465
+ alias: selector,
466
+ ...(codexThreadId === undefined ? {} : { codexThreadId }),
467
+ },
468
+ };
469
+ }
470
+ case "pair":
471
+ case "unpair": {
472
+ const options = parseOptions(args, ["claude", "codex"]);
473
+ assertExactOptionCount(options, 2);
474
+ return {
475
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
476
+ method: command,
477
+ params: {
478
+ claudeAlias: requireClaudeSelector(options, "claude"),
479
+ codexAlias: requireCodexAlias(options, "codex"),
480
+ codexThreadId: requireExclusiveCodexThreadId(env),
481
+ },
482
+ };
483
+ }
484
+ case "send-to-claude": {
485
+ const options = parseOptions(args, ["from", "to", "idle-minutes"], ["expects-reply", "track"]);
486
+ assertExactOptionCount(options, 2, 5);
487
+ const threadId = requireExclusiveCodexThreadId(env);
488
+ const idleMinutes = trackIdleMinutes(options);
489
+ return {
490
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
491
+ method: "send_to_claude",
492
+ params: {
493
+ fromAlias: requireAlias(options, "from"),
494
+ threadId,
495
+ toAlias: requireClaudeSelector(options, "to"),
496
+ text: await readMessageBody(stdin),
497
+ expectsReply: options["expects-reply"] === true,
498
+ ...(idleMinutes === undefined
499
+ ? {}
500
+ : { trackIdleMinutes: idleMinutes }),
501
+ },
502
+ };
503
+ }
504
+ case "send-to-codex": {
505
+ const options = parseOptions(args, ["from", "to", "idle-minutes"], ["expects-reply", "track"]);
506
+ assertExactOptionCount(options, 2, 5);
507
+ const replyAddress = requireExclusiveClaudeReplyAddress(env);
508
+ const idleMinutes = trackIdleMinutes(options);
509
+ return {
510
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
511
+ method: "send_to_codex",
512
+ params: {
513
+ fromAlias: requireAlias(options, "from"),
514
+ toAlias: requireAlias(options, "to"),
515
+ text: await readMessageBody(stdin),
516
+ replyAddress,
517
+ expectsReply: options["expects-reply"] === true,
518
+ ...(idleMinutes === undefined
519
+ ? {}
520
+ : { trackIdleMinutes: idleMinutes }),
521
+ },
522
+ };
523
+ }
524
+ case "reply": {
525
+ const options = parseOptions(args, ["conversation", "alias", "idle-minutes"], ["track"]);
526
+ assertExactOptionCount(options, 2, 4);
527
+ const conversationId = requireString(options, "conversation");
528
+ if (!isGatewayConversationId(conversationId)) {
529
+ throw new CliFault("INVALID_ARGUMENTS");
530
+ }
531
+ const alias = requireAlias(options, "alias");
532
+ const idleMinutes = trackIdleMinutes(options);
533
+ const threadId = env.CODEX_THREAD_ID;
534
+ const replyAddress = optionalClaudeReplyAddress(env);
535
+ const hasCodexIdentity = hasInheritedIdentity(threadId);
536
+ const hasClaudeIdentity = replyAddress !== undefined;
537
+ if (hasCodexIdentity && hasClaudeIdentity) {
538
+ throw new CliFault("CALLER_IDENTITY_CONFLICT");
539
+ }
540
+ if (!hasCodexIdentity && !hasClaudeIdentity) {
541
+ throw new CliFault("CALLER_IDENTITY_REQUIRED");
542
+ }
543
+ return {
544
+ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
545
+ method: "reply",
546
+ params: {
547
+ conversationId,
548
+ text: await readMessageBody(stdin),
549
+ ...(idleMinutes === undefined
550
+ ? {}
551
+ : { trackIdleMinutes: idleMinutes }),
552
+ caller: hasCodexIdentity
553
+ ? {
554
+ kind: "codex",
555
+ alias,
556
+ threadId: requireCodexThreadId(env),
557
+ }
558
+ : {
559
+ kind: "claude",
560
+ alias,
561
+ replyAddress: requireClaudeReplyAddress(env),
562
+ },
563
+ },
564
+ };
565
+ }
566
+ }
567
+ }
568
+ export async function validatePrivateGatewayControlSocket(stateDir, socketPath) {
569
+ if (process.platform === "win32") {
570
+ throw new CliFault("CONTROL_SOCKET_UNAVAILABLE", true);
571
+ }
572
+ let state;
573
+ let socket;
574
+ try {
575
+ [state, socket] = await Promise.all([lstat(stateDir), lstat(socketPath)]);
576
+ }
577
+ catch {
578
+ throw new CliFault("CONTROL_SOCKET_UNAVAILABLE", true);
579
+ }
580
+ const getuid = process.getuid;
581
+ const expectedUid = getuid?.call(process);
582
+ if (state.isSymbolicLink() ||
583
+ !state.isDirectory() ||
584
+ (state.mode & 0o700) !== 0o700 ||
585
+ (state.mode & 0o077) !== 0 ||
586
+ (expectedUid !== undefined && state.uid !== expectedUid)) {
587
+ throw new CliFault("CONTROL_STATE_UNSAFE", false, undefined, "unsafe");
588
+ }
589
+ if (socket.isSymbolicLink() ||
590
+ !socket.isSocket() ||
591
+ (socket.mode & 0o777) !== 0o600 ||
592
+ (expectedUid !== undefined && socket.uid !== expectedUid)) {
593
+ throw new CliFault("CONTROL_SOCKET_UNSAFE", false, undefined, "unsafe");
594
+ }
595
+ let stateReal;
596
+ let socketParentReal;
597
+ try {
598
+ [stateReal, socketParentReal] = await Promise.all([
599
+ realpath(stateDir),
600
+ realpath(path.dirname(socketPath)),
601
+ ]);
602
+ }
603
+ catch {
604
+ throw new CliFault("CONTROL_SOCKET_UNSAFE", false, undefined, "unsafe");
605
+ }
606
+ if (path.dirname(socketPath) !== stateDir ||
607
+ stateReal !== socketParentReal) {
608
+ throw new CliFault("CONTROL_SOCKET_UNSAFE", false, undefined, "unsafe");
609
+ }
610
+ }
611
+ function safeCommandLabel(command) {
612
+ return command ?? "unknown";
613
+ }
614
+ function serializedOutput(value) {
615
+ const line = `${JSON.stringify(value)}\n`;
616
+ if (Buffer.byteLength(line, "utf8") > CLI_MAX_OUTPUT_BYTES) {
617
+ throw new CliFault("OUTPUT_TOO_LARGE");
618
+ }
619
+ return line;
620
+ }
621
+ function writeFailure(stdout, stderr, locale, command, code, options) {
622
+ const ambiguous = options.ambiguous ?? false;
623
+ const retryable = options.retryable ?? false;
624
+ stdout.write(serializedOutput({
625
+ ok: false,
626
+ command: safeCommandLabel(command),
627
+ error: { code, ambiguous, retryable },
628
+ }));
629
+ stderr.write(fixedStderr(locale, options.kind));
630
+ }
631
+ function isRejectedResult(result) {
632
+ return (result !== null &&
633
+ typeof result === "object" &&
634
+ Object.hasOwn(result, "accepted") &&
635
+ result.accepted === false);
636
+ }
637
+ function responseExitCode(response) {
638
+ if (!response.ok)
639
+ return gatewayCliExitCodes.failure;
640
+ if (isRejectedResult(response.result))
641
+ return gatewayCliExitCodes.rejected;
642
+ return gatewayCliExitCodes.ok;
643
+ }
644
+ function waitDeliveryExitCode(response) {
645
+ if (!response.ok ||
646
+ !response.result.found ||
647
+ !response.result.terminal) {
648
+ return gatewayCliExitCodes.failure;
649
+ }
650
+ return response.result.state === "delivered"
651
+ ? gatewayCliExitCodes.ok
652
+ : gatewayCliExitCodes.failure;
653
+ }
654
+ async function defaultDelay(milliseconds) {
655
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
656
+ }
657
+ async function waitForDelivery(socketPath, request, sendRequest, now, delay) {
658
+ let clientDeadlineMs;
659
+ while (true) {
660
+ const beforeRequest = now();
661
+ let requestTimeoutMs = GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS;
662
+ if (clientDeadlineMs !== undefined) {
663
+ const remainingMs = clientDeadlineMs - beforeRequest;
664
+ if (remainingMs <= 0)
665
+ return { kind: "timeout" };
666
+ if (remainingMs < DELIVERY_POLL_MIN_REQUEST_TIMEOUT_MS) {
667
+ await delay(remainingMs);
668
+ return { kind: "timeout" };
669
+ }
670
+ requestTimeoutMs = Math.min(GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS, Math.floor(remainingMs));
671
+ }
672
+ let response;
673
+ try {
674
+ response = await sendRequest({
675
+ socketPath,
676
+ request,
677
+ timeoutMs: requestTimeoutMs,
678
+ });
679
+ }
680
+ catch (error) {
681
+ if (clientDeadlineMs !== undefined &&
682
+ now() >= clientDeadlineMs &&
683
+ error instanceof GatewayControlTransportError &&
684
+ error.recoverable) {
685
+ return { kind: "timeout" };
686
+ }
687
+ throw error;
688
+ }
689
+ if (!response.ok) {
690
+ if (response.error.code === "REQUEST_TIMEOUT" &&
691
+ clientDeadlineMs !== undefined &&
692
+ now() >= clientDeadlineMs) {
693
+ return { kind: "timeout" };
694
+ }
695
+ return { kind: "response", response };
696
+ }
697
+ if (!response.result.found)
698
+ return { kind: "unknown" };
699
+ // A retained terminal decision is authoritative even when the command is
700
+ // invoked after the original delivery deadline and client grace window.
701
+ if (response.result.terminal) {
702
+ return { kind: "response", response };
703
+ }
704
+ const parsedDeadlineMs = Date.parse(response.result.deadlineAt);
705
+ if (!Number.isFinite(parsedDeadlineMs)) {
706
+ throw new Error("invalid validated delivery deadline");
707
+ }
708
+ const observedClientDeadlineMs = parsedDeadlineMs + GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS;
709
+ clientDeadlineMs =
710
+ clientDeadlineMs === undefined
711
+ ? observedClientDeadlineMs
712
+ : Math.min(clientDeadlineMs, observedClientDeadlineMs);
713
+ const afterRequest = now();
714
+ const remainingMs = clientDeadlineMs - afterRequest;
715
+ if (remainingMs <= 0)
716
+ return { kind: "timeout" };
717
+ await delay(Math.min(DELIVERY_POLL_INTERVAL_MS, remainingMs));
718
+ }
719
+ }
720
+ /** Run one command; foreground runners own and release their signal handlers. */
721
+ export async function runGatewayCli(argv = process.argv.slice(2), dependencies = {}) {
722
+ const env = dependencies.env ?? process.env;
723
+ const stdin = dependencies.stdin ?? process.stdin;
724
+ const stdout = dependencies.stdout ?? process.stdout;
725
+ const stderr = dependencies.stderr ?? process.stderr;
726
+ const loadConfig = dependencies.loadConfig ?? loadGatewayConfig;
727
+ const sendRequest = dependencies.sendRequest ?? sendGatewayControlRequest;
728
+ const validateControlSocket = dependencies.validateControlSocket ?? validatePrivateGatewayControlSocket;
729
+ const runServer = dependencies.runServer ?? runGatewayServer;
730
+ const runLiveDashboard = dependencies.runLiveDashboard ?? runLiveDashboardCommand;
731
+ const now = dependencies.now ?? Date.now;
732
+ const delay = dependencies.delay ?? defaultDelay;
733
+ if (argv.length === 1 &&
734
+ (argv[0] === "--version" || argv[0] === "-v")) {
735
+ stdout.write(`embassy ${EMBASSY_VERSION}\n`);
736
+ return gatewayCliExitCodes.ok;
737
+ }
738
+ const command = isCommand(argv[0]) ? argv[0] : undefined;
739
+ let locale = fallbackCliLocale(argv.slice(1), env);
740
+ let serverReadyEmitted = false;
741
+ let liveDashboardReadyEmitted = false;
742
+ try {
743
+ const common = resolveCommonCliOptions(argv.slice(1), env);
744
+ locale = common.locale;
745
+ if (argv.length === 0 ||
746
+ argv[0] === "--help" ||
747
+ argv[0] === "-h") {
748
+ emptyParams(common.args);
749
+ stdout.write(getCliCopy(locale)["help.usage"]);
750
+ return gatewayCliExitCodes.ok;
751
+ }
752
+ if (command === undefined)
753
+ throw new CliFault("UNKNOWN_COMMAND");
754
+ if (command === "serve") {
755
+ const inboundMode = parseServeInboundMode(common.args);
756
+ await runServer({
757
+ env,
758
+ locale,
759
+ inboundMode,
760
+ ...(dependencies.serverSignal === undefined
761
+ ? {}
762
+ : { signal: dependencies.serverSignal }),
763
+ onReady: async (result) => {
764
+ if (serverReadyEmitted) {
765
+ throw new CliFault("SERVER_READY_ALREADY_EMITTED");
766
+ }
767
+ stdout.write(serializedOutput({
768
+ ok: true,
769
+ command,
770
+ result,
771
+ }));
772
+ serverReadyEmitted = true;
773
+ },
774
+ });
775
+ if (!serverReadyEmitted) {
776
+ throw new CliFault("SERVER_NOT_READY");
777
+ }
778
+ return gatewayCliExitCodes.ok;
779
+ }
780
+ if (command === "dashboard") {
781
+ validateLiveDashboardArgs(common.args);
782
+ const outcome = await runLiveDashboard({
783
+ env,
784
+ locale,
785
+ ...(dependencies.liveDashboardSignal === undefined
786
+ ? {}
787
+ : { signal: dependencies.liveDashboardSignal }),
788
+ loadConfig,
789
+ validateControlSocket,
790
+ sendRequest,
791
+ onReady: async (result) => {
792
+ if (liveDashboardReadyEmitted) {
793
+ throw new CliFault("LIVE_DASHBOARD_READY_ALREADY_EMITTED");
794
+ }
795
+ stdout.write(serializedOutput({
796
+ ok: true,
797
+ command,
798
+ result,
799
+ }));
800
+ liveDashboardReadyEmitted = true;
801
+ },
802
+ });
803
+ if (!liveDashboardReadyEmitted) {
804
+ if (outcome?.status === "cancelled") {
805
+ return gatewayCliExitCodes.ok;
806
+ }
807
+ throw new Error("LIVE_DASHBOARD_NOT_READY");
808
+ }
809
+ return gatewayCliExitCodes.ok;
810
+ }
811
+ const request = await buildRequest(command, common.args, env, stdin);
812
+ const config = loadConfig(env);
813
+ await validateControlSocket(config.stateDir, config.controlSocketPath);
814
+ let response;
815
+ let waitedDeliveryResponse;
816
+ if (command === "wait-delivery") {
817
+ if (request.method !== "delivery_status") {
818
+ throw new CliFault("INVALID_ARGUMENTS");
819
+ }
820
+ const outcome = await waitForDelivery(config.controlSocketPath, request, sendRequest, now, delay);
821
+ if (outcome.kind === "unknown") {
822
+ writeFailure(stdout, stderr, locale, command, "DELIVERY_TOKEN_UNKNOWN", {
823
+ kind: "tokenUnknown",
824
+ });
825
+ return gatewayCliExitCodes.rejected;
826
+ }
827
+ if (outcome.kind === "timeout") {
828
+ writeFailure(stdout, stderr, locale, command, "DELIVERY_WAIT_TIMEOUT", {
829
+ retryable: true,
830
+ kind: "deliveryTimeout",
831
+ });
832
+ return gatewayCliExitCodes.unavailable;
833
+ }
834
+ waitedDeliveryResponse = outcome.response;
835
+ response = waitedDeliveryResponse;
836
+ }
837
+ else {
838
+ response = await sendRequest({
839
+ socketPath: config.controlSocketPath,
840
+ request,
841
+ ...(command === "compat-certify"
842
+ ? { timeoutMs: COMPAT_CERTIFY_CONTROL_TIMEOUT_MS }
843
+ : {}),
844
+ });
845
+ }
846
+ if (!response.ok) {
847
+ writeFailure(stdout, stderr, locale, command, response.error.code, {
848
+ kind: "failure",
849
+ });
850
+ return gatewayCliExitCodes.failure;
851
+ }
852
+ stdout.write(serializedOutput({
853
+ ok: true,
854
+ command,
855
+ result: response.result,
856
+ }));
857
+ if (command === "compat-check" && isCompatibilityCheckReport(response.result)) {
858
+ for (const surface of response.result.surfaces) {
859
+ stderr.write(`[embassy] ${surface.surface}: ${surface.tier} (${surface.version})${surface.safeErrorCode === undefined
860
+ ? ""
861
+ : ` — ${surface.safeErrorCode}`}\n`);
862
+ }
863
+ return response.result.compatible
864
+ ? gatewayCliExitCodes.ok
865
+ : gatewayCliExitCodes.failure;
866
+ }
867
+ if (command === "compat-certify" &&
868
+ isCompatibilityCertificationReport(response.result)) {
869
+ for (const surface of response.result.surfaces) {
870
+ const certification = surface.certification;
871
+ if (certification === undefined)
872
+ continue;
873
+ stderr.write(`[embassy] ${surface.surface}: ${certification.outcome} (${certification.depth})${certification.safeErrorCode === undefined
874
+ ? ""
875
+ : ` — ${certification.safeErrorCode}`}\n`);
876
+ }
877
+ if (response.result.certified)
878
+ return gatewayCliExitCodes.ok;
879
+ const failedSurfaces = response.result.surfaces.filter((surface) => surface.certification?.outcome === "fail");
880
+ if (failedSurfaces.length > 1) {
881
+ return gatewayCliExitCodes.multipleCertificationsFailed;
882
+ }
883
+ return failedSurfaces[0]?.surface === "claude"
884
+ ? gatewayCliExitCodes.claudeCertificationFailed
885
+ : gatewayCliExitCodes.codexCertificationFailed;
886
+ }
887
+ const exitCode = waitedDeliveryResponse === undefined
888
+ ? responseExitCode(response)
889
+ : waitDeliveryExitCode(waitedDeliveryResponse);
890
+ if (exitCode === gatewayCliExitCodes.rejected) {
891
+ stderr.write(fixedStderr(locale, "decision"));
892
+ }
893
+ else if (command === "wait-delivery" &&
894
+ exitCode === gatewayCliExitCodes.failure) {
895
+ stderr.write(fixedStderr(locale, "failure"));
896
+ }
897
+ return exitCode;
898
+ }
899
+ catch (error) {
900
+ if ((command === "serve" && serverReadyEmitted) ||
901
+ (command === "dashboard" && liveDashboardReadyEmitted)) {
902
+ stderr.write(fixedStderr(locale, "failure"));
903
+ return gatewayCliExitCodes.failure;
904
+ }
905
+ if (error instanceof GatewayControlTransportError) {
906
+ const ambiguous = error.ambiguous;
907
+ writeFailure(stdout, stderr, locale, command, error.code, {
908
+ ambiguous,
909
+ retryable: ambiguous ? false : error.recoverable,
910
+ kind: ambiguous ? "ambiguous" : "unavailable",
911
+ });
912
+ return ambiguous
913
+ ? gatewayCliExitCodes.ambiguous
914
+ : gatewayCliExitCodes.unavailable;
915
+ }
916
+ if (error instanceof CliFault) {
917
+ writeFailure(stdout, stderr, locale, command, error.code, {
918
+ retryable: error.retryable,
919
+ kind: error.kind ?? (error.retryable ? "unavailable" : "input"),
920
+ });
921
+ if (error.hint !== undefined) {
922
+ stderr.write(`[embassy] ${getCliCopy(locale)[error.hint]}\n`);
923
+ }
924
+ return error.retryable
925
+ ? gatewayCliExitCodes.unavailable
926
+ : gatewayCliExitCodes.invalidInput;
927
+ }
928
+ if (error instanceof BridgeError) {
929
+ writeFailure(stdout, stderr, locale, command, error.code, {
930
+ retryable: error.recoverable,
931
+ kind: error.code === "CLAUDE_VERSION_DRIFT"
932
+ ? "versionDrift"
933
+ : error.recoverable
934
+ ? "unavailable"
935
+ : "input",
936
+ });
937
+ return error.recoverable
938
+ ? gatewayCliExitCodes.unavailable
939
+ : gatewayCliExitCodes.invalidInput;
940
+ }
941
+ writeFailure(stdout, stderr, locale, command, "INTERNAL_ERROR", {
942
+ kind: "failure",
943
+ });
944
+ return gatewayCliExitCodes.failure;
945
+ }
946
+ }
947
+ function isDirectExecution() {
948
+ const entry = process.argv[1];
949
+ if (entry === undefined)
950
+ return false;
951
+ try {
952
+ // npm exposes package binaries through symlinks. Compare canonical paths
953
+ // so an installed `embassy` executable is recognized as the entry point,
954
+ // while importing this module in tests remains side-effect free.
955
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
956
+ }
957
+ catch {
958
+ return false;
959
+ }
960
+ }
961
+ if (isDirectExecution()) {
962
+ void runGatewayCli().then((exitCode) => {
963
+ process.exitCode = exitCode;
964
+ }, () => {
965
+ process.stderr.write(fixedStderr(fallbackCliLocale(process.argv.slice(3), process.env), "failure"));
966
+ process.exitCode = gatewayCliExitCodes.failure;
967
+ });
968
+ }
969
+ //# sourceMappingURL=cli.js.map