hermes-agent/website/docs/user-guide/desktop.md
Teknium 5b43bf7d02
feat: uninstall the Chat GUI without removing the agent (CLI + desktop UI) (#40355)
* feat: uninstall the Chat GUI without removing the agent (CLI + desktop UI)

Adds a GUI-only uninstall path so people can remove the desktop Chat GUI
while keeping the Hermes agent + their config/sessions/.env, and surfaces
the three CLI uninstall modes inside the desktop app's Settings → About.

CLI:
- New hermes_cli/gui_uninstall.py: cross-platform discovery + removal of the
  desktop GUI's artifacts (source-built dist/release/node_modules + build
  stamp, the packaged app bundle, and the Electron userData dir) on Linux,
  macOS, and Windows. Never touches the agent source, venv, or user data.
- `hermes uninstall --gui` removes only the Chat GUI; `--gui-summary` prints a
  JSON install snapshot (used by the desktop UI to gate options + detect a
  missing agent for a future lite client).
- `hermes uninstall --yes` / `--full --yes` now run non-interactively, sharing
  the destructive sequence via a new _perform_uninstall() helper. The keep-data
  and full flows also sweep the GUI artifacts.

Desktop:
- electron/desktop-uninstall.cjs: pure helpers mapping each mode (gui/lite/full)
  to CLI flags, resolving the running app bundle per OS, and building the
  detached cleanup script that waits for the app to exit, runs the Python
  uninstall, and removes the bundle.
- IPC hermes:uninstall:summary / :run, preload bridge, and types.
- Settings → About "Danger zone" with the three options; agent-removing
  options hide when no local agent is detected.

Tests: tests/hermes_cli/test_gui_uninstall.py (22 pass with the existing
uninstall tests), electron/desktop-uninstall.test.cjs (17 pass, wired into
test:desktop:platforms). Docs: desktop.md "Uninstalling" + cli-commands.md.

* fix(desktop): tear down backend process tree before GUI uninstall (Windows lock safety)

The desktop uninstall cleanup script waited only on the desktop app's own
PID, but a backend grandchild (gateway / pty terminal / hermes REPL) can
outlive it and keep hermes.exe + venv files mandatory-locked on Windows —
making the script's rmdir half-fail and leaving a partial install, the same
failure class as the self-update path's #37532.

- main.cjs: runDesktopUninstall now awaits releaseBackendLock() before
  spawning the cleanup script — tree-kills every backend PID the desktop owns
  (primary + pool) via taskkill /T /F and polls the venv shim until unlocked.
  Extracted the shared core out of releaseBackendLockForUpdate so both the
  update hand-off and the uninstaller use the identical, incident-hardened
  teardown. No-op on macOS/Linux (no mandatory locks).
- desktop-uninstall.cjs: Windows cleanup script removes the bundle via a
  bounded rmdir retry loop (10x, 1s) instead of a single rmdir, since Windows
  releases directory handles lazily even after the holding process exits.
- Dropped a fragile tasklist|findstr reap-by-path attempt; the Electron-side
  tree-kill-by-PID is the reliable mechanism.

Tests: desktop-uninstall.test.cjs updated for the retry-loop output (17 pass).

* fix(desktop): address review on GUI uninstall (venv self-delete, gates, wait-loop)

Resolves @OutThisLife's review on #40355:

1. full mode now gated on agent presence (needsAgent: true). It removes the
   agent + user data, so on a lite client with no local agent it's hidden
   like lite — no more offering to remove an agent that isn't there.

2. (Finding 3, the real bug) lite/full no longer rmtree the venv from the
   venv's OWN python. On Windows a running python.exe is mandatory-locked, so
   that half-fails. New lightweight 'python -m hermes_cli.uninstall --mode X'
   entrypoint (stdlib-only imports) lets the desktop run agent-removing modes
   under the SYSTEM python (findSystemPython) with PYTHONPATH=<agentRoot>, so
   import hermes_cli resolves from source while the venv is torn down. Falls
   back to venv python + logs when no system python (gui-only unaffected).

3. Windows wait-loop is now bounded (60 tries, matching POSIX) and matches the
   PID as a whole space-delimited token via findstr (no substring 99->990
   trap, no redundant bare find). set HERMES_HOME/PID/PYTHONPATH now quoted.

4. Renamed the misleading 'returns null for dev run' test — the dev-run safety
   is shouldRemoveAppBundle(isPackaged=false), which the test now asserts.

Docs: note that --gui on a source checkout also sweeps node_modules/build
output. Tests: 18 python + 19 desktop pass.
2026-06-06 18:22:38 -07:00

17 KiB

sidebar_position title description
3 Desktop App The native Hermes desktop app — a polished experience for chatting with Hermes, with streaming tool output, side-by-side previews, a file browser, voice, cron, profiles, skills, and settings. macOS, Windows, and Linux.

Desktop App

The Hermes desktop app is a native app built around the same agent you get from the CLI and the gateway — same config, same API keys, same sessions, same skills, same memory. It is not a separate product or a lightweight clone; it uses the same Hermes Agent core and settings, and drives it through a modern & thoughtfully designed UI. If you have used hermes in a terminal, everything you set up there is already here, and anything you do here shows up there.

It runs on macOS, Windows, and Linux.

:::tip Which interface is which? Hermes has several front ends that all talk to the same agent:

  • Desktop App (this page) — a native application with a purpose-built UI for chat, configuration, and management.
  • CLI (hermes) and TUI (hermes --tui) — terminal interfaces.
  • Web Dashboard (hermes dashboard) — a browser admin panel; its optional Chat tab embeds the TUI through a pseudo-terminal.

Pick whichever fits the moment. They share state, so you can start a session in one and resume it in another. :::

Install

Follow the installation instructions for Hermes Desktop.

If you already have Hermes installed, simply run

hermes desktop

That uses your current config, keys, sessions, and skills.

What's in the app

The desktop app is organized as a chat-first window with a left sidebar for navigation. It's built to allow managing multiple simultaneous agent conversations, configuring messaging providers, creating artifacts, browsing projects' folder structures, and working on multiple projects at once.

Chat

The center of the app. You get:

  • Streaming responses with live tool activity and structured tool-call summaries as the agent works.
  • The same conversation history as every other Hermes surface — sessions started here resume in the CLI/TUI and vice versa.
  • 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 below — and for the full picture of how the remote-hosted dashboard connection works (the auth gate, the /api/ws chat socket, and WebSocket close-code triage), see Web Dashboard → Connecting Hermes Desktop to a remote backend.

File browser

Explore and preview the working directory without leaving the app — useful for following along as the agent reads, writes, and edits files. Set the initial project directory with hermes desktop --cwd <path> (or the HERMES_DESKTOP_CWD environment variable).

Voice

Talk to Hermes and hear it back, the same voice mode available elsewhere. On macOS the OS will prompt once for microphone access.

Settings & onboarding

Manage providers, models, tools, and credentials from a real UI instead of editing YAML. First-run onboarding gets you to your first message in seconds. The settings panes cover providers/keys, model selection, toolset configuration, MCP servers, the gateway, and session management.

Management panes

The app also surfaces the broader Hermes management surface so you don't have to drop to a terminal:

  • Skills — browse, install, and manage skills.
  • Cron — view and manage scheduled jobs.
  • Profiles — switch between Hermes profiles (isolated config/skills/sessions).
  • Messaging — set up gateway channels.
  • Agents and Command Center — orchestration surfaces for multi-agent work.

Updating

The app checks for updates in the background and offers a one-click update when one is ready.

The manual update process also works with the GUI.

Uninstalling

Open Settings → About → Danger zone and pick how much to remove:

  • Uninstall Chat GUI only — removes the desktop app and its data; the Hermes agent, your config, and your chats stay. (Same as hermes uninstall --gui.)
  • Uninstall GUI + agent, keep my data — removes the app and the agent but keeps config, chats, and secrets for a future reinstall. (Same as hermes uninstall.)
  • Uninstall everything — removes the app, the agent, and all user data. (Same as hermes uninstall --full.)

The app closes to finish the job (the cleanup runs after it exits so it can remove the running app bundle and its own venv). The agent-removing options are hidden automatically when no local agent is installed (for example, a GUI-only "lite" client connected to a remote backend).

You can do the same from the terminal — hermes uninstall --gui for the GUI alone, or hermes uninstall / hermes uninstall --full for the agent too.

:::note Running hermes uninstall --gui from a source checkout (a hermes desktop dev build) also removes the workspace node_modules and apps/desktop/{dist,release} build output, since those are GUI build artifacts. They're recoverable with hermes desktop (or npm install + a rebuild) — but if you're actively hacking on the desktop app, expect to reinstall dependencies afterward. :::

CLI reference: hermes desktop

To launch via the CLI, simply run hermes desktop. By default it installs workspace Node dependencies, builds the current OS's unpacked Electron app, then launches that packaged artifact.

Flag Description
--skip-build Skip npm install/package and launch the existing unpacked app from apps/desktop/release
--force-build Force a full rebuild even if the content stamp matches
--build-only Build the desktop app but do not launch it (used by hermes update)
--source Launch via electron . against apps/desktop/dist instead of the packaged app
--cwd PATH Initial project directory for desktop chat sessions (sets HERMES_DESKTOP_CWD)
--hermes-root PATH Override the Hermes source root the app uses (sets HERMES_DESKTOP_HERMES_ROOT)
--ignore-existing Force the app to ignore any hermes CLI already on PATH during backend resolution
--fake-boot Enable deterministic boot delays for validating the startup UI

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 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

By default the app starts and manages its own local backend. You can instead point it at a Hermes backend running on another machine — a VPS, a home server, or a Mini behind Tailscale.

:::info The remote backend is a running hermes dashboard process "Remote backend" means a hermes dashboard server running on the remote machine — that is the process the desktop app connects to. Nothing in this section works unless that dashboard is actually up and reachable. The desktop app does not start it for you; you (or a systemd service) keep hermes dashboard running on the remote host, and the app attaches to it. If you also use messaging channels (Telegram, Discord, etc.), the gateway is a separate long-running process you start independently — see the note after the setup steps. :::

The connection has two halves: on the backend you protect the dashboard with an auth provider, and in the app you enter the backend's URL and sign in. Binding the dashboard to a non-loopback address automatically engages its auth gate, and the provider you configure is what lets the desktop app through.

Pick a provider based on where the backend lives:

  • OAuth (Nous Portal) — preferred for anything reachable beyond your own machine. Logins are verified against your Nous account, so this is the option suitable for a VPS, a public host, or any remote backend. Register the dashboard with hermes dashboard register (or the Portal /local-dashboards page) to provision its OAuth client, then sign in from the app with Sign in with Nous Research. A self-hosted OIDC provider works the same way if you run your own identity provider.
  • Username/password — local / trusted-network use only. The simplest option when the backend is on the same trusted LAN or reachable only over a VPN (e.g. Tailscale). It protects a single shared credential with no external identity provider, so do not use it for a dashboard exposed to the public internet — reach for OAuth there instead.

The rest of this section shows the username/password path because it's the quickest to stand up on a trusted network; for the OAuth path see Web Dashboard → Default provider: Nous Research.

On the backend (the remote machine)

Set a username and password, then start the dashboard bound to a reachable address. The credentials live in ~/.hermes/.env (the secrets file, mode 0600):

# 1. Set the dashboard login credentials.
cat >> ~/.hermes/.env <<'EOF'
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=choose-a-strong-password
# Recommended: a stable signing secret so sessions survive restarts.
# Without it a random key is generated per boot and you'll be logged out
# on every restart.
HERMES_DASHBOARD_BASIC_AUTH_SECRET=$(openssl rand -base64 32)
EOF
chmod 600 ~/.hermes/.env

# 2. Run the dashboard bound to a reachable address. The non-loopback bind
#    engages the auth gate; the username/password provider handles login.
hermes dashboard --no-open --host 0.0.0.0 --port 9119

Keep that hermes dashboard process running for as long as you want the desktop app to be able to connect — if it stops, the app can no longer reach the backend. Run it under systemd, tmux, or your process manager of choice so it survives logout and reboots.

Separately, make sure the gateway is running on the remote host if you rely on messaging channels — the dashboard backend is what the desktop app talks to, but your Telegram/Discord/Slack gateway sessions are a different process that you start and keep running on their own. See Messaging for gateway setup.

Prefer not to keep a plaintext password at rest? Set HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH to a scrypt hash instead — compute it with python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('PW'))". Full configuration surface (config.yaml keys, every env var, the rate limiter): Web Dashboard → Username/password provider.

Running the dashboard as a systemd service? Give the unit EnvironmentFile=%h/.hermes/.env so the credentials are in the environment at boot.

:::warning The dashboard reads and writes your .env (API keys, secrets) and can run agent commands. The username/password setup shown above is for a trusted network — never expose a password-protected dashboard directly to the open internet; put it behind a VPN. Tailscale is the clean option: bind to the machine's tailscale IP (--host <tailscale-ip>) and use http://<tailscale-ip>:9119 as the Remote URL so only your tailnet can reach it. To reach a backend over the public internet, use the OAuth (Nous Portal) provider instead. :::

In the app

Settings → Gateway → Remote gateway:

  1. Remote URLhttp://<backend-host>:9119 (path prefixes like /hermes work if you front it with a reverse proxy)
  2. Sign in — the app detects which provider the backend advertises and adapts the button. For a username/password backend it shows a Sign in button that opens a credential form (enter the credentials from step 1). For an OAuth backend it shows Sign in with <provider> (e.g. Sign in with Nous Research), which runs the provider's browser sign-in. Either way the app ends up with an authenticated session against the backend.
  3. Save and reconnect — switches the desktop shell onto the remote backend. The session refreshes automatically; you stay signed in across restarts when HERMES_DASHBOARD_BASIC_AUTH_SECRET is set.

You can also set the backend URL without the UI via the HERMES_DESKTOP_REMOTE_URL environment variable before launching the app (it overrides the in-app setting); you still sign in from the Gateway settings panel.

Troubleshooting

  • Sign-in fails with 401 / "Invalid credentials" — the username or password doesn't match the backend's HERMES_DASHBOARD_BASIC_AUTH_USERNAME / HERMES_DASHBOARD_BASIC_AUTH_PASSWORD. The backend returns the same generic error for an unknown user and a wrong password (no enumeration oracle), so double-check both. Confirm the gate is on with curl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers' — it should report true and include "basic".
  • No "Sign in" button — it asks for a session token instead — the backend's username/password provider isn't active. /api/status won't list "basic" in auth_providers. Make sure both the username and a password (or password hash) are set in ~/.hermes/.env and that the dashboard process actually loaded them.
  • Signed out on every restart — set HERMES_DASHBOARD_BASIC_AUTH_SECRET to a stable value. Without it the token-signing key is regenerated per boot, invalidating all sessions.
  • 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.

For the same setup from the web-dashboard angle, see Web Dashboard → Connecting Hermes Desktop to a remote backend; the env vars are catalogued under Environment Variables → Web Dashboard & Hermes Desktop.

Troubleshooting

Boot logs land in HERMES_HOME/logs/desktop.log (it includes backend output and recent Python tracebacks) — check it first if the app reports a boot failure. You can also tail it from the CLI:

hermes logs gui -f

Common resets:

# Force a clean first-launch setup (macOS/Linux)
rm "$HOME/.hermes/hermes-agent/.hermes-bootstrap-complete"

# Rebuild a broken Python venv (macOS/Linux)
rm -rf "$HOME/.hermes/hermes-agent/venv"

# Reset a stuck macOS microphone prompt
tccutil reset Microphone com.nousresearch.hermes

Building from source

If you want to hack on the app itself, install workspace deps from the repo root once, then run the dev server from apps/desktop:

npm install          # from repo root — links apps/desktop, web, apps/shared
cd apps/desktop
npm run dev          # Vite renderer + Electron, which boots the Python backend

Point the app at a specific checkout, or sandbox it from your real config:

HERMES_DESKTOP_HERMES_ROOT=/path/to/clone npm run dev
HERMES_HOME=/tmp/throwaway npm run dev
npm run dev:fake-boot   # exercise the startup overlay with deterministic delays

Build installers:

npm run dist:mac     # DMG + zip
npm run dist:win     # NSIS + MSI
npm run dist:linux   # AppImage + deb + rpm
npm run pack         # unpacked app under release/ (no installer)

macOS/Windows signing and notarization run automatically when the relevant credentials are present in the environment (CSC_LINK / CSC_KEY_PASSWORD / APPLE_* for macOS, WIN_CSC_* for Windows).

See also

  • CLI Guide — the terminal interface
  • TUI — the modern terminal UI the desktop backend reuses
  • Web Dashboard — browser admin panel with an embedded chat tab
  • Configuration — config that the desktop app reads and writes
  • Windows (Native) — native Windows install path