@saluzi/saluzi-edu 0.1.15

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.
Binary file
package/package.json ADDED
@@ -0,0 +1,229 @@
1
+ {
2
+ "name": "@saluzi/saluzi-edu",
3
+ "version": "0.1.15",
4
+ "description": "Saluzi CLI — interactive AI coding assistant in the terminal",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "saluzi",
9
+ "cli",
10
+ "ai",
11
+ "coding-assistant",
12
+ "terminal",
13
+ "repl"
14
+ ],
15
+ "engines": {
16
+ "bun": ">=1.3.0",
17
+ "node": ">=18"
18
+ },
19
+ "bin": {
20
+ "slz-edu": "dist/cli-node.js",
21
+ "slz-edu-bun": "dist/cli-bun.js",
22
+ "saluzi-edu": "dist/cli-node.js"
23
+ },
24
+ "workspaces": [
25
+ "packages/*",
26
+ "packages/@ant/*",
27
+ "packages/@anthropic-ai/*"
28
+ ],
29
+ "files": [
30
+ "dist",
31
+ "scripts/postinstall.cjs",
32
+ "scripts/run-parallel.mjs",
33
+ "scripts/setup-chrome-mcp.mjs"
34
+ ],
35
+ "scripts": {
36
+ "build": "bun run build.ts",
37
+ "build:vite": "vite build && node scripts/post-build.mjs",
38
+ "build:vite:only": "vite build",
39
+ "build:bun": "bun run build.ts",
40
+ "dev": "bun run scripts/dev.ts",
41
+ "dev:inspect": "bun run scripts/dev-debug.ts",
42
+ "prepublishOnly": "bun run build:vite",
43
+ "lint": "biome lint .",
44
+ "lint:fix": "biome lint --fix .",
45
+ "format": "biome format --write .",
46
+ "check": "biome check .",
47
+ "check:fix": "biome check --fix .",
48
+ "prepare": "husky",
49
+ "test": "bun test",
50
+ "test:production": "bun run scripts/production-test.ts",
51
+ "test:production:offline": "bun run scripts/production-test.ts --offline",
52
+ "test:production:verbose": "bun run scripts/production-test.ts --verbose",
53
+ "test:production:bun": "bun run scripts/production-test.ts --bun",
54
+ "check:bundle": "bun run scripts/check-bundle-integrity.ts",
55
+ "check:unused": "knip-bun",
56
+ "health": "bun run scripts/health-check.ts",
57
+ "postinstall": "node scripts/run-parallel.mjs scripts/postinstall.cjs scripts/setup-chrome-mcp.mjs",
58
+ "docs:dev": "npx mintlify dev",
59
+ "typecheck": "tsc --noEmit",
60
+ "precheck": "bun run typecheck && bun run check:fix && bun test",
61
+ "rcs": "bun run scripts/rcs.ts"
62
+ },
63
+ "dependencies": {
64
+ "@agentclientprotocol/sdk": "^0.19.0",
65
+ "@claude-code-best/mcp-chrome-bridge": "^3.0.1",
66
+ "@types/figlet": "^1.7.0",
67
+ "figlet": "^1.11.0",
68
+ "highlight.js": "^11.11.1",
69
+ "ws": "^8.20.0",
70
+ "@saluzi/sa-agent-tools": ">=1.0.0",
71
+ "@saluzi/sa-builtin-tools": ">=1.0.0"
72
+ },
73
+ "devDependencies": {
74
+ "@alcalzone/ansi-tokenize": "^0.3.0",
75
+ "@ant/claude-for-chrome-mcp": "workspace:*",
76
+ "@ant/computer-use-input": "workspace:*",
77
+ "@ant/computer-use-mcp": "workspace:*",
78
+ "@ant/computer-use-swift": "workspace:*",
79
+ "@ant/model-provider": "workspace:*",
80
+ "@anthropic-ai/bedrock-sdk": "^0.29.0",
81
+ "@anthropic-ai/claude-agent-sdk": "^0.2.114",
82
+ "@anthropic-ai/foundry-sdk": "^0.2.3",
83
+ "@anthropic-ai/mcpb": "^2.1.2",
84
+ "@anthropic-ai/sandbox-runtime": "^0.0.44",
85
+ "@anthropic-ai/sdk": "^0.81.0",
86
+ "@anthropic-ai/vertex-sdk": "^0.16.0",
87
+ "@anthropic/ink": "workspace:*",
88
+ "@aws-sdk/client-bedrock": "^3.1037.0",
89
+ "@aws-sdk/client-bedrock-runtime": "^3.1037.0",
90
+ "@aws-sdk/client-sts": "^3.1037.0",
91
+ "@aws-sdk/credential-provider-node": "^3.972.36",
92
+ "@aws-sdk/credential-providers": "^3.1037.0",
93
+ "@azure/identity": "^4.13.1",
94
+ "@biomejs/biome": "^2.4.12",
95
+ "@saluzi/sa-agent-tools": "workspace:*",
96
+ "@saluzi/sa-builtin-tools": "workspace:*",
97
+ "@claude-code-best/mcp-client": "workspace:*",
98
+ "@claude-code-best/weixin": "workspace:*",
99
+ "@commander-js/extra-typings": "^14.0.0",
100
+ "@growthbook/growthbook": "^1.6.5",
101
+ "@langfuse/otel": "^5.1.0",
102
+ "@langfuse/tracing": "^5.1.0",
103
+ "@modelcontextprotocol/sdk": "^1.29.0",
104
+ "@opentelemetry/api": "^1.9.1",
105
+ "@opentelemetry/api-logs": "^0.215.0",
106
+ "@opentelemetry/core": "^2.7.0",
107
+ "@opentelemetry/exporter-logs-otlp-grpc": "^0.215.0",
108
+ "@opentelemetry/exporter-logs-otlp-http": "^0.215.0",
109
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.215.0",
110
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0.215.0",
111
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.215.0",
112
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.215.0",
113
+ "@opentelemetry/exporter-prometheus": "^0.215.0",
114
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.215.0",
115
+ "@opentelemetry/exporter-trace-otlp-http": "^0.215.0",
116
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
117
+ "@opentelemetry/resources": "^2.7.0",
118
+ "@opentelemetry/sdk-logs": "^0.215.0",
119
+ "@opentelemetry/sdk-metrics": "^2.7.0",
120
+ "@opentelemetry/sdk-trace-base": "^2.7.0",
121
+ "@opentelemetry/semantic-conventions": "^1.40.0",
122
+ "@sentry/node": "^10.49.0",
123
+ "@smithy/core": "^3.23.15",
124
+ "@smithy/node-http-handler": "^4.5.3",
125
+ "@types/bun": "^1.3.12",
126
+ "@types/cacache": "^20.0.1",
127
+ "@types/he": "^1.2.3",
128
+ "@types/lodash-es": "^4.17.12",
129
+ "@types/node": "^25.6.0",
130
+ "@types/picomatch": "^4.0.3",
131
+ "@types/plist": "^3.0.5",
132
+ "@types/proper-lockfile": "^4.1.4",
133
+ "@types/qrcode": "^1.5.6",
134
+ "@types/react": "^19.2.14",
135
+ "@types/react-reconciler": "^0.33.0",
136
+ "@types/semver": "^7.7.1",
137
+ "@types/sharp": "^0.32.0",
138
+ "@types/shell-quote": "^1.7.5",
139
+ "@types/stack-utils": "^2.0.3",
140
+ "@types/turndown": "^5.0.6",
141
+ "@types/ws": "^8.18.1",
142
+ "ajv": "^8.18.0",
143
+ "asciichart": "^1.5.25",
144
+ "audio-capture-napi": "workspace:*",
145
+ "auto-bind": "^5.0.1",
146
+ "axios": "^1.15.2",
147
+ "bidi-js": "^1.0.3",
148
+ "cacache": "^20.0.4",
149
+ "chalk": "^5.6.2",
150
+ "chokidar": "^5.0.0",
151
+ "cli-boxes": "^4.0.1",
152
+ "cli-highlight": "^2.1.11",
153
+ "code-excerpt": "^4.0.0",
154
+ "color-diff-napi": "workspace:*",
155
+ "diff": "^8.0.4",
156
+ "emoji-regex": "^10.6.0",
157
+ "env-paths": "^4.0.0",
158
+ "execa": "^9.6.1",
159
+ "fflate": "^0.8.2",
160
+ "figures": "^6.1.0",
161
+ "fuse.js": "^7.3.0",
162
+ "get-east-asian-width": "^1.5.0",
163
+ "google-auth-library": "^10.6.2",
164
+ "he": "^1.2.0",
165
+ "https-proxy-agent": "^8.0.0",
166
+ "husky": "^9.1.7",
167
+ "ignore": "^7.0.5",
168
+ "image-processor-napi": "workspace:*",
169
+ "indent-string": "^5.0.0",
170
+ "jsonc-parser": "^3.3.1",
171
+ "knip": "^6.4.1",
172
+ "lint-staged": "^16.4.0",
173
+ "lodash-es": "^4.18.1",
174
+ "lru-cache": "^11.3.5",
175
+ "marked": "^17.0.6",
176
+ "modifiers-napi": "workspace:*",
177
+ "openai": "^6.34.0",
178
+ "p-map": "^7.0.4",
179
+ "picomatch": "^4.0.4",
180
+ "plist": "^3.1.0",
181
+ "proper-lockfile": "^4.1.2",
182
+ "qrcode": "^1.5.4",
183
+ "react": "^19.2.5",
184
+ "react-compiler-runtime": "^1.0.0",
185
+ "react-reconciler": "^0.33.0",
186
+ "rollup": "^4.60.2",
187
+ "semver": "^7.7.4",
188
+ "sharp": "^0.34.5",
189
+ "shell-quote": "^1.8.3",
190
+ "signal-exit": "^4.1.0",
191
+ "stack-utils": "^2.0.6",
192
+ "strip-ansi": "^7.2.0",
193
+ "supports-hyperlinks": "^4.4.0",
194
+ "tree-kill": "^1.2.2",
195
+ "turndown": "^7.2.4",
196
+ "type-fest": "^5.6.0",
197
+ "typescript": "^6.0.3",
198
+ "undici": "^7.25.0",
199
+ "url-handler-napi": "workspace:*",
200
+ "usehooks-ts": "^3.1.1",
201
+ "vite": "^8.0.8",
202
+ "vscode-jsonrpc": "^8.2.1",
203
+ "vscode-languageserver-protocol": "^3.17.5",
204
+ "vscode-languageserver-types": "^3.17.5",
205
+ "wrap-ansi": "^10.0.0",
206
+ "xss": "^1.0.15",
207
+ "yaml": "^2.8.3",
208
+ "zod": "^4.3.6"
209
+ },
210
+ "optionalDependencies": {
211
+ "doubaoime-asr": "^0.1.0"
212
+ },
213
+ "overrides": {
214
+ "@inquirer/prompts": "8.4.2",
215
+ "@xmldom/xmldom": "0.8.13",
216
+ "follow-redirects": "1.16.0",
217
+ "hono": "4.12.15",
218
+ "postcss": "8.5.10",
219
+ "uuid": "14.0.0"
220
+ },
221
+ "lint-staged": {
222
+ "*.{ts,tsx,js,mjs,jsx}": [
223
+ "biome check --fix --no-errors-on-unmatched"
224
+ ],
225
+ "*.{json,jsonc}": [
226
+ "biome format --write --no-errors-on-unmatched"
227
+ ]
228
+ }
229
+ }
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall script — runs automatically after `bun install` or `npm install`.
4
+ *
5
+ * Downloads ripgrep binary (idempotent, skips if exists).
6
+ * Works in dev mode (src/ exists), published mode (dist/ exists), with bun or node.
7
+ *
8
+ * Usage:
9
+ * node scripts/postinstall.js
10
+ * node scripts/postinstall.js --force
11
+ * bun run scripts/postinstall.js
12
+ */
13
+
14
+ const {
15
+ existsSync,
16
+ mkdirSync,
17
+ readFileSync,
18
+ renameSync,
19
+ rmSync,
20
+ statSync,
21
+ writeFileSync,
22
+ chmodSync,
23
+ } = require('fs')
24
+ const { spawnSync } = require('child_process')
25
+ const { setDefaultResultOrder } = require('node:dns')
26
+ const path = require('path')
27
+ const os = require('os')
28
+
29
+ // Prefer IPv4 first — Bun on Windows sometimes fails GitHub over broken IPv6 paths.
30
+ try {
31
+ setDefaultResultOrder('ipv4first')
32
+ } catch {
33
+ /* ignore */
34
+ }
35
+
36
+ // --- Config ---
37
+
38
+ const RG_VERSION = '15.0.1'
39
+ const DEFAULT_RELEASE_BASE = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
40
+ const MIRROR_RELEASE_BASE = `https://ghproxy.net/https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
41
+ const RELEASE_BASE = (
42
+ process.env.RIPGREP_DOWNLOAD_BASE ?? DEFAULT_RELEASE_BASE
43
+ ).replace(/\/$/, '')
44
+
45
+ const scriptDir = path.dirname(__filename)
46
+ const projectRoot = path.resolve(scriptDir, '..')
47
+
48
+ // --- Platform mapping ---
49
+
50
+ function getPlatformMapping() {
51
+ const arch = process.arch
52
+ const platform = process.platform
53
+
54
+ if (platform === 'darwin') {
55
+ if (arch === 'arm64')
56
+ return { target: 'aarch64-apple-darwin', ext: 'tar.gz' }
57
+ if (arch === 'x64') return { target: 'x86_64-apple-darwin', ext: 'tar.gz' }
58
+ throw new Error(`Unsupported macOS arch: ${arch}`)
59
+ }
60
+
61
+ if (platform === 'win32') {
62
+ if (arch === 'x64') return { target: 'x86_64-pc-windows-msvc', ext: 'zip' }
63
+ if (arch === 'arm64')
64
+ return { target: 'aarch64-pc-windows-msvc', ext: 'zip' }
65
+ throw new Error(`Unsupported Windows arch: ${arch}`)
66
+ }
67
+
68
+ if (platform === 'linux') {
69
+ const isMusl = detectMusl()
70
+ if (arch === 'x64') {
71
+ return { target: 'x86_64-unknown-linux-musl', ext: 'tar.gz' }
72
+ }
73
+ if (arch === 'arm64') {
74
+ return isMusl
75
+ ? { target: 'aarch64-unknown-linux-musl', ext: 'tar.gz' }
76
+ : { target: 'aarch64-unknown-linux-gnu', ext: 'tar.gz' }
77
+ }
78
+ throw new Error(`Unsupported Linux arch: ${arch}`)
79
+ }
80
+
81
+ throw new Error(`Unsupported platform: ${platform}`)
82
+ }
83
+
84
+ function detectMusl() {
85
+ const muslArch = process.arch === 'x64' ? 'x86_64' : 'aarch64'
86
+ try {
87
+ statSync(`/lib/libc.musl-${muslArch}.so.1`)
88
+ return true
89
+ } catch {
90
+ return false
91
+ }
92
+ }
93
+
94
+ // --- Paths ---
95
+
96
+ function getVendorDir() {
97
+ if (existsSync(path.join(projectRoot, 'src'))) {
98
+ return path.resolve(projectRoot, 'src', 'utils', 'vendor', 'ripgrep')
99
+ }
100
+ return path.resolve(projectRoot, 'dist', 'vendor', 'ripgrep')
101
+ }
102
+
103
+ function getBinaryPath() {
104
+ const dir = getVendorDir()
105
+ const subdir = `${process.arch}-${process.platform}`
106
+ const binary = process.platform === 'win32' ? 'rg.exe' : 'rg'
107
+ return path.resolve(dir, subdir, binary)
108
+ }
109
+
110
+ // --- Download helpers ---
111
+
112
+ function proxyEnvSet() {
113
+ const v = s => (s ?? '').trim()
114
+ return !!(
115
+ v(process.env.HTTPS_PROXY) ||
116
+ v(process.env.HTTP_PROXY) ||
117
+ v(process.env.ALL_PROXY) ||
118
+ v(process.env.https_proxy) ||
119
+ v(process.env.http_proxy)
120
+ )
121
+ }
122
+
123
+ function tryPowerShellDownload(url, dest) {
124
+ const u = url.replace(/'/g, "''")
125
+ const d = dest.replace(/'/g, "''")
126
+ const cmd = `Invoke-WebRequest -Uri '${u}' -OutFile '${d}' -UseBasicParsing`
127
+ const result = spawnSync(
128
+ 'powershell.exe',
129
+ [
130
+ '-NoProfile',
131
+ '-NonInteractive',
132
+ '-ExecutionPolicy',
133
+ 'Bypass',
134
+ '-Command',
135
+ cmd,
136
+ ],
137
+ { stdio: 'pipe', windowsHide: true },
138
+ )
139
+ return result.status === 0 && existsSync(dest) && statSync(dest).size > 0
140
+ }
141
+
142
+ function tryCurlDownload(url, dest) {
143
+ const curl = process.platform === 'win32' ? 'curl.exe' : 'curl'
144
+ const result = spawnSync(curl, ['-fsSL', '-L', '--fail', '-o', dest, url], {
145
+ stdio: 'pipe',
146
+ windowsHide: true,
147
+ })
148
+ return result.status === 0 && existsSync(dest) && statSync(dest).size > 0
149
+ }
150
+
151
+ async function fetchRelease(url) {
152
+ if (proxyEnvSet()) {
153
+ // Dynamic require so it works in node without bundling issues
154
+ const undici = require('undici')
155
+ return await undici.fetch(url, {
156
+ redirect: 'follow',
157
+ dispatcher: new undici.EnvHttpProxyAgent(),
158
+ })
159
+ }
160
+ // Node 18+ has global fetch, Bun has it too
161
+ return await fetch(url, { redirect: 'follow' })
162
+ }
163
+
164
+ async function downloadUrlToBuffer(url) {
165
+ const response = await fetchRelease(url)
166
+ if (!response.ok) {
167
+ throw new Error(
168
+ `Download failed: ${response.status} ${response.statusText}`,
169
+ )
170
+ }
171
+ return Buffer.from(await response.arrayBuffer())
172
+ }
173
+
174
+ async function downloadUrlToBufferWithFallback(url) {
175
+ let firstError
176
+ try {
177
+ return await downloadUrlToBuffer(url)
178
+ } catch (e) {
179
+ firstError = e
180
+ }
181
+
182
+ const tmpRoot = path.join(
183
+ os.tmpdir(),
184
+ `ripgrep-dl-${process.pid}-${Date.now()}`,
185
+ )
186
+ const tmpFile = path.join(tmpRoot, 'archive')
187
+ mkdirSync(tmpRoot, { recursive: true })
188
+ try {
189
+ if (process.platform === 'win32' && tryPowerShellDownload(url, tmpFile)) {
190
+ return readFileSync(tmpFile)
191
+ }
192
+ if (tryCurlDownload(url, tmpFile)) {
193
+ return readFileSync(tmpFile)
194
+ }
195
+ } finally {
196
+ rmSync(tmpRoot, { recursive: true, force: true })
197
+ }
198
+
199
+ throw firstError
200
+ }
201
+
202
+ // --- Extract ---
203
+
204
+ function findZipEntryKey(files, want) {
205
+ return Object.keys(files).find(k => {
206
+ const norm = k.replace(/\\/g, '/')
207
+ return norm === want || norm.endsWith(`/${want}`)
208
+ })
209
+ }
210
+
211
+ async function extractZip(buffer, binaryPath, extractedBinary) {
212
+ const binaryDir = path.dirname(binaryPath)
213
+ // Try fflate first (bundled dep)
214
+ let fflateError
215
+ try {
216
+ const { unzipSync } = require('fflate')
217
+ const unzipped = unzipSync(new Uint8Array(buffer))
218
+ const key = findZipEntryKey(unzipped, extractedBinary)
219
+ if (!key) {
220
+ throw new Error(`Binary ${extractedBinary} not found in zip`)
221
+ }
222
+ writeFileSync(binaryPath, Buffer.from(unzipped[key]))
223
+ return
224
+ } catch (e) {
225
+ fflateError = e
226
+ }
227
+
228
+ // Fallback: PowerShell Expand-Archive or unzip CLI
229
+ const tmpDir = path.join(binaryDir, '.tmp-download')
230
+ rmSync(tmpDir, { recursive: true, force: true })
231
+ mkdirSync(tmpDir, { recursive: true })
232
+ try {
233
+ const assetName = `archive.zip`
234
+ const archivePath = path.join(tmpDir, assetName)
235
+ writeFileSync(archivePath, buffer)
236
+
237
+ let extracted = false
238
+ if (process.platform === 'win32') {
239
+ const psCmd = `Expand-Archive -Path '${archivePath.replace(/'/g, "''")}' -DestinationPath '${tmpDir.replace(/'/g, "''")}' -Force`
240
+ const psResult = spawnSync(
241
+ 'powershell.exe',
242
+ [
243
+ '-NoProfile',
244
+ '-NonInteractive',
245
+ '-ExecutionPolicy',
246
+ 'Bypass',
247
+ '-Command',
248
+ psCmd,
249
+ ],
250
+ { stdio: 'pipe', windowsHide: true },
251
+ )
252
+ if (psResult.status === 0) {
253
+ extracted = true
254
+ }
255
+ }
256
+
257
+ if (!extracted) {
258
+ const result = spawnSync('unzip', ['-o', archivePath, '-d', tmpDir], {
259
+ stdio: 'pipe',
260
+ })
261
+ if (result.status !== 0) {
262
+ const unzipErr = result.stderr?.toString().trim() || 'command not found'
263
+ const fflateMsg =
264
+ fflateError instanceof Error
265
+ ? fflateError.message
266
+ : String(fflateError)
267
+ throw new Error(
268
+ `zip extraction failed (fflate: ${fflateMsg}; unzip: ${unzipErr})`,
269
+ )
270
+ }
271
+ }
272
+
273
+ const srcBinary = path.join(tmpDir, extractedBinary)
274
+ if (!existsSync(srcBinary)) {
275
+ throw new Error(`Binary not found at expected path: ${srcBinary}`)
276
+ }
277
+ renameSync(srcBinary, binaryPath)
278
+ } finally {
279
+ rmSync(tmpDir, { recursive: true, force: true })
280
+ }
281
+ }
282
+
283
+ async function extractTarGz(buffer, binaryPath, extractedBinary, assetName) {
284
+ const binaryDir = path.dirname(binaryPath)
285
+ const tmpDir = path.join(binaryDir, '.tmp-download')
286
+ rmSync(tmpDir, { recursive: true, force: true })
287
+ mkdirSync(tmpDir, { recursive: true })
288
+ try {
289
+ const archivePath = path.join(tmpDir, assetName)
290
+ writeFileSync(archivePath, buffer)
291
+ const result = spawnSync('tar', ['xzf', archivePath, '-C', tmpDir], {
292
+ stdio: 'pipe',
293
+ })
294
+ if (result.status !== 0) {
295
+ throw new Error(`tar extract failed: ${result.stderr?.toString()}`)
296
+ }
297
+ const srcBinary = path.join(tmpDir, extractedBinary)
298
+ if (!existsSync(srcBinary)) {
299
+ throw new Error(`Binary not found at expected path: ${srcBinary}`)
300
+ }
301
+ renameSync(srcBinary, binaryPath)
302
+ } finally {
303
+ rmSync(tmpDir, { recursive: true, force: true })
304
+ }
305
+ }
306
+
307
+ // --- Main ---
308
+
309
+ async function downloadAndExtract() {
310
+ const { target, ext } = getPlatformMapping()
311
+ const assetName = `ripgrep-v${RG_VERSION}-${target}.${ext}`
312
+
313
+ const binaryPath = getBinaryPath()
314
+ const binaryDir = path.dirname(binaryPath)
315
+
316
+ const force = process.argv.includes('--force')
317
+ if (!force && existsSync(binaryPath)) {
318
+ const stat = statSync(binaryPath)
319
+ if (stat.size > 0) {
320
+ console.log(`[ripgrep] Binary already exists at ${binaryPath}, skipping.`)
321
+ return
322
+ }
323
+ }
324
+
325
+ console.log(`[ripgrep] Downloading v${RG_VERSION} for ${target}...`)
326
+
327
+ const extractedBinary = process.platform === 'win32' ? 'rg.exe' : 'rg'
328
+
329
+ const mirrors = [RELEASE_BASE]
330
+ if (RELEASE_BASE === DEFAULT_RELEASE_BASE.replace(/\/$/, '')) {
331
+ mirrors.push(MIRROR_RELEASE_BASE.replace(/\/$/, ''))
332
+ }
333
+
334
+ let buffer
335
+ let lastError
336
+ for (const base of mirrors) {
337
+ const url = `${base}/${assetName}`
338
+ try {
339
+ console.log(`[ripgrep] Trying ${url}`)
340
+ buffer = await downloadUrlToBufferWithFallback(url)
341
+ break
342
+ } catch (e) {
343
+ console.warn(
344
+ `[ripgrep] Download from ${base} failed: ${e instanceof Error ? e.message : e}`,
345
+ )
346
+ lastError = e
347
+ }
348
+ }
349
+ if (!buffer) {
350
+ throw lastError
351
+ }
352
+
353
+ try {
354
+ console.log(`[ripgrep] Downloaded ${Math.round(buffer.length / 1024)} KB`)
355
+
356
+ mkdirSync(binaryDir, { recursive: true })
357
+
358
+ if (ext === 'tar.gz') {
359
+ await extractTarGz(buffer, binaryPath, extractedBinary, assetName)
360
+ } else {
361
+ await extractZip(buffer, binaryPath, extractedBinary)
362
+ }
363
+
364
+ if (process.platform !== 'win32') {
365
+ chmodSync(binaryPath, 0o755)
366
+ }
367
+
368
+ console.log(`[ripgrep] Installed to ${binaryPath}`)
369
+ } catch (e) {
370
+ const msg = e instanceof Error ? e.message : String(e)
371
+ const hint =
372
+ 'Check network or set HTTPS_PROXY. If GitHub is blocked, set RIPGREP_DOWNLOAD_BASE to a mirror (see script header).'
373
+ throw new Error(`${msg} ${hint}`)
374
+ }
375
+ }
376
+
377
+ async function main() {
378
+ await downloadAndExtract()
379
+ }
380
+
381
+ main().catch(error => {
382
+ const msg = error instanceof Error ? error.message : String(error)
383
+ console.error(`[postinstall] ripgrep download failed (non-fatal): ${msg}`)
384
+ console.error(
385
+ `[postinstall] You can install ripgrep manually: https://github.com/BurntSushi/ripgrep#installation`,
386
+ )
387
+ // Never exit with error code — postinstall must not break install
388
+ process.exit(0)
389
+ })
@@ -0,0 +1,10 @@
1
+ import { spawn } from 'node:child_process'
2
+
3
+ const scripts = process.argv.slice(2)
4
+ if (scripts.length === 0) {
5
+ process.exit(0)
6
+ }
7
+
8
+ for (const script of scripts) {
9
+ spawn(process.execPath, [script], { stdio: 'inherit', shell: false })
10
+ }