Files
2026-10-17 02:33:02 -04:00

6.1 KiB

Development

How the code is organized is described in architecture.md. This page covers working on it: commands, tests, conventions and CI.

Commands

Command Purpose
npm ci Install the exact locked dependencies.
npm run dev Vite with hot reload on 127.0.0.1:5173 plus the relay on 127.0.0.1:3001 (tsx watch).
npm run dev:full npm run dev plus a local hermes serve audio backend on 127.0.0.1:9119.
npm test All tests once (Vitest).
npx vitest run server/config.test.ts One test file; npx vitest watches.
npm run lint oxlint (.oxlintrc.json).
npm run build Type-check the client (tsc -b) and the server (tsconfig.server.json, strict), then the Vite production bundle in dist/.
npm run verify The release gates: tests, lint, build, the Hermes plugin's Python tests and git whitespace checks (scripts/verify.sh).
npm run doctor Read-only environment checks.
npm run setup:env Create .env from .env.example if it does not exist.

Tests

The suite runs without network access, without Hermes and without binding ports, so it works in restricted CI sandboxes.

  • Relay: real Express routing through an in-memory HTTP harness (server/test-request.ts), with injected fetch, DNS, transports, process runners, filesystems and clocks. It covers credential separation, routing, streaming, timeouts, sanitized errors, the artifact handoff (verification before done, dead-link recovery, reset), tool starts reported only as working, local file discovery (symlink, hidden, size, magic and parent-swap cases), SSRF and DNS rebinding boundaries, cache limits and response headers.
  • Transport: real TLS and HTTP/2 servers on loopback with throwaway certificates generated at test time (server/test-certificate.ts), so no private key is stored in the repository.
  • Poppler: unit tests use fake executables. server/artifact-poppler.test.ts renders a real two-page PDF when pdfinfo and pdftoppm are found the same way the server finds them, and is skipped otherwise.
  • Hermes plugin: hermes-plugins/steam-frame-view/tests are Python unittest tests with a stand-in command runner; run python3 -m unittest discover hermes-plugins/steam-frame-view/tests. They never run SSH.
  • Launchers: injected spawn, kill, ports, probes and fake timers. No test starts Hermes, SSH or a real child process. scripts/hermes-production.test.ts runs python3 against stand-in Hermes modules, so python3 must be on PATH.
  • Browser: relay client, conversation states, SSE parsing and artifact logic as unit tests. The scene runs the real Simulator, panel, grab handles, OrbitControls and the pointer events from @react-three/xr in a real React Three Fiber root (src/simulatorStage.tsx), driven by DOM pointer events and staged XR controller sessions. Only WebGL, the model file, Troika text and the WebXR session are replaced.

WebGL rendering, real microphones, live Hermes integration and headset sessions need manual checks on the target browser or device.

Conventions

  • Test first. Write a failing test that names the behavior, watch it fail for the expected reason, then make it pass. Bug fixes start with a reproducing test.
  • Inject, don't mock globally. Server and launcher code takes its dependencies (fetch, DNS, filesystem, clock, spawn, runners) as options; extend those seams rather than patching modules.
  • Fail closed and stay quiet. Validate shapes and bounds at every boundary, return fixed sanitized messages, and never echo upstream bodies, keys, URLs, tool arguments or paths.
  • Server-only configuration. New settings are read in server/config.ts (or the launchers), documented in .env.example and configuration.md, and checked by npm run doctor when they can be misconfigured.
  • Synthetic fixtures only. Use example.com and other reserved names, the documentation address ranges 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 and 2001:db8::/32, paths such as /home/example or /srv/realhermes, and invented names (the tests use a fictional mascot, "Numbix"). Never commit real hostnames, addresses, user names, paths, keys or captured frames. The SSRF tests deliberately list private and reserved ranges (10.0.0.0/8, 192.168.0.0/16 and so on) as addresses that must be refused.
  • Keep docs in step. User-visible behavior changes update the relevant page under docs/.

Release gate

scripts/verify.sh is the single gate: run npm ci, then npm run verify. The script runs the tests, lint, build and the plugin's Python tests. It then checks every committed file for whitespace errors against the empty tree (git diff --check), plus any unstaged or staged changes. It needs Node.js, npm, git, python3, and Poppler for the real PDF test. There is no hosted CI; run it locally before publishing.

Running the gate in a Linux container

With Docker, from a committed checkout:

docker run --rm -v "$PWD:/src:ro" node:22-bookworm sh -c '
  git config --global --add safe.directory /src/.git &&
  git clone -q /src /work && cd /work &&
  apt-get update -qq && apt-get install -y -qq --no-install-recommends poppler-utils >/dev/null &&
  npm ci --no-audit --no-fund && npm run verify'

This clones only committed files into the container, so local node_modules, .env and other ignored files never enter it.

Release checklist

Before publishing a release candidate:

  1. npm ci && npm run verify passes (and the container recipe above, for Linux).
  2. npm run doctor gives no failures on a prepared machine.
  3. No secrets or private data are in tracked files. Review git ls-files, and search them for credentials, private keys, personal paths, private hostnames and addresses outside the documented test ranges.
  4. No generated or local files are tracked: dist/, node_modules/, .env, captures, caches and logs are all git-ignored.
  5. public/models/realhermes-idle.glb is present and loads in the desktop simulator.
  6. Documentation links resolve, and changed behavior is reflected in docs/.