hermes-agent/apps/desktop
emozilla 967bc910b4 fix(desktop): repair voice dictation on Windows
Voice dictation was broken on Windows in two ways:

1. Mic access was denied. The Electron permission request handler only
   granted 'media' requests whose details.mediaTypes included 'audio',
   but Chromium on Windows frequently fires the mic request with an empty
   mediaTypes array, so getUserMedia threw NotAllowedError. The handler
   now grants audio-capture when mediaTypes includes 'audio' OR is
   empty/absent, handles the 'audioCapture' permission name, and adds a
   setPermissionCheckHandler (the synchronous path Chromium also consults
   for getUserMedia on Windows). Video is still denied.

2. Transcripts went nowhere. The composer's insertText handler (used by
   dictation and other inserts) only updated the assistant-ui composer
   store via setText, never the contentEditable editor DOM. The
   draft->editor sync effect only re-renders the editor when it is NOT
   focused, and dictation runs while the editor has/regains focus, so the
   transcript was stored but never shown and could not be sent. insertText
   now renders into the editor DOM and places the caret, mirroring
   appendExternalText.

Also hardens fetchJson: a 2xx response with an HTML body (or text/html
content-type) now rejects with a clear message naming the URL instead of
an opaque JSON.parse 'Unexpected token <' error.
2026-05-31 03:50:19 -04:00
..
assets feat: add install readme et al 2026-05-01 22:20:05 -05:00
electron fix(desktop): repair voice dictation on Windows 2026-05-31 03:50:19 -04:00
public feat(desktop): theme polish, prose chat typography, composer chrome 2026-05-11 10:25:23 -04:00
scripts fix(installer): stamp Hermes icon onto Hermes.exe via rcedit (no winCodeSign) 2026-05-29 00:50:14 -04:00
src fix(desktop): repair voice dictation on Windows 2026-05-31 03:50:19 -04:00
.prettierrc feat(desktop): add structured desktop chat app 2026-05-01 12:49:12 -05:00
components.json feat(desktop): add structured desktop chat app 2026-05-01 12:49:12 -05:00
eslint.config.mjs feat(desktop): add structured desktop chat app 2026-05-01 12:49:12 -05:00
index.html feat(desktop): theme polish, prose chat typography, composer chrome 2026-05-11 10:25:23 -04:00
package-lock.json chore(desktop): bump version to 0.0.1 2026-05-20 14:59:26 -04:00
package.json chore: linux build 2026-05-30 23:38:30 -05:00
preview-demo.html feat: better composer etc 2026-05-04 22:19:16 -05:00
README.md Merge origin/main into bb/gui 2026-05-29 20:40:08 -05:00
tsconfig.json feat: move dashboard to apps/ so we can share ws proto 2026-05-02 13:38:49 -05:00
vite.config.ts feat: glass ui pass 2026-05-16 19:21:33 -05:00

Hermes Desktop

Native Electron shell for Hermes. It packages the desktop renderer, a bundled Hermes source payload, and installer targets for macOS and Windows. Note: this doc needs updating.

Setup

Install workspace dependencies from the repo root so apps/desktop, web, and apps/shared stay linked:

npm install

For Python, you have two options:

Option A — let the desktop provision it for you (recommended for first-time setup): just run npm run dev. On first launch the desktop creates a venv at HERMES_HOME/hermes-agent/venv and runs pip install -e . against the resolved Hermes source automatically. Requires Python 3.11+ on PATH.

Option B — share an existing CLI install: if you already ran scripts/install.ps1 / scripts/install.sh, that's the same layout the desktop uses. The desktop reuses your existing venv and editable install — no extra steps. See Runtime Bootstrap below for details.

If you're hacking on Hermes from a clone outside HERMES_HOME/hermes-agent, point the desktop at it explicitly:

HERMES_DESKTOP_HERMES_ROOT=/path/to/your/clone npm run dev

Runtime prerequisites

Hermes Desktop needs:

  • Python 3.11+ — for the agent runtime, dashboard backend, and tool execution. (required)
  • Git for Windows (Windows only) — provides Git Bash, which Hermes' terminal tool calls directly. Linux and macOS already ship a system bash. (required)
  • ripgrep — used by Hermes' search_files tool for fast .gitignore-aware file/content search. Recommended on all platforms; Hermes falls back to grep/find if missing (works but slower and noisier).

The packaged Windows installer (Hermes-*.exe) detects all three at install time. Required items missing are auto-installed via winget install -e --id Python.Python.3.11 --scope user and winget install -e --id Git.Git. The recommended ripgrep is offered as winget install -e --id BurntSushi.ripgrep.MSVC --scope user. If winget isn't available the installer shows manual download URLs and lets you continue. The MSI installer (Hermes-*.msi) doesn't run the prereq page — enterprise deploys are expected to handle prereqs out-of-band.

For dev (npm run dev) the Python and Git Bash checks happen at first launch via the Electron bootstrapper, which throws a clear error if either prereq is missing. Manual install commands you can run yourself:

winget install -e --id Python.Python.3.11 --scope user
winget install -e --id Git.Git
winget install -e --id BurntSushi.ripgrep.MSVC --scope user

Development

cd apps/desktop
npm run dev

npm run dev starts Vite on 127.0.0.1:5174, launches Electron, and lets Electron boot the Hermes backend (hermes dashboard --no-open --tui) on an open port in 9120-9199. This path is for UI iteration and may still show Electron/dev identities in OS prompts.

Useful overrides:

HERMES_DESKTOP_HERMES_ROOT=/path/to/hermes-agent npm run dev
HERMES_DESKTOP_PYTHON=/path/to/python npm run dev
HERMES_DESKTOP_CWD=/path/to/project npm run dev
HERMES_DESKTOP_IGNORE_EXISTING=1 npm run dev
HERMES_HOME=/tmp/throwaway-hermes-home npm run dev
HERMES_DESKTOP_BOOT_FAKE=1 npm run dev
HERMES_DESKTOP_BOOT_FAKE=1 HERMES_DESKTOP_BOOT_FAKE_STEP_MS=900 npm run dev

HERMES_DESKTOP_IGNORE_EXISTING=1 skips any hermes CLI already on PATH, which is useful when testing the factory-image bootstrap path.

HERMES_HOME overrides the install root (default: %LOCALAPPDATA%\hermes on Windows, ~/.hermes elsewhere) — handy for sandboxed dev runs that shouldn't touch your real config.

HERMES_DESKTOP_BOOT_FAKE=1 adds deterministic per-phase delays to desktop startup so you can validate the startup overlay and progress bar. For convenience, npm run dev:fake-boot enables fake mode with defaults.

On a fresh Hermes profile, Desktop shows a first-run setup overlay after boot. The overlay saves the minimum required provider credential (for example OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY) to the active Hermes .env, reloads the backend env, and then lets the user continue without opening Settings manually.

Dashboard Dev

Run the Python dashboard backend with embedded chat enabled:

hermes dashboard --tui --no-open

For dashboard HMR, start Vite in another terminal:

cd web
npm run dev

Open the Vite URL. The dev server proxies /api, /api/pty, and plugin assets to http://127.0.0.1:9119 and fetches the live dashboard HTML so the ephemeral session token matches the running backend.

Build

npm run build
npm run pack          # unpacked app at release/mac-<arch>/Hermes.app
npm run dist:mac      # macOS DMG + zip
npm run dist:mac:dmg  # DMG only
npm run dist:mac:zip  # zip only
npm run dist:win      # NSIS + MSI

Before packaging, the desktop app no longer bundles a copy of the Hermes Agent Python source. Instead, the packaged Electron app will fetch and install Hermes Agent at first launch via scripts/install.ps1's stage protocol (Windows) — see the bootstrap flow documented in electron/main.cjs. macOS and Linux packaged builds are temporarily non-functional until install.sh gains the same stage protocol; dev workflows on all three platforms continue to work since they resolve a sibling source checkout.

Automated Releases

Desktop installers are published by .github/workflows/desktop-release.yml with two channels:

  • Stable: runs on published GitHub releases and uploads signed artifacts to that release tag.
  • Nightly: runs on main pushes and updates the rolling desktop-nightly prerelease.

The workflow injects a channel-aware desktop version at build time:

  • stable: derived from the release tag (for example v2026.5.5 -> 2026.5.5)
  • nightly: 0.0.0-nightly.YYYYMMDD.<sha>

Artifact names include channel, platform, and architecture:

Hermes-<version>-<channel>-<platform>-<arch>.<ext>

Each run also publishes SHA256SUMS-<platform>.txt so installers can be verified.

Stable release gates

Stable builds fail fast if signing credentials are missing:

  • macOS signing + notarization: CSC_LINK, CSC_KEY_PASSWORD, APPLE_API_KEY, APPLE_API_KEY_ID, APPLE_API_ISSUER
  • Windows signing: WIN_CSC_LINK, WIN_CSC_KEY_PASSWORD

Stable macOS builds also validate stapling and Gatekeeper assessment in CI before upload.

Icons

Desktop icons live in assets/:

  • assets/icon.icns
  • assets/icon.ico
  • assets/icon.png

The builder config points at assets/icon. Replace these files directly if the app icon changes.

Testing Install Paths

Use the package-local test scripts from this directory:

npm run test:desktop:all
npm run test:desktop:existing
npm run test:desktop:fresh
npm run test:desktop:dmg
npm run test:desktop:platforms

test:desktop:existing builds the packaged app and opens it normally. It should use an existing hermes CLI if one is on PATH, preserving the users real ~/.hermes config.

test:desktop:fresh builds the packaged app and launches it in a throwaway fresh-install sandbox. It sets HERMES_DESKTOP_IGNORE_EXISTING=1, points Electron userData at a temp dir, points HERMES_HOME at a temp dir, and launches through the factory-image bootstrap path without touching your real desktop runtime or ~/.hermes.

test:desktop:dmg builds and opens the DMG.

test:desktop:platforms runs platform bootstrap-path assertions, including:

  • existing-CLI vs factory-image runtime path selection semantics
  • WSL2 protection against Windows .exe/.cmd/.bat/.ps1 overrides
  • platform-specific runtime import checks (winpty vs ptyprocess)

For fast reruns without rebuilding:

HERMES_DESKTOP_SKIP_BUILD=1 npm run test:desktop:fresh
HERMES_DESKTOP_SKIP_BUILD=1 npm run test:desktop:existing
HERMES_DESKTOP_SKIP_BUILD=1 npm run test:desktop:dmg

Installing Locally

npm run dist:mac:dmg
open release/Hermes-0.0.0-arm64.dmg

Drag Hermes to Applications. If testing repeated installs, replace the existing app.

Runtime Bootstrap

Hermes Desktop shares its install layout with the CLI installers (scripts/install.ps1, scripts/install.sh) so a desktop-only user and a CLI-only user end up with the same files in the same places.

Where things live

HERMES_HOME/                       # %LOCALAPPDATA%\hermes (Windows)
                                   # ~/.hermes (macOS / Linux)
├── hermes-agent/                  # ACTIVE_HERMES_ROOT — git checkout
│   ├── .git/                      # canonical install is always a git checkout
│   ├── hermes_cli/, agent/, ...   # Python source
│   ├── pyproject.toml             # source of truth for deps
│   ├── venv/                      # virtualenv (Scripts\python.exe on Windows,
│   │                              #             bin/python elsewhere)
│   └── .hermes-bootstrap-complete # marker: first-launch install.ps1 succeeded
├── git/                           # PortableGit (Windows; installed by install.ps1)
├── config.yaml                    # user config
├── .env                           # API keys
└── logs/
    ├── desktop.log                # Electron-side boot log
    ├── agent.log
    ├── errors.log
    └── gateway.log

The packaged installer ships only the Electron app — Hermes Agent itself is fetched and installed at first launch by running scripts/install.ps1 (Windows) against the git ref baked into the .exe at build time (see apps/desktop/scripts/write-build-stamp.cjs).

Resolution order

The desktop resolves a Hermes backend in this order:

  1. HERMES_DESKTOP_HERMES_ROOT — explicit dev override.
  2. Repo source root — only when running npm run dev from a checkout. Takes precedence over HERMES_HOME/hermes-agent so devs always run their local edits.
  3. HERMES_HOME/hermes-agent if the .hermes-bootstrap-complete marker is present. The marker attests that install.ps1 succeeded and the user finished initial configuration; we trust the install and skip the bootstrap flow on every launch after the first.
  4. Existing hermes CLI on PATH (skipped when HERMES_DESKTOP_IGNORE_EXISTING=1).
  5. Pip-installed hermes_cli module via system Python.
  6. None of the above → bootstrap-needed sentinel. The desktop's first-launch wizard runs scripts/install.ps1 stages, then writes the marker on success.

First-launch flow on a packaged install

  1. resolveHermesBackend() returns kind: 'bootstrap-needed'.
  2. The renderer shows the install overlay; main fetches scripts/install.ps1 from GitHub at the pinned commit (from install-stamp.json).
  3. Main drives install.ps1 -Manifest to get the stage list, then iterates install.ps1 -Stage <name> -NonInteractive -Json with live progress events to the renderer.
  4. On all stages succeeding, main writes .hermes-bootstrap-complete with { schemaVersion, pinnedCommit, pinnedBranch, completedAt, desktopVersion }.
  5. Renderer hands off to the existing onboarding overlay (API key / model / persona).
  6. Subsequent launches see the marker and skip everything in steps 1-5.

Updates

Once bootstrapped, the install is a real git checkout. Updates flow through the in-app update path (applyUpdates()git fetch && git pull --ff-only against the configured branch) or hermes update from the CLI. Both check pyproject.toml drift and re-run pip install -e . only when needed.

A user who installed via scripts/install.ps1 directly (so HERMES_HOME/hermes-agent/.git exists but no .hermes-bootstrap-complete marker) is detected via resolver step 4 (their hermes CLI on PATH) and the desktop reuses their install without re-running the bootstrap.

Debugging

Desktop boot logs are written to:

HERMES_HOME/logs/desktop.log     # %LOCALAPPDATA%\hermes\logs\desktop.log on Windows
                                  # ~/.hermes/logs/desktop.log on macOS / Linux

If the UI reports Desktop boot failed, check that log first. It includes the backend command output and recent Python traceback context.

To force a fresh first-launch bootstrap (rare — useful for development / dogfooding the install flow):

# macOS / Linux
rm "$HOME/.hermes/hermes-agent/.hermes-bootstrap-complete"

# Windows (PowerShell)
Remove-Item "$env:LOCALAPPDATA\hermes\hermes-agent\.hermes-bootstrap-complete"

For a full reset of just the Python venv (rare — usually only needed if the venv is broken):

# macOS / Linux
rm -rf "$HOME/.hermes/hermes-agent/venv"

# Windows (PowerShell)
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\hermes\hermes-agent\venv"

To reset stale macOS microphone permission prompts:

tccutil reset Microphone com.github.Electron
tccutil reset Microphone com.nousresearch.hermes

Verification

Run before handing off installer changes:

npm run fix
npm run type-check
npm run lint
npm run test:desktop:all

Current lint may report existing warnings, but it should exit with no errors.