copilot-hub 0.1.25 → 0.1.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-hub",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Copilot Hub CLI and runtime bundle",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -5,6 +5,7 @@ import process, { stdin as input, stdout as output } from "node:process";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { createInterface } from "node:readline/promises";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { spawnCodexSync } from "./codex-spawn.mjs";
8
9
  import { codexInstallPackageSpec } from "./codex-version.mjs";
9
10
  import { initializeCopilotHubLayout, resolveCopilotHubLayout } from "./install-layout.mjs";
10
11
  import { buildCodexCompatibilityError, buildCodexCompatibilityNotice, probeCodexVersion, resolveCodexBinForStart, resolveCompatibleInstalledCodexBin, } from "./codex-runtime.mjs";
@@ -367,22 +368,13 @@ async function ensureCompatibleCodexBinary({ autoInstall, purpose, }) {
367
368
  }
368
369
  function runCodex(codexBin, args, stdioMode) {
369
370
  const stdio = stdioMode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"];
370
- const result = process.platform === "win32" && /\.(cmd|bat)$/i.test(String(codexBin ?? ""))
371
- ? spawnSync([
372
- quoteWindowsShellValue(codexBin),
373
- ...args.map((arg) => quoteWindowsShellValue(arg)),
374
- ].join(" "), {
375
- cwd: repoRoot,
376
- stdio,
377
- shell: true,
378
- encoding: "utf8",
379
- })
380
- : spawnSync(codexBin, args, {
381
- cwd: repoRoot,
382
- stdio,
383
- shell: false,
384
- encoding: "utf8",
385
- });
371
+ const result = spawnCodexSync({
372
+ codexBin,
373
+ args,
374
+ cwd: repoRoot,
375
+ stdio,
376
+ encoding: "utf8",
377
+ });
386
378
  if (result.error) {
387
379
  return {
388
380
  ok: false,
@@ -401,9 +393,6 @@ function runCodex(codexBin, args, stdioMode) {
401
393
  errorCode: "",
402
394
  };
403
395
  }
404
- function quoteWindowsShellValue(value) {
405
- return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
406
- }
407
396
  function runNpm(args, stdioMode) {
408
397
  const stdio = stdioMode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"];
409
398
  const result = spawnNpm(args, {
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { spawnSync } from "node:child_process";
5
+ import { spawnCodexSync } from "./codex-spawn.mjs";
5
6
  import { compareSemver, codexVersionRequirementLabel, extractSemver, isCodexVersionCompatible, } from "./codex-version.mjs";
6
7
  export function resolveCodexBinForStart({ repoRoot, agentEngineEnvPath, controlPlaneEnvPath, env = process.env, }) {
7
8
  const fromEnv = nonEmpty(env.CODEX_BIN);
@@ -262,30 +263,11 @@ function runCodex({ codexBin, args, repoRoot, }) {
262
263
  };
263
264
  }
264
265
  function spawnCodex(codexBin, args, repoRoot) {
265
- if (/\.(cjs|mjs|js)$/i.test(codexBin)) {
266
- return spawnSync(process.execPath, [codexBin, ...args], {
267
- cwd: repoRoot,
268
- stdio: ["ignore", "pipe", "pipe"],
269
- shell: false,
270
- encoding: "utf8",
271
- });
272
- }
273
- if (process.platform === "win32" && /\.(cmd|bat)$/i.test(codexBin)) {
274
- const commandLine = [
275
- quoteWindowsShellValue(codexBin),
276
- ...args.map(quoteWindowsShellValue),
277
- ].join(" ");
278
- return spawnSync(commandLine, {
279
- cwd: repoRoot,
280
- stdio: ["ignore", "pipe", "pipe"],
281
- shell: true,
282
- encoding: "utf8",
283
- });
284
- }
285
- return spawnSync(codexBin, args, {
266
+ return spawnCodexSync({
267
+ codexBin,
268
+ args,
286
269
  cwd: repoRoot,
287
270
  stdio: ["ignore", "pipe", "pipe"],
288
- shell: false,
289
271
  encoding: "utf8",
290
272
  });
291
273
  }
@@ -346,9 +328,6 @@ function normalizeErrorCode(error) {
346
328
  function dedupe(values) {
347
329
  return [...new Set(values.map((value) => String(value ?? "").trim()).filter(Boolean))];
348
330
  }
349
- function quoteWindowsShellValue(value) {
350
- return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
351
- }
352
331
  function spawnNpm(args, repoRoot) {
353
332
  if (process.platform === "win32") {
354
333
  const comspec = process.env.ComSpec || "cmd.exe";
@@ -0,0 +1,42 @@
1
+ import process from "node:process";
2
+ import { spawnSync } from "node:child_process";
3
+ export function requiresNodeScriptCodexBin(command) {
4
+ return /\.(cjs|mjs|js)$/i.test(String(command ?? "").trim());
5
+ }
6
+ export function requiresShellWrappedCodexBin(command, platform = process.platform) {
7
+ return platform === "win32" && /\.(cmd|bat)$/i.test(String(command ?? "").trim());
8
+ }
9
+ export function buildCodexSpawnSpec({ codexBin, args, platform = process.platform, nodeBin = process.execPath, }) {
10
+ if (requiresNodeScriptCodexBin(codexBin)) {
11
+ return {
12
+ command: nodeBin,
13
+ args: [codexBin, ...args],
14
+ shell: false,
15
+ };
16
+ }
17
+ if (requiresShellWrappedCodexBin(codexBin, platform)) {
18
+ return {
19
+ command: [quoteWindowsShellValue(codexBin), ...args.map(quoteWindowsShellValue)].join(" "),
20
+ args: [],
21
+ shell: true,
22
+ };
23
+ }
24
+ return {
25
+ command: codexBin,
26
+ args: [...args],
27
+ shell: false,
28
+ };
29
+ }
30
+ export function spawnCodexSync({ codexBin, args, cwd, stdio, encoding = "utf8", input, }) {
31
+ const spec = buildCodexSpawnSpec({ codexBin, args });
32
+ return spawnSync(spec.command, spec.args, {
33
+ cwd,
34
+ stdio,
35
+ shell: spec.shell,
36
+ encoding,
37
+ ...(input !== undefined ? { input } : {}),
38
+ });
39
+ }
40
+ function quoteWindowsShellValue(value) {
41
+ return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
42
+ }
@@ -5,6 +5,7 @@ import process, { stdin as input, stdout as output } from "node:process";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { createInterface } from "node:readline/promises";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { spawnCodexSync } from "./codex-spawn.mjs";
8
9
  import { codexInstallPackageSpec } from "./codex-version.mjs";
9
10
  import { initializeCopilotHubLayout, resolveCopilotHubLayout } from "./install-layout.mjs";
10
11
  import {
@@ -443,26 +444,13 @@ async function ensureCompatibleCodexBinary({
443
444
 
444
445
  function runCodex(codexBin, args, stdioMode) {
445
446
  const stdio: any = stdioMode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"];
446
- const result =
447
- process.platform === "win32" && /\.(cmd|bat)$/i.test(String(codexBin ?? ""))
448
- ? spawnSync(
449
- [
450
- quoteWindowsShellValue(codexBin),
451
- ...args.map((arg) => quoteWindowsShellValue(arg)),
452
- ].join(" "),
453
- {
454
- cwd: repoRoot,
455
- stdio,
456
- shell: true,
457
- encoding: "utf8",
458
- },
459
- )
460
- : spawnSync(codexBin, args, {
461
- cwd: repoRoot,
462
- stdio,
463
- shell: false,
464
- encoding: "utf8",
465
- });
447
+ const result = spawnCodexSync({
448
+ codexBin,
449
+ args,
450
+ cwd: repoRoot,
451
+ stdio,
452
+ encoding: "utf8",
453
+ });
466
454
 
467
455
  if (result.error) {
468
456
  return {
@@ -484,10 +472,6 @@ function runCodex(codexBin, args, stdioMode) {
484
472
  };
485
473
  }
486
474
 
487
- function quoteWindowsShellValue(value) {
488
- return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
489
- }
490
-
491
475
  function runNpm(args, stdioMode) {
492
476
  const stdio = stdioMode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"];
493
477
  const result = spawnNpm(args, {
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { spawnSync } from "node:child_process";
5
+ import { spawnCodexSync } from "./codex-spawn.mjs";
5
6
  import {
6
7
  compareSemver,
7
8
  codexVersionRequirementLabel,
@@ -395,32 +396,11 @@ function runCodex({
395
396
  }
396
397
 
397
398
  function spawnCodex(codexBin: string, args: string[], repoRoot: string) {
398
- if (/\.(cjs|mjs|js)$/i.test(codexBin)) {
399
- return spawnSync(process.execPath, [codexBin, ...args], {
400
- cwd: repoRoot,
401
- stdio: ["ignore", "pipe", "pipe"],
402
- shell: false,
403
- encoding: "utf8",
404
- });
405
- }
406
-
407
- if (process.platform === "win32" && /\.(cmd|bat)$/i.test(codexBin)) {
408
- const commandLine = [
409
- quoteWindowsShellValue(codexBin),
410
- ...args.map(quoteWindowsShellValue),
411
- ].join(" ");
412
- return spawnSync(commandLine, {
413
- cwd: repoRoot,
414
- stdio: ["ignore", "pipe", "pipe"],
415
- shell: true,
416
- encoding: "utf8",
417
- });
418
- }
419
-
420
- return spawnSync(codexBin, args, {
399
+ return spawnCodexSync({
400
+ codexBin,
401
+ args,
421
402
  cwd: repoRoot,
422
403
  stdio: ["ignore", "pipe", "pipe"],
423
- shell: false,
424
404
  encoding: "utf8",
425
405
  });
426
406
  }
@@ -493,10 +473,6 @@ function dedupe(values: Array<string | null | undefined>): string[] {
493
473
  return [...new Set(values.map((value) => String(value ?? "").trim()).filter(Boolean))];
494
474
  }
495
475
 
496
- function quoteWindowsShellValue(value: string): string {
497
- return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
498
- }
499
-
500
476
  function spawnNpm(args: string[], repoRoot: string) {
501
477
  if (process.platform === "win32") {
502
478
  const comspec = process.env.ComSpec || "cmd.exe";
@@ -0,0 +1,84 @@
1
+ import process from "node:process";
2
+ import { spawnSync } from "node:child_process";
3
+
4
+ type StdioMode = "pipe" | "inherit" | ["ignore", "pipe", "pipe"];
5
+
6
+ type CodexSpawnSpec = {
7
+ command: string;
8
+ args: string[];
9
+ shell: boolean;
10
+ };
11
+
12
+ export function requiresNodeScriptCodexBin(command: string): boolean {
13
+ return /\.(cjs|mjs|js)$/i.test(String(command ?? "").trim());
14
+ }
15
+
16
+ export function requiresShellWrappedCodexBin(
17
+ command: string,
18
+ platform: NodeJS.Platform = process.platform,
19
+ ): boolean {
20
+ return platform === "win32" && /\.(cmd|bat)$/i.test(String(command ?? "").trim());
21
+ }
22
+
23
+ export function buildCodexSpawnSpec({
24
+ codexBin,
25
+ args,
26
+ platform = process.platform,
27
+ nodeBin = process.execPath,
28
+ }: {
29
+ codexBin: string;
30
+ args: string[];
31
+ platform?: NodeJS.Platform;
32
+ nodeBin?: string;
33
+ }): CodexSpawnSpec {
34
+ if (requiresNodeScriptCodexBin(codexBin)) {
35
+ return {
36
+ command: nodeBin,
37
+ args: [codexBin, ...args],
38
+ shell: false,
39
+ };
40
+ }
41
+
42
+ if (requiresShellWrappedCodexBin(codexBin, platform)) {
43
+ return {
44
+ command: [quoteWindowsShellValue(codexBin), ...args.map(quoteWindowsShellValue)].join(" "),
45
+ args: [],
46
+ shell: true,
47
+ };
48
+ }
49
+
50
+ return {
51
+ command: codexBin,
52
+ args: [...args],
53
+ shell: false,
54
+ };
55
+ }
56
+
57
+ export function spawnCodexSync({
58
+ codexBin,
59
+ args,
60
+ cwd,
61
+ stdio,
62
+ encoding = "utf8",
63
+ input,
64
+ }: {
65
+ codexBin: string;
66
+ args: string[];
67
+ cwd: string;
68
+ stdio: StdioMode;
69
+ encoding?: BufferEncoding;
70
+ input?: string;
71
+ }) {
72
+ const spec = buildCodexSpawnSpec({ codexBin, args });
73
+ return spawnSync(spec.command, spec.args, {
74
+ cwd,
75
+ stdio,
76
+ shell: spec.shell,
77
+ encoding,
78
+ ...(input !== undefined ? { input } : {}),
79
+ });
80
+ }
81
+
82
+ function quoteWindowsShellValue(value: string): string {
83
+ return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
84
+ }
@@ -0,0 +1,60 @@
1
+ import assert from "node:assert/strict";
2
+ import process from "node:process";
3
+ import test from "node:test";
4
+ import {
5
+ buildCodexSpawnSpec,
6
+ requiresNodeScriptCodexBin,
7
+ requiresShellWrappedCodexBin,
8
+ } from "../dist/codex-spawn.mjs";
9
+
10
+ test("requiresNodeScriptCodexBin matches js entrypoints only", () => {
11
+ assert.equal(requiresNodeScriptCodexBin("C:/tools/codex.js"), true);
12
+ assert.equal(requiresNodeScriptCodexBin("C:/tools/codex.mjs"), true);
13
+ assert.equal(requiresNodeScriptCodexBin("C:/tools/codex.cjs"), true);
14
+ assert.equal(requiresNodeScriptCodexBin("C:/tools/codex.exe"), false);
15
+ assert.equal(requiresNodeScriptCodexBin("C:/tools/codex.cmd"), false);
16
+ });
17
+
18
+ test("requiresShellWrappedCodexBin matches Windows batch launchers only", () => {
19
+ assert.equal(requiresShellWrappedCodexBin("C:/tools/codex.cmd", "win32"), true);
20
+ assert.equal(requiresShellWrappedCodexBin("C:/tools/codex.bat", "win32"), true);
21
+ assert.equal(requiresShellWrappedCodexBin("C:/tools/codex.exe", "win32"), false);
22
+ assert.equal(requiresShellWrappedCodexBin("C:/tools/codex.js", "win32"), false);
23
+ });
24
+
25
+ test("buildCodexSpawnSpec routes js entrypoints through node", () => {
26
+ const spec = buildCodexSpawnSpec({
27
+ codexBin: "C:/Users/amine/AppData/Roaming/npm/node_modules/@openai/codex/bin/codex.js",
28
+ args: ["login", "status"],
29
+ platform: "win32",
30
+ nodeBin: process.execPath,
31
+ });
32
+
33
+ assert.equal(spec.command, process.execPath);
34
+ assert.deepEqual(spec.args, [
35
+ "C:/Users/amine/AppData/Roaming/npm/node_modules/@openai/codex/bin/codex.js",
36
+ "login",
37
+ "status",
38
+ ]);
39
+ assert.equal(spec.shell, false);
40
+ });
41
+
42
+ test("buildCodexSpawnSpec keeps exe direct and cmd shell-wrapped", () => {
43
+ const exeSpec = buildCodexSpawnSpec({
44
+ codexBin: "C:/tools/codex.exe",
45
+ args: ["--version"],
46
+ platform: "win32",
47
+ });
48
+ assert.equal(exeSpec.command, "C:/tools/codex.exe");
49
+ assert.deepEqual(exeSpec.args, ["--version"]);
50
+ assert.equal(exeSpec.shell, false);
51
+
52
+ const cmdSpec = buildCodexSpawnSpec({
53
+ codexBin: "C:/tools/codex.cmd",
54
+ args: ["login", "status"],
55
+ platform: "win32",
56
+ });
57
+ assert.equal(cmdSpec.shell, true);
58
+ assert.equal(cmdSpec.args.length, 0);
59
+ assert.match(cmdSpec.command, /^"C:\/tools\/codex\.cmd" "login" "status"$/);
60
+ });