@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.
- package/README.md +61 -0
- package/dist/cli.js +1404 -0
- package/dist/host.js +859 -0
- 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
|
+
});
|