Bring 313 commits of upstream main into the bb/gui dashboard
refactor branch. Eight conflicts resolved by hand, the rest
auto-merged. One missing class (_StreamErrorEvent) restored from
main after the auto-merger dropped it.
Conflict resolutions:
apps/dashboard/README.md take HEAD: main's text described
the pre-rename web/ layout that
bb/gui refactored away.
apps/dashboard/package.json combine: keep HEAD's @hermes/shared
workspace dep, take main's
@nous-research/ui 0.16.0 bump.
apps/dashboard/package-lock.json regenerate via
npm install --package-lock-only.
Root lock also regenerated; only
dashboard and apps/desktop entries
moved (apps/desktop version 0.0.1 →
0.0.2 to match bb/gui's
package.json bump).
apps/dashboard/src/pages/ take main (4 hunks): text-xs
EnvPage.tsx replaces text-[0.65rem] per the
typography rule HEAD's own README
documents.
hermes_cli/gateway.py take main (2 hunks): Discord
setup metadata moved to plugin
(architectural migration); s6
service-manager dispatch helpers
additive.
hermes_cli/main.py combine (2 hunks): take main's
Termux-aware
_sync_bundled_skills_for_startup;
combine gui + portal subcommands
in the known-subcommand list.
hermes_cli/web_server.py mixed (10 hunks):
- take main on _PUBLIC_API_PATHS
(bb/gui's own test asserts the
rescan endpoint must require auth)
- combine WS helpers: keep HEAD's
_ws_client_label + main's
Host/Origin guard + composing
_ws_request_is_allowed
- take HEAD's debug-level broadcast
drop log (matches the comment
"subscriber went away mid-send")
- take main's _safe_plugin_api_relpath
GHSA-5qr3-c538-wm9j fix and the
paired discovery-time validation
- take main's {name:path} route
converter for plugin visibility
tui_gateway/server.py take main: PR #31379's verbose-
args gating supersedes HEAD's
unconditional args dump on
tool.start.
Post-merge restoration:
run_agent.py restored class _StreamErrorEvent
(40 lines, from origin/main:288).
Auto-merge silently dropped it,
breaking imports in
agent/codex_runtime.py and three
test files
(test_codex_xai_oauth_recovery.py,
test_streaming.py). Restored
verbatim from main.
Sanity checks:
* git diff --check / --cached --check: clean (no stray markers)
* ast.parse + import on all touched .py files: clean
* targeted pytest on resolved files: 756 passed, 1 pre-existing
Windows-curses failure unrelated to the merge
* full pytest_parallel run: 105 files / 391 failures vs baseline
98 files / 346. Differential vs origin/bb/gui shows all 11
"new" failure files come from main's added tests/code and
reproduce identically against origin/main on the same Windows
host (pure Windows path-separator / perms / git-bash issues
in upstream tests, not merge regressions). 4 baseline
failures fixed: 3 in test_codex_xai_oauth_recovery (the
_StreamErrorEvent restoration), 1 each in test_pairing,
test_runner_startup_failures, test_stream_consumer.
* sentinel-token sweep on main's eight largest commits:
every audited symbol present in the merged tree at expected
counts (TTSProvider 61, NtfyAdapter 29, S6ServiceManager 70,
install_bws 12, security_audit 16, register_image_gen_provider
23, list_profile_gateways 22, DISCORD_FREE_RESPONSE_CHANNELS
48, …).
* byte-diff sweep: 30/30 sampled main-only-modified files
byte-identical to origin/main; the four bb/gui-only files
that drifted (i18n/types.ts, i18n/ru.ts, ThemeSwitcher.tsx,
ToolCall.tsx) correctly absorbed main's web/ → apps/dashboard/
edits through git's rename detection (main's added lines all
present, removed lines all absent).
6.2 KiB
Hermes Agent — Web UI
Browser-based dashboard for managing Hermes Agent configuration, API keys, and monitoring active sessions.
Stack
- Vite + React 19 + TypeScript
- Tailwind CSS v4 with custom dark theme
- shadcn/ui-style components (hand-rolled, no CLI dependency)
Development
Install workspace dependencies from the repo root first:
npm install
Start the backend API server from the repo root:
hermes dashboard --tui --no-open
--tui exposes the in-browser Chat tab through /api/pty. Omit it if you only need the config/session dashboard.
In another terminal, start the Vite dev server:
cd apps/dashboard
npm run dev
The Vite dev server proxies /api, /api/pty, and /dashboard-plugins to http://127.0.0.1:9119 (the FastAPI backend). It also fetches the backend's index.html on each dev page load so the ephemeral session token stays in sync.
If the hermes entry point is not installed, use:
python -m hermes_cli.main dashboard --tui --no-open
Build
npm run build
This outputs to ../../hermes_cli/web_dist/, which the FastAPI server serves as a static SPA. The built assets are included in the Python package via pyproject.toml package-data.
Structure
src/
├── components/ui/ # Reusable UI primitives (Card, Badge, Button, Input, etc.)
├── lib/
│ ├── api.ts # API client — typed fetch wrappers for all backend endpoints
│ └── utils.ts # cn() helper for Tailwind class merging
├── pages/
│ ├── StatusPage # Agent status, active/recent sessions
│ ├── ConfigPage # Dynamic config editor (reads schema from backend)
│ └── EnvPage # API key management with save/clear
├── App.tsx # Main layout and navigation
├── main.tsx # React entry point
└── index.css # Tailwind imports and theme variables
Typography & contrast rules
Read before adding or editing UI styles. These rules keep the dashboard legible across all built-in themes and stop drift back into the patterns the design system was just refactored out of.
Text size floor
- Minimum body size:
text-xs(12px / 0.75rem). Do not use arbitrarytext-[0.6rem],text-[0.65rem],text-[9px],text-[10px], ortext-[11px]on copy, hints, labels, counts, or badges. Use the standard scale:text-xs,text-sm,text-base. - Smaller sizes are only acceptable on decorative overlays (chart stripes, empty-state icons) — never on text the user is meant to read.
Opacity floor on text
- Never apply opacity below 0.7 to text. No
opacity-30,opacity-50,opacity-60on<span>s,<p>s, labels, etc. - Do not stack opacity tokens. Patterns like
text-muted-foreground/60,text-midground/70,text-foreground/50create unpredictable WCAG failures because the parent token already has alpha. - Use the semantic text tokens from
@nous-research/ui'sglobals.css:text-text-primary— default body text.text-text-secondary— subtitles, meta, inactive nav.text-text-tertiary— small chrome labels, counts, footnotes.text-text-disabled— disabled states.text-text-on-accent— text on filled accent surfaces.
Brand uppercase via text-display, not raw uppercase
- The dashboard preserves the Nous brand uppercase aesthetic, but it is opt-in per element, not global.
- Apply uppercase via the DS utility
text-displayon brand chrome only — page titles, nav section headings, badges, brand wordmark. DS components (Button,Badge,Tabs,Segmented, etc.) already self-applytext-display. - Do not introduce new
uppercase(the literal Tailwind class) inhermes-agent/web/src. Prefertext-displayfor new brand chrome. Legacyuppercasecall sites (e.g.components/ui/label.tsx,card.tsx) remain until migrated. - The app shell no longer forces uppercase globally, so blanket
normal-caseopt-outs are unnecessary. Usenormal-caseonly where a DS component appliestext-displaybut the label should stay sentence case — e.g. dynamic user content (model slugs, theme names) or fixed UI copy that is not brand chrome (EnvPage “not configured” toggle, sidebar “New chat”).
Fonts
Typography is opt-in per surface, not global on layout shells — the app shell and page header keep their original theme/expanded fonts; Mondwest applies only where explicitly set.
| Tier | Classes | Use for |
|---|---|---|
| Brand chrome | font-mondwest text-display (or themedChrome) |
Sidebar nav, card section headers (CardTitle), Segmented filter buttons, filter panel headings |
| Themed body | font-mondwest normal-case (or themedBody) |
Card content (Card, CardDescription), session/platform rows, analytics tables — scoped to the component |
| Page chrome | font-expanded |
Page header h1 (PageHeaderProvider) — sentence case, not text-display |
| Wordmark | Typography + size/tracking only |
Sidebar/mobile “Hermes Agent” — mixed case, no Mondwest, no text-display |
| Technical | font-mono-ui / font-mono / font-courier |
Model slugs, env keys, schedules, YAML, repo URLs |
- Do not put
themedBodyorthemedFonton<main>,App, or other layout wrappers — it overrides component-scoped styles. CardappliesthemedBody;CardTitleusestext-display(uppercase chrome);CardDescriptionusesthemedBody.NouiTypographydefaults tofont-sansunless a font prop is passed.- Do not use raw
font-sansorfont-display(theme sans variable) on new dashboard UI — prefer Mondwest tiers above where brand-appropriate.
Color tokens
- Prefer semantic tokens (
text-text-*,bg-card,border-border,text-foreground,text-destructive,text-success,text-warning) over raw layer references (text-midground,text-foreground). text-muted-foregroundis now wired to--color-text-secondary, so existing call sites stay correct, but new code should prefer the semantic name.- When you genuinely need a non-token color (icon de-emphasis on a chart, terminal foreground via inline style), keep alpha at
≥ 0.7for any text.