@simonyea/holysheep-cli 2.0.0 → 2.0.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/README.md CHANGED
@@ -225,6 +225,7 @@ A: OpenClaw 需要 Node.js 20+,运行 `node --version` 确认版本后重试
225
225
 
226
226
  ## Changelog
227
227
 
228
+ - **v2.0.1** — `hs web` 默认切到 HolySheep 登录版 AionUi runtime;本机检测到 AionUi + bun 时直接接管 WebUI,起不来才回退旧版 shell
228
229
  - **v2.0.0** — 修复 `hs web` 的关键可用性问题:工具探测改为异步缓存,避免 WebUI 首屏请求卡死;workspace 对话上游请求增加超时保护,防止发送消息时接口悬挂
229
230
  - **v1.7.135** — Droid CLI 的 GPT-5.4 配置切回官方要求的 `provider=openai + https://api.holysheep.ai/v1`;同时服务端兼容桥接 `gpt-5.4` 的 `/responses` 请求到 `/v1/chat/completions`
230
231
  - **v1.7.134** — 修复并发配置/Worker 路径下 Droid CLI 的 GPT-5.4 BYOK 配置:GPT 走 `generic-chat-completion-api + https://api.holysheep.ai/v1`,避免误走 Anthropic `/v1/messages`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.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"
@@ -8,7 +8,6 @@ const { execSync } = require('child_process')
8
8
 
9
9
  async function webui(opts) {
10
10
  const port = Number(opts.port) || 9876
11
- const { startServer } = require('../webui/server')
12
11
 
13
12
  console.log()
14
13
  console.log(chalk.bold('🌐 HolySheep WebUI'))
@@ -16,9 +15,31 @@ async function webui(opts) {
16
15
  console.log()
17
16
 
18
17
  try {
19
- const server = await startServer(port)
18
+ let child = null
19
+ let mode = 'legacy'
20
+
21
+ if (process.env.HOLYSHEEP_WEBUI_LEGACY !== '1') {
22
+ try {
23
+ const { startAionUiRuntime } = require('../webui/aionui-runtime')
24
+ const result = await startAionUiRuntime(port)
25
+ child = result.child
26
+ mode = 'aionui'
27
+ console.log(chalk.green(`✓ AionUi runtime 已接管 hs web`))
28
+ } catch (error) {
29
+ console.log(chalk.yellow(`! 未切到 AionUi runtime,回退旧版 WebUI: ${error.message}`))
30
+ }
31
+ }
32
+
33
+ if (!child) {
34
+ const { startServer } = require('../webui/server')
35
+ await startServer(port)
36
+ }
37
+
20
38
  const url = `http://127.0.0.1:${port}`
21
39
  console.log(chalk.green(`✓ WebUI 已启动: ${chalk.cyan.bold(url)}`))
40
+ if (mode === 'aionui') {
41
+ console.log(chalk.gray(' 当前模式: AionUi runtime'))
42
+ }
22
43
  console.log(chalk.gray(' 按 Ctrl+C 停止'))
23
44
  console.log()
24
45
 
@@ -31,6 +52,16 @@ async function webui(opts) {
31
52
  } catch {}
32
53
  }
33
54
 
55
+ if (child) {
56
+ const stopChild = () => {
57
+ if (!child.killed) child.kill('SIGTERM')
58
+ }
59
+ process.on('SIGINT', stopChild)
60
+ process.on('SIGTERM', stopChild)
61
+ child.on('exit', (code) => {
62
+ process.exit(code || 0)
63
+ })
64
+ }
34
65
 
35
66
  // Keep alive
36
67
  await new Promise(() => {})
@@ -0,0 +1,120 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const os = require('os')
6
+ const http = require('http')
7
+ const { execSync, spawn } = require('child_process')
8
+
9
+ function resolveBunPath() {
10
+ if (process.env.BUN && fs.existsSync(process.env.BUN)) return process.env.BUN
11
+ try {
12
+ const resolved = execSync('which bun', {
13
+ stdio: ['ignore', 'pipe', 'ignore'],
14
+ encoding: 'utf8',
15
+ timeout: 2000,
16
+ }).trim()
17
+ return resolved || null
18
+ } catch {
19
+ return null
20
+ }
21
+ }
22
+
23
+ function getRuntimeCandidates() {
24
+ const home = os.homedir()
25
+ return [
26
+ process.env.HOLYSHEEP_AIONUI_DIR,
27
+ path.join(home, 'AionUi'),
28
+ path.join(home, 'Projects', 'AionUi'),
29
+ path.join(__dirname, '..', '..', 'vendor', 'aionui'),
30
+ ].filter(Boolean)
31
+ }
32
+
33
+ function isValidRuntimeDir(dir) {
34
+ if (!dir) return false
35
+ return fs.existsSync(path.join(dir, 'dist-server', 'server.mjs')) &&
36
+ fs.existsSync(path.join(dir, 'out', 'renderer', 'index.html'))
37
+ }
38
+
39
+ function resolveAionUiRuntimeDir() {
40
+ return getRuntimeCandidates().find(isValidRuntimeDir) || null
41
+ }
42
+
43
+ function waitForReady(port, timeoutMs = 15000) {
44
+ const startedAt = Date.now()
45
+ return new Promise((resolve, reject) => {
46
+ const tick = () => {
47
+ const req = http.get({
48
+ hostname: '127.0.0.1',
49
+ port,
50
+ path: '/',
51
+ family: 4,
52
+ timeout: 1500,
53
+ }, (res) => {
54
+ res.resume()
55
+ if (res.statusCode && res.statusCode < 500) {
56
+ resolve(true)
57
+ return
58
+ }
59
+ retry()
60
+ })
61
+
62
+ req.on('timeout', () => {
63
+ req.destroy()
64
+ retry()
65
+ })
66
+ req.on('error', retry)
67
+ }
68
+
69
+ const retry = () => {
70
+ if (Date.now() - startedAt >= timeoutMs) {
71
+ reject(new Error('AionUi runtime did not become ready in time'))
72
+ return
73
+ }
74
+ setTimeout(tick, 500)
75
+ }
76
+
77
+ tick()
78
+ })
79
+ }
80
+
81
+ async function startAionUiRuntime(port) {
82
+ const runtimeDir = resolveAionUiRuntimeDir()
83
+ if (!runtimeDir) {
84
+ throw new Error('AionUi runtime not found')
85
+ }
86
+
87
+ const bunPath = resolveBunPath()
88
+ if (!bunPath) {
89
+ throw new Error('bun is required to start the AionUi runtime')
90
+ }
91
+
92
+ const child = spawn(bunPath, ['dist-server/server.mjs'], {
93
+ cwd: runtimeDir,
94
+ env: {
95
+ ...process.env,
96
+ PORT: String(port),
97
+ NODE_ENV: 'production',
98
+ },
99
+ stdio: 'inherit',
100
+ })
101
+
102
+ try {
103
+ await waitForReady(port)
104
+ } catch (error) {
105
+ child.kill('SIGTERM')
106
+ throw error
107
+ }
108
+
109
+ return {
110
+ child,
111
+ runtimeDir,
112
+ bunPath,
113
+ mode: 'aionui',
114
+ }
115
+ }
116
+
117
+ module.exports = {
118
+ resolveAionUiRuntimeDir,
119
+ startAionUiRuntime,
120
+ }