@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.
- package/package.json +1 -1
- package/src/tools/openclaw.js +117 -196
package/package.json
CHANGED
package/src/tools/openclaw.js
CHANGED
|
@@ -1,128 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenClaw 适配器
|
|
2
|
+
* OpenClaw 适配器
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 支持 WhatsApp/Telegram/Signal/Discord/iMessage 等 20+ 渠道
|
|
4
|
+
* 策略:完全通过 openclaw CLI 官方命令完成配置,不手写 JSON。
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
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
|
|
23
|
-
const CONFIG_FILE
|
|
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
|
|
62
|
+
// Windows PATH 未刷新时,npx 探测
|
|
123
63
|
if (process.platform === 'win32') {
|
|
124
64
|
try {
|
|
125
|
-
require('child_process').execSync(
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
141
|
-
const
|
|
142
|
-
const config = buildFullConfig(existing, apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI)
|
|
82
|
+
configure(apiKey, baseUrlAnthropicNoV1) {
|
|
83
|
+
const chalk = require('chalk')
|
|
143
84
|
|
|
144
|
-
|
|
145
|
-
fs.mkdirSync(config.workspace, { recursive: true })
|
|
85
|
+
console.log(chalk.gray('\n ⚙️ 正在通过 OpenClaw 官方向导配置(约 30 秒)...'))
|
|
146
86
|
|
|
147
|
-
|
|
87
|
+
// Step 1: 如果配置文件存在且有问题,先 doctor --fix
|
|
88
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
89
|
+
run(['doctor', '--fix'], { stdio: 'ignore', timeout: 15000 })
|
|
90
|
+
}
|
|
148
91
|
|
|
149
|
-
//
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
|
|
112
|
+
const stdout = r.stdout?.toString() || ''
|
|
113
|
+
const stderr = r.stderr?.toString() || ''
|
|
154
114
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
? '🌐
|
|
177
|
-
: '🌐
|
|
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
|
-
*
|
|
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
|
|
193
|
-
const chalk
|
|
194
|
-
const isWin
|
|
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
|
-
//
|
|
207
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
//
|
|
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', '-
|
|
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(
|
|
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
|
-
// 等
|
|
253
|
-
const deadline = Date.now() +
|
|
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
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
console.log(chalk.gray(' 如仍无法访问,请手动运行: openclaw gateway start'))
|
|
202
|
+
return true
|
|
203
|
+
} catch {
|
|
204
|
+
return false
|
|
284
205
|
}
|
|
285
206
|
}
|