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 { resolve, dirname, join } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync } from 'fs'
|
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync } from 'fs'
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
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() {
|
function getPort() {
|
||||||
if (process.argv[3] && !isNaN(process.argv[3])) return parseInt(process.argv[3])
|
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])
|
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 })
|
mkdirSync(PID_DIR, { recursive: true })
|
||||||
|
|
||||||
|
const token = ensureToken()
|
||||||
|
|
||||||
const logStream = openSync(LOG_FILE, 'a')
|
const logStream = openSync(LOG_FILE, 'a')
|
||||||
const child = spawn(process.execPath, [serverEntry], {
|
const child = spawn(process.execPath, [serverEntry], {
|
||||||
detached: true,
|
detached: true,
|
||||||
stdio: ['ignore', logStream, logStream],
|
stdio: ['ignore', logStream, logStream],
|
||||||
env: { ...process.env, PORT: String(port) },
|
env: { ...process.env, PORT: String(port), AUTH_TOKEN: token },
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -110,14 +127,11 @@ function startDaemon(port) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isRunning(child.pid)) {
|
if (isRunning(child.pid)) {
|
||||||
console.log(` ✓ hermes-web-ui started (PID: ${child.pid}, port: ${port})`)
|
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}`)
|
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 isWin = process.platform === 'win32'
|
||||||
const cmd = isWin ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`
|
const cmd = isWin ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`
|
||||||
try { execSync(cmd, { stdio: 'ignore' }) } catch {}
|
try { execSync(cmd, { stdio: 'ignore' }) } catch {}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hermes-web-ui",
|
"name": "hermes-web-ui",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"description": "Hermes Agent Web UI - Chat and Job Management Dashboard",
|
"description": "Hermes Agent Web UI - Chat and Job Management Dashboard",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
+1
-1
@@ -196,7 +196,7 @@ async function ensureApiServerConfig() {
|
|||||||
let changed = false
|
let changed = false
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(defaults)) {
|
for (const [k, v] of Object.entries(defaults)) {
|
||||||
if (api[k] == null) {
|
if (api[k] != null && api[k] !== v) {
|
||||||
api[k] = v
|
api[k] = v
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user