From 81b95b78e5364b4c3f5880b24fea714b9563d9a7 Mon Sep 17 00:00:00 2001 From: Chiye Ma Date: Tue, 14 Apr 2026 00:14:15 -0700 Subject: [PATCH] fix(nix): include web dashboard dependencies and frontend in Nix build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues prevented 'hermes dashboard' from working in the Nix package: 1. uv.lock was missing the [web] extra — fastapi and uvicorn were only resolved under the [rl] marker, so the [all] extra didn't include them. Regenerated with 'uv lock' to fix the root cause. 2. nix/packages.nix didn't build or bundle the Vite/React frontend. Added buildNpmPackage derivation for the frontend and a site-packages overlay with web_dist symlinked into hermes_cli, prepended via PYTHONPATH in the wrapper scripts. No application code changes needed. Tested on x86_64-linux (NixOS Proxmox LXC): - nix build succeeds with fastapi 0.133.1 and uvicorn 0.41.0 in the venv - web_dist is built and symlinked into the hermes_cli overlay - 'hermes dashboard --host 0.0.0.0 --port 9119 --no-open' starts successfully as a systemd service, returns HTTP 200, and serves the React frontend on LAN See also #9307 for an alternative approach. --- nix/packages.nix | 35 ++++++++++++++++++++++++++++++++++- uv.lock | 13 +++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/nix/packages.nix b/nix/packages.nix index eb50d4a17b34..4c467b56ee22 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -13,6 +13,38 @@ !(pkgs.lib.hasInfix "/index-cache/" path); }; + # Build the Vite/React web dashboard frontend. + # Output lands in hermes_cli/web_dist which the FastAPI server serves + # as a static SPA (see hermes_cli/web_server.py:WEB_DIST). + hermesWeb = pkgs.buildNpmPackage { + pname = "hermes-web"; + version = (builtins.fromTOML (builtins.readFile ../pyproject.toml)).project.version; + src = ../web; + npmDepsHash = "sha256-Y0pOzdFG8BLjfvCLmsvqYpjxFjAQabXp1i7X9W/cCU4="; + # vite build writes to ../hermes_cli/web_dist relative to web/; + # redirect into $out instead. + postPatch = '' + substituteInPlace vite.config.ts \ + --replace-warn '../hermes_cli/web_dist' "$out" + ''; + installPhase = '' + runHook preInstall + npm run build + runHook postInstall + ''; + dontNpmBuild = true; # we run the build in installPhase after substituteInPlace + }; + + # The venv's hermes_cli is in the read-only Nix store and lacks web_dist. + # Create a thin overlay directory that shadows hermes_cli with web_dist + # present, then prepend it via PYTHONPATH so Python finds it first. + hermesSiteOverlay = pkgs.runCommand "hermes-site-overlay" { } '' + venvSite=$(echo ${hermesVenv}/lib/python3.*/site-packages) + mkdir -p $out/lib/hermes-overlay + cp -rL --no-preserve=mode $venvSite/hermes_cli $out/lib/hermes-overlay/hermes_cli + ln -s ${hermesWeb} $out/lib/hermes-overlay/hermes_cli/web_dist + ''; + runtimeDeps = with pkgs; [ nodejs_20 ripgrep git openssh ffmpeg tirith ]; @@ -36,7 +68,8 @@ ${pkgs.lib.concatMapStringsSep "\n" (name: '' makeWrapper ${hermesVenv}/bin/${name} $out/bin/${name} \ --suffix PATH : "${runtimePath}" \ - --set HERMES_BUNDLED_SKILLS $out/share/hermes-agent/skills + --set HERMES_BUNDLED_SKILLS $out/share/hermes-agent/skills \ + --prefix PYTHONPATH : "${hermesSiteOverlay}/lib/hermes-overlay" '') [ "hermes" "hermes-agent" "hermes-acp" ]} runHook postInstall diff --git a/uv.lock b/uv.lock index 45efc2d93ff6..88b03e6564a1 100644 --- a/uv.lock +++ b/uv.lock @@ -1699,7 +1699,7 @@ wheels = [ [[package]] name = "hermes-agent" -version = "0.8.0" +version = "0.9.0" source = { editable = "." } dependencies = [ { name = "anthropic" }, @@ -1737,6 +1737,7 @@ all = [ { name = "dingtalk-stream" }, { name = "discord-py", extra = ["voice"] }, { name = "elevenlabs" }, + { name = "fastapi" }, { name = "faster-whisper" }, { name = "honcho-ai" }, { name = "lark-oapi" }, @@ -1756,6 +1757,7 @@ all = [ { name = "slack-bolt" }, { name = "slack-sdk" }, { name = "sounddevice" }, + { name = "uvicorn", extra = ["standard"] }, ] cli = [ { name = "simple-term-menu" }, @@ -1842,6 +1844,10 @@ voice = [ { name = "numpy" }, { name = "sounddevice" }, ] +web = [ + { name = "fastapi" }, + { name = "uvicorn", extra = ["standard"] }, +] yc-bench = [ { name = "yc-bench", marker = "python_full_version >= '3.12'" }, ] @@ -1866,6 +1872,7 @@ requires-dist = [ { name = "exa-py", specifier = ">=2.9.0,<3" }, { name = "fal-client", specifier = ">=0.13.1,<1" }, { name = "fastapi", marker = "extra == 'rl'", specifier = ">=0.104.0,<1" }, + { name = "fastapi", marker = "extra == 'web'", specifier = ">=0.104.0,<1" }, { name = "faster-whisper", marker = "extra == 'voice'", specifier = ">=1.0.0,<2" }, { name = "fire", specifier = ">=0.7.1,<1" }, { name = "firecrawl-py", specifier = ">=4.16.0,<5" }, @@ -1894,6 +1901,7 @@ requires-dist = [ { name = "hermes-agent", extras = ["sms"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["tts-premium"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["voice"], marker = "extra == 'all'" }, + { name = "hermes-agent", extras = ["web"], marker = "extra == 'all'" }, { name = "honcho-ai", marker = "extra == 'honcho'", specifier = ">=2.0.1,<3" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<1" }, { name = "jinja2", specifier = ">=3.1.5,<4" }, @@ -1929,10 +1937,11 @@ requires-dist = [ { name = "tenacity", specifier = ">=9.1.4,<10" }, { name = "tinker", marker = "extra == 'rl'", git = "https://github.com/thinking-machines-lab/tinker.git" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'rl'", specifier = ">=0.24.0,<1" }, + { name = "uvicorn", extras = ["standard"], marker = "extra == 'web'", specifier = ">=0.24.0,<1" }, { name = "wandb", marker = "extra == 'rl'", specifier = ">=0.15.0,<1" }, { name = "yc-bench", marker = "python_full_version >= '3.12' and extra == 'yc-bench'", git = "https://github.com/collinear-ai/yc-bench.git" }, ] -provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "termux", "dingtalk", "feishu", "rl", "yc-bench", "all"] +provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "termux", "dingtalk", "feishu", "web", "rl", "yc-bench", "all"] [[package]] name = "hf-transfer"