fix(pm): make bootstrap and bundle ownership explicit

Finish bootstrap uv before PM replaces its store entry. Keep failure
receipts stdlib-only and align the cryptography requirement and override
with the locked version.

Let bundle builders declare launch paths and update ownership. Remove
payload discovery, Store probing, and the unused develop command.
Derive Nix Python from the PM lock and share its provenance stamp.

Document setup, activation, optional dependencies, and distribution
ownership. Targeted Windows tests, relocated runtime launches, Electron
bundling, and bilingual docs builds pass. Native Nix and signed-package
acceptance remain CI gates.
This commit is contained in:
ethernet
2026-09-08 00:24:51 -04:00
parent 6590ecdc2d
commit 712734436e
130 changed files with 2997 additions and 2716 deletions

View File

@@ -34,109 +34,112 @@ We value contributions in this order:
| Requirement | Notes |
| -------------------- | --------------------------------------------------------------------------------------------- |
| **Git** | With the `git-lfs` extension installed |
| **Python 3.14** | uv will install it if missing |
| **Python 3.14** | The project requires `>=3.14,<3.15`. PM provides the pinned interpreter. |
| **uv** | Fast Python package manager ([install](https://docs.astral.sh/uv/)) |
| **Node.js 26+** | Optional — needed for browser tools and WhatsApp bridge (matches root `package.json` engines) |
| **Node.js** | Use the PM pin or a version accepted by root `package.json` engines |
### Install with the standard installer
### PM developer environment
For most contributors, the best development bootstrap is the same path users
take: run the standard installer, then work inside the repository it cloned.
The installer creates the Hermes venv, wires the `hermes` command, stamps the
install method for `hermes update`, and clones the full git project into
`$HERMES_HOME/hermes-agent` (usually `~/.hermes/hermes-agent`). That keeps your
development environment on the same layout the CLI, updater, lazy dependency
installer, gateway, and docs assume.
Use the [PM developer workflow](/reference/package-management#developer-workflow) for preparation, activation, everyday commands,
dependency changes, and test environments. Select your development
home before setup so experimental code does not migrate production data.
After successful setup, activate from the repository root in each new shell.
Bash:
```bash
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
cd "${HERMES_HOME:-$HOME/.hermes}/hermes-agent"
# Add dev/test extras on top of the standard install.
uv pip install -e ".[all,dev]"
# Optional: browser tools / docs site dependencies.
npm install
source ./activate
python hermes --version
```
After that, create branches and run tests from that checkout:
PowerShell:
```powershell
. .\activate.ps1
python hermes --version
```
Run `python hermes` for this checkout, not a global `hermes` alias. PM activation
adds installed tools and the selected dependency tree. It does not install
packages or JS workspaces. `deactivate` restores the prior shell environment.
### Manual development and test environment {#manual-development-and-test-environment}
Use Python 3.14 (`>=3.14,<3.15`). Keep a development environment outside the
source tree if an agent will operate on that checkout. Leave PM activation
before this sequence. Keep the same development `HERMES_HOME` when running it.
This environment is for tests and editor tools. Its own interpreter includes
pytest without relying on PM's `PYTHONPATH`. On Windows, initialize the native
C++ build environment for your architecture before building source dependencies.
POSIX:
```bash
git checkout -b fix/description
scripts/run_tests.sh
uv venv "$HOME/.hermes/venvs/hermes-dev" --python 3.14
export UV_PROJECT_ENVIRONMENT="$HOME/.hermes/venvs/hermes-dev"
uv sync --locked --extra all --extra dev
export HERMES_PYTHON="$UV_PROJECT_ENVIRONMENT/bin/python"
"$HERMES_PYTHON" hermes --version
```
You can also run a fully isolated Hermes instance (throwaway HERMES_HOME, separate Electron
userData, distinct Electron app name to avoid the single-instance lock):
PowerShell:
```powershell
$devEnv = Join-Path $env:LOCALAPPDATA 'hermes-dev-env'
uv venv $devEnv --python 3.14
$env:UV_PROJECT_ENVIRONMENT = $devEnv
uv sync --locked --extra all --extra dev
$env:HERMES_PYTHON = Join-Path $devEnv 'Scripts/python.exe'
& $env:HERMES_PYTHON hermes --version
```
This environment is for source development and tests. It does not replace
PM's tool store or a packaged app's dependency selection.
Run `uv pip check --python` with this environment's interpreter to check its dependencies.
Do not install into an MSIX payload or point a bundled app at this environment.
For an isolated development instance, select a disposable `HERMES_HOME` before
starting the source command. Use `python hermes setup` to configure it rather
than copying production credentials into the checkout.
### JavaScript workspaces and website
From the repository root, run `npm ci` for the desktop, TUI, dashboard, and
shared JS workspaces. The website is separate:
```bash
scripts/dev-sandbox.sh python -m hermes_cli.main
scripts/dev-sandbox.sh --persistent python -m hermes_cli.main desktop # state survives restarts, but lives in the worktree :)
npm ci --prefix website
npm run build:fast --prefix website
```
### Manual clone fallback
Use a Node/npm version accepted by the corresponding `package.json` engines.
Native desktop dependencies can also require the platform build toolchain.
Use this only if you intentionally do not want Hermes' managed install layout
(for example, a throwaway clone inside a container or CI job). If you install
this way, make sure you run the `hermes` entrypoint from this venv; running the
system `python3 -m hermes_cli.main` can pick up unrelated system Python
packages.
Logos and icons are generated from `assets/nous-girl-*.svg` and
`assets/backgrounds/`. `node scripts/generate-icons.mjs` uses the locked,
isolated `icon-build` dependency group. Do not commit generated PNG/ICO/ICNS
outputs or add icon renderers to production dependencies.
Create the venv **outside** the cloned source tree. A venv that lives inside
the directory the agent operates from can be wiped by a relative-path command
the agent runs against its own checkout (`rm -rf venv`, `uv venv venv`, etc.),
which silently destroys the running runtime mid-session. Keeping it outside the
tree means no relative path from the workspace resolves to it.
### Run tests
```bash
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent
# Create venv with Python 3.14, OUTSIDE the source tree
uv venv ~/.hermes/venvs/hermes-dev --python 3.14
export VIRTUAL_ENV="$HOME/.hermes/venvs/hermes-dev"
export PATH="$VIRTUAL_ENV/bin:$PATH"
# Install with all extras (messaging, cron, CLI menus, dev tools)
uv pip install -e ".[all,dev]"
# Optional: browser tools
npm install
```
### Configure for Development
```bash
mkdir -p ~/.hermes/{cron,sessions,logs,memories,skills}
cp cli-config.yaml.example ~/.hermes/config.yaml
touch ~/.hermes/.env
# Add at minimum an LLM provider key:
echo 'OPENROUTER_API_KEY=sk-or-v1-your-key' >> ~/.hermes/.env
```
### Run
```bash
# The standard installer already put `hermes` on PATH.
hermes doctor
hermes chat -q "Hello"
```
If you used the manual clone fallback, run `./hermes` from the checkout or
symlink this clone's venv explicitly:
```bash
mkdir -p ~/.local/bin
ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes
```
### Run Tests
Use the canonical runner on every host:
```bash
scripts/run_tests.sh
scripts/run_tests.sh tests/agent/ -v
```
On Windows, run the script through Bash. When no local `.venv` or `venv`
contains pytest, the runner accepts the explicit `HERMES_PYTHON` above. It
clears credentials, isolates `HERMES_HOME`, and runs each test file in a separate
subprocess through `scripts/run_tests_parallel.py`. It does not use xdist.
Run the relevant JS workspace checks for JS changes. Native install/update
E2E runs on disposable CI hosts, never against the developer's live app.
See [Package management](../reference/package-management.md) for PM commands and runtime ownership.
## Code Style
- **PEP 8** with practical exceptions (no strict line length enforcement)
@@ -147,14 +150,14 @@ scripts/run_tests.sh
## Cross-Platform Compatibility
See **[Platform Support](../getting-started/platform-support.md)**. Native Windows uses Git Bash (from [Git for Windows](https://git-scm.com/download/win)) for shell commands. A few features require POSIX kernel primitives and are gated: the dashboard's embedded PTY terminal pane (`/chat` tab) needs a POSIX PTY (Linux, macOS, or WSL2). If you're doing Windows-heavy dev, run the Windows-footgun lint (`scripts/check-windows-footguns.py`) before pushing.
See **[Platform Support](../getting-started/platform-support.md)**. Native Windows uses Git Bash (from [Git for Windows](https://git-scm.com/download/win)) for shell commands. The dashboard uses POSIX PTYs on Unix and the `pywinpty`/ConPTY bridge on Windows. Availability depends on that host's native dependency support. If you're doing Windows-heavy dev, run the Windows-footgun lint (`scripts/check-windows-footguns.py`) before pushing.
When contributing code, keep these rules in mind:
- **Don't add unguarded `signal.SIGKILL` references.** It's not defined on Windows. Either route through `gateway.status.terminate_pid(pid, force=True)` (the centralized primitive that does `taskkill /T /F` on Windows and SIGKILL on POSIX), or fall back with `getattr(signal, "SIGKILL", signal.SIGTERM)`.
- **Catch `OSError` alongside `ProcessLookupError` on `os.kill(pid, 0)` probes.** Windows raises `OSError` (WinError 87, "parameter is incorrect") for an already-gone PID instead of `ProcessLookupError`.
- **Use `psutil.pid_exists()` for process liveness.** Do not use `os.kill(pid, 0)` on Windows; it is not a safe probe.
- **Don't force the terminal to POSIX semantics.** `os.setsid`, `os.killpg`, `os.getpgid`, `os.fork` all raise on Windows — gate them with `if sys.platform != "win32":` or `if os.name != "nt":`.
- **Open files with an explicit `encoding="utf-8"`.** The Python default on Windows is the system locale (often cp1252), which mojibakes or crashes on non-Latin text.
- **Use explicit text encodings.** User-authored UTF-8 reads use `utf-8-sig` to accept a leading BOM. Writes use `utf-8` without adding a BOM.
- **Use `pathlib.Path` / `os.path.join` — never manually concat with `/`.** This matters less for strings the OS gives us back and more for strings we construct to hand to subprocesses.
Key patterns: