@simonyea/holysheep-cli 1.2.8 → 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 -186
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(Custom Provider,Anthropic-compatible)────────
|
|
59
|
-
// openclaw 通过 auth profile 管理凭证;写入一个 holysheep profile
|
|
60
|
-
if (!config.auth) config.auth = {}
|
|
61
|
-
if (!config.auth.profiles) config.auth.profiles = {}
|
|
62
|
-
config.auth.profiles.holysheep = {
|
|
63
|
-
type: 'api-key',
|
|
64
|
-
provider: 'anthropic',
|
|
65
|
-
apiKey,
|
|
66
|
-
baseUrl: baseUrlAnthropicNoV1,
|
|
67
|
-
}
|
|
68
|
-
config.auth.default = 'holysheep'
|
|
69
|
-
|
|
70
|
-
// ── 3. 默认模型 ────────────────────────────────────────────────────
|
|
71
|
-
if (!config.agents) config.agents = {}
|
|
72
|
-
if (!config.agents.defaults) config.agents.defaults = {}
|
|
73
|
-
// 总是覆写为 HolySheep 最新 Sonnet 4.6
|
|
74
|
-
config.agents.defaults.model = {
|
|
75
|
-
primary: 'anthropic/claude-sonnet-4-6',
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ── 4. 自定义 holysheep provider(OpenAI-compatible,支持所有模型)
|
|
79
|
-
if (!config.models) config.models = {}
|
|
80
|
-
config.models.mode = 'merge'
|
|
81
|
-
if (!config.models.providers) config.models.providers = {}
|
|
82
|
-
config.models.providers.holysheep = {
|
|
83
|
-
baseUrl: baseUrlOpenAI, // https://api.holysheep.ai/v1
|
|
84
|
-
apiKey,
|
|
85
|
-
api: 'openai-completions',
|
|
86
|
-
models: [
|
|
87
|
-
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6 (HolySheep)' },
|
|
88
|
-
{ id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet 4.5 (HolySheep)' },
|
|
89
|
-
{ id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4 (HolySheep)' },
|
|
90
|
-
{ id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5 (HolySheep)' },
|
|
91
|
-
{ id: 'claude-opus-4-20250514', name: 'Claude Opus 4 (HolySheep)' },
|
|
92
|
-
{ id: 'gpt-4o', name: 'GPT-4o (HolySheep)' },
|
|
93
|
-
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro (HolySheep)' },
|
|
94
|
-
],
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ── 5. Gateway 配置(如未设置则初始化,保留已有配置)────────────────
|
|
98
|
-
if (!config.gateway) config.gateway = {}
|
|
99
|
-
if (!config.gateway.port) config.gateway.port = 18789
|
|
100
|
-
if (!config.gateway.bind) config.gateway.bind = '127.0.0.1'
|
|
101
|
-
// 生成 gateway token(若已有则不覆盖)
|
|
102
|
-
if (!config.gateway.auth) config.gateway.auth = {}
|
|
103
|
-
if (!config.gateway.auth.token) {
|
|
104
|
-
config.gateway.auth.token = genToken()
|
|
105
|
-
config.gateway.auth.mode = 'token'
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ── 6. Workspace 默认路径 ─────────────────────────────────────────
|
|
109
|
-
if (!config.workspace) {
|
|
110
|
-
config.workspace = path.join(OPENCLAW_DIR, 'workspace')
|
|
111
|
-
}
|
|
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,145 +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'
|
|
160
|
+
function _startGateway() {
|
|
161
|
+
const chalk = require('chalk')
|
|
162
|
+
const isWin = process.platform === 'win32'
|
|
197
163
|
|
|
198
|
-
|
|
164
|
+
// 先 install,再 start
|
|
165
|
+
run(['gateway', 'install'], { stdio: 'ignore', timeout: 20000 })
|
|
199
166
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
shell: true, timeout: 10000, stdio: 'pipe',
|
|
203
|
-
})
|
|
204
|
-
if (r1.status === 0) {
|
|
205
|
-
console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
|
|
206
|
-
console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
|
|
207
|
-
return
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Step 2: 无交互 onboard --install-daemon(注册系统服务)
|
|
211
|
-
console.log(chalk.gray(' → 首次初始化,注册系统服务...'))
|
|
212
|
-
const r2 = spawnSync(bin, ['onboard', '--non-interactive', '--install-daemon'], {
|
|
213
|
-
shell: true, timeout: 60000, stdio: 'pipe',
|
|
214
|
-
env: { ...process.env },
|
|
215
|
-
})
|
|
216
|
-
// 再次尝试 start
|
|
217
|
-
const r3 = spawnSync(bin, ['gateway', 'start'], {
|
|
218
|
-
shell: true, timeout: 10000, stdio: 'pipe',
|
|
219
|
-
})
|
|
220
|
-
if (r3.status === 0) {
|
|
167
|
+
const r = run(['gateway', 'start'], { timeout: 10000 })
|
|
168
|
+
if (r.status === 0) {
|
|
221
169
|
console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
|
|
222
170
|
console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
|
|
223
|
-
return
|
|
171
|
+
return true
|
|
224
172
|
}
|
|
225
173
|
|
|
226
|
-
//
|
|
227
|
-
console.log(chalk.gray(' → 以守护进程模式启动...'))
|
|
174
|
+
// fallback: 直接运行 gateway 进程(Windows 用 Start-Process 后台)
|
|
228
175
|
if (isWin) {
|
|
229
|
-
// Windows: PowerShell Start-Process 后台运行(用 npx 兜底 PATH 未刷新)
|
|
230
176
|
spawnSync('powershell', [
|
|
231
|
-
'-NonInteractive', '-
|
|
232
|
-
`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`
|
|
233
179
|
], { shell: false, timeout: 5000, stdio: 'ignore' })
|
|
234
180
|
} else {
|
|
235
181
|
const { spawn } = require('child_process')
|
|
236
|
-
const child = spawn(
|
|
182
|
+
const child = spawn('openclaw', ['gateway', '--port', '18789'], {
|
|
237
183
|
detached: true, stdio: 'ignore',
|
|
238
184
|
})
|
|
239
185
|
child.unref()
|
|
240
186
|
}
|
|
241
187
|
|
|
242
|
-
// 等
|
|
243
|
-
const deadline = Date.now() +
|
|
188
|
+
// 等 5 秒,验证是否真的起来
|
|
189
|
+
const deadline = Date.now() + 5000
|
|
244
190
|
while (Date.now() < deadline) {}
|
|
245
191
|
|
|
246
|
-
// 验证是否真的起来了
|
|
247
|
-
const http = require('http')
|
|
248
|
-
const verify = new Promise(resolve => {
|
|
249
|
-
const req = http.get('http://127.0.0.1:18789/', res => resolve(true))
|
|
250
|
-
req.on('error', () => resolve(false))
|
|
251
|
-
req.setTimeout(3000, () => { req.destroy(); resolve(false) })
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
// 用同步方式等验证(node 18+)
|
|
255
|
-
let ok = false
|
|
256
192
|
try {
|
|
257
193
|
const { execSync } = require('child_process')
|
|
258
194
|
execSync(
|
|
259
195
|
isWin
|
|
260
|
-
? '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"'
|
|
261
197
|
: 'curl -sf http://127.0.0.1:18789/ -o /dev/null --max-time 3',
|
|
262
198
|
{ stdio: 'ignore', timeout: 5000 }
|
|
263
199
|
)
|
|
264
|
-
ok = true
|
|
265
|
-
} catch {}
|
|
266
|
-
|
|
267
|
-
if (ok) {
|
|
268
200
|
console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
|
|
269
201
|
console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
console.log(chalk.gray(' 如仍无法访问,请手动运行: openclaw gateway start'))
|
|
202
|
+
return true
|
|
203
|
+
} catch {
|
|
204
|
+
return false
|
|
274
205
|
}
|
|
275
206
|
}
|