@simonyea/holysheep-cli 1.7.54 → 1.7.56
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 +2 -2
- package/src/commands/webui.js +7 -14
- package/src/index.js +2 -2
- package/src/tools/codex.js +6 -8
- package/src/webui/index.html +420 -744
- package/src/webui/server.js +143 -8
package/src/webui/server.js
CHANGED
|
@@ -9,7 +9,7 @@ const fs = require('fs')
|
|
|
9
9
|
const path = require('path')
|
|
10
10
|
const { execSync, spawn } = require('child_process')
|
|
11
11
|
const { loadConfig, saveConfig, getApiKey, BASE_URL_ANTHROPIC, BASE_URL_OPENAI, BASE_URL_CLAUDE_RELAY, SHOP_URL, CONFIG_FILE } = require('../utils/config')
|
|
12
|
-
const { removeEnvFromShell } = require('../utils/shell')
|
|
12
|
+
const { removeEnvFromShell, writeEnvToShell, getShellRcFiles } = require('../utils/shell')
|
|
13
13
|
const { commandExists } = require('../utils/which')
|
|
14
14
|
const TOOLS = require('../tools')
|
|
15
15
|
const pkg = require('../../package.json')
|
|
@@ -441,6 +441,122 @@ async function handleToolInstall(req, res) {
|
|
|
441
441
|
res.end()
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
// ── Single-tool configure (SSE) ──────────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
async function handleToolConfigure(req, res) {
|
|
447
|
+
const body = await parseBody(req)
|
|
448
|
+
const { toolId } = body
|
|
449
|
+
const apiKey = getApiKey()
|
|
450
|
+
if (!apiKey) return json(res, { error: '未登录' }, 401)
|
|
451
|
+
|
|
452
|
+
const tool = TOOLS.find(t => t.id === toolId)
|
|
453
|
+
if (!tool) return json(res, { error: '未知工具' }, 400)
|
|
454
|
+
|
|
455
|
+
sseStart(res)
|
|
456
|
+
|
|
457
|
+
if (!tool.checkInstalled()) {
|
|
458
|
+
sseEmit(res, { type: 'error', message: `${tool.name} 未安装` })
|
|
459
|
+
sseEmit(res, { type: 'done', success: false })
|
|
460
|
+
return res.end()
|
|
461
|
+
}
|
|
462
|
+
|
|
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'
|
|
470
|
+
|
|
471
|
+
sseEmit(res, { type: 'progress', message: `正在配置 ${tool.name}...` })
|
|
472
|
+
|
|
473
|
+
// 劫持 console.log/warn,将 tool.configure() 内部的进度消息转发为 SSE 事件
|
|
474
|
+
const origLog = console.log
|
|
475
|
+
const origWarn = console.warn
|
|
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)
|
|
485
|
+
|
|
486
|
+
if (result.envVars && Object.keys(result.envVars).length > 0) {
|
|
487
|
+
writeEnvToShell(result.envVars)
|
|
488
|
+
sseEmit(res, { type: 'progress', message: '已写入环境变量到 shell 配置' })
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
try { removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']) } catch {}
|
|
492
|
+
|
|
493
|
+
if (result.manual) {
|
|
494
|
+
sseEmit(res, { type: 'result', status: 'manual', message: `${tool.name} 需要手动完成配置`, steps: result.steps })
|
|
495
|
+
} else if (result.warning) {
|
|
496
|
+
sseEmit(res, { type: 'result', status: 'warning', message: result.warning, file: result.file })
|
|
497
|
+
} else {
|
|
498
|
+
sseEmit(res, { type: 'result', status: 'ok', message: `${tool.name} 配置成功`, file: result.file, hot: result.hot })
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
sseEmit(res, { type: 'done', success: true, file: result.file, hot: result.hot, dashboardUrl: result.dashboardUrl || null })
|
|
502
|
+
} catch (e) {
|
|
503
|
+
sseEmit(res, { type: 'error', message: e.message })
|
|
504
|
+
sseEmit(res, { type: 'done', success: false })
|
|
505
|
+
} finally {
|
|
506
|
+
console.log = origLog
|
|
507
|
+
console.warn = origWarn
|
|
508
|
+
}
|
|
509
|
+
res.end()
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ── Single-tool reset ────────────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
async function handleToolReset(req, res) {
|
|
515
|
+
const body = await parseBody(req)
|
|
516
|
+
const { toolId } = body
|
|
517
|
+
const tool = TOOLS.find(t => t.id === toolId)
|
|
518
|
+
if (!tool) return json(res, { error: '未知工具' }, 400)
|
|
519
|
+
try {
|
|
520
|
+
tool.reset()
|
|
521
|
+
json(res, { success: true })
|
|
522
|
+
} catch (e) {
|
|
523
|
+
json(res, { success: false, error: e.message }, 500)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ── Environment variables ────────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
const HS_ENV_KEYS = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']
|
|
530
|
+
const MARKER_START = '# >>> holysheep-cli managed >>>'
|
|
531
|
+
|
|
532
|
+
function handleEnv(_req, res) {
|
|
533
|
+
const vars = {}
|
|
534
|
+
for (const k of HS_ENV_KEYS) {
|
|
535
|
+
const v = process.env[k]
|
|
536
|
+
vars[k] = v ? (k.includes('KEY') || k.includes('TOKEN') ? maskKey(v) : v) : null
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const rcFiles = []
|
|
540
|
+
try {
|
|
541
|
+
for (const f of getShellRcFiles()) {
|
|
542
|
+
let has = false
|
|
543
|
+
try { has = fs.readFileSync(f, 'utf8').includes(MARKER_START) } catch {}
|
|
544
|
+
rcFiles.push({ path: f.replace(require('os').homedir(), '~'), hasManagedBlock: has })
|
|
545
|
+
}
|
|
546
|
+
} catch {}
|
|
547
|
+
|
|
548
|
+
json(res, { vars, rcFiles })
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function handleEnvClean(_req, res) {
|
|
552
|
+
try {
|
|
553
|
+
const cleaned = removeEnvFromShell()
|
|
554
|
+
json(res, { success: true, cleaned })
|
|
555
|
+
} catch (e) {
|
|
556
|
+
json(res, { success: false, error: e.message }, 500)
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
444
560
|
// ── Models list ──────────────────────────────────────────────────────────────
|
|
445
561
|
|
|
446
562
|
function handleModels(_req, res) {
|
|
@@ -448,7 +564,9 @@ function handleModels(_req, res) {
|
|
|
448
564
|
{ id: 'gpt-5.4', label: 'GPT 5.4', desc: '通用编码' },
|
|
449
565
|
{ id: 'gpt-5.3-codex-spark', label: 'GPT 5.3 Codex Spark', desc: '编码' },
|
|
450
566
|
{ id: 'claude-sonnet-4-6', label: 'Sonnet 4.6', desc: '均衡推荐' },
|
|
567
|
+
{ id: 'claude-sonnet-4-6[1m]', label: 'Sonnet 4.6 (1M)', desc: '均衡推荐·100万上下文' },
|
|
451
568
|
{ id: 'claude-opus-4-6', label: 'Opus 4.6', desc: '强力旗舰' },
|
|
569
|
+
{ id: 'claude-opus-4-6[1m]', label: 'Opus 4.6 (1M)', desc: '强力旗舰·100万上下文' },
|
|
452
570
|
{ id: 'MiniMax-M2.7-highspeed', label: 'MiniMax M2.7', desc: '高速经济版' },
|
|
453
571
|
{ id: 'claude-haiku-4-5', label: 'Haiku 4.5', desc: '轻快便宜' },
|
|
454
572
|
])
|
|
@@ -478,15 +596,16 @@ async function handleRequest(req, res) {
|
|
|
478
596
|
if (route === '/api/status' && req.method === 'GET') return await handleStatus(req, res)
|
|
479
597
|
if (route === '/api/login' && req.method === 'POST') return await handleLogin(req, res)
|
|
480
598
|
if (route === '/api/logout' && req.method === 'POST') return await handleLogout(req, res)
|
|
481
|
-
if (route === '/api/whoami' && req.method === 'GET') return await handleWhoami(req, res)
|
|
482
599
|
if (route === '/api/balance' && req.method === 'GET') return await handleBalance(req, res)
|
|
483
600
|
if (route === '/api/doctor' && req.method === 'GET') return await handleDoctor(req, res)
|
|
484
601
|
if (route === '/api/tools' && req.method === 'GET') return await handleTools(req, res)
|
|
485
602
|
if (route === '/api/models' && req.method === 'GET') return await handleModels(req, res)
|
|
486
|
-
if (route === '/api/setup' && req.method === 'POST') return await handleSetup(req, res)
|
|
487
|
-
if (route === '/api/reset' && req.method === 'POST') return await handleReset(req, res)
|
|
488
603
|
if (route === '/api/upgrade' && req.method === 'POST') return await handleUpgrade(req, res)
|
|
489
|
-
if (route === '/api/tool/install'
|
|
604
|
+
if (route === '/api/tool/install' && req.method === 'POST') return await handleToolInstall(req, res)
|
|
605
|
+
if (route === '/api/tool/configure' && req.method === 'POST') return await handleToolConfigure(req, res)
|
|
606
|
+
if (route === '/api/tool/reset' && req.method === 'POST') return await handleToolReset(req, res)
|
|
607
|
+
if (route === '/api/env' && req.method === 'GET') return handleEnv(req, res)
|
|
608
|
+
if (route === '/api/env/clean' && req.method === 'POST') return handleEnvClean(req, res)
|
|
490
609
|
|
|
491
610
|
res.writeHead(404)
|
|
492
611
|
res.end('Not Found')
|
|
@@ -498,9 +617,25 @@ async function handleRequest(req, res) {
|
|
|
498
617
|
}
|
|
499
618
|
|
|
500
619
|
function startServer(port) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
620
|
+
return new Promise((resolve, reject) => {
|
|
621
|
+
const server = http.createServer(handleRequest)
|
|
622
|
+
server.on('error', (err) => {
|
|
623
|
+
if (err.code === 'EADDRINUSE') {
|
|
624
|
+
// Try to kill stale process and retry once
|
|
625
|
+
try {
|
|
626
|
+
execSync(`lsof -ti:${port} | xargs kill -9`, { stdio: 'ignore' })
|
|
627
|
+
} catch {}
|
|
628
|
+
setTimeout(() => {
|
|
629
|
+
const retry = http.createServer(handleRequest)
|
|
630
|
+
retry.on('error', (err2) => reject(err2))
|
|
631
|
+
retry.listen(port, '127.0.0.1', () => resolve(retry))
|
|
632
|
+
}, 500)
|
|
633
|
+
} else {
|
|
634
|
+
reject(err)
|
|
635
|
+
}
|
|
636
|
+
})
|
|
637
|
+
server.listen(port, '127.0.0.1', () => resolve(server))
|
|
638
|
+
})
|
|
504
639
|
}
|
|
505
640
|
|
|
506
641
|
module.exports = { startServer }
|