@hanzo/dev 0.6.66
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 +516 -0
- package/bin/coder.js +470 -0
- package/bin/codex.js +197 -0
- package/package.json +44 -0
- package/postinstall.js +786 -0
- package/scripts/preinstall.js +69 -0
- package/scripts/windows-cleanup.ps1 +32 -0
package/bin/coder.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Unified entry point for the Code CLI (fork of OpenAI Codex).
|
|
3
|
+
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { platform as nodePlatform, arch as nodeArch } from "os";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { get as httpsGet } from "https";
|
|
9
|
+
import { runPostinstall } from "../postinstall.js";
|
|
10
|
+
|
|
11
|
+
// __dirname equivalent in ESM
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
const { platform, arch } = process;
|
|
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
|
+
|
|
21
|
+
function isWSL() {
|
|
22
|
+
if (platform !== "linux") return false;
|
|
23
|
+
try {
|
|
24
|
+
const txt = readFileSync("/proc/version", "utf8").toLowerCase();
|
|
25
|
+
return txt.includes("microsoft");
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let targetTriple = null;
|
|
32
|
+
switch (platform) {
|
|
33
|
+
case "linux":
|
|
34
|
+
case "android":
|
|
35
|
+
switch (arch) {
|
|
36
|
+
case "x64":
|
|
37
|
+
targetTriple = "x86_64-unknown-linux-musl";
|
|
38
|
+
break;
|
|
39
|
+
case "arm64":
|
|
40
|
+
targetTriple = "aarch64-unknown-linux-musl";
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case "darwin":
|
|
47
|
+
switch (arch) {
|
|
48
|
+
case "x64":
|
|
49
|
+
targetTriple = "x86_64-apple-darwin";
|
|
50
|
+
break;
|
|
51
|
+
case "arm64":
|
|
52
|
+
targetTriple = "aarch64-apple-darwin";
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
case "win32":
|
|
59
|
+
switch (arch) {
|
|
60
|
+
case "x64":
|
|
61
|
+
targetTriple = "x86_64-pc-windows-msvc.exe";
|
|
62
|
+
break;
|
|
63
|
+
case "arm64":
|
|
64
|
+
// We do not build this today, fall through...
|
|
65
|
+
default:
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!targetTriple) {
|
|
74
|
+
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
|
75
|
+
}
|
|
76
|
+
|
|
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}`);
|
|
80
|
+
|
|
81
|
+
// --- Bootstrap helper (runs if the binary is missing, e.g. Bun blocked postinstall) ---
|
|
82
|
+
import { existsSync, chmodSync, statSync, openSync, readSync, closeSync, mkdirSync, copyFileSync, readFileSync, unlinkSync, createWriteStream } from "fs";
|
|
83
|
+
|
|
84
|
+
const validateBinary = (p) => {
|
|
85
|
+
try {
|
|
86
|
+
const st = statSync(p);
|
|
87
|
+
if (!st.isFile() || st.size === 0) {
|
|
88
|
+
return { ok: false, reason: "empty or not a regular file" };
|
|
89
|
+
}
|
|
90
|
+
const fd = openSync(p, "r");
|
|
91
|
+
try {
|
|
92
|
+
const buf = Buffer.alloc(4);
|
|
93
|
+
const n = readSync(fd, buf, 0, 4, 0);
|
|
94
|
+
if (n < 2) return { ok: false, reason: "too short" };
|
|
95
|
+
if (platform === "win32") {
|
|
96
|
+
if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: "invalid PE header (missing MZ)" };
|
|
97
|
+
} else if (platform === "linux" || platform === "android") {
|
|
98
|
+
if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: "invalid ELF header" };
|
|
99
|
+
} else if (platform === "darwin") {
|
|
100
|
+
const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
|
|
101
|
+
(buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
|
|
102
|
+
if (!isMachO) return { ok: false, reason: "invalid Mach-O header" };
|
|
103
|
+
}
|
|
104
|
+
} finally {
|
|
105
|
+
closeSync(fd);
|
|
106
|
+
}
|
|
107
|
+
return { ok: true };
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return { ok: false, reason: e.message };
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getCacheDir = (version) => {
|
|
114
|
+
const plt = nodePlatform();
|
|
115
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
116
|
+
let base = "";
|
|
117
|
+
if (plt === "win32") {
|
|
118
|
+
base = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
119
|
+
} else if (plt === "darwin") {
|
|
120
|
+
base = path.join(home, "Library", "Caches");
|
|
121
|
+
} else {
|
|
122
|
+
base = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
|
|
123
|
+
}
|
|
124
|
+
const dir = path.join(base, "just-every", "code", version);
|
|
125
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
126
|
+
return dir;
|
|
127
|
+
};
|
|
128
|
+
|
|
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.
|
|
132
|
+
const cacheDir = getCacheDir(version);
|
|
133
|
+
return path.join(cacheDir, `code-${targetTriple}`);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
let lastBootstrapError = null;
|
|
137
|
+
|
|
138
|
+
const httpsDownload = (url, dest) => new Promise((resolve, reject) => {
|
|
139
|
+
const req = httpsGet(url, (res) => {
|
|
140
|
+
const status = res.statusCode || 0;
|
|
141
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
142
|
+
// follow one redirect recursively
|
|
143
|
+
return resolve(httpsDownload(res.headers.location, dest));
|
|
144
|
+
}
|
|
145
|
+
if (status !== 200) {
|
|
146
|
+
return reject(new Error(`HTTP ${status}`));
|
|
147
|
+
}
|
|
148
|
+
const out = createWriteStream(dest);
|
|
149
|
+
res.pipe(out);
|
|
150
|
+
out.on("finish", () => out.close(resolve));
|
|
151
|
+
out.on("error", (e) => {
|
|
152
|
+
try { unlinkSync(dest); } catch {}
|
|
153
|
+
reject(e);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
req.on("error", (e) => {
|
|
157
|
+
try { unlinkSync(dest); } catch {}
|
|
158
|
+
reject(e);
|
|
159
|
+
});
|
|
160
|
+
req.setTimeout(120000, () => {
|
|
161
|
+
req.destroy(new Error("download timed out"));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const tryBootstrapBinary = async () => {
|
|
166
|
+
try {
|
|
167
|
+
// 1) Read our published version
|
|
168
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
169
|
+
const version = pkg.version;
|
|
170
|
+
|
|
171
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
172
|
+
if (!existsSync(binDir)) mkdirSync(binDir, { recursive: true });
|
|
173
|
+
|
|
174
|
+
// 2) Fast path: user cache
|
|
175
|
+
const cachePath = getCachedBinaryPath(version);
|
|
176
|
+
if (existsSync(cachePath)) {
|
|
177
|
+
const v = validateBinary(cachePath);
|
|
178
|
+
if (v.ok) {
|
|
179
|
+
// Prefer running directly from cache; mirror into node_modules on Unix
|
|
180
|
+
if (platform !== "win32") {
|
|
181
|
+
copyFileSync(cachePath, binaryPath);
|
|
182
|
+
try { chmodSync(binaryPath, 0o755); } catch {}
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 3) Try platform package (if present)
|
|
189
|
+
try {
|
|
190
|
+
const req = (await import("module")).createRequire(import.meta.url);
|
|
191
|
+
const name = (() => {
|
|
192
|
+
if (platform === "win32") return "@just-every/code-win32-x64"; // may be unpublished; falls through
|
|
193
|
+
const plt = nodePlatform();
|
|
194
|
+
const cpu = nodeArch();
|
|
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";
|
|
199
|
+
return null;
|
|
200
|
+
})();
|
|
201
|
+
if (name) {
|
|
202
|
+
try {
|
|
203
|
+
const pkgJson = req.resolve(`${name}/package.json`);
|
|
204
|
+
const pkgDir = path.dirname(pkgJson);
|
|
205
|
+
const src = path.join(pkgDir, "bin", `code-${targetTriple}`);
|
|
206
|
+
if (existsSync(src)) {
|
|
207
|
+
// Always ensure cache has the binary; on Unix mirror into node_modules
|
|
208
|
+
copyFileSync(src, cachePath);
|
|
209
|
+
if (platform !== "win32") {
|
|
210
|
+
copyFileSync(cachePath, binaryPath);
|
|
211
|
+
try { chmodSync(binaryPath, 0o755); } catch {}
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
} catch { /* ignore and fall back */ }
|
|
216
|
+
}
|
|
217
|
+
} catch { /* ignore */ }
|
|
218
|
+
|
|
219
|
+
// 4) Download from GitHub release
|
|
220
|
+
const isWin = platform === "win32";
|
|
221
|
+
const archiveName = isWin
|
|
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}`;
|
|
225
|
+
const tmp = path.join(binDir, `.${archiveName}.part`);
|
|
226
|
+
return httpsDownload(url, tmp)
|
|
227
|
+
.then(() => {
|
|
228
|
+
if (isWin) {
|
|
229
|
+
// Extract zip with robust fallbacks and use a safe temp dir, then move to cache
|
|
230
|
+
try {
|
|
231
|
+
const sysRoot = process.env.SystemRoot || process.env.windir || 'C:\\\Windows';
|
|
232
|
+
const psFull = path.join(sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
233
|
+
const unzipDest = getCacheDir(version); // extract directly to cache location
|
|
234
|
+
const psCmd = `Expand-Archive -Path '${tmp}' -DestinationPath '${unzipDest}' -Force`;
|
|
235
|
+
let ok = false;
|
|
236
|
+
try { execSync(`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {}
|
|
237
|
+
if (!ok) { try { execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
|
|
238
|
+
if (!ok) { try { execSync(`pwsh -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
|
|
239
|
+
if (!ok) { execSync(`tar -xf "${tmp}" -C "${unzipDest}"`, { stdio: 'ignore', shell: true }); }
|
|
240
|
+
} catch (e) {
|
|
241
|
+
throw new Error(`failed to unzip: ${e.message}`);
|
|
242
|
+
} finally { try { unlinkSync(tmp); } catch {} }
|
|
243
|
+
} else {
|
|
244
|
+
if (archiveName.endsWith(".zst")) {
|
|
245
|
+
try { execSync(`zstd -d '${tmp}' -o '${binaryPath}'`, { stdio: 'ignore', shell: true }); }
|
|
246
|
+
catch (e) { try { unlinkSync(tmp); } catch {}; throw new Error(`failed to decompress zst: ${e.message}`); }
|
|
247
|
+
try { unlinkSync(tmp); } catch {}
|
|
248
|
+
} else {
|
|
249
|
+
try { execSync(`tar -xzf '${tmp}' -C '${binDir}'`, { stdio: 'ignore', shell: true }); }
|
|
250
|
+
catch (e) { try { unlinkSync(tmp); } catch {}; throw new Error(`failed to extract tar.gz: ${e.message}`); }
|
|
251
|
+
try { unlinkSync(tmp); } catch {}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
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 {
|
|
259
|
+
try { copyFileSync(binaryPath, cachePath); } catch {}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const v = validateBinary(platform === "win32" ? cachePath : binaryPath);
|
|
263
|
+
if (!v.ok) throw new Error(`invalid binary (${v.reason})`);
|
|
264
|
+
if (platform !== "win32") try { chmodSync(binaryPath, 0o755); } catch {}
|
|
265
|
+
return true;
|
|
266
|
+
})
|
|
267
|
+
.catch((e) => { lastBootstrapError = e; return false; });
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// If missing, attempt to bootstrap into place (helps when Bun blocks postinstall)
|
|
274
|
+
let binaryReady = existsSync(binaryPath) || existsSync(legacyBinaryPath);
|
|
275
|
+
if (!binaryReady) {
|
|
276
|
+
let runtimePostinstallError = null;
|
|
277
|
+
try {
|
|
278
|
+
await runPostinstall({ invokedByRuntime: true, skipGlobalAlias: true });
|
|
279
|
+
} catch (err) {
|
|
280
|
+
runtimePostinstallError = err;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
binaryReady = existsSync(binaryPath) || existsSync(legacyBinaryPath);
|
|
284
|
+
if (!binaryReady) {
|
|
285
|
+
const ok = await tryBootstrapBinary();
|
|
286
|
+
if (!ok) {
|
|
287
|
+
if (runtimePostinstallError && !lastBootstrapError) {
|
|
288
|
+
lastBootstrapError = runtimePostinstallError;
|
|
289
|
+
}
|
|
290
|
+
// retry legacy name in case archive provided coder-*
|
|
291
|
+
if (existsSync(legacyBinaryPath) && !existsSync(binaryPath)) {
|
|
292
|
+
binaryPath = legacyBinaryPath;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Prefer cached binary when available
|
|
299
|
+
try {
|
|
300
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
301
|
+
const version = pkg.version;
|
|
302
|
+
const cached = getCachedBinaryPath(version);
|
|
303
|
+
const v = existsSync(cached) ? validateBinary(cached) : { ok: false };
|
|
304
|
+
if (v.ok) {
|
|
305
|
+
binaryPath = cached;
|
|
306
|
+
} else if (!existsSync(binaryPath) && existsSync(legacyBinaryPath)) {
|
|
307
|
+
binaryPath = legacyBinaryPath;
|
|
308
|
+
}
|
|
309
|
+
} catch {
|
|
310
|
+
// ignore
|
|
311
|
+
}
|
|
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
|
|
315
|
+
import { spawnSync } from "child_process";
|
|
316
|
+
if (existsSync(binaryPath)) {
|
|
317
|
+
try {
|
|
318
|
+
// Ensure binary is executable on Unix-like systems
|
|
319
|
+
if (platform !== "win32") {
|
|
320
|
+
chmodSync(binaryPath, 0o755);
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
// Ignore permission errors, will be caught below if it's a real problem
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
console.error(`Binary not found: ${binaryPath}`);
|
|
327
|
+
if (lastBootstrapError) {
|
|
328
|
+
const msg = (lastBootstrapError && (lastBootstrapError.message || String(lastBootstrapError))) || 'unknown bootstrap error';
|
|
329
|
+
console.error(`Bootstrap error: ${msg}`);
|
|
330
|
+
}
|
|
331
|
+
console.error(`Please try reinstalling the package:`);
|
|
332
|
+
console.error(` npm uninstall -g @just-every/code`);
|
|
333
|
+
console.error(` npm install -g @just-every/code`);
|
|
334
|
+
if (isWSL()) {
|
|
335
|
+
console.error("Detected WSL. Install inside WSL (Ubuntu) separately:");
|
|
336
|
+
console.error(" npx -y @just-every/code@latest (run inside WSL)");
|
|
337
|
+
console.error("If installed globally on Windows, those binaries are not usable from WSL.");
|
|
338
|
+
}
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Lightweight header validation to provide clearer errors before spawn
|
|
343
|
+
// Reuse the validateBinary helper defined above in the bootstrap section.
|
|
344
|
+
|
|
345
|
+
const validation = validateBinary(binaryPath);
|
|
346
|
+
if (!validation.ok) {
|
|
347
|
+
console.error(`The native binary at ${binaryPath} appears invalid: ${validation.reason}`);
|
|
348
|
+
console.error("This can happen if the download failed or was modified by antivirus/proxy.");
|
|
349
|
+
console.error("Please try reinstalling:");
|
|
350
|
+
console.error(" npm uninstall -g @just-every/code");
|
|
351
|
+
console.error(" npm install -g @just-every/code");
|
|
352
|
+
if (platform === "win32") {
|
|
353
|
+
console.error("If the issue persists, clear npm cache and disable antivirus temporarily:");
|
|
354
|
+
console.error(" npm cache clean --force");
|
|
355
|
+
}
|
|
356
|
+
if (isWSL()) {
|
|
357
|
+
console.error("Detected WSL. Ensure you install/run inside WSL, not Windows:");
|
|
358
|
+
console.error(" npx -y @just-every/code@latest (inside WSL)");
|
|
359
|
+
}
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// If running under npx/npm, emit a concise notice about which binary path is used
|
|
364
|
+
try {
|
|
365
|
+
const ua = process.env.npm_config_user_agent || "";
|
|
366
|
+
const isNpx = ua.includes("npx");
|
|
367
|
+
if (isNpx && process.stderr && process.stderr.isTTY) {
|
|
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
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} catch {}
|
|
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.
|
|
395
|
+
const { spawn } = await import("child_process");
|
|
396
|
+
|
|
397
|
+
// Make the resolved native binary path visible to spawned agents/subprocesses.
|
|
398
|
+
process.env.CODE_BINARY_PATH = binaryPath;
|
|
399
|
+
|
|
400
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
401
|
+
stdio: "inherit",
|
|
402
|
+
env: { ...process.env, CODER_MANAGED_BY_NPM: "1", CODEX_MANAGED_BY_NPM: "1", CODE_BINARY_PATH: binaryPath },
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
child.on("error", (err) => {
|
|
406
|
+
// Typically triggered when the binary is missing or not executable.
|
|
407
|
+
const code = err && err.code;
|
|
408
|
+
if (code === 'EACCES') {
|
|
409
|
+
console.error(`Permission denied: ${binaryPath}`);
|
|
410
|
+
console.error(`Try running: chmod +x "${binaryPath}"`);
|
|
411
|
+
console.error(`Or reinstall the package with: npm install -g @just-every/code`);
|
|
412
|
+
} else if (code === 'EFTYPE' || code === 'ENOEXEC') {
|
|
413
|
+
console.error(`Failed to execute native binary: ${binaryPath}`);
|
|
414
|
+
console.error("The file may be corrupt or of the wrong type. Reinstall usually fixes this:");
|
|
415
|
+
console.error(" npm uninstall -g @just-every/code && npm install -g @just-every/code");
|
|
416
|
+
if (platform === 'win32') {
|
|
417
|
+
console.error("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
|
|
418
|
+
console.error("Try clearing cache: npm cache clean --force");
|
|
419
|
+
}
|
|
420
|
+
if (isWSL()) {
|
|
421
|
+
console.error("Detected WSL. Windows binaries cannot be executed from WSL.");
|
|
422
|
+
console.error("Install inside WSL and run there: npx -y @just-every/code@latest");
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
console.error(err);
|
|
426
|
+
}
|
|
427
|
+
process.exit(1);
|
|
428
|
+
});
|
|
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).
|
|
434
|
+
const forwardSignal = (signal) => {
|
|
435
|
+
if (child.killed) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
child.kill(signal);
|
|
440
|
+
} catch {
|
|
441
|
+
/* ignore */
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
|
|
446
|
+
process.on(sig, () => forwardSignal(sig));
|
|
447
|
+
});
|
|
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.
|
|
454
|
+
const childResult = await new Promise((resolve) => {
|
|
455
|
+
child.on("exit", (code, signal) => {
|
|
456
|
+
if (signal) {
|
|
457
|
+
resolve({ type: "signal", signal });
|
|
458
|
+
} else {
|
|
459
|
+
resolve({ type: "code", exitCode: code ?? 1 });
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
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).
|
|
467
|
+
process.kill(process.pid, childResult.signal);
|
|
468
|
+
} else {
|
|
469
|
+
process.exit(childResult.exitCode);
|
|
470
|
+
}
|
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
|
+
}
|