@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 +1 -1
- package/src/tools/opencode.js +2 -2
- package/src/webui/configure-worker.js +65 -0
- package/src/webui/index.html +43 -4
- package/src/webui/server.js +38 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "1.7.
|
|
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",
|
package/src/tools/opencode.js
CHANGED
|
@@ -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(
|
|
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
|
+
})
|
package/src/webui/index.html
CHANGED
|
@@ -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')} →</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"
|
|
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
|
}
|
package/src/webui/server.js
CHANGED
|
@@ -460,35 +460,50 @@ async function handleToolConfigure(req, res) {
|
|
|
460
460
|
return res.end()
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
const allModelIds = [
|
|
464
|
-
|
|
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
|
-
|
|
469
|
-
|
|
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
|
-
|
|
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
|
-
|
|
479
|
+
let success = false
|
|
480
|
+
let lastResult = null
|
|
477
481
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
sseEmit(res,
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
])
|