mirror of
https://github.com/nix-community/home-manager.git
synced 2026-06-05 21:02:51 +00:00
pi-coding-agent: add module
This commit is contained in:
committed by
Austin Horstman
parent
5fadbec07a
commit
37e8eef933
15
modules/misc/news/2026/06/2026-06-02_12-00-00.nix
Normal file
15
modules/misc/news/2026/06/2026-06-02_12-00-00.nix
Normal file
@@ -0,0 +1,15 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
time = "2026-06-02T12:00:00+00:00";
|
||||
condition = config.programs.pi-coding-agent.enable;
|
||||
message = ''
|
||||
A new module is available: `programs.pi-coding-agent`.
|
||||
|
||||
The module supports declarative configuration of Pi Coding Agent
|
||||
including settings, keybindings, models, and global context
|
||||
(AGENTS.md).
|
||||
|
||||
For MCP server support, use the existing `programs.mcp` module
|
||||
which writes to `~/.config/mcp/mcp.json`.
|
||||
'';
|
||||
}
|
||||
223
modules/programs/pi-coding-agent.nix
Normal file
223
modules/programs/pi-coding-agent.nix
Normal file
@@ -0,0 +1,223 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
literalExpression
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
mkPackageOption
|
||||
;
|
||||
|
||||
cfg = config.programs.pi-coding-agent;
|
||||
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
|
||||
isPathLike =
|
||||
content:
|
||||
lib.isPath content
|
||||
|| (builtins.isString content && lib.hasPrefix "${builtins.storeDir}/" content)
|
||||
|| lib.isDerivation content;
|
||||
|
||||
upstreamConfigDir = "${config.home.homeDirectory}/.pi/agent";
|
||||
|
||||
packageWithExtraPackages =
|
||||
if cfg.package != null && cfg.extraPackages != [ ] then
|
||||
pkgs.symlinkJoin {
|
||||
inherit (cfg.package) meta;
|
||||
name = "${lib.getName cfg.package}-wrapped-${lib.getVersion cfg.package}";
|
||||
paths = [ cfg.package ];
|
||||
preferLocalBuild = true;
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
postBuild = ''
|
||||
wrapProgram $out/bin/pi \
|
||||
--suffix PATH : ${lib.makeBinPath cfg.extraPackages}
|
||||
'';
|
||||
}
|
||||
else
|
||||
cfg.package;
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.hm.maintainers; [ semi710 ];
|
||||
|
||||
options.programs.pi-coding-agent = {
|
||||
enable = mkEnableOption "pi-coding-agent";
|
||||
|
||||
package = mkPackageOption pkgs "pi-coding-agent" { nullable = true; };
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = with lib.types; listOf package;
|
||||
default = [ ];
|
||||
example = literalExpression "[ pkgs.nodejs pkgs.bun ]";
|
||||
description = ''
|
||||
Extra packages available to Pi Coding Agent.
|
||||
These are added to the PATH of the wrapped pi binary.
|
||||
|
||||
Needed for packages installed by pi (e.g.
|
||||
{command}`npm:@termdraw/pi` requires {command}`npm` and
|
||||
{command}`bun`).
|
||||
'';
|
||||
};
|
||||
|
||||
configDir = mkOption {
|
||||
type = lib.types.str;
|
||||
default = upstreamConfigDir;
|
||||
defaultText = literalExpression ''"''${config.home.homeDirectory}/.pi/agent"'';
|
||||
example = literalExpression ''"''${config.xdg.configHome}/pi/agent"'';
|
||||
description = ''
|
||||
Directory holding Pi Coding Agent's configuration files.
|
||||
|
||||
Defaults to {file}`~/.pi/agent`, matching the upstream
|
||||
{command}`pi` CLI default. The {env}`PI_CODING_AGENT_DIR`
|
||||
environment variable is exported automatically whenever the
|
||||
directory differs from this default so the CLI reads
|
||||
configuration from the same location.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
inherit (jsonFormat) type;
|
||||
default = { };
|
||||
example = {
|
||||
defaultProvider = "anthropic";
|
||||
defaultModel = "claude-sonnet-4-20250514";
|
||||
defaultThinkingLevel = "medium";
|
||||
theme = "dark";
|
||||
packages = [
|
||||
"npm:@termdraw/pi"
|
||||
"npm:pi-mcp-adapter"
|
||||
];
|
||||
compaction = {
|
||||
enabled = true;
|
||||
reserveTokens = 16384;
|
||||
keepRecentTokens = 20000;
|
||||
};
|
||||
retry = {
|
||||
enabled = true;
|
||||
maxRetries = 3;
|
||||
};
|
||||
enabledModels = [
|
||||
"claude-*"
|
||||
"gpt-4o"
|
||||
];
|
||||
};
|
||||
description = ''
|
||||
Configuration written to
|
||||
{file}`~/.pi/agent/settings.json`.
|
||||
See <https://pi.dev/docs/latest/settings> for the
|
||||
documentation.
|
||||
'';
|
||||
};
|
||||
|
||||
keybindings = mkOption {
|
||||
inherit (jsonFormat) type;
|
||||
default = { };
|
||||
example = {
|
||||
"tui.editor.cursorUp" = [
|
||||
"up"
|
||||
"ctrl+p"
|
||||
];
|
||||
"tui.editor.cursorDown" = [
|
||||
"down"
|
||||
"ctrl+n"
|
||||
];
|
||||
"tui.editor.deleteWordBackward" = [
|
||||
"ctrl+w"
|
||||
"alt+backspace"
|
||||
];
|
||||
};
|
||||
description = ''
|
||||
Keybindings configuration written to
|
||||
{file}`~/.pi/agent/keybindings.json`.
|
||||
See <https://pi.dev/docs/latest/keybindings> for the
|
||||
documentation.
|
||||
'';
|
||||
};
|
||||
|
||||
models = mkOption {
|
||||
inherit (jsonFormat) type;
|
||||
default = { };
|
||||
example = {
|
||||
providers = {
|
||||
ollama = {
|
||||
baseUrl = "http://localhost:11434/v1";
|
||||
api = "openai-completions";
|
||||
apiKey = "ollama";
|
||||
models = [ { id = "llama3.1:8b"; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Custom model providers written to
|
||||
{file}`~/.pi/agent/models.json`.
|
||||
|
||||
Each provider entry may contain `baseUrl`,
|
||||
`api`, `apiKey`, `compat`, and a `models`
|
||||
list with `id`, `name`, `reasoning`, etc.
|
||||
|
||||
See <https://pi.dev/docs/latest/models> for the
|
||||
documentation.
|
||||
'';
|
||||
};
|
||||
|
||||
context = mkOption {
|
||||
type = lib.types.either lib.types.lines lib.types.path;
|
||||
default = "";
|
||||
description = ''
|
||||
Global context for Pi Coding Agent.
|
||||
|
||||
The value is either:
|
||||
- Inline content as a string
|
||||
- A path to a file containing the content
|
||||
|
||||
The configured content is written to
|
||||
{file}`AGENTS.md` inside
|
||||
{option}`programs.pi-coding-agent.configDir`
|
||||
(default {file}`~/.pi/agent/AGENTS.md`).
|
||||
'';
|
||||
example = literalExpression "./pi-context.md";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
home = {
|
||||
packages = mkIf (packageWithExtraPackages != null) [
|
||||
packageWithExtraPackages
|
||||
];
|
||||
|
||||
sessionVariables = lib.mkIf (cfg.configDir != upstreamConfigDir) {
|
||||
PI_CODING_AGENT_DIR = cfg.configDir;
|
||||
};
|
||||
|
||||
file = lib.mkMerge [
|
||||
(mkIf (cfg.settings != { }) {
|
||||
"${cfg.configDir}/settings.json".source =
|
||||
jsonFormat.generate "pi-coding-agent-settings.json" cfg.settings;
|
||||
})
|
||||
|
||||
(mkIf (cfg.keybindings != { }) {
|
||||
"${cfg.configDir}/keybindings.json".source =
|
||||
jsonFormat.generate "pi-coding-agent-keybindings.json" cfg.keybindings;
|
||||
})
|
||||
|
||||
(mkIf (cfg.models != { }) {
|
||||
"${cfg.configDir}/models.json".source =
|
||||
jsonFormat.generate "pi-coding-agent-models.json" cfg.models;
|
||||
})
|
||||
|
||||
(
|
||||
if isPathLike cfg.context then
|
||||
{ "${cfg.configDir}/AGENTS.md".source = cfg.context; }
|
||||
else
|
||||
(mkIf (cfg.context != "") {
|
||||
"${cfg.configDir}/AGENTS.md".text = cfg.context;
|
||||
})
|
||||
)
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
9
tests/modules/programs/pi-coding-agent/context-empty.nix
Normal file
9
tests/modules/programs/pi-coding-agent/context-empty.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
context = "";
|
||||
};
|
||||
nmt.script = ''
|
||||
assertPathNotExists home-files/.pi/agent/AGENTS.md
|
||||
'';
|
||||
}
|
||||
4
tests/modules/programs/pi-coding-agent/context-inline.md
Normal file
4
tests/modules/programs/pi-coding-agent/context-inline.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Global Pi Context
|
||||
|
||||
Always use TypeScript strict mode.
|
||||
Follow the project's existing code style.
|
||||
16
tests/modules/programs/pi-coding-agent/context-inline.nix
Normal file
16
tests/modules/programs/pi-coding-agent/context-inline.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
context = ''
|
||||
# Global Pi Context
|
||||
|
||||
Always use TypeScript strict mode.
|
||||
Follow the project's existing code style.
|
||||
'';
|
||||
};
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.pi/agent/AGENTS.md
|
||||
assertFileContent home-files/.pi/agent/AGENTS.md \
|
||||
${./context-inline.md}
|
||||
'';
|
||||
}
|
||||
11
tests/modules/programs/pi-coding-agent/context-path.nix
Normal file
11
tests/modules/programs/pi-coding-agent/context-path.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
context = ./context-inline.md;
|
||||
};
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.pi/agent/AGENTS.md
|
||||
assertFileContent home-files/.pi/agent/AGENTS.md \
|
||||
${./context-inline.md}
|
||||
'';
|
||||
}
|
||||
11
tests/modules/programs/pi-coding-agent/custom-config-dir.nix
Normal file
11
tests/modules/programs/pi-coding-agent/custom-config-dir.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
configDir = "/home/testuser/.config/pi/agent";
|
||||
};
|
||||
nmt.script = ''
|
||||
# Verify env var is set for non-default configDir
|
||||
assertFileRegex home-path/etc/profile.d/hm-session-vars.sh \
|
||||
'PI_CODING_AGENT_DIR'
|
||||
'';
|
||||
}
|
||||
10
tests/modules/programs/pi-coding-agent/default.nix
Normal file
10
tests/modules/programs/pi-coding-agent/default.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
pi-coding-agent-settings = ./settings.nix;
|
||||
pi-coding-agent-empty-settings = ./empty-settings.nix;
|
||||
pi-coding-agent-keybindings = ./keybindings.nix;
|
||||
pi-coding-agent-context-inline = ./context-inline.nix;
|
||||
pi-coding-agent-context-path = ./context-path.nix;
|
||||
pi-coding-agent-context-empty = ./context-empty.nix;
|
||||
pi-coding-agent-custom-config-dir = ./custom-config-dir.nix;
|
||||
pi-coding-agent-models = ./models.nix;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
settings = { };
|
||||
};
|
||||
nmt.script = ''
|
||||
assertPathNotExists home-files/.pi/agent/settings.json
|
||||
'';
|
||||
}
|
||||
14
tests/modules/programs/pi-coding-agent/keybindings.json
Normal file
14
tests/modules/programs/pi-coding-agent/keybindings.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"tui.editor.cursorDown": [
|
||||
"down",
|
||||
"ctrl+n"
|
||||
],
|
||||
"tui.editor.cursorUp": [
|
||||
"up",
|
||||
"ctrl+p"
|
||||
],
|
||||
"tui.editor.deleteWordBackward": [
|
||||
"ctrl+w",
|
||||
"alt+backspace"
|
||||
]
|
||||
}
|
||||
24
tests/modules/programs/pi-coding-agent/keybindings.nix
Normal file
24
tests/modules/programs/pi-coding-agent/keybindings.nix
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
keybindings = {
|
||||
"tui.editor.cursorUp" = [
|
||||
"up"
|
||||
"ctrl+p"
|
||||
];
|
||||
"tui.editor.cursorDown" = [
|
||||
"down"
|
||||
"ctrl+n"
|
||||
];
|
||||
"tui.editor.deleteWordBackward" = [
|
||||
"ctrl+w"
|
||||
"alt+backspace"
|
||||
];
|
||||
};
|
||||
};
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.pi/agent/keybindings.json
|
||||
assertFileContent home-files/.pi/agent/keybindings.json \
|
||||
${./keybindings.json}
|
||||
'';
|
||||
}
|
||||
14
tests/modules/programs/pi-coding-agent/models.json
Normal file
14
tests/modules/programs/pi-coding-agent/models.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"providers": {
|
||||
"litellm": {
|
||||
"api": "openai-completions",
|
||||
"apiKey": "ollama",
|
||||
"baseUrl": "http://localhost:11434/v1",
|
||||
"models": [
|
||||
{
|
||||
"id": "llama3.1:8b"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
20
tests/modules/programs/pi-coding-agent/models.nix
Normal file
20
tests/modules/programs/pi-coding-agent/models.nix
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
models = {
|
||||
providers = {
|
||||
litellm = {
|
||||
baseUrl = "http://localhost:11434/v1";
|
||||
api = "openai-completions";
|
||||
apiKey = "ollama";
|
||||
models = [ { id = "llama3.1:8b"; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.pi/agent/models.json
|
||||
assertFileContent home-files/.pi/agent/models.json \
|
||||
${./models.json}
|
||||
'';
|
||||
}
|
||||
19
tests/modules/programs/pi-coding-agent/settings.json
Normal file
19
tests/modules/programs/pi-coding-agent/settings.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compaction": {
|
||||
"enabled": true,
|
||||
"keepRecentTokens": 20000,
|
||||
"reserveTokens": 16384
|
||||
},
|
||||
"defaultModel": "claude-sonnet-4-20250514",
|
||||
"defaultProvider": "anthropic",
|
||||
"defaultThinkingLevel": "medium",
|
||||
"enabledModels": [
|
||||
"claude-*",
|
||||
"gpt-4o"
|
||||
],
|
||||
"retry": {
|
||||
"enabled": true,
|
||||
"maxRetries": 3
|
||||
},
|
||||
"theme": "dark"
|
||||
}
|
||||
29
tests/modules/programs/pi-coding-agent/settings.nix
Normal file
29
tests/modules/programs/pi-coding-agent/settings.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
settings = {
|
||||
defaultProvider = "anthropic";
|
||||
defaultModel = "claude-sonnet-4-20250514";
|
||||
defaultThinkingLevel = "medium";
|
||||
theme = "dark";
|
||||
compaction = {
|
||||
enabled = true;
|
||||
reserveTokens = 16384;
|
||||
keepRecentTokens = 20000;
|
||||
};
|
||||
retry = {
|
||||
enabled = true;
|
||||
maxRetries = 3;
|
||||
};
|
||||
enabledModels = [
|
||||
"claude-*"
|
||||
"gpt-4o"
|
||||
];
|
||||
};
|
||||
};
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.pi/agent/settings.json
|
||||
assertFileContent home-files/.pi/agent/settings.json \
|
||||
${./settings.json}
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user