@simonyea/holysheep-cli 1.3.0 → 1.3.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools/openclaw.js +88 -128
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "一键配置所有 AI 编程工具接入 HolySheep API — Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw / Aider / Cursor",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,45 +1,26 @@
1
1
  /**
2
2
  * OpenClaw 适配器
3
3
  *
4
- * 策略:完全通过 openclaw CLI 官方命令完成配置,不手写 JSON。
4
+ * 策略:直接写正确格式的 openclaw.json,然后 doctor --fix 迁移,
5
+ * 最后用 wt/cmd 新窗口运行 gateway(Windows)或 detach(Unix)。
5
6
  *
6
- * 核心命令:
7
- * openclaw onboard \
8
- * --non-interactive \
9
- * --auth-choice custom-api-key \
10
- * --custom-base-url https://api.holysheep.ai \
11
- * --custom-api-key <key> \
12
- * --custom-model-id claude-sonnet-4-6 \
13
- * --custom-compatibility anthropic \
14
- * --install-daemon
15
- *
16
- * 文档: https://docs.openclaw.ai/start/wizard-cli-reference
7
+ * 文档: https://docs.openclaw.ai
17
8
  */
18
9
  const fs = require('fs')
19
10
  const path = require('path')
20
11
  const os = require('os')
21
- const { spawnSync } = require('child_process')
12
+ const crypto = require('crypto')
13
+ const { spawnSync, spawn, execSync } = require('child_process')
22
14
 
23
15
  const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
24
16
  const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
17
+ const isWin = process.platform === 'win32'
25
18
 
26
- /** Windows PATH 未刷新时用 npx,其他直接用 openclaw */
27
- function bin(...args) {
28
- const isWin = process.platform === 'win32'
29
- if (isWin) {
30
- return { cmd: 'npx', args: ['openclaw', ...args] }
31
- }
32
- return { cmd: 'openclaw', args }
33
- }
34
-
35
- function run(args, opts = {}) {
36
- const { cmd, args: fullArgs } = bin(...args)
37
- return spawnSync(cmd, fullArgs, {
38
- shell: true,
39
- timeout: opts.timeout || 30000,
40
- stdio: opts.stdio || 'pipe',
41
- env: { ...process.env, ...(opts.env || {}) },
42
- })
19
+ function npx(...args) {
20
+ // Windows 下始终用 npx 以绕过 PATH 问题
21
+ return isWin
22
+ ? spawnSync('npx', ['openclaw', ...args], { shell: true, timeout: 30000, stdio: 'pipe' })
23
+ : spawnSync('openclaw', args, { shell: false, timeout: 30000, stdio: 'pipe' })
43
24
  }
44
25
 
45
26
  function readConfig() {
@@ -52,20 +33,50 @@ function readConfig() {
52
33
  return {}
53
34
  }
54
35
 
36
+ /** 写入正确格式的 openclaw.json */
37
+ function writeCorrectConfig(apiKey, baseUrl) {
38
+ fs.mkdirSync(OPENCLAW_DIR, { recursive: true })
39
+
40
+ const token = crypto.randomBytes(24).toString('hex')
41
+
42
+ // 完全正确的格式,基于 openclaw 2026.3.x 实测
43
+ const config = {
44
+ agents: {
45
+ defaults: {
46
+ model: { primary: 'custom/claude-sonnet-4-6' }
47
+ }
48
+ },
49
+ gateway: {
50
+ port: 18789,
51
+ bind: 'loopback', // 新格式,不用 "127.0.0.1"
52
+ auth: {
53
+ mode: 'token',
54
+ token,
55
+ }
56
+ },
57
+ // Custom provider — OpenAI-compatible
58
+ providers: {
59
+ custom: {
60
+ baseUrl, // https://api.holysheep.ai
61
+ apiKey,
62
+ compatibility: 'anthropic',
63
+ }
64
+ }
65
+ }
66
+
67
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8')
68
+ return token
69
+ }
70
+
55
71
  module.exports = {
56
72
  name: 'OpenClaw',
57
73
  id: 'openclaw',
58
74
 
59
75
  checkInstalled() {
60
- // 先检测命令是否在 PATH 里
61
76
  if (require('../utils/which').commandExists('openclaw')) return true
62
- // Windows PATH 未刷新时,npx 探测
63
- if (process.platform === 'win32') {
77
+ if (isWin) {
64
78
  try {
65
- require('child_process').execSync(
66
- 'npx --yes openclaw --version',
67
- { stdio: 'ignore', timeout: 15000, shell: true }
68
- )
79
+ execSync('npx openclaw --version', { stdio: 'ignore', timeout: 15000, shell: true })
69
80
  return true
70
81
  } catch {}
71
82
  }
@@ -73,81 +84,45 @@ module.exports = {
73
84
  },
74
85
 
75
86
  isConfigured() {
76
- const c = readConfig()
77
- // 检查是否已有 holysheep custom provider 配置
78
- const cfg = JSON.stringify(c)
79
- return cfg.includes('holysheep.ai') || cfg.includes('holysheep')
87
+ const cfg = JSON.stringify(readConfig())
88
+ return cfg.includes('holysheep.ai')
80
89
  },
81
90
 
82
91
  configure(apiKey, baseUrlAnthropicNoV1) {
83
92
  const chalk = require('chalk')
93
+ console.log(chalk.gray('\n ⚙️ 正在配置 OpenClaw...'))
84
94
 
85
- console.log(chalk.gray('\n ⚙️ 正在通过 OpenClaw 官方向导配置(约 30 秒)...'))
95
+ // 1. 写入正确格式配置
96
+ writeCorrectConfig(apiKey, baseUrlAnthropicNoV1)
86
97
 
87
- // Step 1: 如果配置文件存在且有问题,先 doctor --fix
88
- if (fs.existsSync(CONFIG_FILE)) {
89
- run(['doctor', '--fix'], { stdio: 'ignore', timeout: 15000 })
90
- }
91
-
92
- // Step 2: 用官方 onboard 非交互式命令完成配置 + 安装系统服务
93
- const r = run([
94
- 'onboard',
95
- '--non-interactive',
96
- '--auth-choice', 'custom-api-key',
97
- '--custom-base-url', baseUrlAnthropicNoV1, // https://api.holysheep.ai
98
- '--custom-api-key', apiKey,
99
- '--custom-model-id', 'claude-sonnet-4-6',
100
- '--custom-compatibility', 'anthropic',
101
- '--install-daemon',
102
- ], {
103
- timeout: 90000,
104
- stdio: 'pipe',
105
- env: {
106
- CUSTOM_API_KEY: apiKey,
107
- ANTHROPIC_API_KEY: apiKey,
108
- ANTHROPIC_BASE_URL: baseUrlAnthropicNoV1,
109
- },
110
- })
98
+ // 2. doctor --fix 修复任何兼容性问题
99
+ npx('doctor', '--fix')
111
100
 
112
- const stdout = r.stdout?.toString() || ''
113
- const stderr = r.stderr?.toString() || ''
101
+ // 3. 启动 Gateway
102
+ console.log(chalk.gray(' → 正在启动 Gateway...'))
103
+ const ok = _startGateway()
114
104
 
115
- if (r.status === 0) {
116
- console.log(chalk.green(' ✓ OpenClaw 配置完成,Gateway 已在后台启动'))
105
+ if (ok) {
106
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
117
107
  console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
118
- return { file: CONFIG_FILE, hot: false }
119
- }
120
-
121
- // onboard 失败,尝试直接启动 gateway
122
- console.log(chalk.gray(' → 配置完成,正在启动 Gateway...'))
123
- const started = _startGateway()
124
-
125
- if (!started) {
126
- // 给用户明确的手动命令
127
- console.log(chalk.yellow('\n ⚠️ Gateway 需要手动启动,运行以下命令:'))
128
- if (process.platform === 'win32') {
129
- console.log(chalk.cyan(' npx openclaw gateway install'))
130
- console.log(chalk.cyan(' npx openclaw gateway start'))
131
- } else {
132
- console.log(chalk.cyan(' openclaw gateway install'))
133
- console.log(chalk.cyan(' openclaw gateway start'))
134
- }
108
+ } else {
109
+ console.log(chalk.yellow(' ⚠️ 请手动启动 Gateway:'))
110
+ console.log(chalk.cyan(isWin ? ' npx openclaw gateway' : ' openclaw gateway'))
135
111
  }
136
112
 
137
113
  return { file: CONFIG_FILE, hot: false }
138
114
  },
139
115
 
140
116
  reset() {
141
- run(['doctor', '--reset'], { stdio: 'ignore', timeout: 15000 })
117
+ try { fs.unlinkSync(CONFIG_FILE) } catch {}
142
118
  },
143
119
 
144
120
  getConfigPath() { return CONFIG_FILE },
145
- hint: 'Gateway 已自动启动,打开浏览器即可使用',
146
- launchCmd: null,
121
+ hint: 'Gateway 已启动,打开浏览器即可使用',
122
+ launchCmd: null,
147
123
  get launchNote() {
148
- const isWin = process.platform === 'win32'
149
124
  return isWin
150
- ? '🌐 打开浏览器: http://127.0.0.1:18789/\n 如无法访问: npx openclaw gateway start'
125
+ ? '🌐 打开浏览器: http://127.0.0.1:18789/\n 如无法访问: npx openclaw gateway'
151
126
  : '🌐 打开浏览器: http://127.0.0.1:18789/'
152
127
  },
153
128
  installCmd: 'npm install -g openclaw@latest',
@@ -155,52 +130,37 @@ module.exports = {
155
130
  }
156
131
 
157
132
  /**
158
- * 尝试启动 Gateway,返回是否成功
133
+ * 启动 Gateway 后台进程
134
+ * Windows: PowerShell Start-Process 开新的隐藏 PowerShell 窗口
135
+ * Unix: detached child process
159
136
  */
160
137
  function _startGateway() {
161
- const chalk = require('chalk')
162
- const isWin = process.platform === 'win32'
163
-
164
- // 先 install,再 start
165
- run(['gateway', 'install'], { stdio: 'ignore', timeout: 20000 })
166
-
167
- const r = run(['gateway', 'start'], { timeout: 10000 })
168
- if (r.status === 0) {
169
- console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
170
- console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
171
- return true
172
- }
173
-
174
- // fallback: 直接运行 gateway 进程(Windows 用 Start-Process 后台)
175
138
  if (isWin) {
139
+ // 用 Start-Process 开一个后台 powershell 窗口运行 npx openclaw gateway
176
140
  spawnSync('powershell', [
177
141
  '-NonInteractive', '-Command',
178
- `Start-Process -FilePath "npx" -ArgumentList @("openclaw","gateway","--port","18789") -WindowStyle Hidden`
179
- ], { shell: false, timeout: 5000, stdio: 'ignore' })
142
+ `Start-Process -FilePath powershell -ArgumentList @('-NonInteractive','-WindowStyle','Hidden','-Command','npx openclaw gateway --port 18789') -WindowStyle Hidden -PassThru | Out-Null`
143
+ ], { shell: false, timeout: 10000, stdio: 'ignore' })
180
144
  } else {
181
- const { spawn } = require('child_process')
182
145
  const child = spawn('openclaw', ['gateway', '--port', '18789'], {
183
- detached: true, stdio: 'ignore',
146
+ detached: true,
147
+ stdio: 'ignore',
184
148
  })
185
149
  child.unref()
186
150
  }
187
151
 
188
- // 5 秒,验证是否真的起来
189
- const deadline = Date.now() + 5000
190
- while (Date.now() < deadline) {}
191
-
192
- try {
193
- const { execSync } = require('child_process')
194
- execSync(
195
- isWin
196
- ? 'powershell -NonInteractive -Command "(Invoke-WebRequest -Uri http://127.0.0.1:18789/ -TimeoutSec 3 -UseBasicParsing).StatusCode"'
197
- : 'curl -sf http://127.0.0.1:18789/ -o /dev/null --max-time 3',
198
- { stdio: 'ignore', timeout: 5000 }
199
- )
200
- console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
201
- console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
202
- return true
203
- } catch {
204
- return false
152
+ // 等待 gateway 启动(最多 8 秒)
153
+ for (let i = 0; i < 8; i++) {
154
+ const t = Date.now(); while (Date.now() - t < 1000) {}
155
+ try {
156
+ execSync(
157
+ isWin
158
+ ? 'powershell -NonInteractive -Command "try { (Invoke-WebRequest -Uri http://127.0.0.1:18789/ -TimeoutSec 1 -UseBasicParsing).StatusCode } catch { exit 1 }"'
159
+ : 'curl -sf http://127.0.0.1:18789/ -o /dev/null --max-time 1',
160
+ { stdio: 'ignore', timeout: 3000 }
161
+ )
162
+ return true // 成功响应
163
+ } catch {}
205
164
  }
165
+ return false
206
166
  }