@simonyea/holysheep-cli 1.7.33 → 1.7.35

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": "1.7.33",
3
+ "version": "1.7.35",
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
  "keywords": [
6
6
  "openai-china",
@@ -0,0 +1,203 @@
1
+ 'use strict'
2
+
3
+ const { execSync, spawn, spawnSync } = require('child_process')
4
+ const path = require('path')
5
+ const chalk = require('chalk')
6
+ const openclawTool = require('../tools/openclaw')
7
+ const { readBridgeConfig } = require('../tools/openclaw-bridge')
8
+
9
+ const isWin = process.platform === 'win32'
10
+
11
+ function checkBridgeHealth(port) {
12
+ try {
13
+ execSync(
14
+ isWin
15
+ ? `powershell -NonInteractive -Command "try{(Invoke-WebRequest -Uri http://127.0.0.1:${port}/health -TimeoutSec 1 -UseBasicParsing).StatusCode}catch{exit 1}"`
16
+ : `curl -sf http://127.0.0.1:${port}/health -o /dev/null --max-time 1`,
17
+ { stdio: 'ignore', timeout: 3000 }
18
+ )
19
+ return true
20
+ } catch {
21
+ return false
22
+ }
23
+ }
24
+
25
+ function checkGatewayHealth(port) {
26
+ try {
27
+ execSync(
28
+ isWin
29
+ ? `powershell -NonInteractive -Command "try{(Invoke-WebRequest -Uri http://127.0.0.1:${port}/ -TimeoutSec 1 -UseBasicParsing).StatusCode}catch{exit 1}"`
30
+ : `curl -sf http://127.0.0.1:${port}/ -o /dev/null --max-time 1`,
31
+ { stdio: 'ignore', timeout: 3000 }
32
+ )
33
+ return true
34
+ } catch {
35
+ return false
36
+ }
37
+ }
38
+
39
+ function waitForPort(checkFn, maxTries, intervalMs) {
40
+ for (let i = 0; i < maxTries; i++) {
41
+ if (checkFn()) return true
42
+ const t0 = Date.now()
43
+ while (Date.now() - t0 < intervalMs) {}
44
+ }
45
+ return false
46
+ }
47
+
48
+ function killBridge(port) {
49
+ const listeners = openclawTool.getPortListeners(port)
50
+ let killed = 0
51
+ for (const item of listeners) {
52
+ const pid = parseInt(item.pid, 10)
53
+ if (!pid) continue
54
+ try {
55
+ if (isWin) {
56
+ spawnSync('taskkill', ['/PID', String(pid), '/F'], { stdio: 'ignore', shell: true })
57
+ } else {
58
+ process.kill(pid, 'SIGTERM')
59
+ }
60
+ killed++
61
+ } catch {}
62
+ }
63
+ return killed
64
+ }
65
+
66
+ function waitPortFree(port, maxTries, intervalMs) {
67
+ for (let i = 0; i < maxTries; i++) {
68
+ if (openclawTool.getPortListeners(port).length === 0) return true
69
+ const t0 = Date.now()
70
+ while (Date.now() - t0 < intervalMs) {}
71
+ }
72
+ return openclawTool.getPortListeners(port).length === 0
73
+ }
74
+
75
+ function openBrowser(url) {
76
+ const cmd = isWin ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open'
77
+ try {
78
+ spawn(cmd, [url], { detached: true, stdio: 'ignore', shell: isWin }).unref()
79
+ } catch {}
80
+ }
81
+
82
+ async function openclaw() {
83
+ console.log()
84
+
85
+ const runtime = openclawTool.detectRuntime()
86
+ if (!runtime.available) {
87
+ console.log(chalk.red('✗ 未检测到 OpenClaw'))
88
+ console.log(chalk.gray(` 安装命令: ${openclawTool.installCmd}`))
89
+ console.log(chalk.gray(' 安装后运行 hs setup 完成配置'))
90
+ process.exit(1)
91
+ }
92
+
93
+ if (!openclawTool.isConfigured()) {
94
+ console.log(chalk.yellow('⚠ OpenClaw 尚未配置 HolySheep,请先运行 hs setup'))
95
+ process.exit(1)
96
+ }
97
+
98
+ let bridgeConfig
99
+ try {
100
+ bridgeConfig = readBridgeConfig()
101
+ } catch {
102
+ console.log(chalk.yellow('⚠ 未找到 Bridge 配置,请先运行 hs setup'))
103
+ process.exit(1)
104
+ }
105
+
106
+ const bridgePort = openclawTool.getBridgePort()
107
+ const gatewayPort = openclawTool.getGatewayPort()
108
+
109
+ // 先杀掉旧 Bridge,再启动新的
110
+ const existingListeners = openclawTool.getPortListeners(bridgePort)
111
+ if (existingListeners.length > 0) {
112
+ process.stdout.write(chalk.gray(` 正在重启 Bridge (端口 ${bridgePort})... `))
113
+ killBridge(bridgePort)
114
+ waitPortFree(bridgePort, 10, 200)
115
+ } else {
116
+ process.stdout.write(chalk.gray(` 正在启动 Bridge (端口 ${bridgePort})... `))
117
+ }
118
+
119
+ const scriptPath = path.join(__dirname, '..', 'index.js')
120
+ const spawnCmd = isWin ? 'node' : process.execPath
121
+ const child = spawn(spawnCmd, [scriptPath, 'openclaw-bridge', '--port', String(bridgePort)], {
122
+ detached: true,
123
+ stdio: 'ignore',
124
+ windowsHide: true,
125
+ })
126
+ child.unref()
127
+
128
+ const bridgeReady = waitForPort(() => checkBridgeHealth(bridgePort), 10, 1000)
129
+ if (bridgeReady) {
130
+ console.log(chalk.green('✓'))
131
+ } else {
132
+ console.log(chalk.red('✗'))
133
+ console.log(chalk.red(` Bridge 启动失败,请手动运行: hs openclaw-bridge --port ${bridgePort}`))
134
+ process.exit(1)
135
+ }
136
+
137
+ // 确保 Gateway 运行
138
+ if (checkGatewayHealth(gatewayPort)) {
139
+ console.log(chalk.green(`✓ Gateway 已运行 (端口 ${gatewayPort})`))
140
+ } else {
141
+ process.stdout.write(chalk.gray(` 正在启动 Gateway (端口 ${gatewayPort})... `))
142
+
143
+ const preferNpx = runtime.via === 'npx'
144
+ const gatewayStartArgs = preferNpx ? ['openclaw', 'gateway', 'start'] : ['gateway', 'start']
145
+ const gatewayStartCmd = preferNpx ? 'npx' : 'openclaw'
146
+ const serviceResult = spawnSync(gatewayStartCmd, gatewayStartArgs, {
147
+ stdio: 'ignore',
148
+ timeout: 10000,
149
+ shell: isWin,
150
+ })
151
+
152
+ if (serviceResult.status !== 0) {
153
+ const directArgs = preferNpx
154
+ ? ['openclaw', 'gateway', '--port', String(gatewayPort)]
155
+ : ['gateway', '--port', String(gatewayPort)]
156
+ const directCmd = preferNpx ? 'npx' : 'openclaw'
157
+ const gatewayChild = spawn(directCmd, directArgs, {
158
+ detached: true,
159
+ stdio: 'ignore',
160
+ shell: isWin,
161
+ windowsHide: true,
162
+ })
163
+ gatewayChild.unref()
164
+ }
165
+
166
+ const gatewayReady = waitForPort(() => checkGatewayHealth(gatewayPort), 12, 1500)
167
+ if (gatewayReady) {
168
+ console.log(chalk.green('✓'))
169
+ } else {
170
+ console.log(chalk.yellow('⚠'))
171
+ console.log(chalk.yellow(' Gateway 未就绪,仍将尝试打开 Dashboard'))
172
+ }
173
+ }
174
+
175
+ // 获取 Dashboard URL
176
+ const fallbackUrl = `http://127.0.0.1:${gatewayPort}/`
177
+ let dashUrl = fallbackUrl
178
+ try {
179
+ const preferNpx = runtime.via === 'npx'
180
+ const dashArgs = preferNpx ? ['openclaw', 'dashboard', '--no-open'] : ['dashboard', '--no-open']
181
+ const dashCmd = preferNpx ? 'npx' : 'openclaw'
182
+ const result = spawnSync(dashCmd, dashArgs, {
183
+ timeout: preferNpx ? 60000 : 20000,
184
+ shell: isWin,
185
+ encoding: 'utf8',
186
+ stdio: 'pipe',
187
+ })
188
+ if (result.status === 0) {
189
+ const output = String(result.stdout || '')
190
+ const match = output.match(/Dashboard URL:\s*(\S+)/) || output.match(/(https?:\/\/\S+)/)
191
+ if (match) dashUrl = match[1]
192
+ }
193
+ } catch {}
194
+
195
+ console.log()
196
+ console.log(chalk.cyan.bold(`🌐 正在打开 OpenClaw Dashboard: ${dashUrl}`))
197
+ openBrowser(dashUrl)
198
+ console.log(chalk.gray(` Bridge: http://127.0.0.1:${bridgePort}/v1`))
199
+ console.log(chalk.gray(` Gateway: http://127.0.0.1:${gatewayPort}/`))
200
+ console.log()
201
+ }
202
+
203
+ module.exports = openclaw
package/src/index.js CHANGED
@@ -149,6 +149,14 @@ program
149
149
  })
150
150
  })
151
151
 
152
+ // ── openclaw ─────────────────────────────────────────────────────────────────
153
+ program
154
+ .command('openclaw')
155
+ .description('启动 HolySheep 桥接版 OpenClaw(自动启动 Bridge + Gateway 并打开 Dashboard)')
156
+ .action(async () => {
157
+ await require('./commands/openclaw')()
158
+ })
159
+
152
160
  // ── openclaw-bridge ──────────────────────────────────────────────────────────
153
161
  program
154
162
  .command('openclaw-bridge')