@simonyea/holysheep-cli 1.2.9 → 1.3.1

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 +120 -199
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.1",
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')
84
+
85
+ console.log(chalk.gray('\n ⚙️ 正在通过 OpenClaw 官方向导配置(约 30 秒)...'))
86
+
87
+ // Step 1: 删除旧配置,避免 onboard 检测到现有配置后跳过重写
88
+ try {
89
+ if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE)
90
+ } catch {}
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
+ })
143
111
 
144
- // 确保 workspace 目录存在(openclaw 首次启动需要)
145
- fs.mkdirSync(config.workspace, { recursive: true })
112
+ const stdout = r.stdout?.toString() || ''
113
+ const stderr = r.stderr?.toString() || ''
146
114
 
147
- writeConfig(config)
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 }
119
+ }
148
120
 
149
- // 自动初始化并启动 Gateway
150
- _initAndStartGateway()
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
+ }
135
+ }
151
136
 
152
- return { file: CONFIG_FILE, hot: false, _gatewayStarted: true }
137
+ return { file: CONFIG_FILE, hot: false }
153
138
  },
154
139
 
155
140
  reset() {
156
- const config = readConfig()
157
- if (config.env) {
158
- delete config.env.ANTHROPIC_API_KEY
159
- delete config.env.ANTHROPIC_BASE_URL
160
- }
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
166
- }
167
- writeConfig(config)
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'
160
+ function _startGateway() {
161
+ const chalk = require('chalk')
162
+ const isWin = process.platform === 'win32'
197
163
 
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
- )
205
-
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
- }
217
-
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) {
164
+ // 先尝试 gateway start(已有服务时生效)
165
+ const r = run(['gateway', 'start'], { timeout: 10000 })
166
+ if (r.status === 0) {
231
167
  console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
232
168
  console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
233
- return
169
+ return true
234
170
  }
235
171
 
236
- // Step 3: fallback 直接后台运行进程
237
- console.log(chalk.gray(' → 以守护进程模式启动...'))
172
+ // gateway start 失败 直接后台运行进程(不依赖 schtasks/daemon)
173
+ const { spawn } = require('child_process')
238
174
  if (isWin) {
239
- // Windows: PowerShell Start-Process 后台运行(用 npx 兜底 PATH 未刷新)
175
+ // Windows: Start-Process 开隐藏窗口运行 npx openclaw gateway
240
176
  spawnSync('powershell', [
241
- '-NonInteractive', '-WindowStyle', 'Hidden', '-Command',
242
- `Start-Process -FilePath "npx" -ArgumentList "openclaw","gateway","--port","18789" -WindowStyle Hidden`
243
- ], { shell: false, timeout: 5000, stdio: 'ignore' })
177
+ '-NonInteractive', '-Command',
178
+ `Start-Process powershell -ArgumentList '-NonInteractive','-WindowStyle','Hidden','-Command','npx openclaw gateway --port 18789' -WindowStyle Hidden`
179
+ ], { shell: false, timeout: 8000, stdio: 'ignore' })
244
180
  } else {
245
- const { spawn } = require('child_process')
246
- const child = spawn(bin, ['gateway', '--port', '18789'], {
181
+ const child = spawn('openclaw', ['gateway', '--port', '18789'], {
247
182
  detached: true, stdio: 'ignore',
248
183
  })
249
184
  child.unref()
250
185
  }
251
186
 
252
- // 等 4 秒让 gateway 起来再报告
253
- const deadline = Date.now() + 4000
187
+ // 等 5 秒让 gateway 起来
188
+ const deadline = Date.now() + 5000
254
189
  while (Date.now() < deadline) {}
255
190
 
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
191
+ // 验证
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
  }