Hermes Web UI is a TypeScript monorepo that ships a browser dashboard, a Koa backend, and an Electron desktop distribution around Hermes Agent.
| Area | Path | Responsibility |
|---|---|---|
| Client | packages/client/src |
Vue UI, routing, Pinia stores, API wrappers, i18n, browser-visible state. |
| Server | packages/server/src |
HTTP API, auth, Socket.IO, SQLite stores, file access, Hermes runtime integration. |
| Desktop | packages/desktop |
Electron shell, local Web UI server bootstrap, updater, bundled Python/Hermes runtime. |
| Tests | tests |
Vitest unit/integration tests and Playwright browser tests. |
| CI | .github/workflows |
Build, e2e, lockfile, Docker, and desktop release automation. |
Treat these as separate products even when they ship from one monorepo:
Web UI Product— the browser client, the Koa backend, Web UI state, and the in-app update entry points.Hermes Agent Product— the Hermes runtime, profiles, and CLI lifecycle.Device Runtime Product— host-level concerns such as users, Node.js, systemd, sudoers, deploy directories, and health recovery.Release And Distribution Product— npm packages, device packages, manifests, GitHub Releases, OSS objects, and workflow-generated metadata.Desktop Product— the Electron shell, bundled runtime assets, and desktop auto-update flow.
Boundary rules:
- Keep
Web UI Productconcerns out of host bootstrap code. - Keep
Hermes Agent Productlifecycle decisions explicit instead of hiding them behind Web UI update flows. - Keep
Device Runtime Productconcerns in controlled installers or runners, not in ordinary request handlers. - Keep
Release And Distribution Productmetadata as a single contract that runtime code consumes instead of redefining. - Keep the
Desktop Productupdate path separate from device in-app updates.
- The browser loads the Vite-built client from the Koa server.
- Client modules call API helpers from
packages/client/src/api. - Server routes in
packages/server/src/routeswire HTTP paths to controllers. - Controllers validate request concerns and delegate reusable behavior to services.
- Services own side effects: files, SQLite, Hermes profiles, subprocesses, bridges, and credentials.
- Long-running chat and group-chat flows use Socket.IO namespaces managed by server services.
Keep each layer narrow. Routes should not grow business logic, and client code should not duplicate server persistence rules.
- Web UI state defaults to
~/.hermes-web-uithroughconfig.appHome. HERMES_WEB_UI_HOMEandHERMES_WEBUI_STATE_DIRoverride Web UI state location.- Hermes Agent state lives under Hermes profile directories and must stay distinct from Web UI state.
- Uploads default to
config.uploadDir, which is derived from the Web UI home unlessUPLOAD_DIRis set. - Runtime data directories must also live under the Web UI home, not beside built
distassets. - Profile-scoped Hermes data should use existing profile helpers instead of manually joining paths.
routes/registers HTTP and WebSocket entry points.controllers/handles request-level behavior.services/owns reusable IO, domain behavior, external process calls, and integration logic.db/owns SQLite schemas and stores.middleware/owns request middleware such as user auth.shared/contains cross-server constants and helpers.
Architecture rules:
- Register local API routes before proxy catch-all routes.
- Keep auth behavior centralized in
packages/server/src/services/auth.ts. - Prefer
execFileorspawnwith argument arrays over shell command strings. - Use structured file and YAML/JSON parsers when editing structured data.
- Keep update controllers thin: version discovery, plan generation, task orchestration, privileged execution, and runtime reconciliation should not collapse into one file.
- Treat in-app updates as orchestration only. Host bootstrap, privileged install, and repair flows belong behind controlled seams.
views/contains route-level screens.components/contains reusable UI.stores/contains Pinia state.api/contains HTTP clients and should usepackages/client/src/api/client.ts.i18n/contains locale messages for user-facing strings.styles/contains global styling and theme primitives.
Frontend rules:
- Use Vue 3 Composition API with
<script setup lang="ts">. - Use existing Naive UI patterns before adding new UI conventions.
- Add visible text to all locale files.
- Keep component styles scoped unless the style is intentionally global.
Desktop packaging is intentionally split:
- Pull requests run the web UI build and tests in
.github/workflows/build.yml. - Published GitHub Releases run Web UI artifact packaging and Docker image publishing without marking the release as GitHub latest.
- Manual dispatches run full desktop artifact packaging in
.github/workflows/desktop-release.yml. .github/workflows/desktop-manual-build.ymlbuilds one desktop target for targeted repairs or re-runs.- Each release matrix target uploads only the artifact globs for its own platform.
- A successful full desktop release marks the target GitHub Release as latest after all desktop artifacts and the merged macOS updater manifest have been uploaded.
Do not make a Windows job require macOS .dmg files or a Linux job require
Windows installers. Keep fail_on_unmatched_files: true where platform-specific
artifact lists make the expectation explicit.
The repository currently supports multiple update shapes. Keep them distinct:
npm-packageis a distribution shape, not a device-runtime policy.source-deployis a runtime reconciliation path for source-based installs.device-packageis a device-targeted release artifact with its own manifest and checksum contract.bootstrapmeans preparing a host for the first install.runtime reconcilemeans applying a known artifact to an already prepared host and restoring health.Hermes Agent upgradeis a separate lifecycle step, not the default side effect of every Web UI update.
Do not let one script remain the long-term owner of bootstrap, day-two updates, repair, and Hermes Agent upgrade. Those concerns belong to different seams even if they temporarily share implementation. Do not let in-app updates silently expand into host bootstrap or Agent lifecycle changes without an explicit release decision and runtime flag.
For the current device update design and its evolving rules, see:
docs/update-distribution/README.mddocs/update-distribution/10-development-rules.mddocs/update-distribution/11-product-boundaries-and-dependencies.mddocs/update-distribution/12-runtime-module-split-plan.md
The minimum mechanical harness is:
npm run harness:checkfor repository docs, workflow, and package-script invariants.npm run testor focused Vitest tests for local logic.npm run test:e2efor browser-visible routing/auth/chat regressions.npm run buildfor type checking and production bundles.
See docs/harness/validation.md for change-specific commands.