hermes-agent/nix/desktop.nix
ethernet 6079d7dd9d
nix: package apps/desktop as .#desktop (#28964)
Adds nix/desktop.nix building the Electron renderer with buildNpmPackage
and wrapping nixpkgs' electron binary.  Reuses .#default by setting
HERMES_DESKTOP_HERMES to its hermes binary, so the desktop's resolver
picks up the fully-wired nix hermes (venv, bundled skills/plugins,
runtime PATH) without reimplementing agent resolution.

- nix/desktop.nix: renderer + electron wrapper
- nix/hermes-agent.nix: finalAttrs form, exposes hermesDesktop in passthru
- nix/packages.nix: exposes .#desktop + adds to fix-lockfiles
- apps/desktop/package-lock.json: standalone hermetic lockfile

nix build .#desktop && nix run .#desktop both clean.
2026-05-19 18:31:15 -05:00

116 lines
4.5 KiB
Nix

# nix/desktop.nix — Hermes Desktop (Electron) app build + wrapper
#
# `hermesAgent` is the fully-built `.#default` package — it ships the
# `hermes` binary with the venv, runtime PATH, bundled skills/plugins, etc.
# already wired up. We point the desktop at it via the existing
# `HERMES_DESKTOP_HERMES` override env var, so the desktop's resolver
# uses our fully wrapped binary at step 4 ("existing Hermes CLI").
# No reimplementation of the agent resolution in this wrapper.
{ pkgs, lib, stdenv, makeWrapper, hermesNpmLib, electron, hermesAgent, ... }:
let
src = ../apps;
npmDeps = pkgs.fetchNpmDeps {
src = ../apps/desktop;
# buildNpmPackage uses `npm ci` which is strict — peer deps not in the
# lockfile cause network fetch attempts. Fetcher v2 stages the full
# cache (including peer-only deps) so `npm ci` can resolve them offline.
fetcherVersion = 2;
hash = "sha256-7W9ObYz08yDMtybY8+RkUXkKVsJXINLl0qBUB91hpao=";
};
npm = hermesNpmLib.mkNpmPassthru { folder = "apps/desktop"; attr = "desktop"; pname = "hermes-desktop"; };
packageJson = builtins.fromJSON (builtins.readFile (src + "/desktop/package.json"));
version = packageJson.version;
# Build the renderer (dist/ + electron/ + package.json).
renderer = pkgs.buildNpmPackage (npm // {
pname = "hermes-desktop-renderer";
inherit src npmDeps version;
sourceRoot = "apps/desktop";
doCheck = false;
# buildNpmPackage uses `npm ci` which fails on peer deps not in the
# lockfile. npmDepsFetcherVersion=2 stages the full cache (peer deps
# included) so the offline `npm ci` resolves them.
npmDepsFetcherVersion = 2;
# `--ignore-scripts` skips the electron prebuild download (we use nixpkgs
# electron instead). `--legacy-peer-deps` matches the dev workflow —
# apps/desktop has conflicting peer deps (zod, @testing-library) that
# the package.json relies on npm 7+ to relax.
npmFlags = [ "--ignore-scripts" "--legacy-peer-deps" ];
makeCacheWritable = true;
buildPhase = ''
runHook preBuild
# write-build-stamp.cjs replacement. Packaged Electron reads this
# at first-launch to pin the install.ps1 git ref; informational in
# nix builds (the backend comes from the derivation directly).
mkdir -p build
echo '{"schemaVersion":1,"commit":"nix","branch":"nix","dirty":false,"source":"nix"}' > build/install-stamp.json
# The vite config aliases react/react-dom to ../../node_modules/react
# (workspace root, where npm dedups them in dev). In the standalone
# nix build there is no workspace root, so the deps are installed
# locally rewrite the aliases to point at the local copy.
substituteInPlace vite.config.ts \
--replace-quiet '../../node_modules/' './node_modules/'
# vite handles TS transpilation via esbuild no type-checking.
# We skip `tsc -b` to avoid type errors in test files that don't
# ship in the bundle (real upstream peer-dep version mismatches
# in @testing-library/react v16 not blocking the build).
npx vite build --outDir dist
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r dist electron build $out/
cp package.json $out/
runHook postInstall
'';
});
in
# Electron wrapper: nixpkgs' electron binary pointed at the renderer dir.
stdenv.mkDerivation {
pname = "hermes-desktop";
inherit version;
dontUnpack = true;
dontBuild = true;
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
runHook preInstall
mkdir -p $out/share/hermes-desktop $out/bin
cp -r ${renderer}/* $out/share/hermes-desktop/
# Wrap the nixpkgs electron binary to launch our app. Set
# HERMES_DESKTOP_HERMES to the absolute path of the nix-built `hermes`
# binary so the desktop's resolver step 4 ("existing Hermes CLI on
# PATH") uses our fully wrapped binary venv with all deps,
# bundled skills/plugins, runtime PATH (ripgrep/git/ffmpeg/etc).
# No reimplementation of the agent resolver in the wrapper.
makeWrapper ${lib.getExe electron} $out/bin/hermes-desktop \
--add-flags "$out/share/hermes-desktop" \
--set HERMES_DESKTOP_HERMES "${lib.getExe hermesAgent}" \
--set ELECTRON_IS_DEV 0
runHook postInstall
'';
meta = with lib; {
description = "Native Electron desktop shell for Hermes Agent";
homepage = "https://github.com/NousResearch/hermes-agent";
license = licenses.mit;
platforms = platforms.unix;
mainProgram = "hermes-desktop";
};
}