bingocode 1.0.31 → 1.0.33

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.
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "env": {
3
3
  "ANTHROPIC_AUTH_TOKEN": "pending-provider-setup",
4
- "ANTHROPIC_BASE_URL": "http://127.0.0.1:3456"
4
+ "ANTHROPIC_BASE_URL": "http://127.0.0.1:3456",
5
+ "BINGO_SKIP_TRUST_DIALOG": "1"
5
6
  }
6
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -9,6 +9,8 @@
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",
12
14
  "start": "bun run ./bin/bingo-win.cjs",
13
15
  "bingo": "bun run ./bin/bingo-win.cjs",
14
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();
@@ -0,0 +1,164 @@
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] 进程已停止,继续安装...');
@@ -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.