@simonyea/holysheep-cli 1.7.56 → 1.7.57

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.7.56",
3
+ "version": "1.7.57",
4
4
  "description": "Claude Code/Cursor/Cline API relay for China — ¥1=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
5
5
  "keywords": [
6
6
  "openai-china",
@@ -56,9 +56,9 @@ function readConfig() {
56
56
  const file = getConfigFile()
57
57
  try {
58
58
  if (fs.existsSync(file)) {
59
- // 支持 JSONC(带注释的 JSON
59
+ // 支持 JSONC(带注释的 JSON)— 只删行首注释,避免误删 URL 中的 //
60
60
  const content = fs.readFileSync(file, 'utf8')
61
- return JSON.parse(content.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, ''))
61
+ return JSON.parse(content.replace(/^\s*\/\/[^\n]*/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''))
62
62
  }
63
63
  } catch {}
64
64
  return {}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * 子进程 worker — 在独立进程中运行 tool.configure()
3
+ * 避免 spawnSync/busy-wait 阻塞主进程事件循环
4
+ *
5
+ * IPC 协议:
6
+ * 父→子: { toolId, apiKey, baseUrlAnthropic, baseUrlOpenAI, primaryModel, allModelIds }
7
+ * 子→父: { type: 'progress'|'result'|'error', ... }
8
+ */
9
+ 'use strict'
10
+
11
+ process.on('message', (msg) => {
12
+ const TOOLS = require('../tools')
13
+ const { writeEnvToShell, removeEnvFromShell } = require('../utils/shell')
14
+
15
+ const tool = TOOLS.find(t => t.id === msg.toolId)
16
+ if (!tool) {
17
+ process.send({ type: 'error', message: '未知工具: ' + msg.toolId })
18
+ process.exit(1)
19
+ }
20
+
21
+ // 劫持 console.log/warn → IPC 回传进度
22
+ const stripAnsi = (s) => String(s).replace(/\x1b\[[0-9;]*m/g, '')
23
+ console.log = (...args) => {
24
+ const text = stripAnsi(args.join(' ')).trim()
25
+ if (text) process.send({ type: 'progress', message: text })
26
+ }
27
+ console.warn = console.log
28
+
29
+ try {
30
+ const result = tool.configure(
31
+ msg.apiKey,
32
+ msg.baseUrlAnthropic,
33
+ msg.baseUrlOpenAI,
34
+ msg.primaryModel,
35
+ msg.allModelIds,
36
+ )
37
+
38
+ // 写入 shell 环境变量(如果工具需要)
39
+ if (result.envVars && Object.keys(result.envVars).length > 0) {
40
+ writeEnvToShell(result.envVars)
41
+ process.send({ type: 'progress', message: '已写入环境变量到 shell 配置' })
42
+ }
43
+
44
+ // 清理冲突环境变量
45
+ try {
46
+ removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL'])
47
+ } catch {}
48
+
49
+ process.send({
50
+ type: 'result',
51
+ status: result.manual ? 'manual' : result.warning ? 'warning' : 'ok',
52
+ message: result.manual ? `${tool.name} 需要手动完成配置`
53
+ : result.warning ? result.warning
54
+ : `${tool.name} 配置成功`,
55
+ file: result.file || null,
56
+ hot: result.hot || false,
57
+ steps: result.steps || null,
58
+ dashboardUrl: result.dashboardUrl || null,
59
+ })
60
+ } catch (e) {
61
+ process.send({ type: 'error', message: e.message })
62
+ }
63
+
64
+ process.exit(0)
65
+ })
@@ -169,7 +169,7 @@ const I18N = {
169
169
  balance: '余额', today: '今日消费', month: '本月消费', calls: '累计调用',
170
170
  recharge: '充值', register: '没有账号?去注册',
171
171
  apiKeyPlaceholder: '请输入 API Key (cr_xxx)',
172
- tools: 'AI 工具', toolsHint: '一键配置使用 HolySheep API',
172
+ tools: 'AI 工具', toolsHint: '一键配置使用 HolySheep API', upgrade: '升级工具',
173
173
  installed: '已安装', notInstalled: '未安装', configured: '已配置', notConfigured: '未配置',
174
174
  configure: '一键配置', reconfigure: '重新配置', reset: '重置', install: '安装',
175
175
  installManual: '手动安装',
@@ -191,7 +191,7 @@ const I18N = {
191
191
  balance: 'Balance', today: 'Today', month: 'This Month', calls: 'Total Calls',
192
192
  recharge: 'Recharge', register: 'No account? Register',
193
193
  apiKeyPlaceholder: 'Enter API Key (cr_xxx)',
194
- tools: 'AI Tools', toolsHint: 'One-click setup for HolySheep API',
194
+ tools: 'AI Tools', toolsHint: 'One-click setup for HolySheep API', upgrade: 'Upgrade Tools',
195
195
  installed: 'Installed', notInstalled: 'Not installed', configured: 'Configured', notConfigured: 'Not configured',
196
196
  configure: 'Configure', reconfigure: 'Reconfigure', reset: 'Reset', install: 'Install',
197
197
  installManual: 'Manual install',
@@ -265,7 +265,7 @@ async function loadAccount() {
265
265
  <div><div class="stat-val">$${fmtNum(b.todayCost)}</div><div class="stat-lbl">${t('today')}</div></div>
266
266
  <div><div class="stat-val">$${fmtNum(b.monthCost)}</div><div class="stat-lbl">${t('month')}</div></div>
267
267
  <div><div class="stat-val">${b.totalCalls.toLocaleString()}</div><div class="stat-lbl">${t('calls')}</div></div>
268
- </div>` : ''}
268
+ </div>` : `<div style="color:var(--text2);font-size:0.85rem;margin:10px 0">${b.error ? esc(b.error) : t('checking')}</div>`}
269
269
  <div class="account-actions">
270
270
  <a class="btn btn-primary btn-sm" href="https://holysheep.ai/app/recharge" target="_blank">${t('recharge')} &rarr;</a>
271
271
  <button class="btn btn-danger btn-sm" onclick="doLogout()">${t('logout')}</button>
@@ -307,7 +307,10 @@ async function doLogout() {
307
307
  // ── Tools section ────────────────────────────────────────────────────────────
308
308
  async function loadTools() {
309
309
  const el = document.getElementById('tools-section')
310
- el.innerHTML = `<div class="section-title"><span>${t('tools')}</span><span class="hint">${t('toolsHint')}</span></div>
310
+ el.innerHTML = `<div class="section-title">
311
+ <span>${t('tools')} <span class="hint" style="margin-left:8px">${t('toolsHint')}</span></span>
312
+ <button class="btn btn-outline btn-sm" onclick="doUpgrade()" ${busy ? 'disabled' : ''}>${t('upgrade')}</button>
313
+ </div>
311
314
  <div class="tool-grid"><span class="loading">${t('checking')}</span></div>`
312
315
 
313
316
  const tools = await api('tools')
@@ -419,6 +422,29 @@ async function doInstallTool(id, name) {
419
422
  loadTools()
420
423
  }
421
424
 
425
+ async function doUpgrade() {
426
+ if (busy) return
427
+ busy = true
428
+ openConsole(t('upgrade'))
429
+ document.getElementById('console-section').classList.add('busy')
430
+
431
+ await streamSSE('/api/upgrade', {}, (ev) => {
432
+ if (ev.type === 'tool') {
433
+ const cls = ev.status === 'ok' ? 'ok' : ev.status === 'error' ? 'err' : ev.status === 'not-installed' ? 'warn' : 'info'
434
+ const msg = ev.status === 'not-installed' ? `${ev.name}: ${t('notInstalled')}`
435
+ : ev.status === 'upgrading' ? `${ev.name}: ${t('upgrade')}... (${ev.localVer || '?'})`
436
+ : ev.status === 'ok' ? `✓ ${ev.name}: ${ev.localVer || '?'} → ${ev.newVer || 'latest'}`
437
+ : `✗ ${ev.name}: ${t('configFailed')}`
438
+ appendLog(msg, cls)
439
+ } else if (ev.type === 'output') { appendLogRaw(ev.text) }
440
+ else if (ev.type === 'done') { appendLog(`\n✓ ${t('configSuccess')}`, 'ok') }
441
+ })
442
+
443
+ document.getElementById('console-section').classList.remove('busy')
444
+ busy = false
445
+ loadTools()
446
+ }
447
+
422
448
  // ── Console ──────────────────────────────────────────────────────────────────
423
449
  function openConsole(title) {
424
450
  const area = document.getElementById('console-section')
@@ -470,43 +470,40 @@ async function handleToolConfigure(req, res) {
470
470
 
471
471
  sseEmit(res, { type: 'progress', message: `正在配置 ${tool.name}...` })
472
472
 
473
- // 劫持 console.log/warn,将 tool.configure() 内部的进度消息转发为 SSE 事件
474
- const origLog = console.log
475
- const origWarn = console.warn
476
- console.log = (...args) => {
477
- const msg = args.map(a => typeof a === 'string' ? a : String(a)).join(' ')
478
- const clean = msg.replace(/\x1b\[[0-9;]*m/g, '').trim()
479
- if (clean) sseEmit(res, { type: 'progress', message: clean })
480
- }
481
- console.warn = console.log
482
-
483
- try {
484
- const result = tool.configure(apiKey, BASE_URL_ANTHROPIC, BASE_URL_OPENAI, primaryModel, allModelIds)
485
-
486
- if (result.envVars && Object.keys(result.envVars).length > 0) {
487
- writeEnvToShell(result.envVars)
488
- sseEmit(res, { type: 'progress', message: '已写入环境变量到 shell 配置' })
489
- }
490
-
491
- try { removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']) } catch {}
492
-
493
- if (result.manual) {
494
- sseEmit(res, { type: 'result', status: 'manual', message: `${tool.name} 需要手动完成配置`, steps: result.steps })
495
- } else if (result.warning) {
496
- sseEmit(res, { type: 'result', status: 'warning', message: result.warning, file: result.file })
497
- } else {
498
- sseEmit(res, { type: 'result', status: 'ok', message: `${tool.name} 配置成功`, file: result.file, hot: result.hot })
473
+ // 用子进程运行 configure(),避免 spawnSync/busy-wait 阻塞主进程事件循环
474
+ const { fork } = require('child_process')
475
+ const child = fork(path.join(__dirname, 'configure-worker.js'), { silent: true })
476
+
477
+ child.send({ toolId, apiKey, baseUrlAnthropic: BASE_URL_ANTHROPIC, baseUrlOpenAI: BASE_URL_OPENAI, primaryModel, allModelIds })
478
+
479
+ let success = false
480
+ let lastResult = null
481
+
482
+ child.on('message', (msg) => {
483
+ if (msg.type === 'progress') {
484
+ sseEmit(res, msg)
485
+ } else if (msg.type === 'result') {
486
+ lastResult = msg
487
+ sseEmit(res, msg)
488
+ } else if (msg.type === 'error') {
489
+ sseEmit(res, msg)
499
490
  }
491
+ })
500
492
 
501
- sseEmit(res, { type: 'done', success: true, file: result.file, hot: result.hot, dashboardUrl: result.dashboardUrl || null })
502
- } catch (e) {
503
- sseEmit(res, { type: 'error', message: e.message })
504
- sseEmit(res, { type: 'done', success: false })
505
- } finally {
506
- console.log = origLog
507
- console.warn = origWarn
508
- }
509
- res.end()
493
+ await new Promise((resolve) => {
494
+ child.on('exit', (code) => {
495
+ success = code === 0 && lastResult?.status === 'ok'
496
+ sseEmit(res, {
497
+ type: 'done',
498
+ success,
499
+ file: lastResult?.file || null,
500
+ hot: lastResult?.hot || false,
501
+ dashboardUrl: lastResult?.dashboardUrl || null,
502
+ })
503
+ res.end()
504
+ resolve()
505
+ })
506
+ })
510
507
  }
511
508
 
512
509
  // ── Single-tool reset ────────────────────────────────────────────────────────