@simonyea/holysheep-cli 1.2.2 → 1.2.4

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 +152 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "一键配置所有 AI 编程工具接入 HolySheep API — Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw / Aider / Cursor",
5
5
  "keywords": [
6
6
  "claude",
@@ -8,21 +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')
20
+ const { spawnSync } = require('child_process')
17
21
 
18
- const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
19
- 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')
20
24
 
21
25
  function readConfig() {
22
26
  try {
23
27
  if (fs.existsSync(CONFIG_FILE)) {
24
- // openclaw.json 是 JSON5 格式,先去掉注释再 parse
25
28
  const raw = fs.readFileSync(CONFIG_FILE, 'utf8')
29
+ // openclaw.json 是 JSON5 格式,先去掉注释再 parse
26
30
  return JSON.parse(raw.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, ''))
27
31
  }
28
32
  } catch {}
@@ -34,78 +38,173 @@ function writeConfig(data) {
34
38
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8')
35
39
  }
36
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(用户可以在 /model 命令里切换)
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
+
37
116
  module.exports = {
38
117
  name: 'OpenClaw',
39
- id: 'openclaw',
118
+ id: 'openclaw',
119
+
40
120
  checkInstalled() {
41
121
  return require('../utils/which').commandExists('openclaw')
42
122
  },
123
+
43
124
  isConfigured() {
44
125
  const c = readConfig()
45
126
  return !!(
46
- c.env?.ANTHROPIC_BASE_URL?.includes('holysheep') ||
47
- c.models?.providers?.holysheep
127
+ c.auth?.profiles?.holysheep ||
128
+ c.env?.ANTHROPIC_BASE_URL?.includes('holysheep')
48
129
  )
49
130
  },
131
+
50
132
  configure(apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI) {
51
- const config = readConfig()
133
+ const existing = readConfig()
134
+ const config = buildFullConfig(existing, apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI)
52
135
 
53
- // 设置环境变量 Anthropic provider 使用 ANTHROPIC_BASE_URL 覆盖默认地址
54
- if (!config.env) config.env = {}
55
- config.env.ANTHROPIC_API_KEY = apiKey
56
- config.env.ANTHROPIC_BASE_URL = baseUrlAnthropicNoV1 // https://api.holysheep.ai
136
+ // 确保 workspace 目录存在(openclaw 首次启动需要)
137
+ fs.mkdirSync(config.workspace, { recursive: true })
57
138
 
58
- // 设置默认模型(如果未配置)
59
- if (!config.agents) config.agents = {}
60
- if (!config.agents.defaults) config.agents.defaults = {}
61
- if (!config.agents.defaults.model) {
62
- config.agents.defaults.model = { primary: 'anthropic/claude-sonnet-4-5-20250929' }
63
- }
139
+ writeConfig(config)
64
140
 
65
- // 同时注册一个 holysheep 自定义 provider(支持所有模型)
66
- if (!config.models) config.models = {}
67
- config.models.mode = 'merge'
68
- if (!config.models.providers) config.models.providers = {}
69
- config.models.providers.holysheep = {
70
- baseUrl: baseUrlOpenAI, // https://api.holysheep.ai/v1
71
- apiKey,
72
- api: 'openai-completions',
73
- models: [
74
- { id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet 4.5 (HolySheep)' },
75
- { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4 (HolySheep)' },
76
- { id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5 (HolySheep)' },
77
- { id: 'claude-opus-4-20250514', name: 'Claude Opus 4 (HolySheep)' },
78
- { id: 'gpt-4o', name: 'GPT-4o (HolySheep)' },
79
- { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro (HolySheep)' },
80
- ],
81
- }
141
+ // 自动启动 Gateway,用户直接打开浏览器即可
142
+ _autoStartGateway()
82
143
 
83
- writeConfig(config)
84
- return { file: CONFIG_FILE, hot: false }
144
+ return { file: CONFIG_FILE, hot: false, _gatewayStarted: true }
85
145
  },
146
+
86
147
  reset() {
87
148
  const config = readConfig()
88
149
  if (config.env) {
89
150
  delete config.env.ANTHROPIC_API_KEY
90
151
  delete config.env.ANTHROPIC_BASE_URL
91
152
  }
92
- if (config.models?.providers) {
93
- delete config.models.providers.holysheep
94
- }
95
- // 如果默认模型是 anthropic/xxx,清掉
153
+ if (config.auth?.profiles) delete config.auth.profiles.holysheep
154
+ if (config.auth?.default === 'holysheep') delete config.auth.default
155
+ if (config.models?.providers) delete config.models.providers.holysheep
96
156
  if (config.agents?.defaults?.model?.primary?.startsWith('anthropic/')) {
97
157
  delete config.agents.defaults.model
98
158
  }
99
159
  writeConfig(config)
100
160
  },
161
+
101
162
  getConfigPath() { return CONFIG_FILE },
102
- hint: '切换后重启 OpenClaw 生效;支持 /model 命令切换模型',
103
- launchCmd: null,
104
- launchSteps: [
105
- { cmd: 'npx openclaw onboard', note: '首次初始化(设置模型、鉴权等)' },
106
- { cmd: 'npx openclaw gateway start', note: '启动后台 Gateway 服务' },
107
- { cmd: 'npx openclaw dashboard', note: '打开 WebUI → http://127.0.0.1:18789/' },
108
- ],
163
+ hint: 'Gateway 已自动在后台启动,打开浏览器即可使用',
164
+ launchCmd: null,
165
+ launchNote: '🌐 打开浏览器访问 http://127.0.0.1:18789/',
109
166
  installCmd: 'npm install -g openclaw@latest',
110
- docsUrl: 'https://docs.openclaw.ai',
167
+ docsUrl: 'https://docs.openclaw.ai',
168
+ }
169
+
170
+ /**
171
+ * 自动在后台启动 OpenClaw Gateway
172
+ *
173
+ * 策略(按顺序尝试):
174
+ * 1. openclaw gateway start — 已有 daemon/service 直接启动
175
+ * 2. openclaw gateway --port 18789 (detached) — 前台守护模式
176
+ *
177
+ * 不调用 onboard,因为配置文件已由 configure() 完整写入。
178
+ */
179
+ function _autoStartGateway() {
180
+ const chalk = require('chalk')
181
+ const bin = 'openclaw'
182
+
183
+ console.log(chalk.gray('\n ⚙️ 正在启动 OpenClaw Gateway...'))
184
+
185
+ // 先尝试 gateway start(若已注册为系统服务则直接生效)
186
+ const r1 = spawnSync(bin, ['gateway', 'start'], {
187
+ shell: true,
188
+ timeout: 15000,
189
+ stdio: 'pipe',
190
+ })
191
+ if (r1.status === 0) {
192
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已在后台启动'))
193
+ return
194
+ }
195
+
196
+ // 没有 daemon → 直接 detached 前台守护运行
197
+ const { spawn } = require('child_process')
198
+ const child = spawn(bin, ['gateway', '--port', '18789'], {
199
+ shell: true,
200
+ detached: true,
201
+ stdio: 'ignore',
202
+ })
203
+ child.unref()
204
+
205
+ // 等 2 秒让 gateway 起来
206
+ spawnSync('node', ['-e', 'setTimeout(()=>{},2000)'], { timeout: 3000 })
207
+
208
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
209
+ console.log(chalk.cyan(' → 浏览器打开: http://127.0.0.1:18789/'))
111
210
  }