@simonyea/holysheep-cli 2.1.1 → 2.1.3
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 +1 -1
- package/src/commands/webui.js +80 -6
- package/src/webui/aionui-runtime.js +17 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
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",
|
package/src/commands/webui.js
CHANGED
|
@@ -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.
|
|
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
|
|
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,35 @@ async function startAionUiMode(opts) {
|
|
|
272
324
|
return startLegacyMode(opts)
|
|
273
325
|
}
|
|
274
326
|
|
|
275
|
-
// 2. Resolve bun
|
|
276
|
-
|
|
327
|
+
// 2. Resolve bun — and auto-install if missing (opt-out via env, TTY-gated)
|
|
328
|
+
let bunPath = resolveBunPath()
|
|
329
|
+
const autoBunDisabled = process.env.HOLYSHEEP_WEBUI_NO_AUTOFETCH_BUN === '1'
|
|
330
|
+
if (!bunPath && !autoBunDisabled) {
|
|
331
|
+
// TTY guard: auto-install pipes a remote script into a shell. In CI,
|
|
332
|
+
// Docker builds, or other non-interactive environments the user can't
|
|
333
|
+
// cancel and didn't knowingly opt in. Skip auto-install there and fall
|
|
334
|
+
// through to the existing manual-install guidance + legacy fallback.
|
|
335
|
+
if (!process.stdout.isTTY) {
|
|
336
|
+
console.log(chalk.yellow(' bun missing and not running in a TTY — skipping auto-install (safety).'))
|
|
337
|
+
console.log(chalk.gray(' In CI / Docker / non-interactive shells, install bun explicitly first:'))
|
|
338
|
+
console.log(chalk.cyan(` ${describeBunInstall()}`))
|
|
339
|
+
console.log(chalk.gray(' Or opt out of this safety with HOLYSHEEP_WEBUI_FORCE_AUTOFETCH_BUN=1.'))
|
|
340
|
+
if (process.env.HOLYSHEEP_WEBUI_FORCE_AUTOFETCH_BUN === '1') {
|
|
341
|
+
console.log(chalk.cyan('▶ HOLYSHEEP_WEBUI_FORCE_AUTOFETCH_BUN=1 set — attempting auto-install anyway'))
|
|
342
|
+
bunPath = autoInstallBun((m) => console.log(chalk.gray(` ${m}`)))
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
console.log(chalk.cyan('▶ bun runtime not installed — installing automatically (one-time)'))
|
|
346
|
+
console.log(chalk.gray(' (disable with HOLYSHEEP_WEBUI_NO_AUTOFETCH_BUN=1; takes ~30s; source: bun.sh official installer)'))
|
|
347
|
+
bunPath = autoInstallBun((m) => console.log(chalk.gray(` ${m}`)))
|
|
348
|
+
}
|
|
349
|
+
}
|
|
277
350
|
if (!bunPath) {
|
|
278
351
|
console.log(chalk.red('✗ bun is required to run the AionUi server'))
|
|
279
352
|
console.log(chalk.gray(' (AionUi uses bun: URL-scheme imports that Node cannot load directly)'))
|
|
353
|
+
console.log(chalk.gray(' AionUi runtime itself is ready — bun is the last missing piece.'))
|
|
280
354
|
console.log()
|
|
281
|
-
console.log(chalk.yellow(' Install bun:'))
|
|
355
|
+
console.log(chalk.yellow(' Install bun manually:'))
|
|
282
356
|
console.log(chalk.cyan(` ${describeBunInstall()}`))
|
|
283
357
|
console.log(chalk.gray(' After install, close and reopen your terminal, then retry `hs web`.'))
|
|
284
358
|
console.log()
|
|
@@ -286,7 +360,7 @@ async function startAionUiMode(opts) {
|
|
|
286
360
|
console.log(chalk.red(' --aionui flag requires bun. Aborting.'))
|
|
287
361
|
process.exit(1)
|
|
288
362
|
}
|
|
289
|
-
console.log(chalk.yellow('
|
|
363
|
+
console.log(chalk.yellow(' AionUi runtime ready but bun auto-install failed — falling back to legacy HolySheep workspace.'))
|
|
290
364
|
console.log()
|
|
291
365
|
return startLegacyMode(opts)
|
|
292
366
|
}
|
|
@@ -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
|
|
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
|
-
})
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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() {
|