{ config, pkgs, lib, ... }:
let
inherit (config.home) homeDirectory;
nodejs = pkgs.nodejs_22;
# NOT ~/.pi-web: that is xing-shuyin's default and upstream's default too.
dataDir = "${homeDirectory}/.local/state/pi-web";
port = 8504;
pi-web = pkgs.buildNpmPackage {
pname = "pi-web";
version = "1.202609.0";
# Last release whose peer range (>=0.84.0 <0.85.0 || >=0.85.1) accepts pi 0.86.1.
# v1.202609.1 tightened to >=0.87.0.
src = pkgs.npinsed.ai.pi.pi-web-jmfederico;
# Upstream's lock omits `integrity` for the 5 nested @earendil-works/* deps of
# pi-coding-agent; prefetch-npm-deps panics on that. Add the fields.
patches = [ ./lock-integrity.patch ];
npmDepsFetcherVersion = 2;
npmDepsHash = "sha256-pMp+KK0yf3ua0q6Fzn5x8LD/x++0wECL8YWes3aMKrQ=";
inherit nodejs;
nativeBuildInputs = [ pkgs.makeWrapper ];
installPhase = ''
runHook preInstall
mkdir -p $out/lib/node_modules/pi-web $out/bin
cp -r dist node_modules package.json $out/lib/node_modules/pi-web/
# npm workspace symlinks point into the relocated build tree; drop dangling ones.
find $out/lib/node_modules/pi-web/node_modules -type l ! -exec test -e {} \; -delete 2>/dev/null || true
makeWrapper ${nodejs}/bin/node $out/bin/pi-web --add-flags "$out/lib/node_modules/pi-web/dist/cli.js"
makeWrapper ${nodejs}/bin/node $out/bin/pi-web-server --add-flags "$out/lib/node_modules/pi-web/dist/server/index.js"
makeWrapper ${nodejs}/bin/node $out/bin/pi-web-sessiond --add-flags "$out/lib/node_modules/pi-web/dist/server/sessiond.js"
runHook postInstall
'';
};
in
{
home.packages = [ pi-web ];
# Keep the daemon from mutating the (yq-merge-managed) ~/.pi/agent/settings.json:
# at startup it auto-installs its shipped `@jmfederico/pi-relay` Pi package into the
# active profile, writing a Nix-store path there. A dismissal entry disables that
# for this profile; the package can still be installed by hand from Settings.
home.file.".local/state/pi-web/pi-package-dismissals.json".text =
''{"dismissals":[{"profileDir":"${homeDirectory}/.pi/agent","packageId":"@jmfederico/pi-relay","dismissedAt":"1970-01-01T00:00:00.000Z"}]}'';
# Web/API restarts do not interrupt in-flight work; the daemon owns the runtimes.
systemd.user.services.pi-web-sessiond = {
Unit = {
Description = "pi-web session daemon (owns Pi session runtimes)";
After = [ "network.target" ];
};
Install.WantedBy = [ "default.target" ];
Service = {
ExecStart = "${pi-web}/bin/pi-web-sessiond";
Environment = [
"PI_WEB_DATA_DIR=${dataDir}"
];
# No PATH override: inherit the manager PATH (50-systemd-path.conf) so the
# embedded terminal gets ~/.nix-profile/bin (pi, node, git, coreutils...).
Restart = "on-failure";
RestartSec = 5;
};
};
systemd.user.services.pi-web-web = {
Unit = {
Description = "pi-web web/API server";
After = [ "pi-web-sessiond.service" ];
Requires = [ "pi-web-sessiond.service" ];
};
Install.WantedBy = [ "default.target" ];
Service = {
ExecStart = "${pi-web}/bin/pi-web-server";
Environment = [
"PI_WEB_PORT=${toString port}"
"PI_WEB_DATA_DIR=${dataDir}"
];
Restart = "on-failure";
RestartSec = 5;
};
};
}