@simonyea/holysheep-cli 1.3.1 → 1.3.3
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 +91 -130
package/package.json
CHANGED
package/src/tools/openclaw.js
CHANGED
|
@@ -1,45 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenClaw 适配器
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
63
|
-
if (process.platform === 'win32') {
|
|
77
|
+
if (isWin) {
|
|
64
78
|
try {
|
|
65
|
-
|
|
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
|
|
77
|
-
|
|
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
|
-
|
|
95
|
+
// 1. 写入正确格式配置
|
|
96
|
+
writeCorrectConfig(apiKey, baseUrlAnthropicNoV1)
|
|
86
97
|
|
|
87
|
-
//
|
|
88
|
-
|
|
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
|
-
})
|
|
98
|
+
// 2. doctor --fix 修复任何兼容性问题
|
|
99
|
+
npx('doctor', '--fix')
|
|
111
100
|
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
// 3. 启动 Gateway
|
|
102
|
+
console.log(chalk.gray(' → 正在启动 Gateway...'))
|
|
103
|
+
const ok = _startGateway()
|
|
114
104
|
|
|
115
|
-
if (
|
|
116
|
-
console.log(chalk.green(' ✓ OpenClaw
|
|
105
|
+
if (ok) {
|
|
106
|
+
console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
|
|
117
107
|
console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
|
|
118
|
-
|
|
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
|
-
|
|
117
|
+
try { fs.unlinkSync(CONFIG_FILE) } catch {}
|
|
142
118
|
},
|
|
143
119
|
|
|
144
120
|
getConfigPath() { return CONFIG_FILE },
|
|
145
|
-
hint:
|
|
146
|
-
launchCmd:
|
|
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
|
|
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,38 @@ module.exports = {
|
|
|
155
130
|
}
|
|
156
131
|
|
|
157
132
|
/**
|
|
158
|
-
*
|
|
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
|
-
// 先尝试 gateway start(已有服务时生效)
|
|
165
|
-
const r = run(['gateway', 'start'], { timeout: 10000 })
|
|
166
|
-
if (r.status === 0) {
|
|
167
|
-
console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
|
|
168
|
-
console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
|
|
169
|
-
return true
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// gateway start 失败 → 直接后台运行进程(不依赖 schtasks/daemon)
|
|
173
|
-
const { spawn } = require('child_process')
|
|
174
138
|
if (isWin) {
|
|
175
|
-
// Windows:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
139
|
+
// Windows: 开一个新的最小化 cmd 窗口运行 gateway(不隐藏,否则用户看不到)
|
|
140
|
+
// 用 start 命令开新窗口,这样主进程可以继续
|
|
141
|
+
spawnSync('cmd', [
|
|
142
|
+
'/c', 'start', '"OpenClaw Gateway"', '/min',
|
|
143
|
+
'cmd', '/k', 'npx openclaw gateway --port 18789'
|
|
144
|
+
], { shell: false, timeout: 5000, stdio: 'ignore' })
|
|
180
145
|
} else {
|
|
181
146
|
const child = spawn('openclaw', ['gateway', '--port', '18789'], {
|
|
182
|
-
detached: true,
|
|
147
|
+
detached: true,
|
|
148
|
+
stdio: 'ignore',
|
|
183
149
|
})
|
|
184
150
|
child.unref()
|
|
185
151
|
}
|
|
186
152
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
153
|
+
// 等待 gateway 启动(最多 8 秒)
|
|
154
|
+
for (let i = 0; i < 8; i++) {
|
|
155
|
+
const t = Date.now(); while (Date.now() - t < 1000) {}
|
|
156
|
+
try {
|
|
157
|
+
execSync(
|
|
158
|
+
isWin
|
|
159
|
+
? 'powershell -NonInteractive -Command "try { (Invoke-WebRequest -Uri http://127.0.0.1:18789/ -TimeoutSec 1 -UseBasicParsing).StatusCode } catch { exit 1 }"'
|
|
160
|
+
: 'curl -sf http://127.0.0.1:18789/ -o /dev/null --max-time 1',
|
|
161
|
+
{ stdio: 'ignore', timeout: 3000 }
|
|
162
|
+
)
|
|
163
|
+
return true // 成功响应
|
|
164
|
+
} catch {}
|
|
205
165
|
}
|
|
166
|
+
return false
|
|
206
167
|
}
|