@simonyea/holysheep-cli 2.1.40 → 2.1.42

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 (45) hide show
  1. package/dist/configure-worker.js +4510 -0
  2. package/dist/index.js +9610 -0
  3. package/dist/process-proxy-inject.js +117 -0
  4. package/package.json +19 -6
  5. package/.gitea/workflows/sanity.yml +0 -125
  6. package/scripts/check-tarball-size.js +0 -44
  7. package/src/commands/balance.js +0 -57
  8. package/src/commands/claude-proxy.js +0 -248
  9. package/src/commands/claude.js +0 -135
  10. package/src/commands/doctor.js +0 -282
  11. package/src/commands/login.js +0 -211
  12. package/src/commands/openclaw.js +0 -258
  13. package/src/commands/reset.js +0 -53
  14. package/src/commands/setup.js +0 -493
  15. package/src/commands/upgrade.js +0 -168
  16. package/src/commands/webui.js +0 -622
  17. package/src/index.js +0 -226
  18. package/src/tools/aider.js +0 -78
  19. package/src/tools/antigravity.js +0 -42
  20. package/src/tools/claude-code.js +0 -228
  21. package/src/tools/claude-process-proxy.js +0 -1030
  22. package/src/tools/codex.js +0 -254
  23. package/src/tools/continue.js +0 -146
  24. package/src/tools/cursor.js +0 -71
  25. package/src/tools/droid.js +0 -281
  26. package/src/tools/env-config.js +0 -185
  27. package/src/tools/gemini-cli.js +0 -82
  28. package/src/tools/hermes.js +0 -354
  29. package/src/tools/index.js +0 -13
  30. package/src/tools/openclaw-bridge.js +0 -987
  31. package/src/tools/openclaw.js +0 -925
  32. package/src/tools/opencode.js +0 -227
  33. package/src/tools/process-proxy-inject.js +0 -142
  34. package/src/utils/config.js +0 -54
  35. package/src/utils/shell.js +0 -342
  36. package/src/utils/which.js +0 -176
  37. package/src/webui/aionui-runtime-fetcher.js +0 -429
  38. package/src/webui/aionui-runtime.js +0 -139
  39. package/src/webui/aionui-wrapper.js +0 -734
  40. package/src/webui/configure-worker.js +0 -67
  41. package/src/webui/server.js +0 -1572
  42. package/src/webui/workspace-runtime.js +0 -288
  43. package/src/webui/workspace-store.js +0 -325
  44. /package/{src/webui → dist}/index.html +0 -0
  45. /package/{src/tools → dist}/pty-hermes-wrapper.py +0 -0
@@ -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
@@ -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