bingocode 1.0.32 → 1.0.34
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 +41 -15
- package/bin/bingocode-win.cjs +41 -13
- package/bin/claude-win.cjs +38 -12
- package/bingocode-1.0.33.tgz +0 -0
- package/config/bingo-defaults/settings.json +2 -1
- package/package.json +2 -1
- package/scripts/postinstall-ripgrep.cjs +299 -0
- package/src/interactiveHelpers.tsx +2 -2
package/bin/bingo-win.cjs
CHANGED
|
@@ -7,31 +7,57 @@ const fs = require('fs');
|
|
|
7
7
|
|
|
8
8
|
process.env.NoDefaultCurrentDirectoryInExePath = '1';
|
|
9
9
|
|
|
10
|
-
// ──
|
|
11
|
-
//
|
|
10
|
+
// ── 部署 / 增量合并 bingo 默认配置到 ~/.claude/bingo/ ──
|
|
11
|
+
// 首次安装:完整复制 settings.json(含占位符 ANTHROPIC_AUTH_TOKEN),
|
|
12
12
|
// 这样 isOfficialMode() 返回 false,子进程不会走 OAuth 流程。
|
|
13
|
-
//
|
|
13
|
+
// 升级安装:增量合并——只补充默认配置中有但用户配置中缺失的 key,
|
|
14
|
+
// 保留用户已有的自定义值(如修改过的 ANTHROPIC_BASE_URL 等)。
|
|
14
15
|
(function deployBingoDefaults() {
|
|
15
16
|
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
16
17
|
const bingoDir = path.join(configDir, 'bingo');
|
|
17
18
|
const targetSettings = path.join(bingoDir, 'settings.json');
|
|
19
|
+
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
20
|
+
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
if (!fs.existsSync(targetSettings)) {
|
|
21
|
-
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
22
|
-
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
22
|
+
if (!fs.existsSync(srcSettings)) return;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
try {
|
|
25
|
+
if (!fs.existsSync(bingoDir)) {
|
|
26
|
+
fs.mkdirSync(bingoDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(targetSettings)) {
|
|
30
|
+
// 首次安装:直接复制
|
|
31
|
+
fs.copyFileSync(srcSettings, targetSettings);
|
|
32
|
+
console.log('[bingo] 首次启动:已部署默认配置到', targetSettings);
|
|
33
|
+
} else {
|
|
34
|
+
// 升级安装:增量合并缺失的 key
|
|
35
|
+
const defaults = JSON.parse(fs.readFileSync(srcSettings, 'utf-8'));
|
|
36
|
+
const current = JSON.parse(fs.readFileSync(targetSettings, 'utf-8'));
|
|
37
|
+
let changed = false;
|
|
38
|
+
|
|
39
|
+
for (const section of Object.keys(defaults)) {
|
|
40
|
+
if (typeof defaults[section] !== 'object' || defaults[section] === null) continue;
|
|
41
|
+
if (!current[section] || typeof current[section] !== 'object') {
|
|
42
|
+
current[section] = defaults[section];
|
|
43
|
+
changed = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
for (const key of Object.keys(defaults[section])) {
|
|
47
|
+
if (!(key in current[section])) {
|
|
48
|
+
current[section][key] = defaults[section][key];
|
|
49
|
+
changed = true;
|
|
50
|
+
}
|
|
28
51
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (changed) {
|
|
55
|
+
fs.writeFileSync(targetSettings, JSON.stringify(current, null, 2) + '\n');
|
|
56
|
+
console.log('[bingo] 升级:已合并新增默认配置到', targetSettings);
|
|
33
57
|
}
|
|
34
58
|
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.warn('[bingo] 部署默认配置失败:', err.message);
|
|
35
61
|
}
|
|
36
62
|
})();
|
|
37
63
|
|
package/bin/bingocode-win.cjs
CHANGED
|
@@ -7,29 +7,57 @@ const fs = require('fs');
|
|
|
7
7
|
|
|
8
8
|
process.env.NoDefaultCurrentDirectoryInExePath = '1';
|
|
9
9
|
|
|
10
|
-
// ──
|
|
11
|
-
//
|
|
10
|
+
// ── 部署 / 增量合并 bingo 默认配置到 ~/.claude/bingo/ ──
|
|
11
|
+
// 首次安装:完整复制 settings.json(含占位符 ANTHROPIC_AUTH_TOKEN),
|
|
12
12
|
// 这样 isAnthropicAuthEnabled() 返回 false,跳过 OAuth 流程。
|
|
13
|
+
// 升级安装:增量合并——只补充默认配置中有但用户配置中缺失的 key,
|
|
14
|
+
// 保留用户已有的自定义值(如修改过的 ANTHROPIC_BASE_URL 等)。
|
|
13
15
|
(function deployBingoDefaults() {
|
|
14
16
|
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
15
17
|
const bingoDir = path.join(configDir, 'bingo');
|
|
16
18
|
const targetSettings = path.join(bingoDir, 'settings.json');
|
|
19
|
+
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
20
|
+
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
17
21
|
|
|
18
|
-
if (!fs.existsSync(
|
|
19
|
-
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
20
|
-
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
22
|
+
if (!fs.existsSync(srcSettings)) return;
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
try {
|
|
25
|
+
if (!fs.existsSync(bingoDir)) {
|
|
26
|
+
fs.mkdirSync(bingoDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(targetSettings)) {
|
|
30
|
+
// 首次安装:直接复制
|
|
31
|
+
fs.copyFileSync(srcSettings, targetSettings);
|
|
32
|
+
console.log('[bingocode] 首次启动:已部署默认配置到', targetSettings);
|
|
33
|
+
} else {
|
|
34
|
+
// 升级安装:增量合并缺失的 key
|
|
35
|
+
const defaults = JSON.parse(fs.readFileSync(srcSettings, 'utf-8'));
|
|
36
|
+
const current = JSON.parse(fs.readFileSync(targetSettings, 'utf-8'));
|
|
37
|
+
let changed = false;
|
|
38
|
+
|
|
39
|
+
for (const section of Object.keys(defaults)) {
|
|
40
|
+
if (typeof defaults[section] !== 'object' || defaults[section] === null) continue;
|
|
41
|
+
if (!current[section] || typeof current[section] !== 'object') {
|
|
42
|
+
current[section] = defaults[section];
|
|
43
|
+
changed = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
for (const key of Object.keys(defaults[section])) {
|
|
47
|
+
if (!(key in current[section])) {
|
|
48
|
+
current[section][key] = defaults[section][key];
|
|
49
|
+
changed = true;
|
|
50
|
+
}
|
|
26
51
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (changed) {
|
|
55
|
+
fs.writeFileSync(targetSettings, JSON.stringify(current, null, 2) + '\n');
|
|
56
|
+
console.log('[bingocode] 升级:已合并新增默认配置到', targetSettings);
|
|
31
57
|
}
|
|
32
58
|
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.warn('[bingocode] 部署默认配置失败:', err.message);
|
|
33
61
|
}
|
|
34
62
|
})();
|
|
35
63
|
|
package/bin/claude-win.cjs
CHANGED
|
@@ -8,27 +8,53 @@ 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');
|
|
16
18
|
|
|
17
|
-
if (!fs.existsSync(
|
|
18
|
-
const defaultsDir = path.join(__dirname, '..', 'config', 'bingo-defaults');
|
|
19
|
-
const srcSettings = path.join(defaultsDir, 'settings.json');
|
|
19
|
+
if (!fs.existsSync(srcSettings)) return;
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(bingoDir)) {
|
|
23
|
+
fs.mkdirSync(bingoDir, { recursive: true });
|
|
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;
|
|
42
|
+
}
|
|
43
|
+
for (const key of Object.keys(defaults[section])) {
|
|
44
|
+
if (!(key in current[section])) {
|
|
45
|
+
current[section][key] = defaults[section][key];
|
|
46
|
+
changed = true;
|
|
47
|
+
}
|
|
25
48
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (changed) {
|
|
52
|
+
fs.writeFileSync(targetSettings, JSON.stringify(current, null, 2) + '\n');
|
|
53
|
+
console.log('[claude] 升级:已合并新增默认配置到', targetSettings);
|
|
30
54
|
}
|
|
31
55
|
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.warn('[claude] 部署默认配置失败:', err.message);
|
|
32
58
|
}
|
|
33
59
|
})();
|
|
34
60
|
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bingocode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude": "bin/claude-win.cjs",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"preinstall": "node scripts/preinstall-stop.cjs",
|
|
13
|
+
"postinstall": "node scripts/postinstall-ripgrep.cjs",
|
|
13
14
|
"start": "bun run ./bin/bingo-win.cjs",
|
|
14
15
|
"bingo": "bun run ./bin/bingo-win.cjs",
|
|
15
16
|
"bingocode": "bun run ./bin/bingocode-win.cjs",
|
|
@@ -0,0 +1,299 @@
|
|
|
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();
|
|
@@ -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 or when BINGO_SKIP_TRUST_DIALOG is set
|
|
131
|
+
if (!isEnvTruthy(process.env.CLAUBBIT) && !isEnvTruthy(process.env.BINGO_SKIP_TRUST_DIALOG)) {
|
|
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.
|