@simonyea/holysheep-cli 1.7.56 → 1.7.58

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.58",
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')
@@ -158,10 +158,13 @@ async function handleBalance(_req, res) {
158
158
  if (!apiKey) return json(res, { error: '未登录' }, 401)
159
159
  try {
160
160
  const fetch = require('node-fetch')
161
+ const controller = new AbortController()
162
+ const timer = setTimeout(() => controller.abort(), 15000)
161
163
  const r = await fetch(`${SHOP_URL}/api/stats/overview`, {
162
164
  headers: { Authorization: `Bearer ${apiKey}` },
163
- timeout: 8000,
165
+ signal: controller.signal,
164
166
  })
167
+ clearTimeout(timer)
165
168
  if (r.status === 401) return json(res, { error: 'API Key 无效或已过期' }, 401)
166
169
  if (!r.ok) return json(res, { error: `HTTP ${r.status}` }, r.status)
167
170
  const data = await r.json()
@@ -172,7 +175,8 @@ async function handleBalance(_req, res) {
172
175
  totalCalls: Number(data.totalCalls || 0),
173
176
  })
174
177
  } catch (e) {
175
- json(res, { error: e.message }, 500)
178
+ const msg = e.name === 'AbortError' ? '请求超时,请稍后重试' : e.message
179
+ json(res, { error: msg }, 500)
176
180
  }
177
181
  }
178
182
 
@@ -470,43 +474,40 @@ async function handleToolConfigure(req, res) {
470
474
 
471
475
  sseEmit(res, { type: 'progress', message: `正在配置 ${tool.name}...` })
472
476
 
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)
477
+ // 用子进程运行 configure(),避免 spawnSync/busy-wait 阻塞主进程事件循环
478
+ const { fork } = require('child_process')
479
+ const child = fork(path.join(__dirname, 'configure-worker.js'), { silent: true })
485
480
 
486
- if (result.envVars && Object.keys(result.envVars).length > 0) {
487
- writeEnvToShell(result.envVars)
488
- sseEmit(res, { type: 'progress', message: '已写入环境变量到 shell 配置' })
489
- }
481
+ child.send({ toolId, apiKey, baseUrlAnthropic: BASE_URL_ANTHROPIC, baseUrlOpenAI: BASE_URL_OPENAI, primaryModel, allModelIds })
490
482
 
491
- try { removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']) } catch {}
483
+ let success = false
484
+ let lastResult = null
492
485
 
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 })
486
+ child.on('message', (msg) => {
487
+ if (msg.type === 'progress') {
488
+ sseEmit(res, msg)
489
+ } else if (msg.type === 'result') {
490
+ lastResult = msg
491
+ sseEmit(res, msg)
492
+ } else if (msg.type === 'error') {
493
+ sseEmit(res, msg)
499
494
  }
495
+ })
500
496
 
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()
497
+ await new Promise((resolve) => {
498
+ child.on('exit', (code) => {
499
+ success = code === 0 && lastResult?.status === 'ok'
500
+ sseEmit(res, {
501
+ type: 'done',
502
+ success,
503
+ file: lastResult?.file || null,
504
+ hot: lastResult?.hot || false,
505
+ dashboardUrl: lastResult?.dashboardUrl || null,
506
+ })
507
+ res.end()
508
+ resolve()
509
+ })
510
+ })
510
511
  }
511
512
 
512
513
  // ── Single-tool reset ────────────────────────────────────────────────────────