Files
Hermes-ui/bin/hermes-web-ui.mjs
T
ekko ee9f56dfbd feat: add Koa2 BFF server, CLI management, sessions CLI integration, and logs page
- Add Koa2 BFF layer for API proxy, file upload, session management
- Auto-check and enable api_server in ~/.hermes/config.yaml on startup
- Integrate sessions with Hermes CLI (list, get, delete)
- Add Logs page with level filtering, log file selection, and search
- Add CLI commands: start/stop/restart/status for daemon management
- Unify package.json for frontend and server dependencies
- Default port changed to 8648

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:33:04 +08:00

140 lines
3.6 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { spawn } from 'child_process'
import { resolve, dirname, join } from 'path'
import { fileURLToPath } from 'url'
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync } from 'fs'
const __dirname = dirname(fileURLToPath(import.meta.url))
const serverEntry = resolve(__dirname, '..', 'dist', 'server', 'index.js')
const PID_DIR = resolve(__dirname, '..', '.hermes-web-ui')
const PID_FILE = join(PID_DIR, 'server.pid')
const LOG_FILE = join(PID_DIR, 'server.log')
const DEFAULT_PORT = 8648
function getPort() {
if (process.argv[3] && !isNaN(process.argv[3])) return parseInt(process.argv[3])
if (process.argv.includes('--port')) return parseInt(process.argv[process.argv.indexOf('--port') + 1])
return DEFAULT_PORT
}
function getPid() {
try {
return parseInt(readFileSync(PID_FILE, 'utf-8').trim())
} catch {
return null
}
}
function isRunning(pid) {
try {
process.kill(pid, 0)
return true
} catch {
return false
}
}
function writePid(pid) {
writeFileSync(PID_FILE, String(pid))
}
function removePid() {
try { unlinkSync(PID_FILE) } catch {}
}
function startDaemon(port) {
const existing = getPid()
if (existing && isRunning(existing)) {
console.log(` ✗ hermes-web-ui is already running (PID: ${existing})`)
console.log(` Use "hermes-web-ui stop" to stop it first`)
process.exit(1)
}
removePid() // stale pid file
mkdirSync(PID_DIR, { recursive: true })
const logStream = openSync(LOG_FILE, 'a')
const child = spawn(process.execPath, [serverEntry], {
detached: true,
stdio: ['ignore', logStream, logStream],
env: { ...process.env, PORT: String(port) },
})
child.unref()
writePid(child.pid)
// Wait a moment and check if the process is still alive
setTimeout(() => {
if (isRunning(child.pid)) {
console.log(` ✓ hermes-web-ui started (PID: ${child.pid}, port: ${port})`)
console.log(` http://localhost:${port}`)
console.log(` Log: ${LOG_FILE}`)
} else {
console.log(' ✗ Failed to start hermes-web-ui')
console.log(` Check log: ${LOG_FILE}`)
removePid()
process.exit(1)
}
}, 500)
}
function stopDaemon() {
const pid = getPid()
if (!pid) {
console.log(' ✗ hermes-web-ui is not running')
process.exit(1)
}
if (!isRunning(pid)) {
console.log(` ✗ Process ${pid} is not alive (stale PID file)`)
removePid()
process.exit(1)
}
try {
process.kill(pid, 'SIGTERM')
removePid()
console.log(` ✓ hermes-web-ui stopped (PID: ${pid})`)
} catch (err) {
console.log(` ✗ Failed to stop: ${err.message}`)
process.exit(1)
}
}
function showStatus() {
const pid = getPid()
if (pid && isRunning(pid)) {
console.log(` ✓ hermes-web-ui is running (PID: ${pid})`)
} else {
if (pid) removePid() // clean stale
console.log(' ✗ hermes-web-ui is not running')
}
}
const command = process.argv[2] || 'start'
switch (command) {
case 'start':
startDaemon(getPort())
break
case 'stop':
stopDaemon()
break
case 'restart':
stopDaemon()
setTimeout(() => startDaemon(getPort()), 500)
break
case 'status':
showStatus()
break
default:
// Direct run (foreground): hermes-web-ui [port]
const port = !isNaN(command) ? parseInt(command) : DEFAULT_PORT
const child = spawn(process.execPath, [serverEntry], {
stdio: 'inherit',
env: { ...process.env, PORT: String(port) },
})
child.on('exit', (code) => process.exit(code ?? 1))
process.on('SIGTERM', () => child.kill('SIGTERM'))
process.on('SIGINT', () => child.kill('SIGINT'))
}