@dexlyai/dexly 0.1.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 (4) hide show
  1. package/README.md +61 -0
  2. package/dist/cli.js +1404 -0
  3. package/dist/host.js +859 -0
  4. package/package.json +43 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1404 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ "use strict";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+
30
+ // ../DexlyProtocol/dist/companion.js
31
+ var require_companion = __commonJS({
32
+ "../DexlyProtocol/dist/companion.js"(exports2) {
33
+ "use strict";
34
+ Object.defineProperty(exports2, "__esModule", { value: true });
35
+ exports2.DEFAULT_DIRECT_WEBSOCKET_ENDPOINT = exports2.DEXLY_FIXED_ALLOWED_ORIGINS = exports2.DEXLY_DEFAULT_RELEASE_CHANNEL_BY_BUILD = exports2.DEXLY_FIXED_EXTENSION_IDS = exports2.DEXLY_EXTENSION_IDENTITIES = exports2.DEXLY_PROD_EXTENSION_ID = exports2.DEXLY_PROD_EXTENSION_PUBLIC_KEY = exports2.DEXLY_DEV_EXTENSION_ID = exports2.DEXLY_DEV_EXTENSION_PUBLIC_KEY = exports2.DEXLY_CODEX_INSTALL_DOCS_URL = exports2.DEXLY_CODEX_UPGRADE_COMMAND = exports2.DEXLY_CODEX_INSTALL_COMMAND = exports2.DEXLY_COMPANION_CONNECT_COOLDOWN_MS = exports2.DEXLY_COMPANION_GLOBAL_INSTALL_COMMAND = exports2.DEXLY_COMPANION_UPGRADE_COMMAND = exports2.DEXLY_COMPANION_DOCTOR_COMMAND = exports2.DEXLY_COMPANION_INSTALL_COMMAND = exports2.DEXLY_NATIVE_CONNECTION_ENDPOINT = exports2.DEXLY_COMPANION_EXECUTABLE_NAME = exports2.DEXLY_COMPANION_PACKAGE_NAME = exports2.DEXLY_COMPANION_DISPLAY_NAME = exports2.DEXLY_COMPANION_HOST_NAME = void 0;
36
+ exports2.resolveDexlyExtensionBuildChannel = resolveDexlyExtensionBuildChannel;
37
+ exports2.getDexlyExtensionIdentity = getDexlyExtensionIdentity;
38
+ exports2.isDexlyFixedExtensionId = isDexlyFixedExtensionId;
39
+ exports2.toChromeExtensionOrigin = toChromeExtensionOrigin;
40
+ exports2.resolveDexlyExtensionBuildChannelFromExtensionId = resolveDexlyExtensionBuildChannelFromExtensionId;
41
+ exports2.normalizeCompanionReleaseChannel = normalizeCompanionReleaseChannel;
42
+ exports2.DEXLY_COMPANION_HOST_NAME = "ai.dexly.companion";
43
+ exports2.DEXLY_COMPANION_DISPLAY_NAME = "Dexly Companion";
44
+ exports2.DEXLY_COMPANION_PACKAGE_NAME = "@dexlyai/dexly";
45
+ exports2.DEXLY_COMPANION_EXECUTABLE_NAME = "dexly";
46
+ exports2.DEXLY_NATIVE_CONNECTION_ENDPOINT = "native://ai.dexly.companion";
47
+ exports2.DEXLY_COMPANION_INSTALL_COMMAND = "npx -y @dexlyai/dexly install";
48
+ exports2.DEXLY_COMPANION_DOCTOR_COMMAND = "npx -y @dexlyai/dexly doctor";
49
+ exports2.DEXLY_COMPANION_UPGRADE_COMMAND = "npx -y @dexlyai/dexly upgrade";
50
+ exports2.DEXLY_COMPANION_GLOBAL_INSTALL_COMMAND = "npm i -g @dexlyai/dexly && dexly install";
51
+ exports2.DEXLY_COMPANION_CONNECT_COOLDOWN_MS = 15e3;
52
+ exports2.DEXLY_CODEX_INSTALL_COMMAND = "npm install -g @openai/codex";
53
+ exports2.DEXLY_CODEX_UPGRADE_COMMAND = "codex --upgrade";
54
+ exports2.DEXLY_CODEX_INSTALL_DOCS_URL = "https://help.openai.com/en/articles/11096431-openai-codex-ci-getting-started";
55
+ exports2.DEXLY_DEV_EXTENSION_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6fpo7R5tslk/4402QrobrFaQUyW2jk+cgG60FE51YtcQUWtdI8WEyIZBg7bTkpwjt/cvcCWg6B5P/iOZNYoq0o3RxyeqT+8IjuYA0d2kYUaCOoshPsLdEvLTTr3SQ1bg0r2naivw92KxVgZ2fcZuS4kNy6qYhBCWitvTLvDVCnjRN13UT9buskxnsv0Cr+kh7vr/YXA2Boy2jwQyZXEDg9FvnSJF5yEAK6GLzlkvfIKXifxBSMdNaOVp8hKOxR9WjFTotBwZ6mgJXKCNgG+7Xd54xkWRaomU7CEohfvkmmxM9gpJ3S1VqmauogSbkzwCcJQJ1csdU/7TWQ4+hOuKLQIDAQAB";
56
+ exports2.DEXLY_DEV_EXTENSION_ID = "mjihblbjfhdhaidkanpjbnmmcljbafdn";
57
+ exports2.DEXLY_PROD_EXTENSION_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmxkxUCWY3dbeEhjPxW7hOstyhtteCX3blvOTTGoGjju2p+je8eNVo7YDSqlgwRvOQw5N9LWmwb5Ui9wwVhQtLWyYqqq4aTVIAQhUhPDG6gRAUlLMXb/8e+ErWVx707d+aOVTgdc41qA2TVeUelVv/ARjcHL+xcxtk9pcmEMUGnWcZ1kPVx5FTQlLvEpFLVFsTMA6sff/6ba00/S1J28rEUdo2I61Dqku3ceMe3wNmLO4LJ9dj4dLxuYzpF/zH9vEVPWlYgyZY/NGadaAv1rxW4BSboqEOAK54vzcd5FfbxmvuYcRCVyN2A6WRU6sFUFpNrlzTOl+PpogNh+pa2qgxwIDAQAB";
58
+ exports2.DEXLY_PROD_EXTENSION_ID = "mkgnndfbolkjpiijcpkhmgjjhgcibdnp";
59
+ exports2.DEXLY_EXTENSION_IDENTITIES = {
60
+ development: {
61
+ channel: "development",
62
+ label: "development",
63
+ extensionId: exports2.DEXLY_DEV_EXTENSION_ID,
64
+ publicKey: exports2.DEXLY_DEV_EXTENSION_PUBLIC_KEY
65
+ },
66
+ production: {
67
+ channel: "production",
68
+ label: "production",
69
+ extensionId: exports2.DEXLY_PROD_EXTENSION_ID,
70
+ publicKey: exports2.DEXLY_PROD_EXTENSION_PUBLIC_KEY
71
+ }
72
+ };
73
+ exports2.DEXLY_FIXED_EXTENSION_IDS = [
74
+ exports2.DEXLY_EXTENSION_IDENTITIES.development.extensionId,
75
+ exports2.DEXLY_EXTENSION_IDENTITIES.production.extensionId
76
+ ];
77
+ exports2.DEXLY_DEFAULT_RELEASE_CHANNEL_BY_BUILD = {
78
+ development: "beta",
79
+ production: "latest"
80
+ };
81
+ function resolveDexlyExtensionBuildChannel(value) {
82
+ switch (value?.trim().toLowerCase()) {
83
+ case "dev":
84
+ case "development":
85
+ return "development";
86
+ case "prod":
87
+ case "production":
88
+ return "production";
89
+ default:
90
+ return "production";
91
+ }
92
+ }
93
+ function getDexlyExtensionIdentity(channel) {
94
+ return exports2.DEXLY_EXTENSION_IDENTITIES[channel];
95
+ }
96
+ function isDexlyFixedExtensionId(extensionId) {
97
+ return typeof extensionId === "string" && exports2.DEXLY_FIXED_EXTENSION_IDS.some((candidate) => candidate === extensionId);
98
+ }
99
+ function toChromeExtensionOrigin(extensionId) {
100
+ return `chrome-extension://${extensionId}/`;
101
+ }
102
+ function resolveDexlyExtensionBuildChannelFromExtensionId(extensionId) {
103
+ return extensionId === exports2.DEXLY_DEV_EXTENSION_ID ? "development" : "production";
104
+ }
105
+ function normalizeCompanionReleaseChannel(value) {
106
+ switch (value?.trim().toLowerCase()) {
107
+ case "latest":
108
+ case "beta":
109
+ case "canary":
110
+ return value.trim().toLowerCase();
111
+ default:
112
+ return null;
113
+ }
114
+ }
115
+ exports2.DEXLY_FIXED_ALLOWED_ORIGINS = exports2.DEXLY_FIXED_EXTENSION_IDS.map(toChromeExtensionOrigin);
116
+ exports2.DEFAULT_DIRECT_WEBSOCKET_ENDPOINT = "ws://127.0.0.1:4500";
117
+ }
118
+ });
119
+
120
+ // src/cli.ts
121
+ var import_node_path4 = __toESM(require("node:path"));
122
+ var import_node_process5 = __toESM(require("node:process"));
123
+ var import_companion6 = __toESM(require_companion());
124
+
125
+ // src/doctor.ts
126
+ var import_promises = require("node:fs/promises");
127
+ var import_node_child_process = require("node:child_process");
128
+ var import_node_events = require("node:events");
129
+ var import_companion2 = __toESM(require_companion());
130
+
131
+ // src/constants.ts
132
+ var import_node_os = __toESM(require("node:os"));
133
+ var import_node_path = __toESM(require("node:path"));
134
+ var import_companion = __toESM(require_companion());
135
+ var DEXLY_COMPANION_VERSION = true ? "0.1.0" : packageJson.version;
136
+ var DEXLY_COMPANION_DESCRIPTION = "Dexly native bridge for Codex";
137
+ var DEXLY_COMPANION_METADATA_FILE_NAME = "install-metadata.json";
138
+ function defaultInstallRoot(homeDir = import_node_os.default.homedir()) {
139
+ return import_node_path.default.join(homeDir, "Library", "Application Support", "Dexly", "companion");
140
+ }
141
+ function defaultChromeHostManifestDir(homeDir = import_node_os.default.homedir()) {
142
+ return import_node_path.default.join(homeDir, "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
143
+ }
144
+ function defaultChromeAllowedOrigins() {
145
+ return [...import_companion.DEXLY_FIXED_ALLOWED_ORIGINS];
146
+ }
147
+ function versionStorePath(installRoot) {
148
+ return import_node_path.default.join(installRoot, "versions");
149
+ }
150
+ function currentInstallPath(installRoot) {
151
+ return import_node_path.default.join(installRoot, "current");
152
+ }
153
+ function installMetadataPath(installRoot) {
154
+ return import_node_path.default.join(installRoot, DEXLY_COMPANION_METADATA_FILE_NAME);
155
+ }
156
+ function versionInstallPath(installRoot, version) {
157
+ return import_node_path.default.join(versionStorePath(installRoot), version);
158
+ }
159
+ function installedHostScriptPath(installRoot) {
160
+ return import_node_path.default.join(currentInstallPath(installRoot), "dist", "host.js");
161
+ }
162
+ function installedHostLauncherPath(installRoot) {
163
+ return import_node_path.default.join(currentInstallPath(installRoot), "bin", "dexly-companion-host");
164
+ }
165
+ function versionedCliPath(versionRoot) {
166
+ return import_node_path.default.join(versionRoot, "dist", "cli.js");
167
+ }
168
+ function versionedHostScriptPath(versionRoot) {
169
+ return import_node_path.default.join(versionRoot, "dist", "host.js");
170
+ }
171
+ function versionedHostLauncherPath(versionRoot) {
172
+ return import_node_path.default.join(versionRoot, "bin", "dexly-companion-host");
173
+ }
174
+ function versionedPackageJsonPath(versionRoot) {
175
+ return import_node_path.default.join(versionRoot, "package.json");
176
+ }
177
+ function installedHostManifestPath(manifestDir) {
178
+ return import_node_path.default.join(manifestDir, `${import_companion.DEXLY_COMPANION_HOST_NAME}.json`);
179
+ }
180
+ function buildChromeHostManifest(hostPath, allowedOrigins) {
181
+ return {
182
+ name: import_companion.DEXLY_COMPANION_HOST_NAME,
183
+ description: DEXLY_COMPANION_DESCRIPTION,
184
+ path: hostPath,
185
+ type: "stdio",
186
+ allowed_origins: allowedOrigins
187
+ };
188
+ }
189
+
190
+ // src/native-protocol.ts
191
+ var import_node_buffer = require("node:buffer");
192
+ function encodeNativeMessage(message) {
193
+ const body = import_node_buffer.Buffer.from(JSON.stringify(message), "utf8");
194
+ const header = import_node_buffer.Buffer.alloc(4);
195
+ header.writeUInt32LE(body.byteLength, 0);
196
+ return import_node_buffer.Buffer.concat([header, body]);
197
+ }
198
+ var NativeMessageParser = class {
199
+ buffered = import_node_buffer.Buffer.alloc(0);
200
+ push(chunk, handleMessage) {
201
+ this.buffered = import_node_buffer.Buffer.concat([this.buffered, chunk]);
202
+ while (this.buffered.byteLength >= 4) {
203
+ const bodyLength = this.buffered.readUInt32LE(0);
204
+ if (this.buffered.byteLength < bodyLength + 4) {
205
+ return;
206
+ }
207
+ const body = this.buffered.subarray(4, bodyLength + 4);
208
+ this.buffered = this.buffered.subarray(bodyLength + 4);
209
+ handleMessage(JSON.parse(body.toString("utf8")));
210
+ }
211
+ }
212
+ };
213
+ function createNativeMessageReader(input, handleMessage) {
214
+ const parser = new NativeMessageParser();
215
+ const onData = (chunk) => {
216
+ parser.push(import_node_buffer.Buffer.isBuffer(chunk) ? chunk : import_node_buffer.Buffer.from(chunk), handleMessage);
217
+ };
218
+ input.on("data", onData);
219
+ return () => {
220
+ input.off("data", onData);
221
+ };
222
+ }
223
+ function writeNativeMessage(output, message) {
224
+ output.write(encodeNativeMessage(message));
225
+ }
226
+
227
+ // src/doctor.ts
228
+ async function runDoctor(options) {
229
+ const installRoot = options?.installRoot ?? defaultInstallRoot();
230
+ const manifestDir = options?.manifestDir ?? defaultChromeHostManifestDir();
231
+ const manifestPath = installedHostManifestPath(manifestDir);
232
+ const hostLauncherPath = installedHostLauncherPath(installRoot);
233
+ const hostScriptPath = installedHostScriptPath(installRoot);
234
+ const metadataPath = installMetadataPath(installRoot);
235
+ const checks = [];
236
+ checks.push({
237
+ label: "platform",
238
+ ok: process.platform === "darwin",
239
+ detail: process.platform === "darwin" ? "macOS supported." : `Unsupported platform: ${process.platform}`
240
+ });
241
+ checks.push(await checkPath("host launcher", hostLauncherPath));
242
+ checks.push(await checkPath("host bundle", hostScriptPath));
243
+ checks.push(await checkPath("install metadata", metadataPath));
244
+ checks.push(await checkPath("native host manifest", manifestPath));
245
+ const manifestCheck = await checkHostManifest(manifestPath, hostLauncherPath);
246
+ checks.push(manifestCheck);
247
+ checks.push(await checkNativeHostRuntime(manifestPath));
248
+ const codexVersion = resolveCodexVersion();
249
+ checks.push({
250
+ label: "codex",
251
+ ok: codexVersion != null,
252
+ detail: codexVersion ?? "Codex CLI not found in PATH."
253
+ });
254
+ return {
255
+ ok: checks.every((check) => check.ok),
256
+ checks
257
+ };
258
+ }
259
+ async function checkPath(label, filePath) {
260
+ try {
261
+ await (0, import_promises.access)(filePath);
262
+ return {
263
+ label,
264
+ ok: true,
265
+ detail: filePath
266
+ };
267
+ } catch {
268
+ return {
269
+ label,
270
+ ok: false,
271
+ detail: `Missing: ${filePath}`
272
+ };
273
+ }
274
+ }
275
+ async function checkHostManifest(filePath, expectedHostPath) {
276
+ try {
277
+ const raw = await (0, import_promises.readFile)(filePath, "utf8");
278
+ const manifest = JSON.parse(raw);
279
+ const allowedOrigins = Array.isArray(manifest.allowed_origins) ? manifest.allowed_origins.filter((value) => typeof value === "string") : [];
280
+ const ok = manifest.name === import_companion2.DEXLY_COMPANION_HOST_NAME && typeof manifest.path === "string" && manifest.path === expectedHostPath && import_companion2.DEXLY_FIXED_ALLOWED_ORIGINS.every((origin) => allowedOrigins.includes(origin));
281
+ return {
282
+ label: "manifest contents",
283
+ ok,
284
+ detail: ok ? `Native host manifest is valid, points to ${expectedHostPath}, and includes ${import_companion2.DEXLY_FIXED_ALLOWED_ORIGINS.join(", ")}` : "Native host manifest is missing the expected name, expected launcher path, or one of Dexly's fixed extension origins."
285
+ };
286
+ } catch (error) {
287
+ return {
288
+ label: "manifest contents",
289
+ ok: false,
290
+ detail: error instanceof Error ? error.message : "Unable to read native host manifest."
291
+ };
292
+ }
293
+ }
294
+ async function checkNativeHostRuntime(manifestPath) {
295
+ const defaultOrigin = `chrome-extension://${import_companion2.DEXLY_DEV_EXTENSION_ID}/`;
296
+ try {
297
+ const raw = await (0, import_promises.readFile)(manifestPath, "utf8");
298
+ const manifest = JSON.parse(raw);
299
+ if (typeof manifest.path !== "string" || manifest.path.length === 0) {
300
+ return {
301
+ label: "native host runtime",
302
+ ok: false,
303
+ detail: "Native host manifest does not contain a valid executable path."
304
+ };
305
+ }
306
+ const response = await runHostHealthCheck(manifest.path, defaultOrigin);
307
+ if (response.kind === "host/error") {
308
+ return {
309
+ label: "native host runtime",
310
+ ok: false,
311
+ detail: `${response.code}: ${response.message}`
312
+ };
313
+ }
314
+ if (response.kind !== "host/result" || response.action !== "host/health") {
315
+ return {
316
+ label: "native host runtime",
317
+ ok: false,
318
+ detail: `Dexly Companion returned an unexpected message kind: ${response.kind}`
319
+ };
320
+ }
321
+ const healthResult = response.result;
322
+ const codexSuffix = healthResult.codexVersion ? ` and ${healthResult.codexVersion}` : "";
323
+ return {
324
+ label: "native host runtime",
325
+ ok: true,
326
+ detail: `Responded to host/health with ${healthResult.hostVersion}${codexSuffix}.`
327
+ };
328
+ } catch (error) {
329
+ return {
330
+ label: "native host runtime",
331
+ ok: false,
332
+ detail: error instanceof Error ? error.message : "Dexly Companion host smoke test failed."
333
+ };
334
+ }
335
+ }
336
+ async function runHostHealthCheck(hostPath, origin) {
337
+ const child = (0, import_node_child_process.spawn)(hostPath, [origin], {
338
+ stdio: ["pipe", "pipe", "pipe"]
339
+ });
340
+ const parser = new NativeMessageParser();
341
+ const stderrChunks = [];
342
+ try {
343
+ const messagePromise = new Promise((resolve, reject) => {
344
+ const timeout = setTimeout(() => {
345
+ reject(new Error("Dexly Companion did not answer a native host health check within 3 seconds."));
346
+ }, 3e3);
347
+ const cleanup = () => {
348
+ clearTimeout(timeout);
349
+ child.stdout.off("data", handleStdout);
350
+ child.stderr.off("data", handleStderr);
351
+ child.off("error", handleError);
352
+ child.off("exit", handleExit);
353
+ };
354
+ const finishResolve = (message) => {
355
+ cleanup();
356
+ resolve(message);
357
+ };
358
+ const finishReject = (error) => {
359
+ cleanup();
360
+ reject(error);
361
+ };
362
+ const handleStdout = (chunk) => {
363
+ parser.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), (message) => {
364
+ finishResolve(message);
365
+ });
366
+ };
367
+ const handleStderr = (chunk) => {
368
+ stderrChunks.push((Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)).toString("utf8"));
369
+ };
370
+ const handleError = (error) => {
371
+ finishReject(error);
372
+ };
373
+ const handleExit = (code, signal) => {
374
+ const stderrTail = stderrChunks.join("").trim();
375
+ finishReject(new Error(
376
+ stderrTail ? `Dexly Companion exited before replying (code ${code ?? "null"}, signal ${signal ?? "null"}): ${stderrTail}` : `Dexly Companion exited before replying (code ${code ?? "null"}, signal ${signal ?? "null"}).`
377
+ ));
378
+ };
379
+ child.stdout.on("data", handleStdout);
380
+ child.stderr.on("data", handleStderr);
381
+ child.once("error", handleError);
382
+ child.once("exit", handleExit);
383
+ });
384
+ child.stdin.write(
385
+ encodeNativeMessage({
386
+ kind: "host/health",
387
+ requestId: "doctor-health"
388
+ })
389
+ );
390
+ const response = await messagePromise;
391
+ child.stdin.end();
392
+ await Promise.race([
393
+ (0, import_node_events.once)(child, "exit"),
394
+ new Promise((resolve) => setTimeout(resolve, 250))
395
+ ]);
396
+ return response;
397
+ } finally {
398
+ child.kill();
399
+ }
400
+ }
401
+ function resolveCodexVersion() {
402
+ const result = (0, import_node_child_process.spawnSync)("codex", ["--version"], {
403
+ encoding: "utf8"
404
+ });
405
+ if (result.error || result.status !== 0) {
406
+ return null;
407
+ }
408
+ return result.stdout.trim() || null;
409
+ }
410
+
411
+ // src/host-runtime.ts
412
+ var import_node_process4 = __toESM(require("node:process"));
413
+
414
+ // src/codex-host.ts
415
+ var import_node_process3 = __toESM(require("node:process"));
416
+ var import_node_child_process3 = require("node:child_process");
417
+ var import_node_readline = __toESM(require("node:readline"));
418
+ var import_companion5 = __toESM(require_companion());
419
+
420
+ // src/management.ts
421
+ var import_promises4 = require("node:fs/promises");
422
+ var import_companion4 = __toESM(require_companion());
423
+
424
+ // src/install-metadata.ts
425
+ var import_promises2 = require("node:fs/promises");
426
+ var import_companion3 = __toESM(require_companion());
427
+ function normalizeString(value) {
428
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
429
+ }
430
+ function normalizeToolPaths(value) {
431
+ if (!value || typeof value !== "object") {
432
+ return null;
433
+ }
434
+ const candidate = value;
435
+ const nodePath = normalizeString(candidate.nodePath);
436
+ const npmPath = normalizeString(candidate.npmPath);
437
+ if (!nodePath || !npmPath) {
438
+ return null;
439
+ }
440
+ return {
441
+ nodePath,
442
+ npmPath,
443
+ codexPath: normalizeString(candidate.codexPath)
444
+ };
445
+ }
446
+ async function loadInstallMetadata(installRoot) {
447
+ try {
448
+ const raw = await (0, import_promises2.readFile)(installMetadataPath(installRoot), "utf8");
449
+ const parsed = JSON.parse(raw);
450
+ const tools = normalizeToolPaths(parsed.tools);
451
+ const currentVersion = normalizeString(parsed.currentVersion);
452
+ const installedAt = normalizeString(parsed.installedAt);
453
+ const updatedAt = normalizeString(parsed.updatedAt);
454
+ if (!tools || !currentVersion || !installedAt || !updatedAt) {
455
+ return null;
456
+ }
457
+ const knownGoodVersions = Array.isArray(parsed.knownGoodVersions) ? parsed.knownGoodVersions.filter((value) => typeof value === "string" && value.trim().length > 0) : [currentVersion];
458
+ return {
459
+ schemaVersion: 1,
460
+ packageName: import_companion3.DEXLY_COMPANION_PACKAGE_NAME,
461
+ executableName: import_companion3.DEXLY_COMPANION_EXECUTABLE_NAME,
462
+ currentVersion,
463
+ previousVersion: normalizeString(parsed.previousVersion),
464
+ knownGoodVersions: knownGoodVersions.length > 0 ? knownGoodVersions : [currentVersion],
465
+ currentChannel: normalizeString(parsed.currentChannel),
466
+ installedAt,
467
+ updatedAt,
468
+ tools
469
+ };
470
+ } catch {
471
+ return null;
472
+ }
473
+ }
474
+ async function saveInstallMetadata(installRoot, metadata) {
475
+ await (0, import_promises2.writeFile)(
476
+ installMetadataPath(installRoot),
477
+ `${JSON.stringify(metadata, null, 2)}
478
+ `,
479
+ "utf8"
480
+ );
481
+ }
482
+
483
+ // src/install.ts
484
+ var import_promises3 = require("node:fs/promises");
485
+ var import_node_path3 = __toESM(require("node:path"));
486
+ var import_node_process2 = __toESM(require("node:process"));
487
+
488
+ // src/tooling.ts
489
+ var import_node_path2 = __toESM(require("node:path"));
490
+ var import_node_process = __toESM(require("node:process"));
491
+ var import_node_child_process2 = require("node:child_process");
492
+ function detectToolPaths() {
493
+ const nodePath = import_node_process.default.execPath;
494
+ const npmPath = resolveExecutablePath("npm") ?? "npm";
495
+ const codexPath = resolveExecutablePath("codex");
496
+ return {
497
+ nodePath,
498
+ npmPath,
499
+ codexPath
500
+ };
501
+ }
502
+ function resolveExecutablePath(command, env = import_node_process.default.env) {
503
+ const result = (0, import_node_child_process2.spawnSync)("which", [command], {
504
+ encoding: "utf8",
505
+ env
506
+ });
507
+ if (result.status !== 0) {
508
+ return null;
509
+ }
510
+ const executablePath = result.stdout.trim();
511
+ return executablePath.length > 0 ? executablePath : null;
512
+ }
513
+ function buildToolEnv(toolPaths, extraPaths = []) {
514
+ const pathEntries = [
515
+ import_node_path2.default.dirname(toolPaths.nodePath),
516
+ import_node_path2.default.dirname(toolPaths.npmPath),
517
+ toolPaths.codexPath ? import_node_path2.default.dirname(toolPaths.codexPath) : null,
518
+ ...extraPaths,
519
+ "/usr/local/bin",
520
+ "/opt/homebrew/bin",
521
+ "/usr/bin",
522
+ "/bin",
523
+ "/usr/sbin",
524
+ "/sbin",
525
+ import_node_process.default.env.PATH ?? null
526
+ ].filter((entry, index, entries) => typeof entry === "string" && entries.indexOf(entry) === index);
527
+ return {
528
+ ...import_node_process.default.env,
529
+ PATH: pathEntries.join(":"),
530
+ ...toolPaths.codexPath ? { DEXLY_COMPANION_CODEX_PATH: toolPaths.codexPath } : {}
531
+ };
532
+ }
533
+ async function runCommand(command, args, options = {}) {
534
+ return await new Promise((resolve, reject) => {
535
+ const child = (0, import_node_child_process2.spawn)(command, args, {
536
+ cwd: options.cwd,
537
+ env: options.env,
538
+ stdio: "pipe"
539
+ });
540
+ let stdout = "";
541
+ let stderr = "";
542
+ child.stdout.on("data", (chunk) => {
543
+ stdout += (Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)).toString("utf8");
544
+ });
545
+ child.stderr.on("data", (chunk) => {
546
+ stderr += (Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)).toString("utf8");
547
+ });
548
+ child.once("error", (error) => {
549
+ reject(error);
550
+ });
551
+ child.once("exit", (code, signal) => {
552
+ if (code === 0) {
553
+ resolve({ stdout, stderr });
554
+ return;
555
+ }
556
+ const detail = stderr.trim() || stdout.trim();
557
+ reject(new Error(
558
+ detail.length > 0 ? `${command} ${args.join(" ")} failed (${code ?? signal ?? "unknown"}): ${detail}` : `${command} ${args.join(" ")} failed (${code ?? signal ?? "unknown"}).`
559
+ ));
560
+ });
561
+ });
562
+ }
563
+ function resolveCodexVersion2(codexCommand = import_node_process.default.env.DEXLY_COMPANION_CODEX_PATH ?? "codex", env = import_node_process.default.env) {
564
+ const result = (0, import_node_child_process2.spawnSync)(codexCommand, ["--version"], {
565
+ encoding: "utf8",
566
+ env
567
+ });
568
+ if (result.error || result.status !== 0) {
569
+ return null;
570
+ }
571
+ return result.stdout.trim() || null;
572
+ }
573
+ function resolveNpmGlobalBinDir(npmPath, env = import_node_process.default.env) {
574
+ const result = (0, import_node_child_process2.spawnSync)(npmPath, ["config", "get", "prefix"], {
575
+ encoding: "utf8",
576
+ env
577
+ });
578
+ if (result.error || result.status !== 0) {
579
+ return null;
580
+ }
581
+ const prefix = result.stdout.trim();
582
+ return prefix.length > 0 ? import_node_path2.default.join(prefix, "bin") : null;
583
+ }
584
+
585
+ // src/install.ts
586
+ async function installCompanion(options) {
587
+ if (import_node_process2.default.platform !== "darwin") {
588
+ throw new Error("Dexly Companion phase one currently supports macOS only.");
589
+ }
590
+ await assertInstallablePackageExists(options.sourcePackageDir);
591
+ const installRoot = options.installRoot ?? defaultInstallRoot();
592
+ const manifestDir = options.manifestDir ?? defaultChromeHostManifestDir();
593
+ const manifestPath = installedHostManifestPath(manifestDir);
594
+ const hostPath = installedHostLauncherPath(installRoot);
595
+ const allowedOrigins = defaultChromeAllowedOrigins();
596
+ const toolPaths = options.toolPaths ?? detectToolPaths();
597
+ const sourceDistDir = import_node_path3.default.join(options.sourcePackageDir, "dist");
598
+ const sourcePackageJsonPath = import_node_path3.default.join(options.sourcePackageDir, "package.json");
599
+ const versionRoot = versionInstallPath(installRoot, DEXLY_COMPANION_VERSION);
600
+ const stagingVersionRoot = `${versionRoot}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
601
+ await (0, import_promises3.mkdir)(versionStorePath(installRoot), { recursive: true });
602
+ await (0, import_promises3.rm)(stagingVersionRoot, { recursive: true, force: true });
603
+ try {
604
+ await (0, import_promises3.mkdir)(import_node_path3.default.join(stagingVersionRoot, "dist"), { recursive: true });
605
+ await (0, import_promises3.cp)(sourceDistDir, import_node_path3.default.join(stagingVersionRoot, "dist"), { recursive: true });
606
+ await (0, import_promises3.copyFile)(sourcePackageJsonPath, versionedPackageJsonPath(stagingVersionRoot));
607
+ await (0, import_promises3.chmod)(versionedCliPath(stagingVersionRoot), 493);
608
+ await (0, import_promises3.chmod)(versionedHostScriptPath(stagingVersionRoot), 493);
609
+ await (0, import_promises3.mkdir)(import_node_path3.default.dirname(versionedHostLauncherPath(stagingVersionRoot)), { recursive: true });
610
+ await (0, import_promises3.writeFile)(
611
+ versionedHostLauncherPath(stagingVersionRoot),
612
+ renderHostLauncher({
613
+ nodePath: toolPaths.nodePath,
614
+ hostScriptPath: installedHostScriptPath(installRoot),
615
+ codexPath: toolPaths.codexPath
616
+ }),
617
+ "utf8"
618
+ );
619
+ await (0, import_promises3.chmod)(versionedHostLauncherPath(stagingVersionRoot), 493);
620
+ await (0, import_promises3.rm)(versionRoot, { recursive: true, force: true });
621
+ await (0, import_promises3.rename)(stagingVersionRoot, versionRoot);
622
+ } catch (error) {
623
+ await (0, import_promises3.rm)(stagingVersionRoot, { recursive: true, force: true }).catch(() => void 0);
624
+ throw error;
625
+ }
626
+ await activateInstalledVersion(installRoot, DEXLY_COMPANION_VERSION);
627
+ await rewriteCurrentLauncher(installRoot, toolPaths);
628
+ await ensureChromeHostManifest(installRoot, manifestDir);
629
+ const existingMetadata = await loadInstallMetadata(installRoot);
630
+ const now = (/* @__PURE__ */ new Date()).toISOString();
631
+ const metadata = {
632
+ schemaVersion: 1,
633
+ packageName: "@dexlyai/dexly",
634
+ executableName: "dexly",
635
+ currentVersion: DEXLY_COMPANION_VERSION,
636
+ previousVersion: existingMetadata && existingMetadata.currentVersion !== DEXLY_COMPANION_VERSION ? existingMetadata.currentVersion : existingMetadata?.previousVersion ?? null,
637
+ knownGoodVersions: [
638
+ DEXLY_COMPANION_VERSION,
639
+ ...(existingMetadata?.knownGoodVersions ?? []).filter((version) => version !== DEXLY_COMPANION_VERSION)
640
+ ].slice(0, 2),
641
+ currentChannel: options.channel ?? existingMetadata?.currentChannel ?? null,
642
+ installedAt: existingMetadata?.installedAt ?? now,
643
+ updatedAt: now,
644
+ tools: toolPaths
645
+ };
646
+ await saveInstallMetadata(installRoot, metadata);
647
+ await pruneVersionStore(installRoot, metadata.knownGoodVersions);
648
+ return {
649
+ installRoot,
650
+ manifestPath,
651
+ hostPath,
652
+ allowedOrigins,
653
+ version: DEXLY_COMPANION_VERSION,
654
+ metadataPath: import_node_path3.default.join(installRoot, "install-metadata.json")
655
+ };
656
+ }
657
+ async function activateInstalledVersion(installRoot, version) {
658
+ const currentPath = currentInstallPath(installRoot);
659
+ const tempLinkPath = `${currentPath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
660
+ const targetPath = import_node_path3.default.join("versions", version);
661
+ await (0, import_promises3.mkdir)(installRoot, { recursive: true });
662
+ await (0, import_promises3.rm)(tempLinkPath, { recursive: true, force: true });
663
+ await (0, import_promises3.symlink)(targetPath, tempLinkPath);
664
+ try {
665
+ await (0, import_promises3.rename)(tempLinkPath, currentPath);
666
+ } catch {
667
+ await (0, import_promises3.rm)(currentPath, { recursive: true, force: true });
668
+ await (0, import_promises3.rename)(tempLinkPath, currentPath);
669
+ }
670
+ }
671
+ async function rewriteCurrentLauncher(installRoot, toolPaths) {
672
+ const launcherPath = installedHostLauncherPath(installRoot);
673
+ await (0, import_promises3.mkdir)(import_node_path3.default.dirname(launcherPath), { recursive: true });
674
+ await (0, import_promises3.writeFile)(
675
+ launcherPath,
676
+ renderHostLauncher({
677
+ nodePath: toolPaths.nodePath,
678
+ hostScriptPath: installedHostScriptPath(installRoot),
679
+ codexPath: toolPaths.codexPath
680
+ }),
681
+ "utf8"
682
+ );
683
+ await (0, import_promises3.chmod)(launcherPath, 493);
684
+ return launcherPath;
685
+ }
686
+ async function ensureChromeHostManifest(installRoot, manifestDir) {
687
+ const manifestPath = installedHostManifestPath(manifestDir);
688
+ await (0, import_promises3.mkdir)(manifestDir, { recursive: true });
689
+ await (0, import_promises3.writeFile)(
690
+ manifestPath,
691
+ `${JSON.stringify(buildChromeHostManifest(installedHostLauncherPath(installRoot), defaultChromeAllowedOrigins()), null, 2)}
692
+ `,
693
+ "utf8"
694
+ );
695
+ return manifestPath;
696
+ }
697
+ async function pruneVersionStore(installRoot, keepVersions) {
698
+ const keep = new Set(keepVersions);
699
+ const versionsPath = versionStorePath(installRoot);
700
+ const entries = await (0, import_promises3.readdir)(versionsPath).catch(() => []);
701
+ await Promise.all(entries.map(async (entry) => {
702
+ if (keep.has(entry)) {
703
+ return;
704
+ }
705
+ await (0, import_promises3.rm)(import_node_path3.default.join(versionsPath, entry), { recursive: true, force: true });
706
+ }));
707
+ }
708
+ async function assertInstallablePackageExists(sourcePackageDir) {
709
+ const requiredPaths = [
710
+ import_node_path3.default.join(sourcePackageDir, "dist", "cli.js"),
711
+ import_node_path3.default.join(sourcePackageDir, "dist", "host.js"),
712
+ import_node_path3.default.join(sourcePackageDir, "package.json")
713
+ ];
714
+ for (const filePath of requiredPaths) {
715
+ try {
716
+ await (0, import_promises3.access)(filePath);
717
+ } catch {
718
+ throw new Error(
719
+ `Dexly Companion build output is missing ${filePath}. Run npm install && npm run dexly:build before install.`
720
+ );
721
+ }
722
+ }
723
+ }
724
+ function renderHostLauncher(options) {
725
+ const pathEntries = [
726
+ import_node_path3.default.dirname(options.nodePath),
727
+ options.codexPath ? import_node_path3.default.dirname(options.codexPath) : null,
728
+ "/usr/local/bin",
729
+ "/opt/homebrew/bin",
730
+ "/usr/bin",
731
+ "/bin",
732
+ "/usr/sbin",
733
+ "/sbin"
734
+ ].filter((entry, index, entries) => typeof entry === "string" && entries.indexOf(entry) === index);
735
+ const lines = [
736
+ "#!/bin/sh",
737
+ "set -eu",
738
+ `export PATH=${toShellLiteral(pathEntries.join(":"))}`,
739
+ options.codexPath ? `export DEXLY_COMPANION_CODEX_PATH=${toShellLiteral(options.codexPath)}` : null,
740
+ `exec ${toShellLiteral(options.nodePath)} ${toShellLiteral(options.hostScriptPath)} "$@"`
741
+ ].filter((line) => typeof line === "string");
742
+ return `${lines.join("\n")}
743
+ `;
744
+ }
745
+ function toShellLiteral(value) {
746
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
747
+ }
748
+
749
+ // src/management.ts
750
+ function dedupeVersions(versions) {
751
+ return versions.filter((value, index) => versions.indexOf(value) === index);
752
+ }
753
+ function nextMetadata(metadata, nextVersion, channel) {
754
+ const now = (/* @__PURE__ */ new Date()).toISOString();
755
+ const currentVersion = metadata.currentVersion;
756
+ const knownGoodVersions = dedupeVersions([nextVersion, currentVersion, ...metadata.knownGoodVersions]).slice(0, 2);
757
+ return {
758
+ ...metadata,
759
+ currentVersion: nextVersion,
760
+ previousVersion: currentVersion === nextVersion ? metadata.previousVersion : currentVersion,
761
+ knownGoodVersions,
762
+ currentChannel: channel,
763
+ updatedAt: now
764
+ };
765
+ }
766
+ async function upgradeInstalledCompanion(options) {
767
+ const installRoot = options.installRoot ?? defaultInstallRoot();
768
+ const metadata = await loadInstallMetadata(installRoot);
769
+ if (!metadata) {
770
+ throw new Error("Dexly Companion install metadata is missing. Reinstall Dexly Companion first.");
771
+ }
772
+ const specifier = options.version?.trim() ? options.version.trim() : options.distTag.trim();
773
+ const targetSpecifier = `${import_companion4.DEXLY_COMPANION_PACKAGE_NAME}@${specifier}`;
774
+ const env = buildToolEnv(metadata.tools);
775
+ await runCommand(metadata.tools.npmPath, [
776
+ "exec",
777
+ "--yes",
778
+ `--package=${targetSpecifier}`,
779
+ import_companion4.DEXLY_COMPANION_EXECUTABLE_NAME,
780
+ "install",
781
+ "--channel",
782
+ options.distTag
783
+ ], { env });
784
+ const nextInstallMetadata = await loadInstallMetadata(installRoot);
785
+ if (!nextInstallMetadata) {
786
+ throw new Error("Dexly Companion upgraded, but the updated install metadata could not be read.");
787
+ }
788
+ return {
789
+ previousVersion: metadata.currentVersion,
790
+ currentVersion: nextInstallMetadata.currentVersion,
791
+ targetSpecifier
792
+ };
793
+ }
794
+ async function rollbackInstalledCompanion(options) {
795
+ const installRoot = options?.installRoot ?? defaultInstallRoot();
796
+ const manifestDir = options?.manifestDir ?? defaultChromeHostManifestDir();
797
+ const metadata = await loadInstallMetadata(installRoot);
798
+ if (!metadata) {
799
+ throw new Error("Dexly Companion install metadata is missing. Reinstall Dexly Companion first.");
800
+ }
801
+ const previousVersion = metadata.knownGoodVersions.find((version) => version !== metadata.currentVersion);
802
+ if (!previousVersion) {
803
+ throw new Error("Dexly Companion does not have a previous known-good version to roll back to.");
804
+ }
805
+ await (0, import_promises4.access)(versionInstallPath(installRoot, previousVersion));
806
+ await activateInstalledVersion(installRoot, previousVersion);
807
+ await rewriteCurrentLauncher(installRoot, metadata.tools);
808
+ await ensureChromeHostManifest(installRoot, manifestDir);
809
+ const next = nextMetadata(metadata, previousVersion, metadata.currentChannel);
810
+ next.previousVersion = metadata.currentVersion;
811
+ await saveInstallMetadata(installRoot, next);
812
+ return {
813
+ currentVersion: previousVersion,
814
+ previousVersion: metadata.currentVersion
815
+ };
816
+ }
817
+ async function installCodexWithCompanion(options) {
818
+ const installRoot = options?.installRoot ?? defaultInstallRoot();
819
+ const manifestDir = options?.manifestDir ?? defaultChromeHostManifestDir();
820
+ const existingMetadata = await loadInstallMetadata(installRoot);
821
+ const toolPaths = existingMetadata?.tools ?? detectToolPaths();
822
+ const env = buildToolEnv(toolPaths);
823
+ await runCommand(toolPaths.npmPath, ["install", "-g", "@openai/codex"], { env });
824
+ const globalBinDir = resolveNpmGlobalBinDir(toolPaths.npmPath, env);
825
+ const nextEnv = buildToolEnv(toolPaths, globalBinDir ? [globalBinDir] : []);
826
+ const codexPath = resolveExecutablePath("codex", nextEnv);
827
+ const codexVersion = resolveCodexVersion2(codexPath ?? void 0, nextEnv);
828
+ if (!codexPath || !codexVersion) {
829
+ throw new Error("Codex install completed, but Dexly Companion could not resolve the installed codex binary.");
830
+ }
831
+ const nextToolPaths = {
832
+ ...toolPaths,
833
+ codexPath
834
+ };
835
+ await rewriteCurrentLauncher(installRoot, nextToolPaths);
836
+ await ensureChromeHostManifest(installRoot, manifestDir);
837
+ if (existingMetadata) {
838
+ await saveInstallMetadata(installRoot, {
839
+ ...existingMetadata,
840
+ tools: nextToolPaths,
841
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
842
+ });
843
+ }
844
+ return {
845
+ codexPath,
846
+ codexVersion,
847
+ installCommand: import_companion4.DEXLY_CODEX_INSTALL_COMMAND
848
+ };
849
+ }
850
+
851
+ // src/codex-host.ts
852
+ var STDERR_RING_BUFFER_LIMIT = 40;
853
+ var DexlyNativeHost = class {
854
+ send;
855
+ spawnProcess;
856
+ spawnProcessSync;
857
+ codexCommand;
858
+ requestProcessExit;
859
+ child = null;
860
+ childStdoutReader = null;
861
+ expectedChildExit = false;
862
+ stderrLines = [];
863
+ constructor(options) {
864
+ this.send = options.send;
865
+ this.spawnProcess = options.spawnProcess ?? import_node_child_process3.spawn;
866
+ this.spawnProcessSync = options.spawnProcessSync ?? import_node_child_process3.spawnSync;
867
+ this.codexCommand = options.codexCommand ?? import_node_process3.default.env.DEXLY_COMPANION_CODEX_PATH ?? "codex";
868
+ this.requestProcessExit = options.requestProcessExit ?? null;
869
+ }
870
+ async handleMessage(message) {
871
+ switch (message.kind) {
872
+ case "host/health":
873
+ this.send({
874
+ kind: "host/result",
875
+ action: "host/health",
876
+ requestId: message.requestId,
877
+ result: this.buildHealthResult()
878
+ });
879
+ return;
880
+ case "host/connect":
881
+ await this.handleConnect(message);
882
+ return;
883
+ case "host/disconnect":
884
+ await this.stopChild();
885
+ this.send({
886
+ kind: "host/result",
887
+ action: "host/disconnect",
888
+ requestId: message.requestId,
889
+ result: {
890
+ disconnected: true
891
+ }
892
+ });
893
+ return;
894
+ case "host/update":
895
+ await this.handleUpdate(message);
896
+ return;
897
+ case "host/install-codex":
898
+ await this.handleInstallCodex(message);
899
+ return;
900
+ case "codex/jsonrpc":
901
+ this.forwardJsonRpc(message.payload);
902
+ return;
903
+ }
904
+ }
905
+ async close() {
906
+ await this.stopChild();
907
+ }
908
+ buildHealthResult() {
909
+ const codexVersion = this.resolveCodexVersion();
910
+ return {
911
+ ready: codexVersion != null,
912
+ hostVersion: DEXLY_COMPANION_VERSION,
913
+ codexVersion,
914
+ codexInstalled: codexVersion != null,
915
+ capabilities: {
916
+ update: true,
917
+ installCodex: true,
918
+ rollback: true
919
+ }
920
+ };
921
+ }
922
+ async handleConnect(request) {
923
+ if (this.child) {
924
+ this.send({
925
+ kind: "host/result",
926
+ action: "host/connect",
927
+ requestId: request.requestId,
928
+ result: {
929
+ connected: true,
930
+ ...this.buildHealthResult()
931
+ }
932
+ });
933
+ return;
934
+ }
935
+ const codexVersion = this.resolveCodexVersion();
936
+ if (!codexVersion) {
937
+ this.send(this.buildError(
938
+ "host/connect",
939
+ request.requestId,
940
+ "codex_not_found",
941
+ "Codex CLI was not found. Install Codex locally and run Dexly Companion install again."
942
+ ));
943
+ return;
944
+ }
945
+ this.stderrLines = [];
946
+ this.expectedChildExit = false;
947
+ try {
948
+ const child = this.spawnProcess(this.codexCommand, ["app-server", "--listen", "stdio://"], {
949
+ stdio: ["pipe", "pipe", "pipe"]
950
+ });
951
+ await new Promise((resolve, reject) => {
952
+ let settled = false;
953
+ const finish = (error) => {
954
+ if (settled) {
955
+ return;
956
+ }
957
+ settled = true;
958
+ child.off("error", handleError);
959
+ child.off("spawn", handleSpawn);
960
+ if (error) {
961
+ reject(error);
962
+ return;
963
+ }
964
+ resolve();
965
+ };
966
+ const handleError = (error) => {
967
+ finish(error);
968
+ };
969
+ const handleSpawn = () => {
970
+ finish(null);
971
+ };
972
+ child.once("error", handleError);
973
+ child.once("spawn", handleSpawn);
974
+ });
975
+ this.child = child;
976
+ this.attachChildListeners(child);
977
+ this.send({
978
+ kind: "host/result",
979
+ action: "host/connect",
980
+ requestId: request.requestId,
981
+ result: {
982
+ connected: true,
983
+ hostVersion: DEXLY_COMPANION_VERSION,
984
+ codexVersion,
985
+ codexInstalled: true,
986
+ capabilities: {
987
+ update: true,
988
+ installCodex: true,
989
+ rollback: true
990
+ }
991
+ }
992
+ });
993
+ } catch (error) {
994
+ this.send(this.buildError(
995
+ "host/connect",
996
+ request.requestId,
997
+ "codex_spawn_failed",
998
+ error instanceof Error ? error.message : "Dexly Companion could not start Codex app-server."
999
+ ));
1000
+ }
1001
+ }
1002
+ async handleUpdate(request) {
1003
+ if (this.child) {
1004
+ this.send(this.buildError(
1005
+ "host/update",
1006
+ request.requestId,
1007
+ "host_busy",
1008
+ "Dexly Companion cannot update while a live Codex session is running."
1009
+ ));
1010
+ return;
1011
+ }
1012
+ try {
1013
+ const result = await upgradeInstalledCompanion({
1014
+ distTag: request.params.distTag,
1015
+ version: request.params.version ?? null
1016
+ });
1017
+ this.send({
1018
+ kind: "host/result",
1019
+ action: "host/update",
1020
+ requestId: request.requestId,
1021
+ result: {
1022
+ updated: true,
1023
+ previousVersion: result.previousVersion,
1024
+ targetSpecifier: result.targetSpecifier,
1025
+ ...this.buildHealthResult()
1026
+ }
1027
+ });
1028
+ this.requestProcessExit?.();
1029
+ } catch (error) {
1030
+ this.send(this.buildError(
1031
+ "host/update",
1032
+ request.requestId,
1033
+ "host_update_failed",
1034
+ error instanceof Error ? error.message : "Dexly Companion could not update itself."
1035
+ ));
1036
+ }
1037
+ }
1038
+ async handleInstallCodex(request) {
1039
+ if (this.child) {
1040
+ this.send(this.buildError(
1041
+ "host/install-codex",
1042
+ request.requestId,
1043
+ "host_busy",
1044
+ "Dexly Companion cannot install Codex while a live Codex session is running."
1045
+ ));
1046
+ return;
1047
+ }
1048
+ try {
1049
+ const result = await installCodexWithCompanion();
1050
+ this.send({
1051
+ kind: "host/result",
1052
+ action: "host/install-codex",
1053
+ requestId: request.requestId,
1054
+ result: {
1055
+ installed: true,
1056
+ installCommand: result.installCommand,
1057
+ ...this.buildHealthResult()
1058
+ }
1059
+ });
1060
+ } catch (error) {
1061
+ this.send(this.buildError(
1062
+ "host/install-codex",
1063
+ request.requestId,
1064
+ "codex_install_failed",
1065
+ error instanceof Error ? error.message : "Dexly Companion could not install Codex."
1066
+ ));
1067
+ }
1068
+ }
1069
+ attachChildListeners(child) {
1070
+ this.childStdoutReader = import_node_readline.default.createInterface({
1071
+ input: child.stdout
1072
+ });
1073
+ this.childStdoutReader.on("line", (line) => {
1074
+ if (!line.trim()) {
1075
+ return;
1076
+ }
1077
+ try {
1078
+ this.send({
1079
+ kind: "codex/jsonrpc",
1080
+ payload: JSON.parse(line)
1081
+ });
1082
+ } catch {
1083
+ this.send(this.buildError(
1084
+ null,
1085
+ null,
1086
+ "codex_protocol_error",
1087
+ "Dexly Companion received malformed JSON from Codex app-server.",
1088
+ {
1089
+ line
1090
+ }
1091
+ ));
1092
+ }
1093
+ });
1094
+ child.stderr.on("data", (chunk) => {
1095
+ const lines = chunk.toString("utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1096
+ this.stderrLines.push(...lines);
1097
+ if (this.stderrLines.length > STDERR_RING_BUFFER_LIMIT) {
1098
+ this.stderrLines.splice(0, this.stderrLines.length - STDERR_RING_BUFFER_LIMIT);
1099
+ }
1100
+ });
1101
+ child.on("exit", (code, signal) => {
1102
+ const stderrTail = this.stderrLines.length > 0 ? this.stderrLines.join("\n") : null;
1103
+ this.cleanupChild();
1104
+ if (this.expectedChildExit) {
1105
+ this.expectedChildExit = false;
1106
+ return;
1107
+ }
1108
+ this.send(this.buildError(
1109
+ null,
1110
+ null,
1111
+ "codex_exit",
1112
+ "Codex app-server exited unexpectedly.",
1113
+ {
1114
+ code,
1115
+ signal,
1116
+ stderrTail
1117
+ }
1118
+ ));
1119
+ });
1120
+ }
1121
+ forwardJsonRpc(payload) {
1122
+ if (!this.child) {
1123
+ this.send(this.buildError(
1124
+ null,
1125
+ null,
1126
+ "codex_not_connected",
1127
+ `${import_companion5.DEXLY_COMPANION_DISPLAY_NAME} is not connected to Codex app-server.`
1128
+ ));
1129
+ return;
1130
+ }
1131
+ this.child.stdin.write(`${JSON.stringify(payload)}
1132
+ `);
1133
+ }
1134
+ async stopChild() {
1135
+ const child = this.child;
1136
+ if (!child) {
1137
+ return;
1138
+ }
1139
+ this.expectedChildExit = true;
1140
+ await new Promise((resolve) => {
1141
+ const finish = () => {
1142
+ child.off("exit", finish);
1143
+ resolve();
1144
+ };
1145
+ child.once("exit", finish);
1146
+ child.kill();
1147
+ });
1148
+ this.cleanupChild();
1149
+ this.expectedChildExit = false;
1150
+ }
1151
+ cleanupChild() {
1152
+ this.childStdoutReader?.close();
1153
+ this.childStdoutReader = null;
1154
+ this.child = null;
1155
+ }
1156
+ resolveCodexVersion() {
1157
+ const result = this.spawnProcessSync(this.codexCommand, ["--version"], {
1158
+ encoding: "utf8"
1159
+ });
1160
+ if (result.error || result.status !== 0) {
1161
+ return null;
1162
+ }
1163
+ return result.stdout.trim() || null;
1164
+ }
1165
+ buildError(action, requestId, code, message, details) {
1166
+ return {
1167
+ kind: "host/error",
1168
+ action,
1169
+ requestId,
1170
+ code,
1171
+ message,
1172
+ details
1173
+ };
1174
+ }
1175
+ };
1176
+
1177
+ // src/host-runtime.ts
1178
+ async function runNativeHost() {
1179
+ const host = new DexlyNativeHost({
1180
+ send: (message) => writeNativeMessage(import_node_process4.default.stdout, message),
1181
+ requestProcessExit: () => {
1182
+ setTimeout(() => import_node_process4.default.exit(0), 25);
1183
+ }
1184
+ });
1185
+ import_node_process4.default.stdout.on("error", (error) => {
1186
+ if ("code" in error && error.code === "EPIPE") {
1187
+ void host.close().finally(() => import_node_process4.default.exit(0));
1188
+ }
1189
+ });
1190
+ const detachReader = createNativeMessageReader(import_node_process4.default.stdin, (message) => {
1191
+ void host.handleMessage(message).catch((error) => {
1192
+ writeNativeMessage(import_node_process4.default.stdout, {
1193
+ kind: "host/error",
1194
+ action: null,
1195
+ requestId: null,
1196
+ code: "host_runtime_error",
1197
+ message: error instanceof Error ? error.message : "Dexly Companion host failed."
1198
+ });
1199
+ });
1200
+ });
1201
+ const shutdown = async () => {
1202
+ detachReader();
1203
+ await host.close();
1204
+ };
1205
+ import_node_process4.default.stdin.on("end", () => {
1206
+ void shutdown().finally(() => import_node_process4.default.exit(0));
1207
+ });
1208
+ import_node_process4.default.on("SIGINT", () => {
1209
+ void shutdown().finally(() => import_node_process4.default.exit(0));
1210
+ });
1211
+ import_node_process4.default.on("SIGTERM", () => {
1212
+ void shutdown().finally(() => import_node_process4.default.exit(0));
1213
+ });
1214
+ }
1215
+
1216
+ // src/cli.ts
1217
+ async function main() {
1218
+ const [, , rawCommand, ...args] = import_node_process5.default.argv;
1219
+ const command = resolveCommand(rawCommand);
1220
+ switch (command) {
1221
+ case "host":
1222
+ await runHost();
1223
+ return;
1224
+ case "install":
1225
+ await runInstall(args);
1226
+ return;
1227
+ case "doctor":
1228
+ await runDoctorCommand(args);
1229
+ return;
1230
+ case "upgrade":
1231
+ await runUpgradeCommand(args);
1232
+ return;
1233
+ case "rollback":
1234
+ await runRollbackCommand(args);
1235
+ return;
1236
+ case "install-codex":
1237
+ await runInstallCodexCommand(args);
1238
+ return;
1239
+ case "version":
1240
+ import_node_process5.default.stdout.write(`${DEXLY_COMPANION_VERSION}
1241
+ `);
1242
+ return;
1243
+ default:
1244
+ throw new Error(`Unknown Dexly Companion command: ${command}`);
1245
+ }
1246
+ }
1247
+ function resolveCommand(rawCommand) {
1248
+ if (rawCommand?.startsWith("chrome-extension://") || rawCommand?.startsWith("--parent-window=")) {
1249
+ return "host";
1250
+ }
1251
+ if (!rawCommand) {
1252
+ return import_node_process5.default.stdin.isTTY ? "version" : "host";
1253
+ }
1254
+ return rawCommand;
1255
+ }
1256
+ async function runHost() {
1257
+ await runNativeHost();
1258
+ }
1259
+ async function runInstall(args) {
1260
+ const parsed = parseArgs(args);
1261
+ const result = await installCompanion({
1262
+ sourcePackageDir: import_node_path4.default.resolve(__dirname, ".."),
1263
+ installRoot: parsed.installRoot,
1264
+ manifestDir: parsed.manifestDir,
1265
+ channel: parsed.channel
1266
+ });
1267
+ import_node_process5.default.stdout.write(
1268
+ [
1269
+ "Dexly Companion installed.",
1270
+ `Install root: ${result.installRoot}`,
1271
+ `Host manifest: ${result.manifestPath}`,
1272
+ `Host path: ${result.hostPath}`,
1273
+ `Version: ${result.version}`,
1274
+ `Allowed origins: ${result.allowedOrigins.join(", ")}`
1275
+ ].join("\n") + "\n"
1276
+ );
1277
+ }
1278
+ async function runDoctorCommand(args) {
1279
+ const parsed = parseArgs(args);
1280
+ const result = await runDoctor({
1281
+ installRoot: parsed.installRoot,
1282
+ manifestDir: parsed.manifestDir
1283
+ });
1284
+ for (const check of result.checks) {
1285
+ import_node_process5.default.stdout.write(`${check.ok ? "OK" : "FAIL"} ${check.label}: ${check.detail}
1286
+ `);
1287
+ }
1288
+ if (!result.ok) {
1289
+ import_node_process5.default.stdout.write(`
1290
+ If Dexly Companion is missing, run ${import_companion6.DEXLY_COMPANION_INSTALL_COMMAND}.
1291
+ `);
1292
+ import_node_process5.default.exitCode = 1;
1293
+ }
1294
+ }
1295
+ async function runUpgradeCommand(args) {
1296
+ const parsed = parseArgs(args);
1297
+ const result = await upgradeInstalledCompanion({
1298
+ installRoot: parsed.installRoot,
1299
+ manifestDir: parsed.manifestDir,
1300
+ distTag: parsed.distTag ?? parsed.channel ?? "latest",
1301
+ version: parsed.version
1302
+ });
1303
+ import_node_process5.default.stdout.write(
1304
+ [
1305
+ "Dexly Companion upgraded.",
1306
+ `Previous version: ${result.previousVersion}`,
1307
+ `Current version: ${result.currentVersion}`,
1308
+ `Target specifier: ${result.targetSpecifier}`
1309
+ ].join("\n") + "\n"
1310
+ );
1311
+ }
1312
+ async function runRollbackCommand(args) {
1313
+ const parsed = parseArgs(args);
1314
+ const result = await rollbackInstalledCompanion({
1315
+ installRoot: parsed.installRoot,
1316
+ manifestDir: parsed.manifestDir
1317
+ });
1318
+ import_node_process5.default.stdout.write(
1319
+ [
1320
+ "Dexly Companion rolled back.",
1321
+ `Current version: ${result.currentVersion}`,
1322
+ `Replaced version: ${result.previousVersion}`
1323
+ ].join("\n") + "\n"
1324
+ );
1325
+ }
1326
+ async function runInstallCodexCommand(args) {
1327
+ const parsed = parseArgs(args);
1328
+ const result = await installCodexWithCompanion({
1329
+ installRoot: parsed.installRoot,
1330
+ manifestDir: parsed.manifestDir
1331
+ });
1332
+ import_node_process5.default.stdout.write(
1333
+ [
1334
+ "Codex installed for Dexly Companion.",
1335
+ `Codex path: ${result.codexPath}`,
1336
+ `Codex version: ${result.codexVersion}`,
1337
+ `Install command: ${result.installCommand}`
1338
+ ].join("\n") + "\n"
1339
+ );
1340
+ }
1341
+ function parseArgs(args) {
1342
+ const parsed = {};
1343
+ for (let index = 0; index < args.length; index += 1) {
1344
+ const arg = args[index];
1345
+ const next = args[index + 1];
1346
+ if (arg === "--extension-id" || arg.startsWith("--extension-id=")) {
1347
+ throw new Error(
1348
+ "Dynamic extension ids are no longer supported. Dexly Companion now trusts only Dexly's fixed development and production extension ids."
1349
+ );
1350
+ }
1351
+ if (arg === "--install-root" && typeof next === "string") {
1352
+ parsed.installRoot = next;
1353
+ index += 1;
1354
+ continue;
1355
+ }
1356
+ if (arg.startsWith("--install-root=")) {
1357
+ parsed.installRoot = arg.slice("--install-root=".length);
1358
+ continue;
1359
+ }
1360
+ if (arg === "--manifest-dir" && typeof next === "string") {
1361
+ parsed.manifestDir = next;
1362
+ index += 1;
1363
+ continue;
1364
+ }
1365
+ if (arg.startsWith("--manifest-dir=")) {
1366
+ parsed.manifestDir = arg.slice("--manifest-dir=".length);
1367
+ continue;
1368
+ }
1369
+ if (arg === "--channel" && typeof next === "string") {
1370
+ parsed.channel = next;
1371
+ index += 1;
1372
+ continue;
1373
+ }
1374
+ if (arg.startsWith("--channel=")) {
1375
+ parsed.channel = arg.slice("--channel=".length);
1376
+ continue;
1377
+ }
1378
+ if (arg === "--dist-tag" && typeof next === "string") {
1379
+ parsed.distTag = next;
1380
+ index += 1;
1381
+ continue;
1382
+ }
1383
+ if (arg.startsWith("--dist-tag=")) {
1384
+ parsed.distTag = arg.slice("--dist-tag=".length);
1385
+ continue;
1386
+ }
1387
+ if (arg === "--version" && typeof next === "string") {
1388
+ parsed.version = next;
1389
+ index += 1;
1390
+ continue;
1391
+ }
1392
+ if (arg.startsWith("--version=")) {
1393
+ parsed.version = arg.slice("--version=".length);
1394
+ continue;
1395
+ }
1396
+ }
1397
+ return parsed;
1398
+ }
1399
+ void main().catch((error) => {
1400
+ const message = error instanceof Error ? error.message : "Dexly Companion command failed.";
1401
+ import_node_process5.default.stderr.write(`${message}
1402
+ `);
1403
+ import_node_process5.default.exit(1);
1404
+ });