@simonyea/holysheep-cli 2.1.3 → 2.1.4

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.3",
3
+ "version": "2.1.4",
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",
@@ -210,6 +210,12 @@ async function loginWithApiKey(port, apiKey) {
210
210
  }
211
211
 
212
212
  // ── Start patched AionUi server ──────────────────────────────────────────────
213
+ // Timeout is generous for Windows first launch: Defender scans 41MB server.mjs,
214
+ // bun JIT-compiles, AionUi initializes sqlite under ~/.aionui-home, and npm
215
+ // may still pull the renderer. 60s is the realistic upper bound on a cold box.
216
+ const AIONUI_STARTUP_TIMEOUT_MS = Number(process.env.HS_WEB_STARTUP_TIMEOUT_MS) || 60_000
217
+ const AIONUI_LOG_TAIL_BYTES = 4096
218
+
213
219
  function spawnAionUiServer({ bunPath, runtimeDir, port }) {
214
220
  return new Promise((resolve, reject) => {
215
221
  const entry = path.join(runtimeDir, 'dist-server', 'server.mjs')
@@ -231,21 +237,74 @@ function spawnAionUiServer({ bunPath, runtimeDir, port }) {
231
237
  stdio: ['ignore', 'pipe', 'pipe'],
232
238
  })
233
239
 
240
+ // ── Log capture ────────────────────────────────────────────────────────
241
+ // Always subscribe to stdout+stderr. In debug mode we ALSO print live; in
242
+ // normal mode we keep the last ~4KB in a ring buffer so that if startup
243
+ // fails (timeout OR exit-before-ready) we can surface the real error to
244
+ // the user instead of the previous silent "failed to start within 20s".
245
+ const debug = process.env.HS_WEB_DEBUG === '1'
246
+ let logTail = ''
247
+ const appendLog = (stream, chunk) => {
248
+ const s = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk)
249
+ logTail += s
250
+ if (logTail.length > AIONUI_LOG_TAIL_BYTES) {
251
+ logTail = logTail.slice(logTail.length - AIONUI_LOG_TAIL_BYTES)
252
+ }
253
+ if (debug) {
254
+ const target = stream === 'err' ? process.stderr : process.stdout
255
+ target.write(chalk.gray(`[aionui] ${s}`))
256
+ }
257
+ }
258
+ child.stdout.on('data', (d) => appendLog('out', d))
259
+ child.stderr.on('data', (d) => appendLog('err', d))
260
+
261
+ // ── Progress + timeout ─────────────────────────────────────────────────
234
262
  let settled = false
263
+ const startedAt = Date.now()
235
264
  const onReady = () => {
236
265
  if (!settled) {
237
266
  settled = true
267
+ clearInterval(progressTick)
238
268
  resolve(child)
239
269
  }
240
270
  }
241
- const timer = setTimeout(() => {
242
- if (!settled) {
243
- settled = true
244
- reject(new Error('AionUi server failed to start within 20s'))
271
+ const fail = (reason) => {
272
+ if (settled) return
273
+ settled = true
274
+ clearInterval(progressTick)
275
+ const tail = logTail.trim()
276
+ let msg = reason
277
+ if (tail) {
278
+ msg += `\n\n --- last AionUi output (stderr+stdout tail) ---\n${tail
279
+ .split(/\r?\n/)
280
+ .map((line) => ` ${line}`)
281
+ .join('\n')}\n ------------------------------------------------`
282
+ } else {
283
+ msg += '\n (no output captured from AionUi — check ' +
284
+ (process.platform === 'win32' ? 'Windows Defender / antivirus quarantining bun.exe' : 'bun or runtime corruption') +
285
+ ')'
245
286
  }
246
- }, 20_000)
247
- // Poll /health-ish endpoint to detect readiness.
287
+ reject(new Error(msg))
288
+ }
289
+
290
+ // Progress log every 10s, so user knows we're not hung. On Windows first
291
+ // launch (Defender scanning bun + bun JIT + sqlite init), 30-50s is normal.
292
+ const progressTick = setInterval(() => {
293
+ if (settled) return
294
+ const waited = Math.round((Date.now() - startedAt) / 1000)
295
+ const hint = process.platform === 'win32'
296
+ ? ' (Windows first-launch can take up to 60s while Defender scans bun.exe + server.mjs)'
297
+ : ''
298
+ console.log(chalk.gray(` still starting AionUi — waited ${waited}s…${hint}`))
299
+ }, 10_000)
300
+
301
+ const timer = setTimeout(() => {
302
+ fail(`AionUi server failed to start within ${Math.round(AIONUI_STARTUP_TIMEOUT_MS / 1000)}s`)
303
+ }, AIONUI_STARTUP_TIMEOUT_MS)
304
+
305
+ // ── Readiness poll ─────────────────────────────────────────────────────
248
306
  const pollReady = () => {
307
+ if (settled) return
249
308
  const req = http.request(
250
309
  { host: '127.0.0.1', port, path: '/', method: 'HEAD', timeout: 500 },
251
310
  (res) => {
@@ -267,17 +326,17 @@ function spawnAionUiServer({ bunPath, runtimeDir, port }) {
267
326
  // Start polling after a short grace period so bun has time to boot.
268
327
  setTimeout(pollReady, 600)
269
328
 
270
- // Surface logs with prefix (muted by default; set HS_WEB_DEBUG=1 to see)
271
- const debug = process.env.HS_WEB_DEBUG === '1'
272
- if (debug) {
273
- child.stdout.on('data', (d) => process.stdout.write(chalk.gray(`[aionui] ${d}`)))
274
- child.stderr.on('data', (d) => process.stderr.write(chalk.gray(`[aionui] ${d}`)))
275
- }
276
- child.on('exit', (code) => {
277
- if (!settled) {
278
- settled = true
279
- reject(new Error(`AionUi server exited with code ${code} before becoming ready`))
280
- }
329
+ // ── Early exit ─────────────────────────────────────────────────────────
330
+ child.on('exit', (code, signal) => {
331
+ const reason = signal
332
+ ? `AionUi server exited with signal ${signal} before becoming ready`
333
+ : `AionUi server exited with code ${code} before becoming ready`
334
+ clearTimeout(timer)
335
+ fail(reason)
336
+ })
337
+ child.on('error', (err) => {
338
+ clearTimeout(timer)
339
+ fail(`AionUi server spawn error: ${err.message || err}`)
281
340
  })
282
341
  })
283
342
  }
@@ -372,7 +431,18 @@ async function startAionUiMode(opts) {
372
431
  try {
373
432
  aionuiProc = await spawnAionUiServer({ bunPath, runtimeDir: runtime.dir, port })
374
433
  } catch (e) {
375
- console.log(chalk.red(`✗ AionUi server failed to start: ${e.message}`))
434
+ // `e.message` may now be multi-line (includes bun/AionUi stderr tail).
435
+ // Print the first line in red (the reason), then any extra lines verbatim
436
+ // so the user can see the real bun/AionUi error and paste it back to us.
437
+ const [firstLine, ...rest] = String(e.message).split(/\r?\n/)
438
+ console.log(chalk.red(`✗ AionUi server failed to start: ${firstLine}`))
439
+ for (const line of rest) {
440
+ if (line.trim()) console.log(chalk.gray(line))
441
+ }
442
+ console.log()
443
+ console.log(chalk.gray(' Tip: run again with HS_WEB_DEBUG=1 to stream AionUi logs live.'))
444
+ console.log(chalk.gray(' If this is a first launch on Windows, try once more — Defender'))
445
+ console.log(chalk.gray(' will cache bun.exe + server.mjs after the first scan.'))
376
446
  if (opts.aionui) process.exit(1)
377
447
  console.log(chalk.yellow(' Falling back to legacy workspace.'))
378
448
  return startLegacyMode(opts)