The language switcher displayed the *other* language's flag (clicking
the Chinese flag switched to Chinese). This is dissonant — a flag reads
as a state indicator first, so seeing the Chinese flag while the UI is
in English feels wrong. Users expect the flag to reflect the current
language, like every other status indicator.
Flips the flag and label ternaries so English shows UK + EN, Chinese
shows CN + 中文. Tooltip text ("Switch to Chinese" / "切换到英文") still
communicates the click action, which is where that belongs.
Add a theme engine for the web dashboard that mirrors the CLI skin
engine philosophy — pure data, no code changes needed for new themes.
Frontend:
- ThemeProvider context that loads active theme from backend on mount
and applies CSS variable overrides to document.documentElement
- ThemeSwitcher dropdown component in the header (next to language
switcher) with instant preview on click
- 6 built-in themes: Hermes Teal (default), Midnight, Ember, Mono,
Cyberpunk, Rosé — each defines all 21 color tokens + overlay settings
- Theme types, presets, and context in web/src/themes/
Backend:
- GET /api/dashboard/themes — returns available themes + active name
- PUT /api/dashboard/theme — persists selection to config.yaml
- User custom themes discoverable from ~/.hermes/dashboard-themes/*.yaml
- Theme list endpoint added to public API paths (no auth needed)
Config:
- dashboard.theme key in DEFAULT_CONFIG (default: 'default')
- Schema override for select dropdown in config page
- Category merged into 'display' tab in config UI
i18n: theme switcher strings added for en + zh.
Re-applies changes from #9471 that were overwritten by the i18n PR:
- URL-based routing via react-router-dom (NavLink, Routes, BrowserRouter)
- Replace emoji icons with lucide-react in ConfigPage and SkillsPage
- Sidebar layout for ConfigPage, SkillsPage, and LogsPage
- Custom dropdown Select component (SelectOption) in CronPage
- Remove all non-functional rounded borders across the UI
- Fixed header with proper content offset
Made-with: Cursor
Add a lightweight i18n system to the web dashboard with English (default) and
Chinese language support. A language switcher with flag icons is placed in the
header bar, allowing users to toggle between languages. The choice persists
to localStorage.
Implementation:
- src/i18n/ — types, translation files (en.ts, zh.ts), React context + hook
- LanguageSwitcher component shows the *other* language's flag as the toggle
- I18nProvider wraps the app in main.tsx
- All 8 pages + OAuth components updated to use t() translation calls
- Zero new dependencies — pure React context + localStorage
- Add GET /api/model/info endpoint that resolves model metadata using the
same 10-step context-length detection chain the agent uses. Returns
auto-detected context length, config override, effective value, and
model capabilities (tools, vision, reasoning, max output, model family).
- Surface model.context_length as model_context_length virtual field in
the config normalize/denormalize cycle. 0 = auto-detect (default),
positive value overrides. Writing 0 removes context_length from the
model dict on disk.
- Add ModelInfoCard component showing resolved context window (e.g. '1M
auto-detected' or '500K override — auto: 1M'), max output tokens, and
colored capability badges (Tools, Vision, Reasoning, model family).
- Inject ModelInfoCard between model field and context_length override in
ConfigPage General tab. Card re-fetches on model change and after save.
- Insert model_context_length right after model in CONFIG_SCHEMA ordering
so the three elements (model input → info card → override) are adjacent.
- Nav: icons only on mobile, icon+label on sm+
- Brand: abbreviated "H A" on mobile, full "Hermes Agent" on sm+
- Content: reduced padding on mobile (px-3 vs px-6)
- StatusPage: session cards stack vertically on mobile, truncate
overflow text, strip model namespace for brevity
- ConfigPage: sidebar becomes horizontal scrollable pills on mobile
instead of fixed left column, search hidden on mobile
- SessionsPage: title + search stack vertically on mobile, search
goes full-width
- Card component: add overflow-hidden to prevent content bleed
- Body/root: add overflow-x-hidden to prevent horizontal scroll
- Footer: reduced font sizes on mobile
All changes use Tailwind responsive breakpoints (sm: prefix).
No logic changes — purely layout/CSS adjustments.
Add OAuth provider management to the Hermes dashboard with full
lifecycle support for Anthropic (PKCE), Nous and OpenAI Codex
(device-code) flows.
## Backend (hermes_cli/web_server.py)
- 6 new API endpoints:
GET /api/providers/oauth — list providers with connection status
POST /api/providers/oauth/{id}/start — initiate PKCE or device-code
POST /api/providers/oauth/{id}/submit — exchange PKCE auth code
GET /api/providers/oauth/{id}/poll/{session} — poll device-code
DELETE /api/providers/oauth/{id} — disconnect provider
DELETE /api/providers/oauth/sessions/{id} — cancel pending session
- OAuth constants imported from anthropic_adapter (no duplication)
- Blocking I/O wrapped in run_in_executor for async safety
- In-memory session store with 15-minute TTL and automatic GC
- Auth token required on all mutating endpoints
## Frontend
- OAuthLoginModal — PKCE (paste auth code) and device-code (poll) flows
- OAuthProvidersCard — status, token preview, connect/disconnect actions
- Toast fix: createPortal to document.body for correct z-index
- App.tsx: skip animation key bump on initial mount (prevent double-mount)
- Integrated into the Env/Keys page
* feat: web UI dashboard for managing Hermes Agent (salvage of #8204/#7621)
Adds an embedded web UI dashboard accessible via `hermes web`:
- Status page: agent version, active sessions, gateway status, connected platforms
- Config editor: schema-driven form with tabbed categories, import/export, reset
- API Keys page: set, clear, and view redacted values with category grouping
- Sessions, Skills, Cron, Logs, and Analytics pages
Backend:
- hermes_cli/web_server.py: FastAPI server with REST endpoints
- hermes_cli/config.py: reload_env() utility for hot-reloading .env
- hermes_cli/main.py: `hermes web` subcommand (--port, --host, --no-open)
- cli.py / commands.py: /reload slash command for .env hot-reload
- pyproject.toml: [web] optional dependency extra (fastapi + uvicorn)
- Both update paths (git + zip) auto-build web frontend when npm available
Frontend:
- Vite + React + TypeScript + Tailwind v4 SPA in web/
- shadcn/ui-style components, Nous design language
- Auto-refresh status page, toast notifications, masked password inputs
Security:
- Path traversal guard (resolve().is_relative_to()) on SPA file serving
- CORS localhost-only via allow_origin_regex
- Generic error messages (no internal leak), SessionDB handles closed properly
Tests: 47 tests covering reload_env, redact_key, API endpoints, schema
generation, path traversal, category merging, internal key stripping,
and full config round-trip.
Original work by @austinpickett (PR #1813), salvaged by @kshitijk4poor
(PR #7621 → #8204), re-salvaged onto current main with stale-branch
regressions removed.
* fix(web): clean up status page cards, always rebuild on `hermes web`
- Remove config version migration alert banner from status page
- Remove config version card (internal noise, not surfaced in TUI)
- Reorder status cards: Agent → Gateway → Active Sessions (3-col grid)
- `hermes web` now always rebuilds from source before serving,
preventing stale web_dist when editing frontend files
* feat(web): full-text search across session messages
- Add GET /api/sessions/search endpoint backed by FTS5
- Auto-append prefix wildcards so partial words match (e.g. 'nimb' → 'nimby')
- Debounced search (300ms) with spinner in the search icon slot
- Search results show FTS5 snippets with highlighted match delimiters
- Expanding a search hit auto-scrolls to the first matching message
- Matching messages get a warning ring + 'match' badge
- Inline term highlighting within Markdown (text, bold, italic, headings, lists)
- Clear button (x) on search input for quick reset
---------
Co-authored-by: emozilla <emozilla@nousresearch.com>