@simonyea/holysheep-cli 2.1.1 → 2.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Claude Code/Cursor/Cline API relay for China — ¥1=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
5
5
  "scripts": {
6
6
  "test": "node tests/droid.test.js && node tests/workspace-store.test.js",
@@ -23,6 +23,18 @@ function isLegacy(opts) {
23
23
  }
24
24
 
25
25
  // ── Bun resolution ───────────────────────────────────────────────────────────
26
+ function homeBunCandidate() {
27
+ // bun's official installer always drops the binary at:
28
+ // Windows: %USERPROFILE%\.bun\bin\bun.exe
29
+ // Unix: $HOME/.bun/bin/bun
30
+ // This fallback covers the very common "user just installed bun but hasn't
31
+ // reopened the terminal, so PATH doesn't see it yet" case.
32
+ const home = process.env.HOME || process.env.USERPROFILE || require('os').homedir()
33
+ if (!home) return null
34
+ const name = process.platform === 'win32' ? 'bun.exe' : 'bun'
35
+ return path.join(home, '.bun', 'bin', name)
36
+ }
37
+
26
38
  function resolveBunPath() {
27
39
  // 1. Dev: use bundled bun if present (darwin-arm64 only in 2.0.x)
28
40
  const bundledBun = path.join(__dirname, '..', 'webui', 'vendor', 'bun-darwin-arm64')
@@ -48,9 +60,14 @@ function resolveBunPath() {
48
60
  if (fs.existsSync(p)) return p
49
61
  }
50
62
  } catch {}
51
- // 4. No bun found. AionUi's dist-server/server.mjs uses `bun:` URL scheme
63
+ // 4. ~/.bun/bin fallback — bun installer's canonical location. Catches the
64
+ // "installed bun, didn't reopen terminal" scenario that previously forced
65
+ // users to fallback to legacy workspace on first `hs web`.
66
+ const homeBun = homeBunCandidate()
67
+ if (homeBun && fs.existsSync(homeBun)) return homeBun
68
+ // 5. No bun found. AionUi's dist-server/server.mjs uses `bun:` URL scheme
52
69
  // imports (verified 2026-04-21: node 25 throws ERR_UNSUPPORTED_ESM_URL_SCHEME
53
- // immediately), so bun is a hard runtime dep. Caller shows install guidance.
70
+ // immediately), so bun is a hard runtime dep. Caller can auto-install.
54
71
  return null
55
72
  }
56
73
 
@@ -61,6 +78,40 @@ function describeBunInstall() {
61
78
  return 'curl -fsSL https://bun.sh/install | bash'
62
79
  }
63
80
 
81
+ // Auto-install bun using the official bun.sh installer. Returns the newly
82
+ // resolved bun path on success, or null on any failure (caller falls back).
83
+ // Controlled by HOLYSHEEP_WEBUI_NO_AUTOFETCH_BUN=1 (opt-out).
84
+ function autoInstallBun(logger) {
85
+ const isWindows = process.platform === 'win32'
86
+ // Both installers are official bun.sh first-party scripts. We log the URL
87
+ // explicitly so the user knows what's running.
88
+ const cmd = isWindows
89
+ ? 'powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "irm https://bun.sh/install.ps1 | iex"'
90
+ : 'bash -c "curl -fsSL https://bun.sh/install | bash"'
91
+ logger(`[auto-install-bun] platform=${process.platform} command=${cmd}`)
92
+ logger(' Fetching official bun installer from https://bun.sh — this takes ~30s')
93
+ try {
94
+ execSync(cmd, {
95
+ stdio: 'inherit',
96
+ timeout: 180_000,
97
+ // Keep env minimal — don't leak weird node_options into the installer
98
+ env: { ...process.env, NODE_OPTIONS: '' },
99
+ })
100
+ } catch (e) {
101
+ logger(`[auto-install-bun] failed: ${e.message || e}`)
102
+ return null
103
+ }
104
+ // Re-resolve — the installer drops bun at ~/.bun/bin/bun[.exe], which our
105
+ // resolveBunPath() fallback (step 4) picks up without needing a shell reload.
106
+ const p = resolveBunPath()
107
+ if (!p) {
108
+ logger('[auto-install-bun] installer finished but bun still not found at ~/.bun/bin — aborting')
109
+ return null
110
+ }
111
+ logger(`[auto-install-bun] success → ${p}`)
112
+ return p
113
+ }
114
+
64
115
  // ── Runtime resolution ───────────────────────────────────────────────────────
65
116
  // Priority:
66
117
  // 1. Local dev checkout at ../aionui-fork/dist-server (developer building locally)
@@ -246,6 +297,7 @@ async function startAionUiMode(opts) {
246
297
  const downloaded = await downloadAionUiRuntime((m) => console.log(chalk.gray(` ${m}`)))
247
298
  if (downloaded) {
248
299
  runtime = { dir: downloaded.dir, source: downloaded.source }
300
+ console.log(chalk.gray(' AionUi runtime installed. Next: ensure bun is available to launch it.'))
249
301
  }
250
302
  }
251
303
  if (!runtime) {
@@ -272,13 +324,20 @@ async function startAionUiMode(opts) {
272
324
  return startLegacyMode(opts)
273
325
  }
274
326
 
275
- // 2. Resolve bun
276
- const bunPath = resolveBunPath()
327
+ // 2. Resolve bun — and auto-install if missing (opt-out via env)
328
+ let bunPath = resolveBunPath()
329
+ const autoBunDisabled = process.env.HOLYSHEEP_WEBUI_NO_AUTOFETCH_BUN === '1'
330
+ if (!bunPath && !autoBunDisabled) {
331
+ console.log(chalk.cyan('▶ bun runtime not installed — installing automatically (one-time)'))
332
+ console.log(chalk.gray(' (disable with HOLYSHEEP_WEBUI_NO_AUTOFETCH_BUN=1; takes ~30s; source: bun.sh official installer)'))
333
+ bunPath = autoInstallBun((m) => console.log(chalk.gray(` ${m}`)))
334
+ }
277
335
  if (!bunPath) {
278
336
  console.log(chalk.red('✗ bun is required to run the AionUi server'))
279
337
  console.log(chalk.gray(' (AionUi uses bun: URL-scheme imports that Node cannot load directly)'))
338
+ console.log(chalk.gray(' AionUi runtime itself is ready — bun is the last missing piece.'))
280
339
  console.log()
281
- console.log(chalk.yellow(' Install bun:'))
340
+ console.log(chalk.yellow(' Install bun manually:'))
282
341
  console.log(chalk.cyan(` ${describeBunInstall()}`))
283
342
  console.log(chalk.gray(' After install, close and reopen your terminal, then retry `hs web`.'))
284
343
  console.log()
@@ -286,7 +345,7 @@ async function startAionUiMode(opts) {
286
345
  console.log(chalk.red(' --aionui flag requires bun. Aborting.'))
287
346
  process.exit(1)
288
347
  }
289
- console.log(chalk.yellow(' Falling back to legacy HolySheep workspace.'))
348
+ console.log(chalk.yellow(' AionUi runtime ready but bun auto-install failed — falling back to legacy HolySheep workspace.'))
290
349
  console.log()
291
350
  return startLegacyMode(opts)
292
351
  }
@@ -12,16 +12,28 @@ function resolveBunPath() {
12
12
  return bundledBun
13
13
  }
14
14
  if (process.env.BUN && fs.existsSync(process.env.BUN)) return process.env.BUN
15
+ const isWindows = process.platform === 'win32'
15
16
  try {
16
- const resolved = execSync('which bun', {
17
+ const cmd = isWindows ? 'where.exe bun' : 'which bun'
18
+ const raw = execSync(cmd, {
17
19
  stdio: ['ignore', 'pipe', 'ignore'],
18
20
  encoding: 'utf8',
19
21
  timeout: 2000,
20
- }).trim()
21
- return resolved || null
22
- } catch {
23
- return null
22
+ })
23
+ const candidates = String(raw).split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
24
+ for (const p of candidates) {
25
+ if (fs.existsSync(p)) return p
26
+ }
27
+ } catch {}
28
+ // ~/.bun/bin fallback — canonical location of bun's official installer.
29
+ // Catches "installed bun but didn't reopen terminal" without needing PATH refresh.
30
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir()
31
+ if (home) {
32
+ const name = isWindows ? 'bun.exe' : 'bun'
33
+ const homeBun = path.join(home, '.bun', 'bin', name)
34
+ if (fs.existsSync(homeBun)) return homeBun
24
35
  }
36
+ return null
25
37
  }
26
38
 
27
39
  function getRuntimeCandidates() {