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>
This commit is contained in:
ekko
2026-04-11 21:33:04 +08:00
parent a2f8f6aec5
commit ee9f56dfbd
25 changed files with 1613 additions and 713 deletions
+36
View File
@@ -0,0 +1,36 @@
import { request } from './client'
export interface LogFileInfo {
name: string
size: string
modified: string
}
export interface LogEntry {
timestamp: string
level: string
logger: string
message: string
raw: string
}
export async function fetchLogFiles(): Promise<LogFileInfo[]> {
const res = await request<{ files: LogFileInfo[] }>('/api/logs')
return res.files
}
export async function fetchLogs(name: string, params?: {
lines?: number
level?: string
session?: string
since?: string
}): Promise<LogEntry[]> {
const query = new URLSearchParams()
if (params?.lines) query.set('lines', String(params.lines))
if (params?.level) query.set('level', params.level)
if (params?.session) query.set('session', params.session)
if (params?.since) query.set('since', params.since)
const qs = query.toString()
const res = await request<{ entries: (LogEntry | null)[] }>(`/api/logs/${name}${qs ? `?${qs}` : ''}`)
return res.entries.filter((e): e is LogEntry => e !== null)
}
+61
View File
@@ -0,0 +1,61 @@
import { request } from './client'
export interface SessionSummary {
id: string
source: string
model: string
title: string | null
started_at: number
ended_at: number | null
message_count: number
tool_call_count: number
input_tokens: number
output_tokens: number
billing_provider: string | null
estimated_cost_usd: number
}
export interface SessionDetail extends SessionSummary {
messages: HermesMessage[]
}
export interface HermesMessage {
id: number
session_id: string
role: 'user' | 'assistant' | 'system' | 'tool'
content: string
tool_call_id: string | null
tool_calls: any[] | null
tool_name: string | null
timestamp: number
token_count: number | null
finish_reason: string | null
reasoning: string | null
}
export async function fetchSessions(source?: string, limit?: number): Promise<SessionSummary[]> {
const params = new URLSearchParams()
if (source) params.set('source', source)
if (limit) params.set('limit', String(limit))
const query = params.toString()
const res = await request<{ sessions: SessionSummary[] }>(`/api/sessions${query ? `?${query}` : ''}`)
return res.sessions
}
export async function fetchSession(id: string): Promise<SessionDetail | null> {
try {
const res = await request<{ session: SessionDetail }>(`/api/sessions/${id}`)
return res.session
} catch {
return null
}
}
export async function deleteSession(id: string): Promise<boolean> {
try {
await request(`/api/sessions/${id}`, { method: 'DELETE' })
return true
} catch {
return false
}
}