2026-04-13 16:52:47 +02:00
|
|
|
{
|
|
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-14 18:36:13 +02:00
|
|
|
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; }
|
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-13 16:52:47 +02:00
|
|
|
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";
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-14 18:36:13 +02:00
|
|
|
# Skills (merged from personal AGENTS repo + optional external skills)
|
|
|
|
|
home.file.".claude/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;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-13 16:52:47 +02:00
|
|
|
# Rendered settings.json with permissions + MCP servers
|
|
|
|
|
home.file.".claude/settings.json" = mkIf (settingsJson != null) {
|
|
|
|
|
source = "${settingsJson}";
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|