bhg-helper 1.0.3 → 1.0.5
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/cli/cli.js +118 -34
- package/package.json +1 -1
package/cli/cli.js
CHANGED
|
@@ -12,10 +12,19 @@ import fs from 'node:fs'
|
|
|
12
12
|
import { fileURLToPath } from 'node:url'
|
|
13
13
|
|
|
14
14
|
// ── 色彩 ──────────────────────────────────────────────────────
|
|
15
|
-
const C = {
|
|
15
|
+
const C = {
|
|
16
|
+
r: '\x1b[0m',
|
|
17
|
+
bold: '\x1b[1m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
dim: '\x1b[2m',
|
|
22
|
+
red: '\x1b[31m',
|
|
23
|
+
}
|
|
16
24
|
const theme = { prefix: `${C.cyan}?${C.r}` }
|
|
17
25
|
|
|
18
26
|
// ── 常量 ──────────────────────────────────────────────────────
|
|
27
|
+
const VERSION = '1.0.2'
|
|
19
28
|
const RELAY_PORT = 8787
|
|
20
29
|
const API_URL = 'http://127.0.0.1:3001'
|
|
21
30
|
const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
@@ -23,6 +32,66 @@ const BHG_HELPER_DIR = path.join(os.homedir(), '.bhg-helper')
|
|
|
23
32
|
const RELAY_CONFIG_FILE = path.join(BHG_HELPER_DIR, 'relay.config.json')
|
|
24
33
|
const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.json')
|
|
25
34
|
|
|
35
|
+
const TOOLS_LIST = [
|
|
36
|
+
{ name: 'Claude Code', desc: 'Anthropic 官方 CLI 编程助手' },
|
|
37
|
+
{ name: 'OpenCode', desc: '开源 AI 编程助手' },
|
|
38
|
+
{ name: 'Gemini CLI', desc: 'Google 官方 CLI 编程助手' },
|
|
39
|
+
{ name: 'OpenAI Codex CLI', desc: 'OpenAI 官方 CLI 编程助手' },
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
// ── ANSI 24 位色渐变 Logo ──────────────────────────────────────
|
|
43
|
+
function rgb(r, g, b) {
|
|
44
|
+
return `\x1b[38;2;${r};${g};${b}m`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 蓝 → 紫 → 粉 渐变 10 行
|
|
48
|
+
function colorLine(row, totalRows = 10) {
|
|
49
|
+
const t = row / (totalRows - 1)
|
|
50
|
+
if (t < 0.5) {
|
|
51
|
+
const p = t * 2
|
|
52
|
+
return rgb(
|
|
53
|
+
Math.round(80 + p * 70),
|
|
54
|
+
Math.round(180 - p * 40),
|
|
55
|
+
Math.round(255)
|
|
56
|
+
)
|
|
57
|
+
} else {
|
|
58
|
+
const p = (t - 0.5) * 2
|
|
59
|
+
return rgb(
|
|
60
|
+
Math.round(150 + p * 95),
|
|
61
|
+
Math.round(140 - p * 70),
|
|
62
|
+
Math.round(255)
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const BHG_LOGO = [
|
|
68
|
+
'██████╗ ██╗ ██╗ ███████╗',
|
|
69
|
+
'██╔══██╗ ██║ ██║ ██╔════╝',
|
|
70
|
+
'██████╔╝ ███████║ ███████╗',
|
|
71
|
+
'██╔══██╗ ██╔══██║ ╚════██║',
|
|
72
|
+
'██████╔╝ ██║ ██║ ███████║',
|
|
73
|
+
'╚═════╝ ╚═╝ ╚═╝ ╚══════╝',
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
const HELPER_LOGO = [
|
|
77
|
+
'██╗ ██╗ ███████╗ ██╗ ██████╗ ███████╗ ██████╗ ',
|
|
78
|
+
'██║ ██║ ██╔════╝ ██║ ██╔══██╗ ██╔════╝ ██╔══██╗',
|
|
79
|
+
'███████║ █████╗ ██║ ██████╔╝ █████╗ ██████╔╝',
|
|
80
|
+
'██╔══██║ ██╔══╝ ██║ ██╔═══╝ ██╔══╝ ██╔══██╗',
|
|
81
|
+
'██║ ██║ ███████╗ ███████╗ ██║ ███████╗ ██║ ██║',
|
|
82
|
+
'╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝',
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
function printLogo() {
|
|
86
|
+
const gap = ' '
|
|
87
|
+
for (let i = 0; i < BHG_LOGO.length; i++) {
|
|
88
|
+
console.log(
|
|
89
|
+
`${colorLine(i)}${C.bold}${BHG_LOGO[i]}${gap}${HELPER_LOGO[i]}${C.r}`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
console.log()
|
|
93
|
+
}
|
|
94
|
+
|
|
26
95
|
// ── 工具定义 ──────────────────────────────────────────────────
|
|
27
96
|
const TOOLS = {
|
|
28
97
|
claude: {
|
|
@@ -97,7 +166,6 @@ function hasApiConfig() {
|
|
|
97
166
|
function getCurrentModel() {
|
|
98
167
|
const cfg = loadConfig()
|
|
99
168
|
if (!cfg) return 'deepseek-v4-pro'
|
|
100
|
-
// 优先读取当前激活的模型
|
|
101
169
|
return cfg.currentModel || 'deepseek-v4-pro'
|
|
102
170
|
}
|
|
103
171
|
|
|
@@ -115,6 +183,19 @@ async function apiPost(pathname) {
|
|
|
115
183
|
return res.ok
|
|
116
184
|
}
|
|
117
185
|
|
|
186
|
+
async function apiGet(pathname) {
|
|
187
|
+
try {
|
|
188
|
+
const controller = new AbortController()
|
|
189
|
+
const timer = setTimeout(() => controller.abort(), 800)
|
|
190
|
+
const res = await fetch(`${API_URL}${pathname}`, { signal: controller.signal })
|
|
191
|
+
clearTimeout(timer)
|
|
192
|
+
if (!res.ok) return null
|
|
193
|
+
return await res.json().catch(() => null)
|
|
194
|
+
} catch {
|
|
195
|
+
return null
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
118
199
|
async function apiHealth() {
|
|
119
200
|
try {
|
|
120
201
|
const controller = new AbortController()
|
|
@@ -146,14 +227,33 @@ async function ensureRelay() {
|
|
|
146
227
|
return await apiPost('/api/relay/start')
|
|
147
228
|
}
|
|
148
229
|
|
|
149
|
-
// ──
|
|
150
|
-
function printHeader() {
|
|
230
|
+
// ── 打印头部(ArkHelper 风格)─────────────────────────────────
|
|
231
|
+
async function printHeader() {
|
|
151
232
|
console.clear()
|
|
152
|
-
|
|
233
|
+
printLogo()
|
|
234
|
+
|
|
235
|
+
console.log(` v${VERSION} · DeepSeek LLM bridge for your AI coding tools`)
|
|
236
|
+
console.log()
|
|
237
|
+
console.log(` ${C.bold}一键配置${C.r}`)
|
|
238
|
+
for (const t of TOOLS_LIST) {
|
|
239
|
+
console.log(` ${C.cyan}◆${C.r} ${t.name} ${C.dim}— ${t.desc}${C.r}`)
|
|
240
|
+
}
|
|
241
|
+
console.log()
|
|
242
|
+
|
|
153
243
|
const apiOk = hasApiConfig()
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
244
|
+
const backendOk = await apiHealth()
|
|
245
|
+
const relayJson = await apiGet('/api/relay/status')
|
|
246
|
+
const relayOk = !!(relayJson && relayJson.data?.running)
|
|
247
|
+
|
|
248
|
+
console.log(
|
|
249
|
+
` ${C.dim}API:${C.r} ${apiOk ? C.green + '✓' + C.r : C.red + '✗' + C.r}`
|
|
250
|
+
+ ` ${C.dim}Backend:${C.r} ${backendOk ? C.green + '✓' + C.r : C.red + '✗' + C.r}`
|
|
251
|
+
+ ` ${C.dim}Relay:${C.r} ${relayOk ? C.green + '✓' + C.r : C.red + '✗' + C.r}`
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
console.log()
|
|
255
|
+
console.log(` ${C.dim}powered by BH6BHG${C.r}`)
|
|
256
|
+
console.log()
|
|
157
257
|
}
|
|
158
258
|
|
|
159
259
|
// ── 一级菜单 ──────────────────────────────────────────────────
|
|
@@ -193,36 +293,27 @@ async function modelMenu() {
|
|
|
193
293
|
if (choice === 'back') return 'back'
|
|
194
294
|
|
|
195
295
|
if (choice === 'switch') {
|
|
196
|
-
// 从 relay config 读取模型列表
|
|
197
296
|
const models = []
|
|
198
297
|
if (cfg && cfg.modelMap) {
|
|
199
298
|
const seen = new Set()
|
|
200
|
-
for (const [from
|
|
201
|
-
if (!seen.has(
|
|
202
|
-
seen.add(
|
|
203
|
-
models.push(
|
|
299
|
+
for (const [from] of Object.entries(cfg.modelMap)) {
|
|
300
|
+
if (!seen.has(from)) {
|
|
301
|
+
seen.add(from)
|
|
302
|
+
models.push(from)
|
|
204
303
|
}
|
|
205
304
|
}
|
|
206
305
|
} else {
|
|
207
|
-
|
|
208
|
-
models.push(
|
|
209
|
-
{ from: 'deepseek-v4-pro', to: 'deepseek-v4-pro' },
|
|
210
|
-
{ from: 'deepseek-v4-flash', to: 'deepseek-v4-flash' },
|
|
211
|
-
{ from: 'deepseek-chat', to: 'deepseek-v4-pro' },
|
|
212
|
-
{ from: 'deepseek-reasoner', to: 'deepseek-v4-pro' },
|
|
213
|
-
)
|
|
306
|
+
models.push('deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek-chat')
|
|
214
307
|
}
|
|
215
308
|
|
|
216
309
|
const modelChoice = await select({
|
|
217
310
|
message: '选择模型',
|
|
218
311
|
choices: [
|
|
219
312
|
...models.map(m => ({
|
|
220
|
-
name: m
|
|
221
|
-
|
|
222
|
-
: ` ${m.from} ${C.dim}→ ${m.to}${C.r}`,
|
|
223
|
-
value: m.from,
|
|
313
|
+
name: m === currentModel ? `${C.green}● ${m}${C.r}` : ` ${m}`,
|
|
314
|
+
value: m,
|
|
224
315
|
})),
|
|
225
|
-
{ name: `${C.dim}──────────${C.r}`, value: '---
|
|
316
|
+
{ name: `${C.dim}──────────${C.r}`, value: '---', disabled: true },
|
|
226
317
|
{ name: '返回', value: 'back' },
|
|
227
318
|
],
|
|
228
319
|
theme,
|
|
@@ -256,7 +347,6 @@ async function modelMenu() {
|
|
|
256
347
|
saveConfig(next)
|
|
257
348
|
console.log(`\n ${C.green}✓ API Key 已保存${C.r}`)
|
|
258
349
|
|
|
259
|
-
// 自动启动中转服务
|
|
260
350
|
console.log(` ${C.dim}正在启动中转服务...${C.r}`)
|
|
261
351
|
const relayOk = await ensureRelay()
|
|
262
352
|
console.log(` ${relayOk ? C.green + '✓ 中转服务已启动' + C.r : C.yellow + '中转服务启动失败(可稍后手动启动)' + C.r}\n`)
|
|
@@ -296,7 +386,6 @@ async function toolsMenu() {
|
|
|
296
386
|
async function toolDetailMenu(toolKey) {
|
|
297
387
|
const tool = TOOLS[toolKey]
|
|
298
388
|
|
|
299
|
-
// Cursor / Continue / Trae — 无 CLI,显示安装方式
|
|
300
389
|
if (!tool.cmd) {
|
|
301
390
|
console.log(`\n ${C.bold}${tool.name}${C.r} — ${tool.desc}`)
|
|
302
391
|
console.log(` ${C.dim}安装方式:${tool.install}${C.r}`)
|
|
@@ -305,7 +394,6 @@ async function toolDetailMenu(toolKey) {
|
|
|
305
394
|
return 'back'
|
|
306
395
|
}
|
|
307
396
|
|
|
308
|
-
// 有 CLI 的工具
|
|
309
397
|
const installed = checkInstalled(tool.cmd)
|
|
310
398
|
|
|
311
399
|
const choices = []
|
|
@@ -344,7 +432,6 @@ async function toolDetailMenu(toolKey) {
|
|
|
344
432
|
}
|
|
345
433
|
|
|
346
434
|
if (action === 'launch') {
|
|
347
|
-
// 启动前检查 API 配置
|
|
348
435
|
if (!hasApiConfig()) {
|
|
349
436
|
console.log(`\n ${C.red}API 未配置!请先在「模型选择」→「输入 API」中配置。${C.r}\n`)
|
|
350
437
|
await confirm({ message: '按回车继续...', theme })
|
|
@@ -358,7 +445,6 @@ async function toolDetailMenu(toolKey) {
|
|
|
358
445
|
return 'back'
|
|
359
446
|
}
|
|
360
447
|
|
|
361
|
-
// 写入 ~/.claude/settings.json(Claude Code 专用)
|
|
362
448
|
if (toolKey === 'claude') {
|
|
363
449
|
const currentModel = getCurrentModel()
|
|
364
450
|
const dir = path.dirname(CLAUDE_SETTINGS_FILE)
|
|
@@ -383,7 +469,6 @@ async function toolDetailMenu(toolKey) {
|
|
|
383
469
|
console.log(` ${C.green}✓ Claude Code 配置已写入${C.r}`)
|
|
384
470
|
}
|
|
385
471
|
|
|
386
|
-
// 写入 opencode 配置
|
|
387
472
|
if (toolKey === 'opencode') {
|
|
388
473
|
const file = TOOLS.opencode.configFile
|
|
389
474
|
const dir = path.dirname(file)
|
|
@@ -436,7 +521,7 @@ async function langMenu() {
|
|
|
436
521
|
async function main() {
|
|
437
522
|
let running = true
|
|
438
523
|
while (running) {
|
|
439
|
-
printHeader()
|
|
524
|
+
await printHeader()
|
|
440
525
|
const choice = await mainMenu()
|
|
441
526
|
|
|
442
527
|
switch (choice) {
|
|
@@ -462,7 +547,6 @@ async function main() {
|
|
|
462
547
|
if (result === 'exit') { console.log(`\n ${C.dim}再见。${C.r}\n`); running = false }
|
|
463
548
|
break
|
|
464
549
|
}
|
|
465
|
-
|
|
466
550
|
}
|
|
467
551
|
}
|
|
468
552
|
}
|
|
@@ -474,4 +558,4 @@ main().catch((err) => {
|
|
|
474
558
|
}
|
|
475
559
|
console.error('Error:', err.message)
|
|
476
560
|
process.exit(1)
|
|
477
|
-
})
|
|
561
|
+
})
|