@simonyea/holysheep-cli 1.7.58 → 1.7.60
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/openclaw.js +23 -2
- package/src/webui/index.html +35 -3
- package/src/webui/server.js +80 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.60",
|
|
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/openclaw.js
CHANGED
|
@@ -199,6 +199,21 @@ function waitForBridge(port) {
|
|
|
199
199
|
return false
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
function stopBridge() {
|
|
203
|
+
// 杀掉所有已有的 bridge 进程,避免重新配置时端口冲突
|
|
204
|
+
try {
|
|
205
|
+
if (isWin) {
|
|
206
|
+
execSync('taskkill /F /FI "WINDOWTITLE eq openclaw-bridge*" 2>nul', { shell: true, stdio: 'ignore' })
|
|
207
|
+
// 按命令行匹配
|
|
208
|
+
const out = execSync('wmic process where "commandline like \'%openclaw-bridge%\'" get processid', { shell: true, stdio: 'pipe', encoding: 'utf8' })
|
|
209
|
+
const pids = out.match(/\d+/g)
|
|
210
|
+
if (pids) pids.forEach(pid => { try { execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' }) } catch {} })
|
|
211
|
+
} else {
|
|
212
|
+
execSync("pkill -f 'openclaw-bridge' 2>/dev/null || true", { shell: true, stdio: 'ignore' })
|
|
213
|
+
}
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
|
|
202
217
|
function startBridge(port) {
|
|
203
218
|
if (waitForBridge(port)) return true
|
|
204
219
|
|
|
@@ -611,6 +626,14 @@ module.exports = {
|
|
|
611
626
|
this._lastRuntimeCommand = runtime.command
|
|
612
627
|
this._lastRuntimeVia = runtime.via
|
|
613
628
|
|
|
629
|
+
// 重新配置前先停掉旧的 Bridge 和 Gateway,释放端口
|
|
630
|
+
console.log(chalk.gray(' → 停止旧的 Bridge 和 Gateway...'))
|
|
631
|
+
stopBridge()
|
|
632
|
+
runOpenClaw(['gateway', 'stop'], { preferNpx: runtime.via === 'npx' })
|
|
633
|
+
// 等端口释放
|
|
634
|
+
const t0 = Date.now()
|
|
635
|
+
while (Date.now() - t0 < 1000) {}
|
|
636
|
+
|
|
614
637
|
if (runtime.via === 'npx' && removeBrokenLaunchAgent()) {
|
|
615
638
|
console.log(chalk.gray(' → 已清理旧的 OpenClaw 守护进程配置(失效的 npx 缓存路径)'))
|
|
616
639
|
}
|
|
@@ -654,8 +677,6 @@ module.exports = {
|
|
|
654
677
|
}
|
|
655
678
|
const bridgeBaseUrl = getBridgeBaseUrl(bridgePort)
|
|
656
679
|
|
|
657
|
-
runOpenClaw(['gateway', 'stop'], { preferNpx: runtime.via === 'npx' })
|
|
658
|
-
|
|
659
680
|
if (gatewayPort !== DEFAULT_GATEWAY_PORT) {
|
|
660
681
|
console.log(chalk.yellow(` ⚠️ 端口 ${DEFAULT_GATEWAY_PORT} 已占用,自动切换到 ${gatewayPort}`))
|
|
661
682
|
const listeners = listPortListeners(DEFAULT_GATEWAY_PORT)
|
package/src/webui/index.html
CHANGED
|
@@ -129,6 +129,9 @@ a:hover { text-decoration: underline; }
|
|
|
129
129
|
.footer-links { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; font-size: 0.85rem; margin-bottom: 8px; }
|
|
130
130
|
.footer-sub { font-size: 0.78rem; color: var(--text2); }
|
|
131
131
|
|
|
132
|
+
/* Update banner */
|
|
133
|
+
.update-banner { background: var(--primary-dim); border: 1px solid var(--primary); border-radius: 8px; padding: 10px 16px; margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; font-size: 0.85rem; }
|
|
134
|
+
|
|
132
135
|
/* Misc */
|
|
133
136
|
.loading { color: var(--text2); font-size: 0.85rem; }
|
|
134
137
|
.hidden { display: none !important; }
|
|
@@ -184,7 +187,8 @@ const I18N = {
|
|
|
184
187
|
installSuccess: '安装完成', installFailed: '安装失败',
|
|
185
188
|
needLogin: '请先登录', cleanDone: '已清理',
|
|
186
189
|
hotReload: '已生效,无需重启', needRestart: '重启终端后生效',
|
|
187
|
-
launch: '启动命令',
|
|
190
|
+
launch: '启动命令', upgradeOne: '升级',
|
|
191
|
+
updateAvailable: '有新版本可用', updateNow: '立即升级',
|
|
188
192
|
},
|
|
189
193
|
en: {
|
|
190
194
|
loggedIn: 'Logged in', notLoggedIn: 'Not logged in', login: 'Login', logout: 'Logout',
|
|
@@ -206,7 +210,8 @@ const I18N = {
|
|
|
206
210
|
installSuccess: 'Installed', installFailed: 'Install failed',
|
|
207
211
|
needLogin: 'Please login first', cleanDone: 'Cleaned',
|
|
208
212
|
hotReload: 'Active, no restart needed', needRestart: 'Restart terminal to apply',
|
|
209
|
-
launch: 'Launch',
|
|
213
|
+
launch: 'Launch', upgradeOne: 'Upgrade',
|
|
214
|
+
updateAvailable: 'Update available', updateNow: 'Update now',
|
|
210
215
|
},
|
|
211
216
|
}
|
|
212
217
|
|
|
@@ -251,6 +256,11 @@ async function loadAccount() {
|
|
|
251
256
|
|
|
252
257
|
document.getElementById('version').textContent = 'v' + (s.version || '')
|
|
253
258
|
|
|
259
|
+
// 版本更新提示
|
|
260
|
+
if (s.updateAvailable) {
|
|
261
|
+
document.getElementById('version').innerHTML = `v${s.version} <span style="color:var(--primary);cursor:pointer" onclick="doUpgradeTool('holysheep','HolySheep CLI')" title="${t('updateNow')}">→ v${s.updateAvailable} ${t('updateNow')}</span>`
|
|
262
|
+
}
|
|
263
|
+
|
|
254
264
|
if (s.loggedIn) {
|
|
255
265
|
const hasBalance = !b.error && typeof b.balance === 'number'
|
|
256
266
|
el.innerHTML = `<div class="card account-card account-logged-in">
|
|
@@ -333,12 +343,15 @@ function renderToolCard(tool) {
|
|
|
333
343
|
} else if (!tool.configured) {
|
|
334
344
|
dotClass = 'dot-warn'
|
|
335
345
|
statusBadges = `<span class="badge badge-ok">${t('installed')}</span> <span class="badge badge-warn">${t('notConfigured')}</span>`
|
|
336
|
-
|
|
346
|
+
const upgradeBtn = tool.canUpgrade ? `<button class="btn btn-outline btn-sm" onclick="doUpgradeTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('upgradeOne')}</button>` : ''
|
|
347
|
+
actions = `<button class="btn btn-primary btn-sm" onclick="doConfigureTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('configure')}</button>${upgradeBtn}`
|
|
337
348
|
hintLine = tool.version ? `<span class="mono" style="font-size:0.78rem;color:var(--text2)">${esc(tool.version)}</span>` : ''
|
|
338
349
|
} else {
|
|
339
350
|
dotClass = 'dot-ok'
|
|
340
351
|
statusBadges = `<span class="badge badge-ok">${t('installed')}</span> <span class="badge badge-ok">${t('configured')}</span>`
|
|
352
|
+
const upgradeBtn = tool.canUpgrade ? `<button class="btn btn-outline btn-sm" onclick="doUpgradeTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('upgradeOne')}</button>` : ''
|
|
341
353
|
actions = `<button class="btn btn-outline btn-sm" onclick="doConfigureTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('reconfigure')}</button>
|
|
354
|
+
${upgradeBtn}
|
|
342
355
|
<button class="btn btn-danger btn-sm" onclick="doResetTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('reset')}</button>`
|
|
343
356
|
hintLine = tool.version ? `<span class="mono" style="font-size:0.78rem;color:var(--text2)">${esc(tool.version)}</span>` : ''
|
|
344
357
|
}
|
|
@@ -445,6 +458,25 @@ async function doUpgrade() {
|
|
|
445
458
|
loadTools()
|
|
446
459
|
}
|
|
447
460
|
|
|
461
|
+
async function doUpgradeTool(id, name) {
|
|
462
|
+
if (busy) return
|
|
463
|
+
busy = true
|
|
464
|
+
openConsole(`${t('upgradeOne')}: ${name}`)
|
|
465
|
+
document.getElementById('console-section').classList.add('busy')
|
|
466
|
+
|
|
467
|
+
await streamSSE('/api/tool/upgrade', { toolId: id }, (ev) => {
|
|
468
|
+
if (ev.type === 'progress') appendLog(ev.message, 'info')
|
|
469
|
+
else if (ev.type === 'output') appendLogRaw(ev.text)
|
|
470
|
+
else if (ev.type === 'done') {
|
|
471
|
+
appendLog(ev.success ? `\n✓ ${t('configSuccess')}` : `\n✗ ${t('configFailed')}`, ev.success ? 'ok' : 'err')
|
|
472
|
+
}
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
document.getElementById('console-section').classList.remove('busy')
|
|
476
|
+
busy = false
|
|
477
|
+
loadTools()
|
|
478
|
+
}
|
|
479
|
+
|
|
448
480
|
// ── Console ──────────────────────────────────────────────────────────────────
|
|
449
481
|
function openConsole(title) {
|
|
450
482
|
const area = document.getElementById('console-section')
|
package/src/webui/server.js
CHANGED
|
@@ -96,6 +96,7 @@ const AUTO_INSTALL = {
|
|
|
96
96
|
// ── UPGRADABLE_TOOLS (from upgrade.js) ───────────────────────────────────────
|
|
97
97
|
|
|
98
98
|
const UPGRADABLE_TOOLS = [
|
|
99
|
+
{ name: 'HolySheep CLI', id: 'holysheep', command: 'hs', versionCmd: 'hs --version', npmPkg: '@simonyea/holysheep-cli', installCmd: 'npm install -g @simonyea/holysheep-cli@latest' },
|
|
99
100
|
{
|
|
100
101
|
name: 'Claude Code', id: 'claude-code', command: 'claude',
|
|
101
102
|
versionCmd: 'claude --version', npmPkg: null,
|
|
@@ -104,19 +105,49 @@ const UPGRADABLE_TOOLS = [
|
|
|
104
105
|
: 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
105
106
|
},
|
|
106
107
|
{ name: 'Codex CLI', id: 'codex', command: 'codex', versionCmd: 'codex --version', npmPkg: '@openai/codex', installCmd: 'npm install -g @openai/codex@latest' },
|
|
108
|
+
{ name: 'Droid CLI', id: 'droid', command: 'droid', versionCmd: 'droid --version', npmPkg: null, installCmd: 'brew install --cask droid' },
|
|
109
|
+
{ name: 'OpenCode', id: 'opencode', command: 'opencode', versionCmd: 'opencode --version', npmPkg: 'opencode-ai', installCmd: 'npm install -g opencode-ai@latest' },
|
|
110
|
+
{ name: 'OpenClaw', id: 'openclaw', command: 'openclaw', versionCmd: 'openclaw --version', npmPkg: 'openclaw', installCmd: 'npm install -g openclaw@latest --ignore-scripts' },
|
|
107
111
|
{ name: 'Gemini CLI', id: 'gemini-cli', command: 'gemini', versionCmd: 'gemini --version', npmPkg: '@google/gemini-cli', installCmd: 'npm install -g @google/gemini-cli@latest' },
|
|
108
112
|
]
|
|
109
113
|
|
|
114
|
+
// ── Update check (cached, refreshes every 30min) ────────────────────────────
|
|
115
|
+
|
|
116
|
+
let _latestVersion = null
|
|
117
|
+
let _lastCheckTime = 0
|
|
118
|
+
const UPDATE_CHECK_INTERVAL = 30 * 60 * 1000
|
|
119
|
+
|
|
120
|
+
async function checkLatestVersion() {
|
|
121
|
+
const now = Date.now()
|
|
122
|
+
if (_latestVersion && now - _lastCheckTime < UPDATE_CHECK_INTERVAL) return _latestVersion
|
|
123
|
+
try {
|
|
124
|
+
const fetch = require('node-fetch')
|
|
125
|
+
const r = await fetch('https://registry.npmjs.org/@simonyea/holysheep-cli/latest', { timeout: 5000 })
|
|
126
|
+
if (r.ok) {
|
|
127
|
+
const data = await r.json()
|
|
128
|
+
_latestVersion = data.version || null
|
|
129
|
+
_lastCheckTime = now
|
|
130
|
+
}
|
|
131
|
+
} catch {}
|
|
132
|
+
return _latestVersion
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 启动时立即检查一次
|
|
136
|
+
checkLatestVersion()
|
|
137
|
+
|
|
110
138
|
// ── API Handlers ─────────────────────────────────────────────────────────────
|
|
111
139
|
|
|
112
140
|
async function handleStatus(_req, res) {
|
|
113
141
|
const apiKey = getApiKey()
|
|
114
142
|
const config = loadConfig()
|
|
143
|
+
const latest = await checkLatestVersion()
|
|
115
144
|
json(res, {
|
|
116
145
|
loggedIn: !!apiKey,
|
|
117
146
|
apiKey: apiKey ? maskKey(apiKey) : null,
|
|
118
147
|
savedAt: config.savedAt || null,
|
|
119
148
|
version: pkg.version,
|
|
149
|
+
latestVersion: latest || null,
|
|
150
|
+
updateAvailable: latest && latest !== pkg.version ? latest : null,
|
|
120
151
|
})
|
|
121
152
|
}
|
|
122
153
|
|
|
@@ -243,6 +274,7 @@ async function handleTools(_req, res) {
|
|
|
243
274
|
hint: t.hint || null,
|
|
244
275
|
launchCmd: t.launchCmd || null,
|
|
245
276
|
canAutoInstall: !!AUTO_INSTALL[t.id],
|
|
277
|
+
canUpgrade: !!UPGRADABLE_TOOLS.find(u => u.id === t.id),
|
|
246
278
|
}
|
|
247
279
|
})
|
|
248
280
|
json(res, tools)
|
|
@@ -466,11 +498,10 @@ async function handleToolConfigure(req, res) {
|
|
|
466
498
|
|
|
467
499
|
const allModelIds = [
|
|
468
500
|
'gpt-5.4', 'gpt-5.3-codex-spark',
|
|
469
|
-
'claude-sonnet-4-6', 'claude-
|
|
470
|
-
'claude-opus-4-6', 'claude-opus-4-6[1m]',
|
|
501
|
+
'claude-sonnet-4-6', 'claude-opus-4-6',
|
|
471
502
|
'MiniMax-M2.7-highspeed', 'claude-haiku-4-5',
|
|
472
503
|
]
|
|
473
|
-
const primaryModel =
|
|
504
|
+
const primaryModel = 'claude-sonnet-4-6'
|
|
474
505
|
|
|
475
506
|
sseEmit(res, { type: 'progress', message: `正在配置 ${tool.name}...` })
|
|
476
507
|
|
|
@@ -510,6 +541,51 @@ async function handleToolConfigure(req, res) {
|
|
|
510
541
|
})
|
|
511
542
|
}
|
|
512
543
|
|
|
544
|
+
// ── Single-tool upgrade (SSE) ────────────────────────────────────────────────
|
|
545
|
+
|
|
546
|
+
async function handleToolUpgrade(req, res) {
|
|
547
|
+
const body = await parseBody(req)
|
|
548
|
+
const { toolId } = body
|
|
549
|
+
const entry = UPGRADABLE_TOOLS.find(t => t.id === toolId)
|
|
550
|
+
if (!entry) return json(res, { error: '不支持升级此工具' }, 400)
|
|
551
|
+
|
|
552
|
+
sseStart(res)
|
|
553
|
+
|
|
554
|
+
let localVer = null
|
|
555
|
+
try {
|
|
556
|
+
const out = execSync(entry.versionCmd, { stdio: 'pipe', timeout: 10000 }).toString().trim()
|
|
557
|
+
const m = out.match(/(\d+\.\d+\.\d+[\w.-]*)/)
|
|
558
|
+
localVer = m ? m[1] : out.split('\n')[0].slice(0, 30)
|
|
559
|
+
} catch {}
|
|
560
|
+
|
|
561
|
+
sseEmit(res, { type: 'progress', message: `${entry.name} 当前版本: ${localVer || '未知'}` })
|
|
562
|
+
sseEmit(res, { type: 'progress', message: `正在升级 ${entry.name}...` })
|
|
563
|
+
|
|
564
|
+
const ok = await new Promise(resolve => {
|
|
565
|
+
const child = spawn(entry.installCmd, [], { shell: true })
|
|
566
|
+
child.stdout?.on('data', chunk => sseEmit(res, { type: 'output', text: chunk.toString() }))
|
|
567
|
+
child.stderr?.on('data', chunk => sseEmit(res, { type: 'output', text: chunk.toString() }))
|
|
568
|
+
child.on('close', code => resolve(code === 0))
|
|
569
|
+
child.on('error', () => resolve(false))
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
let newVer = null
|
|
573
|
+
try {
|
|
574
|
+
const out = execSync(entry.versionCmd, { stdio: 'pipe', timeout: 10000 }).toString().trim()
|
|
575
|
+
const m = out.match(/(\d+\.\d+\.\d+[\w.-]*)/)
|
|
576
|
+
newVer = m ? m[1] : null
|
|
577
|
+
} catch {}
|
|
578
|
+
|
|
579
|
+
if (ok) {
|
|
580
|
+
sseEmit(res, { type: 'progress', message: `✓ ${entry.name} 升级成功: ${localVer || '?'} → ${newVer || 'latest'}` })
|
|
581
|
+
} else {
|
|
582
|
+
sseEmit(res, { type: 'progress', message: `✗ ${entry.name} 升级失败` })
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
sseEmit(res, { type: 'done', success: ok, localVer, newVer })
|
|
586
|
+
res.end()
|
|
587
|
+
}
|
|
588
|
+
|
|
513
589
|
// ── Single-tool reset ────────────────────────────────────────────────────────
|
|
514
590
|
|
|
515
591
|
async function handleToolReset(req, res) {
|
|
@@ -605,6 +681,7 @@ async function handleRequest(req, res) {
|
|
|
605
681
|
if (route === '/api/tool/install' && req.method === 'POST') return await handleToolInstall(req, res)
|
|
606
682
|
if (route === '/api/tool/configure' && req.method === 'POST') return await handleToolConfigure(req, res)
|
|
607
683
|
if (route === '/api/tool/reset' && req.method === 'POST') return await handleToolReset(req, res)
|
|
684
|
+
if (route === '/api/tool/upgrade' && req.method === 'POST') return await handleToolUpgrade(req, res)
|
|
608
685
|
if (route === '/api/env' && req.method === 'GET') return handleEnv(req, res)
|
|
609
686
|
if (route === '/api/env/clean' && req.method === 'POST') return handleEnvClean(req, res)
|
|
610
687
|
|