@simonyea/holysheep-cli 2.1.0 → 2.1.1

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.0",
3
+ "version": "2.1.1",
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",
@@ -31,21 +31,36 @@ function resolveBunPath() {
31
31
  }
32
32
  // 2. Env override
33
33
  if (process.env.BUN && fs.existsSync(process.env.BUN)) return process.env.BUN
34
- // 3. System bun
34
+ // 3. System bun — platform-correct lookup command
35
+ const isWindows = process.platform === 'win32'
35
36
  try {
36
- const resolved = execSync('which bun', {
37
+ // `where.exe bun` on Windows prints one line per match; pick the first.
38
+ // `which bun` on Unix prints exactly one line (or fails with exit !=0).
39
+ const cmd = isWindows ? 'where.exe bun' : 'which bun'
40
+ const raw = execSync(cmd, {
37
41
  stdio: ['ignore', 'pipe', 'ignore'],
38
42
  encoding: 'utf8',
39
- timeout: 2000,
40
- }).trim()
41
- if (resolved && fs.existsSync(resolved)) return resolved
43
+ timeout: 3000,
44
+ })
45
+ // Windows emits CRLF and may list multiple paths — take the first existing one.
46
+ const candidates = raw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
47
+ for (const p of candidates) {
48
+ if (fs.existsSync(p)) return p
49
+ }
42
50
  } catch {}
43
- // 4. Fallback: node with experimental support (dist-server/server.mjs is ESM)
44
- // AionUi server.mjs targets bun, but node 20+ with --experimental-vm-modules can work.
45
- // We'll prefer node as a last-resort if bun is absent.
51
+ // 4. No bun found. AionUi's dist-server/server.mjs uses `bun:` URL scheme
52
+ // 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.
46
54
  return null
47
55
  }
48
56
 
57
+ function describeBunInstall() {
58
+ if (process.platform === 'win32') {
59
+ return 'powershell -c "irm bun.sh/install.ps1 | iex"'
60
+ }
61
+ return 'curl -fsSL https://bun.sh/install | bash'
62
+ }
63
+
49
64
  // ── Runtime resolution ───────────────────────────────────────────────────────
50
65
  // Priority:
51
66
  // 1. Local dev checkout at ../aionui-fork/dist-server (developer building locally)
@@ -71,14 +86,10 @@ function resolveAionUiRuntimeDir() {
71
86
  }
72
87
 
73
88
  async function downloadAionUiRuntime(logger) {
74
- const url = process.env.HOLYSHEEP_AIONUI_RUNTIME_URL
75
- if (!url) {
76
- logger('HOLYSHEEP_AIONUI_RUNTIME_URL not set — cannot auto-download')
77
- return null
78
- }
89
+ // Fetcher has a baked-in default URL + SHA256 — no env var required.
90
+ // HOLYSHEEP_AIONUI_RUNTIME_URL still works as an override.
79
91
  const { resolveRuntime } = require('../webui/aionui-runtime-fetcher')
80
- const runtime = await resolveRuntime({ allowDownload: true, logger })
81
- return runtime
92
+ return resolveRuntime({ allowDownload: true, logger })
82
93
  }
83
94
 
84
95
  // ── HolySheep config → API key ───────────────────────────────────────────────
@@ -224,10 +235,14 @@ function spawnAionUiServer({ bunPath, runtimeDir, port }) {
224
235
  async function startAionUiMode(opts) {
225
236
  const port = Number(opts.port) || 9876
226
237
 
227
- // 1. Resolve runtime
238
+ // 1. Resolve runtime. First launch after `npm i -g` won't have one locally
239
+ // — we auto-download from the baked-in URL (verified by SHA256) unless the
240
+ // user opts out via HOLYSHEEP_WEBUI_NO_AUTOFETCH=1.
228
241
  let runtime = resolveAionUiRuntimeDir()
229
- if (!runtime && opts.setupRuntime) {
230
- console.log(chalk.gray(' Downloading AionUi runtime...'))
242
+ const autoFetchDisabled = process.env.HOLYSHEEP_WEBUI_NO_AUTOFETCH === '1'
243
+ if (!runtime && !autoFetchDisabled) {
244
+ console.log(chalk.cyan('▶ AionUi runtime not installed — downloading automatically (~21 MB, one-time)'))
245
+ console.log(chalk.gray(' (disable with HOLYSHEEP_WEBUI_NO_AUTOFETCH=1; override URL with HOLYSHEEP_AIONUI_RUNTIME_URL)'))
231
246
  const downloaded = await downloadAionUiRuntime((m) => console.log(chalk.gray(` ${m}`)))
232
247
  if (downloaded) {
233
248
  runtime = { dir: downloaded.dir, source: downloaded.source }
@@ -235,14 +250,17 @@ async function startAionUiMode(opts) {
235
250
  }
236
251
  if (!runtime) {
237
252
  const home = process.env.HOME || process.env.USERPROFILE || '~'
238
- console.log(chalk.red('✗ AionUi runtime not found'))
253
+ console.log(chalk.red('✗ AionUi runtime not found and auto-download did not succeed'))
239
254
  console.log()
240
255
  console.log(chalk.gray(' Expected at one of:'))
241
256
  console.log(chalk.gray(` • ${path.resolve(__dirname, '..', '..', 'aionui-fork', 'dist-server')} (dev checkout)`))
242
257
  console.log(chalk.gray(` • ${path.join(home, '.holysheep', 'aionui-runtime', 'dist-server')} (installed)`))
243
258
  console.log()
244
- console.log(chalk.yellow(' Install via:'))
259
+ console.log(chalk.yellow(' Retry manually:'))
260
+ console.log(chalk.cyan(' hs web --setup-runtime'))
261
+ console.log(chalk.gray(' Or pin a specific runtime URL + SHA256:'))
245
262
  console.log(chalk.cyan(' export HOLYSHEEP_AIONUI_RUNTIME_URL=https://mail.holysheep.ai/app/cli/aionui-runtime-v1.9.18-holysheep.tar.gz'))
263
+ console.log(chalk.cyan(' export HOLYSHEEP_AIONUI_RUNTIME_SHA256=<see README>'))
246
264
  console.log(chalk.cyan(' hs web --setup-runtime'))
247
265
  console.log()
248
266
  if (opts.aionui) {
@@ -258,12 +276,18 @@ async function startAionUiMode(opts) {
258
276
  const bunPath = resolveBunPath()
259
277
  if (!bunPath) {
260
278
  console.log(chalk.red('✗ bun is required to run the AionUi server'))
261
- console.log(chalk.gray(' Install: curl -fsSL https://bun.sh/install | bash'))
279
+ console.log(chalk.gray(' (AionUi uses bun: URL-scheme imports that Node cannot load directly)'))
280
+ console.log()
281
+ console.log(chalk.yellow(' Install bun:'))
282
+ console.log(chalk.cyan(` ${describeBunInstall()}`))
283
+ console.log(chalk.gray(' After install, close and reopen your terminal, then retry `hs web`.'))
262
284
  console.log()
263
285
  if (opts.aionui) {
286
+ console.log(chalk.red(' --aionui flag requires bun. Aborting.'))
264
287
  process.exit(1)
265
288
  }
266
- console.log(chalk.yellow(' Falling back to legacy workspace.'))
289
+ console.log(chalk.yellow(' Falling back to legacy HolySheep workspace.'))
290
+ console.log()
267
291
  return startLegacyMode(opts)
268
292
  }
269
293
 
@@ -366,8 +390,16 @@ async function webui(opts) {
366
390
  try {
367
391
  // 默认走 AionUi;HOLYSHEEP_WEBUI_LEGACY=1 才退回 legacy
368
392
  if (isLegacy(opts) && !opts.aionui) {
393
+ console.log(chalk.gray(`[mode=legacy platform=${process.platform}]`))
394
+ console.log()
369
395
  return await startLegacyMode(opts)
370
396
  }
397
+ // Tri-state mode line: helps user-reports when things go sideways
398
+ const bunFound = resolveBunPath() ? 'found' : 'missing'
399
+ const rt = resolveAionUiRuntimeDir()
400
+ const rtLabel = rt ? rt.source : 'none'
401
+ console.log(chalk.gray(`[mode=aionui platform=${process.platform} bun=${bunFound} runtime=${rtLabel}]`))
402
+ console.log()
371
403
  return await startAionUiMode(opts)
372
404
  } catch (err) {
373
405
  console.log(chalk.red(`✗ 启动失败: ${err.message}`))
@@ -239,7 +239,7 @@ async function handleLogin(req, res) {
239
239
  }
240
240
  try {
241
241
  const valid = await validateApiKey(apiKey)
242
- if (!valid) return json(res, { success: false, message: 'API Key 无效' }, 401)
242
+ if (!valid) return json(res, { success: false, message: 'API Key 无效 (server returned non-2xx)' }, 401)
243
243
  saveConfig({ apiKey, savedAt: new Date().toISOString() })
244
244
  workspaceStore.saveHolySheepApiConfig({
245
245
  apiKey,
@@ -248,7 +248,25 @@ async function handleLogin(req, res) {
248
248
  })
249
249
  json(res, { success: true, apiKey: maskKey(apiKey) })
250
250
  } catch (e) {
251
- json(res, { success: false, message: `验证失败: ${e.message}` }, 500)
251
+ // Translate cryptic node-fetch / DNS / proxy errors into something actionable.
252
+ // Previously users just saw "链接失败" in the browser alert — now we surface
253
+ // the underlying cause (DNS, timeout, proxy interference, TLS, etc).
254
+ const code = e && (e.code || e.errno || e.type)
255
+ const name = e && e.name
256
+ let hint
257
+ if (code === 'EAI_AGAIN' || code === 'ENOTFOUND') {
258
+ hint = 'DNS 解析失败 — 检查网络 / 代理 / 防火墙是否拦截了 api.holysheep.ai'
259
+ } else if (code === 'ETIMEDOUT' || name === 'AbortError') {
260
+ hint = '连接超时 — 可能是网络慢或代理阻塞;试试开关代理再重试'
261
+ } else if (code === 'ECONNREFUSED' || code === 'ECONNRESET') {
262
+ hint = '连接被拒绝 — 检查本机防火墙或公司代理'
263
+ } else if (code === 'CERT_HAS_EXPIRED' || code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
264
+ hint = 'TLS 证书校验失败 — 可能是系统时间错误或中间人代理'
265
+ } else {
266
+ hint = e && e.message ? e.message : String(e)
267
+ }
268
+ const detail = `${hint}${code ? ` [${code}]` : ''}`
269
+ json(res, { success: false, message: `验证失败: ${detail}` }, 500)
252
270
  }
253
271
  }
254
272