diff --git a/OFFICIAL-API-TODO.md b/OFFICIAL-API-TODO.md deleted file mode 100644 index d2a8b7f..0000000 --- a/OFFICIAL-API-TODO.md +++ /dev/null @@ -1,59 +0,0 @@ -# Official Hermes Dashboard API Integration - -Branch: `feat/official-api` - -## Overview -Integrate with the official Hermes REST API (`hermes dashboard` on `127.0.0.1:9119`) to replace or supplement CLI-based data fetching. - -Reference: https://hermes-agent.nousresearch.com/docs/user-guide/features/web-dashboard - -## Priority - -### High -1. **Config management page** — `GET/PUT /api/config`, `GET /api/config/schema` - - New settings page with form-based config editor - - All config fields auto-discovered from schema - - Save, reset to defaults, export/import -2. **API Key management** — `GET/PUT/DELETE /api/env` - - View, set, delete API keys - - Grouped by category (LLM, Tools, Messaging) - - Redacted value display -3. **Session search** — `GET /api/sessions/search?q=...` - - Full-text search across all message content - - Highlighted snippets - -### Medium -4. **Analytics** — `GET /api/analytics/usage?days=30` - - Use official API data instead of computing from session list - - More accurate cost/cache stats -5. **Cron job management** — Full CRUD - - Create, pause/resume, trigger, delete scheduled jobs - - Job list with status, schedule, run history -6. **Skills toggle** — `PUT /api/skills/toggle` - - Enable/disable skills directly from UI -7. **Status enhancement** — `GET /api/status` - - Platform connection states - - Active session count - -### Low -8. **Toolsets** — `GET /api/tools/toolsets` - - Display available toolsets with status - -## Architecture -- BFF (Koa) proxies requests to official API at `127.0.0.1:9119` -- Fallback to CLI when official API is not available -- User can configure official dashboard address in settings -- CORS: official API restricts to localhost, our BFF handles this - -## API Endpoints to Integrate -- `GET /api/status` -- `GET /api/sessions`, `GET /api/sessions/{id}`, `GET /api/sessions/{id}/messages` -- `GET /api/sessions/search?q=...` -- `DELETE /api/sessions/{id}` -- `GET /api/config`, `GET /api/config/defaults`, `GET /api/config/schema`, `PUT /api/config` -- `GET /api/env`, `PUT /api/env`, `DELETE /api/env` -- `GET /api/logs` -- `GET /api/analytics/usage?days=N` -- `GET /api/cron/jobs`, `POST /api/cron/jobs`, `POST/DELETE /api/cron/jobs/{id}/*` -- `GET /api/skills`, `PUT /api/skills/toggle` -- `GET /api/tools/toolsets` diff --git a/bin/hermes-web-ui.mjs b/bin/hermes-web-ui.mjs index 0c5afd2..4036062 100755 --- a/bin/hermes-web-ui.mjs +++ b/bin/hermes-web-ui.mjs @@ -3,6 +3,7 @@ import { spawn, execSync } from 'child_process' import { resolve, dirname, join } from 'path' import { fileURLToPath } from 'url' import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync } from 'fs' +import { randomBytes } from 'crypto' import { homedir } from 'os' const __dirname = dirname(fileURLToPath(import.meta.url)) @@ -23,6 +24,20 @@ function getToken() { } } +function ensureToken() { + // If AUTH_DISABLED or AUTH_TOKEN is set, let server handle it + if (process.env.AUTH_DISABLED === '1' || process.env.AUTH_DISABLED === 'true') return null + if (process.env.AUTH_TOKEN) return process.env.AUTH_TOKEN + + let token = getToken() + if (!token) { + mkdirSync(dirname(TOKEN_FILE), { recursive: true }) + token = randomBytes(32).toString('hex') + writeFileSync(TOKEN_FILE, token + '\n', { mode: 0o600 }) + } + return token +} + 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]) @@ -90,11 +105,13 @@ function startDaemon(port) { mkdirSync(PID_DIR, { recursive: true }) + const token = ensureToken() + const logStream = openSync(LOG_FILE, 'a') const child = spawn(process.execPath, [serverEntry], { detached: true, stdio: ['ignore', logStream, logStream], - env: { ...process.env, PORT: String(port) }, + env: { ...process.env, PORT: String(port), AUTH_TOKEN: token }, windowsHide: true, }) @@ -110,14 +127,11 @@ function startDaemon(port) { setTimeout(() => { if (isRunning(child.pid)) { console.log(` ✓ hermes-web-ui started (PID: ${child.pid}, port: ${port})`) - console.log(` http://localhost:${port}`) + const url = token + ? `http://localhost:${port}/#/?token=${token}` + : `http://localhost:${port}` + console.log(` ${url}`) console.log(` Log: ${LOG_FILE}`) - const token = getToken() - if (token) { - console.log(` Token: ${token}`) - } - // Open browser - const url = `http://localhost:${port}` const isWin = process.platform === 'win32' const cmd = isWin ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}` try { execSync(cmd, { stdio: 'ignore' }) } catch {} diff --git a/package.json b/package.json index b2ff00d..2abfb67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hermes-web-ui", - "version": "0.2.2", + "version": "0.2.3", "description": "Hermes Agent Web UI - Chat and Job Management Dashboard", "repository": { "type": "git", diff --git a/server/src/index.ts b/server/src/index.ts index 1c587d4..29cf864 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -196,7 +196,7 @@ async function ensureApiServerConfig() { let changed = false for (const [k, v] of Object.entries(defaults)) { - if (api[k] == null) { + if (api[k] != null && api[k] !== v) { api[k] = v changed = true }