@mk-co/neox-cli 2.2.16 → 2.4.7-test

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/neox CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- require('../cli-wrapper.cjs');
2
+ require('../cli-wrapper.cjs').run();
package/cli-wrapper.cjs CHANGED
@@ -1,83 +1,120 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @mk-co/neox-cli · cli-wrapper
3
+ * @mk-co/neox-cli · cli-wrapper (launcher)
4
4
  *
5
- * 主包安装后, bin/neox 是个 symlink → @mk-co/neox-cli-${platform}/neox.
6
- * 理论上 npm bin shim 直接走 symlink 就行, 不一定需要 wrapper.
5
+ * Windows EBUSY 根治
6
+ * 以前: bin/neox = symlink/copy node_modules 里的平台二进制, 直接跑它 → 运行中的 .exe 被
7
+ * Windows 锁死 → `npm i -g` 要覆盖 node_modules\...\neox.exe → EBUSY (Claude Code 是纯 JS
8
+ * 跑在 node 上, .js 不被锁, 所以没这问题)。
7
9
  *
8
- * 但保留这个 wrapper 是给两种 fallback 路径:
9
- *
10
- * 1. 用户 main 包当 require('@mk-co/neox-cli') module 用 (尽管罕见) —
11
- * package.json `"main": "./cli-wrapper.cjs"` require 拿到这个 stub,
12
- * 不会拿到 binary 然后 SyntaxError.
13
- *
14
- * 2. 用户 install 时 postinstall 失败 (e.g. --no-optional), bin/neox 是
15
- * placeholder, 直接跑 `neox` 时 placeholder script 调本 wrapper 退出
16
- * + 友好提示.
17
- *
18
- * binary 跑起来后, 这个 wrapper 完全不在 critical path, 所以零业务逻辑.
10
+ * 现在: bin/neox 永远是这个 JS launcher (node 跑, 不被锁)。它把平台二进制【copy 到缓存】
11
+ * `~/.neox/bin/neox-<版本>(.exe)` (在 node_modules 之外), 跑【缓存】那一份。daemon / workers
12
+ * 都用 process.execPath = 缓存二进制起, 也跑在缓存。
13
+ * `npm i -g` 只覆盖 node_modules (JS launcher + 源二进制, 都没在运行) → 永不 EBUSY。
14
+ * 缓存按版本命名, 升级时换新名字 (旧的在跑也不冲突), 用完清理旧的。
19
15
  */
20
16
 
21
17
  'use strict';
22
18
 
23
19
  const fs = require('node:fs');
24
20
  const path = require('node:path');
21
+ const os = require('node:os');
22
+ const { spawn } = require('node:child_process');
25
23
 
26
24
  const SUPPORTED = {
27
- 'darwin-arm64': '@mk-co/neox-cli-darwin-arm64',
28
- 'darwin-x64': '@mk-co/neox-cli-darwin-x64',
29
- 'linux-x64': '@mk-co/neox-cli-linux-x64',
30
- 'linux-arm64': '@mk-co/neox-cli-linux-arm64',
31
- 'win32-x64': '@mk-co/neox-cli-win32-x64',
25
+ 'darwin-arm64': '@mk-co/neox-cli-darwin-arm64',
26
+ 'darwin-x64': '@mk-co/neox-cli-darwin-x64',
27
+ 'linux-x64': '@mk-co/neox-cli-linux-x64',
28
+ 'linux-arm64': '@mk-co/neox-cli-linux-arm64',
29
+ 'win32-x64': '@mk-co/neox-cli-win32-x64',
32
30
  };
33
31
 
34
- function findBinary() {
35
- const platformKey = `${process.platform}-${process.arch}`;
36
- const pkgName = SUPPORTED[platformKey];
37
- if (!pkgName) {
38
- process.stderr.write(`neox: 不支持当前平台 ${platformKey}.\n`);
39
- process.stderr.write(`支持的平台: ${Object.keys(SUPPORTED).join(', ')}\n`);
40
- process.exit(1);
41
- }
32
+ const EXE = process.platform === 'win32' ? 'neox.exe' : 'neox';
33
+
34
+ /** 解析 node_modules 里的平台二进制 (源) + 其版本号。失败返回 { error }。 */
35
+ function resolveSource() {
36
+ const key = `${process.platform}-${process.arch}`;
37
+ const pkg = SUPPORTED[key];
38
+ if (!pkg) return { error: `不支持当前平台 ${key} (支持: ${Object.keys(SUPPORTED).join(', ')})` };
42
39
  try {
43
- const pkgRoot = path.dirname(require.resolve(`${pkgName}/package.json`));
44
- const exeName = process.platform === 'win32' ? 'neox.exe' : 'neox';
45
- const binary = path.join(pkgRoot, exeName);
46
- if (fs.existsSync(binary)) return binary;
40
+ const root = path.dirname(require.resolve(`${pkg}/package.json`));
41
+ const bin = path.join(root, EXE);
42
+ if (fs.existsSync(bin)) {
43
+ let version = '0';
44
+ try { version = require(`${pkg}/package.json`).version || '0'; } catch { /* keep 0 */ }
45
+ return { bin, version };
46
+ }
47
47
  } catch { /* fall through */ }
48
+ return { error: `平台子包 ${pkg} 未安装 (可能用了 --no-optional)。修复: npm install -g ${pkg}` };
49
+ }
48
50
 
49
- process.stderr.write(`neox: 平台子包 ${pkgName} 没装 (可能用了 --no-optional).\n`);
50
- process.stderr.write(`修复: npm install -g ${pkgName}\n`);
51
- process.exit(1);
51
+ /**
52
+ * 把源二进制 copy 到缓存 ~/.neox/bin/neox-<版本>(.exe), 返回缓存路径。
53
+ * · 已存在且大小一致 → 跳过 copy
54
+ * · 原子: 临时文件 + rename (rename 失败说明被同版本 daemon 锁着, 即已存在, 直接用)
55
+ * · 清理旧版本缓存 (best-effort; 锁着就跳过)
56
+ * · 任何失败 → 回落直接返回源路径 (退化成旧行为, 至少能跑)
57
+ */
58
+ function ensureCached(src, version) {
59
+ const cacheDir = path.join(os.homedir(), '.neox', 'bin');
60
+ const name = process.platform === 'win32' ? `neox-${version}.exe` : `neox-${version}`;
61
+ const dest = path.join(cacheDir, name);
62
+ try {
63
+ fs.mkdirSync(cacheDir, { recursive: true });
64
+ let need = true;
65
+ try {
66
+ if (fs.statSync(dest).size === fs.statSync(src).size) need = false; // 已缓存好
67
+ } catch { /* dest 不存在 → need=true */ }
68
+ if (need) {
69
+ const tmp = path.join(cacheDir, `.tmp-${process.pid}-${Date.now()}-${name}`);
70
+ fs.copyFileSync(src, tmp);
71
+ if (process.platform !== 'win32') { try { fs.chmodSync(tmp, 0o755); } catch { /* ignore */ } }
72
+ try {
73
+ fs.renameSync(tmp, dest);
74
+ } catch {
75
+ // dest 被同版本运行中的 daemon 锁住 → 它已存在且就是对的, 删临时文件用现有的
76
+ try { fs.rmSync(tmp, { force: true }); } catch { /* ignore */ }
77
+ }
78
+ }
79
+ // 清理旧版本缓存 (锁着的旧 daemon 二进制跳过, 下次再清)
80
+ try {
81
+ for (const f of fs.readdirSync(cacheDir)) {
82
+ if (f === name || f.startsWith('.tmp-') || !f.startsWith('neox-')) continue;
83
+ try { fs.rmSync(path.join(cacheDir, f), { force: true }); } catch { /* 锁着, 跳过 */ }
84
+ }
85
+ } catch { /* ignore */ }
86
+ if (fs.existsSync(dest)) return dest;
87
+ } catch { /* 缓存整体失败 → 回落源路径 */ }
88
+ return src;
52
89
  }
53
90
 
54
- /* 直接 exec, 跨进程 forward stdio + signals. */
55
- const { spawn } = require('node:child_process');
56
- const binary = findBinary();
57
- const child = spawn(binary, process.argv.slice(2), {
58
- stdio: 'inherit',
59
- windowsHide: false,
60
- });
91
+ /** 安装期预热: 把当前平台二进制 copy 进缓存, 让首次 `neox` 不用现 copy。失败静默。 */
92
+ function prewarm() {
93
+ try {
94
+ const s = resolveSource();
95
+ if (s.bin) ensureCached(s.bin, s.version);
96
+ } catch { /* best-effort */ }
97
+ }
61
98
 
62
- const passSignal = (sig) => () => {
63
- try { child.kill(sig); } catch { /* ignore */ }
64
- };
65
- process.on('SIGINT', passSignal('SIGINT'));
66
- process.on('SIGTERM', passSignal('SIGTERM'));
67
- process.on('SIGHUP', passSignal('SIGHUP'));
99
+ /** 真正启动: 解析源 → 确保缓存 → 跑缓存二进制 (透传 argv / stdio / signals) */
100
+ function run() {
101
+ const s = resolveSource();
102
+ if (s.error) { process.stderr.write(`neox: ${s.error}\n`); process.exit(1); return; }
103
+ const target = ensureCached(s.bin, s.version);
68
104
 
69
- child.on('exit', (code, sig) => {
70
- if (sig) {
71
- /* 让 parent 也以同样 signal 死, shell 才能正确 detect Ctrl-C 退出 */
72
- process.kill(process.pid, sig);
73
- } else {
74
- process.exit(code ?? 0);
75
- }
76
- });
105
+ const child = spawn(target, process.argv.slice(2), { stdio: 'inherit', windowsHide: false });
106
+ const pass = (sig) => () => { try { child.kill(sig); } catch { /* ignore */ } };
107
+ process.on('SIGINT', pass('SIGINT'));
108
+ process.on('SIGTERM', pass('SIGTERM'));
109
+ process.on('SIGHUP', pass('SIGHUP'));
110
+ child.on('exit', (code, sig) => {
111
+ if (sig) process.kill(process.pid, sig); // 让 parent 也以同 signal 死, shell 能正确 detect Ctrl-C
112
+ else process.exit(code ?? 0);
113
+ });
114
+ child.on('error', (e) => { process.stderr.write(`neox: 无法启动 binary: ${e.message}\n`); process.exit(2); });
115
+ }
77
116
 
78
- child.on('error', (e) => {
79
- process.stderr.write(`neox: 无法启动 binary: ${e.message}\n`);
80
- process.exit(2);
81
- });
117
+ module.exports = { run, prewarm, resolveSource, ensureCached };
82
118
 
83
- module.exports = { findBinary };
119
+ /* 作为 binary 直接跑 (bin/neox → require(this).run())。被 require module 用时不自动跑。 */
120
+ if (require.main === module) run();
package/install.cjs CHANGED
@@ -3,40 +3,30 @@
3
3
  * @mk-co/neox-cli · postinstall
4
4
  *
5
5
  * npm 装完主包 + 当前 platform 的 optional dep 之后跑这里:
6
- * · 检测当前 platform = "${process.platform}-${process.arch}"
7
- * · 找到 @mk-co/neox-cli-${platform} native binary 路径
8
- * · symlink (Unix) / copy (Windows) 到 ./bin/neox 给 npm bin shim 用
6
+ * · 校验当前 platform 受支持 + 平台子包的二进制在位
7
+ * · 预热: 把二进制 copy 进缓存 ~/.neox/bin/neox-<版本>(.exe), 让首次 `neox` 不用现 copy
9
8
  *
10
- * 设计跟 @anthropic-ai/claude-code 同套路 主包零业务代码, binary 在
11
- * 平台子包里, 用户 `npm install -g` optional deps 自动只装当前 platform.
9
+ * 不再把 bin/neox 替换成指向 node_modules 二进制的 symlink/copy
10
+ * bin/neox 永远是 JS launcher (cli-wrapper)。真正运行的二进制在缓存里 (node_modules 之外),
11
+ * 所以 `npm i -g` 重装/升级时 npm 不会去覆盖一个【正在运行 / 被锁】的文件 → 根治 Windows EBUSY。
12
12
  *
13
- * 失败容错:
14
- * · platform 不支持 → 退 1 + 友好提示 (列出我们支持的 platforms)
15
- * · platform 包没装 → 友好提示用户 manual install (常见: 用了 --no-optional)
16
- *
17
- * 不抛 throw, 因为 npm postinstall 抛了会让整个 install 显红, 但其实没 fatal.
13
+ * 不抛 throw — postinstall 抛了会让整个 install 显红, 但其实没 fatal (跑 neox 时 launcher 会兜底)。
18
14
  */
19
15
 
20
16
  'use strict';
21
17
 
22
18
  const fs = require('node:fs');
23
19
  const path = require('node:path');
24
- const os = require('node:os');
25
20
 
26
21
  const SUPPORTED = {
27
- 'darwin-arm64': '@mk-co/neox-cli-darwin-arm64',
28
- 'linux-x64': '@mk-co/neox-cli-linux-x64',
29
- 'win32-x64': '@mk-co/neox-cli-win32-x64',
30
- // darwin-x64 (Intel Mac) + linux-arm64 本版未发布 (runner 排队 / 设备少). 这些平台会落到
31
- // "platform 不支持" 友好提示, 待加回时同步 build-cli-binaries PLATFORMS + package.json.tpl.
22
+ 'darwin-arm64': '@mk-co/neox-cli-darwin-arm64',
23
+ 'linux-x64': '@mk-co/neox-cli-linux-x64',
24
+ 'win32-x64': '@mk-co/neox-cli-win32-x64',
25
+ // darwin-x64 / linux-arm64 本版未发布; 落到"平台不支持"友好提示。
32
26
  };
33
27
 
34
- function log(msg) {
35
- process.stdout.write(`[neox-cli postinstall] ${msg}\n`);
36
- }
37
- function warn(msg) {
38
- process.stderr.write(`[neox-cli postinstall] ⚠ ${msg}\n`);
39
- }
28
+ function log(msg) { process.stdout.write(`[neox-cli postinstall] ${msg}\n`); }
29
+ function warn(msg) { process.stderr.write(`[neox-cli postinstall] ${msg}\n`); }
40
30
 
41
31
  function main() {
42
32
  const platformKey = `${process.platform}-${process.arch}`;
@@ -45,18 +35,15 @@ function main() {
45
35
  if (!pkgName) {
46
36
  warn(`不支持当前平台 ${platformKey}.`);
47
37
  warn(`支持的平台: ${Object.keys(SUPPORTED).join(', ')}`);
48
- warn(`你的 neox 命令会找不到, 请联系 MK-CO 支持其他平台.`);
49
- return; /* 不退 1, 让 npm install 整体成功, 用户跑 neox 时再报错 */
38
+ warn(`你的 neox 命令会找不到二进制, 请联系 MK-CO 支持其他平台.`);
39
+ return; /* 不退 1 */
50
40
  }
51
41
 
52
- /* platform sub-package 的 binary. node_modules 解析路径取决于安装方式
53
- * (-g vs 项目级), 用 require.resolve 让 npm 自己决定. */
54
- let binarySrc;
42
+ /* 校验平台子包二进制在位 */
55
43
  try {
56
- /* 期待: node_modules/@mk-co/neox-cli-${platform}/neox (Unix) or neox.exe (Win) */
57
44
  const pkgRoot = path.dirname(require.resolve(`${pkgName}/package.json`));
58
45
  const exeName = process.platform === 'win32' ? 'neox.exe' : 'neox';
59
- binarySrc = path.join(pkgRoot, exeName);
46
+ const binarySrc = path.join(pkgRoot, exeName);
60
47
  if (!fs.existsSync(binarySrc)) {
61
48
  warn(`平台子包已装但 binary 不在 ${binarySrc} — 可能 publish 出错.`);
62
49
  return;
@@ -68,30 +55,13 @@ function main() {
68
55
  return;
69
56
  }
70
57
 
71
- /* 创建 bin/neox 指向 platform binary. Unix 用 symlink, Windows 用 copy. */
72
- const binDir = path.join(__dirname, 'bin');
73
- fs.mkdirSync(binDir, { recursive: true });
74
- const exeName = process.platform === 'win32' ? 'neox.exe' : 'neox';
75
- const binDest = path.join(binDir, exeName);
76
-
58
+ /* 预热缓存 (copy 二进制到 ~/.neox/bin/neox-<版本>)。失败不致命 launcher 首次跑时会兜底 copy */
77
59
  try {
78
- if (fs.existsSync(binDest) || fs.lstatSync(binDest).isSymbolicLink?.()) {
79
- fs.rmSync(binDest, { force: true });
80
- }
81
- } catch { /* ignore */ }
82
-
83
- try {
84
- if (process.platform === 'win32') {
85
- /* Windows: 不能 symlink (要管理员), 直接 copy */
86
- fs.copyFileSync(binarySrc, binDest);
87
- } else {
88
- fs.symlinkSync(binarySrc, binDest);
89
- fs.chmodSync(binDest, 0o755);
90
- }
91
- log(`installed ${platformKey}: ${path.relative(process.cwd(), binDest)} → ${path.relative(process.cwd(), binarySrc)}`);
60
+ const { prewarm } = require('./cli-wrapper.cjs');
61
+ prewarm();
62
+ log(`ready (${platformKey}) — 二进制已缓存到 ~/.neox/bin, 跑 neox 即可。`);
92
63
  } catch (e) {
93
- warn(`无法 ${process.platform === 'win32' ? 'copy' : 'symlink'} binary: ${e.message}`);
94
- warn(`手动 fix: ${process.platform === 'win32' ? 'copy' : 'ln -s'} "${binarySrc}" "${binDest}"`);
64
+ warn(`缓存预热失败 (非致命, 首次跑 neox 时会自动补): ${e?.message || e}`);
95
65
  }
96
66
  }
97
67
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mk-co/neox-cli",
3
- "version": "2.2.16",
3
+ "version": "2.4.7-test",
4
4
  "description": "Neox CLI · Professional AI code assistant",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -21,9 +21,9 @@
21
21
  "postinstall": "node install.cjs"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@mk-co/neox-cli-darwin-arm64": "2.2.16",
25
- "@mk-co/neox-cli-linux-x64": "2.2.16",
26
- "@mk-co/neox-cli-win32-x64": "2.2.16"
24
+ "@mk-co/neox-cli-darwin-arm64": "2.4.7-test",
25
+ "@mk-co/neox-cli-linux-x64": "2.4.7-test",
26
+ "@mk-co/neox-cli-win32-x64": "2.4.7-test"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"