@simonyea/holysheep-cli 1.7.55 → 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.55",
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
+ })
@@ -101,6 +101,11 @@ a:hover { text-decoration: underline; }
101
101
  /* Console */
102
102
  .console-area { margin-top: 12px; display: none; }
103
103
  .console-area.open { display: block; }
104
+ .console-area.busy .console-header span::after {
105
+ content: ' '; display: inline-block; width: 6px; height: 6px; border-radius: 50%;
106
+ background: var(--primary); margin-left: 8px; animation: pulse 1s infinite;
107
+ }
108
+ @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.2; } }
104
109
  .console-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
105
110
  .console-header span { font-size: 0.85rem; font-weight: 600; }
106
111
  .console { background: #111; color: #ccc; font-family: var(--mono); font-size: 0.78rem; padding: 12px; border-radius: 8px; max-height: 260px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; line-height: 1.6; }
@@ -164,7 +169,7 @@ const I18N = {
164
169
  balance: '余额', today: '今日消费', month: '本月消费', calls: '累计调用',
165
170
  recharge: '充值', register: '没有账号?去注册',
166
171
  apiKeyPlaceholder: '请输入 API Key (cr_xxx)',
167
- tools: 'AI 工具', toolsHint: '一键配置使用 HolySheep API',
172
+ tools: 'AI 工具', toolsHint: '一键配置使用 HolySheep API', upgrade: '升级工具',
168
173
  installed: '已安装', notInstalled: '未安装', configured: '已配置', notConfigured: '未配置',
169
174
  configure: '一键配置', reconfigure: '重新配置', reset: '重置', install: '安装',
170
175
  installManual: '手动安装',
@@ -186,7 +191,7 @@ const I18N = {
186
191
  balance: 'Balance', today: 'Today', month: 'This Month', calls: 'Total Calls',
187
192
  recharge: 'Recharge', register: 'No account? Register',
188
193
  apiKeyPlaceholder: 'Enter API Key (cr_xxx)',
189
- tools: 'AI Tools', toolsHint: 'One-click setup for HolySheep API',
194
+ tools: 'AI Tools', toolsHint: 'One-click setup for HolySheep API', upgrade: 'Upgrade Tools',
190
195
  installed: 'Installed', notInstalled: 'Not installed', configured: 'Configured', notConfigured: 'Not configured',
191
196
  configure: 'Configure', reconfigure: 'Reconfigure', reset: 'Reset', install: 'Install',
192
197
  installManual: 'Manual install',
@@ -260,7 +265,7 @@ async function loadAccount() {
260
265
  <div><div class="stat-val">$${fmtNum(b.todayCost)}</div><div class="stat-lbl">${t('today')}</div></div>
261
266
  <div><div class="stat-val">$${fmtNum(b.monthCost)}</div><div class="stat-lbl">${t('month')}</div></div>
262
267
  <div><div class="stat-val">${b.totalCalls.toLocaleString()}</div><div class="stat-lbl">${t('calls')}</div></div>
263
- </div>` : ''}
268
+ </div>` : `<div style="color:var(--text2);font-size:0.85rem;margin:10px 0">${b.error ? esc(b.error) : t('checking')}</div>`}
264
269
  <div class="account-actions">
265
270
  <a class="btn btn-primary btn-sm" href="https://holysheep.ai/app/recharge" target="_blank">${t('recharge')} &rarr;</a>
266
271
  <button class="btn btn-danger btn-sm" onclick="doLogout()">${t('logout')}</button>
@@ -302,7 +307,10 @@ async function doLogout() {
302
307
  // ── Tools section ────────────────────────────────────────────────────────────
303
308
  async function loadTools() {
304
309
  const el = document.getElementById('tools-section')
305
- 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>
306
314
  <div class="tool-grid"><span class="loading">${t('checking')}</span></div>`
307
315
 
308
316
  const tools = await api('tools')
@@ -360,6 +368,7 @@ async function doConfigureTool(id, name) {
360
368
  if (busy) return
361
369
  busy = true
362
370
  openConsole(`${t('configure')}: ${name}`)
371
+ document.getElementById('console-section').classList.add('busy')
363
372
 
364
373
  await streamSSE('/api/tool/configure', { toolId: id }, (ev) => {
365
374
  if (ev.type === 'progress') appendLog(ev.message, 'info')
@@ -374,9 +383,14 @@ async function doConfigureTool(id, name) {
374
383
  else if (ev.type === 'error') appendLog(`✗ ${ev.message}`, 'err')
375
384
  else if (ev.type === 'done') {
376
385
  appendLog(ev.success ? `\n✓ ${t('configSuccess')}` : `\n✗ ${t('configFailed')}`, ev.success ? 'ok' : 'err')
386
+ if (ev.dashboardUrl) {
387
+ appendLog(`\n→ ${ev.dashboardUrl}`, 'ok')
388
+ window.open(ev.dashboardUrl, '_blank')
389
+ }
377
390
  }
378
391
  })
379
392
 
393
+ document.getElementById('console-section').classList.remove('busy')
380
394
  busy = false
381
395
  loadTools()
382
396
  }
@@ -393,6 +407,7 @@ async function doInstallTool(id, name) {
393
407
  if (busy) return
394
408
  busy = true
395
409
  openConsole(`${t('install')}: ${name}`)
410
+ document.getElementById('console-section').classList.add('busy')
396
411
 
397
412
  await streamSSE('/api/tool/install', { toolId: id }, (ev) => {
398
413
  if (ev.type === 'progress') appendLog(ev.message, 'info')
@@ -402,6 +417,30 @@ async function doInstallTool(id, name) {
402
417
  }
403
418
  })
404
419
 
420
+ document.getElementById('console-section').classList.remove('busy')
421
+ busy = false
422
+ loadTools()
423
+ }
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')
405
444
  busy = false
406
445
  loadTools()
407
446
  }
@@ -460,35 +460,50 @@ async function handleToolConfigure(req, res) {
460
460
  return res.end()
461
461
  }
462
462
 
463
- const allModelIds = ['gpt-5.4', 'gpt-5.3-codex-spark', 'claude-sonnet-4-6', 'claude-opus-4-6', 'MiniMax-M2.7-highspeed', 'claude-haiku-4-5']
464
- const primaryModel = 'claude-sonnet-4-6'
463
+ const allModelIds = [
464
+ 'gpt-5.4', 'gpt-5.3-codex-spark',
465
+ 'claude-sonnet-4-6', 'claude-sonnet-4-6[1m]',
466
+ 'claude-opus-4-6', 'claude-opus-4-6[1m]',
467
+ 'MiniMax-M2.7-highspeed', 'claude-haiku-4-5',
468
+ ]
469
+ const primaryModel = toolId === 'openclaw' ? 'claude-sonnet-4-6[1m]' : 'claude-sonnet-4-6'
465
470
 
466
471
  sseEmit(res, { type: 'progress', message: `正在配置 ${tool.name}...` })
467
472
 
468
- try {
469
- const result = tool.configure(apiKey, BASE_URL_ANTHROPIC, BASE_URL_OPENAI, primaryModel, allModelIds)
473
+ // 用子进程运行 configure(),避免 spawnSync/busy-wait 阻塞主进程事件循环
474
+ const { fork } = require('child_process')
475
+ const child = fork(path.join(__dirname, 'configure-worker.js'), { silent: true })
470
476
 
471
- if (result.envVars && Object.keys(result.envVars).length > 0) {
472
- writeEnvToShell(result.envVars)
473
- sseEmit(res, { type: 'progress', message: '已写入环境变量到 shell 配置' })
474
- }
477
+ child.send({ toolId, apiKey, baseUrlAnthropic: BASE_URL_ANTHROPIC, baseUrlOpenAI: BASE_URL_OPENAI, primaryModel, allModelIds })
475
478
 
476
- try { removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']) } catch {}
479
+ let success = false
480
+ let lastResult = null
477
481
 
478
- if (result.manual) {
479
- sseEmit(res, { type: 'result', status: 'manual', message: `${tool.name} 需要手动完成配置`, steps: result.steps })
480
- } else if (result.warning) {
481
- sseEmit(res, { type: 'result', status: 'warning', message: result.warning, file: result.file })
482
- } else {
483
- sseEmit(res, { type: 'result', status: 'ok', message: `${tool.name} 配置成功`, file: result.file, hot: result.hot })
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)
484
490
  }
491
+ })
485
492
 
486
- sseEmit(res, { type: 'done', success: true, file: result.file, hot: result.hot })
487
- } catch (e) {
488
- sseEmit(res, { type: 'error', message: e.message })
489
- sseEmit(res, { type: 'done', success: false })
490
- }
491
- 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
+ })
492
507
  }
493
508
 
494
509
  // ── Single-tool reset ────────────────────────────────────────────────────────
@@ -546,7 +561,9 @@ function handleModels(_req, res) {
546
561
  { id: 'gpt-5.4', label: 'GPT 5.4', desc: '通用编码' },
547
562
  { id: 'gpt-5.3-codex-spark', label: 'GPT 5.3 Codex Spark', desc: '编码' },
548
563
  { id: 'claude-sonnet-4-6', label: 'Sonnet 4.6', desc: '均衡推荐' },
564
+ { id: 'claude-sonnet-4-6[1m]', label: 'Sonnet 4.6 (1M)', desc: '均衡推荐·100万上下文' },
549
565
  { id: 'claude-opus-4-6', label: 'Opus 4.6', desc: '强力旗舰' },
566
+ { id: 'claude-opus-4-6[1m]', label: 'Opus 4.6 (1M)', desc: '强力旗舰·100万上下文' },
550
567
  { id: 'MiniMax-M2.7-highspeed', label: 'MiniMax M2.7', desc: '高速经济版' },
551
568
  { id: 'claude-haiku-4-5', label: 'Haiku 4.5', desc: '轻快便宜' },
552
569
  ])