@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 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 browser URL shown in the terminal and start chatting — no token required
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. 按终端里显示的准确地址打开浏览器,直接开始聊天,无需填写 token
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.12",
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",
@@ -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: `Gateway 端口 ${gatewayPort} 当前空闲;如刚完成配置,可运行 ${tool.launchCmd}`,
166
+ level: launchAgent?.unstable ? 'warn' : 'info',
167
+ text: launchAgent?.unstable
168
+ ? `Gateway 端口 ${gatewayPort} 当前未监听;这通常会导致浏览器只剩空白/黑屏窗口`
169
+ : `Gateway 端口 ${gatewayPort} 当前空闲;如刚完成配置,可运行 ${launchHint}`,
144
170
  })
145
171
  }
146
172
 
@@ -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)
@@ -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 `http://127.0.0.1:${port}/`
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 result = runOpenClaw([
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
- '--install-daemon',
558
- ], { preferNpx: runtime.via === 'npx' })
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
- return `🌐 请先启动 Bridge,再启动 Gateway;最后运行 ${getDashboardCommand()}`
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',