1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

claude-code: added 'hooks' and 'hooksDir' options to specify hooks from filesystem

This commit is contained in:
Sewer56 2025-08-22 22:20:10 +01:00 committed by Austin Horstman
parent be37a3492d
commit 5e06d0f184
6 changed files with 88 additions and 1 deletions

View file

@ -156,6 +156,26 @@ in
}; };
}; };
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 = { memory = {
text = lib.mkOption { text = lib.mkOption {
type = lib.types.nullOr lib.types.lines; type = lib.types.nullOr lib.types.lines;
@ -206,6 +226,16 @@ in
example = lib.literalExpression "./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 { mcpServers = lib.mkOption {
type = lib.types.attrsOf jsonFormat.type; type = lib.types.attrsOf jsonFormat.type;
default = { }; default = { };
@ -265,6 +295,10 @@ in
assertion = !(cfg.commands != { } && cfg.commandsDir != null); assertion = !(cfg.commands != { } && cfg.commandsDir != null);
message = "Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`"; 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 = programs.claude-code.finalPackage =
@ -319,6 +353,11 @@ in
source = cfg.commandsDir; source = cfg.commandsDir;
recursive = true; recursive = true;
}; };
".claude/hooks" = lib.mkIf (cfg.hooksDir != null) {
source = cfg.hooksDir;
recursive = true;
};
} }
// lib.mapAttrs' ( // lib.mapAttrs' (
name: content: name: content:
@ -331,7 +370,13 @@ in
lib.nameValuePair ".claude/commands/${name}.md" { lib.nameValuePair ".claude/commands/${name}.md" {
text = content; text = content;
} }
) cfg.commands; ) cfg.commands
// lib.mapAttrs' (
name: content:
lib.nameValuePair ".claude/hooks/${name}" {
text = content;
}
) cfg.hooks;
}; };
}; };
} }

View file

@ -32,6 +32,12 @@
test-command = "test content"; test-command = "test content";
}; };
commandsDir = ./commands; commandsDir = ./commands;
# assert fail: cannot set hooks and hooksDir at the same time.
hooks = {
test-hook = "test content";
};
hooksDir = ./hooks;
}; };
test.asserts.assertions.expected = [ test.asserts.assertions.expected = [
@ -39,5 +45,6 @@
"Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`" "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`"
"Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`" "Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`"
"Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`" "Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`"
"Cannot specify both `programs.claude-code.hooks` and `programs.claude-code.hooksDir`"
]; ];
} }

View file

@ -7,4 +7,5 @@
claude-code-memory-from-source = ./memory-from-source.nix; claude-code-memory-from-source = ./memory-from-source.nix;
claude-code-agents-dir = ./agents-dir.nix; claude-code-agents-dir = ./agents-dir.nix;
claude-code-commands-dir = ./commands-dir.nix; claude-code-commands-dir = ./commands-dir.nix;
claude-code-hooks-dir = ./hooks-dir.nix;
} }

View file

@ -104,6 +104,17 @@
Focus on user-friendly explanations and examples. Focus on user-friendly explanations and examples.
''; '';
}; };
hooks = {
pre-edit = ''
#!/usr/bin/env bash
echo "About to edit file: $1"
'';
post-commit = ''
#!/usr/bin/env bash
echo "Committed with message: $1"
'';
};
}; };
nmt.script = '' nmt.script = ''
@ -122,5 +133,11 @@
assertFileExists home-files/.claude/commands/commit.md assertFileExists home-files/.claude/commands/commit.md
assertFileContent home-files/.claude/commands/commit.md ${./expected-commit} assertFileContent home-files/.claude/commands/commit.md ${./expected-commit}
assertFileExists home-files/.claude/hooks/pre-edit
assertFileRegex home-files/.claude/hooks/pre-edit "About to edit file"
assertFileExists home-files/.claude/hooks/post-commit
assertFileRegex home-files/.claude/hooks/post-commit "Committed with message"
''; '';
} }

View file

@ -0,0 +1,14 @@
{
programs.claude-code = {
enable = true;
hooksDir = ./hooks;
};
nmt.script = ''
assertFileExists home-files/.claude/hooks/test-hook
assertLinkExists home-files/.claude/hooks/test-hook
assertFileContent \
home-files/.claude/hooks/test-hook \
${./hooks/test-hook}
'';
}

View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
# Test hook for claude-code
echo "Test hook executed with: $@"