@simonyea/holysheep-cli 1.7.116 → 1.7.118
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/webui/index.html +28 -4
- package/src/webui/server.js +199 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.118",
|
|
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/webui/index.html
CHANGED
|
@@ -187,7 +187,7 @@ const I18N = {
|
|
|
187
187
|
installSuccess: '安装完成', installFailed: '安装失败',
|
|
188
188
|
needLogin: '请先登录', cleanDone: '已清理',
|
|
189
189
|
hotReload: '已生效,无需重启', needRestart: '重启终端后生效',
|
|
190
|
-
launch: '启动命令', upgradeOne: '升级', open: '打开',
|
|
190
|
+
launch: '启动命令', upgradeOne: '升级', rollback: '回退版本', open: '打开',
|
|
191
191
|
updateAvailable: '有新版本可用', updateNow: '立即升级',
|
|
192
192
|
},
|
|
193
193
|
en: {
|
|
@@ -210,7 +210,7 @@ const I18N = {
|
|
|
210
210
|
installSuccess: 'Installed', installFailed: 'Install failed',
|
|
211
211
|
needLogin: 'Please login first', cleanDone: 'Cleaned',
|
|
212
212
|
hotReload: 'Active, no restart needed', needRestart: 'Restart terminal to apply',
|
|
213
|
-
launch: 'Launch', upgradeOne: 'Upgrade', open: 'Open',
|
|
213
|
+
launch: 'Launch', upgradeOne: 'Upgrade', rollback: 'Rollback', open: 'Open',
|
|
214
214
|
updateAvailable: 'Update available', updateNow: 'Update now',
|
|
215
215
|
},
|
|
216
216
|
}
|
|
@@ -384,15 +384,17 @@ function renderToolCard(tool) {
|
|
|
384
384
|
dotClass = 'dot-warn'
|
|
385
385
|
statusBadges = `<span class="badge badge-ok">${t('installed')}</span> <span class="badge badge-warn">${t('notConfigured')}</span>`
|
|
386
386
|
const upgradeBtn = tool.canUpgrade ? `<button class="btn btn-outline btn-sm" onclick="doUpgradeTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('upgradeOne')}</button>` : ''
|
|
387
|
-
|
|
387
|
+
const rollbackBtn = tool.canUpgrade && tool.npmPkg ? `<button class="btn btn-outline btn-sm" onclick="doRollbackTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('rollback')}</button>` : ''
|
|
388
|
+
actions = `<button class="btn btn-primary btn-sm" onclick="doConfigureTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('configure')}</button>${upgradeBtn}${rollbackBtn}`
|
|
388
389
|
hintLine = tool.version ? `<span class="mono" style="font-size:0.78rem;color:var(--text2)">${esc(tool.version)}</span>` : ''
|
|
389
390
|
} else {
|
|
390
391
|
dotClass = 'dot-ok'
|
|
391
392
|
statusBadges = `<span class="badge badge-ok">${t('installed')}</span> <span class="badge badge-ok">${t('configured')}</span>`
|
|
392
393
|
const upgradeBtn = tool.canUpgrade ? `<button class="btn btn-outline btn-sm" onclick="doUpgradeTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('upgradeOne')}</button>` : ''
|
|
394
|
+
const rollbackBtn = tool.canUpgrade && tool.npmPkg ? `<button class="btn btn-outline btn-sm" onclick="doRollbackTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('rollback')}</button>` : ''
|
|
393
395
|
actions = `<button class="btn btn-primary btn-sm" onclick="doLaunchTool('${tool.id}')">${t('open')}</button>
|
|
394
396
|
<button class="btn btn-outline btn-sm" onclick="doConfigureTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('reconfigure')}</button>
|
|
395
|
-
${upgradeBtn}
|
|
397
|
+
${upgradeBtn}${rollbackBtn}
|
|
396
398
|
<button class="btn btn-danger btn-sm" onclick="doResetTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('reset')}</button>`
|
|
397
399
|
hintLine = tool.version ? `<span class="mono" style="font-size:0.78rem;color:var(--text2)">${esc(tool.version)}</span>` : ''
|
|
398
400
|
}
|
|
@@ -548,6 +550,28 @@ async function doUpgradeTool(id, name) {
|
|
|
548
550
|
loadTools()
|
|
549
551
|
}
|
|
550
552
|
|
|
553
|
+
async function doRollbackTool(id, name) {
|
|
554
|
+
if (busy) return
|
|
555
|
+
if (!confirm(lang === 'zh' ? `确认回退 ${name} 到上一个版本?` : `Rollback ${name} to previous version?`)) return
|
|
556
|
+
busy = true
|
|
557
|
+
openConsole(`${t('rollback')}: ${name}`)
|
|
558
|
+
document.getElementById('console-section').classList.add('busy')
|
|
559
|
+
|
|
560
|
+
let success = false
|
|
561
|
+
await streamSSE('/api/tool/rollback', { toolId: id }, (ev) => {
|
|
562
|
+
if (ev.type === 'progress') appendLog(ev.message, 'info')
|
|
563
|
+
else if (ev.type === 'output') appendLogRaw(ev.text)
|
|
564
|
+
else if (ev.type === 'done') {
|
|
565
|
+
success = ev.success
|
|
566
|
+
appendLog(ev.success ? `\n✓ ${lang === 'zh' ? '回退成功' : 'Rollback succeeded'}` : `\n✗ ${lang === 'zh' ? '回退失败' : 'Rollback failed'}`, ev.success ? 'ok' : 'err')
|
|
567
|
+
}
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
document.getElementById('console-section').classList.remove('busy')
|
|
571
|
+
busy = false
|
|
572
|
+
loadTools()
|
|
573
|
+
}
|
|
574
|
+
|
|
551
575
|
// ── Console ──────────────────────────────────────────────────────────────────
|
|
552
576
|
function openConsole(title) {
|
|
553
577
|
const area = document.getElementById('console-section')
|
package/src/webui/server.js
CHANGED
|
@@ -310,6 +310,7 @@ async function handleTools(_req, res) {
|
|
|
310
310
|
launchCmd: t.launchCmd || null,
|
|
311
311
|
canAutoInstall: !!AUTO_INSTALL[t.id],
|
|
312
312
|
canUpgrade: !!UPGRADABLE_TOOLS.find(u => u.id === t.id),
|
|
313
|
+
npmPkg: UPGRADABLE_TOOLS.find(u => u.id === t.id)?.npmPkg || null,
|
|
313
314
|
}
|
|
314
315
|
}))
|
|
315
316
|
|
|
@@ -502,12 +503,30 @@ async function handleUpgrade(_req, res) {
|
|
|
502
503
|
newVer = m ? m[1] : null
|
|
503
504
|
} catch {}
|
|
504
505
|
|
|
505
|
-
// OpenClaw
|
|
506
|
+
// OpenClaw 升级后:Gateway → 更新 PID → Bridge
|
|
506
507
|
if (ok && tool.id === 'openclaw') {
|
|
507
508
|
try { execSync('openclaw daemon stop', { stdio: 'ignore', timeout: 10000 }) } catch {}
|
|
509
|
+
try { execSync('openclaw daemon start', { stdio: 'ignore', timeout: 30000 }) } catch {}
|
|
508
510
|
const openclawTool = TOOLS.find(t => t.id === 'openclaw')
|
|
511
|
+
try {
|
|
512
|
+
const gPort = openclawTool?.getGatewayPort?.() || 18789
|
|
513
|
+
let gPid = null
|
|
514
|
+
if (process.platform === 'win32') {
|
|
515
|
+
const o = execSync(`powershell -NonInteractive -Command "(Get-NetTCPConnection -LocalPort ${gPort} -State Listen -ErrorAction SilentlyContinue).OwningProcess"`, { stdio: 'pipe', timeout: 5000 }).toString().trim()
|
|
516
|
+
gPid = Number(o.split(/\r?\n/)[0]) || null
|
|
517
|
+
} else {
|
|
518
|
+
const o = execSync(`lsof -iTCP:${gPort} -sTCP:LISTEN -t 2>/dev/null | head -1`, { stdio: 'pipe', timeout: 5000 }).toString().trim()
|
|
519
|
+
gPid = Number(o) || null
|
|
520
|
+
}
|
|
521
|
+
if (gPid) {
|
|
522
|
+
const bridgeMod = require('../tools/openclaw-bridge')
|
|
523
|
+
const bc = bridgeMod.readBridgeConfig()
|
|
524
|
+
bc.gatewayPid = gPid
|
|
525
|
+
bc.gatewayStartedAt = new Date().toISOString()
|
|
526
|
+
fs.writeFileSync(bridgeMod.BRIDGE_CONFIG_FILE, JSON.stringify(bc, null, 2), 'utf8')
|
|
527
|
+
}
|
|
528
|
+
} catch {}
|
|
509
529
|
try { openclawTool?.ensureBridgeRunning?.() } catch {}
|
|
510
|
-
try { execSync('openclaw daemon start', { stdio: 'ignore', timeout: 30000 }) } catch {}
|
|
511
530
|
}
|
|
512
531
|
|
|
513
532
|
sseEmit(res, { type: 'tool', name: tool.name, status: ok ? 'ok' : 'error', localVer, newVer })
|
|
@@ -661,11 +680,37 @@ async function handleToolUpgrade(req, res) {
|
|
|
661
680
|
|
|
662
681
|
if (ok) {
|
|
663
682
|
sseEmit(res, { type: 'progress', message: `✓ ${entry.name} 升级成功: ${localVer || '?'} → ${newVer || 'latest'}` })
|
|
664
|
-
// OpenClaw
|
|
683
|
+
// OpenClaw 升级后:先停 → 启动 Gateway(拿 PID)→ 更新 bridge config → 启动 Bridge
|
|
665
684
|
if (entry.id === 'openclaw') {
|
|
666
685
|
const openclawTool = TOOLS.find(t => t.id === 'openclaw')
|
|
667
686
|
sseEmit(res, { type: 'progress', message: '正在重启 OpenClaw...' })
|
|
668
687
|
try { execSync('openclaw daemon stop', { stdio: 'ignore', timeout: 10000 }) } catch {}
|
|
688
|
+
try {
|
|
689
|
+
execSync('openclaw daemon start', { stdio: 'ignore', timeout: 30000 })
|
|
690
|
+
sseEmit(res, { type: 'progress', message: '✓ OpenClaw Gateway 已启动' })
|
|
691
|
+
} catch {
|
|
692
|
+
sseEmit(res, { type: 'progress', message: '⚠️ Gateway 启动失败' })
|
|
693
|
+
}
|
|
694
|
+
// 获取 Gateway PID 写入 bridge config
|
|
695
|
+
try {
|
|
696
|
+
const gatewayPort = openclawTool?.getGatewayPort?.() || 18789
|
|
697
|
+
let gatewayPid = null
|
|
698
|
+
if (process.platform === 'win32') {
|
|
699
|
+
const out = execSync(`powershell -NonInteractive -Command "(Get-NetTCPConnection -LocalPort ${gatewayPort} -State Listen -ErrorAction SilentlyContinue).OwningProcess"`, { stdio: 'pipe', timeout: 5000 }).toString().trim()
|
|
700
|
+
gatewayPid = Number(out.split(/\r?\n/)[0]) || null
|
|
701
|
+
} else {
|
|
702
|
+
const out = execSync(`lsof -iTCP:${gatewayPort} -sTCP:LISTEN -t 2>/dev/null | head -1`, { stdio: 'pipe', timeout: 5000 }).toString().trim()
|
|
703
|
+
gatewayPid = Number(out) || null
|
|
704
|
+
}
|
|
705
|
+
if (gatewayPid) {
|
|
706
|
+
const bridgeMod = require('../tools/openclaw-bridge')
|
|
707
|
+
const bc = bridgeMod.readBridgeConfig()
|
|
708
|
+
bc.gatewayPid = gatewayPid
|
|
709
|
+
bc.gatewayStartedAt = new Date().toISOString()
|
|
710
|
+
fs.writeFileSync(bridgeMod.BRIDGE_CONFIG_FILE, JSON.stringify(bc, null, 2), 'utf8')
|
|
711
|
+
}
|
|
712
|
+
} catch {}
|
|
713
|
+
// 启动 Bridge
|
|
669
714
|
if (openclawTool?.ensureBridgeRunning) {
|
|
670
715
|
try {
|
|
671
716
|
openclawTool.ensureBridgeRunning()
|
|
@@ -674,18 +719,164 @@ async function handleToolUpgrade(req, res) {
|
|
|
674
719
|
sseEmit(res, { type: 'progress', message: '⚠️ Bridge 启动失败' })
|
|
675
720
|
}
|
|
676
721
|
}
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
sseEmit(res, { type: 'progress', message: `✗ ${entry.name} 升级失败` })
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
sseEmit(res, { type: 'done', success: ok, localVer, newVer })
|
|
728
|
+
res.end()
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// ── Single-tool rollback (SSE) ────────────────────────────────────────────────
|
|
732
|
+
|
|
733
|
+
async function handleToolRollback(req, res) {
|
|
734
|
+
const body = await parseBody(req)
|
|
735
|
+
const { toolId } = body
|
|
736
|
+
const entry = UPGRADABLE_TOOLS.find(t => t.id === toolId)
|
|
737
|
+
if (!entry || !entry.npmPkg) return json(res, { error: '不支持回退此工具(仅限 npm 工具)' }, 400)
|
|
738
|
+
|
|
739
|
+
sseStart(res)
|
|
740
|
+
|
|
741
|
+
// 1. 获取当前本地版本
|
|
742
|
+
let localVer = null
|
|
743
|
+
try {
|
|
744
|
+
const out = execSync(entry.versionCmd, { stdio: 'pipe', timeout: 10000 }).toString().trim()
|
|
745
|
+
const m = out.match(/(\d+\.\d+\.\d+[\w.-]*)/)
|
|
746
|
+
localVer = m ? m[1] : null
|
|
747
|
+
} catch {}
|
|
748
|
+
sseEmit(res, { type: 'progress', message: `${entry.name} 当前版本: ${localVer || '未知'}` })
|
|
749
|
+
|
|
750
|
+
// 2. 从 npm registry 获取版本列表,找到倒数第二个
|
|
751
|
+
let targetVer = null
|
|
752
|
+
try {
|
|
753
|
+
sseEmit(res, { type: 'progress', message: '正在查询可用版本...' })
|
|
754
|
+
const r = await fetchWithRetry(`https://registry.npmjs.org/${entry.npmPkg}`, {}, 2, 15000)
|
|
755
|
+
if (r.ok) {
|
|
756
|
+
const data = await r.json()
|
|
757
|
+
const versions = Object.keys(data.versions || {})
|
|
758
|
+
.filter(v => !v.includes('alpha') && !v.includes('beta') && !v.includes('rc') && !v.includes('canary'))
|
|
759
|
+
if (versions.length >= 2) {
|
|
760
|
+
// 找到当前版本的前一个,或者倒数第二个
|
|
761
|
+
const currentIdx = localVer ? versions.indexOf(localVer) : -1
|
|
762
|
+
if (currentIdx > 0) {
|
|
763
|
+
targetVer = versions[currentIdx - 1]
|
|
764
|
+
} else {
|
|
765
|
+
targetVer = versions[versions.length - 2]
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
} catch (e) {
|
|
770
|
+
sseEmit(res, { type: 'progress', message: `⚠️ 查询版本失败: ${e.message}` })
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (!targetVer) {
|
|
774
|
+
sseEmit(res, { type: 'progress', message: '✗ 无法确定回退版本' })
|
|
775
|
+
sseEmit(res, { type: 'done', success: false })
|
|
776
|
+
return res.end()
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
sseEmit(res, { type: 'progress', message: `回退目标: ${localVer || '?'} → ${targetVer}` })
|
|
780
|
+
|
|
781
|
+
// 3. Windows: kill 进程 + 停止 daemon
|
|
782
|
+
if (entry.id === 'openclaw') {
|
|
783
|
+
try { execSync('openclaw daemon stop', { stdio: 'ignore', timeout: 10000 }) } catch {}
|
|
784
|
+
}
|
|
785
|
+
if (process.platform === 'win32' && entry.command) {
|
|
786
|
+
try {
|
|
787
|
+
execSync(`taskkill /F /IM ${entry.command}.exe /T`, { stdio: 'ignore', shell: true })
|
|
788
|
+
sseEmit(res, { type: 'progress', message: `已关闭 ${entry.name} 进程` })
|
|
789
|
+
} catch {}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// 4. 清理残留: npm cache + 删除 node_modules 目录
|
|
793
|
+
sseEmit(res, { type: 'progress', message: '正在清理残留文件...' })
|
|
794
|
+
try { execSync('npm cache clean --force', { stdio: 'ignore', timeout: 30000 }) } catch {}
|
|
795
|
+
try { execSync(`npm uninstall -g ${entry.npmPkg}`, { stdio: 'ignore', timeout: 30000 }) } catch {}
|
|
796
|
+
// Windows 上 npm uninstall 可能残留文件
|
|
797
|
+
if (process.platform === 'win32') {
|
|
798
|
+
const globalPrefix = String(execSync('npm prefix -g', { stdio: 'pipe', timeout: 5000 })).trim()
|
|
799
|
+
const modulePath = path.join(globalPrefix, 'node_modules', ...entry.npmPkg.split('/'))
|
|
800
|
+
try {
|
|
801
|
+
if (fs.existsSync(modulePath)) {
|
|
802
|
+
execSync(`rd /s /q "${modulePath}"`, { stdio: 'ignore', shell: true, timeout: 10000 })
|
|
803
|
+
sseEmit(res, { type: 'progress', message: '已清理残留目录' })
|
|
804
|
+
}
|
|
805
|
+
} catch {}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// 5. 安装目标版本
|
|
809
|
+
sseEmit(res, { type: 'progress', message: `正在安装 ${entry.npmPkg}@${targetVer}...` })
|
|
810
|
+
const installCmd = `npm install -g ${entry.npmPkg}@${targetVer}`
|
|
811
|
+
const ok = await new Promise(resolve => {
|
|
812
|
+
const child = spawn(installCmd, [], { shell: true })
|
|
813
|
+
const timer = setTimeout(() => {
|
|
814
|
+
sseEmit(res, { type: 'progress', message: `⚠️ 安装超时` })
|
|
815
|
+
try { child.kill('SIGKILL') } catch {}
|
|
816
|
+
resolve(false)
|
|
817
|
+
}, 5 * 60 * 1000)
|
|
818
|
+
child.stdout?.on('data', chunk => sseEmit(res, { type: 'output', text: chunk.toString() }))
|
|
819
|
+
child.stderr?.on('data', chunk => sseEmit(res, { type: 'output', text: chunk.toString() }))
|
|
820
|
+
child.on('close', code => { clearTimeout(timer); resolve(code === 0) })
|
|
821
|
+
child.on('error', () => { clearTimeout(timer); resolve(false) })
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
// 6. 验证版本
|
|
825
|
+
let newVer = null
|
|
826
|
+
try {
|
|
827
|
+
const out = execSync(entry.versionCmd, { stdio: 'pipe', timeout: 10000 }).toString().trim()
|
|
828
|
+
const m = out.match(/(\d+\.\d+\.\d+[\w.-]*)/)
|
|
829
|
+
newVer = m ? m[1] : null
|
|
830
|
+
} catch {}
|
|
831
|
+
|
|
832
|
+
if (ok) {
|
|
833
|
+
sseEmit(res, { type: 'progress', message: `✓ ${entry.name} 已回退: ${localVer || '?'} → ${newVer || targetVer}` })
|
|
834
|
+
// OpenClaw: 先启动 Gateway(拿 PID),更新 bridge config,再启动 Bridge
|
|
835
|
+
if (entry.id === 'openclaw') {
|
|
836
|
+
sseEmit(res, { type: 'progress', message: '正在重启 OpenClaw...' })
|
|
837
|
+
// 1. 启动 Gateway
|
|
677
838
|
try {
|
|
678
839
|
execSync('openclaw daemon start', { stdio: 'ignore', timeout: 30000 })
|
|
679
|
-
sseEmit(res, { type: 'progress', message: '✓ OpenClaw Gateway
|
|
840
|
+
sseEmit(res, { type: 'progress', message: '✓ OpenClaw Gateway 已启动' })
|
|
680
841
|
} catch {
|
|
681
|
-
sseEmit(res, { type: 'progress', message: '⚠️ Gateway
|
|
842
|
+
sseEmit(res, { type: 'progress', message: '⚠️ Gateway 启动失败' })
|
|
843
|
+
}
|
|
844
|
+
// 2. 获取 Gateway PID 并写入 bridge config,否则 bridge watchdog 会自杀
|
|
845
|
+
const openclawTool = TOOLS.find(t => t.id === 'openclaw')
|
|
846
|
+
try {
|
|
847
|
+
const gatewayPort = openclawTool?.getGatewayPort?.() || 18789
|
|
848
|
+
let gatewayPid = null
|
|
849
|
+
if (process.platform === 'win32') {
|
|
850
|
+
const out = execSync(`powershell -NonInteractive -Command "(Get-NetTCPConnection -LocalPort ${gatewayPort} -State Listen -ErrorAction SilentlyContinue).OwningProcess"`, { stdio: 'pipe', timeout: 5000 }).toString().trim()
|
|
851
|
+
gatewayPid = Number(out.split(/\r?\n/)[0]) || null
|
|
852
|
+
} else {
|
|
853
|
+
const out = execSync(`lsof -iTCP:${gatewayPort} -sTCP:LISTEN -t 2>/dev/null | head -1`, { stdio: 'pipe', timeout: 5000 }).toString().trim()
|
|
854
|
+
gatewayPid = Number(out) || null
|
|
855
|
+
}
|
|
856
|
+
if (gatewayPid) {
|
|
857
|
+
const bridgeMod = require('../tools/openclaw-bridge')
|
|
858
|
+
const bc = bridgeMod.readBridgeConfig()
|
|
859
|
+
bc.gatewayPid = gatewayPid
|
|
860
|
+
bc.gatewayStartedAt = new Date().toISOString()
|
|
861
|
+
fs.writeFileSync(bridgeMod.BRIDGE_CONFIG_FILE, JSON.stringify(bc, null, 2), 'utf8')
|
|
862
|
+
sseEmit(res, { type: 'progress', message: `Gateway PID: ${gatewayPid}` })
|
|
863
|
+
}
|
|
864
|
+
} catch {}
|
|
865
|
+
// 3. 启动 Bridge
|
|
866
|
+
if (openclawTool?.ensureBridgeRunning) {
|
|
867
|
+
try {
|
|
868
|
+
openclawTool.ensureBridgeRunning()
|
|
869
|
+
sseEmit(res, { type: 'progress', message: '✓ HolySheep Bridge 已启动' })
|
|
870
|
+
} catch {
|
|
871
|
+
sseEmit(res, { type: 'progress', message: '⚠️ Bridge 启动失败' })
|
|
872
|
+
}
|
|
682
873
|
}
|
|
683
874
|
}
|
|
684
875
|
} else {
|
|
685
|
-
sseEmit(res, { type: 'progress', message: `✗ ${
|
|
876
|
+
sseEmit(res, { type: 'progress', message: `✗ 回退失败,请手动运行: ${installCmd}` })
|
|
686
877
|
}
|
|
687
878
|
|
|
688
|
-
sseEmit(res, { type: 'done', success: ok, localVer, newVer })
|
|
879
|
+
sseEmit(res, { type: 'done', success: ok, localVer, newVer: newVer || targetVer })
|
|
689
880
|
res.end()
|
|
690
881
|
}
|
|
691
882
|
|
|
@@ -866,6 +1057,7 @@ async function handleRequest(req, res) {
|
|
|
866
1057
|
if (route === '/api/tool/configure' && req.method === 'POST') return await handleToolConfigure(req, res)
|
|
867
1058
|
if (route === '/api/tool/reset' && req.method === 'POST') return await handleToolReset(req, res)
|
|
868
1059
|
if (route === '/api/tool/upgrade' && req.method === 'POST') return await handleToolUpgrade(req, res)
|
|
1060
|
+
if (route === '/api/tool/rollback' && req.method === 'POST') return await handleToolRollback(req, res)
|
|
869
1061
|
if (route === '/api/tool/launch' && req.method === 'POST') return await handleToolLaunch(req, res)
|
|
870
1062
|
if (route === '/api/claude-proxy/start' && req.method === 'POST') return await handleClaudeProxyStart(req, res)
|
|
871
1063
|
if (route === '/api/claude-proxy/stop' && req.method === 'POST') return await handleClaudeProxyStop(req, res)
|