#!/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')) }