feat: config with agents rework
This commit is contained in:
90
modules/home-manager/coding/agents/claude-code.nix
Normal file
90
modules/home-manager/coding/agents/claude-code.nix
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.agents.claude-code;
|
||||
mcpCfg = config.programs.mcp or null;
|
||||
in {
|
||||
options.coding.agents.claude-code = {
|
||||
enable = mkEnableOption "Claude Code agent management via canonical agent.toml definitions";
|
||||
|
||||
agentsInput = mkOption {
|
||||
type = types.nullOr types.anything;
|
||||
default = null;
|
||||
description = ''
|
||||
The `agents` flake input (your personal AGENTS repo).
|
||||
When set, agents are rendered from canonical agent.toml files
|
||||
and symlinked to ~/.claude/agents/.
|
||||
'';
|
||||
};
|
||||
|
||||
modelOverrides = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Per-agent model overrides. Maps agent slug to model alias or ID.
|
||||
Example: { chiron = "claude-sonnet-4-20250514"; }
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
chiron = "claude-sonnet-4-20250514";
|
||||
"chiron-forge" = "claude-sonnet-4-20250514";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
mcpServers = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
default = if mcpCfg != null then mcpCfg.servers else {};
|
||||
defaultText = literalExpression "config.programs.mcp.servers";
|
||||
description = ''
|
||||
MCP server configurations for Claude Code.
|
||||
Merged into ~/.claude/settings.json alongside permissions.
|
||||
Automatically inherits from config.programs.mcp.servers.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (let
|
||||
agentsLib = (import ../../../../lib {inherit lib;}).agents;
|
||||
|
||||
# Rendered agents + permissions (only if agentsInput is set)
|
||||
rendered = mkIf (cfg.agentsInput != null) (
|
||||
agentsLib.renderForClaudeCode {
|
||||
inherit pkgs;
|
||||
canonical = cfg.agentsInput.lib.loadAgents;
|
||||
modelOverrides = cfg.modelOverrides;
|
||||
}
|
||||
);
|
||||
|
||||
# Merge MCP servers into the rendered settings.json.
|
||||
# The renderer produces { permissions: { allow, deny } }.
|
||||
# We add mcpServers on top.
|
||||
settingsJson =
|
||||
if cfg.agentsInput != null
|
||||
then let
|
||||
renderedSettings = builtins.fromJSON (builtins.readFile "${rendered}/.claude/settings.json");
|
||||
withMcp =
|
||||
if cfg.mcpServers != {}
|
||||
then renderedSettings // {mcpServers = cfg.mcpServers;}
|
||||
else renderedSettings;
|
||||
in
|
||||
pkgs.writeText "claude-settings.json" (builtins.toJSON withMcp)
|
||||
else if cfg.mcpServers != {}
|
||||
then pkgs.writeText "claude-settings.json" (builtins.toJSON {mcpServers = cfg.mcpServers;})
|
||||
else null;
|
||||
in {
|
||||
# Rendered agent files symlinked to ~/.claude/agents/
|
||||
home.file.".claude/agents" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${rendered}/.claude/agents";
|
||||
};
|
||||
|
||||
# Rendered settings.json with permissions + MCP servers
|
||||
home.file.".claude/settings.json" = mkIf (settingsJson != null) {
|
||||
source = "${settingsJson}";
|
||||
};
|
||||
});
|
||||
}
|
||||
10
modules/home-manager/coding/agents/default.nix
Normal file
10
modules/home-manager/coding/agents/default.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
# Per-tool agent sub-modules
|
||||
# Each module handles rendering canonical agent.toml definitions
|
||||
# for a specific AI coding tool.
|
||||
{
|
||||
imports = [
|
||||
./opencode.nix
|
||||
./claude-code.nix
|
||||
./pi.nix
|
||||
];
|
||||
}
|
||||
113
modules/home-manager/coding/agents/opencode.nix
Normal file
113
modules/home-manager/coding/agents/opencode.nix
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.agents.opencode;
|
||||
in {
|
||||
options.coding.agents.opencode = {
|
||||
enable = mkEnableOption "OpenCode agent management via canonical agent.toml definitions";
|
||||
|
||||
agentsInput = mkOption {
|
||||
type = types.nullOr types.anything;
|
||||
default = null;
|
||||
description = ''
|
||||
The `agents` flake input (your personal AGENTS repo).
|
||||
When set, agents are rendered from canonical agent.toml files
|
||||
and symlinked to ~/.config/opencode/agents/.
|
||||
'';
|
||||
};
|
||||
|
||||
modelOverrides = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Per-agent model overrides. Maps agent slug to model string.
|
||||
Example: { chiron = "anthropic/claude-sonnet-4"; }
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
chiron = "anthropic/claude-sonnet-4";
|
||||
"chiron-forge" = "anthropic/claude-sonnet-4";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
externalSkills = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
src = mkOption {
|
||||
type = types.anything;
|
||||
description = "Flake input pointing to a skills repository root.";
|
||||
};
|
||||
skillsDir = mkOption {
|
||||
type = types.str;
|
||||
default = "skills";
|
||||
description = ''
|
||||
Subdirectory inside src that contains skill folders.
|
||||
'';
|
||||
};
|
||||
selectSkills = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
List of skill names to cherry-pick from this source.
|
||||
null means include every skill found in skillsDir.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
description = ''
|
||||
External skill sources passed to mkOpencodeSkills.
|
||||
Each entry maps directly to an element of the externalSkills
|
||||
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[
|
||||
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
|
||||
{ src = inputs.skills-vercel; }
|
||||
]
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Rendered agent files symlinked to ~/.config/opencode/agents/
|
||||
xdg.configFile."opencode/agents" = mkIf (cfg.agentsInput != null) {
|
||||
source = (import ../../../../lib {inherit lib;}).agents.renderForOpencode {
|
||||
inherit pkgs;
|
||||
canonical = cfg.agentsInput.lib.loadAgents;
|
||||
modelOverrides = cfg.modelOverrides;
|
||||
};
|
||||
};
|
||||
|
||||
# Skills (merged from personal AGENTS repo + optional external skills)
|
||||
xdg.configFile."opencode/skills" = mkIf (cfg.agentsInput != null) {
|
||||
source = cfg.agentsInput.lib.mkOpencodeSkills {
|
||||
inherit pkgs;
|
||||
customSkills = "${cfg.agentsInput}/skills";
|
||||
externalSkills =
|
||||
map (
|
||||
entry:
|
||||
{inherit (entry) src skillsDir;}
|
||||
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
|
||||
)
|
||||
cfg.externalSkills;
|
||||
};
|
||||
};
|
||||
|
||||
# Static config dirs from AGENTS repo
|
||||
xdg.configFile."opencode/context" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${cfg.agentsInput}/context";
|
||||
};
|
||||
xdg.configFile."opencode/commands" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${cfg.agentsInput}/commands";
|
||||
};
|
||||
xdg.configFile."opencode/prompts" = mkIf (cfg.agentsInput != null) {
|
||||
source = "${cfg.agentsInput}/prompts";
|
||||
};
|
||||
};
|
||||
}
|
||||
234
modules/home-manager/coding/agents/pi.nix
Normal file
234
modules/home-manager/coding/agents/pi.nix
Normal file
@@ -0,0 +1,234 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.coding.agents.pi;
|
||||
mcpCfg = config.programs.mcp or null;
|
||||
in {
|
||||
options.coding.agents.pi = {
|
||||
enable = mkEnableOption "Pi agent management via canonical agent.toml definitions";
|
||||
|
||||
mcpServers = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
default = if mcpCfg != null then mcpCfg.servers else {};
|
||||
defaultText = literalExpression "config.programs.mcp.servers";
|
||||
description = ''
|
||||
MCP server configurations for Pi (pi-mcp-adapter).
|
||||
Written to ~/.pi/agent/mcp.json.
|
||||
Automatically inherits from config.programs.mcp.servers.
|
||||
'';
|
||||
};
|
||||
|
||||
agentsInput = mkOption {
|
||||
type = types.nullOr types.anything;
|
||||
default = null;
|
||||
description = ''
|
||||
The `agents` flake input (your personal AGENTS repo).
|
||||
When set, the primary agent's system prompt is rendered as SYSTEM.md,
|
||||
all agents are listed in AGENTS.md, and subagent .md files are deployed.
|
||||
'';
|
||||
};
|
||||
|
||||
modelOverrides = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Per-agent model overrides for Pi subagents.
|
||||
Maps agent slug to model string, e.g.:
|
||||
{ chiron = "anthropic/claude-sonnet-4"; chiron-forge = "anthropic/claude-sonnet-4"; }
|
||||
'';
|
||||
};
|
||||
|
||||
primaryAgent = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Override which canonical agent is used as primary for SYSTEM.md.
|
||||
When null, the first agent with mode="primary" is used.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = types.attrsOf types.anything;
|
||||
options = {
|
||||
packages = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Pi packages to install (npm:, git:, or local paths).
|
||||
These are written to ~/.pi/agent/settings.json.
|
||||
'';
|
||||
};
|
||||
|
||||
defaultProvider = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Default LLM provider (e.g. 'anthropic', 'openai', 'zai').";
|
||||
};
|
||||
|
||||
defaultModel = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Default model ID.";
|
||||
};
|
||||
|
||||
defaultThinkingLevel = mkOption {
|
||||
type = types.nullOr (types.enum ["off" "minimal" "low" "medium" "high" "xhigh"]);
|
||||
default = null;
|
||||
description = "Default extended thinking level.";
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Pi theme name.";
|
||||
};
|
||||
|
||||
hideThinkingBlock = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
description = "Hide thinking blocks in output.";
|
||||
};
|
||||
|
||||
quietStartup = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
description = "Hide startup header.";
|
||||
};
|
||||
|
||||
compaction = mkOption {
|
||||
type = types.nullOr (types.submodule {
|
||||
options = {
|
||||
enabled = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
};
|
||||
reserveTokens = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
};
|
||||
keepRecentTokens = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
default = null;
|
||||
description = "Auto-compaction settings.";
|
||||
};
|
||||
|
||||
enabledModels = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = "Model patterns for Ctrl+P cycling.";
|
||||
};
|
||||
|
||||
sessionDir = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Directory where session files are stored.";
|
||||
};
|
||||
|
||||
extensions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Local extension file paths or directories.";
|
||||
};
|
||||
|
||||
skills = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Local skill file paths or directories.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
description = ''
|
||||
Pi settings written to ~/.pi/agent/settings.json.
|
||||
Only non-null values are included in the generated JSON.
|
||||
See pi docs/settings.md for all options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (let
|
||||
# Build settings.json by filtering out null values recursively
|
||||
filterNulls = attrs:
|
||||
lib.filterAttrs (_: v: v != null) (
|
||||
builtins.mapAttrs (_: v:
|
||||
if builtins.isAttrs v
|
||||
then let
|
||||
filtered = filterNulls v;
|
||||
in
|
||||
if filtered == {} then null else filtered
|
||||
else v) attrs
|
||||
);
|
||||
|
||||
piSettings = filterNulls cfg.settings;
|
||||
|
||||
# Rendered agents (only computed when agentsInput is set)
|
||||
rendered =
|
||||
if cfg.agentsInput != null
|
||||
then
|
||||
(import ../../../../lib {inherit lib;}).agents.renderForPi {
|
||||
inherit pkgs;
|
||||
canonical = cfg.agentsInput.lib.loadAgents;
|
||||
modelOverrides = cfg.modelOverrides;
|
||||
primaryAgent = cfg.primaryAgent;
|
||||
}
|
||||
else null;
|
||||
|
||||
# Dynamic home.file entries for agent .md files
|
||||
agentFiles =
|
||||
if cfg.agentsInput != null
|
||||
then
|
||||
let
|
||||
agentNames = builtins.attrNames cfg.agentsInput.lib.loadAgents;
|
||||
in
|
||||
builtins.listToAttrs (
|
||||
map (name: {
|
||||
name = ".pi/agent/agents/${name}.md";
|
||||
value = {source = "${rendered}/agents/${name}.md";};
|
||||
})
|
||||
agentNames
|
||||
)
|
||||
else {};
|
||||
in {
|
||||
home.file = mkMerge [
|
||||
# ── MCP servers from programs.mcp → ~/.pi/agent/mcp.json ───────
|
||||
(mkIf (cfg.mcpServers != {}) {
|
||||
".pi/agent/mcp.json".text = builtins.toJSON {mcpServers = cfg.mcpServers;};
|
||||
})
|
||||
|
||||
# ── ~/.pi/agent/settings.json ──────────────────────────────────
|
||||
{
|
||||
".pi/agent/settings.json".text = builtins.toJSON piSettings;
|
||||
}
|
||||
|
||||
# ── AGENTS.md — agent descriptions and specialist listing ──────
|
||||
(mkIf (cfg.agentsInput != null) {
|
||||
".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md";
|
||||
})
|
||||
|
||||
# ── SYSTEM.md — primary agent's system prompt ──────────────────
|
||||
(mkIf (cfg.agentsInput != null) {
|
||||
".pi/agent/SYSTEM.md".source = "${rendered}/SYSTEM.md";
|
||||
})
|
||||
|
||||
# ── Agents — pi-subagents .md files ────────────────────────────
|
||||
agentFiles
|
||||
|
||||
# ── Skills symlinked from AGENTS repo ──────────────────────────
|
||||
(mkIf (cfg.agentsInput != null) {
|
||||
".pi/agent/skills".source = cfg.agentsInput.lib.mkOpencodeSkills {
|
||||
inherit pkgs;
|
||||
customSkills = "${cfg.agentsInput}/skills";
|
||||
};
|
||||
})
|
||||
];
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user