@just-every/code 0.2.3 → 0.2.5

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/bin/coder.js CHANGED
@@ -10,6 +10,20 @@ const __dirname = path.dirname(__filename);
10
10
 
11
11
  const { platform, arch } = process;
12
12
 
13
+ const isWSL = () => {
14
+ if (platform !== "linux") return false;
15
+ try {
16
+ const os = require("os");
17
+ const rel = os.release().toLowerCase();
18
+ if (rel.includes("microsoft")) return true;
19
+ const fs = require("fs");
20
+ const txt = fs.readFileSync("/proc/version", "utf8").toLowerCase();
21
+ return txt.includes("microsoft");
22
+ } catch {
23
+ return false;
24
+ }
25
+ };
26
+
13
27
  let targetTriple = null;
14
28
  switch (platform) {
15
29
  case "linux":
@@ -63,7 +77,7 @@ if (!existsSync(binaryPath)) {
63
77
  }
64
78
 
65
79
  // Check if binary exists and try to fix permissions if needed
66
- import { existsSync, chmodSync } from "fs";
80
+ import { existsSync, chmodSync, statSync, openSync, readSync, closeSync } from "fs";
67
81
  if (existsSync(binaryPath)) {
68
82
  try {
69
83
  // Ensure binary is executable on Unix-like systems
@@ -78,6 +92,59 @@ if (existsSync(binaryPath)) {
78
92
  console.error(`Please try reinstalling the package:`);
79
93
  console.error(` npm uninstall -g @just-every/code`);
80
94
  console.error(` npm install -g @just-every/code`);
95
+ if (isWSL()) {
96
+ console.error("Detected WSL. Install inside WSL (Ubuntu) separately:");
97
+ console.error(" npx -y @just-every/code@latest (run inside WSL)");
98
+ console.error("If installed globally on Windows, those binaries are not usable from WSL.");
99
+ }
100
+ process.exit(1);
101
+ }
102
+
103
+ // Lightweight header validation to provide clearer errors before spawn
104
+ const validateBinary = (p) => {
105
+ try {
106
+ const st = statSync(p);
107
+ if (!st.isFile() || st.size === 0) {
108
+ return { ok: false, reason: "empty or not a regular file" };
109
+ }
110
+ const fd = openSync(p, "r");
111
+ try {
112
+ const buf = Buffer.alloc(4);
113
+ const n = readSync(fd, buf, 0, 4, 0);
114
+ if (n < 2) return { ok: false, reason: "too short" };
115
+ if (platform === "win32") {
116
+ if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: "invalid PE header (missing MZ)" };
117
+ } else if (platform === "linux" || platform === "android") {
118
+ if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: "invalid ELF header" };
119
+ } else if (platform === "darwin") {
120
+ const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
121
+ (buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
122
+ if (!isMachO) return { ok: false, reason: "invalid Mach-O header" };
123
+ }
124
+ } finally {
125
+ closeSync(fd);
126
+ }
127
+ return { ok: true };
128
+ } catch (e) {
129
+ return { ok: false, reason: e.message };
130
+ }
131
+ };
132
+
133
+ const validation = validateBinary(binaryPath);
134
+ if (!validation.ok) {
135
+ console.error(`The native binary at ${binaryPath} appears invalid: ${validation.reason}`);
136
+ console.error("This can happen if the download failed or was modified by antivirus/proxy.");
137
+ console.error("Please try reinstalling:");
138
+ console.error(" npm uninstall -g @just-every/code");
139
+ console.error(" npm install -g @just-every/code");
140
+ if (platform === "win32") {
141
+ console.error("If the issue persists, clear npm cache and disable antivirus temporarily:");
142
+ console.error(" npm cache clean --force");
143
+ }
144
+ if (isWSL()) {
145
+ console.error("Detected WSL. Ensure you install/run inside WSL, not Windows:");
146
+ console.error(" npx -y @just-every/code@latest (inside WSL)");
147
+ }
81
148
  process.exit(1);
82
149
  }
83
150
 
@@ -95,10 +162,23 @@ const child = spawn(binaryPath, process.argv.slice(2), {
95
162
 
96
163
  child.on("error", (err) => {
97
164
  // Typically triggered when the binary is missing or not executable.
98
- if (err.code === 'EACCES') {
165
+ const code = err && err.code;
166
+ if (code === 'EACCES') {
99
167
  console.error(`Permission denied: ${binaryPath}`);
100
168
  console.error(`Try running: chmod +x "${binaryPath}"`);
101
169
  console.error(`Or reinstall the package with: npm install -g @just-every/code`);
170
+ } else if (code === 'EFTYPE' || code === 'ENOEXEC') {
171
+ console.error(`Failed to execute native binary: ${binaryPath}`);
172
+ console.error("The file may be corrupt or of the wrong type. Reinstall usually fixes this:");
173
+ console.error(" npm uninstall -g @just-every/code && npm install -g @just-every/code");
174
+ if (platform === 'win32') {
175
+ console.error("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
176
+ console.error("Try clearing cache: npm cache clean --force");
177
+ }
178
+ if (isWSL()) {
179
+ console.error("Detected WSL. Windows binaries cannot be executed from WSL.");
180
+ console.error("Install inside WSL and run there: npx -y @just-every/code@latest");
181
+ }
102
182
  } else {
103
183
  console.error(err);
104
184
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@just-every/code",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
6
6
  "bin": {
package/postinstall.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Non-functional change to trigger release workflow
3
3
 
4
- import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync } from 'fs';
4
+ import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync, unlinkSync, statSync, openSync, closeSync } from 'fs';
5
5
  import { join, dirname } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { get } from 'https';
@@ -29,31 +29,123 @@ function getTargetTriple() {
29
29
  return `${rustArch}-${rustPlatform}`;
30
30
  }
31
31
 
32
- async function downloadBinary(url, dest) {
33
- return new Promise((resolve, reject) => {
34
- const file = createWriteStream(dest);
35
-
36
- get(url, (response) => {
37
- if (response.statusCode === 302 || response.statusCode === 301) {
38
- // Follow redirect
39
- get(response.headers.location, (redirectResponse) => {
40
- redirectResponse.pipe(file);
32
+ async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
33
+ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
34
+
35
+ const doAttempt = () => new Promise((resolve, reject) => {
36
+ const attempt = (currentUrl, redirectsLeft) => {
37
+ const req = get(currentUrl, (response) => {
38
+ const status = response.statusCode || 0;
39
+ const location = response.headers.location;
40
+
41
+ if ((status === 301 || status === 302 || status === 303 || status === 307 || status === 308) && location) {
42
+ if (redirectsLeft <= 0) {
43
+ reject(new Error(`Too many redirects while downloading ${currentUrl}`));
44
+ return;
45
+ }
46
+ attempt(location, redirectsLeft - 1);
47
+ return;
48
+ }
49
+
50
+ if (status === 200) {
51
+ const expected = parseInt(response.headers['content-length'] || '0', 10) || 0;
52
+ let bytes = 0;
53
+ let timer;
54
+ const timeoutMs = 30000; // 30s inactivity timeout
55
+
56
+ const resetTimer = () => {
57
+ if (timer) clearTimeout(timer);
58
+ timer = setTimeout(() => {
59
+ req.destroy(new Error('download stalled'));
60
+ }, timeoutMs);
61
+ };
62
+
63
+ resetTimer();
64
+ response.on('data', (chunk) => {
65
+ bytes += chunk.length;
66
+ resetTimer();
67
+ });
68
+
69
+ const file = createWriteStream(dest);
70
+ response.pipe(file);
41
71
  file.on('finish', () => {
72
+ if (timer) clearTimeout(timer);
42
73
  file.close();
43
- resolve();
74
+ if (expected && bytes !== expected) {
75
+ try { unlinkSync(dest); } catch {}
76
+ reject(new Error(`incomplete download: got ${bytes} of ${expected} bytes`));
77
+ } else if (bytes === 0) {
78
+ try { unlinkSync(dest); } catch {}
79
+ reject(new Error('empty download'));
80
+ } else {
81
+ resolve();
82
+ }
44
83
  });
45
- }).on('error', reject);
46
- } else if (response.statusCode === 200) {
47
- response.pipe(file);
48
- file.on('finish', () => {
49
- file.close();
50
- resolve();
51
- });
52
- } else {
53
- reject(new Error(`Failed to download: ${response.statusCode}`));
54
- }
55
- }).on('error', reject);
84
+ file.on('error', (err) => {
85
+ if (timer) clearTimeout(timer);
86
+ try { unlinkSync(dest); } catch {}
87
+ reject(err);
88
+ });
89
+ } else {
90
+ reject(new Error(`Failed to download: HTTP ${status}`));
91
+ }
92
+ });
93
+
94
+ req.on('error', (err) => {
95
+ try { unlinkSync(dest); } catch {}
96
+ reject(err);
97
+ });
98
+
99
+ // Absolute request timeout to avoid hanging forever
100
+ req.setTimeout(120000, () => {
101
+ req.destroy(new Error('download timed out'));
102
+ });
103
+ };
104
+
105
+ attempt(url, maxRedirects);
56
106
  });
107
+
108
+ let attemptNum = 0;
109
+ while (true) {
110
+ try {
111
+ return await doAttempt();
112
+ } catch (e) {
113
+ attemptNum += 1;
114
+ if (attemptNum > maxRetries) throw e;
115
+ const backoff = Math.min(2000, 200 * attemptNum);
116
+ await sleep(backoff);
117
+ }
118
+ }
119
+ }
120
+
121
+ function validateDownloadedBinary(p) {
122
+ try {
123
+ const st = statSync(p);
124
+ if (!st.isFile() || st.size === 0) {
125
+ return { ok: false, reason: 'empty or not a regular file' };
126
+ }
127
+ const fd = openSync(p, 'r');
128
+ try {
129
+ const buf = Buffer.alloc(4);
130
+ const n = readSync(fd, buf, 0, 4, 0);
131
+ if (n < 2) return { ok: false, reason: 'too short' };
132
+ const plt = platform();
133
+ if (plt === 'win32') {
134
+ if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: 'invalid PE header (missing MZ)' };
135
+ } else if (plt === 'linux' || plt === 'android') {
136
+ if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: 'invalid ELF header' };
137
+ } else if (plt === 'darwin') {
138
+ const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
139
+ (buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
140
+ if (!isMachO) return { ok: false, reason: 'invalid Mach-O header' };
141
+ }
142
+ return { ok: true };
143
+ } finally {
144
+ closeSync(fd);
145
+ }
146
+ } catch (e) {
147
+ return { ok: false, reason: e.message };
148
+ }
57
149
  }
58
150
 
59
151
  async function main() {
@@ -124,7 +216,14 @@ async function main() {
124
216
  console.log(`Downloading ${binaryName}...`);
125
217
  try {
126
218
  await downloadBinary(downloadUrl, localPath);
127
-
219
+
220
+ // Validate header to avoid corrupt binaries causing spawn EFTYPE/ENOEXEC
221
+ const valid = validateDownloadedBinary(localPath);
222
+ if (!valid.ok) {
223
+ try { unlinkSync(localPath); } catch {}
224
+ throw new Error(`invalid binary (${valid.reason})`);
225
+ }
226
+
128
227
  // Make executable on Unix-like systems
129
228
  if (!isWindows) {
130
229
  chmodSync(localPath, 0o755);
@@ -143,6 +242,20 @@ async function main() {
143
242
  const mainBinaryPath = join(binDir, mainBinary);
144
243
 
145
244
  if (existsSync(mainBinaryPath)) {
245
+ try {
246
+ const stats = statSync(mainBinaryPath);
247
+ if (!stats.size) {
248
+ throw new Error('binary is empty (download likely failed)');
249
+ }
250
+ const valid = validateDownloadedBinary(mainBinaryPath);
251
+ if (!valid.ok) {
252
+ console.warn(`⚠ Main code binary appears invalid: ${valid.reason}`);
253
+ console.warn(' Try reinstalling or check your network/proxy settings.');
254
+ }
255
+ } catch (e) {
256
+ console.warn(`⚠ Main code binary appears invalid: ${e.message}`);
257
+ console.warn(' Try reinstalling or check your network/proxy settings.');
258
+ }
146
259
  console.log('Setting up main code binary...');
147
260
 
148
261
  // On Windows, we can't use symlinks easily, so update the JS wrapper