@simonyea/holysheep-cli 1.6.3 → 1.6.6

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/README.md CHANGED
@@ -218,6 +218,9 @@ A: OpenClaw 需要 Node.js 20+,运行 `node --version` 确认版本后重试
218
218
 
219
219
  ## Changelog
220
220
 
221
+ - **v1.6.6** — 修复 Droid CLI 的 GPT-5.4 配置残留问题,同时同步 `~/.factory/settings.json` 和 `~/.factory/config.json`,统一使用 `openai + https://api.holysheep.ai/v1`
222
+ - **v1.6.5** — 修复 HolySheep 对 Droid Responses API 的兼容
223
+ - **v1.6.4** — 修复 OpenClaw 的 npx 运行时检测,避免配置后页面仍卡在 Unauthorized / 未连接状态
221
224
  - **v1.6.3** — OpenClaw 默认模型改为 GPT-5.4,并继续保留 Claude 模型切换能力
222
225
  - **v1.6.2** — 修复 OpenClaw 配置误判与 npx 回退,端口冲突时自动切换空闲端口,并补充 Doctor 诊断
223
226
  - **v1.6.0** — 新增 Droid CLI 一键配置,默认写入 GPT-5.4 / Sonnet 4.6 / Opus 4.6 / MiniMax 2.7 Highspeed / Haiku 4.5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.6.3",
3
+ "version": "1.6.6",
4
4
  "description": "Claude Code/Cursor/Cline API relay for China — ¥1=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
5
5
  "keywords": [
6
6
  "openai-china",
@@ -3,7 +3,7 @@
3
3
  * 配置文件: ~/.factory/settings.json
4
4
  *
5
5
  * 使用 Droid 原生 customModels 配置 HolySheep 的多个模型入口:
6
- * - GPT 走 OpenAI 兼容入口: https://api.holysheep.ai/openai
6
+ * - GPT 走 OpenAI 兼容入口: https://api.holysheep.ai/v1
7
7
  * - Claude 走 Anthropic 入口: https://api.holysheep.ai
8
8
  * - MiniMax 走 Anthropic 入口: https://api.holysheep.ai/minimax
9
9
  */
@@ -13,12 +13,13 @@ const os = require('os')
13
13
 
14
14
  const CONFIG_DIR = path.join(os.homedir(), '.factory')
15
15
  const SETTINGS_FILE = path.join(CONFIG_DIR, 'settings.json')
16
+ const LEGACY_CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
16
17
 
17
18
  const DEFAULT_MODELS = [
18
19
  {
19
20
  model: 'gpt-5.4',
20
21
  id: 'custom:gpt-5.4-0',
21
- baseUrlSuffix: '/openai',
22
+ baseUrlSuffix: '',
22
23
  displayName: 'GPT-5.4',
23
24
  provider: 'openai',
24
25
  },
@@ -66,6 +67,24 @@ function writeSettings(data) {
66
67
  fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), 'utf8')
67
68
  }
68
69
 
70
+ function readLegacyConfig() {
71
+ try {
72
+ if (fs.existsSync(LEGACY_CONFIG_FILE)) {
73
+ return JSON.parse(fs.readFileSync(LEGACY_CONFIG_FILE, 'utf8'))
74
+ }
75
+ } catch {}
76
+ return {}
77
+ }
78
+
79
+ function writeLegacyConfig(data) {
80
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
81
+ fs.writeFileSync(LEGACY_CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8')
82
+ }
83
+
84
+ function isHolySheepModel(item) {
85
+ return typeof item?.baseUrl === 'string' && item.baseUrl.includes('api.holysheep.ai')
86
+ }
87
+
69
88
  function normalizeSelectedModels(selectedModels) {
70
89
  const selected = new Set(
71
90
  Array.isArray(selectedModels) && selectedModels.length > 0
@@ -84,13 +103,17 @@ function normalizeSelectedModels(selectedModels) {
84
103
  return models.length > 0 ? models : DEFAULT_MODELS.map((item, index) => ({ ...item, index }))
85
104
  }
86
105
 
87
- function buildCustomModels(apiKey, baseUrlAnthropic, selectedModels) {
88
- const rootUrl = String(baseUrlAnthropic || '').replace(/\/+$/, '')
106
+ function buildCustomModels(apiKey, baseUrlAnthropic, baseUrlOpenAI, selectedModels) {
107
+ const anthropicRootUrl = String(baseUrlAnthropic || '').replace(/\/+$/, '')
108
+ const openaiRootUrl = String(baseUrlOpenAI || '').replace(/\/+$/, '')
89
109
  return normalizeSelectedModels(selectedModels).map((item) => ({
90
110
  model: item.model,
91
111
  id: item.id,
92
112
  index: item.index,
93
- baseUrl: `${rootUrl}${item.baseUrlSuffix}`,
113
+ baseUrl:
114
+ item.provider === 'openai'
115
+ ? `${openaiRootUrl}${item.baseUrlSuffix}`
116
+ : `${anthropicRootUrl}${item.baseUrlSuffix}`,
94
117
  apiKey,
95
118
  displayName: item.displayName,
96
119
  maxOutputTokens: 64000,
@@ -108,25 +131,37 @@ module.exports = {
108
131
  isConfigured() {
109
132
  const settings = readSettings()
110
133
  const customModels = Array.isArray(settings.customModels) ? settings.customModels : []
111
- return customModels.some((item) =>
112
- typeof item.baseUrl === 'string' && item.baseUrl.includes('api.holysheep.ai')
113
- )
134
+ if (customModels.some(isHolySheepModel)) return true
135
+
136
+ const legacy = readLegacyConfig()
137
+ const legacyModels = Array.isArray(legacy.customModels) ? legacy.customModels : []
138
+ return legacyModels.some(isHolySheepModel)
114
139
  },
115
- configure(apiKey, baseUrlAnthropic, _baseUrlOpenAI, _primaryModel, selectedModels) {
140
+ configure(apiKey, baseUrlAnthropic, baseUrlOpenAI, _primaryModel, selectedModels) {
141
+ const nextModels = buildCustomModels(apiKey, baseUrlAnthropic, baseUrlOpenAI, selectedModels)
142
+
116
143
  const settings = readSettings()
117
144
  const preservedModels = Array.isArray(settings.customModels)
118
- ? settings.customModels.filter(
119
- (item) => !(typeof item.baseUrl === 'string' && item.baseUrl.includes('api.holysheep.ai'))
120
- )
145
+ ? settings.customModels.filter((item) => !isHolySheepModel(item))
121
146
  : []
122
-
123
147
  settings.customModels = [
124
- ...buildCustomModels(apiKey, baseUrlAnthropic, selectedModels),
148
+ ...nextModels,
125
149
  ...preservedModels,
126
150
  ]
127
151
  settings.logoAnimation = 'off'
128
152
  writeSettings(settings)
129
153
 
154
+ const legacy = readLegacyConfig()
155
+ const preservedLegacyModels = Array.isArray(legacy.customModels)
156
+ ? legacy.customModels.filter((item) => !isHolySheepModel(item))
157
+ : []
158
+ legacy.customModels = [
159
+ ...nextModels,
160
+ ...preservedLegacyModels,
161
+ ]
162
+ legacy.logoAnimation = 'off'
163
+ writeLegacyConfig(legacy)
164
+
130
165
  return {
131
166
  file: SETTINGS_FILE,
132
167
  hot: true,
@@ -135,11 +170,15 @@ module.exports = {
135
170
  reset() {
136
171
  const settings = readSettings()
137
172
  if (Array.isArray(settings.customModels)) {
138
- settings.customModels = settings.customModels.filter(
139
- (item) => !(typeof item.baseUrl === 'string' && item.baseUrl.includes('api.holysheep.ai'))
140
- )
173
+ settings.customModels = settings.customModels.filter((item) => !isHolySheepModel(item))
141
174
  }
142
175
  writeSettings(settings)
176
+
177
+ const legacy = readLegacyConfig()
178
+ if (Array.isArray(legacy.customModels)) {
179
+ legacy.customModels = legacy.customModels.filter((item) => !isHolySheepModel(item))
180
+ }
181
+ writeLegacyConfig(legacy)
143
182
  },
144
183
  getConfigPath() { return SETTINGS_FILE },
145
184
  hint: '已写入 ~/.factory/settings.json;重启 Droid 后可见 HolySheep 模型列表',
@@ -74,34 +74,31 @@ function firstLine(text) {
74
74
  }
75
75
 
76
76
  function getOpenClawVersion(preferNpx = false) {
77
- const result = runOpenClaw(['--version'], { preferNpx, timeout: 15000 })
77
+ const result = runOpenClaw(['--version'], { preferNpx, timeout: preferNpx ? 60000 : 15000 })
78
78
  if (result.status !== 0) return null
79
79
  return firstLine(result.stdout)
80
80
  }
81
81
 
82
82
  function detectRuntime() {
83
83
  const preferNpx = getPreferredRuntime()
84
- const version = getOpenClawVersion(preferNpx)
84
+ const preferredRunner = getRunner(preferNpx)
85
85
 
86
- if (version) {
87
- const runner = getRunner(preferNpx)
86
+ if (preferredRunner) {
88
87
  return {
89
88
  available: true,
90
- via: runner?.via || (preferNpx ? 'npx' : 'binary'),
91
- command: runner?.label || (preferNpx ? 'npx openclaw' : 'openclaw'),
92
- version,
89
+ via: preferredRunner.via,
90
+ command: preferredRunner.label,
91
+ version: getOpenClawVersion(preferNpx),
93
92
  }
94
93
  }
95
94
 
96
- if (!preferNpx && hasNpx()) {
97
- const fallbackVersion = getOpenClawVersion(true)
98
- if (fallbackVersion) {
99
- return {
100
- available: true,
101
- via: 'npx',
102
- command: 'npx openclaw',
103
- version: fallbackVersion,
104
- }
95
+ const fallbackRunner = getRunner(true)
96
+ if (fallbackRunner) {
97
+ return {
98
+ available: true,
99
+ via: fallbackRunner.via,
100
+ command: fallbackRunner.label,
101
+ version: getOpenClawVersion(true),
105
102
  }
106
103
  }
107
104
 
@@ -192,6 +189,11 @@ function getLaunchCommand(port = getConfiguredGatewayPort()) {
192
189
  return `${runtime} gateway --port ${port}`
193
190
  }
194
191
 
192
+ function getDashboardCommand() {
193
+ const runtime = module.exports._lastRuntimeCommand || (hasOpenClawBinary() ? 'openclaw' : 'npx openclaw')
194
+ return `${runtime} dashboard --no-open`
195
+ }
196
+
195
197
  function buildProviderName(baseUrl, prefix) {
196
198
  const hostname = new URL(baseUrl).hostname.replace(/\./g, '-')
197
199
  return `${prefix}-${hostname}`
@@ -365,6 +367,18 @@ function _startGateway(port, preferNpx = false, preferService = true) {
365
367
  return false
366
368
  }
367
369
 
370
+ function getDashboardUrl(port, preferNpx = false) {
371
+ const result = runOpenClaw(['dashboard', '--no-open'], {
372
+ preferNpx,
373
+ timeout: preferNpx ? 60000 : 20000,
374
+ })
375
+ if (result.status === 0) {
376
+ const match = String(result.stdout || '').match(/Dashboard URL:\s*(\S+)/)
377
+ if (match) return match[1]
378
+ }
379
+ return `http://127.0.0.1:${port}/`
380
+ }
381
+
368
382
  module.exports = {
369
383
  name: 'OpenClaw',
370
384
  id: 'openclaw',
@@ -455,10 +469,11 @@ module.exports = {
455
469
  console.log(chalk.yellow(' ⚠️ Gateway 启动中,稍等几秒后刷新浏览器'))
456
470
  }
457
471
 
458
- const dashUrl = `http://127.0.0.1:${gatewayPort}/`
459
- console.log(chalk.cyan('\n → 浏览器打开(无需 token):'))
472
+ const dashUrl = getDashboardUrl(gatewayPort, runtime.via === 'npx')
473
+ console.log(chalk.cyan('\n → 浏览器打开(推荐使用此地址):'))
460
474
  console.log(chalk.bold.cyan(` ${dashUrl}`))
461
475
  console.log(chalk.gray(` 默认模型: ${OPENCLAW_DEFAULT_MODEL}`))
476
+ console.log(chalk.gray(' 如在 Windows 上打开裸 http://127.0.0.1:PORT/ 仍报 Unauthorized,请使用上面的 dashboard 地址'))
462
477
 
463
478
  return {
464
479
  file: CONFIG_FILE,
@@ -480,11 +495,15 @@ module.exports = {
480
495
  get hint() {
481
496
  return `Gateway 已启动,默认模型为 ${getConfiguredPrimaryModel() || OPENCLAW_DEFAULT_MODEL}`
482
497
  },
483
- get launchCmd() {
484
- return getLaunchCommand(getConfiguredGatewayPort())
498
+ get launchSteps() {
499
+ const port = getConfiguredGatewayPort()
500
+ return [
501
+ { cmd: getLaunchCommand(port), note: '先启动 OpenClaw Gateway' },
502
+ { cmd: getDashboardCommand(), note: '再生成/打开可直接连接的 Dashboard 地址(推荐)' },
503
+ ]
485
504
  },
486
505
  get launchNote() {
487
- return `🌐 打开浏览器: http://127.0.0.1:${getConfiguredGatewayPort()}/`
506
+ return `🌐 推荐运行 ${getDashboardCommand()};Windows 上不要只打开裸 http://127.0.0.1:${getConfiguredGatewayPort()}/`
488
507
  },
489
508
  installCmd: 'npm install -g openclaw@latest',
490
509
  docsUrl: 'https://docs.openclaw.ai',