mirror of
https://github.com/nix-community/home-manager.git
synced 2025-11-08 19:46:05 +01:00
Support passing actual files to the agents/commands since they will be used to generate files. Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
373 lines
11 KiB
Nix
373 lines
11 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.programs.claude-code;
|
|
jsonFormat = pkgs.formats.json { };
|
|
in
|
|
{
|
|
meta.maintainers = [ lib.maintainers.khaneliman ];
|
|
|
|
options.programs.claude-code = {
|
|
enable = lib.mkEnableOption "Claude Code, Anthropic's official CLI";
|
|
|
|
package = lib.mkPackageOption pkgs "claude-code" { nullable = true; };
|
|
|
|
finalPackage = lib.mkOption {
|
|
type = lib.types.package;
|
|
readOnly = true;
|
|
internal = true;
|
|
description = "Resulting customized claude-code package.";
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
inherit (jsonFormat) type;
|
|
default = { };
|
|
example = {
|
|
theme = "dark";
|
|
permissions = {
|
|
allow = [
|
|
"Bash(git diff:*)"
|
|
"Edit"
|
|
];
|
|
ask = [ "Bash(git push:*)" ];
|
|
deny = [
|
|
"WebFetch"
|
|
"Bash(curl:*)"
|
|
"Read(./.env)"
|
|
"Read(./secrets/**)"
|
|
];
|
|
additionalDirectories = [ "../docs/" ];
|
|
defaultMode = "acceptEdits";
|
|
disableBypassPermissionsMode = "disable";
|
|
};
|
|
model = "claude-3-5-sonnet-20241022";
|
|
hooks = {
|
|
PreToolUse = [
|
|
{
|
|
matcher = "Bash";
|
|
hooks = [
|
|
{
|
|
type = "command";
|
|
command = "echo 'Running command: $CLAUDE_TOOL_INPUT'";
|
|
}
|
|
];
|
|
}
|
|
];
|
|
PostToolUse = [
|
|
{
|
|
matcher = "Edit|MultiEdit|Write";
|
|
hooks = [
|
|
{
|
|
type = "command";
|
|
command = "nix fmt $(jq -r '.tool_input.file_path' <<< '$CLAUDE_TOOL_INPUT')";
|
|
}
|
|
];
|
|
}
|
|
];
|
|
};
|
|
statusLine = {
|
|
type = "command";
|
|
command = "input=$(cat); echo \"[$(echo \"$input\" | jq -r '.model.display_name')] 📁 $(basename \"$(echo \"$input\" | jq -r '.workspace.current_dir')\")\"";
|
|
padding = 0;
|
|
};
|
|
includeCoAuthoredBy = false;
|
|
};
|
|
description = "JSON configuration for Claude Code settings.json";
|
|
};
|
|
|
|
agents = lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path);
|
|
default = { };
|
|
description = ''
|
|
Custom agents for Claude Code.
|
|
The attribute name becomes the agent filename, and the value is either:
|
|
- Inline content as a string with frontmatter
|
|
- A path to a file containing the agent content with frontmatter
|
|
Agents are stored in .claude/agents/ directory.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
code-reviewer = '''
|
|
---
|
|
name: code-reviewer
|
|
description: Specialized code review agent
|
|
tools: Read, Edit, Grep
|
|
---
|
|
|
|
You are a senior software engineer specializing in code reviews.
|
|
Focus on code quality, security, and maintainability.
|
|
''';
|
|
documentation = ./agents/documentation.md;
|
|
}
|
|
'';
|
|
};
|
|
|
|
commands = lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path);
|
|
default = { };
|
|
description = ''
|
|
Custom commands for Claude Code.
|
|
The attribute name becomes the command filename, and the value is either:
|
|
- Inline content as a string
|
|
- A path to a file containing the command content
|
|
Commands are stored in .claude/commands/ directory.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
changelog = '''
|
|
---
|
|
allowed-tools: Bash(git log:*), Bash(git diff:*)
|
|
argument-hint: [version] [change-type] [message]
|
|
description: Update CHANGELOG.md with new entry
|
|
---
|
|
Parse the version, change type, and message from the input
|
|
and update the CHANGELOG.md file accordingly.
|
|
''';
|
|
fix-issue = ./commands/fix-issue.md;
|
|
commit = '''
|
|
---
|
|
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
|
|
description: Create a git commit with proper message
|
|
---
|
|
## Context
|
|
|
|
- Current git status: !`git status`
|
|
- Current git diff: !`git diff HEAD`
|
|
- Recent commits: !`git log --oneline -5`
|
|
|
|
## Task
|
|
|
|
Based on the changes above, create a single atomic git commit with a descriptive message.
|
|
''';
|
|
}
|
|
'';
|
|
};
|
|
|
|
hooks = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.lines;
|
|
default = { };
|
|
description = ''
|
|
Custom hooks for Claude Code.
|
|
The attribute name becomes the hook filename, and the value is the hook script content.
|
|
Hooks are stored in .claude/hooks/ directory.
|
|
'';
|
|
example = {
|
|
pre-edit = ''
|
|
#!/usr/bin/env bash
|
|
echo "About to edit file: $1"
|
|
'';
|
|
post-commit = ''
|
|
#!/usr/bin/env bash
|
|
echo "Committed with message: $1"
|
|
'';
|
|
};
|
|
};
|
|
|
|
memory = {
|
|
text = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.lines;
|
|
default = null;
|
|
description = ''
|
|
Inline memory content for CLAUDE.md.
|
|
This option is mutually exclusive with memory.source.
|
|
'';
|
|
example = ''
|
|
# Project Memory
|
|
|
|
## Current Task
|
|
Implementing enhanced claude-code module for home-manager.
|
|
|
|
## Key Files
|
|
- claude-code.nix: Main module implementation
|
|
'';
|
|
};
|
|
|
|
source = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = ''
|
|
Path to a file containing memory content for CLAUDE.md.
|
|
This option is mutually exclusive with memory.text.
|
|
'';
|
|
example = lib.literalExpression "./claude-memory.md";
|
|
};
|
|
};
|
|
|
|
agentsDir = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = ''
|
|
Path to a directory containing agent files for Claude Code.
|
|
Agent files from this directory will be symlinked to .claude/agents/.
|
|
'';
|
|
example = lib.literalExpression "./agents";
|
|
};
|
|
|
|
commandsDir = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = ''
|
|
Path to a directory containing command files for Claude Code.
|
|
Command files from this directory will be symlinked to .claude/commands/.
|
|
'';
|
|
example = lib.literalExpression "./commands";
|
|
};
|
|
|
|
hooksDir = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = ''
|
|
Path to a directory containing hook files for Claude Code.
|
|
Hook files from this directory will be symlinked to .claude/hooks/.
|
|
'';
|
|
example = lib.literalExpression "./hooks";
|
|
};
|
|
|
|
mcpServers = lib.mkOption {
|
|
type = lib.types.attrsOf jsonFormat.type;
|
|
default = { };
|
|
description = "MCP (Model Context Protocol) servers configuration";
|
|
example = {
|
|
github = {
|
|
type = "http";
|
|
url = "https://api.githubcopilot.com/mcp/";
|
|
};
|
|
filesystem = {
|
|
type = "stdio";
|
|
command = "npx";
|
|
args = [
|
|
"-y"
|
|
"@modelcontextprotocol/server-filesystem"
|
|
"/tmp"
|
|
];
|
|
};
|
|
database = {
|
|
type = "stdio";
|
|
command = "npx";
|
|
args = [
|
|
"-y"
|
|
"@bytebase/dbhub"
|
|
"--dsn"
|
|
"postgresql://user:pass@localhost:5432/db"
|
|
];
|
|
env = {
|
|
DATABASE_URL = "postgresql://user:pass@localhost:5432/db";
|
|
};
|
|
};
|
|
customTransport = {
|
|
type = "websocket";
|
|
url = "wss://example.com/mcp";
|
|
customOption = "value";
|
|
timeout = 5000;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = cfg.mcpServers == { } || cfg.package != null;
|
|
message = "`programs.claude-code.package` cannot be null when `mcpServers` is configured";
|
|
}
|
|
{
|
|
assertion = !(cfg.memory.text != null && cfg.memory.source != null);
|
|
message = "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`";
|
|
}
|
|
{
|
|
assertion = !(cfg.agents != { } && cfg.agentsDir != null);
|
|
message = "Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`";
|
|
}
|
|
{
|
|
assertion = !(cfg.commands != { } && cfg.commandsDir != null);
|
|
message = "Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`";
|
|
}
|
|
{
|
|
assertion = !(cfg.hooks != { } && cfg.hooksDir != null);
|
|
message = "Cannot specify both `programs.claude-code.hooks` and `programs.claude-code.hooksDir`";
|
|
}
|
|
];
|
|
|
|
programs.claude-code.finalPackage =
|
|
let
|
|
makeWrapperArgs = lib.flatten (
|
|
lib.filter (x: x != [ ]) [
|
|
(lib.optional (cfg.mcpServers != { }) [
|
|
"--add-flags"
|
|
"--mcp-config ${jsonFormat.generate "claude-code-mcp-config.json" { inherit (cfg) mcpServers; }}"
|
|
])
|
|
]
|
|
);
|
|
|
|
hasWrapperArgs = makeWrapperArgs != [ ];
|
|
in
|
|
if hasWrapperArgs then
|
|
pkgs.symlinkJoin {
|
|
name = "claude-code";
|
|
paths = [ cfg.package ];
|
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
|
postBuild = ''
|
|
wrapProgram $out/bin/claude ${lib.escapeShellArgs makeWrapperArgs}
|
|
'';
|
|
inherit (cfg.package) meta;
|
|
}
|
|
else
|
|
cfg.package;
|
|
|
|
home = {
|
|
packages = lib.mkIf (cfg.package != null) [ cfg.finalPackage ];
|
|
|
|
file = {
|
|
".claude/settings.json" = lib.mkIf (cfg.settings != { }) {
|
|
source = jsonFormat.generate "claude-code-settings.json" (
|
|
cfg.settings
|
|
// {
|
|
"$schema" = "https://json.schemastore.org/claude-code-settings.json";
|
|
}
|
|
);
|
|
};
|
|
|
|
".claude/CLAUDE.md" = lib.mkIf (cfg.memory.text != null || cfg.memory.source != null) (
|
|
if cfg.memory.text != null then { text = cfg.memory.text; } else { source = cfg.memory.source; }
|
|
);
|
|
|
|
".claude/agents" = lib.mkIf (cfg.agentsDir != null) {
|
|
source = cfg.agentsDir;
|
|
recursive = true;
|
|
};
|
|
|
|
".claude/commands" = lib.mkIf (cfg.commandsDir != null) {
|
|
source = cfg.commandsDir;
|
|
recursive = true;
|
|
};
|
|
|
|
".claude/hooks" = lib.mkIf (cfg.hooksDir != null) {
|
|
source = cfg.hooksDir;
|
|
recursive = true;
|
|
};
|
|
}
|
|
// lib.mapAttrs' (
|
|
name: content:
|
|
lib.nameValuePair ".claude/agents/${name}.md" (
|
|
if lib.isPath content then { source = content; } else { text = content; }
|
|
)
|
|
) cfg.agents
|
|
// lib.mapAttrs' (
|
|
name: content:
|
|
lib.nameValuePair ".claude/commands/${name}.md" (
|
|
if lib.isPath content then { source = content; } else { text = content; }
|
|
)
|
|
) cfg.commands
|
|
// lib.mapAttrs' (
|
|
name: content:
|
|
lib.nameValuePair ".claude/hooks/${name}" {
|
|
text = content;
|
|
}
|
|
) cfg.hooks;
|
|
};
|
|
};
|
|
}
|