@hanzo/dev 3.0.10 → 3.0.12

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Unified entry point for the Hanzo Dev CLI.
2
+ // Unified entry point for the Code CLI (fork of OpenAI Codex).
3
3
 
4
4
  import path from "path";
5
5
  import { fileURLToPath } from "url";
@@ -14,6 +14,10 @@ const __dirname = path.dirname(__filename);
14
14
 
15
15
  const { platform, arch } = process;
16
16
 
17
+ // Important: Never delegate to another system's `code` binary (e.g., VS Code).
18
+ // When users run via `npx @just-every/code`, we must always execute our
19
+ // packaged native binary by absolute path to avoid PATH collisions.
20
+
17
21
  function isWSL() {
18
22
  if (platform !== "linux") return false;
19
23
  try {
@@ -70,10 +74,11 @@ if (!targetTriple) {
70
74
  throw new Error(`Unsupported platform: ${platform} (${arch})`);
71
75
  }
72
76
 
73
- // Binary names for Hanzo dev
74
- let binaryPath = path.join(__dirname, "..", "bin", `dev-${targetTriple}`);
75
- let legacyBinaryPath = path.join(__dirname, "..", "bin", `code-${targetTriple}`);
77
+ // Prefer new 'code-*' binary names; fall back to legacy 'coder-*' if missing.
78
+ let binaryPath = path.join(__dirname, "..", "bin", `code-${targetTriple}`);
79
+ let legacyBinaryPath = path.join(__dirname, "..", "bin", `coder-${targetTriple}`);
76
80
 
81
+ // --- Bootstrap helper (runs if the binary is missing, e.g. Bun blocked postinstall) ---
77
82
  import { existsSync, chmodSync, statSync, openSync, readSync, closeSync, mkdirSync, copyFileSync, readFileSync, unlinkSync, createWriteStream } from "fs";
78
83
 
79
84
  const validateBinary = (p) => {
@@ -116,14 +121,16 @@ const getCacheDir = (version) => {
116
121
  } else {
117
122
  base = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
118
123
  }
119
- const dir = path.join(base, "hanzo", "dev", version);
124
+ const dir = path.join(base, "just-every", "code", version);
120
125
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
121
126
  return dir;
122
127
  };
123
128
 
124
129
  const getCachedBinaryPath = (version) => {
130
+ // targetTriple already includes the proper extension on Windows ("...msvc.exe").
131
+ // Do not append another suffix; just use the exact targetTriple-derived name.
125
132
  const cacheDir = getCacheDir(version);
126
- return path.join(cacheDir, `dev-${targetTriple}`);
133
+ return path.join(cacheDir, `code-${targetTriple}`);
127
134
  };
128
135
 
129
136
  let lastBootstrapError = null;
@@ -132,6 +139,7 @@ const httpsDownload = (url, dest) => new Promise((resolve, reject) => {
132
139
  const req = httpsGet(url, (res) => {
133
140
  const status = res.statusCode || 0;
134
141
  if (status >= 300 && status < 400 && res.headers.location) {
142
+ // follow one redirect recursively
135
143
  return resolve(httpsDownload(res.headers.location, dest));
136
144
  }
137
145
  if (status !== 200) {
@@ -156,17 +164,19 @@ const httpsDownload = (url, dest) => new Promise((resolve, reject) => {
156
164
 
157
165
  const tryBootstrapBinary = async () => {
158
166
  try {
167
+ // 1) Read our published version
159
168
  const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
160
169
  const version = pkg.version;
161
170
 
162
171
  const binDir = path.join(__dirname, "..", "bin");
163
172
  if (!existsSync(binDir)) mkdirSync(binDir, { recursive: true });
164
173
 
165
- // Fast path: user cache
174
+ // 2) Fast path: user cache
166
175
  const cachePath = getCachedBinaryPath(version);
167
176
  if (existsSync(cachePath)) {
168
177
  const v = validateBinary(cachePath);
169
178
  if (v.ok) {
179
+ // Prefer running directly from cache; mirror into node_modules on Unix
170
180
  if (platform !== "win32") {
171
181
  copyFileSync(cachePath, binaryPath);
172
182
  try { chmodSync(binaryPath, 0o755); } catch {}
@@ -175,25 +185,26 @@ const tryBootstrapBinary = async () => {
175
185
  }
176
186
  }
177
187
 
178
- // Try platform package (if present)
188
+ // 3) Try platform package (if present)
179
189
  try {
180
190
  const req = (await import("module")).createRequire(import.meta.url);
181
191
  const name = (() => {
182
- if (platform === "win32") return "@hanzo/dev-win32-x64";
192
+ if (platform === "win32") return "@just-every/code-win32-x64"; // may be unpublished; falls through
183
193
  const plt = nodePlatform();
184
194
  const cpu = nodeArch();
185
- if (plt === "darwin" && cpu === "arm64") return "@hanzo/dev-darwin-arm64";
186
- if (plt === "darwin" && cpu === "x64") return "@hanzo/dev-darwin-x64";
187
- if (plt === "linux" && cpu === "x64") return "@hanzo/dev-linux-x64-musl";
188
- if (plt === "linux" && cpu === "arm64") return "@hanzo/dev-linux-arm64-musl";
195
+ if (plt === "darwin" && cpu === "arm64") return "@just-every/code-darwin-arm64";
196
+ if (plt === "darwin" && cpu === "x64") return "@just-every/code-darwin-x64";
197
+ if (plt === "linux" && cpu === "x64") return "@just-every/code-linux-x64-musl";
198
+ if (plt === "linux" && cpu === "arm64") return "@just-every/code-linux-arm64-musl";
189
199
  return null;
190
200
  })();
191
201
  if (name) {
192
202
  try {
193
203
  const pkgJson = req.resolve(`${name}/package.json`);
194
204
  const pkgDir = path.dirname(pkgJson);
195
- const src = path.join(pkgDir, "bin", `dev-${targetTriple}`);
205
+ const src = path.join(pkgDir, "bin", `code-${targetTriple}`);
196
206
  if (existsSync(src)) {
207
+ // Always ensure cache has the binary; on Unix mirror into node_modules
197
208
  copyFileSync(src, cachePath);
198
209
  if (platform !== "win32") {
199
210
  copyFileSync(cachePath, binaryPath);
@@ -205,22 +216,21 @@ const tryBootstrapBinary = async () => {
205
216
  }
206
217
  } catch { /* ignore */ }
207
218
 
208
- // Download from GitHub release
219
+ // 4) Download from GitHub release
209
220
  const isWin = platform === "win32";
210
- // Use 'code-*' binary names since that's what the release produces
211
- const binaryName = `code-${targetTriple}`;
212
221
  const archiveName = isWin
213
- ? `${binaryName}.zip`
214
- : (() => { try { execSync("zstd --version", { stdio: "ignore", shell: true }); return `${binaryName}.zst`; } catch { return `${binaryName}.tar.gz`; } })();
215
- const url = `https://github.com/hanzoai/dev/releases/download/v${version}/${archiveName}`;
222
+ ? `code-${targetTriple}.zip`
223
+ : (() => { try { execSync("zstd --version", { stdio: "ignore", shell: true }); return `code-${targetTriple}.zst`; } catch { return `code-${targetTriple}.tar.gz`; } })();
224
+ const url = `https://github.com/just-every/code/releases/download/v${version}/${archiveName}`;
216
225
  const tmp = path.join(binDir, `.${archiveName}.part`);
217
226
  return httpsDownload(url, tmp)
218
227
  .then(() => {
219
228
  if (isWin) {
229
+ // Extract zip with robust fallbacks and use a safe temp dir, then move to cache
220
230
  try {
221
- const sysRoot = process.env.SystemRoot || process.env.windir || 'C:\\Windows';
231
+ const sysRoot = process.env.SystemRoot || process.env.windir || 'C:\\\Windows';
222
232
  const psFull = path.join(sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
223
- const unzipDest = getCacheDir(version);
233
+ const unzipDest = getCacheDir(version); // extract directly to cache location
224
234
  const psCmd = `Expand-Archive -Path '${tmp}' -DestinationPath '${unzipDest}' -Force`;
225
235
  let ok = false;
226
236
  try { execSync(`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {}
@@ -241,7 +251,11 @@ const tryBootstrapBinary = async () => {
241
251
  try { unlinkSync(tmp); } catch {}
242
252
  }
243
253
  }
244
- if (platform !== "win32") {
254
+ // On Windows, the file was extracted directly into the cache dir
255
+ if (platform === "win32") {
256
+ // Ensure the expected filename exists in cache; Expand-Archive extracts exact name
257
+ // No action required here; validation occurs below against cachePath
258
+ } else {
245
259
  try { copyFileSync(binaryPath, cachePath); } catch {}
246
260
  }
247
261
 
@@ -256,7 +270,7 @@ const tryBootstrapBinary = async () => {
256
270
  }
257
271
  };
258
272
 
259
- // If missing, attempt to bootstrap into place
273
+ // If missing, attempt to bootstrap into place (helps when Bun blocks postinstall)
260
274
  let binaryReady = existsSync(binaryPath) || existsSync(legacyBinaryPath);
261
275
  if (!binaryReady) {
262
276
  let runtimePostinstallError = null;
@@ -273,6 +287,7 @@ if (!binaryReady) {
273
287
  if (runtimePostinstallError && !lastBootstrapError) {
274
288
  lastBootstrapError = runtimePostinstallError;
275
289
  }
290
+ // retry legacy name in case archive provided coder-*
276
291
  if (existsSync(legacyBinaryPath) && !existsSync(binaryPath)) {
277
292
  binaryPath = legacyBinaryPath;
278
293
  }
@@ -295,14 +310,17 @@ try {
295
310
  // ignore
296
311
  }
297
312
 
313
+ // Check if binary exists and try to fix permissions if needed
314
+ // fs imports are above; keep for readability if tree-shaken by bundlers
298
315
  import { spawnSync } from "child_process";
299
316
  if (existsSync(binaryPath)) {
300
317
  try {
318
+ // Ensure binary is executable on Unix-like systems
301
319
  if (platform !== "win32") {
302
320
  chmodSync(binaryPath, 0o755);
303
321
  }
304
322
  } catch (e) {
305
- // Ignore permission errors
323
+ // Ignore permission errors, will be caught below if it's a real problem
306
324
  }
307
325
  } else {
308
326
  console.error(`Binary not found: ${binaryPath}`);
@@ -311,69 +329,97 @@ if (existsSync(binaryPath)) {
311
329
  console.error(`Bootstrap error: ${msg}`);
312
330
  }
313
331
  console.error(`Please try reinstalling the package:`);
314
- console.error(` npm uninstall -g @hanzo/dev`);
315
- console.error(` npm install -g @hanzo/dev`);
332
+ console.error(` npm uninstall -g @just-every/code`);
333
+ console.error(` npm install -g @just-every/code`);
316
334
  if (isWSL()) {
317
335
  console.error("Detected WSL. Install inside WSL (Ubuntu) separately:");
318
- console.error(" npx -y @hanzo/dev@latest (run inside WSL)");
336
+ console.error(" npx -y @just-every/code@latest (run inside WSL)");
319
337
  console.error("If installed globally on Windows, those binaries are not usable from WSL.");
320
338
  }
321
339
  process.exit(1);
322
340
  }
323
341
 
342
+ // Lightweight header validation to provide clearer errors before spawn
343
+ // Reuse the validateBinary helper defined above in the bootstrap section.
344
+
324
345
  const validation = validateBinary(binaryPath);
325
346
  if (!validation.ok) {
326
347
  console.error(`The native binary at ${binaryPath} appears invalid: ${validation.reason}`);
327
348
  console.error("This can happen if the download failed or was modified by antivirus/proxy.");
328
349
  console.error("Please try reinstalling:");
329
- console.error(" npm uninstall -g @hanzo/dev");
330
- console.error(" npm install -g @hanzo/dev");
350
+ console.error(" npm uninstall -g @just-every/code");
351
+ console.error(" npm install -g @just-every/code");
331
352
  if (platform === "win32") {
332
353
  console.error("If the issue persists, clear npm cache and disable antivirus temporarily:");
333
354
  console.error(" npm cache clean --force");
334
355
  }
335
356
  if (isWSL()) {
336
357
  console.error("Detected WSL. Ensure you install/run inside WSL, not Windows:");
337
- console.error(" npx -y @hanzo/dev@latest (inside WSL)");
358
+ console.error(" npx -y @just-every/code@latest (inside WSL)");
338
359
  }
339
360
  process.exit(1);
340
361
  }
341
362
 
342
- // If running under npx/npm, emit a concise notice
363
+ // If running under npx/npm, emit a concise notice about which binary path is used
343
364
  try {
344
365
  const ua = process.env.npm_config_user_agent || "";
345
366
  const isNpx = ua.includes("npx");
346
367
  if (isNpx && process.stderr && process.stderr.isTTY) {
347
- console.error(`@hanzo/dev: running bundled binary -> ${binaryPath}`);
368
+ // Best-effort discovery of another 'code' on PATH for user clarity
369
+ let otherCode = "";
370
+ try {
371
+ const cmd = process.platform === "win32" ? "where code" : "command -v code || which code || true";
372
+ const out = spawnSync(process.platform === "win32" ? "cmd" : "bash", [
373
+ process.platform === "win32" ? "/c" : "-lc",
374
+ cmd,
375
+ ], { encoding: "utf8" });
376
+ const line = (out.stdout || "").split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
377
+ if (line && !line.includes("@just-every/code")) {
378
+ otherCode = line;
379
+ }
380
+ } catch {}
381
+ if (otherCode) {
382
+ console.error(`@just-every/code: running bundled binary -> ${binaryPath}`);
383
+ console.error(`Note: a different 'code' exists at ${otherCode}; not delegating.`);
384
+ } else {
385
+ console.error(`@just-every/code: running bundled binary -> ${binaryPath}`);
386
+ }
348
387
  }
349
388
  } catch {}
350
389
 
390
+ // Use an asynchronous spawn instead of spawnSync so that Node is able to
391
+ // respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
392
+ // executing. This allows us to forward those signals to the child process
393
+ // and guarantees that when either the child terminates or the parent
394
+ // receives a fatal signal, both processes exit in a predictable manner.
351
395
  const { spawn } = await import("child_process");
352
396
 
353
- process.env.DEV_BINARY_PATH = binaryPath;
397
+ // Make the resolved native binary path visible to spawned agents/subprocesses.
398
+ process.env.CODE_BINARY_PATH = binaryPath;
354
399
 
355
400
  const child = spawn(binaryPath, process.argv.slice(2), {
356
401
  stdio: "inherit",
357
- env: { ...process.env, DEV_MANAGED_BY_NPM: "1", DEV_BINARY_PATH: binaryPath },
402
+ env: { ...process.env, CODER_MANAGED_BY_NPM: "1", CODEX_MANAGED_BY_NPM: "1", CODE_BINARY_PATH: binaryPath },
358
403
  });
359
404
 
360
405
  child.on("error", (err) => {
406
+ // Typically triggered when the binary is missing or not executable.
361
407
  const code = err && err.code;
362
408
  if (code === 'EACCES') {
363
409
  console.error(`Permission denied: ${binaryPath}`);
364
410
  console.error(`Try running: chmod +x "${binaryPath}"`);
365
- console.error(`Or reinstall the package with: npm install -g @hanzo/dev`);
411
+ console.error(`Or reinstall the package with: npm install -g @just-every/code`);
366
412
  } else if (code === 'EFTYPE' || code === 'ENOEXEC') {
367
413
  console.error(`Failed to execute native binary: ${binaryPath}`);
368
414
  console.error("The file may be corrupt or of the wrong type. Reinstall usually fixes this:");
369
- console.error(" npm uninstall -g @hanzo/dev && npm install -g @hanzo/dev");
415
+ console.error(" npm uninstall -g @just-every/code && npm install -g @just-every/code");
370
416
  if (platform === 'win32') {
371
417
  console.error("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
372
418
  console.error("Try clearing cache: npm cache clean --force");
373
419
  }
374
420
  if (isWSL()) {
375
421
  console.error("Detected WSL. Windows binaries cannot be executed from WSL.");
376
- console.error("Install inside WSL and run there: npx -y @hanzo/dev@latest");
422
+ console.error("Install inside WSL and run there: npx -y @just-every/code@latest");
377
423
  }
378
424
  } else {
379
425
  console.error(err);
@@ -381,6 +427,10 @@ child.on("error", (err) => {
381
427
  process.exit(1);
382
428
  });
383
429
 
430
+ // Forward common termination signals to the child so that it shuts down
431
+ // gracefully. In the handler we temporarily disable the default behavior of
432
+ // exiting immediately; once the child has been signaled we simply wait for
433
+ // its exit event which will in turn terminate the parent (see below).
384
434
  const forwardSignal = (signal) => {
385
435
  if (child.killed) {
386
436
  return;
@@ -396,6 +446,11 @@ const forwardSignal = (signal) => {
396
446
  process.on(sig, () => forwardSignal(sig));
397
447
  });
398
448
 
449
+ // When the child exits, mirror its termination reason in the parent so that
450
+ // shell scripts and other tooling observe the correct exit status.
451
+ // Wrap the lifetime of the child process in a Promise so that we can await
452
+ // its termination in a structured way. The Promise resolves with an object
453
+ // describing how the child exited: either via exit code or due to a signal.
399
454
  const childResult = await new Promise((resolve) => {
400
455
  child.on("exit", (code, signal) => {
401
456
  if (signal) {
@@ -407,6 +462,8 @@ const childResult = await new Promise((resolve) => {
407
462
  });
408
463
 
409
464
  if (childResult.type === "signal") {
465
+ // Re-emit the same signal so that the parent terminates with the expected
466
+ // semantics (this also sets the correct exit code of 128 + n).
410
467
  process.kill(process.pid, childResult.signal);
411
468
  } else {
412
469
  process.exit(childResult.exitCode);
package/bin/codex.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ // Unified entry point for the Codex CLI.
3
+
4
+ import { spawn } from "node:child_process";
5
+ import { existsSync } from "fs";
6
+ import { createRequire } from "node:module";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+
10
+ // __dirname equivalent in ESM
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ const require = createRequire(import.meta.url);
14
+
15
+ const PLATFORM_PACKAGE_BY_TARGET = {
16
+ "x86_64-unknown-linux-musl": "@openai/codex-linux-x64",
17
+ "aarch64-unknown-linux-musl": "@openai/codex-linux-arm64",
18
+ "x86_64-apple-darwin": "@openai/codex-darwin-x64",
19
+ "aarch64-apple-darwin": "@openai/codex-darwin-arm64",
20
+ "x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
21
+ "aarch64-pc-windows-msvc": "@openai/codex-win32-arm64",
22
+ };
23
+
24
+ const { platform, arch } = process;
25
+
26
+ let targetTriple = null;
27
+ switch (platform) {
28
+ case "linux":
29
+ case "android":
30
+ switch (arch) {
31
+ case "x64":
32
+ targetTriple = "x86_64-unknown-linux-musl";
33
+ break;
34
+ case "arm64":
35
+ targetTriple = "aarch64-unknown-linux-musl";
36
+ break;
37
+ default:
38
+ break;
39
+ }
40
+ break;
41
+ case "darwin":
42
+ switch (arch) {
43
+ case "x64":
44
+ targetTriple = "x86_64-apple-darwin";
45
+ break;
46
+ case "arm64":
47
+ targetTriple = "aarch64-apple-darwin";
48
+ break;
49
+ default:
50
+ break;
51
+ }
52
+ break;
53
+ case "win32":
54
+ switch (arch) {
55
+ case "x64":
56
+ targetTriple = "x86_64-pc-windows-msvc";
57
+ break;
58
+ case "arm64":
59
+ targetTriple = "aarch64-pc-windows-msvc";
60
+ break;
61
+ default:
62
+ break;
63
+ }
64
+ break;
65
+ default:
66
+ break;
67
+ }
68
+
69
+ if (!targetTriple) {
70
+ throw new Error(`Unsupported platform: ${platform} (${arch})`);
71
+ }
72
+
73
+ const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
74
+ if (!platformPackage) {
75
+ throw new Error(`Unsupported target triple: ${targetTriple}`);
76
+ }
77
+
78
+ const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
79
+ const localVendorRoot = path.join(__dirname, "..", "vendor");
80
+ const localBinaryPath = path.join(
81
+ localVendorRoot,
82
+ targetTriple,
83
+ "codex",
84
+ codexBinaryName,
85
+ );
86
+
87
+ let vendorRoot;
88
+ try {
89
+ const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
90
+ vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
91
+ } catch {
92
+ if (existsSync(localBinaryPath)) {
93
+ vendorRoot = localVendorRoot;
94
+ } else {
95
+ const packageManager = detectPackageManager();
96
+ const updateCommand =
97
+ packageManager === "bun"
98
+ ? "bun install -g @openai/codex@latest"
99
+ : "npm install -g @openai/codex@latest";
100
+ throw new Error(
101
+ `Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
102
+ );
103
+ }
104
+ }
105
+
106
+ if (!vendorRoot) {
107
+ const packageManager = detectPackageManager();
108
+ const updateCommand =
109
+ packageManager === "bun"
110
+ ? "bun install -g @openai/codex@latest"
111
+ : "npm install -g @openai/codex@latest";
112
+ throw new Error(
113
+ `Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
114
+ );
115
+ }
116
+
117
+ const archRoot = path.join(vendorRoot, targetTriple);
118
+ const binaryPath = path.join(archRoot, "codex", codexBinaryName);
119
+
120
+ // Use an asynchronous spawn instead of spawnSync so that Node is able to
121
+ // respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
122
+ // executing. This allows us to forward those signals to the child process
123
+ // and guarantees that when either the child terminates or the parent
124
+ // receives a fatal signal, both processes exit in a predictable manner.
125
+
126
+ function getUpdatedPath(newDirs) {
127
+ const pathSep = process.platform === "win32" ? ";" : ":";
128
+ const existingPath = process.env.PATH || "";
129
+ const updatedPath = [
130
+ ...newDirs,
131
+ ...existingPath.split(pathSep).filter(Boolean),
132
+ ].join(pathSep);
133
+ return updatedPath;
134
+ }
135
+
136
+ const additionalDirs = [];
137
+ const pathDir = path.join(archRoot, "path");
138
+ if (existsSync(pathDir)) {
139
+ additionalDirs.push(pathDir);
140
+ }
141
+ const updatedPath = getUpdatedPath(additionalDirs);
142
+
143
+ const child = spawn(binaryPath, process.argv.slice(2), {
144
+ stdio: "inherit",
145
+ env: { ...process.env, PATH: updatedPath, CODEX_MANAGED_BY_NPM: "1" },
146
+ });
147
+
148
+ child.on("error", (err) => {
149
+ // Typically triggered when the binary is missing or not executable.
150
+ // Re-throwing here will terminate the parent with a non-zero exit code
151
+ // while still printing a helpful stack trace.
152
+ // eslint-disable-next-line no-console
153
+ console.error(err);
154
+ process.exit(1);
155
+ });
156
+
157
+ // Forward common termination signals to the child so that it shuts down
158
+ // gracefully. In the handler we temporarily disable the default behavior of
159
+ // exiting immediately; once the child has been signaled we simply wait for
160
+ // its exit event which will in turn terminate the parent (see below).
161
+ const forwardSignal = (signal) => {
162
+ if (child.killed) {
163
+ return;
164
+ }
165
+ try {
166
+ child.kill(signal);
167
+ } catch {
168
+ /* ignore */
169
+ }
170
+ };
171
+
172
+ ["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
173
+ process.on(sig, () => forwardSignal(sig));
174
+ });
175
+
176
+ // When the child exits, mirror its termination reason in the parent so that
177
+ // shell scripts and other tooling observe the correct exit status.
178
+ // Wrap the lifetime of the child process in a Promise so that we can await
179
+ // its termination in a structured way. The Promise resolves with an object
180
+ // describing how the child exited: either via exit code or due to a signal.
181
+ const childResult = await new Promise((resolve) => {
182
+ child.on("exit", (code, signal) => {
183
+ if (signal) {
184
+ resolve({ type: "signal", signal });
185
+ } else {
186
+ resolve({ type: "code", exitCode: code ?? 1 });
187
+ }
188
+ });
189
+ });
190
+
191
+ if (childResult.type === "signal") {
192
+ // Re-emit the same signal so that the parent terminates with the expected
193
+ // semantics (this also sets the correct exit code of 128 + n).
194
+ process.kill(process.pid, childResult.signal);
195
+ } else {
196
+ process.exit(childResult.exitCode);
197
+ }
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@hanzo/dev",
3
- "version": "3.0.10",
3
+ "version": "3.0.12",
4
4
  "license": "Apache-2.0",
5
- "description": "Hanzo AI coding assistant - intelligent CLI for developers",
5
+ "description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
6
6
  "bin": {
7
- "dev": "bin/dev.js",
8
- "hanzo": "bin/dev.js"
7
+ "coder": "bin/coder.js"
9
8
  },
10
9
  "type": "module",
11
10
  "engines": {
12
11
  "node": ">=16"
13
12
  },
14
13
  "files": [
15
- "bin/dev.js",
14
+ "bin/coder.js",
15
+ "bin/codex.js",
16
16
  "postinstall.js",
17
17
  "scripts/preinstall.js",
18
18
  "scripts/windows-cleanup.ps1",
@@ -21,7 +21,7 @@
21
21
  "scripts": {
22
22
  "preinstall": "node scripts/preinstall.js",
23
23
  "postinstall": "node postinstall.js",
24
- "prepublishOnly": "node -e \"const fs=require('fs'),path=require('path'); const repoGit=path.join(__dirname,'..','.git'); const inCi=process.env.GITHUB_ACTIONS==='true'||process.env.CI==='true'; if(fs.existsSync(repoGit) && !inCi){ console.error('Refusing to publish from dev-cli. Publishing happens via release.yml.'); process.exit(1);} else { console.log(inCi ? 'CI publish detected.' : 'Publishing staged package...'); }\""
24
+ "prepublishOnly": "node -e \"const fs=require('fs'),path=require('path'); const repoGit=path.join(__dirname,'..','.git'); const inCi=process.env.GITHUB_ACTIONS==='true'||process.env.CI==='true'; if(fs.existsSync(repoGit) && !inCi){ console.error('Refusing to publish from codex-cli. Publishing happens via release.yml.'); process.exit(1);} else { console.log(inCi ? 'CI publish detected.' : 'Publishing staged package...'); }\""
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
@@ -35,10 +35,10 @@
35
35
  "prettier": "^3.3.3"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@hanzo/dev-darwin-arm64": "3.0.10",
39
- "@hanzo/dev-darwin-x64": "3.0.10",
40
- "@hanzo/dev-linux-x64-musl": "3.0.10",
41
- "@hanzo/dev-linux-arm64-musl": "3.0.10",
42
- "@hanzo/dev-win32-x64": "3.0.10"
38
+ "@hanzo/dev-darwin-arm64": "3.0.12",
39
+ "@hanzo/dev-darwin-x64": "3.0.12",
40
+ "@hanzo/dev-linux-x64-musl": "3.0.12",
41
+ "@hanzo/dev-linux-arm64-musl": "3.0.12",
42
+ "@hanzo/dev-win32-x64": "3.0.12"
43
43
  }
44
44
  }