@simonyea/holysheep-cli 1.2.1 → 1.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "一键配置所有 AI 编程工具接入 HolySheep API — Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw / Aider / Cursor",
5
5
  "keywords": [
6
6
  "claude",
@@ -11,9 +11,10 @@
11
11
  * HolySheep 接入方式:通过 env.ANTHROPIC_API_KEY + env.ANTHROPIC_BASE_URL
12
12
  * 设置 Anthropic provider 自定义 base URL 指向 HolySheep 中继
13
13
  */
14
- const fs = require('fs')
15
- const path = require('path')
16
- const os = require('os')
14
+ const fs = require('fs')
15
+ const path = require('path')
16
+ const os = require('os')
17
+ const { spawnSync } = require('child_process')
17
18
 
18
19
  const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
19
20
  const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
@@ -81,7 +82,12 @@ module.exports = {
81
82
  }
82
83
 
83
84
  writeConfig(config)
84
- return { file: CONFIG_FILE, hot: false }
85
+
86
+ // 自动启动 Gateway 后台服务(无需用户手动操作)
87
+ // 先尝试安装 daemon(系统服务),再 start;失败不阻断配置流程
88
+ _autoStartGateway()
89
+
90
+ return { file: CONFIG_FILE, hot: false, _gatewayStarted: true }
85
91
  },
86
92
  reset() {
87
93
  const config = readConfig()
@@ -99,13 +105,52 @@ module.exports = {
99
105
  writeConfig(config)
100
106
  },
101
107
  getConfigPath() { return CONFIG_FILE },
102
- hint: '切换后重启 OpenClaw 生效;支持 /model 命令切换模型',
108
+ hint: 'Gateway 已自动启动,浏览器打开 http://127.0.0.1:18789/ 即可使用',
103
109
  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
- ],
110
+ launchNote: '🌐 打开浏览器访问 http://127.0.0.1:18789/',
109
111
  installCmd: 'npm install -g openclaw@latest',
110
112
  docsUrl: 'https://docs.openclaw.ai',
111
113
  }
114
+
115
+ /**
116
+ * 自动启动 OpenClaw Gateway 后台服务
117
+ * 1. 先尝试 `openclaw gateway start`(已有 daemon 或上次安装过)
118
+ * 2. 若失败,尝试 `openclaw onboard --install-daemon --yes`(无交互,自动安装系统服务)
119
+ * 3. 再 `openclaw gateway start`
120
+ */
121
+ function _autoStartGateway() {
122
+ const chalk = require('chalk')
123
+ const bin = process.platform === 'win32' ? 'openclaw.cmd' : 'openclaw'
124
+
125
+ console.log(chalk.gray('\n ⚙️ 正在启动 OpenClaw Gateway...'))
126
+
127
+ // 先直接 start
128
+ const r1 = spawnSync(bin, ['gateway', 'start'], { shell: true, timeout: 15000 })
129
+ if (r1.status === 0) {
130
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已在后台启动'))
131
+ return
132
+ }
133
+
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',
140
+ })
141
+
142
+ const r2 = spawnSync(bin, ['gateway', 'start'], { shell: true, timeout: 15000 })
143
+ if (r2.status === 0) {
144
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已在后台启动'))
145
+ } else {
146
+ // 仍然失败 → fallback:前台静默运行(detached)
147
+ const { spawn } = require('child_process')
148
+ const child = spawn(bin, ['gateway'], {
149
+ shell: true,
150
+ detached: true,
151
+ stdio: 'ignore',
152
+ })
153
+ child.unref()
154
+ console.log(chalk.green(' ✓ OpenClaw Gateway 已启动(前台守护模式)'))
155
+ }
156
+ }
@@ -41,6 +41,25 @@ function removeHsBlock(content) {
41
41
  return content.replace(re, '')
42
42
  }
43
43
 
44
+ /**
45
+ * 移除 rc 文件里用户手动写的同名 export/set -gx 行
46
+ * 防止旧值在 holysheep-cli managed 块之后覆盖新值
47
+ */
48
+ function removeStaleExports(content, keys, isFish = false) {
49
+ let result = content
50
+ for (const key of keys) {
51
+ if (isFish) {
52
+ // fish: set -gx KEY "..." 或 set -gx KEY ...
53
+ result = result.replace(new RegExp(`\\n?set\\s+-gx\\s+${escapeRegex(key)}\\s+[^\\n]*\\n?`, 'g'), '\n')
54
+ } else {
55
+ // bash/zsh: export KEY="..." 或 export KEY=...
56
+ result = result.replace(new RegExp(`\\n?export\\s+${escapeRegex(key)}=[^\\n]*\\n?`, 'g'), '\n')
57
+ }
58
+ }
59
+ // 清理多余空行
60
+ return result.replace(/\n{3,}/g, '\n\n')
61
+ }
62
+
44
63
  function buildEnvBlock(envVars, isFish = false) {
45
64
  const lines = [MARKER_START]
46
65
  for (const [k, v] of Object.entries(envVars)) {
@@ -75,8 +94,12 @@ function writeEnvToShell(envVars) {
75
94
  for (const file of files) {
76
95
  let content = ''
77
96
  try { content = fs.readFileSync(file, 'utf8') } catch {}
78
- content = removeHsBlock(content)
79
97
  const isFish = file.endsWith('config.fish')
98
+ // 1. 清理旧的 holysheep managed 块
99
+ content = removeHsBlock(content)
100
+ // 2. 清理用户手动写的同名 export(防止旧值覆盖新值)
101
+ content = removeStaleExports(content, Object.keys(envVars), isFish)
102
+ // 3. 追加新的 managed 块
80
103
  content += buildEnvBlock(envVars, isFish)
81
104
  fs.writeFileSync(file, content, 'utf8')
82
105
  written.push(file)
@@ -84,15 +107,24 @@ function writeEnvToShell(envVars) {
84
107
  return written
85
108
  }
86
109
 
87
- function removeEnvFromShell() {
110
+ function removeEnvFromShell(extraKeys = []) {
111
+ // 默认清理的 key 列表(holysheep 相关的所有环境变量)
112
+ const HS_KEYS = [
113
+ 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL',
114
+ 'OPENAI_API_KEY', 'OPENAI_BASE_URL',
115
+ 'HOLYSHEEP_API_KEY',
116
+ ...extraKeys,
117
+ ]
88
118
  const files = getShellRcFiles()
89
119
  const cleaned = []
90
120
  for (const file of files) {
91
121
  if (!fs.existsSync(file)) continue
122
+ const isFish = file.endsWith('config.fish')
92
123
  let content = fs.readFileSync(file, 'utf8')
93
- const cleaned_content = removeHsBlock(content)
94
- if (cleaned_content !== content) {
95
- fs.writeFileSync(file, cleaned_content, 'utf8')
124
+ let updated = removeHsBlock(content)
125
+ updated = removeStaleExports(updated, HS_KEYS, isFish)
126
+ if (updated !== content) {
127
+ fs.writeFileSync(file, updated, 'utf8')
96
128
  cleaned.push(file)
97
129
  }
98
130
  }