fix: generate token on start, include token in URL, reset api_server config
- Pre-generate auth token before server start and pass via AUTH_TOKEN env var - Append token to startup URL for auto-login - Reset api_server config values to defaults on startup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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`
|
||||
+22
-8
@@ -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 {}
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user