diff --git a/home/common.nix b/home/common.nix index 1edd08e..1f93c52 100644 --- a/home/common.nix +++ b/home/common.nix @@ -21,6 +21,7 @@ ./mise.nix ./migration.nix ./nvim.nix + ./pi.nix ./prompt.nix ./skills.nix ./scripts.nix diff --git a/home/pi.nix b/home/pi.nix new file mode 100644 index 0000000..59498b3 --- /dev/null +++ b/home/pi.nix @@ -0,0 +1,47 @@ +{ + lib, + pkgs, + hostConfig, + ... +}: +lib.mkIf hostConfig.isLinux { + # Install pi-coding-agent globally via npm at activation time. + home.activation.installPiAgent = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + export PATH="${ + lib.makeBinPath [ + pkgs.nodejs_22 + pkgs.coreutils + ] + }:$PATH" + + npm_prefix="$(npm prefix -g 2>/dev/null)" + pkg_dir="$npm_prefix/lib/node_modules/@mariozechner/pi-coding-agent" + + if [ ! -d "$pkg_dir" ]; then + npm install -g @mariozechner/pi-coding-agent 2>/dev/null || true + fi + ''; + + # Install Pi extensions at activation time: + # - @e9n/pi-channels: Telegram/Slack bridge with RPC-based persistent sessions + # - pi-schedule-prompt: cron/interval scheduled prompts + # - pi-subagents: background task delegation with async execution + home.activation.installPiExtensions = lib.hm.dag.entryAfter [ "installPiAgent" ] '' + export PATH="${ + lib.makeBinPath [ + pkgs.nodejs_22 + pkgs.coreutils + pkgs.git + ] + }:$PATH" + + npm_prefix="$(npm prefix -g 2>/dev/null)" + pi_bin="$npm_prefix/bin/pi" + + if [ -x "$pi_bin" ]; then + for pkg in "@e9n/pi-channels" "pi-schedule-prompt" "pi-subagents"; do + "$pi_bin" install "npm:$pkg" 2>/dev/null || true + done + fi + ''; +} diff --git a/hosts/netty/configuration.nix b/hosts/netty/configuration.nix index 86803d0..f3a3cf5 100644 --- a/hosts/netty/configuration.nix +++ b/hosts/netty/configuration.nix @@ -18,6 +18,7 @@ in ./vaultwarden.nix ./forgejo.nix ./betternas.nix + ./pi-agent.nix ../../modules/base.nix (modulesPath + "/profiles/minimal.nix") (modulesPath + "/profiles/headless.nix") diff --git a/hosts/netty/pi-agent.nix b/hosts/netty/pi-agent.nix new file mode 100644 index 0000000..3802dfa --- /dev/null +++ b/hosts/netty/pi-agent.nix @@ -0,0 +1,76 @@ +{ + pkgs, + username, + ... +}: +let + piAgentEnvFile = "/var/lib/pi-agent/pi-agent.env"; + piAgentEnvCheck = pkgs.writeShellScript "pi-agent-env-check" '' + [ -f "${piAgentEnvFile}" ] + ''; + + # Wrapper that exec's pi inside tmux's foreground process so systemd + # tracks the actual PID. When pi dies, tmux exits, systemd sees it + # and triggers Restart=on-failure. + piAgentStart = pkgs.writeShellScript "start-pi-agent" '' + export PATH="${pkgs.nodejs_22}/bin:$PATH" + npm_prefix="$(npm prefix -g 2>/dev/null)" + pi_bin="$npm_prefix/bin/pi" + + if [ ! -x "$pi_bin" ]; then + echo "pi binary not found at $pi_bin" >&2 + exit 1 + fi + + # tmux runs in the foreground (-D) so systemd tracks this process. + # The inner shell exec's pi so the tmux pane PID *is* the pi PID. + exec ${pkgs.tmux}/bin/tmux new-session -D -s pi-agent \ + "exec $pi_bin --chat-bridge" + ''; +in +{ + # Ensure state directory and env file have correct permissions. + systemd.tmpfiles.rules = [ + "d /var/lib/pi-agent 0750 ${username} users -" + "z ${piAgentEnvFile} 0600 ${username} users -" + ]; + + # Pi agent running 24/7 in a foreground tmux session. + # Extensions (pi-channels, pi-schedule-prompt, pi-subagents) load + # inside Pi's process and handle Telegram bridging, scheduled tasks, + # and background subagent delegation. + # + # The --chat-bridge flag auto-enables the Telegram bridge on startup. + # Telegram bot token lives in ~/.pi/agent/settings.json (see pi-channels docs). + # ANTHROPIC_API_KEY comes from the env file. + # + # tmux session name: pi-agent + # Attach for debugging: tmux attach -t pi-agent + systemd.services.pi-agent = { + description = "Pi Coding Agent (24/7)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ + pkgs.nodejs_22 + pkgs.git + pkgs.tmux + pkgs.coreutils + ]; + environment = { + HOME = "/home/${username}"; + NODE_NO_WARNINGS = "1"; + }; + serviceConfig = { + Type = "simple"; + User = username; + Group = "users"; + WorkingDirectory = "/home/${username}"; + ExecCondition = piAgentEnvCheck; + EnvironmentFile = piAgentEnvFile; + ExecStart = piAgentStart; + Restart = "on-failure"; + RestartSec = 10; + }; + }; +}