@simonyea/holysheep-cli 1.7.12 → 1.7.13
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 +7 -2
- package/package.json +1 -1
- package/src/commands/doctor.js +28 -2
- package/src/commands/setup.js +3 -0
- package/src/tools/openclaw.js +89 -7
package/README.md
CHANGED
|
@@ -70,11 +70,12 @@ You'll be prompted for your API Key (`cr_xxx`), then select the tools to configu
|
|
|
70
70
|
1. HolySheep configures OpenClaw to use HolySheep API
|
|
71
71
|
2. The OpenClaw Gateway starts on **`http://127.0.0.1:18789/` by default**
|
|
72
72
|
3. If `18789` is occupied, `hs setup` automatically picks the next available local port
|
|
73
|
-
4. Open the exact
|
|
73
|
+
4. Open the exact dashboard URL shown in the terminal and start chatting — do not use a blank browser shell or `about:blank`
|
|
74
74
|
|
|
75
75
|
**Default OpenClaw model:** `gpt-5.4`
|
|
76
76
|
|
|
77
77
|
> **Keep the gateway window open** while using OpenClaw. The gateway must be running for the browser UI to work.
|
|
78
|
+
> If only `npx openclaw` is available, HolySheep will start Gateway as a direct process and will not install a persistent daemon from a temporary `npx` cache path.
|
|
78
79
|
|
|
79
80
|
> **OpenClaw itself requires Node.js 20+**. If setup fails, first check `node --version`.
|
|
80
81
|
|
|
@@ -157,11 +158,12 @@ hs setup
|
|
|
157
158
|
1. HolySheep 会自动把 OpenClaw 接到 HolySheep API
|
|
158
159
|
2. 默认启动在 **`http://127.0.0.1:18789/`**
|
|
159
160
|
3. 如果 `18789` 被占用,`hs setup` 会自动切换到下一个可用本地端口
|
|
160
|
-
4.
|
|
161
|
+
4. 按终端里显示的准确 dashboard 地址打开浏览器,直接开始聊天,不要打开空白浏览器壳窗口或 `about:blank`
|
|
161
162
|
|
|
162
163
|
**OpenClaw 默认模型:** `gpt-5.4`
|
|
163
164
|
|
|
164
165
|
> ⚠️ **保持 Gateway 窗口开启**,关闭后 Gateway 停止,浏览器界面无法使用。
|
|
166
|
+
> 如果机器上只有 `npx openclaw`,HolySheep 会直接启动 Gateway 进程,不会把 daemon 安装到临时 `npx` 缓存路径上。
|
|
165
167
|
|
|
166
168
|
> ⚠️ **OpenClaw 自身要求 Node.js 20+**。如果配置失败,请先运行 `node --version` 检查版本。
|
|
167
169
|
|
|
@@ -205,6 +207,9 @@ A: 支持,需要 Node.js 16+。如果 `hs` 命令找不到,请重启终端
|
|
|
205
207
|
**Q: OpenClaw Gateway 窗口可以最小化吗?**
|
|
206
208
|
A: 可以最小化,但不能关闭。关闭后 Gateway 停止,需要按 `hs setup` / `hs doctor` 显示的端口重新运行 `openclaw gateway --port <端口>` 或 `npx openclaw gateway --port <端口>`。
|
|
207
209
|
|
|
210
|
+
**Q: 为什么浏览器会只剩黑屏 / 空白窗口?**
|
|
211
|
+
A: 最常见原因是 Gateway 实际没有启动成功,或者你打开了空白浏览器壳窗口而不是终端输出的 dashboard URL。先运行 `hs doctor` 检查 `gateway.port` 是否正在监听,再打开终端里输出的准确 dashboard 地址。
|
|
212
|
+
|
|
208
213
|
**Q: 18789 端口被占用怎么办?**
|
|
209
214
|
A: `hs setup` 会自动切换到下一个可用本地端口,并把准确访问地址打印出来;也可以运行 `hs doctor` 查看当前 `gateway.port` 和端口占用情况。
|
|
210
215
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.13",
|
|
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",
|
package/src/commands/doctor.js
CHANGED
|
@@ -99,9 +99,14 @@ function printCheck(ok, label, detail = '') {
|
|
|
99
99
|
function printOpenClawDetails(tool, installState, nodeMajor) {
|
|
100
100
|
const details = []
|
|
101
101
|
const gatewayPort = typeof tool.getGatewayPort === 'function' ? tool.getGatewayPort() : 18789
|
|
102
|
+
const bridgePort = typeof tool.getBridgePort === 'function' ? tool.getBridgePort() : null
|
|
102
103
|
const primaryModel = typeof tool.getPrimaryModel === 'function' ? tool.getPrimaryModel() : ''
|
|
103
104
|
const listeners = typeof tool.getPortListeners === 'function' ? tool.getPortListeners(gatewayPort) : []
|
|
104
105
|
const foreignListeners = listeners.filter((item) => !String(item.command || '').toLowerCase().includes('openclaw'))
|
|
106
|
+
const launchAgent = typeof tool.getLaunchAgentDiagnosis === 'function' ? tool.getLaunchAgentDiagnosis() : null
|
|
107
|
+
const launchHint = Array.isArray(tool.launchSteps) && tool.launchSteps.length > 1
|
|
108
|
+
? tool.launchSteps[1].cmd
|
|
109
|
+
: tool.launchCmd || 'npx openclaw gateway --port <port>'
|
|
105
110
|
|
|
106
111
|
if (installState.detail === 'npx fallback') {
|
|
107
112
|
details.push({
|
|
@@ -123,6 +128,25 @@ function printOpenClawDetails(tool, installState, nodeMajor) {
|
|
|
123
128
|
})
|
|
124
129
|
}
|
|
125
130
|
|
|
131
|
+
if (bridgePort) {
|
|
132
|
+
details.push({
|
|
133
|
+
level: 'info',
|
|
134
|
+
text: `Bridge 端口:${bridgePort}`,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (launchAgent?.unstable) {
|
|
139
|
+
details.push({
|
|
140
|
+
level: 'warn',
|
|
141
|
+
text: `检测到失效的 LaunchAgent,引用了临时 npx 缓存路径:${launchAgent.unstableArg}`,
|
|
142
|
+
})
|
|
143
|
+
} else if (launchAgent?.exists) {
|
|
144
|
+
details.push({
|
|
145
|
+
level: 'ok',
|
|
146
|
+
text: `LaunchAgent 配置存在:${launchAgent.path}`,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
126
150
|
if (foreignListeners.length) {
|
|
127
151
|
const occupiedBy = foreignListeners
|
|
128
152
|
.slice(0, 2)
|
|
@@ -139,8 +163,10 @@ function printOpenClawDetails(tool, installState, nodeMajor) {
|
|
|
139
163
|
})
|
|
140
164
|
} else {
|
|
141
165
|
details.push({
|
|
142
|
-
level: 'info',
|
|
143
|
-
text:
|
|
166
|
+
level: launchAgent?.unstable ? 'warn' : 'info',
|
|
167
|
+
text: launchAgent?.unstable
|
|
168
|
+
? `Gateway 端口 ${gatewayPort} 当前未监听;这通常会导致浏览器只剩空白/黑屏窗口`
|
|
169
|
+
: `Gateway 端口 ${gatewayPort} 当前空闲;如刚完成配置,可运行 ${launchHint}`,
|
|
144
170
|
})
|
|
145
171
|
}
|
|
146
172
|
|
package/src/commands/setup.js
CHANGED
|
@@ -378,6 +378,9 @@ async function setup(options) {
|
|
|
378
378
|
const hot = r.result?.hot ? chalk.cyan(' (热切换,无需重启)') : chalk.gray(' (重启终端生效)')
|
|
379
379
|
console.log(` ✓ ${r.tool.name}${hot}`)
|
|
380
380
|
if (r.tool.hint) console.log(` ${chalk.gray('💡 ' + r.tool.hint)}`)
|
|
381
|
+
if (r.result?.dashboardUrl) {
|
|
382
|
+
console.log(` ${chalk.gray('🌐 Dashboard:')} ${chalk.cyan.bold(r.result.dashboardUrl)}`)
|
|
383
|
+
}
|
|
381
384
|
// 显示启动命令
|
|
382
385
|
if (r.tool.launchSteps) {
|
|
383
386
|
// 多步骤启动(如 openclaw)
|
package/src/tools/openclaw.js
CHANGED
|
@@ -13,6 +13,8 @@ const { BRIDGE_CONFIG_FILE } = require('./openclaw-bridge')
|
|
|
13
13
|
|
|
14
14
|
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
|
|
15
15
|
const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
|
|
16
|
+
const OPENCLAW_LAUNCH_AGENTS_DIR = path.join(os.homedir(), 'Library', 'LaunchAgents')
|
|
17
|
+
const OPENCLAW_GATEWAY_PLIST = path.join(OPENCLAW_LAUNCH_AGENTS_DIR, 'ai.openclaw.gateway.plist')
|
|
16
18
|
const isWin = process.platform === 'win32'
|
|
17
19
|
const DEFAULT_BRIDGE_PORT = 18788
|
|
18
20
|
const DEFAULT_GATEWAY_PORT = 18789
|
|
@@ -299,6 +301,68 @@ function getDashboardCommand() {
|
|
|
299
301
|
return `${runtime} dashboard --no-open`
|
|
300
302
|
}
|
|
301
303
|
|
|
304
|
+
function getDashboardUrlForPort(port) {
|
|
305
|
+
return `http://127.0.0.1:${port}/`
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function isNpxCachePath(value) {
|
|
309
|
+
return /[\\/]_npx[\\/]/i.test(String(value || ''))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function readOpenClawLaunchAgent() {
|
|
313
|
+
if (process.platform !== 'darwin') return null
|
|
314
|
+
try {
|
|
315
|
+
if (!fs.existsSync(OPENCLAW_GATEWAY_PLIST)) return null
|
|
316
|
+
return fs.readFileSync(OPENCLAW_GATEWAY_PLIST, 'utf8')
|
|
317
|
+
} catch {
|
|
318
|
+
return null
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function parseLaunchAgentProgramArguments(content) {
|
|
323
|
+
const match = String(content || '').match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/i)
|
|
324
|
+
if (!match) return []
|
|
325
|
+
|
|
326
|
+
return Array.from(match[1].matchAll(/<string>([\s\S]*?)<\/string>/g)).map((item) => item[1])
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getLaunchAgentDiagnosis() {
|
|
330
|
+
const content = readOpenClawLaunchAgent()
|
|
331
|
+
if (!content) {
|
|
332
|
+
return { exists: false, path: OPENCLAW_GATEWAY_PLIST, unstable: false, programArguments: [] }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const programArguments = parseLaunchAgentProgramArguments(content)
|
|
336
|
+
const unstableArg = programArguments.find(isNpxCachePath) || ''
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
exists: true,
|
|
340
|
+
path: OPENCLAW_GATEWAY_PLIST,
|
|
341
|
+
unstable: Boolean(unstableArg),
|
|
342
|
+
unstableArg,
|
|
343
|
+
programArguments,
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function removeBrokenLaunchAgent() {
|
|
348
|
+
const diagnosis = getLaunchAgentDiagnosis()
|
|
349
|
+
if (!diagnosis.exists || !diagnosis.unstable) return false
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
execSync(`launchctl bootout "gui/${process.getuid()}" "${diagnosis.path}"`, {
|
|
353
|
+
shell: true,
|
|
354
|
+
stdio: 'ignore',
|
|
355
|
+
})
|
|
356
|
+
} catch {}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
fs.unlinkSync(diagnosis.path)
|
|
360
|
+
return true
|
|
361
|
+
} catch {
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
302
366
|
function buildModelEntry(id) {
|
|
303
367
|
return {
|
|
304
368
|
id,
|
|
@@ -417,6 +481,8 @@ function _disableGatewayAuth(preferNpx = false) {
|
|
|
417
481
|
}
|
|
418
482
|
|
|
419
483
|
function _installGatewayService(port, preferNpx = false) {
|
|
484
|
+
if (preferNpx) return false
|
|
485
|
+
|
|
420
486
|
const result = runOpenClaw(['gateway', 'install', '--force', '--port', String(port)], {
|
|
421
487
|
preferNpx,
|
|
422
488
|
timeout: 60000,
|
|
@@ -466,7 +532,7 @@ function getDashboardUrl(port, preferNpx = false) {
|
|
|
466
532
|
const match = output.match(/Dashboard URL:\s*(\S+)/) || output.match(/(https?:\/\/\S+)/)
|
|
467
533
|
if (match) return match[1]
|
|
468
534
|
}
|
|
469
|
-
return
|
|
535
|
+
return getDashboardUrlForPort(port)
|
|
470
536
|
}
|
|
471
537
|
|
|
472
538
|
module.exports = {
|
|
@@ -499,6 +565,11 @@ module.exports = {
|
|
|
499
565
|
throw new Error('未检测到 OpenClaw;请先全局安装,或确保 npx 可用')
|
|
500
566
|
}
|
|
501
567
|
this._lastRuntimeCommand = runtime.command
|
|
568
|
+
this._lastRuntimeVia = runtime.via
|
|
569
|
+
|
|
570
|
+
if (runtime.via === 'npx' && removeBrokenLaunchAgent()) {
|
|
571
|
+
console.log(chalk.gray(' → 已清理旧的 OpenClaw 守护进程配置(失效的 npx 缓存路径)'))
|
|
572
|
+
}
|
|
502
573
|
|
|
503
574
|
const resolvedPrimaryModel = pickPrimaryModel(primaryModel, selectedModels)
|
|
504
575
|
const bridgePort = findAvailableGatewayPort(DEFAULT_BRIDGE_PORT)
|
|
@@ -544,7 +615,7 @@ module.exports = {
|
|
|
544
615
|
try { fs.unlinkSync(CONFIG_FILE) } catch {}
|
|
545
616
|
|
|
546
617
|
console.log(chalk.gray(' → 写入配置...'))
|
|
547
|
-
const
|
|
618
|
+
const onboardArgs = [
|
|
548
619
|
'onboard',
|
|
549
620
|
'--non-interactive',
|
|
550
621
|
'--accept-risk',
|
|
@@ -554,8 +625,10 @@ module.exports = {
|
|
|
554
625
|
'--custom-model-id', resolvedPrimaryModel,
|
|
555
626
|
'--custom-compatibility', 'openai',
|
|
556
627
|
'--gateway-port', String(gatewayPort),
|
|
557
|
-
|
|
558
|
-
|
|
628
|
+
]
|
|
629
|
+
if (runtime.via !== 'npx') onboardArgs.push('--install-daemon')
|
|
630
|
+
|
|
631
|
+
const result = runOpenClaw(onboardArgs, { preferNpx: runtime.via === 'npx' })
|
|
559
632
|
|
|
560
633
|
if (result.status !== 0) {
|
|
561
634
|
console.log(chalk.yellow(' ⚠️ onboard 失败,使用备用配置...'))
|
|
@@ -571,6 +644,9 @@ module.exports = {
|
|
|
571
644
|
|
|
572
645
|
_disableGatewayAuth(runtime.via === 'npx')
|
|
573
646
|
const serviceReady = _installGatewayService(gatewayPort, runtime.via === 'npx')
|
|
647
|
+
if (runtime.via === 'npx') {
|
|
648
|
+
console.log(chalk.gray(' → 当前仅检测到 npx openclaw,跳过 daemon 安装,改为直接启动 Gateway 进程'))
|
|
649
|
+
}
|
|
574
650
|
|
|
575
651
|
console.log(chalk.gray(' → 正在启动 Gateway...'))
|
|
576
652
|
const ok = _startGateway(gatewayPort, runtime.via === 'npx', serviceReady)
|
|
@@ -578,14 +654,16 @@ module.exports = {
|
|
|
578
654
|
if (ok) {
|
|
579
655
|
console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
|
|
580
656
|
} else {
|
|
581
|
-
console.log(chalk.yellow(' ⚠️ Gateway
|
|
657
|
+
console.log(chalk.yellow(' ⚠️ Gateway 未就绪;当前不要打开 about:blank 或裸浏览器壳窗口'))
|
|
582
658
|
}
|
|
583
659
|
|
|
584
|
-
const dashUrl = getDashboardUrl(gatewayPort, runtime.via === 'npx')
|
|
660
|
+
const dashUrl = ok ? getDashboardUrl(gatewayPort, runtime.via === 'npx') : getDashboardUrlForPort(gatewayPort)
|
|
585
661
|
console.log(chalk.cyan('\n → 浏览器打开(推荐使用此地址):'))
|
|
586
662
|
console.log(chalk.bold.cyan(` ${dashUrl}`))
|
|
587
663
|
console.log(chalk.gray(` Bridge 地址: ${bridgeBaseUrl}`))
|
|
588
664
|
console.log(chalk.gray(` 默认模型: ${plan.primaryRef || OPENCLAW_DEFAULT_MODEL}`))
|
|
665
|
+
console.log(chalk.gray(` Gateway 启动方式: ${serviceReady ? 'daemon' : 'direct process'}`))
|
|
666
|
+
console.log(chalk.gray(' 浏览器应直接打开 dashboard URL,不应停在 about:blank'))
|
|
589
667
|
console.log(chalk.gray(' 如在 Windows 上打开裸 http://127.0.0.1:PORT/ 仍报 Unauthorized,请使用上面的 dashboard 地址'))
|
|
590
668
|
|
|
591
669
|
return {
|
|
@@ -593,6 +671,8 @@ module.exports = {
|
|
|
593
671
|
hot: false,
|
|
594
672
|
dashboardUrl: dashUrl,
|
|
595
673
|
gatewayPort,
|
|
674
|
+
gatewayReady: ok,
|
|
675
|
+
gatewayLaunchMode: serviceReady ? 'daemon' : 'direct-process',
|
|
596
676
|
launchCmd: getLaunchCommand(gatewayPort),
|
|
597
677
|
}
|
|
598
678
|
},
|
|
@@ -607,6 +687,7 @@ module.exports = {
|
|
|
607
687
|
getGatewayPort() { return getConfiguredGatewayPort() },
|
|
608
688
|
getPrimaryModel() { return getConfiguredPrimaryModel() },
|
|
609
689
|
getPortListeners(port = getConfiguredGatewayPort()) { return listPortListeners(port) },
|
|
690
|
+
getLaunchAgentDiagnosis,
|
|
610
691
|
get hint() {
|
|
611
692
|
return `Bridge + Gateway 已配置,默认模型为 ${getConfiguredPrimaryModel() || OPENCLAW_DEFAULT_MODEL}`
|
|
612
693
|
},
|
|
@@ -620,7 +701,8 @@ module.exports = {
|
|
|
620
701
|
]
|
|
621
702
|
},
|
|
622
703
|
get launchNote() {
|
|
623
|
-
|
|
704
|
+
const runtime = module.exports._lastRuntimeVia === 'npx' ? '当前为 npx 模式,不安装常驻 daemon。' : ''
|
|
705
|
+
return `🌐 请先启动 Bridge,再启动 Gateway;最后运行 ${getDashboardCommand()} ${runtime}`.trim()
|
|
624
706
|
},
|
|
625
707
|
installCmd: 'npm install -g openclaw@latest',
|
|
626
708
|
docsUrl: 'https://docs.openclaw.ai',
|