@just-every/code 0.2.118 → 0.2.120

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
@@ -177,9 +177,12 @@ const tryBootstrapBinary = async () => {
177
177
  if (existsSync(cachePath)) {
178
178
  const v = validateBinary(cachePath);
179
179
  if (v.ok) {
180
- copyFileSync(cachePath, binaryPath);
181
- if (platform !== "win32") chmodSync(binaryPath, 0o755);
182
- return existsSync(binaryPath);
180
+ // Prefer running directly from cache; mirror into node_modules on Unix
181
+ if (platform !== "win32") {
182
+ copyFileSync(cachePath, binaryPath);
183
+ try { chmodSync(binaryPath, 0o755); } catch {}
184
+ }
185
+ return true;
183
186
  }
184
187
  }
185
188
 
@@ -202,11 +205,13 @@ const tryBootstrapBinary = async () => {
202
205
  const pkgDir = path.dirname(pkgJson);
203
206
  const src = path.join(pkgDir, "bin", `code-${targetTriple}${platform === "win32" ? ".exe" : ""}`);
204
207
  if (existsSync(src)) {
205
- copyFileSync(src, binaryPath);
206
- if (platform !== "win32") chmodSync(binaryPath, 0o755);
207
- // refresh cache
208
- try { copyFileSync(binaryPath, cachePath); } catch {}
209
- return existsSync(binaryPath);
208
+ // Always ensure cache has the binary; on Unix mirror into node_modules
209
+ copyFileSync(src, cachePath);
210
+ if (platform !== "win32") {
211
+ copyFileSync(cachePath, binaryPath);
212
+ try { chmodSync(binaryPath, 0o755); } catch {}
213
+ }
214
+ return true;
210
215
  }
211
216
  } catch { /* ignore and fall back */ }
212
217
  }
@@ -239,10 +244,19 @@ const tryBootstrapBinary = async () => {
239
244
  try { unlinkSync(tmp); } catch {}
240
245
  }
241
246
  }
242
- const v = validateBinary(binaryPath);
247
+ // On Windows, prefer cache and avoid leaving the executable in node_modules
248
+ if (platform === "win32") {
249
+ try {
250
+ copyFileSync(binaryPath, cachePath);
251
+ } catch {}
252
+ try { unlinkSync(binaryPath); } catch {}
253
+ } else {
254
+ try { copyFileSync(binaryPath, cachePath); } catch {}
255
+ }
256
+
257
+ const v = validateBinary(platform === "win32" ? cachePath : binaryPath);
243
258
  if (!v.ok) throw new Error(`invalid binary (${v.reason})`);
244
- if (platform !== "win32") chmodSync(binaryPath, 0o755);
245
- try { copyFileSync(binaryPath, cachePath); } catch {}
259
+ if (platform !== "win32") try { chmodSync(binaryPath, 0o755); } catch {}
246
260
  return true;
247
261
  })
248
262
  .catch((_e) => false);
@@ -262,9 +276,19 @@ if (!existsSync(binaryPath) && !existsSync(legacyBinaryPath)) {
262
276
  }
263
277
  }
264
278
 
265
- // Fall back to legacy name if primary is still missing
266
- if (!existsSync(binaryPath) && existsSync(legacyBinaryPath)) {
267
- binaryPath = legacyBinaryPath;
279
+ // Prefer cached binary when available
280
+ try {
281
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
282
+ const version = pkg.version;
283
+ const cached = getCachedBinaryPath(version);
284
+ const v = existsSync(cached) ? validateBinary(cached) : { ok: false };
285
+ if (v.ok) {
286
+ binaryPath = cached;
287
+ } else if (!existsSync(binaryPath) && existsSync(legacyBinaryPath)) {
288
+ binaryPath = legacyBinaryPath;
289
+ }
290
+ } catch {
291
+ // ignore
268
292
  }
269
293
 
270
294
  // Check if binary exists and try to fix permissions if needed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@just-every/code",
3
- "version": "0.2.118",
3
+ "version": "0.2.120",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
6
6
  "bin": {
@@ -14,9 +14,12 @@
14
14
  "bin/coder.js",
15
15
  "bin/codex.js",
16
16
  "postinstall.js",
17
+ "scripts/preinstall.js",
18
+ "scripts/windows-cleanup.ps1",
17
19
  "dist"
18
20
  ],
19
21
  "scripts": {
22
+ "preinstall": "node scripts/preinstall.js",
20
23
  "postinstall": "node postinstall.js",
21
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. Use codex-cli/scripts/stage_release.sh to stage a release.'); process.exit(1);} else { console.log(inCi ? 'CI publish detected.' : 'Publishing staged package...'); }\""
22
25
  },
@@ -32,10 +35,10 @@
32
35
  "prettier": "^3.3.3"
33
36
  },
34
37
  "optionalDependencies": {
35
- "@just-every/code-darwin-arm64": "0.2.118",
36
- "@just-every/code-darwin-x64": "0.2.118",
37
- "@just-every/code-linux-x64-musl": "0.2.118",
38
- "@just-every/code-linux-arm64-musl": "0.2.118",
39
- "@just-every/code-win32-x64": "0.2.118"
38
+ "@just-every/code-darwin-arm64": "0.2.120",
39
+ "@just-every/code-darwin-x64": "0.2.120",
40
+ "@just-every/code-linux-x64-musl": "0.2.120",
41
+ "@just-every/code-linux-arm64-musl": "0.2.120",
42
+ "@just-every/code-win32-x64": "0.2.120"
40
43
  }
41
44
  }
package/postinstall.js CHANGED
@@ -1,11 +1,11 @@
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, unlinkSync, statSync, openSync, closeSync, copyFileSync } from 'fs';
4
+ import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync, unlinkSync, statSync, openSync, closeSync, copyFileSync, fsyncSync, renameSync, realpathSync } from 'fs';
5
5
  import { join, dirname } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { get } from 'https';
8
- import { platform, arch } from 'os';
8
+ import { platform, arch, tmpdir } from 'os';
9
9
  import { execSync } from 'child_process';
10
10
  import { createRequire } from 'module';
11
11
 
@@ -54,6 +54,56 @@ function getCachedBinaryPath(version, targetTriple, isWindows) {
54
54
  return join(cacheDir, `code-${targetTriple}${ext}`);
55
55
  }
56
56
 
57
+ function isWSL() {
58
+ if (platform() !== 'linux') return false;
59
+ try {
60
+ const ver = readFileSync('/proc/version', 'utf8').toLowerCase();
61
+ return ver.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
62
+ } catch { return false; }
63
+ }
64
+
65
+ function isPathOnWindowsFs(p) {
66
+ try {
67
+ const mounts = readFileSync('/proc/mounts', 'utf8').split(/\n/).filter(Boolean);
68
+ let best = { mount: '/', type: 'unknown', len: 1 };
69
+ for (const line of mounts) {
70
+ const parts = line.split(' ');
71
+ if (parts.length < 3) continue;
72
+ const mnt = parts[1];
73
+ const typ = parts[2];
74
+ if (p.startsWith(mnt) && mnt.length > best.len) best = { mount: mnt, type: typ, len: mnt.length };
75
+ }
76
+ return best.type === 'drvfs' || best.type === 'cifs';
77
+ } catch { return false; }
78
+ }
79
+
80
+ async function writeCacheAtomic(srcPath, cachePath) {
81
+ try {
82
+ if (existsSync(cachePath)) {
83
+ const ok = validateDownloadedBinary(cachePath).ok;
84
+ if (ok) return;
85
+ }
86
+ } catch {}
87
+ const dir = dirname(cachePath);
88
+ if (!existsSync(dir)) { try { mkdirSync(dir, { recursive: true }); } catch {} }
89
+ const tmp = cachePath + '.tmp-' + Math.random().toString(36).slice(2, 8);
90
+ copyFileSync(srcPath, tmp);
91
+ try { const fd = openSync(tmp, 'r'); try { fsyncSync(fd); } finally { closeSync(fd); } } catch {}
92
+ // Retry with exponential backoff up to ~1.6s total
93
+ const delays = [100, 200, 400, 800, 1200, 1600];
94
+ for (let i = 0; i < delays.length; i++) {
95
+ try {
96
+ if (existsSync(cachePath)) { try { unlinkSync(cachePath); } catch {} }
97
+ renameSync(tmp, cachePath);
98
+ return;
99
+ } catch {
100
+ await new Promise(r => setTimeout(r, delays[i]));
101
+ }
102
+ }
103
+ if (existsSync(cachePath)) { try { unlinkSync(cachePath); } catch {} }
104
+ renameSync(tmp, cachePath);
105
+ }
106
+
57
107
  async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
58
108
  const sleep = (ms) => new Promise(r => setTimeout(r, ms));
59
109
 
@@ -227,30 +277,26 @@ async function main() {
227
277
  const localPath = join(binDir, binaryName);
228
278
  const cachePath = getCachedBinaryPath(version, targetTriple, isWindows);
229
279
 
230
- // Skip if already exists and has correct permissions
231
- if (existsSync(localPath)) {
232
- // Always try to fix permissions on Unix-like systems
233
- if (!isWindows) {
234
- try {
235
- chmodSync(localPath, 0o755);
236
- console.log(`✓ ${binaryName} already exists (permissions fixed)`);
237
- } catch (e) {
238
- console.log(`✓ ${binaryName} already exists`);
239
- }
240
- } else {
241
- console.log(`✓ ${binaryName} already exists`);
242
- }
243
- continue;
244
- }
280
+ // On Windows we avoid placing the executable inside node_modules to prevent
281
+ // EBUSY/EPERM during global upgrades when the binary is in use.
282
+ // We treat the user cache path as the canonical home of the native binary.
283
+ // For macOS/Linux we keep previous behavior and also place a copy in binDir
284
+ // for convenience.
245
285
 
246
286
  // Fast path: if a valid cached binary exists for this version+triple, reuse it.
247
287
  try {
248
288
  if (existsSync(cachePath)) {
249
289
  const valid = validateDownloadedBinary(cachePath);
250
290
  if (valid.ok) {
251
- copyFileSync(cachePath, localPath);
252
- if (!isWindows) chmodSync(localPath, 0o755);
253
- console.log(`✓ Installed ${binaryName} from user cache`);
291
+ // Avoid mirroring into node_modules on Windows or WSL-on-NTFS.
292
+ const wsl = isWSL();
293
+ const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
294
+ const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
295
+ if (mirrorToLocal) {
296
+ copyFileSync(cachePath, localPath);
297
+ try { chmodSync(localPath, 0o755); } catch {}
298
+ }
299
+ console.log(`✓ ${binaryName} ready from user cache`);
254
300
  continue; // next binary
255
301
  }
256
302
  }
@@ -288,15 +334,17 @@ async function main() {
288
334
  if (!existsSync(src)) {
289
335
  throw new Error(`platform package missing binary: ${platformPkg.name}`);
290
336
  }
291
- copyFileSync(src, localPath);
292
- if (!isWindows) chmodSync(localPath, 0o755);
293
- console.log(`✓ Installed ${binaryName} from ${platformPkg.name}`);
294
- // Populate cache for future npx runs
295
- try {
296
- if (!existsSync(cachePath)) {
297
- copyFileSync(localPath, cachePath);
298
- }
299
- } catch {}
337
+ // Populate cache first (canonical location) atomically
338
+ await writeCacheAtomic(src, cachePath);
339
+ // Mirror into local bin only on Unix-like filesystems (not Windows/WSL-on-NTFS)
340
+ const wsl = isWSL();
341
+ const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
342
+ const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
343
+ if (mirrorToLocal) {
344
+ copyFileSync(cachePath, localPath);
345
+ try { chmodSync(localPath, 0o755); } catch {}
346
+ }
347
+ console.log(`✓ Installed ${binaryName} from ${platformPkg.name} (cached)`);
300
348
  continue; // next binary
301
349
  } catch (e) {
302
350
  console.warn(`⚠ Failed platform package install (${e.message}), falling back to GitHub download`);
@@ -307,6 +355,15 @@ async function main() {
307
355
  // - Windows: .zip
308
356
  // - macOS/Linux: prefer .zst if `zstd` CLI is available; otherwise use .tar.gz
309
357
  const isWin = isWindows;
358
+ const isWSL = (() => {
359
+ if (platform() !== 'linux') return false;
360
+ try {
361
+ const ver = readFileSync('/proc/version', 'utf8').toLowerCase();
362
+ return ver.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
363
+ } catch { return false; }
364
+ })();
365
+ const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
366
+ const mirrorToLocal = !(isWin || (isWSL && isPathOnWindowsFs(binDirReal)));
310
367
  let useZst = false;
311
368
  if (!isWin) {
312
369
  try {
@@ -321,24 +378,57 @@ async function main() {
321
378
 
322
379
  console.log(`Downloading ${archiveName}...`);
323
380
  try {
324
- const tmpPath = join(binDir, `.${archiveName}.part`);
381
+ const needsIsolation = isWin || (!isWin && !mirrorToLocal); // Windows or WSL-on-NTFS
382
+ let safeTempDir = needsIsolation ? join(tmpdir(), 'just-every', 'code', version) : binDir;
383
+ // Ensure staging dir exists; if tmp fails (permissions/space), fall back to user cache.
384
+ if (needsIsolation) {
385
+ try {
386
+ if (!existsSync(safeTempDir)) mkdirSync(safeTempDir, { recursive: true });
387
+ } catch {
388
+ try {
389
+ safeTempDir = getCacheDir(version);
390
+ if (!existsSync(safeTempDir)) mkdirSync(safeTempDir, { recursive: true });
391
+ } catch {}
392
+ }
393
+ }
394
+ const tmpPath = join(needsIsolation ? safeTempDir : binDir, `.${archiveName}.part`);
325
395
  await downloadBinary(downloadUrl, tmpPath);
326
396
 
327
397
  if (isWin) {
328
- // Unzip the single-file archive using PowerShell (built-in)
398
+ // Unzip to a temp directory, then move into the per-user cache.
399
+ const unzipDest = safeTempDir;
329
400
  try {
330
- const psCmd = `powershell -NoProfile -NonInteractive -Command "Expand-Archive -Path '${tmpPath}' -DestinationPath '${binDir}' -Force"`;
331
- execSync(psCmd, { stdio: 'ignore' });
401
+ const sysRoot = process.env.SystemRoot || process.env.windir || 'C:\\Windows';
402
+ const psFull = join(sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
403
+ const psCmd = `Expand-Archive -Path '${tmpPath}' -DestinationPath '${unzipDest}' -Force`;
404
+ let ok = false;
405
+ // Attempt full-path powershell.exe
406
+ try { execSync(`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {}
407
+ // Fallback to powershell in PATH
408
+ if (!ok) { try { execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
409
+ // Fallback to pwsh (PowerShell 7)
410
+ if (!ok) { try { execSync(`pwsh -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
411
+ // Final fallback: bsdtar can extract .zip
412
+ if (!ok) { execSync(`tar -xf "${tmpPath}" -C "${unzipDest}"`, { stdio: 'ignore', shell: true }); }
332
413
  } catch (e) {
333
414
  throw new Error(`failed to unzip archive: ${e.message}`);
334
415
  } finally {
335
416
  try { unlinkSync(tmpPath); } catch {}
336
417
  }
418
+ // Move the extracted file from temp to cache; do not leave a copy in node_modules
419
+ try {
420
+ const extractedPath = join(unzipDest, binaryName);
421
+ await writeCacheAtomic(extractedPath, cachePath);
422
+ try { unlinkSync(extractedPath); } catch {}
423
+ } catch (e) {
424
+ throw new Error(`failed to move binary to cache: ${e.message}`);
425
+ }
337
426
  } else {
338
427
  if (useZst) {
339
428
  // Decompress .zst via system zstd
340
429
  try {
341
- execSync(`zstd -d '${tmpPath}' -o '${localPath}'`, { stdio: 'ignore', shell: true });
430
+ const outPath = mirrorToLocal ? localPath : join(safeTempDir, binaryName);
431
+ execSync(`zstd -d '${tmpPath}' -o '${outPath}'`, { stdio: 'ignore', shell: true });
342
432
  } catch (e) {
343
433
  try { unlinkSync(tmpPath); } catch {}
344
434
  throw new Error(`failed to decompress .zst (need zstd CLI): ${e.message}`);
@@ -347,32 +437,43 @@ async function main() {
347
437
  } else {
348
438
  // Extract .tar.gz using system tar
349
439
  try {
350
- execSync(`tar -xzf '${tmpPath}' -C '${binDir}'`, { stdio: 'ignore', shell: true });
440
+ const dest = mirrorToLocal ? binDir : safeTempDir;
441
+ execSync(`tar -xzf '${tmpPath}' -C '${dest}'`, { stdio: 'ignore', shell: true });
351
442
  } catch (e) {
352
443
  try { unlinkSync(tmpPath); } catch {}
353
444
  throw new Error(`failed to extract .tar.gz: ${e.message}`);
354
445
  }
355
446
  try { unlinkSync(tmpPath); } catch {}
356
447
  }
448
+ if (!mirrorToLocal) {
449
+ try {
450
+ const extractedPath = join(safeTempDir, binaryName);
451
+ await writeCacheAtomic(extractedPath, cachePath);
452
+ try { unlinkSync(extractedPath); } catch {}
453
+ } catch (e) {
454
+ throw new Error(`failed to move binary to cache: ${e.message}`);
455
+ }
456
+ }
357
457
  }
358
458
 
359
459
  // Validate header to avoid corrupt binaries causing spawn EFTYPE/ENOEXEC
360
- const valid = validateDownloadedBinary(localPath);
460
+
461
+ const valid = validateDownloadedBinary(isWin ? cachePath : (mirrorToLocal ? localPath : cachePath));
361
462
  if (!valid.ok) {
362
- try { unlinkSync(localPath); } catch {}
463
+ try { (isWin || !mirrorToLocal) ? unlinkSync(cachePath) : unlinkSync(localPath); } catch {}
363
464
  throw new Error(`invalid binary (${valid.reason})`);
364
465
  }
365
466
 
366
467
  // Make executable on Unix-like systems
367
- if (!isWindows) {
468
+ if (!isWin && mirrorToLocal) {
368
469
  chmodSync(localPath, 0o755);
369
470
  }
370
471
 
371
- console.log(`✓ Installed ${binaryName}`);
372
- // Save into persistent cache for future fast installs
373
- try {
374
- copyFileSync(localPath, cachePath);
375
- } catch {}
472
+ console.log(`✓ Installed ${binaryName}${(isWin || !mirrorToLocal) ? ' (cached)' : ''}`);
473
+ // Ensure persistent cache holds the binary (already true for Windows path)
474
+ if (!isWin && mirrorToLocal) {
475
+ try { await writeCacheAtomic(localPath, cachePath); } catch {}
476
+ }
376
477
  } catch (error) {
377
478
  console.error(`✗ Failed to install ${binaryName}: ${error.message}`);
378
479
  console.error(` Downloaded from: ${downloadUrl}`);
@@ -384,13 +485,12 @@ async function main() {
384
485
  const mainBinary = `code-${targetTriple}${binaryExt}`;
385
486
  const mainBinaryPath = join(binDir, mainBinary);
386
487
 
387
- if (existsSync(mainBinaryPath)) {
488
+ if (existsSync(mainBinaryPath) || existsSync(getCachedBinaryPath(version, targetTriple, platform() === 'win32'))) {
388
489
  try {
389
- const stats = statSync(mainBinaryPath);
390
- if (!stats.size) {
391
- throw new Error('binary is empty (download likely failed)');
392
- }
393
- const valid = validateDownloadedBinary(mainBinaryPath);
490
+ const probePath = existsSync(mainBinaryPath) ? mainBinaryPath : getCachedBinaryPath(version, targetTriple, platform() === 'win32');
491
+ const stats = statSync(probePath);
492
+ if (!stats.size) throw new Error('binary is empty (download likely failed)');
493
+ const valid = validateDownloadedBinary(probePath);
394
494
  if (!valid.ok) {
395
495
  console.warn(`⚠ Main code binary appears invalid: ${valid.reason}`);
396
496
  console.warn(' Try reinstalling or check your network/proxy settings.');
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ // Windows-friendly preinstall: proactively free file locks from prior installs
3
+ // so npm/yarn/pnpm can stage the new package. No-ops on non-Windows.
4
+
5
+ import { platform } from 'os';
6
+ import { execSync } from 'child_process';
7
+ import { existsSync, readdirSync, rmSync, readFileSync, statSync } from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ function isWSL() {
12
+ if (platform() !== 'linux') return false;
13
+ try {
14
+ const rel = readFileSync('/proc/version', 'utf8').toLowerCase();
15
+ return rel.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
16
+ } catch { return false; }
17
+ }
18
+
19
+ const isWin = platform() === 'win32';
20
+ const wsl = isWSL();
21
+ const isWinLike = isWin || wsl;
22
+
23
+ // Scope: only run for global installs, unless explicitly forced. Allow opt-out.
24
+ const isGlobal = process.env.npm_config_global === 'true';
25
+ const force = process.env.CODE_FORCE_PREINSTALL === '1';
26
+ const skip = process.env.CODE_SKIP_PREINSTALL === '1';
27
+ if (!isWinLike || skip || (!isGlobal && !force)) process.exit(0);
28
+
29
+ function tryExec(cmd, opts = {}) {
30
+ try { execSync(cmd, { stdio: ['ignore', 'ignore', 'ignore'], shell: true, ...opts }); } catch { /* ignore */ }
31
+ }
32
+
33
+ // 1) Stop our native binary if it is holding locks. Avoid killing unrelated tools.
34
+ // Only available on native Windows; skip entirely on WSL to avoid noise.
35
+ if (isWin) {
36
+ tryExec('taskkill /IM code-x86_64-pc-windows-msvc.exe /F');
37
+ }
38
+
39
+ // 2) Remove stale staging dirs from previous failed installs under the global
40
+ // @just-every scope, which npm will reuse (e.g., .code-XXXXX). Remove only
41
+ // old entries and never the current staging or live package.
42
+ try {
43
+ let scopeDir = '';
44
+ try {
45
+ const root = execSync('npm root -g', { stdio: ['ignore', 'pipe', 'ignore'], shell: true }).toString().trim();
46
+ scopeDir = path.join(root, '@just-every');
47
+ } catch {
48
+ // Fall back to guessing from this script location: <staging>\..\..\
49
+ const here = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
50
+ scopeDir = path.resolve(here, '..');
51
+ }
52
+ if (existsSync(scopeDir)) {
53
+ const now = Date.now();
54
+ const maxAgeMs = 2 * 60 * 60 * 1000; // 2 hours
55
+ const currentDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
56
+ for (const name of readdirSync(scopeDir)) {
57
+ if (!name.startsWith('.code-')) continue;
58
+ const p = path.join(scopeDir, name);
59
+ if (path.resolve(p) === currentDir) continue; // never remove our current dir
60
+ try {
61
+ const st = statSync(p);
62
+ const age = now - st.mtimeMs;
63
+ if (age > maxAgeMs) rmSync(p, { recursive: true, force: true });
64
+ } catch { /* ignore */ }
65
+ }
66
+ }
67
+ } catch { /* ignore */ }
68
+
69
+ process.exit(0);
@@ -0,0 +1,32 @@
1
+ <#
2
+ Helper to recover from EBUSY/EPERM during global npm upgrades on Windows.
3
+ Closes running processes and removes stale package folders.
4
+
5
+ Usage (PowerShell):
6
+ Set-ExecutionPolicy -Scope Process Bypass -Force
7
+ ./codex-cli/scripts/windows-cleanup.ps1
8
+ #>
9
+
10
+ $ErrorActionPreference = 'SilentlyContinue'
11
+
12
+ Write-Host "Stopping running Code/Coder processes..."
13
+ taskkill /IM code-x86_64-pc-windows-msvc.exe /F 2>$null | Out-Null
14
+ taskkill /IM code.exe /F 2>$null | Out-Null
15
+ taskkill /IM coder.exe /F 2>$null | Out-Null
16
+
17
+ Write-Host "Removing old global package (if present)..."
18
+ $npmRoot = (& npm root -g).Trim()
19
+ $pkgPath = Join-Path $npmRoot "@just-every\code"
20
+ if (Test-Path $pkgPath) {
21
+ try { Remove-Item -LiteralPath $pkgPath -Recurse -Force -ErrorAction Stop } catch {}
22
+ }
23
+
24
+ Write-Host "Removing temp staging directories (if present)..."
25
+ Get-ChildItem -LiteralPath (Join-Path $npmRoot "@just-every") -Force -ErrorAction SilentlyContinue |
26
+ Where-Object { $_.Name -like '.code-*' } |
27
+ ForEach-Object {
28
+ try { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction Stop } catch {}
29
+ }
30
+
31
+ Write-Host "Cleanup complete. You can now run: npm install -g @just-every/code@latest"
32
+