bingocode 1.0.34 → 1.0.35
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/bingo-win.cjs +96 -148
- package/bin/bingocode-win.cjs +63 -143
- package/bin/claude-win.cjs +15 -93
- package/package.json +1 -3
- package/src/entrypoints/cli.tsx +2 -4
- package/src/interactiveHelpers.tsx +2 -2
- package/src/server/ensureSingletonLocalServer.ts +1 -1
- package/src/utils/config.ts +0 -15
- package/scripts/postinstall-ripgrep.cjs +0 -299
- package/scripts/preinstall-stop.cjs +0 -164
package/bin/bingo-win.cjs
CHANGED
|
@@ -1,148 +1,96 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { spawn, spawnSync } = require('node:child_process');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
|
|
8
|
-
process.env.NoDefaultCurrentDirectoryInExePath = '1';
|
|
9
|
-
|
|
10
|
-
// ──
|
|
11
|
-
//
|
|
12
|
-
// 这样 isOfficialMode() 返回 false,子进程不会走 OAuth 流程。
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// 安装后 bun.exe 在固定位置;若在 PATH 里则直接用 "bun"
|
|
98
|
-
const bun = fs.existsSync(bunPath) ? bunPath : 'bun';
|
|
99
|
-
|
|
100
|
-
// ── 自动检测并安装 git ──
|
|
101
|
-
function gitExists() {
|
|
102
|
-
const result = spawnSync('git', ['--version'], { stdio: 'ignore', shell: true });
|
|
103
|
-
return result.status === 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function installGit() {
|
|
107
|
-
console.log('[bingo] git 未检测到,正在通过 winget 自动安装...');
|
|
108
|
-
const result = spawnSync(
|
|
109
|
-
'winget',
|
|
110
|
-
['install', '--id', 'Git.Git', '-e', '--source', 'winget',
|
|
111
|
-
'--accept-package-agreements', '--accept-source-agreements'],
|
|
112
|
-
{ stdio: 'inherit', shell: true }
|
|
113
|
-
);
|
|
114
|
-
if (result.status !== 0) {
|
|
115
|
-
console.warn('[bingo] git 自动安装失败,请手动安装:https://git-scm.com');
|
|
116
|
-
// 不退出 —— git 不是启动必须依赖,缺少时仅降级部分功能
|
|
117
|
-
} else {
|
|
118
|
-
console.log('[bingo] git 安装完成,若 PATH 未生效请重启终端。');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!gitExists()) {
|
|
123
|
-
installGit();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Bingo Manager 入口(窗口管理控制台,不走 cli.tsx 完整启动流程)
|
|
127
|
-
const entry = path.join(__dirname, '..', 'src', 'entrypoints', 'manager.tsx');
|
|
128
|
-
|
|
129
|
-
// preload shim(定义 MACRO 全局变量)——必须用绝对路径,bunfig.toml 在 npm 全局安装后不生效
|
|
130
|
-
const preload = path.join(__dirname, '..', 'preload.ts');
|
|
131
|
-
if (!fs.existsSync(preload)) {
|
|
132
|
-
console.error('[bingocode] 找不到 preload.ts,MACRO 将无法注入:' + preload);
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 检查 .env
|
|
137
|
-
let envFlag = '';
|
|
138
|
-
const envPath = path.join(__dirname, '..', '.env');
|
|
139
|
-
if (fs.existsSync(envPath)) {
|
|
140
|
-
envFlag = `--env-file=${envPath}`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const extraArgs = process.argv.slice(2);
|
|
144
|
-
const args = [`--preload=${preload}`, envFlag, entry, ...extraArgs].filter(Boolean);
|
|
145
|
-
|
|
146
|
-
const child = spawn(bun, args, { stdio: 'inherit' });
|
|
147
|
-
|
|
148
|
-
child.on('exit', (code) => process.exit(code));
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require('node:child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
process.env.NoDefaultCurrentDirectoryInExePath = '1';
|
|
9
|
+
|
|
10
|
+
// ── 首次部署:将默认 bingo 配置复制到 ~/.claude/bingo/ ──
|
|
11
|
+
// 确保新电脑首次启动时 settings.json 存在(含占位符 ANTHROPIC_AUTH_TOKEN),
|
|
12
|
+
// 这样 isOfficialMode() 返回 false,子进程不会走 OAuth 流程。
|
|
13
|
+
// 用户通过 ProviderPanel 激活 provider 后 syncToSettings() 会覆盖此文件。
|
|
14
|
+
(function deployBingoDefaults() {
|
|
15
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
16
|
+
const bingoDir = path.join(configDir, 'bingo');
|
|
17
|
+
const targetSettings = path.join(bingoDir, 'settings.json');
|
|
18
|
+
|
|
19
|
+
// 只在 settings.json 不存在时才部署(不覆盖已有配置)
|
|
20
|
+
if (!fs.existsSync(targetSettings)) {
|
|
21
|
+
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
22
|
+
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
23
|
+
|
|
24
|
+
if (fs.existsSync(srcSettings)) {
|
|
25
|
+
try {
|
|
26
|
+
if (!fs.existsSync(bingoDir)) {
|
|
27
|
+
fs.mkdirSync(bingoDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
fs.copyFileSync(srcSettings, targetSettings);
|
|
30
|
+
console.log('[bingo] 首次启动:已部署默认配置到', targetSettings);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.warn('[bingo] 部署默认配置失败:', err.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
// 自动定位 bun 路径
|
|
39
|
+
const bunPath =
|
|
40
|
+
process.env.BUN_PATH ||
|
|
41
|
+
path.join(os.homedir(), '.bun', 'bin', 'bun.exe');
|
|
42
|
+
|
|
43
|
+
// 检查 bun 是否可用(先看固定路径,再看 PATH)
|
|
44
|
+
function bunExists() {
|
|
45
|
+
if (fs.existsSync(bunPath)) return true;
|
|
46
|
+
// 尝试 PATH 中的 bun
|
|
47
|
+
const result = spawnSync('bun', ['--version'], { stdio: 'ignore', shell: true });
|
|
48
|
+
return result.status === 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 安装 bun(Windows PowerShell 方式)
|
|
52
|
+
function installBun() {
|
|
53
|
+
console.log('[bingocode] bun 未检测到,正在自动安装...');
|
|
54
|
+
const result = spawnSync(
|
|
55
|
+
'powershell',
|
|
56
|
+
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command',
|
|
57
|
+
'irm bun.sh/install.ps1 | iex'],
|
|
58
|
+
{ stdio: 'inherit', shell: false }
|
|
59
|
+
);
|
|
60
|
+
if (result.status !== 0) {
|
|
61
|
+
console.error('[bingocode] bun 安装失败,请手动安装:https://bun.sh');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
console.log('[bingocode] bun 安装完成,正在启动...');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!bunExists()) {
|
|
68
|
+
installBun();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 安装后 bun.exe 在固定位置;若在 PATH 里则直接用 "bun"
|
|
72
|
+
const bun = fs.existsSync(bunPath) ? bunPath : 'bun';
|
|
73
|
+
|
|
74
|
+
// Bingo Manager 入口(窗口管理控制台,不走 cli.tsx 完整启动流程)
|
|
75
|
+
const entry = path.join(__dirname, '..', 'src', 'entrypoints', 'manager.tsx');
|
|
76
|
+
|
|
77
|
+
// preload shim(定义 MACRO 全局变量)——必须用绝对路径,bunfig.toml 在 npm 全局安装后不生效
|
|
78
|
+
const preload = path.join(__dirname, '..', 'preload.ts');
|
|
79
|
+
if (!fs.existsSync(preload)) {
|
|
80
|
+
console.error('[bingocode] 找不到 preload.ts,MACRO 将无法注入:' + preload);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 检查 .env
|
|
85
|
+
let envFlag = '';
|
|
86
|
+
const envPath = path.join(__dirname, '..', '.env');
|
|
87
|
+
if (fs.existsSync(envPath)) {
|
|
88
|
+
envFlag = `--env-file=${envPath}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const extraArgs = process.argv.slice(2);
|
|
92
|
+
const args = [`--preload=${preload}`, envFlag, entry, ...extraArgs].filter(Boolean);
|
|
93
|
+
|
|
94
|
+
const child = spawn(bun, args, { stdio: 'inherit' });
|
|
95
|
+
|
|
96
|
+
child.on('exit', (code) => process.exit(code));
|
package/bin/bingocode-win.cjs
CHANGED
|
@@ -1,143 +1,63 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { spawn
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
|
|
8
|
-
process.env.NoDefaultCurrentDirectoryInExePath = '1';
|
|
9
|
-
|
|
10
|
-
// ──
|
|
11
|
-
//
|
|
12
|
-
// 这样 isAnthropicAuthEnabled() 返回 false,跳过 OAuth 流程。
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
(
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// ── 自动定位并安装 bun ──
|
|
65
|
-
const bunPath =
|
|
66
|
-
process.env.BUN_PATH ||
|
|
67
|
-
path.join(os.homedir(), '.bun', 'bin', 'bun.exe');
|
|
68
|
-
|
|
69
|
-
function bunExists() {
|
|
70
|
-
if (fs.existsSync(bunPath)) return true;
|
|
71
|
-
const result = spawnSync('bun', ['--version'], { stdio: 'ignore', shell: true });
|
|
72
|
-
return result.status === 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function installBun() {
|
|
76
|
-
console.log('[bingocode] bun 未检测到,正在自动安装...');
|
|
77
|
-
const result = spawnSync(
|
|
78
|
-
'powershell',
|
|
79
|
-
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command',
|
|
80
|
-
'irm bun.sh/install.ps1 | iex'],
|
|
81
|
-
{ stdio: 'inherit', shell: false }
|
|
82
|
-
);
|
|
83
|
-
if (result.status !== 0) {
|
|
84
|
-
console.error('[bingocode] bun 安装失败,请手动安装:https://bun.sh');
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
console.log('[bingocode] bun 安装完成,正在启动...');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!bunExists()) {
|
|
91
|
-
installBun();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const bun = fs.existsSync(bunPath) ? bunPath : 'bun';
|
|
95
|
-
|
|
96
|
-
// ── 自动检测并安装 git ──
|
|
97
|
-
function gitExists() {
|
|
98
|
-
const result = spawnSync('git', ['--version'], { stdio: 'ignore', shell: true });
|
|
99
|
-
return result.status === 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function installGit() {
|
|
103
|
-
console.log('[bingocode] git 未检测到,正在通过 winget 自动安装...');
|
|
104
|
-
const result = spawnSync(
|
|
105
|
-
'winget',
|
|
106
|
-
['install', '--id', 'Git.Git', '-e', '--source', 'winget',
|
|
107
|
-
'--accept-package-agreements', '--accept-source-agreements'],
|
|
108
|
-
{ stdio: 'inherit', shell: true }
|
|
109
|
-
);
|
|
110
|
-
if (result.status !== 0) {
|
|
111
|
-
console.warn('[bingocode] git 自动安装失败,请手动安装:https://git-scm.com');
|
|
112
|
-
} else {
|
|
113
|
-
console.log('[bingocode] git 安装完成,若 PATH 未生效请重启终端。');
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!gitExists()) {
|
|
118
|
-
installGit();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// 主 CLI 入口
|
|
122
|
-
const entry = path.join(__dirname, '..', 'src', 'entrypoints', 'cli.tsx');
|
|
123
|
-
|
|
124
|
-
// preload shim(定义 MACRO 全局变量)——必须用绝对路径,bunfig.toml 在 npm 全局安装后不生效
|
|
125
|
-
const preload = path.join(__dirname, '..', 'preload.ts');
|
|
126
|
-
if (!fs.existsSync(preload)) {
|
|
127
|
-
console.error('[bingocode] 找不到 preload.ts,MACRO 将无法注入:' + preload);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 检查 .env
|
|
132
|
-
let envFlag = '';
|
|
133
|
-
const envPath = path.join(__dirname, '..', '.env');
|
|
134
|
-
if (fs.existsSync(envPath)) {
|
|
135
|
-
envFlag = `--env-file=${envPath}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const extraArgs = process.argv.slice(2);
|
|
139
|
-
const args = [`--preload=${preload}`, envFlag, entry, ...extraArgs].filter(Boolean);
|
|
140
|
-
|
|
141
|
-
const child = spawn(bun, args, { stdio: 'inherit' });
|
|
142
|
-
|
|
143
|
-
child.on('exit', (code) => process.exit(code));
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
process.env.NoDefaultCurrentDirectoryInExePath = '1';
|
|
9
|
+
|
|
10
|
+
// ── 首次部署:将默认 bingo 配置复制到 ~/.claude/bingo/ ──
|
|
11
|
+
// 确保新电脑首次启动时 settings.json 存在(含占位符 ANTHROPIC_AUTH_TOKEN),
|
|
12
|
+
// 这样 isAnthropicAuthEnabled() 返回 false,跳过 OAuth 流程。
|
|
13
|
+
(function deployBingoDefaults() {
|
|
14
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
15
|
+
const bingoDir = path.join(configDir, 'bingo');
|
|
16
|
+
const targetSettings = path.join(bingoDir, 'settings.json');
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(targetSettings)) {
|
|
19
|
+
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
20
|
+
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
21
|
+
|
|
22
|
+
if (fs.existsSync(srcSettings)) {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(bingoDir)) {
|
|
25
|
+
fs.mkdirSync(bingoDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
fs.copyFileSync(srcSettings, targetSettings);
|
|
28
|
+
console.log('[bingocode] 首次启动:已部署默认配置到', targetSettings);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.warn('[bingocode] 部署默认配置失败:', err.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
|
|
36
|
+
// 自动定位 bun 路径
|
|
37
|
+
const bun =
|
|
38
|
+
process.env.BUN_PATH ||
|
|
39
|
+
path.join(os.homedir(), '.bun', 'bin', 'bun.exe');
|
|
40
|
+
|
|
41
|
+
// 主 CLI 入口
|
|
42
|
+
const entry = path.join(__dirname, '..', 'src', 'entrypoints', 'cli.tsx');
|
|
43
|
+
|
|
44
|
+
// preload shim(定义 MACRO 全局变量)——必须用绝对路径,bunfig.toml 在 npm 全局安装后不生效
|
|
45
|
+
const preload = path.join(__dirname, '..', 'preload.ts');
|
|
46
|
+
if (!fs.existsSync(preload)) {
|
|
47
|
+
console.error('[bingocode] 找不到 preload.ts,MACRO 将无法注入:' + preload);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 检查 .env
|
|
52
|
+
let envFlag = '';
|
|
53
|
+
const envPath = path.join(__dirname, '..', '.env');
|
|
54
|
+
if (fs.existsSync(envPath)) {
|
|
55
|
+
envFlag = `--env-file=${envPath}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const extraArgs = process.argv.slice(2);
|
|
59
|
+
const args = [`--preload=${preload}`, envFlag, entry, ...extraArgs].filter(Boolean);
|
|
60
|
+
|
|
61
|
+
const child = spawn(bun, args, { stdio: 'inherit' });
|
|
62
|
+
|
|
63
|
+
child.on('exit', (code) => process.exit(code));
|
package/bin/claude-win.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const fs = require('fs');
|
|
@@ -8,113 +8,35 @@ const fs = require('fs');
|
|
|
8
8
|
// 使 Windows bun/spawn 更可靠
|
|
9
9
|
process.env.NoDefaultCurrentDirectoryInExePath = '1';
|
|
10
10
|
|
|
11
|
-
// ──
|
|
11
|
+
// ── 首次部署:将默认 bingo 配置复制到 ~/.claude/bingo/ ──
|
|
12
12
|
(function deployBingoDefaults() {
|
|
13
13
|
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
14
14
|
const bingoDir = path.join(configDir, 'bingo');
|
|
15
15
|
const targetSettings = path.join(bingoDir, 'settings.json');
|
|
16
|
-
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
17
|
-
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
18
16
|
|
|
19
|
-
if (!fs.existsSync(
|
|
17
|
+
if (!fs.existsSync(targetSettings)) {
|
|
18
|
+
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
19
|
+
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!fs.existsSync(targetSettings)) {
|
|
27
|
-
// 首次安装:直接复制
|
|
28
|
-
fs.copyFileSync(srcSettings, targetSettings);
|
|
29
|
-
console.log('[claude] 首次启动:已部署默认配置到', targetSettings);
|
|
30
|
-
} else {
|
|
31
|
-
// 升级安装:增量合并缺失的 key
|
|
32
|
-
const defaults = JSON.parse(fs.readFileSync(srcSettings, 'utf-8'));
|
|
33
|
-
const current = JSON.parse(fs.readFileSync(targetSettings, 'utf-8'));
|
|
34
|
-
let changed = false;
|
|
35
|
-
|
|
36
|
-
for (const section of Object.keys(defaults)) {
|
|
37
|
-
if (typeof defaults[section] !== 'object' || defaults[section] === null) continue;
|
|
38
|
-
if (!current[section] || typeof current[section] !== 'object') {
|
|
39
|
-
current[section] = defaults[section];
|
|
40
|
-
changed = true;
|
|
41
|
-
continue;
|
|
21
|
+
if (fs.existsSync(srcSettings)) {
|
|
22
|
+
try {
|
|
23
|
+
if (!fs.existsSync(bingoDir)) {
|
|
24
|
+
fs.mkdirSync(bingoDir, { recursive: true });
|
|
42
25
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (changed) {
|
|
52
|
-
fs.writeFileSync(targetSettings, JSON.stringify(current, null, 2) + '\n');
|
|
53
|
-
console.log('[claude] 升级:已合并新增默认配置到', targetSettings);
|
|
26
|
+
fs.copyFileSync(srcSettings, targetSettings);
|
|
27
|
+
console.log('[claude] 首次启动:已部署默认配置到', targetSettings);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.warn('[claude] 部署默认配置失败:', err.message);
|
|
54
30
|
}
|
|
55
31
|
}
|
|
56
|
-
} catch (err) {
|
|
57
|
-
console.warn('[claude] 部署默认配置失败:', err.message);
|
|
58
32
|
}
|
|
59
33
|
})();
|
|
60
34
|
|
|
61
|
-
//
|
|
62
|
-
const
|
|
35
|
+
// 自动定位 bun 路径
|
|
36
|
+
const bun =
|
|
63
37
|
process.env.BUN_PATH ||
|
|
64
38
|
path.join(os.homedir(), '.bun', 'bin', 'bun.exe');
|
|
65
39
|
|
|
66
|
-
function bunExists() {
|
|
67
|
-
if (fs.existsSync(bunPath)) return true;
|
|
68
|
-
const result = spawnSync('bun', ['--version'], { stdio: 'ignore', shell: true });
|
|
69
|
-
return result.status === 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function installBun() {
|
|
73
|
-
console.log('[claude] bun 未检测到,正在自动安装...');
|
|
74
|
-
const result = spawnSync(
|
|
75
|
-
'powershell',
|
|
76
|
-
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command',
|
|
77
|
-
'irm bun.sh/install.ps1 | iex'],
|
|
78
|
-
{ stdio: 'inherit', shell: false }
|
|
79
|
-
);
|
|
80
|
-
if (result.status !== 0) {
|
|
81
|
-
console.error('[claude] bun 安装失败,请手动安装:https://bun.sh');
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
console.log('[claude] bun 安装完成,正在启动...');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!bunExists()) {
|
|
88
|
-
installBun();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const bun = fs.existsSync(bunPath) ? bunPath : 'bun';
|
|
92
|
-
|
|
93
|
-
// ── 自动检测并安装 git ──
|
|
94
|
-
function gitExists() {
|
|
95
|
-
const result = spawnSync('git', ['--version'], { stdio: 'ignore', shell: true });
|
|
96
|
-
return result.status === 0;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function installGit() {
|
|
100
|
-
console.log('[claude] git 未检测到,正在通过 winget 自动安装...');
|
|
101
|
-
const result = spawnSync(
|
|
102
|
-
'winget',
|
|
103
|
-
['install', '--id', 'Git.Git', '-e', '--source', 'winget',
|
|
104
|
-
'--accept-package-agreements', '--accept-source-agreements'],
|
|
105
|
-
{ stdio: 'inherit', shell: true }
|
|
106
|
-
);
|
|
107
|
-
if (result.status !== 0) {
|
|
108
|
-
console.warn('[claude] git 自动安装失败,请手动安装:https://git-scm.com');
|
|
109
|
-
} else {
|
|
110
|
-
console.log('[claude] git 安装完成,若 PATH 未生效请重启终端。');
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!gitExists()) {
|
|
115
|
-
installGit();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
40
|
// 主 CLI 入口
|
|
119
41
|
const entry = path.join(__dirname, '..', 'src', 'entrypoints', 'cli.tsx');
|
|
120
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bingocode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude": "bin/claude-win.cjs",
|
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
"bingo": "bin/bingo-win.cjs"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"preinstall": "node scripts/preinstall-stop.cjs",
|
|
13
|
-
"postinstall": "node scripts/postinstall-ripgrep.cjs",
|
|
14
12
|
"start": "bun run ./bin/bingo-win.cjs",
|
|
15
13
|
"bingo": "bun run ./bin/bingo-win.cjs",
|
|
16
14
|
"bingocode": "bun run ./bin/bingocode-win.cjs",
|
package/src/entrypoints/cli.tsx
CHANGED
|
@@ -30,15 +30,13 @@ if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
|
|
|
30
30
|
* All imports are dynamic to minimize module evaluation for fast paths.
|
|
31
31
|
* Fast-path for --version has zero imports beyond this file.
|
|
32
32
|
*/
|
|
33
|
+
import { CliMenuManager } from '../manager/CliMenuManager';
|
|
34
|
+
import { render } from 'ink';
|
|
33
35
|
|
|
34
36
|
async function main(): Promise<void> {
|
|
35
37
|
const args = process.argv.slice(2);
|
|
36
38
|
// 兼容demo参数:只渲染CLI新主菜单管理器,不影响原有逻辑
|
|
37
|
-
// CliMenuManager 和 ink 改为动态 import,避免顶层 import 导致模块副作用
|
|
38
|
-
// 抢占 stdin,使新电脑首次启动时 TrustDialog 卡死
|
|
39
39
|
if (args.includes('--cli-menu-demo')) {
|
|
40
|
-
const { CliMenuManager } = await import('../manager/CliMenuManager');
|
|
41
|
-
const { render } = await import('ink');
|
|
42
40
|
render(<CliMenuManager />);
|
|
43
41
|
return;
|
|
44
42
|
}
|
|
@@ -127,8 +127,8 @@ export async function showSetupScreens(root: Root, permissionMode: PermissionMod
|
|
|
127
127
|
// and checks CLAUDE.md external includes. bypassPermissions mode
|
|
128
128
|
// only affects tool execution permissions, not workspace trust.
|
|
129
129
|
// Note: non-interactive sessions (CI/CD with -p) never reach showSetupScreens at all.
|
|
130
|
-
// Skip permission checks in claubbit
|
|
131
|
-
if (!isEnvTruthy(process.env.CLAUBBIT)
|
|
130
|
+
// Skip permission checks in claubbit
|
|
131
|
+
if (!isEnvTruthy(process.env.CLAUBBIT)) {
|
|
132
132
|
// Fast-path: skip TrustDialog import+render when CWD is already trusted.
|
|
133
133
|
// If it returns true, the TrustDialog would auto-resolve regardless of
|
|
134
134
|
// security features, so we can skip the dynamic import and render cycle.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
const DEFAULT_HOST = '127.0.0.1';
|
|
21
21
|
const DEFAULT_PORT = Number(process.env.SERVER_PORT || 3456);
|
|
22
|
-
const HEALTH_TIMEOUT_MS =
|
|
22
|
+
const HEALTH_TIMEOUT_MS = 12000;
|
|
23
23
|
const HEALTH_RETRY_MS = 300;
|
|
24
24
|
|
|
25
25
|
function mkdirp(p: string) { fs.mkdirSync(p, { recursive: true }); }
|
package/src/utils/config.ts
CHANGED
|
@@ -720,21 +720,6 @@ function computeTrustDialogAccepted(): boolean {
|
|
|
720
720
|
return true
|
|
721
721
|
}
|
|
722
722
|
|
|
723
|
-
// Fallback: also check the raw original CWD path directly.
|
|
724
|
-
// This handles the case where trust was saved under CWD (no .git at the time),
|
|
725
|
-
// but now .git exists so getProjectPathForConfig() returns the git root instead.
|
|
726
|
-
// Without this fallback the old trust entry would never be found, causing the
|
|
727
|
-
// trust dialog to reappear after `git init` / `git clone`.
|
|
728
|
-
const normalizedOriginalCwd = normalizePathForConfigKey(
|
|
729
|
-
resolve(getOriginalCwd()),
|
|
730
|
-
)
|
|
731
|
-
if (normalizedOriginalCwd !== projectPath) {
|
|
732
|
-
const cwdConfig = config.projects?.[normalizedOriginalCwd]
|
|
733
|
-
if (cwdConfig?.hasTrustDialogAccepted) {
|
|
734
|
-
return true
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
723
|
// Now check from current working directory and its parents
|
|
739
724
|
// Normalize paths for consistent JSON key lookup
|
|
740
725
|
let currentPath = normalizePathForConfigKey(getCwd())
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* npm postinstall hook: download ripgrep binary for the current platform.
|
|
5
|
-
*
|
|
6
|
-
* The runtime code (src/utils/ripgrep.ts) expects the binary at:
|
|
7
|
-
* src/vendor/ripgrep/{arch}-{platform}/rg[.exe]
|
|
8
|
-
*
|
|
9
|
-
* When installed via npm (non-bundled mode), there is no embedded ripgrep,
|
|
10
|
-
* and if the system `rg` is not on PATH, all Grep/file-search operations
|
|
11
|
-
* will hang. This script ensures a working ripgrep binary is always available.
|
|
12
|
-
*
|
|
13
|
-
* Must be CJS (.cjs) because npm executes lifecycle scripts with node,
|
|
14
|
-
* and package.json has "type": "module".
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const { execSync } = require('child_process');
|
|
18
|
-
const fs = require('fs');
|
|
19
|
-
const path = require('path');
|
|
20
|
-
const os = require('os');
|
|
21
|
-
const https = require('https');
|
|
22
|
-
const http = require('http');
|
|
23
|
-
|
|
24
|
-
// ── Configuration ──────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
const RG_VERSION = '14.1.1';
|
|
27
|
-
|
|
28
|
-
// Map Node.js platform/arch to ripgrep release asset names
|
|
29
|
-
const PLATFORM_MAP = {
|
|
30
|
-
'x64-win32': {
|
|
31
|
-
asset: `ripgrep-${RG_VERSION}-x86_64-pc-windows-msvc.zip`,
|
|
32
|
-
binary: 'rg.exe',
|
|
33
|
-
archiveType: 'zip',
|
|
34
|
-
},
|
|
35
|
-
'x64-linux': {
|
|
36
|
-
asset: `ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl.tar.gz`,
|
|
37
|
-
binary: 'rg',
|
|
38
|
-
archiveType: 'tar.gz',
|
|
39
|
-
},
|
|
40
|
-
'arm64-darwin': {
|
|
41
|
-
asset: `ripgrep-${RG_VERSION}-aarch64-apple-darwin.tar.gz`,
|
|
42
|
-
binary: 'rg',
|
|
43
|
-
archiveType: 'tar.gz',
|
|
44
|
-
},
|
|
45
|
-
'x64-darwin': {
|
|
46
|
-
asset: `ripgrep-${RG_VERSION}-x86_64-apple-darwin.tar.gz`,
|
|
47
|
-
binary: 'rg',
|
|
48
|
-
archiveType: 'tar.gz',
|
|
49
|
-
},
|
|
50
|
-
'arm64-linux': {
|
|
51
|
-
asset: `ripgrep-${RG_VERSION}-aarch64-unknown-linux-gnu.tar.gz`,
|
|
52
|
-
binary: 'rg',
|
|
53
|
-
archiveType: 'tar.gz',
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// ── Path resolution ────────────────────────────────────────────────────────
|
|
58
|
-
// Must match src/utils/ripgrep.ts path resolution:
|
|
59
|
-
// __dirname = src/ (one level up from src/utils/)
|
|
60
|
-
// rgRoot = src/vendor/ripgrep/
|
|
61
|
-
// binary = src/vendor/ripgrep/{arch}-{platform}/rg[.exe]
|
|
62
|
-
|
|
63
|
-
const packageRoot = path.resolve(__dirname, '..');
|
|
64
|
-
const rgRoot = path.join(packageRoot, 'src', 'vendor', 'ripgrep');
|
|
65
|
-
|
|
66
|
-
const platformKey = `${process.arch}-${process.platform}`;
|
|
67
|
-
const platformInfo = PLATFORM_MAP[platformKey];
|
|
68
|
-
|
|
69
|
-
if (!platformInfo) {
|
|
70
|
-
console.log(
|
|
71
|
-
`[bingocode] postinstall: 当前平台 ${platformKey} 不在 ripgrep 预构建列表中,跳过下载。`
|
|
72
|
-
);
|
|
73
|
-
console.log(
|
|
74
|
-
'[bingocode] 请确保系统已安装 ripgrep (rg) 并在 PATH 中可用。'
|
|
75
|
-
);
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const targetDir = path.join(rgRoot, platformKey);
|
|
80
|
-
const targetBinary = path.join(targetDir, platformInfo.binary);
|
|
81
|
-
|
|
82
|
-
// ── Skip if already present ────────────────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
if (fs.existsSync(targetBinary)) {
|
|
85
|
-
console.log(`[bingocode] postinstall: ripgrep 已存在 (${targetBinary}),跳过下载。`);
|
|
86
|
-
process.exit(0);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ── Download helpers ───────────────────────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
const DOWNLOAD_BASE = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}`;
|
|
92
|
-
const downloadUrl = `${DOWNLOAD_BASE}/${platformInfo.asset}`;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Download a URL to a local file, following redirects (GitHub releases redirect
|
|
96
|
-
* to CDN). Returns a Promise that resolves when the file is fully written.
|
|
97
|
-
*/
|
|
98
|
-
function downloadFile(url, destPath, maxRedirects = 5) {
|
|
99
|
-
return new Promise((resolve, reject) => {
|
|
100
|
-
if (maxRedirects <= 0) {
|
|
101
|
-
return reject(new Error('Too many redirects'));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const protocol = url.startsWith('https') ? https : http;
|
|
105
|
-
|
|
106
|
-
protocol
|
|
107
|
-
.get(url, { headers: { 'User-Agent': 'bingocode-postinstall' } }, (res) => {
|
|
108
|
-
// Handle redirects (301, 302, 303, 307, 308)
|
|
109
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
110
|
-
res.resume(); // Drain the response
|
|
111
|
-
return downloadFile(res.headers.location, destPath, maxRedirects - 1)
|
|
112
|
-
.then(resolve)
|
|
113
|
-
.catch(reject);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (res.statusCode !== 200) {
|
|
117
|
-
res.resume();
|
|
118
|
-
return reject(
|
|
119
|
-
new Error(`Download failed: HTTP ${res.statusCode} for ${url}`)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const fileStream = fs.createWriteStream(destPath);
|
|
124
|
-
res.pipe(fileStream);
|
|
125
|
-
fileStream.on('finish', () => {
|
|
126
|
-
fileStream.close(resolve);
|
|
127
|
-
});
|
|
128
|
-
fileStream.on('error', reject);
|
|
129
|
-
})
|
|
130
|
-
.on('error', reject);
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ── Extract helpers ────────────────────────────────────────────────────────
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Extract the rg binary from a .tar.gz archive.
|
|
138
|
-
* The archive structure is: ripgrep-{version}-{target}/rg
|
|
139
|
-
*/
|
|
140
|
-
function extractTarGz(archivePath, binaryName, destDir) {
|
|
141
|
-
// Use tar to list, find the rg binary, then extract just that file
|
|
142
|
-
const stripComponents = 1; // strip the top-level directory
|
|
143
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
execSync(
|
|
147
|
-
`tar -xzf "${archivePath}" --strip-components=${stripComponents} -C "${destDir}" --wildcards "*/${binaryName}"`,
|
|
148
|
-
{ stdio: 'pipe' }
|
|
149
|
-
);
|
|
150
|
-
} catch {
|
|
151
|
-
// Some tar implementations (e.g., macOS BSD tar) don't support --wildcards
|
|
152
|
-
// Fall back to extracting everything and moving the binary
|
|
153
|
-
const tmpExtract = path.join(os.tmpdir(), `rg-extract-${Date.now()}`);
|
|
154
|
-
fs.mkdirSync(tmpExtract, { recursive: true });
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
execSync(`tar -xzf "${archivePath}" -C "${tmpExtract}"`, { stdio: 'pipe' });
|
|
158
|
-
|
|
159
|
-
// Find the rg binary in the extracted contents
|
|
160
|
-
const entries = fs.readdirSync(tmpExtract);
|
|
161
|
-
let found = false;
|
|
162
|
-
for (const entry of entries) {
|
|
163
|
-
const candidate = path.join(tmpExtract, entry, binaryName);
|
|
164
|
-
if (fs.existsSync(candidate)) {
|
|
165
|
-
fs.copyFileSync(candidate, path.join(destDir, binaryName));
|
|
166
|
-
found = true;
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!found) {
|
|
172
|
-
// Maybe the binary is directly in the tmp dir
|
|
173
|
-
const directCandidate = path.join(tmpExtract, binaryName);
|
|
174
|
-
if (fs.existsSync(directCandidate)) {
|
|
175
|
-
fs.copyFileSync(directCandidate, path.join(destDir, binaryName));
|
|
176
|
-
} else {
|
|
177
|
-
throw new Error(`Could not find ${binaryName} in extracted archive`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
} finally {
|
|
181
|
-
// Cleanup temp dir
|
|
182
|
-
try {
|
|
183
|
-
fs.rmSync(tmpExtract, { recursive: true, force: true });
|
|
184
|
-
} catch {
|
|
185
|
-
// Best effort
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Extract the rg.exe binary from a .zip archive (Windows).
|
|
193
|
-
* Uses PowerShell's Expand-Archive on Windows.
|
|
194
|
-
*/
|
|
195
|
-
function extractZip(archivePath, binaryName, destDir) {
|
|
196
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
197
|
-
|
|
198
|
-
const tmpExtract = path.join(os.tmpdir(), `rg-extract-${Date.now()}`);
|
|
199
|
-
fs.mkdirSync(tmpExtract, { recursive: true });
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
if (process.platform === 'win32') {
|
|
203
|
-
// Use PowerShell to extract
|
|
204
|
-
execSync(
|
|
205
|
-
`powershell -NoProfile -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpExtract}' -Force"`,
|
|
206
|
-
{ stdio: 'pipe' }
|
|
207
|
-
);
|
|
208
|
-
} else {
|
|
209
|
-
// Use unzip on non-Windows (unlikely but defensive)
|
|
210
|
-
execSync(`unzip -o "${archivePath}" -d "${tmpExtract}"`, { stdio: 'pipe' });
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Find the binary in extracted contents
|
|
214
|
-
const entries = fs.readdirSync(tmpExtract);
|
|
215
|
-
let found = false;
|
|
216
|
-
for (const entry of entries) {
|
|
217
|
-
const entryPath = path.join(tmpExtract, entry);
|
|
218
|
-
const stat = fs.statSync(entryPath);
|
|
219
|
-
|
|
220
|
-
if (stat.isDirectory()) {
|
|
221
|
-
const candidate = path.join(entryPath, binaryName);
|
|
222
|
-
if (fs.existsSync(candidate)) {
|
|
223
|
-
fs.copyFileSync(candidate, path.join(destDir, binaryName));
|
|
224
|
-
found = true;
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
} else if (entry === binaryName) {
|
|
228
|
-
fs.copyFileSync(entryPath, path.join(destDir, binaryName));
|
|
229
|
-
found = true;
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!found) {
|
|
235
|
-
throw new Error(`Could not find ${binaryName} in extracted zip archive`);
|
|
236
|
-
}
|
|
237
|
-
} finally {
|
|
238
|
-
try {
|
|
239
|
-
fs.rmSync(tmpExtract, { recursive: true, force: true });
|
|
240
|
-
} catch {
|
|
241
|
-
// Best effort
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// ── Main ───────────────────────────────────────────────────────────────────
|
|
247
|
-
|
|
248
|
-
async function main() {
|
|
249
|
-
console.log(`[bingocode] postinstall: 正在下载 ripgrep v${RG_VERSION} (${platformKey})...`);
|
|
250
|
-
console.log(`[bingocode] URL: ${downloadUrl}`);
|
|
251
|
-
|
|
252
|
-
const tmpArchive = path.join(
|
|
253
|
-
os.tmpdir(),
|
|
254
|
-
`ripgrep-${RG_VERSION}-${Date.now()}${platformInfo.archiveType === 'zip' ? '.zip' : '.tar.gz'}`
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
// 1. Download
|
|
259
|
-
await downloadFile(downloadUrl, tmpArchive);
|
|
260
|
-
console.log('[bingocode] postinstall: 下载完成,正在解压...');
|
|
261
|
-
|
|
262
|
-
// 2. Extract
|
|
263
|
-
if (platformInfo.archiveType === 'tar.gz') {
|
|
264
|
-
extractTarGz(tmpArchive, platformInfo.binary, targetDir);
|
|
265
|
-
} else {
|
|
266
|
-
extractZip(tmpArchive, platformInfo.binary, targetDir);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// 3. Set executable permission on non-Windows
|
|
270
|
-
if (process.platform !== 'win32') {
|
|
271
|
-
fs.chmodSync(targetBinary, 0o755);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// 4. Verify
|
|
275
|
-
if (fs.existsSync(targetBinary)) {
|
|
276
|
-
const stat = fs.statSync(targetBinary);
|
|
277
|
-
console.log(
|
|
278
|
-
`[bingocode] postinstall: ripgrep 安装成功!(${targetBinary}, ${(stat.size / 1024 / 1024).toFixed(1)} MB)`
|
|
279
|
-
);
|
|
280
|
-
} else {
|
|
281
|
-
throw new Error('Binary not found after extraction');
|
|
282
|
-
}
|
|
283
|
-
} catch (err) {
|
|
284
|
-
console.warn(`[bingocode] postinstall: ripgrep 下载/安装失败: ${err.message}`);
|
|
285
|
-
console.warn('[bingocode] 请手动安装 ripgrep: https://github.com/BurntSushi/ripgrep#installation');
|
|
286
|
-
console.warn('[bingocode] 或确保系统 PATH 中有 rg 命令可用。');
|
|
287
|
-
// Don't fail the install — ripgrep is a best-effort enhancement.
|
|
288
|
-
// The runtime will fall back to system rg if available.
|
|
289
|
-
} finally {
|
|
290
|
-
// Cleanup temp archive
|
|
291
|
-
try {
|
|
292
|
-
fs.rmSync(tmpArchive, { force: true });
|
|
293
|
-
} catch {
|
|
294
|
-
// Best effort
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
main();
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* npm preinstall hook: gracefully stop running bingo/bingocode processes
|
|
5
|
-
* so that npm can overwrite files without EBUSY/EPERM on Windows.
|
|
6
|
-
*
|
|
7
|
-
* On Windows, running processes hold mandatory file locks on their loaded .js
|
|
8
|
-
* files and dependencies. When `npm install -g bingocode` tries to overwrite
|
|
9
|
-
* these files, it fails with EBUSY or EPERM. This script discovers all running
|
|
10
|
-
* bingo/bingocode processes via their PID files and stops them before npm
|
|
11
|
-
* writes any files.
|
|
12
|
-
*
|
|
13
|
-
* PID file locations (matching the runtime code):
|
|
14
|
-
* - Singleton server: ~/.claude-cli/runtime/server.lock.json { pid, port, ... }
|
|
15
|
-
* - Active sessions: ~/.claude/sessions/<pid>.json { pid, sessionId, ... }
|
|
16
|
-
*
|
|
17
|
-
* Must be CJS (.cjs) because npm executes lifecycle scripts with node,
|
|
18
|
-
* and package.json has "type": "module".
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const { spawnSync } = require('child_process');
|
|
22
|
-
const fs = require('fs');
|
|
23
|
-
const path = require('path');
|
|
24
|
-
const os = require('os');
|
|
25
|
-
|
|
26
|
-
// ── Paths (mirroring the runtime code) ──────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
29
|
-
const runtimeDir = path.join(os.homedir(), '.claude-cli', 'runtime');
|
|
30
|
-
const serverLockPath = path.join(runtimeDir, 'server.lock.json');
|
|
31
|
-
const leasesDir = path.join(runtimeDir, 'leases');
|
|
32
|
-
const sessionsDir = path.join(configDir, 'sessions');
|
|
33
|
-
|
|
34
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
function isPidAlive(pid) {
|
|
37
|
-
try {
|
|
38
|
-
process.kill(pid, 0);
|
|
39
|
-
return true;
|
|
40
|
-
} catch {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function killPid(pid) {
|
|
46
|
-
try {
|
|
47
|
-
if (process.platform === 'win32') {
|
|
48
|
-
// /T = kill the entire process tree; /F = force
|
|
49
|
-
spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], {
|
|
50
|
-
stdio: 'ignore',
|
|
51
|
-
});
|
|
52
|
-
} else {
|
|
53
|
-
process.kill(pid, 'SIGTERM');
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
56
|
-
// Process may have already exited
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function readJsonSafe(filePath) {
|
|
61
|
-
try {
|
|
62
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function sleepMs(ms) {
|
|
69
|
-
// Cross-platform synchronous sleep via spawnSync
|
|
70
|
-
if (process.platform === 'win32') {
|
|
71
|
-
// "ping -n 2 127.0.0.1" sleeps ~1s (more reliable than timeout in non-interactive)
|
|
72
|
-
spawnSync('ping', ['-n', '2', '127.0.0.1'], { stdio: 'ignore' });
|
|
73
|
-
} else {
|
|
74
|
-
spawnSync('sleep', [String(ms / 1000)], { stdio: 'ignore' });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ── Collect PIDs ────────────────────────────────────────────────────────────
|
|
79
|
-
|
|
80
|
-
const pidsToKill = new Set();
|
|
81
|
-
|
|
82
|
-
// 1. Singleton server PID from server.lock.json
|
|
83
|
-
const serverLock = readJsonSafe(serverLockPath);
|
|
84
|
-
if (serverLock && serverLock.pid && isPidAlive(serverLock.pid)) {
|
|
85
|
-
pidsToKill.add(serverLock.pid);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 2. Active session PIDs from ~/.claude/sessions/<pid>.json
|
|
89
|
-
try {
|
|
90
|
-
const files = fs.readdirSync(sessionsDir);
|
|
91
|
-
for (const f of files) {
|
|
92
|
-
// Strict filename guard: only "<digits>.json" files are PID files
|
|
93
|
-
if (!/^\d+\.json$/.test(f)) continue;
|
|
94
|
-
const data = readJsonSafe(path.join(sessionsDir, f));
|
|
95
|
-
if (data && data.pid && isPidAlive(data.pid)) {
|
|
96
|
-
pidsToKill.add(data.pid);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
} catch {
|
|
100
|
-
// sessionsDir may not exist yet — that's fine
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ── Nothing to do ───────────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
if (pidsToKill.size === 0) {
|
|
106
|
-
// No running processes, npm can proceed safely
|
|
107
|
-
process.exit(0);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ── Kill processes ──────────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
console.log(
|
|
113
|
-
`[bingocode] 检测到 ${pidsToKill.size} 个运行中的进程,正在停止以便安装...`
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
for (const pid of pidsToKill) {
|
|
117
|
-
console.log(` 停止 PID ${pid}`);
|
|
118
|
-
killPid(pid);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ── Wait for processes to exit (max 5 seconds) ─────────────────────────────
|
|
122
|
-
|
|
123
|
-
const MAX_WAIT_MS = 5000;
|
|
124
|
-
const deadline = Date.now() + MAX_WAIT_MS;
|
|
125
|
-
|
|
126
|
-
while (Date.now() < deadline) {
|
|
127
|
-
const alive = [...pidsToKill].filter(isPidAlive);
|
|
128
|
-
if (alive.length === 0) break;
|
|
129
|
-
sleepMs(500);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check if any processes are still alive after timeout
|
|
133
|
-
const stillAlive = [...pidsToKill].filter(isPidAlive);
|
|
134
|
-
if (stillAlive.length > 0) {
|
|
135
|
-
console.warn(
|
|
136
|
-
`[bingocode] 警告:${stillAlive.length} 个进程未能在 ${MAX_WAIT_MS / 1000} 秒内停止 (PIDs: ${stillAlive.join(', ')})`
|
|
137
|
-
);
|
|
138
|
-
console.warn(
|
|
139
|
-
'[bingocode] 安装可能仍会因文件锁定而失败,请手动关闭这些进程后重试'
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ── Clean up stale lock/lease files ─────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
fs.rmSync(serverLockPath, { force: true });
|
|
147
|
-
} catch {
|
|
148
|
-
// Ignore — file may not exist or may already be cleaned up
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const leaseFiles = fs.readdirSync(leasesDir);
|
|
153
|
-
for (const f of leaseFiles) {
|
|
154
|
-
try {
|
|
155
|
-
fs.rmSync(path.join(leasesDir, f), { force: true });
|
|
156
|
-
} catch {
|
|
157
|
-
// Best-effort cleanup
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} catch {
|
|
161
|
-
// leasesDir may not exist
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
console.log('[bingocode] 进程已停止,继续安装...');
|