@simonyea/holysheep-cli 1.7.54 → 1.7.55
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 +406 -743
- package/src/webui/server.js +123 -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,104 @@ 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 = ['gpt-5.4', 'gpt-5.3-codex-spark', 'claude-sonnet-4-6', 'claude-opus-4-6', 'MiniMax-M2.7-highspeed', 'claude-haiku-4-5']
|
|
464
|
+
const primaryModel = 'claude-sonnet-4-6'
|
|
465
|
+
|
|
466
|
+
sseEmit(res, { type: 'progress', message: `正在配置 ${tool.name}...` })
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
const result = tool.configure(apiKey, BASE_URL_ANTHROPIC, BASE_URL_OPENAI, primaryModel, allModelIds)
|
|
470
|
+
|
|
471
|
+
if (result.envVars && Object.keys(result.envVars).length > 0) {
|
|
472
|
+
writeEnvToShell(result.envVars)
|
|
473
|
+
sseEmit(res, { type: 'progress', message: '已写入环境变量到 shell 配置' })
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
try { removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']) } catch {}
|
|
477
|
+
|
|
478
|
+
if (result.manual) {
|
|
479
|
+
sseEmit(res, { type: 'result', status: 'manual', message: `${tool.name} 需要手动完成配置`, steps: result.steps })
|
|
480
|
+
} else if (result.warning) {
|
|
481
|
+
sseEmit(res, { type: 'result', status: 'warning', message: result.warning, file: result.file })
|
|
482
|
+
} else {
|
|
483
|
+
sseEmit(res, { type: 'result', status: 'ok', message: `${tool.name} 配置成功`, file: result.file, hot: result.hot })
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
sseEmit(res, { type: 'done', success: true, file: result.file, hot: result.hot })
|
|
487
|
+
} catch (e) {
|
|
488
|
+
sseEmit(res, { type: 'error', message: e.message })
|
|
489
|
+
sseEmit(res, { type: 'done', success: false })
|
|
490
|
+
}
|
|
491
|
+
res.end()
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ── Single-tool reset ────────────────────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
async function handleToolReset(req, res) {
|
|
497
|
+
const body = await parseBody(req)
|
|
498
|
+
const { toolId } = body
|
|
499
|
+
const tool = TOOLS.find(t => t.id === toolId)
|
|
500
|
+
if (!tool) return json(res, { error: '未知工具' }, 400)
|
|
501
|
+
try {
|
|
502
|
+
tool.reset()
|
|
503
|
+
json(res, { success: true })
|
|
504
|
+
} catch (e) {
|
|
505
|
+
json(res, { success: false, error: e.message }, 500)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ── Environment variables ────────────────────────────────────────────────────
|
|
510
|
+
|
|
511
|
+
const HS_ENV_KEYS = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']
|
|
512
|
+
const MARKER_START = '# >>> holysheep-cli managed >>>'
|
|
513
|
+
|
|
514
|
+
function handleEnv(_req, res) {
|
|
515
|
+
const vars = {}
|
|
516
|
+
for (const k of HS_ENV_KEYS) {
|
|
517
|
+
const v = process.env[k]
|
|
518
|
+
vars[k] = v ? (k.includes('KEY') || k.includes('TOKEN') ? maskKey(v) : v) : null
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const rcFiles = []
|
|
522
|
+
try {
|
|
523
|
+
for (const f of getShellRcFiles()) {
|
|
524
|
+
let has = false
|
|
525
|
+
try { has = fs.readFileSync(f, 'utf8').includes(MARKER_START) } catch {}
|
|
526
|
+
rcFiles.push({ path: f.replace(require('os').homedir(), '~'), hasManagedBlock: has })
|
|
527
|
+
}
|
|
528
|
+
} catch {}
|
|
529
|
+
|
|
530
|
+
json(res, { vars, rcFiles })
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function handleEnvClean(_req, res) {
|
|
534
|
+
try {
|
|
535
|
+
const cleaned = removeEnvFromShell()
|
|
536
|
+
json(res, { success: true, cleaned })
|
|
537
|
+
} catch (e) {
|
|
538
|
+
json(res, { success: false, error: e.message }, 500)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
444
542
|
// ── Models list ──────────────────────────────────────────────────────────────
|
|
445
543
|
|
|
446
544
|
function handleModels(_req, res) {
|
|
@@ -478,15 +576,16 @@ async function handleRequest(req, res) {
|
|
|
478
576
|
if (route === '/api/status' && req.method === 'GET') return await handleStatus(req, res)
|
|
479
577
|
if (route === '/api/login' && req.method === 'POST') return await handleLogin(req, res)
|
|
480
578
|
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
579
|
if (route === '/api/balance' && req.method === 'GET') return await handleBalance(req, res)
|
|
483
580
|
if (route === '/api/doctor' && req.method === 'GET') return await handleDoctor(req, res)
|
|
484
581
|
if (route === '/api/tools' && req.method === 'GET') return await handleTools(req, res)
|
|
485
582
|
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
583
|
if (route === '/api/upgrade' && req.method === 'POST') return await handleUpgrade(req, res)
|
|
489
|
-
if (route === '/api/tool/install'
|
|
584
|
+
if (route === '/api/tool/install' && req.method === 'POST') return await handleToolInstall(req, res)
|
|
585
|
+
if (route === '/api/tool/configure' && req.method === 'POST') return await handleToolConfigure(req, res)
|
|
586
|
+
if (route === '/api/tool/reset' && req.method === 'POST') return await handleToolReset(req, res)
|
|
587
|
+
if (route === '/api/env' && req.method === 'GET') return handleEnv(req, res)
|
|
588
|
+
if (route === '/api/env/clean' && req.method === 'POST') return handleEnvClean(req, res)
|
|
490
589
|
|
|
491
590
|
res.writeHead(404)
|
|
492
591
|
res.end('Not Found')
|
|
@@ -498,9 +597,25 @@ async function handleRequest(req, res) {
|
|
|
498
597
|
}
|
|
499
598
|
|
|
500
599
|
function startServer(port) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
600
|
+
return new Promise((resolve, reject) => {
|
|
601
|
+
const server = http.createServer(handleRequest)
|
|
602
|
+
server.on('error', (err) => {
|
|
603
|
+
if (err.code === 'EADDRINUSE') {
|
|
604
|
+
// Try to kill stale process and retry once
|
|
605
|
+
try {
|
|
606
|
+
execSync(`lsof -ti:${port} | xargs kill -9`, { stdio: 'ignore' })
|
|
607
|
+
} catch {}
|
|
608
|
+
setTimeout(() => {
|
|
609
|
+
const retry = http.createServer(handleRequest)
|
|
610
|
+
retry.on('error', (err2) => reject(err2))
|
|
611
|
+
retry.listen(port, '127.0.0.1', () => resolve(retry))
|
|
612
|
+
}, 500)
|
|
613
|
+
} else {
|
|
614
|
+
reject(err)
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
server.listen(port, '127.0.0.1', () => resolve(server))
|
|
618
|
+
})
|
|
504
619
|
}
|
|
505
620
|
|
|
506
621
|
module.exports = { startServer }
|