# Hermes PWA A mobile-first **Progressive Web App** front end for a self-hosted [Hermes Agent](https://hermes-agent.nousresearch.com) (Nous Research). It talks directly to the **Hermes Agent API Server** — the OpenAI-compatible HTTP + SSE endpoint — so you can chat with your agent, browse sessions, and use text, voice, and images from an installable app. Branding (logo, colour scheme, app icons) follows [derez.ai](https://derez.ai/); the logo in the sidebar links to the homepage. --- ## Which URL do I use? (dashboard vs API server) Hermes exposes **two different servers**. This app needs the second one. | URL | What it is | Auth | Works with this app? | | --- | --- | --- | --- | | `https://your-host/` (e.g. `beaver.derez.ai`) | The **web dashboard** | username + password → session cookie | ❌ No. It only allows CORS from its own origin and never sends `Access-Control-Allow-Credentials: true`, so a separately-hosted app can't use its cookie. | | `https://api.your-host/` (e.g. `api.beaver.derez.ai`, default port `8642`) | The **API Server** | `Authorization: Bearer ` | ✅ **Yes — this is the one.** | So enter the **API server URL** and your **`API_SERVER_KEY`** on the config screen. There is no QR login and no username/password here — the API server uses a bearer token. ### Enabling the API server (on the Hermes host) If it isn't already running, add to `~/.hermes/.env` and start the gateway: ```bash API_SERVER_ENABLED=true API_SERVER_KEY=choose-a-long-random-secret # Only needed if a browser calls the API directly (see CORS below): # API_SERVER_CORS_ORIGINS=https://your-pwa-origin ``` ```bash hermes gateway # → [API Server] API server listening on http://127.0.0.1:8642 ``` Verify from anywhere: ```bash curl -s https://api.your-host/health # {"status": "ok", "platform": "hermes-agent", "version": "..."} curl -s -H "Authorization: Bearer $API_SERVER_KEY" https://api.your-host/v1/capabilities ``` --- ## Reaching the API server (CORS) The API server has **no browser CORS by default**. Two options: ### Option A — same origin (recommended): use the bundled proxy `server/proxy.mjs` serves the built app **and** proxies `/api/*` and `/v1/*` to the API server, so everything is same-origin and CORS never applies. **You normally paste the API key into the app in the browser** (it's saved in that browser's `localStorage`). The proxy does not need the key: ```bash HERMES_TARGET=https://api.beaver.derez.ai PORT=8787 ./run.sh serve # open http://localhost:8787 ``` Then in the app's config screen, tick **"Same origin (behind the bundled proxy)"** and paste your API key. Done — no key on the server, no CORS. > `HERMES_API_KEY` is **optional**. Only set it if you'd rather keep the key on the proxy so it never reaches the browser: > > ```bash > HERMES_TARGET=https://api.beaver.derez.ai HERMES_API_KEY=your-key PORT=8787 ./run.sh serve > ``` ### Option B — direct, cross-origin Set an explicit allowlist on the Hermes host and enter the API URL + key in the app's config screen: ```bash API_SERVER_CORS_ORIGINS=https://your-pwa-origin ``` For local development you can skip CORS entirely with a dev proxy: ```bash # .env HERMES_DEV_TARGET=https://api.beaver.derez.ai ``` --- ## Quick start ```bash ./run.sh # dev server -> http://localhost:5173 ``` Then on the config screen enter your **API server URL** and **API key**. ### `run.sh` modes ```bash ./run.sh # dev server (http://localhost:5173) ./run.sh build # production build into dist/ ./run.sh serve # build if needed, then host + proxy /api/*, /v1/* (needs HERMES_TARGET) ./run.sh preview # build, then serve the build with vite preview ./run.sh icons # regenerate the PWA icon set ./run.sh typecheck # type-check without emitting ``` | Variable | Used by | Meaning | | --- | --- | --- | | `HERMES_TARGET` | `serve` | API server base URL to proxy to | | `HERMES_API_KEY` | `serve` | Optional — injected as `Authorization: Bearer …` | | `PORT` | `serve` | Port to listen on (default `8787`) | | `HERMES_DEV_TARGET` | `dev` | API server base URL to proxy to during development | --- ## Features - **Config screen** — add one or more API-server connections (URL, API key, optional profile prefix, custom headers) with a "Test" probe. - **Sessions** — list, open, create, rename, fork, and delete; filter by title. - **Streaming chat** — token-by-token replies over SSE, with live tool-call cards, cancel, and mid-turn steer. - **Images** — attach/paste/drag-drop; sent inline as `data:` URLs. - **Voice** — dictation via the browser's Web Speech API and spoken replies via on-device speech synthesis. Tap the mic to speak your prompt; tap again to stop. Requires a **secure context** (HTTPS or `localhost`) and a browser with the Web Speech API (Chrome, Edge, Safari). Note: Chrome's dictation sends audio to Google's servers. - **Installable PWA** — offline app shell, home-screen install, standalone display. --- ## Notes & limitations - **Voice is client-side.** The API server has no `/transcribe` or `/tts` endpoint, so dictation uses the browser's `SpeechRecognition` and replies use `speechSynthesis`. Support varies by browser (best on Chrome/Edge/Safari). - **Profiles.** The API server routes profiles via a `/p//…` prefix, and each profile has its own `API_SERVER_KEY`. Add one connection per profile to see them all; the sidebar switches between connections. - **Session ownership.** The API server can create and drive its own sessions, but it refuses to write to a session owned by another surface (CLI, TUI, dashboard, messaging) — those open read-only. - **Response shapes.** The Sessions REST API's exact JSON envelope isn't fully documented, so the client normalises several shapes (`sessions`/`data`/`items`, `id`/`session_id`, string/array content). If a field looks off on your version, it's a one-line tweak in `src/api/client.ts`. ## Project layout ``` src/ api/ client.ts # HermesClient: bearer auth, sessions REST, SSE chat types.ts # API server types + normalised UI types hooks/ useSpeechRecognition.ts # browser dictation useTts.ts # browser speech synthesis lib/ config.ts # saved connections (localStorage) format.ts # time / token formatting markdown.tsx # tiny safe markdown renderer state/ store.ts # zustand store: connections, sessions, streaming components/ # ConfigScreen, ConnectionForm, Sidebar, ChatView, Composer, … server/ proxy.mjs # optional static host + /api,/v1 reverse proxy (no deps) scripts/ gen-icons.mjs # dependency-free PNG icon generator ``` ## License Licensed under the [PolyForm Internal Use License 1.0.0](./LICENSE) — internal business use only; redistribution is not permitted. The license text is also shipped in the build output as `dist/LICENSE`. ## API endpoints used `GET /health`, `GET /v1/capabilities`, `GET /v1/models`, `GET|POST /api/sessions`, `GET|PATCH|DELETE /api/sessions/{id}`, `GET /api/sessions/{id}/messages`, `POST /api/sessions/{id}/fork`, `POST /api/sessions/{id}/chat/stream` (SSE), and `POST /v1/runs/{id}/stop|steer|approval|clarify`.