@simonyea/holysheep-cli 2.1.40 → 2.1.41
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/dist/configure-worker.js +4491 -0
- package/dist/index.js +9591 -0
- package/dist/process-proxy-inject.js +117 -0
- package/package.json +20 -7
- package/.gitea/workflows/sanity.yml +0 -125
- package/scripts/check-tarball-size.js +0 -44
- package/src/commands/balance.js +0 -57
- package/src/commands/claude-proxy.js +0 -248
- package/src/commands/claude.js +0 -135
- package/src/commands/doctor.js +0 -282
- package/src/commands/login.js +0 -211
- package/src/commands/openclaw.js +0 -258
- package/src/commands/reset.js +0 -53
- package/src/commands/setup.js +0 -493
- package/src/commands/upgrade.js +0 -168
- package/src/commands/webui.js +0 -622
- package/src/index.js +0 -226
- package/src/tools/aider.js +0 -78
- package/src/tools/antigravity.js +0 -42
- package/src/tools/claude-code.js +0 -228
- package/src/tools/claude-process-proxy.js +0 -1030
- package/src/tools/codex.js +0 -254
- package/src/tools/continue.js +0 -146
- package/src/tools/cursor.js +0 -71
- package/src/tools/droid.js +0 -281
- package/src/tools/env-config.js +0 -185
- package/src/tools/gemini-cli.js +0 -82
- package/src/tools/hermes.js +0 -354
- package/src/tools/index.js +0 -13
- package/src/tools/openclaw-bridge.js +0 -987
- package/src/tools/openclaw.js +0 -925
- package/src/tools/opencode.js +0 -227
- package/src/tools/process-proxy-inject.js +0 -142
- package/src/utils/config.js +0 -54
- package/src/utils/shell.js +0 -342
- package/src/utils/which.js +0 -176
- package/src/webui/aionui-runtime-fetcher.js +0 -429
- package/src/webui/aionui-runtime.js +0 -139
- package/src/webui/aionui-wrapper.js +0 -734
- package/src/webui/configure-worker.js +0 -67
- package/src/webui/server.js +0 -1572
- package/src/webui/workspace-runtime.js +0 -288
- package/src/webui/workspace-store.js +0 -325
- /package/{src/webui → dist}/index.html +0 -0
- /package/{src/tools → dist}/pty-hermes-wrapper.py +0 -0
package/src/commands/setup.js
DELETED
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hs setup — 一键配置所有 AI 工具接入 HolySheep API
|
|
3
|
-
*/
|
|
4
|
-
const inquirer = require('inquirer')
|
|
5
|
-
const chalk = require('chalk')
|
|
6
|
-
const ora = require('ora')
|
|
7
|
-
const { execSync, spawnSync } = require('child_process')
|
|
8
|
-
const pkg = require('../../package.json')
|
|
9
|
-
const { saveConfig, getApiKey, BASE_URL_ANTHROPIC, BASE_URL_OPENAI, SHOP_URL } = require('../utils/config')
|
|
10
|
-
const {
|
|
11
|
-
writeEnvToShell,
|
|
12
|
-
removeEnvFromShell,
|
|
13
|
-
ensureWindowsUserPathHasNpmBin,
|
|
14
|
-
installWindowsCliShims,
|
|
15
|
-
removeWindowsUserEnvVars,
|
|
16
|
-
} = require('../utils/shell')
|
|
17
|
-
const { commandExists } = require('../utils/which')
|
|
18
|
-
const TOOLS = require('../tools')
|
|
19
|
-
|
|
20
|
-
async function getLatestHolysheepVersion() {
|
|
21
|
-
return new Promise((resolve) => {
|
|
22
|
-
const https = require('https')
|
|
23
|
-
const req = https.get(
|
|
24
|
-
'https://registry.npmjs.org/@simonyea/holysheep-cli/latest',
|
|
25
|
-
{ timeout: 5000 },
|
|
26
|
-
(res) => {
|
|
27
|
-
let data = ''
|
|
28
|
-
res.on('data', chunk => { data += chunk })
|
|
29
|
-
res.on('end', () => {
|
|
30
|
-
try { resolve(JSON.parse(data).version || null) } catch { resolve(null) }
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
)
|
|
34
|
-
req.on('error', () => resolve(null))
|
|
35
|
-
req.setTimeout(5000, () => { req.destroy(); resolve(null) })
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function checkAndUpdateSelf() {
|
|
40
|
-
process.stdout.write(chalk.gray('检查 HolySheep CLI 版本... '))
|
|
41
|
-
const latest = await getLatestHolysheepVersion()
|
|
42
|
-
|
|
43
|
-
if (!latest || latest === pkg.version) {
|
|
44
|
-
console.log(chalk.green(`v${pkg.version} 已是最新`))
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(chalk.yellow(`发现新版本 v${latest}(当前 v${pkg.version})`))
|
|
49
|
-
|
|
50
|
-
const { doUpdate } = await inquirer.prompt([{
|
|
51
|
-
type: 'confirm',
|
|
52
|
-
name: 'doUpdate',
|
|
53
|
-
message: `立即更新到 v${latest}?(推荐,确保配置逻辑最新)`,
|
|
54
|
-
default: true,
|
|
55
|
-
}])
|
|
56
|
-
|
|
57
|
-
if (!doUpdate) {
|
|
58
|
-
console.log(chalk.gray(' 跳过更新,继续使用当前版本配置...'))
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const spinner = ora(`更新 HolySheep CLI → v${latest}...`).start()
|
|
63
|
-
try {
|
|
64
|
-
execSync('npm install -g @simonyea/holysheep-cli@latest', { stdio: 'ignore', timeout: 60000 })
|
|
65
|
-
spinner.succeed(`HolySheep CLI 已更新到 v${latest}`)
|
|
66
|
-
console.log(chalk.cyan(' 请重新运行 hs setup 以使用最新版本完成配置'))
|
|
67
|
-
process.exit(0)
|
|
68
|
-
} catch (e) {
|
|
69
|
-
spinner.fail(`更新失败: ${e.message}`)
|
|
70
|
-
console.log(chalk.gray(' 可手动运行: npm install -g @simonyea/holysheep-cli@latest'))
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// 工具的自动安装命令(npm/pip)
|
|
75
|
-
const AUTO_INSTALL = {
|
|
76
|
-
'claude-code': {
|
|
77
|
-
cmd: process.platform === 'win32'
|
|
78
|
-
? 'powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "irm https://claude.ai/install.ps1 | iex"'
|
|
79
|
-
: 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
80
|
-
mgr: process.platform === 'win32' ? 'powershell.exe' : 'bash',
|
|
81
|
-
},
|
|
82
|
-
'codex': { cmd: 'npm install -g @openai/codex', mgr: 'npm' },
|
|
83
|
-
'gemini-cli': { cmd: 'npm install -g @google/gemini-cli', mgr: 'npm' },
|
|
84
|
-
'opencode': { cmd: 'npm install -g opencode-ai', mgr: 'npm' },
|
|
85
|
-
'openclaw': { cmd: 'npm install -g openclaw@latest', mgr: 'npm' },
|
|
86
|
-
'aider': { cmd: 'pip install aider-chat', mgr: 'pip' },
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function getWindowsImmediateLaunchCmd(tool) {
|
|
90
|
-
if (process.platform !== 'win32' || !tool?.launchCmd) return null
|
|
91
|
-
|
|
92
|
-
if (/^npx\b/i.test(tool.launchCmd)) {
|
|
93
|
-
return tool.launchCmd
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const [cmdBin, ...cmdArgs] = tool.launchCmd.split(' ')
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const npmPrefix = execSync('npm prefix -g', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
100
|
-
if (npmPrefix) {
|
|
101
|
-
const directCmd = `${npmPrefix}\\${cmdBin}.cmd`
|
|
102
|
-
return `& "${directCmd}"${cmdArgs.length ? ` ${cmdArgs.join(' ')}` : ''}`
|
|
103
|
-
}
|
|
104
|
-
} catch {}
|
|
105
|
-
|
|
106
|
-
const appData = process.env.APPDATA
|
|
107
|
-
if (appData) {
|
|
108
|
-
return `& "${appData}\\npm\\${cmdBin}.cmd"${cmdArgs.length ? ` ${cmdArgs.join(' ')}` : ''}`
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return null
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// 检测当前 shell 里的 hs 命令是否已包含 claude 子命令
|
|
115
|
-
function isHsClaudeAvailable() {
|
|
116
|
-
try {
|
|
117
|
-
const out = execSync('hs --help', {
|
|
118
|
-
encoding: 'utf8',
|
|
119
|
-
timeout: 4000,
|
|
120
|
-
shell: process.platform === 'win32',
|
|
121
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
122
|
-
})
|
|
123
|
-
return out.includes('claude')
|
|
124
|
-
} catch {
|
|
125
|
-
return false
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function getPreferredCliPrefix() {
|
|
130
|
-
const mainEntry = String(require.main?.filename || '')
|
|
131
|
-
const runningFromNpxCache =
|
|
132
|
-
/[\\/]_npx[\\/]/i.test(mainEntry) ||
|
|
133
|
-
/[\\/]npm-cache[\\/]/i.test(mainEntry)
|
|
134
|
-
|
|
135
|
-
if (runningFromNpxCache) {
|
|
136
|
-
return `npx @simonyea/holysheep-cli@${pkg.version}`
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return commandExists('hs')
|
|
140
|
-
? 'hs'
|
|
141
|
-
: `npx @simonyea/holysheep-cli@${pkg.version}`
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function getPreferredLaunchCmd(tool) {
|
|
145
|
-
if (!tool?.launchCmd) return null
|
|
146
|
-
if (!tool.launchCmd.startsWith('hs')) return tool.launchCmd
|
|
147
|
-
const prefix = getPreferredCliPrefix()
|
|
148
|
-
return tool.launchCmd.replace(/^hs\b/, prefix)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function canAutoInstall(toolId) {
|
|
152
|
-
return !!AUTO_INSTALL[toolId]
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function tryAutoInstall(tool) {
|
|
156
|
-
const info = AUTO_INSTALL[tool.id]
|
|
157
|
-
if (!info) return false
|
|
158
|
-
|
|
159
|
-
// 检查 npm/pip 是否可用
|
|
160
|
-
try {
|
|
161
|
-
if (process.platform === 'win32' && info.mgr.toLowerCase() === 'powershell.exe') {
|
|
162
|
-
execSync('where powershell.exe', { stdio: 'ignore' })
|
|
163
|
-
} else {
|
|
164
|
-
execSync(`${info.mgr} --version`, { stdio: 'ignore' })
|
|
165
|
-
}
|
|
166
|
-
} catch {
|
|
167
|
-
console.log(chalk.red(` ✗ 未找到 ${info.mgr},无法自动安装 ${tool.name}`))
|
|
168
|
-
return false
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const spinner = ora(`正在安装 ${tool.name}...`).start()
|
|
172
|
-
try {
|
|
173
|
-
const ret = spawnSync(info.cmd, [], { stdio: 'inherit', shell: true })
|
|
174
|
-
if (ret.status !== 0) {
|
|
175
|
-
spinner.fail(`安装失败,请手动运行: ${chalk.cyan(info.cmd)}`)
|
|
176
|
-
return false
|
|
177
|
-
}
|
|
178
|
-
spinner.succeed(`${tool.name} 安装成功`)
|
|
179
|
-
// Windows PATH 在当前进程内不会刷新,但安装已成功,直接继续配置
|
|
180
|
-
// 非 Windows 再额外做一次检测
|
|
181
|
-
if (process.platform !== 'win32' && !tool.checkInstalled()) {
|
|
182
|
-
console.log(chalk.yellow(` ⚠ 安装后未检测到命令,尝试直接配置...`))
|
|
183
|
-
}
|
|
184
|
-
if (process.platform === 'win32') {
|
|
185
|
-
ensureWindowsUserPathHasNpmBin()
|
|
186
|
-
tool._winJustInstalled = true // 标记为 Windows 刚装的,摘要里特殊处理
|
|
187
|
-
}
|
|
188
|
-
return true // 安装成功就视为可配置
|
|
189
|
-
} catch (e) {
|
|
190
|
-
spinner.fail(`安装失败: ${e.message}`)
|
|
191
|
-
return false
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function setup(options) {
|
|
196
|
-
let windowsCliArtifacts = []
|
|
197
|
-
if (process.platform === 'win32') {
|
|
198
|
-
windowsCliArtifacts = [
|
|
199
|
-
...installWindowsCliShims(),
|
|
200
|
-
...ensureWindowsUserPathHasNpmBin(),
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
console.log()
|
|
205
|
-
console.log(chalk.bold('🐑 HolySheep CLI — 一键配置 AI 工具'))
|
|
206
|
-
console.log(chalk.gray('━'.repeat(50)))
|
|
207
|
-
console.log()
|
|
208
|
-
|
|
209
|
-
await checkAndUpdateSelf()
|
|
210
|
-
console.log()
|
|
211
|
-
|
|
212
|
-
// Step 1: 获取 API Key
|
|
213
|
-
let apiKey = options.key || getApiKey()
|
|
214
|
-
|
|
215
|
-
if (!apiKey) {
|
|
216
|
-
console.log(chalk.yellow('需要 API Key 才能配置工具。'))
|
|
217
|
-
console.log(chalk.cyan(`👉 免费注册获取 Key:${SHOP_URL}/register`))
|
|
218
|
-
console.log(chalk.gray(` 支持微信/支付宝充值,按量计费`))
|
|
219
|
-
console.log(chalk.gray(` (¥10 起充,按量计费,支持微信/支付宝)`))
|
|
220
|
-
console.log(chalk.gray(`提示:可先运行 ${chalk.cyan('hs login')} 登录并保存 Key,之后 setup 将自动读取。`))
|
|
221
|
-
if (process.platform === 'win32') {
|
|
222
|
-
console.log(chalk.gray(` ⚠️ Windows 用户:如果 ${chalk.cyan('hs')} 命令暂时找不到,请先运行:`))
|
|
223
|
-
console.log(chalk.gray(` ${chalk.white('npx @simonyea/holysheep-cli@latest login')}`))
|
|
224
|
-
console.log(chalk.gray(` 或重启终端后再试`))
|
|
225
|
-
}
|
|
226
|
-
console.log()
|
|
227
|
-
|
|
228
|
-
const { key } = await inquirer.prompt([{
|
|
229
|
-
type: 'password',
|
|
230
|
-
name: 'key',
|
|
231
|
-
message: 'API Key (cr_xxx):',
|
|
232
|
-
validate: v => v.startsWith('cr_') ? true : '请输入以 cr_ 开头的 API Key',
|
|
233
|
-
}])
|
|
234
|
-
apiKey = key
|
|
235
|
-
} else {
|
|
236
|
-
console.log(`${chalk.green('✓')} 已保存的 API Key: ${chalk.cyan(maskKey(apiKey))}`)
|
|
237
|
-
const { useExisting } = await inquirer.prompt([{
|
|
238
|
-
type: 'confirm',
|
|
239
|
-
name: 'useExisting',
|
|
240
|
-
message: '使用此 Key 继续?(输入 N 可更换)',
|
|
241
|
-
default: true,
|
|
242
|
-
}])
|
|
243
|
-
if (!useExisting) {
|
|
244
|
-
const { key } = await inquirer.prompt([{
|
|
245
|
-
type: 'password',
|
|
246
|
-
name: 'key',
|
|
247
|
-
message: '请输入新的 API Key (cr_xxx):',
|
|
248
|
-
validate: v => v.startsWith('cr_') ? true : '请输入以 cr_ 开头的 API Key',
|
|
249
|
-
}])
|
|
250
|
-
apiKey = key
|
|
251
|
-
saveConfig({ apiKey: key })
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Step 1.5: 选择要配置的模型
|
|
256
|
-
const MODEL_CHOICES = [
|
|
257
|
-
{ name: 'gpt-5.4 (GPT 5.4, 通用编码)', value: 'gpt-5.4', checked: true },
|
|
258
|
-
{ name: 'gpt-5.3-codex-spark (GPT 5.3 Codex Spark, 编码)', value: 'gpt-5.3-codex-spark', checked: true },
|
|
259
|
-
{ name: 'claude-sonnet-4-6 (Sonnet 4.6, 均衡推荐)', value: 'claude-sonnet-4-6', checked: true },
|
|
260
|
-
{ name: 'claude-opus-4-6 (Opus 4.6, 强力旗舰)', value: 'claude-opus-4-6', checked: true },
|
|
261
|
-
{ name: 'MiniMax-M2.7-highspeed (高速经济版)', value: 'MiniMax-M2.7-highspeed', checked: true },
|
|
262
|
-
{ name: 'claude-haiku-4-5 (Haiku 4.5, 轻快便宜)', value: 'claude-haiku-4-5', checked: true },
|
|
263
|
-
]
|
|
264
|
-
const { selectedModels } = await inquirer.prompt([{
|
|
265
|
-
type: 'checkbox',
|
|
266
|
-
name: 'selectedModels',
|
|
267
|
-
message: '选择要配置的模型(默认全选,空格取消选中):',
|
|
268
|
-
choices: MODEL_CHOICES,
|
|
269
|
-
pageSize: 5,
|
|
270
|
-
}])
|
|
271
|
-
const primaryModel = selectedModels.find(m => m.startsWith('claude-')) || selectedModels[0] || 'claude-sonnet-4-6'
|
|
272
|
-
|
|
273
|
-
// Step 2: 选择工具(已安装 + 未安装分组显示)
|
|
274
|
-
const installedTools = TOOLS.filter(t => t.checkInstalled())
|
|
275
|
-
const uninstalledTools = TOOLS.filter(t => !t.checkInstalled())
|
|
276
|
-
|
|
277
|
-
const choices = []
|
|
278
|
-
if (installedTools.length) {
|
|
279
|
-
choices.push(new inquirer.Separator(chalk.green('── 已安装 ──')))
|
|
280
|
-
installedTools.forEach(t => choices.push({
|
|
281
|
-
name: `${chalk.green('●')} ${t.name.padEnd(18)} ${chalk.gray('(已安装)')}`,
|
|
282
|
-
value: t.id,
|
|
283
|
-
short: t.name,
|
|
284
|
-
checked: true, // 已安装的默认全选
|
|
285
|
-
}))
|
|
286
|
-
}
|
|
287
|
-
if (uninstalledTools.length) {
|
|
288
|
-
choices.push(new inquirer.Separator(chalk.gray('── 未安装(可自动安装)──')))
|
|
289
|
-
uninstalledTools.forEach(t => choices.push({
|
|
290
|
-
name: `${chalk.gray('○')} ${t.name.padEnd(18)} ${canAutoInstall(t.id) ? chalk.cyan('(选中后自动安装)') : chalk.gray('(需手动安装)')}`,
|
|
291
|
-
value: t.id,
|
|
292
|
-
short: t.name,
|
|
293
|
-
checked: false,
|
|
294
|
-
}))
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const { toolIds } = await inquirer.prompt([{
|
|
298
|
-
type: 'checkbox',
|
|
299
|
-
name: 'toolIds',
|
|
300
|
-
message: '选择要配置的工具(空格选中,回车确认):',
|
|
301
|
-
choices,
|
|
302
|
-
pageSize: 14,
|
|
303
|
-
}])
|
|
304
|
-
|
|
305
|
-
if (toolIds.length === 0) {
|
|
306
|
-
console.log(chalk.yellow('\n未选择任何工具,退出。'))
|
|
307
|
-
return
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
console.log()
|
|
311
|
-
|
|
312
|
-
// Step 3: 对未安装但被选中的工具,询问是否自动安装
|
|
313
|
-
const selectedTools = TOOLS.filter(t => toolIds.includes(t.id))
|
|
314
|
-
const needInstall = selectedTools.filter(t => !t.checkInstalled() && canAutoInstall(t.id))
|
|
315
|
-
const cantInstall = selectedTools.filter(t => !t.checkInstalled() && !canAutoInstall(t.id))
|
|
316
|
-
const justInstalled = new Set() // 记录本次刚安装成功的工具 id
|
|
317
|
-
|
|
318
|
-
// 提示不能自动安装的工具
|
|
319
|
-
if (cantInstall.length) {
|
|
320
|
-
console.log(chalk.yellow(`以下工具需要手动安装后再运行 hs setup:`))
|
|
321
|
-
cantInstall.forEach(t => console.log(` ${chalk.gray('→')} ${t.name}: ${chalk.cyan(t.installCmd)}`))
|
|
322
|
-
console.log()
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// 自动安装可以安装的工具
|
|
326
|
-
if (needInstall.length) {
|
|
327
|
-
const { doInstall } = await inquirer.prompt([{
|
|
328
|
-
type: 'confirm',
|
|
329
|
-
name: 'doInstall',
|
|
330
|
-
message: `检测到 ${needInstall.map(t => chalk.cyan(t.name)).join('、')} 未安装,现在自动安装?`,
|
|
331
|
-
default: true,
|
|
332
|
-
}])
|
|
333
|
-
|
|
334
|
-
if (doInstall) {
|
|
335
|
-
for (const tool of needInstall) {
|
|
336
|
-
const ok = await tryAutoInstall(tool)
|
|
337
|
-
if (ok) {
|
|
338
|
-
justInstalled.add(tool.id)
|
|
339
|
-
} else if (tool.id === 'openclaw') {
|
|
340
|
-
// openclaw 安装失败时(如无 git),改用 npx 模式继续配置
|
|
341
|
-
// 这里直接标记为本次可配置,具体执行阶段再走 npx fallback
|
|
342
|
-
console.log(chalk.yellow(` ⚠️ 全局安装失败,将使用 npx openclaw 代替`))
|
|
343
|
-
tool._useNpx = true
|
|
344
|
-
justInstalled.add(tool.id)
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
console.log()
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Step 4: 配置每个已安装的工具(包含刚刚安装成功的)
|
|
352
|
-
const envVarsToWrite = {}
|
|
353
|
-
const results = []
|
|
354
|
-
const toConfigureTools = selectedTools.filter(t => t.checkInstalled() || justInstalled.has(t.id))
|
|
355
|
-
|
|
356
|
-
if (toConfigureTools.length === 0) {
|
|
357
|
-
console.log(chalk.yellow('没有可配置的工具(请先安装),退出。'))
|
|
358
|
-
return
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
for (const tool of toConfigureTools) {
|
|
362
|
-
const spinner = ora(`配置 ${tool.name}...`).start()
|
|
363
|
-
try {
|
|
364
|
-
const result = tool.configure(apiKey, BASE_URL_ANTHROPIC, BASE_URL_OPENAI, primaryModel, selectedModels)
|
|
365
|
-
|
|
366
|
-
if (result.manual) {
|
|
367
|
-
spinner.info(`${chalk.yellow(tool.name)} 需要手动配置:`)
|
|
368
|
-
result.steps.forEach((s, i) => console.log(` ${chalk.gray(i + 1 + '.')} ${s}`))
|
|
369
|
-
results.push({ tool, status: 'manual' })
|
|
370
|
-
} else if (result.warning) {
|
|
371
|
-
if (result.envVars) Object.assign(envVarsToWrite, result.envVars)
|
|
372
|
-
spinner.warn(`${chalk.yellow(tool.name)} ${chalk.gray(result.file ? `→ ${result.file}` : '')}`)
|
|
373
|
-
console.log(chalk.yellow(` ⚠️ ${result.warning}`))
|
|
374
|
-
results.push({ tool, status: 'warning', result })
|
|
375
|
-
} else {
|
|
376
|
-
if (result.envVars) Object.assign(envVarsToWrite, result.envVars)
|
|
377
|
-
spinner.succeed(`${chalk.green(tool.name)} ${chalk.gray(result.file ? `→ ${result.file}` : '')}`)
|
|
378
|
-
if (Array.isArray(result.extraFiles) && result.extraFiles.length > 0) {
|
|
379
|
-
console.log(chalk.gray(` ↳ 附加配置: ${result.extraFiles.join(', ')}`))
|
|
380
|
-
}
|
|
381
|
-
results.push({ tool, status: 'ok', result })
|
|
382
|
-
}
|
|
383
|
-
} catch (e) {
|
|
384
|
-
spinner.fail(`${chalk.red(tool.name)}: ${e.message}`)
|
|
385
|
-
results.push({ tool, status: 'error', error: e.message })
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Step 5: 清理 shell 环境变量(跳过已选全局环境变量工具的情况)
|
|
390
|
-
if (!toolIds.includes('env-config')) {
|
|
391
|
-
const staleKeys = ['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']
|
|
392
|
-
try {
|
|
393
|
-
const cleaned = removeEnvFromShell(staleKeys)
|
|
394
|
-
if (cleaned.length > 0) {
|
|
395
|
-
console.log(chalk.gray(`已清理 shell 中的过时变量: ${cleaned.map(f => chalk.cyan(f)).join(', ')}`))
|
|
396
|
-
}
|
|
397
|
-
} catch {}
|
|
398
|
-
if (process.platform === 'win32') {
|
|
399
|
-
const removed = removeWindowsUserEnvVars(staleKeys)
|
|
400
|
-
if (removed.length > 0) {
|
|
401
|
-
console.log(chalk.gray(`已移除过时环境变量: ${removed.map(item => chalk.cyan(item)).join(', ')}`))
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (process.platform === 'win32' && windowsCliArtifacts.length > 0) {
|
|
407
|
-
console.log(chalk.gray(`Windows 启动器已就绪: ${windowsCliArtifacts.map(item => chalk.cyan(item)).join(', ')}`))
|
|
408
|
-
console.log()
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Step 6: 保存 API Key
|
|
412
|
-
saveConfig({ apiKey })
|
|
413
|
-
|
|
414
|
-
// 摘要
|
|
415
|
-
console.log()
|
|
416
|
-
console.log(chalk.bold('━'.repeat(50)))
|
|
417
|
-
console.log(chalk.green.bold('✅ 配置完成!'))
|
|
418
|
-
console.log()
|
|
419
|
-
|
|
420
|
-
const ok = results.filter(r => r.status === 'ok')
|
|
421
|
-
const manual = results.filter(r => r.status === 'manual')
|
|
422
|
-
const errors = results.filter(r => r.status === 'error')
|
|
423
|
-
|
|
424
|
-
if (ok.length) {
|
|
425
|
-
console.log(chalk.green(`已配置 ${ok.length} 个工具:`))
|
|
426
|
-
ok.forEach(r => {
|
|
427
|
-
const hot = r.result?.hot ? chalk.cyan(' (热切换,无需重启)') : chalk.gray(' (重启终端生效)')
|
|
428
|
-
console.log(` ✓ ${r.tool.name}${hot}`)
|
|
429
|
-
if (r.tool.hint) console.log(` ${chalk.gray('💡 ' + r.tool.hint)}`)
|
|
430
|
-
if (r.result?.dashboardUrl) {
|
|
431
|
-
console.log(` ${chalk.gray('🌐 Dashboard:')} ${chalk.cyan.bold(r.result.dashboardUrl)}`)
|
|
432
|
-
}
|
|
433
|
-
// 显示启动命令
|
|
434
|
-
if (r.tool.launchSteps) {
|
|
435
|
-
// 多步骤启动(如 openclaw)
|
|
436
|
-
console.log(` ${chalk.gray('▶ 启动步骤:')}`)
|
|
437
|
-
r.tool.launchSteps.forEach((s, i) => {
|
|
438
|
-
console.log(` ${chalk.gray(` ${i + 1}.`)} ${chalk.cyan.bold(s.cmd)} ${chalk.gray(s.note)}`)
|
|
439
|
-
})
|
|
440
|
-
} else if (r.tool.launchCmd) {
|
|
441
|
-
const preferredLaunchCmd = getPreferredLaunchCmd(r.tool)
|
|
442
|
-
if (r.tool._winJustInstalled) {
|
|
443
|
-
const immediateCmd = getWindowsImmediateLaunchCmd({ ...r.tool, launchCmd: preferredLaunchCmd || r.tool.launchCmd })
|
|
444
|
-
console.log(` ${chalk.gray('▶ 立即启动:')} ${chalk.cyan.bold(immediateCmd || preferredLaunchCmd || r.tool.launchCmd)}`)
|
|
445
|
-
console.log(` ${chalk.gray('▶ 新开终端后:')} ${chalk.cyan.bold(preferredLaunchCmd || r.tool.launchCmd)}`)
|
|
446
|
-
} else if (r.tool.id === 'claude-code' && !isHsClaudeAvailable()) {
|
|
447
|
-
// hs claude 在当前 shell 不可用(hs 未安装或版本过旧),显示两条命令
|
|
448
|
-
const npxCmd = `npx @simonyea/holysheep-cli@${pkg.version} claude`
|
|
449
|
-
console.log(` ${chalk.gray('▶ 立即启动:')} ${chalk.cyan.bold(npxCmd)}`)
|
|
450
|
-
console.log(` ${chalk.gray('▶ 新开终端后:')} ${chalk.cyan.bold('hs claude')}`)
|
|
451
|
-
} else {
|
|
452
|
-
console.log(` ${chalk.gray('▶ 启动命令:')} ${chalk.cyan.bold(preferredLaunchCmd || r.tool.launchCmd)}`)
|
|
453
|
-
}
|
|
454
|
-
if (r.tool.launchNote) console.log(` ${chalk.gray(' ' + r.tool.launchNote)}`)
|
|
455
|
-
} else if (r.tool.launchNote) {
|
|
456
|
-
console.log(` ${chalk.gray('▶ ' + r.tool.launchNote)}`)
|
|
457
|
-
}
|
|
458
|
-
})
|
|
459
|
-
console.log()
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (manual.length) {
|
|
463
|
-
console.log(chalk.yellow(`${manual.length} 个工具需要手动配置(见上方步骤)`))
|
|
464
|
-
console.log()
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (errors.length) {
|
|
468
|
-
console.log(chalk.red(`${errors.length} 个工具配置失败:`))
|
|
469
|
-
errors.forEach(r => console.log(` ✗ ${r.tool.name}: ${r.error}`))
|
|
470
|
-
console.log()
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const cliPrefix = getPreferredCliPrefix()
|
|
474
|
-
console.log(chalk.gray(`如需切换其他工具,运行: ${cliPrefix} setup`))
|
|
475
|
-
console.log(chalk.gray(`查看余额: ${cliPrefix} balance`))
|
|
476
|
-
console.log(chalk.gray(`检查配置: ${cliPrefix} doctor`))
|
|
477
|
-
console.log()
|
|
478
|
-
|
|
479
|
-
// 注册引导 banner
|
|
480
|
-
console.log(chalk.cyan('╔══════════════════════════════════════════════════╗'))
|
|
481
|
-
console.log(chalk.cyan('║') + chalk.bold(' 🎁 还没有 Key?立即注册即可使用! ') + chalk.cyan('║'))
|
|
482
|
-
console.log(chalk.cyan('║') + chalk.green(' 👉 https://holysheep.ai/register ') + chalk.cyan('║'))
|
|
483
|
-
console.log(chalk.cyan('║') + chalk.yellow(' 💳 支持微信 / 支付宝,无需国际信用卡! ') + chalk.cyan('║'))
|
|
484
|
-
console.log(chalk.cyan('╚══════════════════════════════════════════════════╝'))
|
|
485
|
-
console.log()
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function maskKey(key) {
|
|
489
|
-
if (!key || key.length < 8) return '****'
|
|
490
|
-
return key.slice(0, 6) + '...' + key.slice(-4)
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
module.exports = setup
|
package/src/commands/upgrade.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hs upgrade — 自动升级 AI 编程工具到最新版本
|
|
3
|
-
*/
|
|
4
|
-
const chalk = require('chalk')
|
|
5
|
-
const { execSync } = require('child_process')
|
|
6
|
-
const { commandExists } = require('../utils/which')
|
|
7
|
-
|
|
8
|
-
// 支持升级的工具列表
|
|
9
|
-
const UPGRADABLE_TOOLS = [
|
|
10
|
-
{
|
|
11
|
-
name: 'Claude Code',
|
|
12
|
-
id: 'claude-code',
|
|
13
|
-
command: 'claude',
|
|
14
|
-
versionCmd: 'claude --version',
|
|
15
|
-
npmPkg: null,
|
|
16
|
-
installCmd: process.platform === 'win32'
|
|
17
|
-
? 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://claude.ai/install.ps1 | iex"'
|
|
18
|
-
: 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: 'Codex CLI',
|
|
22
|
-
id: 'codex',
|
|
23
|
-
command: 'codex',
|
|
24
|
-
versionCmd: 'codex --version',
|
|
25
|
-
npmPkg: '@openai/codex',
|
|
26
|
-
installCmd: 'npm install -g @openai/codex@latest',
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: 'Gemini CLI',
|
|
30
|
-
id: 'gemini-cli',
|
|
31
|
-
command: 'gemini',
|
|
32
|
-
versionCmd: 'gemini --version',
|
|
33
|
-
npmPkg: '@google/gemini-cli',
|
|
34
|
-
installCmd: 'npm install -g @google/gemini-cli@latest',
|
|
35
|
-
},
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 获取本地已安装版本
|
|
40
|
-
*/
|
|
41
|
-
function getLocalVersion(tool) {
|
|
42
|
-
try {
|
|
43
|
-
const output = execSync(tool.versionCmd, { stdio: 'pipe', timeout: 10000 })
|
|
44
|
-
.toString().trim()
|
|
45
|
-
// 提取版本号(匹配 x.y.z 格式)
|
|
46
|
-
const match = output.match(/(\d+\.\d+\.\d+[\w.-]*)/)
|
|
47
|
-
return match ? match[1] : output.split('\n')[0].slice(0, 30)
|
|
48
|
-
} catch {
|
|
49
|
-
return null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 从 npm registry 获取最新版本
|
|
55
|
-
*/
|
|
56
|
-
async function getLatestVersion(npmPkg) {
|
|
57
|
-
try {
|
|
58
|
-
const https = require('https')
|
|
59
|
-
return new Promise((resolve) => {
|
|
60
|
-
const req = https.get(
|
|
61
|
-
`https://registry.npmjs.org/${npmPkg}/latest`,
|
|
62
|
-
{ timeout: 8000 },
|
|
63
|
-
(res) => {
|
|
64
|
-
let data = ''
|
|
65
|
-
res.on('data', chunk => { data += chunk })
|
|
66
|
-
res.on('end', () => {
|
|
67
|
-
try {
|
|
68
|
-
resolve(JSON.parse(data).version || null)
|
|
69
|
-
} catch { resolve(null) }
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
req.on('error', () => resolve(null))
|
|
74
|
-
req.setTimeout(8000, () => { req.destroy(); resolve(null) })
|
|
75
|
-
})
|
|
76
|
-
} catch {
|
|
77
|
-
return null
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 执行升级
|
|
83
|
-
*/
|
|
84
|
-
function runUpgrade(tool) {
|
|
85
|
-
try {
|
|
86
|
-
console.log(chalk.gray(` 运行: ${tool.installCmd}`))
|
|
87
|
-
execSync(tool.installCmd, { stdio: 'inherit', timeout: 120000 })
|
|
88
|
-
return true
|
|
89
|
-
} catch (e) {
|
|
90
|
-
console.log(chalk.red(` ✗ 升级失败: ${e.message}`))
|
|
91
|
-
return false
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function upgrade() {
|
|
96
|
-
console.log()
|
|
97
|
-
console.log(chalk.bold('🔄 HolySheep Upgrade — 升级 AI 编程工具'))
|
|
98
|
-
console.log(chalk.gray('━'.repeat(50)))
|
|
99
|
-
console.log()
|
|
100
|
-
|
|
101
|
-
let hasInstalled = false
|
|
102
|
-
let upgraded = 0
|
|
103
|
-
let alreadyLatest = 0
|
|
104
|
-
|
|
105
|
-
for (const tool of UPGRADABLE_TOOLS) {
|
|
106
|
-
const installed = commandExists(tool.command)
|
|
107
|
-
|
|
108
|
-
if (!installed) {
|
|
109
|
-
console.log(` ${chalk.gray('○')} ${chalk.gray(tool.name.padEnd(18))} ${chalk.gray('未安装')} ${chalk.gray(`— ${tool.installCmd}`)}`)
|
|
110
|
-
continue
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
hasInstalled = true
|
|
114
|
-
const localVer = getLocalVersion(tool)
|
|
115
|
-
process.stdout.write(` ${chalk.cyan('◌')} ${tool.name.padEnd(18)} ${chalk.gray('v' + (localVer || '?'))} → 检查最新版本...`)
|
|
116
|
-
|
|
117
|
-
const latestVer = await getLatestVersion(tool.npmPkg)
|
|
118
|
-
|
|
119
|
-
if (!tool.npmPkg) {
|
|
120
|
-
console.log(`\r ${chalk.yellow('↑')} ${chalk.yellow(tool.name.padEnd(18))} ${chalk.gray('v' + (localVer || '?'))} → ${chalk.cyan('official installer')} `)
|
|
121
|
-
const success = runUpgrade(tool)
|
|
122
|
-
if (success) {
|
|
123
|
-
const newVer = getLocalVersion(tool)
|
|
124
|
-
console.log(` ${chalk.green('✓')} ${chalk.green(tool.name)} 升级成功 → ${chalk.cyan('v' + (newVer || 'latest'))}`)
|
|
125
|
-
upgraded++
|
|
126
|
-
}
|
|
127
|
-
console.log()
|
|
128
|
-
continue
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!latestVer) {
|
|
132
|
-
console.log(chalk.yellow(' 无法获取最新版本'))
|
|
133
|
-
continue
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (localVer === latestVer) {
|
|
137
|
-
// 用 \r 覆盖当前行
|
|
138
|
-
console.log(`\r ${chalk.green('✓')} ${chalk.green(tool.name.padEnd(18))} ${chalk.gray('v' + localVer)} ${chalk.green('已是最新版本')} `)
|
|
139
|
-
alreadyLatest++
|
|
140
|
-
continue
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
console.log(`\r ${chalk.yellow('↑')} ${chalk.yellow(tool.name.padEnd(18))} ${chalk.gray('v' + (localVer || '?'))} → ${chalk.cyan('v' + latestVer)} `)
|
|
144
|
-
|
|
145
|
-
const success = runUpgrade(tool)
|
|
146
|
-
if (success) {
|
|
147
|
-
const newVer = getLocalVersion(tool)
|
|
148
|
-
console.log(` ${chalk.green('✓')} ${chalk.green(tool.name)} 升级成功 → ${chalk.cyan('v' + (newVer || latestVer))}`)
|
|
149
|
-
upgraded++
|
|
150
|
-
}
|
|
151
|
-
console.log()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 总结
|
|
155
|
-
console.log()
|
|
156
|
-
if (!hasInstalled) {
|
|
157
|
-
console.log(chalk.yellow('没有检测到已安装的 AI 编程工具。'))
|
|
158
|
-
console.log(chalk.gray('支持的工具: Claude Code, Codex CLI, Gemini CLI'))
|
|
159
|
-
console.log(chalk.gray(`安装示例: ${process.platform === 'win32' ? 'irm https://claude.ai/install.ps1 | iex' : 'curl -fsSL https://claude.ai/install.sh | bash'}`))
|
|
160
|
-
} else if (upgraded > 0) {
|
|
161
|
-
console.log(chalk.green(`✓ 升级了 ${upgraded} 个工具`))
|
|
162
|
-
} else if (alreadyLatest > 0) {
|
|
163
|
-
console.log(chalk.green('✓ 所有工具已是最新版本'))
|
|
164
|
-
}
|
|
165
|
-
console.log()
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
module.exports = upgrade
|