@simonyea/holysheep-cli 1.2.9 → 1.3.0

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 +117 -196
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "description": "一键配置所有 AI 编程工具接入 HolySheep API — Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw / Aider / Cursor",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,128 +1,71 @@
1
1
  /**
2
- * OpenClaw 适配器 (github.com/openclaw/openclaw)
2
+ * OpenClaw 适配器
3
3
  *
4
- * OpenClaw 是个人 AI 助手 + 多渠道消息网关
5
- * 支持 WhatsApp/Telegram/Signal/Discord/iMessage 等 20+ 渠道
4
+ * 策略:完全通过 openclaw CLI 官方命令完成配置,不手写 JSON。
6
5
  *
7
- * 安装方式: npm install -g openclaw@latest
8
- * 配置文件: ~/.openclaw/openclaw.json (JSON5 格式)
9
- * 文档: https://docs.openclaw.ai
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
10
15
  *
11
- * HolySheep 接入方式:
12
- * 1. 直接写入完整的 openclaw.json(含 auth profile + gateway + 默认模型)
13
- * 2. 自动启动 Gateway,用户直接打开 http://127.0.0.1:18789/
14
- * 不需要用户手动跑 onboard / gateway start
16
+ * 文档: https://docs.openclaw.ai/start/wizard-cli-reference
15
17
  */
16
18
  const fs = require('fs')
17
19
  const path = require('path')
18
20
  const os = require('os')
19
- const crypto = require('crypto')
20
21
  const { spawnSync } = require('child_process')
21
22
 
22
- const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
23
- const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
23
+ const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
24
+ const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
25
+
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
+ })
43
+ }
24
44
 
25
45
  function readConfig() {
26
46
  try {
27
47
  if (fs.existsSync(CONFIG_FILE)) {
28
48
  const raw = fs.readFileSync(CONFIG_FILE, 'utf8')
29
- // openclaw.json 是 JSON5 格式,先去掉注释再 parse
30
49
  return JSON.parse(raw.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, ''))
31
50
  }
32
51
  } catch {}
33
52
  return {}
34
53
  }
35
54
 
36
- function writeConfig(data) {
37
- fs.mkdirSync(OPENCLAW_DIR, { recursive: true })
38
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8')
39
- }
40
-
41
- /** 生成随机 Gateway token */
42
- function genToken() {
43
- return crypto.randomBytes(24).toString('hex')
44
- }
45
-
46
- /**
47
- * 构建完整的初始化配置
48
- * 包含:auth profile(HolySheep Anthropic-compatible)+ gateway + 默认模型 + holysheep provider
49
- */
50
- function buildFullConfig(existing, apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI) {
51
- const config = JSON.parse(JSON.stringify(existing || {})) // deep clone
52
-
53
- // ── 1. 环境变量(兼容 Anthropic SDK 自动读取)──────────────────────
54
- if (!config.env) config.env = {}
55
- config.env.ANTHROPIC_API_KEY = apiKey
56
- config.env.ANTHROPIC_BASE_URL = baseUrlAnthropicNoV1 // https://api.holysheep.ai
57
-
58
- // ── 2. Auth profile — openclaw 官方格式(Anthropic API key)─────────
59
- // 参考: docs.openclaw.ai — auth profile type=anthropic-api-key
60
- if (!config.auth) config.auth = {}
61
- if (!config.auth.profiles) config.auth.profiles = {}
62
- config.auth.profiles.holysheep = {
63
- provider: 'anthropic',
64
- key: apiKey,
65
- baseUrl: baseUrlAnthropicNoV1,
66
- }
67
- // 不设 auth.default — openclaw 不认识这个字段
68
-
69
- // ── 3. 默认模型 ────────────────────────────────────────────────────
70
- if (!config.agents) config.agents = {}
71
- if (!config.agents.defaults) config.agents.defaults = {}
72
- // 总是覆写为 HolySheep 最新 Sonnet 4.6
73
- config.agents.defaults.model = {
74
- primary: 'anthropic/claude-sonnet-4-6',
75
- }
76
-
77
- // ── 4. 自定义 holysheep provider(OpenAI-compatible,支持所有模型)
78
- if (!config.models) config.models = {}
79
- config.models.mode = 'merge'
80
- if (!config.models.providers) config.models.providers = {}
81
- config.models.providers.holysheep = {
82
- baseUrl: baseUrlOpenAI, // https://api.holysheep.ai/v1
83
- apiKey,
84
- api: 'openai-completions',
85
- models: [
86
- { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6 (HolySheep)' },
87
- { id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet 4.5 (HolySheep)' },
88
- { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4 (HolySheep)' },
89
- { id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5 (HolySheep)' },
90
- { id: 'claude-opus-4-20250514', name: 'Claude Opus 4 (HolySheep)' },
91
- { id: 'gpt-4o', name: 'GPT-4o (HolySheep)' },
92
- { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro (HolySheep)' },
93
- ],
94
- }
95
-
96
- // ── 5. Gateway 配置(使用 openclaw 2026+ 格式)──────────────────────
97
- if (!config.gateway) config.gateway = {}
98
- if (!config.gateway.port) config.gateway.port = 18789
99
- // bind 用新格式的 mode,不用 IP 字符串
100
- if (!config.gateway.bind || config.gateway.bind === '127.0.0.1' || config.gateway.bind === 'localhost') {
101
- config.gateway.bind = 'loopback'
102
- }
103
- // 生成 gateway token(若已有则不覆盖)
104
- if (!config.gateway.auth) config.gateway.auth = {}
105
- if (!config.gateway.auth.token) {
106
- config.gateway.auth.token = genToken()
107
- config.gateway.auth.mode = 'token'
108
- }
109
-
110
- // ── 6. 不写 workspace 字段(openclaw 不认识,会报 unknown key)──────
111
- delete config.workspace
112
-
113
- return config
114
- }
115
-
116
55
  module.exports = {
117
56
  name: 'OpenClaw',
118
57
  id: 'openclaw',
119
58
 
120
59
  checkInstalled() {
60
+ // 先检测命令是否在 PATH 里
121
61
  if (require('../utils/which').commandExists('openclaw')) return true
122
- // Windows PATH 未刷新时,用 npx 探测
62
+ // Windows PATH 未刷新时,npx 探测
123
63
  if (process.platform === 'win32') {
124
64
  try {
125
- require('child_process').execSync('npx openclaw --version', { stdio: 'ignore', timeout: 10000 })
65
+ require('child_process').execSync(
66
+ 'npx --yes openclaw --version',
67
+ { stdio: 'ignore', timeout: 15000, shell: true }
68
+ )
126
69
  return true
127
70
  } catch {}
128
71
  }
@@ -131,155 +74,133 @@ module.exports = {
131
74
 
132
75
  isConfigured() {
133
76
  const c = readConfig()
134
- return !!(
135
- c.auth?.profiles?.holysheep ||
136
- c.env?.ANTHROPIC_BASE_URL?.includes('holysheep')
137
- )
77
+ // 检查是否已有 holysheep custom provider 配置
78
+ const cfg = JSON.stringify(c)
79
+ return cfg.includes('holysheep.ai') || cfg.includes('holysheep')
138
80
  },
139
81
 
140
- configure(apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI) {
141
- const existing = readConfig()
142
- const config = buildFullConfig(existing, apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI)
82
+ configure(apiKey, baseUrlAnthropicNoV1) {
83
+ const chalk = require('chalk')
143
84
 
144
- // 确保 workspace 目录存在(openclaw 首次启动需要)
145
- fs.mkdirSync(config.workspace, { recursive: true })
85
+ console.log(chalk.gray('\n ⚙️ 正在通过 OpenClaw 官方向导配置(约 30 秒)...'))
146
86
 
147
- writeConfig(config)
87
+ // Step 1: 如果配置文件存在且有问题,先 doctor --fix
88
+ if (fs.existsSync(CONFIG_FILE)) {
89
+ run(['doctor', '--fix'], { stdio: 'ignore', timeout: 15000 })
90
+ }
148
91
 
149
- // 自动初始化并启动 Gateway
150
- _initAndStartGateway()
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
+ })
151
111
 
152
- return { file: CONFIG_FILE, hot: false, _gatewayStarted: true }
153
- },
112
+ const stdout = r.stdout?.toString() || ''
113
+ const stderr = r.stderr?.toString() || ''
154
114
 
155
- reset() {
156
- const config = readConfig()
157
- if (config.env) {
158
- delete config.env.ANTHROPIC_API_KEY
159
- delete config.env.ANTHROPIC_BASE_URL
115
+ if (r.status === 0) {
116
+ console.log(chalk.green(' ✓ OpenClaw 配置完成,Gateway 已在后台启动'))
117
+ console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
118
+ return { file: CONFIG_FILE, hot: false }
160
119
  }
161
- if (config.auth?.profiles) delete config.auth.profiles.holysheep
162
- if (config.auth?.default === 'holysheep') delete config.auth.default
163
- if (config.models?.providers) delete config.models.providers.holysheep
164
- if (config.agents?.defaults?.model?.primary?.startsWith('anthropic/')) {
165
- delete config.agents.defaults.model
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
+ }
166
135
  }
167
- writeConfig(config)
136
+
137
+ return { file: CONFIG_FILE, hot: false }
138
+ },
139
+
140
+ reset() {
141
+ run(['doctor', '--reset'], { stdio: 'ignore', timeout: 15000 })
168
142
  },
169
143
 
170
144
  getConfigPath() { return CONFIG_FILE },
171
- hint: 'Gateway 已自动在后台启动,打开浏览器即可使用',
145
+ hint: 'Gateway 已自动启动,打开浏览器即可使用',
172
146
  launchCmd: null,
173
147
  get launchNote() {
174
148
  const isWin = process.platform === 'win32'
175
149
  return isWin
176
- ? '🌐 打开浏览器访问 http://127.0.0.1:18789/\n 如无法访问请运行: npx openclaw gateway start'
177
- : '🌐 打开浏览器访问 http://127.0.0.1:18789/'
150
+ ? '🌐 打开浏览器: http://127.0.0.1:18789/\n 如无法访问: npx openclaw gateway start'
151
+ : '🌐 打开浏览器: http://127.0.0.1:18789/'
178
152
  },
179
153
  installCmd: 'npm install -g openclaw@latest',
180
154
  docsUrl: 'https://docs.openclaw.ai',
181
155
  }
182
156
 
183
157
  /**
184
- * 自动初始化并启动 OpenClaw Gateway
185
- *
186
- * 策略:
187
- * 1. openclaw onboard --non-interactive --install-daemon
188
- * 读取已写好的 openclaw.json,无交互完成初始化 + 注册系统服务 + 启动 gateway
189
- * 2. 若 onboard 失败(如 Windows 不支持 daemon),直接 gateway start
190
- * 3. 仍失败 → Windows: start /B openclaw gateway;Unix: detached spawn
158
+ * 尝试启动 Gateway,返回是否成功
191
159
  */
192
- function _initAndStartGateway() {
193
- const chalk = require('chalk')
194
- const isWin = process.platform === 'win32'
195
- // Windows 刚装完 npm 包,PATH 未刷新,用 npx 兜底
196
- const bin = isWin ? 'npx openclaw' : 'openclaw'
197
-
198
- console.log(chalk.gray('\n ⚙️ 正在启动 OpenClaw Gateway...'))
199
-
200
- // Step 0: 先 doctor --fix 修复配置兼容性问题
201
- spawnSync(isWin ? 'npx' : bin,
202
- isWin ? ['openclaw', 'doctor', '--fix'] : ['doctor', '--fix'],
203
- { shell: true, timeout: 15000, stdio: 'ignore' }
204
- )
160
+ function _startGateway() {
161
+ const chalk = require('chalk')
162
+ const isWin = process.platform === 'win32'
205
163
 
206
- // Step 1: gateway start(已有 daemon 时直接生效)
207
- const r1 = spawnSync(
208
- isWin ? 'npx' : bin,
209
- isWin ? ['openclaw', 'gateway', 'start'] : ['gateway', 'start'],
210
- { shell: true, timeout: 10000, stdio: 'pipe' }
211
- )
212
- if (r1.status === 0) {
213
- console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
214
- console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
215
- return
216
- }
164
+ // install,再 start
165
+ run(['gateway', 'install'], { stdio: 'ignore', timeout: 20000 })
217
166
 
218
- // Step 2: 安装系统服务 + 启动
219
- console.log(chalk.gray(' → 注册系统服务...'))
220
- spawnSync(
221
- isWin ? 'npx' : bin,
222
- isWin ? ['openclaw', 'gateway', 'install'] : ['gateway', 'install'],
223
- { shell: true, timeout: 30000, stdio: 'ignore' }
224
- )
225
- const r3 = spawnSync(
226
- isWin ? 'npx' : bin,
227
- isWin ? ['openclaw', 'gateway', 'start'] : ['gateway', 'start'],
228
- { shell: true, timeout: 10000, stdio: 'pipe' }
229
- )
230
- if (r3.status === 0) {
167
+ const r = run(['gateway', 'start'], { timeout: 10000 })
168
+ if (r.status === 0) {
231
169
  console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
232
170
  console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
233
- return
171
+ return true
234
172
  }
235
173
 
236
- // Step 3: fallback 直接后台运行进程
237
- console.log(chalk.gray(' → 以守护进程模式启动...'))
174
+ // fallback: 直接运行 gateway 进程(Windows 用 Start-Process 后台)
238
175
  if (isWin) {
239
- // Windows: PowerShell Start-Process 后台运行(用 npx 兜底 PATH 未刷新)
240
176
  spawnSync('powershell', [
241
- '-NonInteractive', '-WindowStyle', 'Hidden', '-Command',
242
- `Start-Process -FilePath "npx" -ArgumentList "openclaw","gateway","--port","18789" -WindowStyle Hidden`
177
+ '-NonInteractive', '-Command',
178
+ `Start-Process -FilePath "npx" -ArgumentList @("openclaw","gateway","--port","18789") -WindowStyle Hidden`
243
179
  ], { shell: false, timeout: 5000, stdio: 'ignore' })
244
180
  } else {
245
181
  const { spawn } = require('child_process')
246
- const child = spawn(bin, ['gateway', '--port', '18789'], {
182
+ const child = spawn('openclaw', ['gateway', '--port', '18789'], {
247
183
  detached: true, stdio: 'ignore',
248
184
  })
249
185
  child.unref()
250
186
  }
251
187
 
252
- // 等 4 秒让 gateway 起来再报告
253
- const deadline = Date.now() + 4000
188
+ // 等 5 秒,验证是否真的起来
189
+ const deadline = Date.now() + 5000
254
190
  while (Date.now() < deadline) {}
255
191
 
256
- // 验证是否真的起来了
257
- const http = require('http')
258
- const verify = new Promise(resolve => {
259
- const req = http.get('http://127.0.0.1:18789/', res => resolve(true))
260
- req.on('error', () => resolve(false))
261
- req.setTimeout(3000, () => { req.destroy(); resolve(false) })
262
- })
263
-
264
- // 用同步方式等验证(node 18+)
265
- let ok = false
266
192
  try {
267
193
  const { execSync } = require('child_process')
268
194
  execSync(
269
195
  isWin
270
- ? 'powershell -NonInteractive -Command "Invoke-WebRequest -Uri http://127.0.0.1:18789/ -TimeoutSec 3 -UseBasicParsing | Out-Null"'
196
+ ? 'powershell -NonInteractive -Command "(Invoke-WebRequest -Uri http://127.0.0.1:18789/ -TimeoutSec 3 -UseBasicParsing).StatusCode"'
271
197
  : 'curl -sf http://127.0.0.1:18789/ -o /dev/null --max-time 3',
272
198
  { stdio: 'ignore', timeout: 5000 }
273
199
  )
274
- ok = true
275
- } catch {}
276
-
277
- if (ok) {
278
200
  console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
279
201
  console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
280
- } else {
281
- console.log(chalk.yellow(' ⚠️ Gateway 启动中,请稍等 10 秒后访问:'))
282
- console.log(chalk.cyan(' → http://127.0.0.1:18789/'))
283
- console.log(chalk.gray(' 如仍无法访问,请手动运行: openclaw gateway start'))
202
+ return true
203
+ } catch {
204
+ return false
284
205
  }
285
206
  }