@codexhost/cli 0.1.4 → 0.1.6

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 CHANGED
@@ -5,7 +5,7 @@ Run Pi and Claude Code as first-class external harnesses inside Codex Desktop.
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install -g @codexhost/cli@0.1.4
8
+ npm install -g @codexhost/cli@0.1.6
9
9
  ```
10
10
 
11
11
  npm automatically installs the matching macOS or Windows platform package. Node.js 22 or newer and Codex Desktop are required.
package/bin/codexhost.js CHANGED
@@ -1,12 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, realpathSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
- const version = "0.1.4";
8
+ const version = "0.1.6";
9
9
  const userArguments = process.argv.slice(2);
10
+ const startupTraceStartedAt = Date.now();
11
+ function startupTrace(stage) {
12
+ if (process.env.CODEXHOST_STARTUP_TRACE !== "1") return;
13
+ console.error(
14
+ "[codexhost startup +" + (Date.now() - startupTraceStartedAt) + "ms] npm: " + stage,
15
+ );
16
+ }
10
17
  if (
11
18
  userArguments.length === 1 &&
12
19
  (userArguments[0] === "--version" || userArguments[0] === "-v")
@@ -14,6 +21,7 @@ if (
14
21
  console.log(version);
15
22
  process.exit(0);
16
23
  }
24
+ startupTrace("entry");
17
25
 
18
26
  const platformPackages = {
19
27
  "darwin-arm64": "@codexhost/cli-darwin-arm64",
@@ -52,6 +60,7 @@ try {
52
60
  }
53
61
  }
54
62
 
63
+ startupTrace("platform package resolved");
55
64
  const executableSuffix = process.platform === "win32" ? ".exe" : "";
56
65
  const launcher = path.join(packageRoot, "bin", `codexhost${executableSuffix}`);
57
66
  const shim = path.join(packageRoot, "libexec", `codexhost-shim${executableSuffix}`);
@@ -74,20 +83,81 @@ for (const [label, filePath] of [
74
83
  if (!existsSync(filePath)) fail(`missing ${label}: ${filePath}`);
75
84
  }
76
85
 
77
- const npmCliCandidates = [
78
- process.env.npm_execpath,
79
- process.platform === "win32"
80
- ? path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")
86
+ function existingFile(filePath) {
87
+ return typeof filePath === "string" && filePath.length > 0 && existsSync(filePath)
88
+ ? filePath
89
+ : undefined;
90
+ }
91
+ function officialNpmCliPath(nodePath) {
92
+ return process.platform === "win32"
93
+ ? path.join(path.dirname(nodePath), "node_modules", "npm", "bin", "npm-cli.js")
81
94
  : path.join(
82
- path.dirname(path.dirname(process.execPath)),
95
+ path.dirname(path.dirname(nodePath)),
83
96
  "lib",
84
97
  "node_modules",
85
98
  "npm",
86
99
  "bin",
87
100
  "npm-cli.js",
88
- ),
89
- ].filter((candidate) => typeof candidate === "string" && candidate.length > 0);
90
- const npmCliPath = npmCliCandidates.find((candidate) => existsSync(candidate));
101
+ );
102
+ }
103
+ function homebrewNpmCliPath(nodePath) {
104
+ if (process.platform === "win32") return undefined;
105
+ const segments = nodePath.split(path.sep);
106
+ const cellarIndex = segments.lastIndexOf("Cellar");
107
+ if (
108
+ cellarIndex >= 1 &&
109
+ segments[cellarIndex + 1] === "node" &&
110
+ segments.at(-2) === "bin" &&
111
+ segments.at(-1) === "node"
112
+ ) {
113
+ const brewPrefix = segments.slice(0, cellarIndex).join(path.sep);
114
+ return (
115
+ existingFile(
116
+ path.join(brewPrefix, "lib", "node_modules", "npm", "bin", "npm-cli.js"),
117
+ ) ??
118
+ existingFile(
119
+ path.join(
120
+ path.dirname(path.dirname(nodePath)),
121
+ "libexec",
122
+ "lib",
123
+ "node_modules",
124
+ "npm",
125
+ "bin",
126
+ "npm-cli.js",
127
+ ),
128
+ )
129
+ );
130
+ }
131
+ const brewPrefix = process.env.HOMEBREW_PREFIX;
132
+ if (typeof brewPrefix === "string" && brewPrefix.length > 0) {
133
+ return existingFile(
134
+ path.join(brewPrefix, "lib", "node_modules", "npm", "bin", "npm-cli.js"),
135
+ );
136
+ }
137
+ return undefined;
138
+ }
139
+ function pathNpmCliPath() {
140
+ const pathValue = process.env.PATH;
141
+ if (typeof pathValue !== "string" || pathValue.length === 0) return undefined;
142
+ const npmName = process.platform === "win32" ? "npm.cmd" : "npm";
143
+ for (const directory of pathValue.split(path.delimiter)) {
144
+ if (!directory) continue;
145
+ const candidate = path.join(directory, npmName);
146
+ if (!existsSync(candidate)) continue;
147
+ try {
148
+ const resolved = realpathSync(candidate);
149
+ if (path.basename(resolved) === "npm-cli.js") return resolved;
150
+ } catch {
151
+ continue;
152
+ }
153
+ }
154
+ return undefined;
155
+ }
156
+ const npmCliPath =
157
+ existingFile(process.env.npm_execpath) ??
158
+ existingFile(officialNpmCliPath(process.execPath)) ??
159
+ homebrewNpmCliPath(process.execPath) ??
160
+ pathNpmCliPath();
91
161
  if (!npmCliPath) fail("could not locate the npm CLI used to update this global installation");
92
162
  const updateEnvironment = {
93
163
  ...process.env,
@@ -154,6 +224,7 @@ if (launchArguments[0] === "launch") {
154
224
  // The Launcher prints "ready" once the Desktop, Controller, and Host chain
155
225
  // are up, then detaches from the terminal to keep supervising. Return
156
226
  // success immediately so the terminal is not held open by this command.
227
+ startupTrace("spawning Launcher");
157
228
  const child = spawn(launcher, launchArguments, {
158
229
  env: updateEnvironment,
159
230
  stdio: ["ignore", "pipe", "inherit"],
@@ -163,16 +234,24 @@ if (launchArguments[0] === "launch") {
163
234
  const finish = (code) => {
164
235
  if (finished) return;
165
236
  finished = true;
237
+ startupTrace("Launcher finished startup with code " + code);
166
238
  process.exit(code);
167
239
  };
168
240
  child.stdout.setEncoding("utf8");
169
241
  let launcherOutput = "";
170
242
  child.stdout.on("data", (chunk) => {
171
243
  launcherOutput += chunk;
172
- if (launcherOutput.includes("ready\n")) finish(0);
244
+ if (launcherOutput.includes("ready\n")) {
245
+ startupTrace("received Launcher ready");
246
+ finish(0);
247
+ }
248
+ });
249
+ child.on("error", (error) => {
250
+ startupTrace("Launcher spawn failed: " + error.message);
251
+ fail(error.message);
173
252
  });
174
- child.on("error", (error) => fail(error.message));
175
253
  child.on("exit", (code, signal) => {
254
+ startupTrace("Launcher exited before ready");
176
255
  if (signal) {
177
256
  process.kill(process.pid, signal);
178
257
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexhost/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Run Pi and Claude Code as first-class external harnesses inside Codex Desktop.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,10 +14,10 @@
14
14
  "node": ">=22"
15
15
  },
16
16
  "optionalDependencies": {
17
- "@codexhost/cli-darwin-arm64": "0.1.4",
18
- "@codexhost/cli-darwin-x64": "0.1.4",
19
- "@codexhost/cli-win32-x64": "0.1.4",
20
- "@codexhost/cli-win32-arm64": "0.1.4"
17
+ "@codexhost/cli-darwin-arm64": "0.1.6",
18
+ "@codexhost/cli-darwin-x64": "0.1.6",
19
+ "@codexhost/cli-win32-x64": "0.1.6",
20
+ "@codexhost/cli-win32-arm64": "0.1.6"
21
21
  },
22
22
  "keywords": [
23
23
  "codex",