Wires the new /api/pty WebSocket into the dashboard as a top-level
Chat tab. Clicking Chat (or the ▶ play icon on any session row)
spawns a PTY running hermes --tui and renders its ANSI output with
xterm.js in the browser.
Frontend
--------
web/src/pages/ChatPage.tsx
* @xterm/xterm v6 + @xterm/addon-webgl renderer (pixel-perfect cell
grid — DOM and canvas renderers each have layout artifacts that
break box-drawing glyph connectivity in a browser)
* @xterm/addon-fit for container-driven resize
* @xterm/addon-unicode11 for modern wide-char widths (matches Ink's
string-width computation so kaomoji / CJK / emoji land on the
same cell boundaries as the host expects)
* @xterm/addon-web-links for URL auto-linking
* Rounded dark-teal "terminal window" container with 12px internal
padding + drop shadow for visual identity within the dashboard
* Clipboard wiring:
- Ctrl/Cmd+Shift+C copies xterm selection to system clipboard
- Ctrl/Cmd+Shift+V pastes system clipboard into the PTY
- OSC 52 handler writes terminal-emitted clipboard sequences
(how Ink's own Ctrl+C and /copy command deliver copy events);
decodes via TextDecoder so multi-byte UTF-8 codepoints
(U+2265, emoji, CJK) round-trip correctly
- Plain Ctrl+C still passes through as SIGINT to interrupt a
running response
* Floating "copy last response" button in the bottom-right corner.
Triggers Ink's /copy slash by sending bytes in two frames with a
100ms gap — Ink's tokenizer coalesces rapid adjacent bytes into
a paste event (bypasses the slash dispatcher), so we deliberately
split '/copy' and '\r' into separate packets to land them as
individual keypresses.
web/src/App.tsx
Chat nav entry (Terminal icon) at position 2 and <Route path="/chat">.
web/src/pages/SessionsPage.tsx
Play-icon button per session row that navigates to /chat?resume=<id>;
the PTY bridge forwards the resume param to hermes --tui --resume.
web/src/i18n/{en,zh,types}.ts
nav.chat label + sessions.resumeInChat action label.
web/vite.config.ts
/api proxy gains ws: true so WebSocket upgrades forward to :9119
when running Vite dev mode against a separate hermes dashboard
backend.
web/src/index.css + web/public/fonts-terminal/
Bundles JetBrains Mono (Regular/Bold/Italic, Apache-2.0, ~280 KB
total) as a local webfont. Fonts live outside web/public/fonts/
because the sync-assets prebuild step wipes that directory from
@nous-research/ui every build.
Package deps
------------
Net new: @xterm/xterm ^6.0.0, @xterm/addon-fit ^0.11.0,
@xterm/addon-webgl ^0.19.0, @xterm/addon-unicode11 ^0.9.0,
@xterm/addon-web-links ^0.12.0.
Bundle impact: +420 KB minified / +105 KB gzipped. Acceptable for a
feature that replaces what would otherwise be a rewrite of the entire
TUI surface in React.
Backend contract preserved
---------------------------
Every TUI affordance (slash popover, model picker, tool cards,
markdown streaming, clarify/sudo/approval prompts, skin engine, wide
chars, mouse tracking) lands in the browser unchanged because we are
running the real Ink binary. Adding a feature to the TUI surfaces in
the dashboard immediately. Do NOT add parallel React chat surfaces.
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
* 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>