@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 +1 -1
- package/src/tools/opencode.js +2 -2
- package/src/webui/configure-worker.js +65 -0
- package/src/webui/index.html +30 -4
- package/src/webui/server.js +35 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "1.7.
|
|
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",
|
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
|
@@ -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')} →</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"
|
|
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')
|
package/src/webui/server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
474
|
-
const
|
|
475
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
483
|
+
let success = false
|
|
484
|
+
let lastResult = null
|
|
492
485
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
sseEmit(res,
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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 ────────────────────────────────────────────────────────
|