diff --git a/hermes_cli/main.py b/hermes_cli/main.py index a13a6f88e..ce02c2e72 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -6229,8 +6229,9 @@ def cmd_dashboard(args): print(f"Install them with: {sys.executable} -m pip install 'fastapi' 'uvicorn[standard]'") sys.exit(1) - if not _build_web_ui(PROJECT_ROOT / "web", fatal=True): - sys.exit(1) + if "HERMES_WEB_DIST" not in os.environ: + if not _build_web_ui(PROJECT_ROOT / "web", fatal=True): + sys.exit(1) from hermes_cli.web_server import start_server diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 0d0dc4a66..110b81e4b 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -59,7 +59,7 @@ except ImportError: f"Install with: {sys.executable} -m pip install 'fastapi' 'uvicorn[standard]'" ) -WEB_DIST = Path(__file__).parent / "web_dist" +WEB_DIST = Path(os.environ["HERMES_WEB_DIST"]) if "HERMES_WEB_DIST" in os.environ else Path(__file__).parent / "web_dist" _log = logging.getLogger(__name__) app = FastAPI(title="Hermes Agent", version=__version__) diff --git a/nix/packages.nix b/nix/packages.nix index 968ad12fb..94e84af6d 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -18,6 +18,10 @@ filter = path: _type: !(pkgs.lib.hasInfix "/index-cache/" path); }; + hermesWeb = pkgs.callPackage ./web.nix { + npm-lockfile-fix = inputs'.npm-lockfile-fix.packages.default; + }; + runtimeDeps = with pkgs; [ nodejs_22 ripgrep @@ -52,6 +56,7 @@ mkdir -p $out/share/hermes-agent $out/bin cp -r ${bundledSkills} $out/share/hermes-agent/skills + cp -r ${hermesWeb} $out/share/hermes-agent/web_dist # copy pre-built TUI (same layout as dev: ui-tui/dist/ + node_modules/) mkdir -p $out/ui-tui @@ -62,6 +67,7 @@ makeWrapper ${hermesVenv}/bin/${name} $out/bin/${name} \ --suffix PATH : "${runtimePath}" \ --set HERMES_BUNDLED_SKILLS $out/share/hermes-agent/skills \ + --set HERMES_WEB_DIST $out/share/hermes-agent/web_dist \ --set HERMES_TUI_DIR $out/ui-tui \ --set HERMES_PYTHON ${hermesVenv}/bin/python3 \ --set HERMES_NODE ${pkgs.nodejs_22}/bin/node @@ -104,6 +110,7 @@ }; tui = hermesTui; + web = hermesWeb; }; }; } diff --git a/nix/web.nix b/nix/web.nix new file mode 100644 index 000000000..247889753 --- /dev/null +++ b/nix/web.nix @@ -0,0 +1,63 @@ +# nix/web.nix — Hermes Web Dashboard (Vite/React) frontend build +{ pkgs, npm-lockfile-fix, ... }: +let + src = ../web; + npmDeps = pkgs.fetchNpmDeps { + inherit src; + hash = "sha256-Y0pOzdFG8BLjfvCLmsvqYpjxFjAQabXp1i7X9W/cCU4="; + }; + + npmLockHash = builtins.hashString "sha256" (builtins.readFile ../web/package-lock.json); +in +pkgs.buildNpmPackage { + pname = "hermes-web"; + version = "0.0.0"; + inherit src npmDeps; + + doCheck = false; + + buildPhase = '' + npx tsc -b + npx vite build --outDir dist + ''; + + installPhase = '' + runHook preInstall + cp -r dist $out + runHook postInstall + ''; + + nativeBuildInputs = [ + (pkgs.writeShellScriptBin "update_web_lockfile" '' + set -euox pipefail + + REPO_ROOT=$(git rev-parse --show-toplevel) + + cd "$REPO_ROOT/web" + rm -rf node_modules/ + npm cache clean --force + CI=true npm install + ${pkgs.lib.getExe npm-lockfile-fix} ./package-lock.json + + NIX_FILE="$REPO_ROOT/nix/web.nix" + sed -i "s/hash = \"[^\"]*\";/hash = \"\";/" $NIX_FILE + NIX_OUTPUT=$(nix build .#web 2>&1 || true) + NEW_HASH=$(echo "$NIX_OUTPUT" | grep 'got:' | awk '{print $2}') + echo got new hash $NEW_HASH + sed -i "s|hash = \"[^\"]*\";|hash = \"$NEW_HASH\";|" $NIX_FILE + nix build .#web + echo "Updated npm hash in $NIX_FILE to $NEW_HASH" + '') + ]; + + passthru.devShellHook = '' + STAMP=".nix-stamps/hermes-web" + STAMP_VALUE="${npmLockHash}" + if [ ! -f "$STAMP" ] || [ "$(cat "$STAMP")" != "$STAMP_VALUE" ]; then + echo "hermes-web: installing npm dependencies..." + cd web && CI=true npm install --silent --no-fund --no-audit 2>/dev/null && cd .. + mkdir -p .nix-stamps + echo "$STAMP_VALUE" > "$STAMP" + fi + ''; +}