@dexlyai/dexly 0.1.1 → 0.1.3

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 +4 -2
  2. package/dist/cli.js +611 -300
  3. package/dist/host.js +332 -111
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
  "use strict";
4
3
  var __create = Object.create;
5
4
  var __defProp = Object.defineProperty;
@@ -118,29 +117,21 @@ var require_companion = __commonJS({
118
117
  });
119
118
 
120
119
  // src/cli.ts
121
- var import_node_path4 = __toESM(require("node:path"));
120
+ var import_node_path5 = __toESM(require("node:path"));
122
121
  var import_node_process5 = __toESM(require("node:process"));
123
- var import_companion6 = __toESM(require_companion());
122
+ var import_companion7 = __toESM(require_companion());
124
123
 
125
124
  // src/doctor.ts
126
- var import_promises = require("node:fs/promises");
127
- var import_node_child_process = require("node:child_process");
125
+ var import_promises2 = require("node:fs/promises");
128
126
  var import_node_events = require("node:events");
129
- var import_companion2 = __toESM(require_companion());
127
+ var import_companion4 = __toESM(require_companion());
130
128
 
131
129
  // src/constants.ts
132
- var import_node_os = __toESM(require("node:os"));
133
130
  var import_node_path = __toESM(require("node:path"));
134
131
  var import_companion = __toESM(require_companion());
135
- var DEXLY_COMPANION_VERSION = true ? "0.1.1" : packageJson.version;
132
+ var DEXLY_COMPANION_VERSION = true ? "0.1.3" : packageJson.version;
136
133
  var DEXLY_COMPANION_DESCRIPTION = "Dexly native bridge for Codex";
137
134
  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
135
  function defaultChromeAllowedOrigins() {
145
136
  return [...import_companion.DEXLY_FIXED_ALLOWED_ORIGINS];
146
137
  }
@@ -159,8 +150,8 @@ function versionInstallPath(installRoot, version) {
159
150
  function installedHostScriptPath(installRoot) {
160
151
  return import_node_path.default.join(currentInstallPath(installRoot), "dist", "host.js");
161
152
  }
162
- function installedHostLauncherPath(installRoot) {
163
- return import_node_path.default.join(currentInstallPath(installRoot), "bin", "dexly-companion-host");
153
+ function installedHostLauncherPath(installRoot, platform = process.platform) {
154
+ return import_node_path.default.join(currentInstallPath(installRoot), "bin", resolveLauncherFileName(platform));
164
155
  }
165
156
  function versionedCliPath(versionRoot) {
166
157
  return import_node_path.default.join(versionRoot, "dist", "cli.js");
@@ -168,15 +159,12 @@ function versionedCliPath(versionRoot) {
168
159
  function versionedHostScriptPath(versionRoot) {
169
160
  return import_node_path.default.join(versionRoot, "dist", "host.js");
170
161
  }
171
- function versionedHostLauncherPath(versionRoot) {
172
- return import_node_path.default.join(versionRoot, "bin", "dexly-companion-host");
162
+ function versionedHostLauncherPath(versionRoot, platform = process.platform) {
163
+ return import_node_path.default.join(versionRoot, "bin", resolveLauncherFileName(platform));
173
164
  }
174
165
  function versionedPackageJsonPath(versionRoot) {
175
166
  return import_node_path.default.join(versionRoot, "package.json");
176
167
  }
177
- function installedHostManifestPath(manifestDir) {
178
- return import_node_path.default.join(manifestDir, `${import_companion.DEXLY_COMPANION_HOST_NAME}.json`);
179
- }
180
168
  function buildChromeHostManifest(hostPath, allowedOrigins) {
181
169
  return {
182
170
  name: import_companion.DEXLY_COMPANION_HOST_NAME,
@@ -186,6 +174,68 @@ function buildChromeHostManifest(hostPath, allowedOrigins) {
186
174
  allowed_origins: allowedOrigins
187
175
  };
188
176
  }
177
+ function resolveLauncherFileName(platform) {
178
+ return platform === "win32" ? "dexly-companion-host.cmd" : "dexly-companion-host";
179
+ }
180
+
181
+ // src/install-metadata.ts
182
+ var import_promises = require("node:fs/promises");
183
+ var import_companion2 = __toESM(require_companion());
184
+ function normalizeString(value) {
185
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
186
+ }
187
+ function normalizeToolPaths(value) {
188
+ if (!value || typeof value !== "object") {
189
+ return null;
190
+ }
191
+ const candidate = value;
192
+ const nodePath = normalizeString(candidate.nodePath);
193
+ const npmPath = normalizeString(candidate.npmPath);
194
+ if (!nodePath || !npmPath) {
195
+ return null;
196
+ }
197
+ return {
198
+ nodePath,
199
+ npmPath,
200
+ codexPath: normalizeString(candidate.codexPath)
201
+ };
202
+ }
203
+ async function loadInstallMetadata(installRoot) {
204
+ try {
205
+ const raw = await (0, import_promises.readFile)(installMetadataPath(installRoot), "utf8");
206
+ const parsed = JSON.parse(raw);
207
+ const tools = normalizeToolPaths(parsed.tools);
208
+ const currentVersion = normalizeString(parsed.currentVersion);
209
+ const installedAt = normalizeString(parsed.installedAt);
210
+ const updatedAt = normalizeString(parsed.updatedAt);
211
+ if (!tools || !currentVersion || !installedAt || !updatedAt) {
212
+ return null;
213
+ }
214
+ const knownGoodVersions = Array.isArray(parsed.knownGoodVersions) ? parsed.knownGoodVersions.filter((value) => typeof value === "string" && value.trim().length > 0) : [currentVersion];
215
+ return {
216
+ schemaVersion: 1,
217
+ packageName: import_companion2.DEXLY_COMPANION_PACKAGE_NAME,
218
+ executableName: import_companion2.DEXLY_COMPANION_EXECUTABLE_NAME,
219
+ currentVersion,
220
+ previousVersion: normalizeString(parsed.previousVersion),
221
+ knownGoodVersions: knownGoodVersions.length > 0 ? knownGoodVersions : [currentVersion],
222
+ currentChannel: normalizeString(parsed.currentChannel),
223
+ installedAt,
224
+ updatedAt,
225
+ tools
226
+ };
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+ async function saveInstallMetadata(installRoot, metadata) {
232
+ await (0, import_promises.writeFile)(
233
+ installMetadataPath(installRoot),
234
+ `${JSON.stringify(metadata, null, 2)}
235
+ `,
236
+ "utf8"
237
+ );
238
+ }
189
239
 
190
240
  // src/native-protocol.ts
191
241
  var import_node_buffer = require("node:buffer");
@@ -224,33 +274,352 @@ function writeNativeMessage(output, message) {
224
274
  output.write(encodeNativeMessage(message));
225
275
  }
226
276
 
277
+ // src/platform.ts
278
+ var import_node_os = __toESM(require("node:os"));
279
+ var import_node_path2 = __toESM(require("node:path"));
280
+ var import_node_process = __toESM(require("node:process"));
281
+ var import_companion3 = __toESM(require_companion());
282
+ function assertSupportedPlatform(platform = import_node_process.default.platform) {
283
+ switch (platform) {
284
+ case "darwin":
285
+ case "linux":
286
+ case "win32":
287
+ return platform;
288
+ default:
289
+ throw new Error(
290
+ `Dexly Companion currently supports macOS, Windows, and Linux only. Unsupported platform: ${platform}.`
291
+ );
292
+ }
293
+ }
294
+ function describePlatform(platform = import_node_process.default.platform) {
295
+ switch (platform) {
296
+ case "darwin":
297
+ return "macOS";
298
+ case "linux":
299
+ return "Linux";
300
+ case "win32":
301
+ return "Windows";
302
+ default:
303
+ return platform;
304
+ }
305
+ }
306
+ function defaultChromeAllowedOrigins2() {
307
+ return [...import_companion3.DEXLY_FIXED_ALLOWED_ORIGINS];
308
+ }
309
+ function defaultInstallRoot(options = {}) {
310
+ const platform = assertSupportedPlatform(options.platform);
311
+ const homeDir = options.homeDir ?? import_node_os.default.homedir();
312
+ const env = options.env ?? import_node_process.default.env;
313
+ switch (platform) {
314
+ case "darwin":
315
+ return import_node_path2.default.join(homeDir, "Library", "Application Support", "Dexly", "companion");
316
+ case "linux": {
317
+ const dataHome = env.XDG_DATA_HOME?.trim() || import_node_path2.default.join(homeDir, ".local", "share");
318
+ return import_node_path2.default.join(dataHome, "Dexly", "companion");
319
+ }
320
+ case "win32": {
321
+ const localAppData = env.LOCALAPPDATA?.trim() || import_node_path2.default.join(homeDir, "AppData", "Local");
322
+ return import_node_path2.default.join(localAppData, "Dexly", "companion");
323
+ }
324
+ }
325
+ }
326
+ function resolveChromeHostRegistration(options = {}) {
327
+ const platform = assertSupportedPlatform(options.platform);
328
+ const homeDir = options.homeDir ?? import_node_os.default.homedir();
329
+ const env = options.env ?? import_node_process.default.env;
330
+ const installRoot = options.installRoot ?? defaultInstallRoot({
331
+ platform,
332
+ homeDir,
333
+ env
334
+ });
335
+ const manifestPath = options.manifestDir ? import_node_path2.default.join(options.manifestDir, `${import_companion3.DEXLY_COMPANION_HOST_NAME}.json`) : defaultChromeManifestPath({
336
+ platform,
337
+ homeDir,
338
+ env,
339
+ installRoot
340
+ });
341
+ if (platform === "win32") {
342
+ return {
343
+ kind: "windows-registry",
344
+ manifestPath,
345
+ allowedOrigins: defaultChromeAllowedOrigins2(),
346
+ registryKey: options.windowsRegistryKey ?? `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${import_companion3.DEXLY_COMPANION_HOST_NAME}`
347
+ };
348
+ }
349
+ return {
350
+ kind: "manifest-file",
351
+ manifestPath,
352
+ allowedOrigins: defaultChromeAllowedOrigins2(),
353
+ registryKey: null
354
+ };
355
+ }
356
+ function resolvePlatformLayout(options = {}) {
357
+ const platform = assertSupportedPlatform(options.platform);
358
+ const installRoot = options.installRoot ?? defaultInstallRoot(options);
359
+ return {
360
+ platform,
361
+ installRoot,
362
+ activationStrategy: platform === "win32" ? "copy" : "symlink",
363
+ launcherKind: platform === "win32" ? "windows-cmd" : "posix",
364
+ launcherFileName: platform === "win32" ? "dexly-companion-host.cmd" : "dexly-companion-host",
365
+ hostRegistration: resolveChromeHostRegistration({
366
+ ...options,
367
+ platform,
368
+ installRoot
369
+ })
370
+ };
371
+ }
372
+ function defaultLauncherPathEntries(platform = import_node_process.default.platform, env = import_node_process.default.env) {
373
+ switch (assertSupportedPlatform(platform)) {
374
+ case "darwin":
375
+ return ["/usr/local/bin", "/opt/homebrew/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"];
376
+ case "linux":
377
+ return ["/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"];
378
+ case "win32": {
379
+ const systemRoot = env.SystemRoot?.trim() || "C:\\Windows";
380
+ return [import_node_path2.default.join(systemRoot, "System32"), systemRoot];
381
+ }
382
+ }
383
+ }
384
+ function renderHostLauncher(options) {
385
+ const platform = assertSupportedPlatform(options.platform);
386
+ const pathEntries = [
387
+ import_node_path2.default.dirname(options.nodePath),
388
+ options.codexPath ? import_node_path2.default.dirname(options.codexPath) : null,
389
+ ...defaultLauncherPathEntries(platform, options.env ?? import_node_process.default.env)
390
+ ].filter((entry, index, entries) => typeof entry === "string" && entries.indexOf(entry) === index);
391
+ if (platform === "win32") {
392
+ const lines2 = [
393
+ "@echo off",
394
+ "setlocal",
395
+ `set "PATH=${toBatchLiteral(pathEntries.join(import_node_path2.default.delimiter))};%PATH%"`,
396
+ options.codexPath ? `set "DEXLY_COMPANION_CODEX_PATH=${toBatchLiteral(options.codexPath)}"` : null,
397
+ `set "DEXLY_COMPANION_NODE_PATH=${toBatchLiteral(options.nodePath)}"`,
398
+ `set "DEXLY_COMPANION_HOST_SCRIPT=${toBatchLiteral(options.hostScriptPath)}"`,
399
+ '"%DEXLY_COMPANION_NODE_PATH%" "%DEXLY_COMPANION_HOST_SCRIPT%" %*'
400
+ ].filter((line) => typeof line === "string");
401
+ return `${lines2.join("\r\n")}\r
402
+ `;
403
+ }
404
+ const lines = [
405
+ "#!/bin/sh",
406
+ "set -eu",
407
+ `export PATH=${toShellLiteral(pathEntries.join(import_node_path2.default.delimiter))}`,
408
+ options.codexPath ? `export DEXLY_COMPANION_CODEX_PATH=${toShellLiteral(options.codexPath)}` : null,
409
+ `exec ${toShellLiteral(options.nodePath)} ${toShellLiteral(options.hostScriptPath)} "$@"`
410
+ ].filter((line) => typeof line === "string");
411
+ return `${lines.join("\n")}
412
+ `;
413
+ }
414
+ function defaultChromeManifestPath(options) {
415
+ switch (options.platform) {
416
+ case "darwin":
417
+ return import_node_path2.default.join(
418
+ options.homeDir,
419
+ "Library",
420
+ "Application Support",
421
+ "Google",
422
+ "Chrome",
423
+ "NativeMessagingHosts",
424
+ `${import_companion3.DEXLY_COMPANION_HOST_NAME}.json`
425
+ );
426
+ case "linux": {
427
+ const configHome = options.env.XDG_CONFIG_HOME?.trim() || import_node_path2.default.join(options.homeDir, ".config");
428
+ return import_node_path2.default.join(
429
+ configHome,
430
+ "google-chrome",
431
+ "NativeMessagingHosts",
432
+ `${import_companion3.DEXLY_COMPANION_HOST_NAME}.json`
433
+ );
434
+ }
435
+ case "win32":
436
+ return import_node_path2.default.join(options.installRoot, "native-host", `${import_companion3.DEXLY_COMPANION_HOST_NAME}.json`);
437
+ }
438
+ }
439
+ function toShellLiteral(value) {
440
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
441
+ }
442
+ function toBatchLiteral(value) {
443
+ return value.replace(/%/g, "%%").replace(/"/g, '""');
444
+ }
445
+
446
+ // src/tooling.ts
447
+ var import_node_path3 = __toESM(require("node:path"));
448
+ var import_node_process2 = __toESM(require("node:process"));
449
+ var import_node_child_process = require("node:child_process");
450
+ function detectToolPaths() {
451
+ const nodePath = import_node_process2.default.execPath;
452
+ const npmPath = resolveExecutablePath("npm") ?? "npm";
453
+ const codexPath = resolveExecutablePath("codex");
454
+ return {
455
+ nodePath,
456
+ npmPath,
457
+ codexPath
458
+ };
459
+ }
460
+ function resolveExecutablePath(command, env = import_node_process2.default.env, platform = import_node_process2.default.platform) {
461
+ const lookupCommand = platform === "win32" ? "where" : "which";
462
+ const result = (0, import_node_child_process.spawnSync)(lookupCommand, [command], {
463
+ encoding: "utf8",
464
+ env
465
+ });
466
+ if (result.error || result.status !== 0) {
467
+ return null;
468
+ }
469
+ const executablePath = result.stdout.split(/\r?\n/).map((value) => value.trim()).find((value) => value.length > 0);
470
+ return executablePath ?? null;
471
+ }
472
+ function buildToolEnv(toolPaths, extraPaths = [], platform = import_node_process2.default.platform) {
473
+ const supportedPlatform = assertSupportedPlatform(platform);
474
+ const pathSeparator = supportedPlatform === "win32" ? ";" : ":";
475
+ const pathEntries = [
476
+ import_node_path3.default.dirname(toolPaths.nodePath),
477
+ import_node_path3.default.dirname(toolPaths.npmPath),
478
+ toolPaths.codexPath ? import_node_path3.default.dirname(toolPaths.codexPath) : null,
479
+ ...extraPaths,
480
+ ...defaultLauncherPathEntries(supportedPlatform, import_node_process2.default.env),
481
+ import_node_process2.default.env.PATH ?? null
482
+ ].filter((entry, index, entries) => typeof entry === "string" && entries.indexOf(entry) === index);
483
+ return {
484
+ ...import_node_process2.default.env,
485
+ PATH: pathEntries.join(pathSeparator),
486
+ ...toolPaths.codexPath ? { DEXLY_COMPANION_CODEX_PATH: toolPaths.codexPath } : {}
487
+ };
488
+ }
489
+ async function runCommand(command, args, options = {}) {
490
+ return await new Promise((resolve, reject) => {
491
+ const child = spawnManaged(command, args, {
492
+ cwd: options.cwd,
493
+ env: options.env,
494
+ stdio: "pipe"
495
+ });
496
+ let stdout = "";
497
+ let stderr = "";
498
+ child.stdout.on("data", (chunk) => {
499
+ stdout += (Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)).toString("utf8");
500
+ });
501
+ child.stderr.on("data", (chunk) => {
502
+ stderr += (Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)).toString("utf8");
503
+ });
504
+ child.once("error", (error) => {
505
+ reject(error);
506
+ });
507
+ child.once("exit", (code, signal) => {
508
+ if (code === 0) {
509
+ resolve({ stdout, stderr });
510
+ return;
511
+ }
512
+ const detail = stderr.trim() || stdout.trim();
513
+ reject(new Error(
514
+ detail.length > 0 ? `${command} ${args.join(" ")} failed (${code ?? signal ?? "unknown"}): ${detail}` : `${command} ${args.join(" ")} failed (${code ?? signal ?? "unknown"}).`
515
+ ));
516
+ });
517
+ });
518
+ }
519
+ function resolveCodexVersion(codexCommand = import_node_process2.default.env.DEXLY_COMPANION_CODEX_PATH ?? "codex", env = import_node_process2.default.env) {
520
+ const result = spawnManagedSync(codexCommand, ["--version"], {
521
+ encoding: "utf8",
522
+ env
523
+ });
524
+ if (result.error || result.status !== 0) {
525
+ return null;
526
+ }
527
+ return result.stdout.trim() || null;
528
+ }
529
+ function resolveNpmGlobalBinDir(npmPath, env = import_node_process2.default.env, platform = import_node_process2.default.platform) {
530
+ const result = spawnManagedSync(npmPath, ["config", "get", "prefix"], {
531
+ encoding: "utf8",
532
+ env
533
+ });
534
+ if (result.error || result.status !== 0) {
535
+ return null;
536
+ }
537
+ const prefix = result.stdout.trim();
538
+ if (prefix.length === 0) {
539
+ return null;
540
+ }
541
+ return npmGlobalBinDirFromPrefix(prefix, platform);
542
+ }
543
+ function spawnManaged(command, args, options = {}) {
544
+ const invocation = buildSpawnInvocation(command, args, options.env);
545
+ return (0, import_node_child_process.spawn)(invocation.command, invocation.args, {
546
+ ...options,
547
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
548
+ });
549
+ }
550
+ function spawnManagedSync(command, args, options = {}) {
551
+ const invocation = buildSpawnInvocation(command, args, options.env);
552
+ return (0, import_node_child_process.spawnSync)(invocation.command, invocation.args, {
553
+ ...options,
554
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
555
+ });
556
+ }
557
+ function buildSpawnInvocation(command, args, env = import_node_process2.default.env, platform = import_node_process2.default.platform) {
558
+ if (platform !== "win32" || !isWindowsCommandScript(command, platform)) {
559
+ return {
560
+ command,
561
+ args
562
+ };
563
+ }
564
+ return {
565
+ command: resolveWindowsCommandShell(env),
566
+ args: ["/d", "/s", "/c", quoteWindowsCommand([command, ...args])],
567
+ windowsVerbatimArguments: false
568
+ };
569
+ }
570
+ function isWindowsCommandScript(command, platform = import_node_process2.default.platform) {
571
+ if (platform !== "win32") {
572
+ return false;
573
+ }
574
+ const normalized = command.trim().toLowerCase();
575
+ return normalized.endsWith(".cmd") || normalized.endsWith(".bat");
576
+ }
577
+ function resolveWindowsCommandShell(env = import_node_process2.default.env) {
578
+ const comSpec = env.ComSpec?.trim();
579
+ if (comSpec) {
580
+ return comSpec;
581
+ }
582
+ const systemRoot = env.SystemRoot?.trim() || "C:\\Windows";
583
+ return import_node_path3.default.join(systemRoot, "System32", "cmd.exe");
584
+ }
585
+ function quoteWindowsCommand(parts) {
586
+ return parts.map(quoteWindowsArgument).join(" ");
587
+ }
588
+ function quoteWindowsArgument(value) {
589
+ if (value.length === 0) {
590
+ return '""';
591
+ }
592
+ if (!/[ \t"&()^<>|]/.test(value)) {
593
+ return value;
594
+ }
595
+ return `"${value.replace(/"/g, '""')}"`;
596
+ }
597
+ function npmGlobalBinDirFromPrefix(prefix, platform = import_node_process2.default.platform) {
598
+ return platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
599
+ }
600
+
227
601
  // 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);
602
+ async function runDoctor(options = {}) {
603
+ const layout = resolvePlatformLayout(options);
604
+ const installRoot = layout.installRoot;
605
+ const manifestPath = layout.hostRegistration.manifestPath;
606
+ const hostLauncherPath = installedHostLauncherPath(installRoot, layout.platform);
233
607
  const hostScriptPath = installedHostScriptPath(installRoot);
234
608
  const metadataPath = installMetadataPath(installRoot);
235
609
  const checks = [];
236
610
  checks.push({
237
611
  label: "platform",
238
- ok: process.platform === "darwin",
239
- detail: process.platform === "darwin" ? "macOS supported." : `Unsupported platform: ${process.platform}`
612
+ ok: true,
613
+ detail: `${describePlatform(layout.platform)} supported.`
240
614
  });
615
+ checks.push(await checkHostRegistration(layout.hostRegistration, options.registryReader));
241
616
  checks.push(await checkPath("host launcher", hostLauncherPath));
242
617
  checks.push(await checkPath("host bundle", hostScriptPath));
243
618
  checks.push(await checkPath("install metadata", metadataPath));
244
619
  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
- });
620
+ checks.push(await checkHostManifest(manifestPath, hostLauncherPath));
621
+ checks.push(await checkNativeHostRuntime(layout.hostRegistration, options.registryReader));
622
+ checks.push(await checkCodexAvailability(installRoot, layout.platform));
254
623
  return {
255
624
  ok: checks.every((check) => check.ok),
256
625
  checks
@@ -258,7 +627,7 @@ async function runDoctor(options) {
258
627
  }
259
628
  async function checkPath(label, filePath) {
260
629
  try {
261
- await (0, import_promises.access)(filePath);
630
+ await (0, import_promises2.access)(filePath);
262
631
  return {
263
632
  label,
264
633
  ok: true,
@@ -272,16 +641,39 @@ async function checkPath(label, filePath) {
272
641
  };
273
642
  }
274
643
  }
644
+ async function checkHostRegistration(registration, registryReader) {
645
+ if (registration.kind === "manifest-file") {
646
+ return {
647
+ label: "native host registration",
648
+ ok: true,
649
+ detail: registration.manifestPath
650
+ };
651
+ }
652
+ const registeredManifestPath = await (registryReader ?? readWindowsRegistryManifestPath)(registration.registryKey ?? "");
653
+ if (!registeredManifestPath) {
654
+ return {
655
+ label: "native host registration",
656
+ ok: false,
657
+ detail: `Chrome registry key is missing or empty: ${registration.registryKey}`
658
+ };
659
+ }
660
+ const ok = registeredManifestPath === registration.manifestPath;
661
+ return {
662
+ label: "native host registration",
663
+ ok,
664
+ detail: ok ? `Registry points to ${registeredManifestPath}` : `Registry points to ${registeredManifestPath}, expected ${registration.manifestPath}`
665
+ };
666
+ }
275
667
  async function checkHostManifest(filePath, expectedHostPath) {
276
668
  try {
277
- const raw = await (0, import_promises.readFile)(filePath, "utf8");
669
+ const raw = await (0, import_promises2.readFile)(filePath, "utf8");
278
670
  const manifest = JSON.parse(raw);
279
671
  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));
672
+ const ok = manifest.name === import_companion4.DEXLY_COMPANION_HOST_NAME && typeof manifest.path === "string" && manifest.path === expectedHostPath && import_companion4.DEXLY_FIXED_ALLOWED_ORIGINS.every((origin) => allowedOrigins.includes(origin));
281
673
  return {
282
674
  label: "manifest contents",
283
675
  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."
676
+ detail: ok ? `Native host manifest is valid, points to ${expectedHostPath}, and includes ${import_companion4.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
677
  };
286
678
  } catch (error) {
287
679
  return {
@@ -291,10 +683,18 @@ async function checkHostManifest(filePath, expectedHostPath) {
291
683
  };
292
684
  }
293
685
  }
294
- async function checkNativeHostRuntime(manifestPath) {
295
- const defaultOrigin = `chrome-extension://${import_companion2.DEXLY_DEV_EXTENSION_ID}/`;
686
+ async function checkNativeHostRuntime(registration, registryReader) {
687
+ const defaultOrigin = `chrome-extension://${import_companion4.DEXLY_DEV_EXTENSION_ID}/`;
688
+ const registeredManifestPath = registration.kind === "windows-registry" ? await (registryReader ?? readWindowsRegistryManifestPath)(registration.registryKey ?? "") : registration.manifestPath;
689
+ if (!registeredManifestPath) {
690
+ return {
691
+ label: "native host runtime",
692
+ ok: false,
693
+ detail: "Dexly Companion could not resolve the registered native host manifest path."
694
+ };
695
+ }
296
696
  try {
297
- const raw = await (0, import_promises.readFile)(manifestPath, "utf8");
697
+ const raw = await (0, import_promises2.readFile)(registeredManifestPath, "utf8");
298
698
  const manifest = JSON.parse(raw);
299
699
  if (typeof manifest.path !== "string" || manifest.path.length === 0) {
300
700
  return {
@@ -333,8 +733,32 @@ async function checkNativeHostRuntime(manifestPath) {
333
733
  };
334
734
  }
335
735
  }
736
+ async function checkCodexAvailability(installRoot, platform) {
737
+ const metadata = await loadInstallMetadata(installRoot);
738
+ const env = metadata ? buildToolEnv(metadata.tools, [], platform) : process.env;
739
+ const codexVersion = metadata ? resolveCodexVersion(metadata.tools.codexPath ?? void 0, env) : resolveCodexVersion(void 0, env);
740
+ if (codexVersion) {
741
+ return {
742
+ label: "codex",
743
+ ok: true,
744
+ detail: codexVersion
745
+ };
746
+ }
747
+ if (metadata?.tools.codexPath) {
748
+ return {
749
+ label: "codex",
750
+ ok: false,
751
+ detail: `Codex CLI was not available via stored install metadata (${metadata.tools.codexPath}).`
752
+ };
753
+ }
754
+ return {
755
+ label: "codex",
756
+ ok: false,
757
+ detail: "Codex CLI not found in stored install metadata or PATH."
758
+ };
759
+ }
336
760
  async function runHostHealthCheck(hostPath, origin) {
337
- const child = (0, import_node_child_process.spawn)(hostPath, [origin], {
761
+ const child = spawnManaged(hostPath, [origin], {
338
762
  stdio: ["pipe", "pipe", "pipe"]
339
763
  });
340
764
  const parser = new NativeMessageParser();
@@ -346,8 +770,8 @@ async function runHostHealthCheck(hostPath, origin) {
346
770
  }, 3e3);
347
771
  const cleanup = () => {
348
772
  clearTimeout(timeout);
349
- child.stdout.off("data", handleStdout);
350
- child.stderr.off("data", handleStderr);
773
+ child.stdout?.off("data", handleStdout);
774
+ child.stderr?.off("data", handleStderr);
351
775
  child.off("error", handleError);
352
776
  child.off("exit", handleExit);
353
777
  };
@@ -376,19 +800,19 @@ async function runHostHealthCheck(hostPath, origin) {
376
800
  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
801
  ));
378
802
  };
379
- child.stdout.on("data", handleStdout);
380
- child.stderr.on("data", handleStderr);
803
+ child.stdout?.on("data", handleStdout);
804
+ child.stderr?.on("data", handleStderr);
381
805
  child.once("error", handleError);
382
806
  child.once("exit", handleExit);
383
807
  });
384
- child.stdin.write(
808
+ child.stdin?.write(
385
809
  encodeNativeMessage({
386
810
  kind: "host/health",
387
811
  requestId: "doctor-health"
388
812
  })
389
813
  );
390
814
  const response = await messagePromise;
391
- child.stdin.end();
815
+ child.stdin?.end();
392
816
  await Promise.race([
393
817
  (0, import_node_events.once)(child, "exit"),
394
818
  new Promise((resolve) => setTimeout(resolve, 250))
@@ -398,14 +822,21 @@ async function runHostHealthCheck(hostPath, origin) {
398
822
  child.kill();
399
823
  }
400
824
  }
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) {
825
+ async function readWindowsRegistryManifestPath(registryKey) {
826
+ if (!registryKey.trim()) {
827
+ return null;
828
+ }
829
+ try {
830
+ const result = await runCommand("reg.exe", ["query", registryKey, "/ve"]);
831
+ const match = result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.includes("REG_SZ"));
832
+ if (!match) {
833
+ return null;
834
+ }
835
+ const [, value] = match.split(/REG_SZ/i);
836
+ return value?.trim() || null;
837
+ } catch {
406
838
  return null;
407
839
  }
408
- return result.stdout.trim() || null;
409
840
  }
410
841
 
411
842
  // src/host-runtime.ts
@@ -413,220 +844,62 @@ var import_node_process4 = __toESM(require("node:process"));
413
844
 
414
845
  // src/codex-host.ts
415
846
  var import_node_process3 = __toESM(require("node:process"));
416
- var import_node_child_process3 = require("node:child_process");
417
847
  var import_node_readline = __toESM(require("node:readline"));
418
- var import_companion5 = __toESM(require_companion());
848
+ var import_companion6 = __toESM(require_companion());
419
849
 
420
850
  // src/management.ts
421
851
  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
- }
852
+ var import_companion5 = __toESM(require_companion());
482
853
 
483
854
  // src/install.ts
484
855
  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
856
+ var import_node_path4 = __toESM(require("node:path"));
586
857
  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
858
  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);
859
+ const layout = resolvePlatformLayout(options);
860
+ const installRoot = layout.installRoot;
861
+ const manifestPath = layout.hostRegistration.manifestPath;
862
+ const hostPath = installedHostLauncherPath(installRoot, layout.platform);
595
863
  const allowedOrigins = defaultChromeAllowedOrigins();
596
864
  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");
865
+ const sourceDistDir = import_node_path4.default.join(options.sourcePackageDir, "dist");
866
+ const sourcePackageJsonPath = import_node_path4.default.join(options.sourcePackageDir, "package.json");
599
867
  const installVersion = await readInstallPackageVersion(sourcePackageJsonPath);
600
868
  const versionRoot = versionInstallPath(installRoot, installVersion);
601
869
  const stagingVersionRoot = `${versionRoot}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
602
870
  await (0, import_promises3.mkdir)(versionStorePath(installRoot), { recursive: true });
603
871
  await (0, import_promises3.rm)(stagingVersionRoot, { recursive: true, force: true });
604
872
  try {
605
- await (0, import_promises3.mkdir)(import_node_path3.default.join(stagingVersionRoot, "dist"), { recursive: true });
606
- await (0, import_promises3.cp)(sourceDistDir, import_node_path3.default.join(stagingVersionRoot, "dist"), { recursive: true });
873
+ await (0, import_promises3.mkdir)(import_node_path4.default.join(stagingVersionRoot, "dist"), { recursive: true });
874
+ await (0, import_promises3.cp)(sourceDistDir, import_node_path4.default.join(stagingVersionRoot, "dist"), { recursive: true });
607
875
  await (0, import_promises3.copyFile)(sourcePackageJsonPath, versionedPackageJsonPath(stagingVersionRoot));
608
- await (0, import_promises3.chmod)(versionedCliPath(stagingVersionRoot), 493);
609
- await (0, import_promises3.chmod)(versionedHostScriptPath(stagingVersionRoot), 493);
610
- await (0, import_promises3.mkdir)(import_node_path3.default.dirname(versionedHostLauncherPath(stagingVersionRoot)), { recursive: true });
876
+ await chmodIfSupported(layout.platform, versionedCliPath(stagingVersionRoot));
877
+ await chmodIfSupported(layout.platform, versionedHostScriptPath(stagingVersionRoot));
878
+ await (0, import_promises3.mkdir)(import_node_path4.default.dirname(versionedHostLauncherPath(stagingVersionRoot, layout.platform)), { recursive: true });
611
879
  await (0, import_promises3.writeFile)(
612
- versionedHostLauncherPath(stagingVersionRoot),
880
+ versionedHostLauncherPath(stagingVersionRoot, layout.platform),
613
881
  renderHostLauncher({
882
+ platform: layout.platform,
614
883
  nodePath: toolPaths.nodePath,
615
884
  hostScriptPath: installedHostScriptPath(installRoot),
616
885
  codexPath: toolPaths.codexPath
617
886
  }),
618
887
  "utf8"
619
888
  );
620
- await (0, import_promises3.chmod)(versionedHostLauncherPath(stagingVersionRoot), 493);
889
+ await chmodIfSupported(layout.platform, versionedHostLauncherPath(stagingVersionRoot, layout.platform));
621
890
  await (0, import_promises3.rm)(versionRoot, { recursive: true, force: true });
622
891
  await (0, import_promises3.rename)(stagingVersionRoot, versionRoot);
623
892
  } catch (error) {
624
893
  await (0, import_promises3.rm)(stagingVersionRoot, { recursive: true, force: true }).catch(() => void 0);
625
894
  throw error;
626
895
  }
627
- await activateInstalledVersion(installRoot, installVersion);
628
- await rewriteCurrentLauncher(installRoot, toolPaths);
629
- await ensureChromeHostManifest(installRoot, manifestDir);
896
+ await activateInstalledVersion(installRoot, installVersion, { platform: layout.platform });
897
+ await rewriteCurrentLauncher(installRoot, toolPaths, { platform: layout.platform });
898
+ await ensureChromeHostRegistration(installRoot, {
899
+ ...options,
900
+ platform: layout.platform,
901
+ registryWriter: options.registryWriter
902
+ });
630
903
  const existingMetadata = await loadInstallMetadata(installRoot);
631
904
  const now = (/* @__PURE__ */ new Date()).toISOString();
632
905
  const metadata = {
@@ -652,13 +925,30 @@ async function installCompanion(options) {
652
925
  hostPath,
653
926
  allowedOrigins,
654
927
  version: installVersion,
655
- metadataPath: import_node_path3.default.join(installRoot, "install-metadata.json")
928
+ metadataPath: import_node_path4.default.join(installRoot, "install-metadata.json")
656
929
  };
657
930
  }
658
- async function activateInstalledVersion(installRoot, version) {
931
+ async function activateInstalledVersion(installRoot, version, options = {}) {
932
+ const layout = resolvePlatformLayout({
933
+ installRoot,
934
+ platform: options.platform
935
+ });
659
936
  const currentPath = currentInstallPath(installRoot);
937
+ const versionPath = versionInstallPath(installRoot, version);
938
+ if (layout.activationStrategy === "copy") {
939
+ const stagingCurrentPath = `${currentPath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
940
+ await (0, import_promises3.rm)(stagingCurrentPath, { recursive: true, force: true });
941
+ await (0, import_promises3.cp)(versionPath, stagingCurrentPath, { recursive: true });
942
+ try {
943
+ await (0, import_promises3.rename)(stagingCurrentPath, currentPath);
944
+ } catch {
945
+ await (0, import_promises3.rm)(currentPath, { recursive: true, force: true });
946
+ await (0, import_promises3.rename)(stagingCurrentPath, currentPath);
947
+ }
948
+ return;
949
+ }
660
950
  const tempLinkPath = `${currentPath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
661
- const targetPath = import_node_path3.default.join("versions", version);
951
+ const targetPath = import_node_path4.default.join("versions", version);
662
952
  await (0, import_promises3.mkdir)(installRoot, { recursive: true });
663
953
  await (0, import_promises3.rm)(tempLinkPath, { recursive: true, force: true });
664
954
  await (0, import_promises3.symlink)(targetPath, tempLinkPath);
@@ -669,30 +959,51 @@ async function activateInstalledVersion(installRoot, version) {
669
959
  await (0, import_promises3.rename)(tempLinkPath, currentPath);
670
960
  }
671
961
  }
672
- async function rewriteCurrentLauncher(installRoot, toolPaths) {
673
- const launcherPath = installedHostLauncherPath(installRoot);
674
- await (0, import_promises3.mkdir)(import_node_path3.default.dirname(launcherPath), { recursive: true });
962
+ async function rewriteCurrentLauncher(installRoot, toolPaths, options = {}) {
963
+ const layout = resolvePlatformLayout({
964
+ installRoot,
965
+ platform: options.platform
966
+ });
967
+ const launcherPath = installedHostLauncherPath(installRoot, layout.platform);
968
+ await (0, import_promises3.mkdir)(import_node_path4.default.dirname(launcherPath), { recursive: true });
675
969
  await (0, import_promises3.writeFile)(
676
970
  launcherPath,
677
971
  renderHostLauncher({
972
+ platform: layout.platform,
678
973
  nodePath: toolPaths.nodePath,
679
974
  hostScriptPath: installedHostScriptPath(installRoot),
680
975
  codexPath: toolPaths.codexPath
681
976
  }),
682
977
  "utf8"
683
978
  );
684
- await (0, import_promises3.chmod)(launcherPath, 493);
979
+ await chmodIfSupported(layout.platform, launcherPath);
685
980
  return launcherPath;
686
981
  }
687
- async function ensureChromeHostManifest(installRoot, manifestDir) {
688
- const manifestPath = installedHostManifestPath(manifestDir);
689
- await (0, import_promises3.mkdir)(manifestDir, { recursive: true });
982
+ async function ensureChromeHostRegistration(installRoot, options = {}) {
983
+ const layout = resolvePlatformLayout({
984
+ installRoot,
985
+ platform: options.platform,
986
+ manifestDir: options.manifestDir,
987
+ windowsRegistryKey: options.windowsRegistryKey
988
+ });
989
+ const manifestPath = layout.hostRegistration.manifestPath;
990
+ await (0, import_promises3.mkdir)(import_node_path4.default.dirname(manifestPath), { recursive: true });
690
991
  await (0, import_promises3.writeFile)(
691
992
  manifestPath,
692
- `${JSON.stringify(buildChromeHostManifest(installedHostLauncherPath(installRoot), defaultChromeAllowedOrigins()), null, 2)}
993
+ `${JSON.stringify(
994
+ buildChromeHostManifest(installedHostLauncherPath(installRoot, layout.platform), layout.hostRegistration.allowedOrigins),
995
+ null,
996
+ 2
997
+ )}
693
998
  `,
694
999
  "utf8"
695
1000
  );
1001
+ if (layout.hostRegistration.kind === "windows-registry" && layout.hostRegistration.registryKey) {
1002
+ await (options.registryWriter ?? writeWindowsRegistryManifestPath)(
1003
+ layout.hostRegistration.registryKey,
1004
+ manifestPath
1005
+ );
1006
+ }
696
1007
  return manifestPath;
697
1008
  }
698
1009
  async function pruneVersionStore(installRoot, keepVersions) {
@@ -703,14 +1014,14 @@ async function pruneVersionStore(installRoot, keepVersions) {
703
1014
  if (keep.has(entry)) {
704
1015
  return;
705
1016
  }
706
- await (0, import_promises3.rm)(import_node_path3.default.join(versionsPath, entry), { recursive: true, force: true });
1017
+ await (0, import_promises3.rm)(import_node_path4.default.join(versionsPath, entry), { recursive: true, force: true });
707
1018
  }));
708
1019
  }
709
1020
  async function assertInstallablePackageExists(sourcePackageDir) {
710
1021
  const requiredPaths = [
711
- import_node_path3.default.join(sourcePackageDir, "dist", "cli.js"),
712
- import_node_path3.default.join(sourcePackageDir, "dist", "host.js"),
713
- import_node_path3.default.join(sourcePackageDir, "package.json")
1022
+ import_node_path4.default.join(sourcePackageDir, "dist", "cli.js"),
1023
+ import_node_path4.default.join(sourcePackageDir, "dist", "host.js"),
1024
+ import_node_path4.default.join(sourcePackageDir, "package.json")
714
1025
  ];
715
1026
  for (const filePath of requiredPaths) {
716
1027
  try {
@@ -729,29 +1040,23 @@ async function readInstallPackageVersion(packageJsonPath) {
729
1040
  }
730
1041
  return packageJson.version.trim();
731
1042
  }
732
- function renderHostLauncher(options) {
733
- const pathEntries = [
734
- import_node_path3.default.dirname(options.nodePath),
735
- options.codexPath ? import_node_path3.default.dirname(options.codexPath) : null,
736
- "/usr/local/bin",
737
- "/opt/homebrew/bin",
738
- "/usr/bin",
739
- "/bin",
740
- "/usr/sbin",
741
- "/sbin"
742
- ].filter((entry, index, entries) => typeof entry === "string" && entries.indexOf(entry) === index);
743
- const lines = [
744
- "#!/bin/sh",
745
- "set -eu",
746
- `export PATH=${toShellLiteral(pathEntries.join(":"))}`,
747
- options.codexPath ? `export DEXLY_COMPANION_CODEX_PATH=${toShellLiteral(options.codexPath)}` : null,
748
- `exec ${toShellLiteral(options.nodePath)} ${toShellLiteral(options.hostScriptPath)} "$@"`
749
- ].filter((line) => typeof line === "string");
750
- return `${lines.join("\n")}
751
- `;
1043
+ async function chmodIfSupported(platform, targetPath) {
1044
+ if (platform === "win32") {
1045
+ return;
1046
+ }
1047
+ await (0, import_promises3.chmod)(targetPath, 493);
752
1048
  }
753
- function toShellLiteral(value) {
754
- return `'${value.replace(/'/g, `'"'"'`)}'`;
1049
+ async function writeWindowsRegistryManifestPath(registryKey, manifestPath) {
1050
+ await runCommand("reg.exe", [
1051
+ "add",
1052
+ registryKey,
1053
+ "/ve",
1054
+ "/t",
1055
+ "REG_SZ",
1056
+ "/d",
1057
+ manifestPath,
1058
+ "/f"
1059
+ ]);
755
1060
  }
756
1061
 
757
1062
  // src/management.ts
@@ -772,19 +1077,19 @@ function nextMetadata(metadata, nextVersion, channel) {
772
1077
  };
773
1078
  }
774
1079
  async function upgradeInstalledCompanion(options) {
775
- const installRoot = options.installRoot ?? defaultInstallRoot();
1080
+ const installRoot = options.installRoot ?? defaultInstallRoot({ platform: options.platform });
776
1081
  const metadata = await loadInstallMetadata(installRoot);
777
1082
  if (!metadata) {
778
1083
  throw new Error("Dexly Companion install metadata is missing. Reinstall Dexly Companion first.");
779
1084
  }
780
1085
  const specifier = options.version?.trim() ? options.version.trim() : options.distTag.trim();
781
- const targetSpecifier = `${import_companion4.DEXLY_COMPANION_PACKAGE_NAME}@${specifier}`;
782
- const env = buildToolEnv(metadata.tools);
1086
+ const targetSpecifier = `${import_companion5.DEXLY_COMPANION_PACKAGE_NAME}@${specifier}`;
1087
+ const env = buildToolEnv(metadata.tools, [], options.platform);
783
1088
  await runCommand(metadata.tools.npmPath, [
784
1089
  "exec",
785
1090
  "--yes",
786
1091
  `--package=${targetSpecifier}`,
787
- import_companion4.DEXLY_COMPANION_EXECUTABLE_NAME,
1092
+ import_companion5.DEXLY_COMPANION_EXECUTABLE_NAME,
788
1093
  "install",
789
1094
  "--channel",
790
1095
  options.distTag
@@ -800,8 +1105,7 @@ async function upgradeInstalledCompanion(options) {
800
1105
  };
801
1106
  }
802
1107
  async function rollbackInstalledCompanion(options) {
803
- const installRoot = options?.installRoot ?? defaultInstallRoot();
804
- const manifestDir = options?.manifestDir ?? defaultChromeHostManifestDir();
1108
+ const installRoot = options?.installRoot ?? defaultInstallRoot({ platform: options?.platform });
805
1109
  const metadata = await loadInstallMetadata(installRoot);
806
1110
  if (!metadata) {
807
1111
  throw new Error("Dexly Companion install metadata is missing. Reinstall Dexly Companion first.");
@@ -811,9 +1115,13 @@ async function rollbackInstalledCompanion(options) {
811
1115
  throw new Error("Dexly Companion does not have a previous known-good version to roll back to.");
812
1116
  }
813
1117
  await (0, import_promises4.access)(versionInstallPath(installRoot, previousVersion));
814
- await activateInstalledVersion(installRoot, previousVersion);
815
- await rewriteCurrentLauncher(installRoot, metadata.tools);
816
- await ensureChromeHostManifest(installRoot, manifestDir);
1118
+ await activateInstalledVersion(installRoot, previousVersion, { platform: options?.platform });
1119
+ await rewriteCurrentLauncher(installRoot, metadata.tools, { platform: options?.platform });
1120
+ await ensureChromeHostRegistration(installRoot, {
1121
+ platform: options?.platform,
1122
+ manifestDir: options?.manifestDir,
1123
+ windowsRegistryKey: options?.windowsRegistryKey
1124
+ });
817
1125
  const next = nextMetadata(metadata, previousVersion, metadata.currentChannel);
818
1126
  next.previousVersion = metadata.currentVersion;
819
1127
  await saveInstallMetadata(installRoot, next);
@@ -823,16 +1131,15 @@ async function rollbackInstalledCompanion(options) {
823
1131
  };
824
1132
  }
825
1133
  async function installCodexWithCompanion(options) {
826
- const installRoot = options?.installRoot ?? defaultInstallRoot();
827
- const manifestDir = options?.manifestDir ?? defaultChromeHostManifestDir();
1134
+ const installRoot = options?.installRoot ?? defaultInstallRoot({ platform: options?.platform });
828
1135
  const existingMetadata = await loadInstallMetadata(installRoot);
829
1136
  const toolPaths = existingMetadata?.tools ?? detectToolPaths();
830
- const env = buildToolEnv(toolPaths);
1137
+ const env = buildToolEnv(toolPaths, [], options?.platform);
831
1138
  await runCommand(toolPaths.npmPath, ["install", "-g", "@openai/codex"], { env });
832
- const globalBinDir = resolveNpmGlobalBinDir(toolPaths.npmPath, env);
833
- const nextEnv = buildToolEnv(toolPaths, globalBinDir ? [globalBinDir] : []);
834
- const codexPath = resolveExecutablePath("codex", nextEnv);
835
- const codexVersion = resolveCodexVersion2(codexPath ?? void 0, nextEnv);
1139
+ const globalBinDir = resolveNpmGlobalBinDir(toolPaths.npmPath, env, options?.platform);
1140
+ const nextEnv = buildToolEnv(toolPaths, globalBinDir ? [globalBinDir] : [], options?.platform);
1141
+ const codexPath = resolveExecutablePath("codex", nextEnv, options?.platform);
1142
+ const codexVersion = resolveCodexVersion(codexPath ?? void 0, nextEnv);
836
1143
  if (!codexPath || !codexVersion) {
837
1144
  throw new Error("Codex install completed, but Dexly Companion could not resolve the installed codex binary.");
838
1145
  }
@@ -840,8 +1147,12 @@ async function installCodexWithCompanion(options) {
840
1147
  ...toolPaths,
841
1148
  codexPath
842
1149
  };
843
- await rewriteCurrentLauncher(installRoot, nextToolPaths);
844
- await ensureChromeHostManifest(installRoot, manifestDir);
1150
+ await rewriteCurrentLauncher(installRoot, nextToolPaths, { platform: options?.platform });
1151
+ await ensureChromeHostRegistration(installRoot, {
1152
+ platform: options?.platform,
1153
+ manifestDir: options?.manifestDir,
1154
+ windowsRegistryKey: options?.windowsRegistryKey
1155
+ });
845
1156
  if (existingMetadata) {
846
1157
  await saveInstallMetadata(installRoot, {
847
1158
  ...existingMetadata,
@@ -852,7 +1163,7 @@ async function installCodexWithCompanion(options) {
852
1163
  return {
853
1164
  codexPath,
854
1165
  codexVersion,
855
- installCommand: import_companion4.DEXLY_CODEX_INSTALL_COMMAND
1166
+ installCommand: import_companion5.DEXLY_CODEX_INSTALL_COMMAND
856
1167
  };
857
1168
  }
858
1169
 
@@ -870,8 +1181,8 @@ var DexlyNativeHost = class {
870
1181
  stderrLines = [];
871
1182
  constructor(options) {
872
1183
  this.send = options.send;
873
- this.spawnProcess = options.spawnProcess ?? import_node_child_process3.spawn;
874
- this.spawnProcessSync = options.spawnProcessSync ?? import_node_child_process3.spawnSync;
1184
+ this.spawnProcess = options.spawnProcess ?? ((command, args, spawnOptions) => spawnManaged(command, [...args], spawnOptions));
1185
+ this.spawnProcessSync = options.spawnProcessSync ?? ((command, args, spawnOptions) => spawnManagedSync(command, [...args], spawnOptions));
875
1186
  this.codexCommand = options.codexCommand ?? import_node_process3.default.env.DEXLY_COMPANION_CODEX_PATH ?? "codex";
876
1187
  this.requestProcessExit = options.requestProcessExit ?? null;
877
1188
  }
@@ -1132,7 +1443,7 @@ var DexlyNativeHost = class {
1132
1443
  null,
1133
1444
  null,
1134
1445
  "codex_not_connected",
1135
- `${import_companion5.DEXLY_COMPANION_DISPLAY_NAME} is not connected to Codex app-server.`
1446
+ `${import_companion6.DEXLY_COMPANION_DISPLAY_NAME} is not connected to Codex app-server.`
1136
1447
  ));
1137
1448
  return;
1138
1449
  }
@@ -1267,7 +1578,7 @@ async function runHost() {
1267
1578
  async function runInstall(args) {
1268
1579
  const parsed = parseArgs(args);
1269
1580
  const result = await installCompanion({
1270
- sourcePackageDir: import_node_path4.default.resolve(__dirname, ".."),
1581
+ sourcePackageDir: import_node_path5.default.resolve(__dirname, ".."),
1271
1582
  installRoot: parsed.installRoot,
1272
1583
  manifestDir: parsed.manifestDir,
1273
1584
  channel: parsed.channel
@@ -1295,7 +1606,7 @@ async function runDoctorCommand(args) {
1295
1606
  }
1296
1607
  if (!result.ok) {
1297
1608
  import_node_process5.default.stdout.write(`
1298
- If Dexly Companion is missing, run ${import_companion6.DEXLY_COMPANION_INSTALL_COMMAND}.
1609
+ If Dexly Companion is missing, run ${import_companion7.DEXLY_COMPANION_INSTALL_COMMAND}.
1299
1610
  `);
1300
1611
  import_node_process5.default.exitCode = 1;
1301
1612
  }