bhg-helper 1.0.2 → 1.0.4

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/api/app.ts CHANGED
@@ -40,6 +40,7 @@ app.get('/api/health', (_req: Request, res: Response) => {
40
40
  })
41
41
 
42
42
  app.use((error: Error, _req: Request, res: Response, _next: NextFunction) => {
43
+ void _next
43
44
  log('ERR', 'http', error.message)
44
45
  res.status(500).json({ ok: false, error: error.message })
45
46
  })
@@ -239,8 +239,6 @@ export interface AnthropicStreamEvent {
239
239
  [key: string]: unknown
240
240
  }
241
241
 
242
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
243
-
244
242
  function makeId(): string {
245
243
  // 用 crypto.randomUUID 如果存在
246
244
  if (typeof globalThis.crypto?.randomUUID === 'function') {
@@ -278,7 +276,6 @@ export function* openAIStreamToAnthropicEvents(
278
276
  let finishReason: string | null = null
279
277
  let hasText = false
280
278
  let toolBlockIndex = -1
281
- let toolInputBuffer = ''
282
279
  let toolId: string | null = null
283
280
  let toolName: string | null = null
284
281
  const usedModel = openAIChunks[0]?.model ?? model
@@ -357,7 +354,6 @@ export function* openAIStreamToAnthropicEvents(
357
354
  }
358
355
  }
359
356
  if (tc.function?.arguments) {
360
- toolInputBuffer += tc.function.arguments
361
357
  yield {
362
358
  type: 'content_block_delta',
363
359
  index: toolBlockIndex,
package/cli/cli.js CHANGED
@@ -1,26 +1,97 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * BHG-helper CLI — 命令行交互菜单
4
- * 一级菜单:模型选择 / 工具配置 / 语言配置 / BHG-helper UI / 退出
4
+ * 一级菜单:模型选择 / 工具配置 / 语言配置 / 退出
5
5
  */
6
6
 
7
7
  import { select, confirm, input } from '@inquirer/prompts'
8
- import { execSync } from 'node:child_process'
8
+ import { execSync, spawn } from 'node:child_process'
9
9
  import os from 'node:os'
10
10
  import path from 'node:path'
11
11
  import fs from 'node:fs'
12
+ import { fileURLToPath } from 'node:url'
12
13
 
13
14
  // ── 色彩 ──────────────────────────────────────────────────────
14
- const C = { r: '\x1b[0m', bold: '\x1b[1m', cyan: '\x1b[36m', green: '\x1b[32m', yellow: '\x1b[33m', dim: '\x1b[2m', red: '\x1b[31m' }
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
+ }
15
24
  const theme = { prefix: `${C.cyan}?${C.r}` }
16
25
 
17
26
  // ── 常量 ──────────────────────────────────────────────────────
27
+ const VERSION = '1.0.2'
18
28
  const RELAY_PORT = 8787
19
- const UI_URL = 'http://localhost:5173'
29
+ const API_URL = 'http://127.0.0.1:3001'
30
+ const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
20
31
  const BHG_HELPER_DIR = path.join(os.homedir(), '.bhg-helper')
21
32
  const RELAY_CONFIG_FILE = path.join(BHG_HELPER_DIR, 'relay.config.json')
22
33
  const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.json')
23
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
+
24
95
  // ── 工具定义 ──────────────────────────────────────────────────
25
96
  const TOOLS = {
26
97
  claude: {
@@ -95,7 +166,6 @@ function hasApiConfig() {
95
166
  function getCurrentModel() {
96
167
  const cfg = loadConfig()
97
168
  if (!cfg) return 'deepseek-v4-pro'
98
- // 优先读取当前激活的模型
99
169
  return cfg.currentModel || 'deepseek-v4-pro'
100
170
  }
101
171
 
@@ -108,21 +178,75 @@ function checkInstalled(cmd) {
108
178
  } catch { return false }
109
179
  }
110
180
 
111
- function openBrowser(url) {
181
+ async function apiPost(pathname) {
182
+ const res = await fetch(`${API_URL}${pathname}`, { method: 'POST' })
183
+ return res.ok
184
+ }
185
+
186
+ async function apiGet(pathname) {
187
+ const res = await fetch(`${API_URL}${pathname}`)
188
+ if (!res.ok) return null
189
+ return await res.json().catch(() => null)
190
+ }
191
+
192
+ async function apiHealth() {
112
193
  try {
113
- execSync(`start "" "${url}"`, { shell: true })
114
- } catch { /* */ }
194
+ const controller = new AbortController()
195
+ const timer = setTimeout(() => controller.abort(), 800)
196
+ const res = await fetch(`${API_URL}/api/health`, { signal: controller.signal })
197
+ clearTimeout(timer)
198
+ return res.ok
199
+ } catch {
200
+ return false
201
+ }
202
+ }
203
+
204
+ async function ensureBackend() {
205
+ if (await apiHealth()) return true
206
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
207
+ const child = spawn(npmCmd, ['run', 'server:raw'], {
208
+ cwd: PROJECT_ROOT,
209
+ detached: true,
210
+ stdio: 'ignore',
211
+ shell: false,
212
+ })
213
+ child.unref()
214
+ await new Promise(resolve => setTimeout(resolve, 1500))
215
+ return await apiHealth()
115
216
  }
116
217
 
117
- // ── 打印头部 ──────────────────────────────────────────────────
118
- function printHeader() {
218
+ async function ensureRelay() {
219
+ if (!(await ensureBackend())) return false
220
+ return await apiPost('/api/relay/start')
221
+ }
222
+
223
+ // ── 打印头部(ArkHelper 风格)─────────────────────────────────
224
+ async function printHeader() {
119
225
  console.clear()
120
- console.log(`${C.bold}${C.cyan} ◆ BHG-helper${C.r}`)
226
+ printLogo()
227
+
228
+ console.log(` v${VERSION} · DeepSeek LLM bridge for your AI coding tools`)
229
+ console.log()
230
+ console.log(` ${C.bold}一键配置${C.r}`)
231
+ for (const t of TOOLS_LIST) {
232
+ console.log(` ${C.cyan}◆${C.r} ${t.name} ${C.dim}— ${t.desc}${C.r}`)
233
+ }
234
+ console.log()
235
+
121
236
  const apiOk = hasApiConfig()
122
- const model = getCurrentModel()
123
- console.log(` ${C.dim}API${C.r} ${apiOk ? `${C.green}已配置${C.r}` : `${C.red}未配置${C.r}`}`)
124
- console.log(` ${C.dim}模型${C.r} ${C.cyan}${model}${C.r}`)
125
- console.log(` ${C.dim}UI${C.r} ${UI_URL}\n`)
237
+ const backendOk = await apiHealth()
238
+ const relayJson = await apiGet('/api/relay/status')
239
+ const relayOk = !!(relayJson && relayJson.data?.running)
240
+
241
+ console.log(
242
+ ` ${C.dim}API:${C.r} ${apiOk ? C.green + '✓' + C.r : C.red + '✗' + C.r}`
243
+ + ` ${C.dim}Backend:${C.r} ${backendOk ? C.green + '✓' + C.r : C.red + '✗' + C.r}`
244
+ + ` ${C.dim}Relay:${C.r} ${relayOk ? C.green + '✓' + C.r : C.red + '✗' + C.r}`
245
+ )
246
+
247
+ console.log()
248
+ console.log(` ${C.dim}powered by BH6BHG${C.r}`)
249
+ console.log()
126
250
  }
127
251
 
128
252
  // ── 一级菜单 ──────────────────────────────────────────────────
@@ -133,7 +257,6 @@ async function mainMenu() {
133
257
  { name: '模型选择', value: 'model', description: '切换模型 / 输入 API Key' },
134
258
  { name: '工具配置', value: 'tools', description: '配置各 AI 编程工具' },
135
259
  { name: '语言配置', value: 'lang', description: '界面语言设置' },
136
- { name: `${C.yellow}BHG-helper 网页界面${C.r}`, value: 'ui', description: '打开网页控制台' },
137
260
  { name: `${C.dim}──────────${C.r}`, value: '---', disabled: true },
138
261
  { name: '退出', value: 'exit' },
139
262
  ],
@@ -163,36 +286,27 @@ async function modelMenu() {
163
286
  if (choice === 'back') return 'back'
164
287
 
165
288
  if (choice === 'switch') {
166
- // 从 relay config 读取模型列表
167
289
  const models = []
168
290
  if (cfg && cfg.modelMap) {
169
291
  const seen = new Set()
170
- for (const [from, to] of Object.entries(cfg.modelMap)) {
171
- if (!seen.has(to)) {
172
- seen.add(to)
173
- models.push({ from, to })
292
+ for (const [from] of Object.entries(cfg.modelMap)) {
293
+ if (!seen.has(from)) {
294
+ seen.add(from)
295
+ models.push(from)
174
296
  }
175
297
  }
176
298
  } else {
177
- // 默认列表
178
- models.push(
179
- { from: 'deepseek-v4-pro', to: 'deepseek-v4-pro' },
180
- { from: 'deepseek-v4-flash', to: 'deepseek-v4-flash' },
181
- { from: 'deepseek-chat', to: 'deepseek-v4-pro' },
182
- { from: 'deepseek-reasoner', to: 'deepseek-v4-pro' },
183
- )
299
+ models.push('deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek-chat')
184
300
  }
185
301
 
186
302
  const modelChoice = await select({
187
303
  message: '选择模型',
188
304
  choices: [
189
305
  ...models.map(m => ({
190
- name: m.from === currentModel
191
- ? `${C.green}● ${m.from}${C.r} ${C.dim}→ ${m.to}${C.r}`
192
- : ` ${m.from} ${C.dim}→ ${m.to}${C.r}`,
193
- value: m.from,
306
+ name: m === currentModel ? `${C.green}● ${m}${C.r}` : ` ${m}`,
307
+ value: m,
194
308
  })),
195
- { name: `${C.dim}──────────${C.r}`, value: '---2', disabled: true },
309
+ { name: `${C.dim}──────────${C.r}`, value: '---', disabled: true },
196
310
  { name: '返回', value: 'back' },
197
311
  ],
198
312
  theme,
@@ -224,7 +338,11 @@ async function modelMenu() {
224
338
 
225
339
  const next = { ...(cfg || {}), deepseekApiKey: newKey, port: cfg?.port || RELAY_PORT, deepseekBaseUrl: cfg?.deepseekBaseUrl || 'https://api.deepseek.com', modelMap: cfg?.modelMap || { 'deepseek-v4-pro': 'deepseek-v4-pro', 'deepseek-chat': 'deepseek-v4-pro' }, spoofProvider: cfg?.spoofProvider || 'deepseek', verbose: cfg?.verbose ?? true }
226
340
  saveConfig(next)
227
- console.log(`\n ${C.green}✓ API Key 已保存${C.r}\n`)
341
+ console.log(`\n ${C.green}✓ API Key 已保存${C.r}`)
342
+
343
+ console.log(` ${C.dim}正在启动中转服务...${C.r}`)
344
+ const relayOk = await ensureRelay()
345
+ console.log(` ${relayOk ? C.green + '✓ 中转服务已启动' + C.r : C.yellow + '中转服务启动失败(可稍后手动启动)' + C.r}\n`)
228
346
  await confirm({ message: '按回车继续...', theme })
229
347
  return 'back'
230
348
  }
@@ -254,13 +372,6 @@ async function toolsMenu() {
254
372
  if (choice === 'exit') return 'exit'
255
373
  if (choice === 'back') return 'back'
256
374
 
257
- if (choice === 'ui') {
258
- openBrowser(UI_URL)
259
- console.log(`\n ${C.green}✓ 浏览器已打开 → ${UI_URL}${C.r}\n`)
260
- await confirm({ message: '按回车继续...', theme })
261
- return 'back'
262
- }
263
-
264
375
  return await toolDetailMenu(choice)
265
376
  }
266
377
 
@@ -268,23 +379,14 @@ async function toolsMenu() {
268
379
  async function toolDetailMenu(toolKey) {
269
380
  const tool = TOOLS[toolKey]
270
381
 
271
- // Cursor / Continue / Trae — 无 CLI,只引导去 UI
272
382
  if (!tool.cmd) {
273
383
  console.log(`\n ${C.bold}${tool.name}${C.r} — ${tool.desc}`)
274
384
  console.log(` ${C.dim}安装方式:${tool.install}${C.r}`)
275
- console.log(` ${C.yellow}${tool.name} 需要在 BHG-helper 网页界面配置${C.r}\n`)
276
-
277
- const openUi = await confirm({ message: '打开 BHG-helper 网页界面?', default: true, theme })
278
- if (openUi) {
279
- openBrowser(UI_URL)
280
- console.log(` ${C.green}✓ 浏览器已打开${C.r}`)
281
- }
282
- console.log()
385
+ console.log(` ${C.yellow}当前纯命令行版暂不自动配置 ${tool.name}${C.r}\n`)
283
386
  await confirm({ message: '按回车继续...', theme })
284
387
  return 'back'
285
388
  }
286
389
 
287
- // 有 CLI 的工具
288
390
  const installed = checkInstalled(tool.cmd)
289
391
 
290
392
  const choices = []
@@ -323,14 +425,19 @@ async function toolDetailMenu(toolKey) {
323
425
  }
324
426
 
325
427
  if (action === 'launch') {
326
- // 启动前检查 API 配置
327
428
  if (!hasApiConfig()) {
328
429
  console.log(`\n ${C.red}API 未配置!请先在「模型选择」→「输入 API」中配置。${C.r}\n`)
329
430
  await confirm({ message: '按回车继续...', theme })
330
431
  return 'back'
331
432
  }
332
433
 
333
- // 写入 ~/.claude/settings.json(Claude Code 专用)
434
+ const relayOk = await ensureRelay()
435
+ if (!relayOk) {
436
+ console.log(`\n ${C.red}中转服务启动失败,请重新运行 BHG-helper。${C.r}\n`)
437
+ await confirm({ message: '按回车继续...', theme })
438
+ return 'back'
439
+ }
440
+
334
441
  if (toolKey === 'claude') {
335
442
  const currentModel = getCurrentModel()
336
443
  const dir = path.dirname(CLAUDE_SETTINGS_FILE)
@@ -355,7 +462,6 @@ async function toolDetailMenu(toolKey) {
355
462
  console.log(` ${C.green}✓ Claude Code 配置已写入${C.r}`)
356
463
  }
357
464
 
358
- // 写入 opencode 配置
359
465
  if (toolKey === 'opencode') {
360
466
  const file = TOOLS.opencode.configFile
361
467
  const dir = path.dirname(file)
@@ -408,7 +514,7 @@ async function langMenu() {
408
514
  async function main() {
409
515
  let running = true
410
516
  while (running) {
411
- printHeader()
517
+ await printHeader()
412
518
  const choice = await mainMenu()
413
519
 
414
520
  switch (choice) {
@@ -434,12 +540,6 @@ async function main() {
434
540
  if (result === 'exit') { console.log(`\n ${C.dim}再见。${C.r}\n`); running = false }
435
541
  break
436
542
  }
437
-
438
- case 'ui':
439
- openBrowser(UI_URL)
440
- console.log(`\n ${C.green}✓ 浏览器已打开 → ${UI_URL}${C.r}\n`)
441
- await confirm({ message: '按回车继续...', theme })
442
- break
443
543
  }
444
544
  }
445
545
  }
@@ -451,4 +551,4 @@ main().catch((err) => {
451
551
  }
452
552
  console.error('Error:', err.message)
453
553
  process.exit(1)
454
- })
554
+ })