@simonyea/holysheep-cli 1.2.3 → 1.2.5

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 +150 -75
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "一键配置所有 AI 编程工具接入 HolySheep API — Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw / Aider / Cursor",
5
5
  "keywords": [
6
6
  "claude",
@@ -8,22 +8,25 @@
8
8
  * 配置文件: ~/.openclaw/openclaw.json (JSON5 格式)
9
9
  * 文档: https://docs.openclaw.ai
10
10
  *
11
- * HolySheep 接入方式:通过 env.ANTHROPIC_API_KEY + env.ANTHROPIC_BASE_URL
12
- * 设置 Anthropic provider 自定义 base URL 指向 HolySheep 中继
11
+ * HolySheep 接入方式:
12
+ * 1. 直接写入完整的 openclaw.json(含 auth profile + gateway + 默认模型)
13
+ * 2. 自动启动 Gateway,用户直接打开 http://127.0.0.1:18789/
14
+ * 不需要用户手动跑 onboard / gateway start
13
15
  */
14
- const fs = require('fs')
15
- const path = require('path')
16
- const os = require('os')
16
+ const fs = require('fs')
17
+ const path = require('path')
18
+ const os = require('os')
19
+ const crypto = require('crypto')
17
20
  const { spawnSync } = require('child_process')
18
21
 
19
- const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
20
- const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
22
+ const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
23
+ const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
21
24
 
22
25
  function readConfig() {
23
26
  try {
24
27
  if (fs.existsSync(CONFIG_FILE)) {
25
- // openclaw.json 是 JSON5 格式,先去掉注释再 parse
26
28
  const raw = fs.readFileSync(CONFIG_FILE, 'utf8')
29
+ // openclaw.json 是 JSON5 格式,先去掉注释再 parse
27
30
  return JSON.parse(raw.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, ''))
28
31
  }
29
32
  } catch {}
@@ -35,122 +38,194 @@ function writeConfig(data) {
35
38
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8')
36
39
  }
37
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(用户可以在 /model 命令里切换)
74
+ config.agents.defaults.model = {
75
+ primary: 'anthropic/claude-sonnet-4-5-20250929',
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-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 配置(如未设置则初始化,保留已有配置)────────────────
97
+ if (!config.gateway) config.gateway = {}
98
+ if (!config.gateway.port) config.gateway.port = 18789
99
+ if (!config.gateway.bind) config.gateway.bind = '127.0.0.1'
100
+ // 生成 gateway token(若已有则不覆盖)
101
+ if (!config.gateway.auth) config.gateway.auth = {}
102
+ if (!config.gateway.auth.token) {
103
+ config.gateway.auth.token = genToken()
104
+ config.gateway.auth.mode = 'token'
105
+ }
106
+
107
+ // ── 6. Workspace 默认路径 ─────────────────────────────────────────
108
+ if (!config.workspace) {
109
+ config.workspace = path.join(OPENCLAW_DIR, 'workspace')
110
+ }
111
+
112
+ return config
113
+ }
114
+
38
115
  module.exports = {
39
116
  name: 'OpenClaw',
40
- id: 'openclaw',
117
+ id: 'openclaw',
118
+
41
119
  checkInstalled() {
42
120
  return require('../utils/which').commandExists('openclaw')
43
121
  },
122
+
44
123
  isConfigured() {
45
124
  const c = readConfig()
46
125
  return !!(
47
- c.env?.ANTHROPIC_BASE_URL?.includes('holysheep') ||
48
- c.models?.providers?.holysheep
126
+ c.auth?.profiles?.holysheep ||
127
+ c.env?.ANTHROPIC_BASE_URL?.includes('holysheep')
49
128
  )
50
129
  },
51
- configure(apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI) {
52
- const config = readConfig()
53
-
54
- // 设置环境变量 — Anthropic provider 使用 ANTHROPIC_BASE_URL 覆盖默认地址
55
- if (!config.env) config.env = {}
56
- config.env.ANTHROPIC_API_KEY = apiKey
57
- config.env.ANTHROPIC_BASE_URL = baseUrlAnthropicNoV1 // https://api.holysheep.ai
58
130
 
59
- // 设置默认模型(如果未配置)
60
- if (!config.agents) config.agents = {}
61
- if (!config.agents.defaults) config.agents.defaults = {}
62
- if (!config.agents.defaults.model) {
63
- config.agents.defaults.model = { primary: 'anthropic/claude-sonnet-4-5-20250929' }
64
- }
131
+ configure(apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI) {
132
+ const existing = readConfig()
133
+ const config = buildFullConfig(existing, apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI)
65
134
 
66
- // 同时注册一个 holysheep 自定义 provider(支持所有模型)
67
- if (!config.models) config.models = {}
68
- config.models.mode = 'merge'
69
- if (!config.models.providers) config.models.providers = {}
70
- config.models.providers.holysheep = {
71
- baseUrl: baseUrlOpenAI, // https://api.holysheep.ai/v1
72
- apiKey,
73
- api: 'openai-completions',
74
- models: [
75
- { id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet 4.5 (HolySheep)' },
76
- { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4 (HolySheep)' },
77
- { id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5 (HolySheep)' },
78
- { id: 'claude-opus-4-20250514', name: 'Claude Opus 4 (HolySheep)' },
79
- { id: 'gpt-4o', name: 'GPT-4o (HolySheep)' },
80
- { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro (HolySheep)' },
81
- ],
82
- }
135
+ // 确保 workspace 目录存在(openclaw 首次启动需要)
136
+ fs.mkdirSync(config.workspace, { recursive: true })
83
137
 
84
138
  writeConfig(config)
85
139
 
86
- // 自动启动 Gateway 后台服务(无需用户手动操作)
87
- // 先尝试安装 daemon(系统服务),再 start;失败不阻断配置流程
88
- _autoStartGateway()
140
+ // 自动初始化并启动 Gateway
141
+ _initAndStartGateway()
89
142
 
90
143
  return { file: CONFIG_FILE, hot: false, _gatewayStarted: true }
91
144
  },
145
+
92
146
  reset() {
93
147
  const config = readConfig()
94
148
  if (config.env) {
95
149
  delete config.env.ANTHROPIC_API_KEY
96
150
  delete config.env.ANTHROPIC_BASE_URL
97
151
  }
98
- if (config.models?.providers) {
99
- delete config.models.providers.holysheep
100
- }
101
- // 如果默认模型是 anthropic/xxx,清掉
152
+ if (config.auth?.profiles) delete config.auth.profiles.holysheep
153
+ if (config.auth?.default === 'holysheep') delete config.auth.default
154
+ if (config.models?.providers) delete config.models.providers.holysheep
102
155
  if (config.agents?.defaults?.model?.primary?.startsWith('anthropic/')) {
103
156
  delete config.agents.defaults.model
104
157
  }
105
158
  writeConfig(config)
106
159
  },
160
+
107
161
  getConfigPath() { return CONFIG_FILE },
108
- hint: 'Gateway 已自动启动,浏览器打开 http://127.0.0.1:18789/ 即可使用',
109
- launchCmd: null,
162
+ hint: 'Gateway 已自动在后台启动,打开浏览器即可使用',
163
+ launchCmd: null,
110
164
  launchNote: '🌐 打开浏览器访问 http://127.0.0.1:18789/',
111
165
  installCmd: 'npm install -g openclaw@latest',
112
- docsUrl: 'https://docs.openclaw.ai',
166
+ docsUrl: 'https://docs.openclaw.ai',
113
167
  }
114
168
 
115
169
  /**
116
- * 自动启动 OpenClaw Gateway 后台服务
117
- * 1. 先尝试 `openclaw gateway start`(已有 daemon 或上次安装过)
118
- * 2. 若失败,尝试 `openclaw onboard --install-daemon --yes`(无交互,自动安装系统服务)
119
- * 3. 再 `openclaw gateway start`
170
+ * 自动初始化并启动 OpenClaw Gateway
171
+ *
172
+ * 策略:
173
+ * 1. openclaw onboard --non-interactive --install-daemon
174
+ * 读取已写好的 openclaw.json,无交互完成初始化 + 注册系统服务 + 启动 gateway
175
+ * 2. 若 onboard 失败(如 Windows 不支持 daemon),直接 gateway start
176
+ * 3. 仍失败 → Windows: start /B openclaw gateway;Unix: detached spawn
120
177
  */
121
- function _autoStartGateway() {
178
+ function _initAndStartGateway() {
122
179
  const chalk = require('chalk')
123
- const bin = process.platform === 'win32' ? 'openclaw.cmd' : 'openclaw'
180
+ const isWin = process.platform === 'win32'
181
+ const bin = 'openclaw'
124
182
 
125
- console.log(chalk.gray('\n ⚙️ 正在启动 OpenClaw Gateway...'))
183
+ console.log(chalk.gray('\n ⚙️ 正在初始化并启动 OpenClaw Gateway(约 15 秒)...'))
184
+
185
+ // Step 1: 无交互 onboard + install-daemon
186
+ const r1 = spawnSync(bin, ['onboard', '--non-interactive', '--install-daemon'], {
187
+ shell: true,
188
+ timeout: 60000,
189
+ stdio: 'pipe',
190
+ env: { ...process.env },
191
+ })
126
192
 
127
- // 先直接 start
128
- const r1 = spawnSync(bin, ['gateway', 'start'], { shell: true, timeout: 15000 })
129
193
  if (r1.status === 0) {
130
- console.log(chalk.green(' ✓ OpenClaw Gateway 已在后台启动'))
194
+ console.log(chalk.green(' ✓ OpenClaw 初始化完成,Gateway 已在后台启动'))
195
+ console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
131
196
  return
132
197
  }
133
198
 
134
- // start 失败 → 先 onboard --install-daemon --yes(无交互)再 start
135
- console.log(chalk.gray(' 首次运行,正在初始化服务(约 10 秒)...'))
136
- spawnSync(bin, ['onboard', '--install-daemon', '--yes'], {
137
- shell: true,
138
- timeout: 60000,
139
- stdio: 'ignore',
199
+ // Step 2: onboard 失败,直接 gateway start
200
+ const r2 = spawnSync(bin, ['gateway', 'start'], {
201
+ shell: true,
202
+ timeout: 15000,
203
+ stdio: 'pipe',
140
204
  })
141
-
142
- const r2 = spawnSync(bin, ['gateway', 'start'], { shell: true, timeout: 15000 })
143
205
  if (r2.status === 0) {
144
- console.log(chalk.green(' ✓ OpenClaw Gateway 已在后台启动'))
206
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
207
+ console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
208
+ return
209
+ }
210
+
211
+ // Step 3: fallback — 后台守护进程
212
+ if (isWin) {
213
+ // Windows: 用 cmd start 开新窗口后台运行
214
+ spawnSync('cmd', ['/c', `start /B "" openclaw gateway --port 18789`], {
215
+ shell: true, stdio: 'ignore',
216
+ })
145
217
  } else {
146
- // 仍然失败 → fallback:前台静默运行(detached)
147
218
  const { spawn } = require('child_process')
148
- const child = spawn(bin, ['gateway'], {
149
- shell: true,
219
+ const child = spawn(bin, ['gateway', '--port', '18789'], {
150
220
  detached: true,
151
- stdio: 'ignore',
221
+ stdio: 'ignore',
152
222
  })
153
223
  child.unref()
154
- console.log(chalk.green(' ✓ OpenClaw Gateway 已启动(前台守护模式)'))
155
224
  }
225
+
226
+ // 等 3 秒让 gateway 起来
227
+ const t = Date.now(); while (Date.now() - t < 3000) {}
228
+
229
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
230
+ console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
156
231
  }