@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 +1 -1
- package/src/commands/webui.js +88 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "2.1.
|
|
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",
|
package/src/commands/webui.js
CHANGED
|
@@ -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
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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)
|