feat(dashboard): always enable embedded chat; remove dashboard --tui flag
The dashboard's embedded Chat surface (/chat, /api/ws, /api/pty) was gated behind `hermes dashboard --tui` / HERMES_DASHBOARD_TUI=1. The desktop app and the dashboard's own Chat tab both drive the agent over the /api/ws + /api/pty WebSockets, so a dashboard started without the flag would pass the /api/status health check but slam the chat WebSocket shut with WS code 4403 — the app connects, reports "ready", and chat stays dead. This was the root cause behind multiple user reports of the desktop app failing to connect to a self-hosted gateway/dashboard, and it bit Docker and host installs alike. Make the embedded chat unconditional: - web_server.py: _DASHBOARD_EMBEDDED_CHAT_ENABLED defaults to True; drop the embedded_chat parameter and the runtime reassignment from start_server(). The WS gates still read the constant (now always true) so the seam — and its "rejects when disabled" contract test — stays meaningful. - main.py: remove the `--tui` argument from the dashboard subparser and the `embedded_chat = args.tui or HERMES_DASHBOARD_TUI==1` derivation. - web/: isDashboardEmbeddedChatEnabled() returns true unconditionally; drop the deprecated __HERMES_DASHBOARD_TUI__ alias and the dead LEGACY_TUI_RE scrape in the vite dev-token plugin. - apps/desktop/electron/main.cjs: drop `--tui` from the spawned dashboardArgs (it would now error with "unrecognized arguments: --tui") and the redundant HERMES_DASHBOARD_TUI env injection. - Docker: no s6 run-script change needed — the script never passed --tui; the HERMES_DASHBOARD_TUI env var is now simply a no-op, so the image works out of the box with no extra var. - Docs: remove every dashboard --tui / HERMES_DASHBOARD_TUI reference across the CLI reference, env-var reference, docker/desktop/web-dashboard guides, in-app tips, and the zh-Hans translations. The terminal `hermes --tui` / HERMES_TUI references are intentionally left untouched. Tests: 270 passing across web_server, dashboard lifecycle, host-header, auth-gate, and docker-override-scripts suites.
This commit is contained in:
@ -94,7 +94,7 @@ Installers are built and uploaded to GitHub Releases manually. macOS/Windows sig
|
||||
|
||||
### How it works
|
||||
|
||||
The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — the **same layout a CLI install uses**, so the two are interchangeable. The renderer (React, in `src/`) talks to a `hermes dashboard --tui` backend over the standard gateway APIs and reuses the embedded TUI rather than reimplementing chat. The install, backend-resolution, and self-update logic all live in `electron/main.cjs`.
|
||||
The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — the **same layout a CLI install uses**, so the two are interchangeable. The renderer (React, in `src/`) talks to a `hermes dashboard` backend over the standard gateway APIs and reuses the embedded TUI rather than reimplementing chat. The install, backend-resolution, and self-update logic all live in `electron/main.cjs`.
|
||||
|
||||
### Verification
|
||||
|
||||
|
||||
@ -3747,7 +3747,7 @@ async function startHermes() {
|
||||
await advanceBootProgress('backend.port', 'Finding an open local port', 16)
|
||||
const port = await pickPort()
|
||||
const token = crypto.randomBytes(32).toString('base64url')
|
||||
const dashboardArgs = ['dashboard', '--no-open', '--tui', '--host', '127.0.0.1', '--port', String(port)]
|
||||
const dashboardArgs = ['dashboard', '--no-open', '--host', '127.0.0.1', '--port', String(port)]
|
||||
await advanceBootProgress('backend.runtime', 'Resolving Hermes runtime', 28)
|
||||
const backend = await ensureRuntime(resolveHermesBackend(dashboardArgs))
|
||||
const hermesCwd = resolveHermesCwd()
|
||||
@ -3771,7 +3771,6 @@ async function startHermes() {
|
||||
HERMES_HOME,
|
||||
...backend.env,
|
||||
HERMES_DASHBOARD_SESSION_TOKEN: token,
|
||||
HERMES_DASHBOARD_TUI: '1',
|
||||
HERMES_WEB_DIST: webDist
|
||||
},
|
||||
shell: backend.shell,
|
||||
|
||||
@ -12022,13 +12022,14 @@ def cmd_dashboard(args):
|
||||
|
||||
from hermes_cli.web_server import start_server
|
||||
|
||||
embedded_chat = args.tui or os.environ.get("HERMES_DASHBOARD_TUI") == "1"
|
||||
# The in-browser Chat tab (the embedded TUI over PTY/WebSocket) is always
|
||||
# available — the desktop app and the dashboard's own Chat tab both rely on
|
||||
# the `/api/ws` + `/api/pty` sockets, so there is no reason to gate them.
|
||||
start_server(
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
open_browser=not args.no_open,
|
||||
allow_public=getattr(args, "insecure", False),
|
||||
embedded_chat=embedded_chat,
|
||||
)
|
||||
|
||||
|
||||
@ -15314,14 +15315,6 @@ Examples:
|
||||
action="store_true",
|
||||
help="Allow binding to non-localhost (DANGEROUS: exposes API keys on the network)",
|
||||
)
|
||||
dashboard_parser.add_argument(
|
||||
"--tui",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Expose the in-browser Chat tab (embedded `hermes --tui` via PTY/WebSocket). "
|
||||
"Alternatively set HERMES_DASHBOARD_TUI=1."
|
||||
),
|
||||
)
|
||||
dashboard_parser.add_argument(
|
||||
"--skip-build",
|
||||
action="store_true",
|
||||
|
||||
@ -381,7 +381,7 @@ TIPS = [
|
||||
'Ctrl+G or Ctrl+X Ctrl+E in the TUI opens the input buffer in $EDITOR for long multi-line prompts.',
|
||||
'The TUI renders LaTeX inline — $E=mc^2$ becomes Unicode math instead of raw TeX.',
|
||||
'hermes dashboard launches a local web UI at 127.0.0.1:9119 — zero data leaves localhost.',
|
||||
'hermes dashboard --tui embeds the full Hermes TUI in your browser via xterm.js and a WebSocket PTY.',
|
||||
'hermes dashboard embeds the full Hermes TUI in your browser via xterm.js and a WebSocket PTY.',
|
||||
'Drop a YAML in ~/.hermes/dashboard-themes/ with two palette colors to reskin the entire dashboard.',
|
||||
'Dashboard plugins are drop-in: manifest.json + JS bundle in ~/.hermes/dashboard-plugins/ — no npm build required.',
|
||||
'layoutVariant: cockpit in a dashboard theme adds a 260px left rail that plugins can populate via the sidebar slot.',
|
||||
|
||||
@ -135,9 +135,13 @@ app = FastAPI(title="Hermes Agent", version=__version__, lifespan=_lifespan)
|
||||
_SESSION_TOKEN = os.environ.get("HERMES_DASHBOARD_SESSION_TOKEN") or secrets.token_urlsafe(32)
|
||||
_SESSION_HEADER_NAME = "X-Hermes-Session-Token"
|
||||
|
||||
# In-browser Chat tab (/chat, /api/pty, …). Off unless ``hermes dashboard --tui``
|
||||
# or HERMES_DASHBOARD_TUI=1. Set from :func:`start_server`.
|
||||
_DASHBOARD_EMBEDDED_CHAT_ENABLED = False
|
||||
# In-browser Chat tab (/chat, /api/pty, /api/ws, …). Always enabled: the
|
||||
# desktop app and the dashboard's own Chat tab both drive the agent over the
|
||||
# `/api/ws` + `/api/pty` WebSockets, so the embedded-chat surface is an
|
||||
# unconditional part of the dashboard. Kept as a module-level constant (rather
|
||||
# than inlining ``True`` at every gate) so the WS endpoints and the SPA token
|
||||
# injection share a single, testable seam.
|
||||
_DASHBOARD_EMBEDDED_CHAT_ENABLED = True
|
||||
|
||||
# Simple rate limiter for the reveal endpoint
|
||||
_reveal_timestamps: List[float] = []
|
||||
@ -8677,15 +8681,10 @@ def start_server(
|
||||
port: int = 9119,
|
||||
open_browser: bool = True,
|
||||
allow_public: bool = False,
|
||||
*,
|
||||
embedded_chat: bool = False,
|
||||
):
|
||||
"""Start the web UI server."""
|
||||
import uvicorn
|
||||
|
||||
global _DASHBOARD_EMBEDDED_CHAT_ENABLED
|
||||
_DASHBOARD_EMBEDDED_CHAT_ENABLED = embedded_chat
|
||||
|
||||
# Phase 0: stash the auth-gate flag on app.state so middleware / SPA-token
|
||||
# injection / WS-auth paths can branch on it consistently. Phase 3.5
|
||||
# uses this to decide whether to refuse the bind, log the gate-on
|
||||
|
||||
@ -389,7 +389,7 @@ def test_dashboard_insecure_env_var_opts_out_of_gate(
|
||||
"""``HERMES_DASHBOARD_INSECURE=1`` re-enables the legacy no-gate mode
|
||||
for operators running on trusted LANs behind a reverse proxy without
|
||||
the OAuth contract. Same opt-out shape as the rest of the s6 boolean
|
||||
envs (``HERMES_DASHBOARD``, ``HERMES_DASHBOARD_TUI``).
|
||||
envs (e.g. ``HERMES_DASHBOARD``).
|
||||
|
||||
With the gate off, ``/api/status`` (a public endpoint under the
|
||||
legacy ``_SESSION_TOKEN`` middleware) returns 200 with the
|
||||
|
||||
@ -22,7 +22,7 @@ def _ns(**kw):
|
||||
"""Build an argparse.Namespace with dashboard defaults plus overrides."""
|
||||
defaults = dict(
|
||||
port=9119, host="127.0.0.1", no_open=False, insecure=False,
|
||||
tui=False, stop=False, status=False,
|
||||
stop=False, status=False,
|
||||
)
|
||||
defaults.update(kw)
|
||||
return argparse.Namespace(**defaults)
|
||||
|
||||
@ -72,7 +72,7 @@ def test_dashboard_run_does_not_derive_insecure_from_bind_host() -> None:
|
||||
"Explicit HERMES_DASHBOARD_INSECURE opt-in is missing."
|
||||
)
|
||||
# Truthy values aligned with the rest of the s6 scripts
|
||||
# (HERMES_DASHBOARD, HERMES_DASHBOARD_TUI).
|
||||
# (e.g. HERMES_DASHBOARD).
|
||||
for truthy in ("1", "true", "TRUE", "True", "yes", "YES", "Yes"):
|
||||
assert truthy in text, (
|
||||
f"HERMES_DASHBOARD_INSECURE should accept truthy value {truthy!r}"
|
||||
|
||||
@ -1,15 +1,24 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
/** Set true by the server only for `hermes dashboard --tui` (or HERMES_DASHBOARD_TUI=1). */
|
||||
/**
|
||||
* Injected by the server as `true`. The embedded TUI Chat surface
|
||||
* (`/chat`, `/api/ws`, `/api/pty`) is always enabled, so this is
|
||||
* effectively a constant; kept on `window` for any consumer that reads
|
||||
* it directly and for parity with the server's bootstrap script.
|
||||
*/
|
||||
__HERMES_DASHBOARD_EMBEDDED_CHAT__?: boolean;
|
||||
/** @deprecated Older injected name; treated as on when true. */
|
||||
__HERMES_DASHBOARD_TUI__?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
/** True only when the dashboard was started with embedded TUI Chat (`hermes dashboard --tui`). */
|
||||
/**
|
||||
* Whether the dashboard's embedded TUI Chat surface is available.
|
||||
*
|
||||
* The embedded chat (`/chat` tab, `/api/ws` + `/api/pty` WebSockets) is now
|
||||
* an unconditional part of the dashboard — the desktop app and the in-browser
|
||||
* Chat tab both depend on it — so this always returns `true`. The function is
|
||||
* retained as a stable seam so call sites don't need to change if the surface
|
||||
* ever becomes conditional again.
|
||||
*/
|
||||
export function isDashboardEmbeddedChatEnabled(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
if (window.__HERMES_DASHBOARD_EMBEDDED_CHAT__ === true) return true;
|
||||
return window.__HERMES_DASHBOARD_TUI__ === true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -19,8 +19,6 @@ function hermesDevToken(): Plugin {
|
||||
const TOKEN_RE = /window\.__HERMES_SESSION_TOKEN__\s*=\s*"([^"]+)"/;
|
||||
const EMBEDDED_RE =
|
||||
/window\.__HERMES_DASHBOARD_EMBEDDED_CHAT__\s*=\s*(true|false)/;
|
||||
const LEGACY_TUI_RE =
|
||||
/window\.__HERMES_DASHBOARD_TUI__\s*=\s*(true|false)/;
|
||||
|
||||
return {
|
||||
name: "hermes:dev-session-token",
|
||||
@ -38,12 +36,7 @@ function hermesDevToken(): Plugin {
|
||||
return;
|
||||
}
|
||||
const embeddedMatch = html.match(EMBEDDED_RE);
|
||||
const legacyMatch = html.match(LEGACY_TUI_RE);
|
||||
const embeddedJs = embeddedMatch
|
||||
? embeddedMatch[1]
|
||||
: legacyMatch
|
||||
? legacyMatch[1]
|
||||
: "false";
|
||||
const embeddedJs = embeddedMatch ? embeddedMatch[1] : "true";
|
||||
return [
|
||||
{
|
||||
tag: "script",
|
||||
|
||||
@ -1342,14 +1342,13 @@ hermes claw migrate --source /home/user/old-openclaw
|
||||
hermes dashboard [options]
|
||||
```
|
||||
|
||||
Launch the web dashboard — a browser-based UI for managing configuration, API keys, and monitoring sessions. Requires `pip install hermes-agent[web]` (FastAPI + Uvicorn). The embedded browser Chat tab requires `--tui` plus the `pty` extra. See [Web Dashboard](/user-guide/features/web-dashboard) for full documentation.
|
||||
Launch the web dashboard — a browser-based UI for managing configuration, API keys, and monitoring sessions. Requires `pip install hermes-agent[web]` (FastAPI + Uvicorn). The embedded browser Chat tab is always available and additionally needs the `pty` extra (`pip install 'hermes-agent[web,pty]'`) plus a POSIX PTY environment such as Linux, macOS, or WSL2. See [Web Dashboard](/user-guide/features/web-dashboard) for full documentation.
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `--port` | `9119` | Port to run the web server on |
|
||||
| `--host` | `127.0.0.1` | Bind address |
|
||||
| `--no-open` | — | Don't auto-open the browser |
|
||||
| `--tui` | off | Enable the in-browser Chat tab by running `hermes --tui` behind a PTY/WebSocket bridge. Requires `pip install 'hermes-agent[web,pty]'` and a POSIX PTY environment such as Linux, macOS, or WSL2. |
|
||||
| `--insecure` | off | Allow binding to non-localhost hosts. Exposes dashboard credentials on the network; use only behind trusted network controls. |
|
||||
| `--stop` | — | Stop running `hermes dashboard` processes and exit. |
|
||||
| `--status` | — | List running `hermes dashboard` processes and exit. |
|
||||
@ -1360,9 +1359,6 @@ hermes dashboard
|
||||
|
||||
# Custom port, no browser
|
||||
hermes dashboard --port 8080 --no-open
|
||||
|
||||
# Enable the browser Chat tab
|
||||
hermes dashboard --tui
|
||||
```
|
||||
|
||||
## `hermes profile`
|
||||
|
||||
@ -425,7 +425,6 @@ Auth for the [web dashboard](/user-guide/features/web-dashboard) and for connect
|
||||
| `HERMES_DASHBOARD_SESSION_TOKEN` | Pins the dashboard session token instead of generating a random one per boot. Set this (e.g. `openssl rand -base64 32`) on the backend, then paste the same value into Hermes Desktop → Settings → Gateway → Remote gateway → Session token. Required for a stable remote desktop connection. |
|
||||
| `HERMES_DESKTOP_REMOTE_URL` | (Desktop side) Base URL of the remote backend, e.g. `http://host:9119`. When set, overrides the in-app Gateway settings. Must be paired with `HERMES_DESKTOP_REMOTE_TOKEN`. |
|
||||
| `HERMES_DESKTOP_REMOTE_TOKEN` | (Desktop side) The session token to authenticate with the remote backend — the same value as the backend's `HERMES_DASHBOARD_SESSION_TOKEN`. |
|
||||
| `HERMES_DASHBOARD_TUI` | `1` exposes the in-browser Chat tab (embedded `hermes --tui`), same as the `--tui` flag. |
|
||||
| `HERMES_DASHBOARD_OAUTH_CLIENT_ID` | OAuth client id (`agent:{instance_id}`) for the gated/public dashboard. Overrides `dashboard.oauth.client_id`. Provisioned by the Nous Portal for hosted deploys. |
|
||||
| `HERMES_DASHBOARD_PORTAL_URL` | OAuth portal URL (default: `https://portal.nousresearch.com`). Override only for staging/custom deploys. |
|
||||
| `HERMES_DASHBOARD_PUBLIC_URL` | Complete public URL the dashboard is reached at, for OAuth callback construction behind reverse proxies. Overrides `dashboard.public_url`. |
|
||||
|
||||
@ -57,7 +57,7 @@ The center of the app. You get:
|
||||
- **Drag-and-drop files** anywhere in the chat area to attach them to your next message.
|
||||
- **A right-hand preview rail** — render web pages, files, and tool outputs side by side while you keep chatting.
|
||||
|
||||
Chatting against a Hermes instance on another machine instead of the bundled local backend? See [Connecting to a remote backend](#connecting-to-a-remote-backend) below — and for the full picture of how the remote-hosted dashboard connection works (the `/api/ws` chat socket, the `--tui` requirement, session-token pinning, and WebSocket close-code triage), see [Web Dashboard → Connecting Hermes Desktop to a remote backend](./features/web-dashboard.md#connecting-hermes-desktop-to-a-remote-backend).
|
||||
Chatting against a Hermes instance on another machine instead of the bundled local backend? See [Connecting to a remote backend](#connecting-to-a-remote-backend) below — and for the full picture of how the remote-hosted dashboard connection works (the `/api/ws` chat socket, session-token pinning, and WebSocket close-code triage), see [Web Dashboard → Connecting Hermes Desktop to a remote backend](./features/web-dashboard.md#connecting-hermes-desktop-to-a-remote-backend).
|
||||
|
||||
### File browser
|
||||
|
||||
@ -104,7 +104,7 @@ To launch via the CLI, simply run `hermes desktop`. By default it installs works
|
||||
|
||||
## How it works
|
||||
|
||||
The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — **the same layout a CLI install uses**, which is why the two are interchangeable. The React renderer talks to a `hermes dashboard --tui` backend over the standard gateway APIs and reuses the agent rather than reimplementing it. Install, backend-resolution, and self-update logic live in the Electron main process.
|
||||
The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — **the same layout a CLI install uses**, which is why the two are interchangeable. The React renderer talks to a `hermes dashboard` backend over the standard gateway APIs and reuses the agent rather than reimplementing it. Install, backend-resolution, and self-update logic live in the Electron main process.
|
||||
|
||||
## Connecting to a remote backend
|
||||
|
||||
@ -115,8 +115,6 @@ By default the app starts and manages its own **local** backend. You can instead
|
||||
|
||||
The session token is the part that trips people up. **Hermes does not print it for you to copy** — by default the backend mints a fresh random token on every boot and injects it straight into the served HTML, so there is nothing in `config.yaml`, in `/gateway`, or in the logs to grab. For a remote connection you pin the token yourself on the backend, then paste that same value into the app.
|
||||
|
||||
The backend also has to be started with **`--tui`** (or `HERMES_DASHBOARD_TUI=1`). The desktop's chat runs over the dashboard's `/api/ws` + `/api/pty` WebSockets, and those endpoints are refused unless the embedded-chat surface is enabled. Without `--tui` the connection still passes the `/api/status` health check and the app reports "Remote Hermes backend is ready" — but chat never works because the WebSocket is closed immediately. A plain `hermes dashboard` or `hermes gateway` is not enough.
|
||||
|
||||
### On the backend (the remote machine)
|
||||
|
||||
```bash
|
||||
@ -129,12 +127,10 @@ chmod 600 ~/.hermes/.env
|
||||
echo "$TOKEN" # copy this value into the desktop app
|
||||
|
||||
# 2. Run the dashboard bound to a reachable address.
|
||||
# --tui enables the embedded chat (the /api/ws + /api/pty WebSockets the
|
||||
# desktop drives) — without it the app connects but chat stays dead.
|
||||
# --insecure is required for any non-loopback bind and keeps the legacy
|
||||
# session-token auth path (a non-loopback bind WITHOUT --insecure engages
|
||||
# the OAuth gate, which ignores the session token).
|
||||
hermes dashboard --tui --no-open --insecure --host 0.0.0.0 --port 9119
|
||||
hermes dashboard --no-open --insecure --host 0.0.0.0 --port 9119
|
||||
```
|
||||
|
||||
Running the dashboard as a systemd service? Give the unit `EnvironmentFile=%h/.hermes/.env` so the token is in the environment at boot.
|
||||
@ -157,7 +153,6 @@ The token is stored encrypted in the app's local config; leave the field blank o
|
||||
### Troubleshooting
|
||||
|
||||
- **Test fails with 401** — the token doesn't match the backend's `HERMES_DASHBOARD_SESSION_TOKEN`, or the backend is bound non-loopback *without* `--insecure` (OAuth gate is on, ignoring the token). Verify with `curl -s -H "X-Hermes-Session-Token: $TOKEN" http://<host>:9119/api/status` — that should return JSON, not a 401.
|
||||
- **App says "Remote Hermes backend is ready" but chat does nothing** — the backend was started without `--tui` (or `HERMES_DASHBOARD_TUI=1`). The status probe passes, but the chat WebSocket (`/api/ws` / `/api/pty`) is refused. Restart the backend with `--tui`.
|
||||
- **Connection refused / times out** — the backend bound to `127.0.0.1` (the default) or a firewall/VPN is blocking the port. Bind to `0.0.0.0` or the tailscale IP and open the port to your trusted network.
|
||||
- **No token to copy** — expected. You mint it yourself; Hermes never surfaces the default ephemeral one.
|
||||
|
||||
|
||||
@ -96,7 +96,6 @@ The dashboard is supervised by s6 — if it crashes, `s6-supervise` restarts it
|
||||
| `HERMES_DASHBOARD` | Set to `1` (or `true` / `yes`) to enable the supervised dashboard service | *(unset — service is registered but stays down)* |
|
||||
| `HERMES_DASHBOARD_HOST` | Bind address for the dashboard HTTP server | `0.0.0.0` |
|
||||
| `HERMES_DASHBOARD_PORT` | Port for the dashboard HTTP server | `9119` |
|
||||
| `HERMES_DASHBOARD_TUI` | Set to `1` to expose the in-browser Chat tab (embedded `hermes --tui` via PTY/WebSocket) | *(unset)* |
|
||||
| `HERMES_DASHBOARD_INSECURE` | Set to `1` (or `true` / `yes`) to bind without the OAuth auth gate. Only use on trusted networks behind a reverse proxy without the OAuth contract — the dashboard exposes API keys and session data | *(unset — gate enforced when a `DashboardAuthProvider` is registered)* |
|
||||
|
||||
The dashboard inside the container defaults to binding `0.0.0.0` — without it, the published `-p 9119:9119` port would not be reachable from the host. To restrict the bind to container loopback (for sidecar / reverse-proxy setups), set `HERMES_DASHBOARD_HOST=127.0.0.1`.
|
||||
|
||||
@ -28,7 +28,6 @@ This starts a local web server and opens `http://127.0.0.1:9119` in your browser
|
||||
| `--host` | `127.0.0.1` | Bind address |
|
||||
| `--no-open` | — | Don't auto-open the browser |
|
||||
| `--insecure` | off | Allow binding to non-localhost hosts (**DANGEROUS** — exposes API keys on the network; pair with a firewall and strong auth) |
|
||||
| `--tui` | off | Expose the in-browser Chat tab (embedded `hermes --tui` via PTY/WebSocket). Alternatively set `HERMES_DASHBOARD_TUI=1`. |
|
||||
|
||||
```bash
|
||||
# Custom port
|
||||
@ -39,9 +38,6 @@ hermes dashboard --host 0.0.0.0
|
||||
|
||||
# Start without opening browser
|
||||
hermes dashboard --no-open
|
||||
|
||||
# Enable the in-browser Chat tab
|
||||
hermes dashboard --tui
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
@ -56,7 +52,7 @@ The `web` extra pulls in FastAPI/Uvicorn; `pty` pulls in `ptyprocess` (POSIX) or
|
||||
|
||||
When you run `hermes dashboard` without the dependencies, it will tell you what to install. If the frontend hasn't been built yet and `npm` is available, it builds automatically on first launch.
|
||||
|
||||
The Chat tab is intentionally off for a plain `hermes dashboard` launch. Start the dashboard with `hermes dashboard --tui` or set `HERMES_DASHBOARD_TUI=1` when you want the embedded browser chat pane.
|
||||
The Chat tab is part of every `hermes dashboard` launch — the embedded browser chat pane (running the TUI over PTY/WebSocket) is always available, with no extra flag required.
|
||||
|
||||
## Pages
|
||||
|
||||
@ -101,9 +97,8 @@ Hermes Desktop normally launches its own local backend, but it can also attach t
|
||||
|
||||
Desktop's "remote backend is ready" probe only hits `GET /api/status`, which is a public endpoint — it answers as soon as *any* dashboard is running on the host. The live chat connection is a **separate** WebSocket to `/api/ws` (and `/api/pty`), and that socket is gated by two more checks the status probe never touches:
|
||||
|
||||
1. **The embedded chat must be enabled.** `/api/ws` and `/api/pty` close immediately with WS code **4403** unless the dashboard was started with `--tui` (or `HERMES_DASHBOARD_TUI=1`). A plain `hermes dashboard` or `hermes gateway` serves the status page but refuses the chat socket.
|
||||
2. **The session token must match.** Even with chat enabled, the socket closes with WS code **4401** if the token Desktop sends doesn't match the dashboard's session token. By default the dashboard generates a **fresh random token on every restart**, so a token you saved in Desktop yesterday is invalid after the service restarts. Pin it by setting `HERMES_DASHBOARD_SESSION_TOKEN` to a stable value.
|
||||
3. **The bind host must allow the client and match the Host header.** A loopback bind (`127.0.0.1`) only accepts loopback clients, so a remote machine is rejected at the socket layer regardless of token. Bind to a non-loopback address (`--host 0.0.0.0 --insecure` for a trusted LAN) so the peer-IP guard lets the remote client through. The remote URL you enter in Desktop must reach the dashboard by the same host it bound to — the DNS-rebinding guard requires the Host header to match.
|
||||
1. **The session token must match.** The socket closes with WS code **4401** if the token Desktop sends doesn't match the dashboard's session token. By default the dashboard generates a **fresh random token on every restart**, so a token you saved in Desktop yesterday is invalid after the service restarts. Pin it by setting `HERMES_DASHBOARD_SESSION_TOKEN` to a stable value.
|
||||
2. **The bind host must allow the client and match the Host header.** A loopback bind (`127.0.0.1`) only accepts loopback clients, so a remote machine is rejected at the socket layer regardless of token. Bind to a non-loopback address (`--host 0.0.0.0 --insecure` for a trusted LAN) so the peer-IP guard lets the remote client through. The remote URL you enter in Desktop must reach the dashboard by the same host it bound to — the DNS-rebinding guard requires the Host header to match.
|
||||
|
||||
#### Remote dashboard setup
|
||||
|
||||
@ -716,8 +711,6 @@ The "session token" is the dashboard's session token — the same secret the loc
|
||||
|
||||
The desktop app sends the token as an `X-Hermes-Session-Token` header. The backend accepts it only in legacy session-token mode — i.e. when bound non-loopback **with `--insecure`**. A non-loopback bind *without* `--insecure` engages the [OAuth gate](#oauth-authentication-gated-mode) instead, which ignores the session token. So a remote desktop connection means: `--insecure` + a token you control.
|
||||
|
||||
The backend must also be started with **`--tui`** (or `HERMES_DASHBOARD_TUI=1`). The desktop's chat runs over the `/api/ws` + `/api/pty` WebSockets, and those are refused unless the embedded-chat surface is enabled. Without `--tui` the desktop still passes the `/api/status` health check (so the app reports the backend "ready") but the chat WebSocket is closed on connect — connects, looks ready, chat stays dead. A plain `hermes dashboard` or `hermes gateway` is not enough.
|
||||
|
||||
### On the backend (the remote machine)
|
||||
|
||||
```bash
|
||||
@ -731,11 +724,9 @@ chmod 600 ~/.hermes/.env
|
||||
echo "$TOKEN" # copy this value into the desktop app
|
||||
|
||||
# 2. Run the dashboard bound to a reachable address.
|
||||
# --tui enables the embedded chat (the /api/ws + /api/pty WebSockets the
|
||||
# desktop drives) — without it the app connects but chat stays dead.
|
||||
# --insecure is required for any non-loopback bind and keeps the
|
||||
# legacy session-token auth path (instead of the OAuth gate).
|
||||
hermes dashboard --tui --no-open --insecure --host 0.0.0.0 --port 9119
|
||||
hermes dashboard --no-open --insecure --host 0.0.0.0 --port 9119
|
||||
```
|
||||
|
||||
If you run the dashboard as a systemd service, `~/.hermes/.env` is picked up automatically when the unit has `EnvironmentFile=%h/.hermes/.env`, so the token is in the environment at boot.
|
||||
@ -770,7 +761,6 @@ Both must be set together — setting only the URL is an error.
|
||||
|
||||
- **"Remote gateway incomplete"** — you haven't entered both a URL and a token. The token only needs re-entering if `remoteTokenSet` is false (no saved token yet).
|
||||
- **Test remote fails with 401** — the token doesn't match the backend's `HERMES_DASHBOARD_SESSION_TOKEN`, or the backend is running *without* `--insecure` on a non-loopback bind (the OAuth gate is on and ignores the session token). Confirm `--insecure` and that the env var is actually loaded (`curl -s -H "X-Hermes-Session-Token: $TOKEN" http://<host>:9119/api/status` should return JSON, not 401).
|
||||
- **Backend reports "ready" but chat does nothing** — the backend was started without `--tui` (or `HERMES_DASHBOARD_TUI=1`), so `/api/status` answers but the chat WebSocket (`/api/ws` / `/api/pty`) is refused. Restart the backend with `--tui`.
|
||||
- **Connection refused / times out** — the backend bound to `127.0.0.1` (the default) instead of a reachable address, or a firewall/VPN is blocking the port. Bind to `0.0.0.0` or the tailscale IP and open the port to your trusted network.
|
||||
- **No token anywhere to copy** — expected. You mint it (`HERMES_DASHBOARD_SESSION_TOKEN`); Hermes never auto-surfaces the default ephemeral one.
|
||||
|
||||
|
||||
@ -1144,14 +1144,13 @@ hermes claw migrate --source /home/user/old-openclaw
|
||||
hermes dashboard [options]
|
||||
```
|
||||
|
||||
启动 Web 控制台——基于浏览器的界面,用于管理配置、API 密钥和监控会话。需要 `pip install hermes-agent[web]`(FastAPI + Uvicorn)。内嵌浏览器 Chat 标签页需要 `--tui` 加上 `pty` extra。完整文档请参阅 [Web 控制台](/user-guide/features/web-dashboard)。
|
||||
启动 Web 控制台——基于浏览器的界面,用于管理配置、API 密钥和监控会话。需要 `pip install hermes-agent[web]`(FastAPI + Uvicorn)。内嵌浏览器 Chat 标签页始终可用,但额外需要 `pty` extra(`pip install 'hermes-agent[web,pty]'`)以及 POSIX PTY 环境(如 Linux、macOS 或 WSL2)。完整文档请参阅 [Web 控制台](/user-guide/features/web-dashboard)。
|
||||
|
||||
| 选项 | 默认值 | 说明 |
|
||||
|--------|---------|-------------|
|
||||
| `--port` | `9119` | Web 服务器运行端口 |
|
||||
| `--host` | `127.0.0.1` | 绑定地址 |
|
||||
| `--no-open` | — | 不自动打开浏览器 |
|
||||
| `--tui` | 关闭 | 通过 PTY/WebSocket 桥接在后台运行 `hermes --tui`,启用浏览器内 Chat 标签页。需要 `pip install 'hermes-agent[web,pty]'` 以及 Linux、macOS 或 WSL2 等 POSIX PTY 环境。 |
|
||||
| `--insecure` | 关闭 | 允许绑定到非 localhost 主机。会在网络上暴露控制台凭据;仅在受信任的网络控制下使用。 |
|
||||
| `--stop` | — | 停止正在运行的 `hermes dashboard` 进程并退出。 |
|
||||
| `--status` | — | 列出正在运行的 `hermes dashboard` 进程并退出。 |
|
||||
@ -1162,9 +1161,6 @@ hermes dashboard
|
||||
|
||||
# 自定义端口,不打开浏览器
|
||||
hermes dashboard --port 8080 --no-open
|
||||
|
||||
# 启用浏览器 Chat 标签页
|
||||
hermes dashboard --tui
|
||||
```
|
||||
|
||||
## `hermes profile`
|
||||
|
||||
@ -79,7 +79,6 @@ docker run -d \
|
||||
| `HERMES_DASHBOARD` | 设为 `1`(或 `true` / `yes`)以在主命令旁启动 dashboard | *(未设置——不启动 dashboard)* |
|
||||
| `HERMES_DASHBOARD_HOST` | dashboard HTTP 服务器的绑定地址 | `127.0.0.1` |
|
||||
| `HERMES_DASHBOARD_PORT` | dashboard HTTP 服务器的端口 | `9119` |
|
||||
| `HERMES_DASHBOARD_TUI` | 设为 `1` 以启用浏览器内 Chat 标签页(通过 PTY/WebSocket 嵌入 `hermes --tui`) | *(未设置)* |
|
||||
| `HERMES_DASHBOARD_INSECURE` | 设为 `1`(或 `true` / `yes`)以在不启用 OAuth 鉴权门控的情况下绑定。仅在可信网络(且通过没有 OAuth 契约的反向代理时)使用——dashboard 会暴露 API 密钥与会话数据 | *(未设置——当注册了 `DashboardAuthProvider` 时启用门控)* |
|
||||
|
||||
默认情况下,dashboard 保持在回环地址(`127.0.0.1`),以避免将
|
||||
|
||||
@ -24,7 +24,6 @@ hermes dashboard
|
||||
| `--host` | `127.0.0.1` | 绑定地址 |
|
||||
| `--no-open` | — | 不自动打开浏览器 |
|
||||
| `--insecure` | 关闭 | 允许绑定到非 localhost 主机(**危险**——会在网络上暴露 API 密钥;请配合防火墙和强认证使用) |
|
||||
| `--tui` | 关闭 | 启用浏览器内 Chat 标签页(通过 PTY/WebSocket 嵌入 `hermes --tui`)。也可设置 `HERMES_DASHBOARD_TUI=1`。 |
|
||||
|
||||
```bash
|
||||
# 自定义端口
|
||||
@ -35,9 +34,6 @@ hermes dashboard --host 0.0.0.0
|
||||
|
||||
# 启动时不打开浏览器
|
||||
hermes dashboard --no-open
|
||||
|
||||
# 启用浏览器内 Chat 标签页
|
||||
hermes dashboard --tui
|
||||
```
|
||||
|
||||
## 前置条件
|
||||
@ -52,7 +48,7 @@ pip install 'hermes-agent[web,pty]'
|
||||
|
||||
在没有依赖项的情况下运行 `hermes dashboard` 时,它会告诉你需要安装什么。如果前端尚未构建且 `npm` 可用,则会在首次启动时自动构建。
|
||||
|
||||
Chat 标签页在普通 `hermes dashboard` 启动时默认关闭。如需嵌入式浏览器聊天面板,请使用 `hermes dashboard --tui` 启动,或设置 `HERMES_DASHBOARD_TUI=1`。
|
||||
Chat 标签页是每次 `hermes dashboard` 启动的一部分——内嵌的浏览器聊天面板(通过 PTY/WebSocket 运行 TUI)始终可用,无需任何额外参数。
|
||||
|
||||
## 页面
|
||||
|
||||
|
||||
Reference in New Issue
Block a user