claude-code: split module files

Move public option declarations and file-entry helpers into dedicated files while keeping module assembly in default.nix. Update the AI labeler for the new module directory.
This commit is contained in:
Austin Horstman
2026-07-10 14:19:11 -05:00
parent 1c029fb0dc
commit 5c7199cd3b
4 changed files with 384 additions and 349 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
- modules/programs/aiac.nix - modules/programs/aiac.nix
- modules/programs/aichat.nix - modules/programs/aichat.nix
- modules/programs/aider-chat.nix - modules/programs/aider-chat.nix
- modules/programs/claude-code.nix - modules/programs/claude-code/**/*
- modules/programs/codex.nix - modules/programs/codex.nix
- modules/programs/fabric-ai.nix - modules/programs/fabric-ai.nix
- modules/programs/antigravity-cli.nix - modules/programs/antigravity-cli.nix
+291
View File
@@ -0,0 +1,291 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
mkChangedOptionModule
nameValuePair
optionalAttrs
;
cfg = config.programs.claude-code;
jsonFormat = pkgs.formats.json { };
claudeCodeLib = import ./lib.nix { inherit lib pkgs; };
packageVersion = if cfg.package == null then null else lib.getVersion cfg.package;
hasPackageVersion = packageVersion != null && packageVersion != "";
# A null package has no detectable version, so assume the latest Claude Code
# and enable version-gated behavior by default. Preserve the legacy wrapper
# for non-null packages without version metadata.
atLeast =
version: cfg.package == null || (hasPackageVersion && lib.versionAtLeast packageVersion version);
supportsPluginDir = !hasPackageVersion || atLeast "2.1.76";
supportsPersonalPlugins = atLeast "2.1.157";
upstreamConfigDir = "${config.home.homeDirectory}/.claude";
isMcpServerEnabled =
server:
let
enabled = server.enabled or null;
disabled = (server.disabled or false) == true;
in
enabled != false && !disabled;
transformMcpServer =
name: server:
lib.hm.mcp.transformMcpServer {
inherit server;
exclude = [ "enabled" ];
extraTransforms = [
lib.hm.mcp.addType
(lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) # envFiles currently still need wrapping https://github.com/anthropics/claude-code/issues/28942
];
};
# Shared MCP servers participate only when both integrations are enabled.
sharedMcpServers = lib.optionalAttrs (
cfg.enableMcpIntegration && config.programs.mcp.enable
) config.programs.mcp.servers;
transformedMcpServers = lib.mapAttrs transformMcpServer sharedMcpServers;
disabledMcpServerNames = lib.attrNames (
lib.filterAttrs (_: server: !(isMcpServerEnabled server)) (sharedMcpServers // cfg.mcpServers)
);
in
{
meta.maintainers = [ lib.maintainers.khaneliman ];
imports = [
(mkChangedOptionModule
[ "programs" "claude-code" "memory" "text" ]
[ "programs" "claude-code" "context" ]
(config: lib.getAttrFromPath [ "programs" "claude-code" "memory" "text" ] config)
)
(mkChangedOptionModule
[ "programs" "claude-code" "memory" "source" ]
[ "programs" "claude-code" "context" ]
(config: lib.getAttrFromPath [ "programs" "claude-code" "memory" "source" ] config)
)
(mkChangedOptionModule
[ "programs" "claude-code" "skillsDir" ]
[ "programs" "claude-code" "skills" ]
(config: lib.getAttrFromPath [ "programs" "claude-code" "skillsDir" ] config)
)
./options.nix
];
config =
let
helpers = claudeCodeLib.mkHelpers { inherit (cfg) configDir; };
inherit (helpers)
mkHookEntries
mkInstalledMarketplaceEntry
mkMarkdownEntries
mkMarketplaceEntry
mkPluginEntry
mkRecursiveDirAttrs
mkSkillEntry
;
mergedMcpServers =
transformedMcpServers
// lib.mapAttrs (_: server: removeAttrs (lib.hm.mcp.addType server) [ "enabled" ]) cfg.mcpServers;
generatedPluginFiles =
lib.optional (mergedMcpServers != { }) {
name = ".mcp.json";
path = jsonFormat.generate "claude-code-mcp.json" { mcpServers = mergedMcpServers; };
}
++ lib.optional (cfg.lspServers != { }) {
name = ".lsp.json";
path = jsonFormat.generate "claude-code-lsp.json" cfg.lspServers;
};
generatedPlugin = pkgs.runCommand "claude-code-hm-plugin" { } (
''
install -Dm644 ${
jsonFormat.generate "claude-code-plugin.json" {
name = "claude-code-home-manager";
}
} $out/.claude-plugin/plugin.json
''
+ lib.concatLines (
map (pluginFile: "install -Dm644 ${pluginFile.path} $out/${pluginFile.name}") generatedPluginFiles
)
);
pluginEntries =
lib.optional (generatedPluginFiles != [ ]) {
name = "claude-code-home-manager";
source = generatedPlugin;
}
++ map mkPluginEntry cfg.plugins;
legacyPluginPaths = lib.optional (generatedPluginFiles != [ ]) generatedPlugin ++ cfg.plugins;
hasManagedPlugins = legacyPluginPaths != [ ];
useLegacyPluginWrapper = hasManagedPlugins && !supportsPersonalPlugins;
legacyWrapperArgs = lib.flatten (
map (plugin: [
"--plugin-dir"
"${plugin}"
]) legacyPluginPaths
);
legacyFinalPackage = pkgs.symlinkJoin {
name = "claude-code";
paths = [ cfg.package ];
postBuild = ''
mv $out/bin/claude $out/bin/.claude-wrapped
cat > $out/bin/claude <<EOF
#! ${pkgs.bash}/bin/bash -e
exec -a "\$0" "$out/bin/.claude-wrapped" ${lib.escapeShellArgs legacyWrapperArgs} "\$@"
EOF
chmod +x $out/bin/claude
'';
inherit (cfg.package) meta;
};
pluginNames = map (plugin: plugin.name) pluginEntries;
skillNames =
if builtins.isAttrs cfg.skills then
lib.attrNames cfg.skills
else if lib.hm.strings.isPathLike cfg.skills && lib.pathIsDirectory cfg.skills then
lib.attrNames (builtins.readDir cfg.skills)
else
[ ];
pluginFileEntries = lib.optionalAttrs supportsPersonalPlugins (
lib.listToAttrs (
map (
plugin:
nameValuePair "${cfg.configDir}/skills/${plugin.name}" {
inherit (plugin) source;
recursive = true;
}
) pluginEntries
)
);
in
lib.mkIf cfg.enable {
warnings = lib.optional (useLegacyPluginWrapper && supportsPluginDir) ''
`programs.claude-code.package` ${
if hasPackageVersion then "version ${packageVersion}" else "has no detectable version and"
}
uses the legacy `--plugin-dir` wrapper. Strict-parser subcommands such
as `claude rc` may reject managed MCP, LSP, or plugin arguments. Upgrade
Claude Code to version 2.1.157 or later to use persistent personal
plugins instead.
'';
assertions =
let
exclusiveInlineDirNames = [
"rules"
"agents"
"commands"
"hooks"
];
mkExclusiveAssertion = inline: {
assertion = !(cfg.${inline} != { } && cfg.${inline + "Dir"} != null);
message = "Cannot specify both `programs.claude-code.${inline}` and `programs.claude-code.${inline}Dir`";
};
in
[
{
assertion = !hasManagedPlugins || supportsPluginDir;
message = "Managed Claude Code MCP, LSP, and plugins require `programs.claude-code.package` version 2.1.76 or later";
}
{
assertion = !lib.hm.strings.isPathLike cfg.skills || lib.pathIsDirectory cfg.skills;
message = "`programs.claude-code.skills` must be a directory when set to a path";
}
{
assertion =
!supportsPersonalPlugins || lib.length pluginNames == lib.length (lib.unique pluginNames);
message = "`programs.claude-code.plugins` entries must resolve to unique personal-plugin directory names";
}
{
assertion = !supportsPersonalPlugins || lib.intersectLists skillNames pluginNames == [ ];
message = "`programs.claude-code.skills` and managed plugins must have unique directory names";
}
]
++ map mkExclusiveAssertion exclusiveInlineDirNames;
programs.claude-code.finalPackage = lib.mkIf (cfg.package != null) (
if useLegacyPluginWrapper then legacyFinalPackage else cfg.package
);
home = {
packages = lib.mkIf (cfg.package != null) [ cfg.finalPackage ];
sessionVariables = lib.mkIf (cfg.configDir != upstreamConfigDir) {
CLAUDE_CONFIG_DIR = cfg.configDir;
};
file = lib.mkMerge [
(lib.mkIf (cfg.settings != { } || cfg.marketplaces != { } || disabledMcpServerNames != [ ]) {
"${cfg.configDir}/settings.json".source = jsonFormat.generate "claude-code-settings.json" (
cfg.settings
// {
"$schema" = "https://json.schemastore.org/claude-code-settings.json";
}
// optionalAttrs (cfg.marketplaces != { }) {
extraKnownMarketplaces = lib.mapAttrs mkMarketplaceEntry cfg.marketplaces;
}
// optionalAttrs (disabledMcpServerNames != [ ]) {
disabledMcpjsonServers = lib.unique (
(cfg.settings.disabledMcpjsonServers or [ ]) ++ disabledMcpServerNames
);
}
);
})
(
if lib.isPath cfg.context then
{
"${cfg.configDir}/CLAUDE.md".source = cfg.context;
}
else
(lib.mkIf (cfg.context != "") {
"${cfg.configDir}/CLAUDE.md".text = cfg.context;
})
)
(lib.mkIf (cfg.marketplaces != { }) {
"${cfg.configDir}/plugins/known_marketplaces.json".source =
jsonFormat.generate "claude-code-known-marketplaces.json" (
lib.mapAttrs mkInstalledMarketplaceEntry cfg.marketplaces
);
})
(mkMarkdownEntries "agents" cfg.agents)
(mkMarkdownEntries "commands" cfg.commands)
(mkMarkdownEntries "rules" cfg.rules)
(mkRecursiveDirAttrs "agents" cfg.agentsDir)
(mkRecursiveDirAttrs "commands" cfg.commandsDir)
(mkRecursiveDirAttrs "hooks" cfg.hooksDir)
(mkRecursiveDirAttrs "rules" cfg.rulesDir)
(lib.mkIf (lib.hm.strings.isPathLike cfg.skills) {
"${cfg.configDir}/skills" = {
source = cfg.skills;
recursive = true;
};
})
pluginFileEntries
(mkHookEntries cfg.hooks)
(lib.optionalAttrs (builtins.isAttrs cfg.skills) (lib.mapAttrs' mkSkillEntry cfg.skills))
(mkMarkdownEntries "output-styles" cfg.outputStyles)
];
};
};
}
+90
View File
@@ -0,0 +1,90 @@
{ lib, pkgs }:
let
jsonFormat = pkgs.formats.json { };
in
{
mkHelpers =
{ configDir }:
let
mkSourceEntry = content: if lib.isPath content then { source = content; } else { text = content; };
mkMarketplaceEntry = _name: content: {
source = {
source = "directory";
path = content;
};
};
in
{
inherit mkMarketplaceEntry;
mkHookEntries =
attrs:
lib.mapAttrs' (
name: content:
lib.nameValuePair "${configDir}/hooks/${name}" {
text = content;
executable = true;
}
) attrs;
mkInstalledMarketplaceEntry =
name: content:
(mkMarketplaceEntry name content)
// {
installLocation = content;
lastUpdated = "1970-01-01T00:00:00Z";
};
mkMarkdownEntries =
subdir: attrs:
lib.mapAttrs' (
name: content: lib.nameValuePair "${configDir}/${subdir}/${name}.md" (mkSourceEntry content)
) attrs;
mkPluginEntry =
plugin:
let
name = builtins.unsafeDiscardStringContext (
lib.strings.sanitizeDerivationName (baseNameOf (toString plugin))
);
in
{
inherit name;
source = pkgs.symlinkJoin {
name = "claude-code-${name}";
paths = [ plugin ];
postBuild = ''
if [[ ! -e $out/.claude-plugin/plugin.json ]]; then
install -Dm644 ${
jsonFormat.generate "claude-code-${name}.json" {
inherit name;
}
} $out/.claude-plugin/plugin.json
fi
'';
};
};
mkRecursiveDirAttrs =
subdir: dir:
lib.optionalAttrs (dir != null) {
"${configDir}/${subdir}" = {
source = dir;
recursive = true;
};
};
mkSkillEntry =
name: content:
if lib.hm.strings.isPathLike content && lib.pathIsDirectory content then
lib.nameValuePair "${configDir}/skills/${name}" {
source = content;
recursive = true;
}
else
lib.nameValuePair "${configDir}/skills/${name}/SKILL.md" (
if lib.hm.strings.isPathLike content then { source = content; } else { text = content; }
);
};
}
@@ -5,61 +5,10 @@
... ...
}: }:
let let
inherit (lib) inherit (lib) literalExpression mkOption optionalAttrs;
literalExpression
mkChangedOptionModule
mkOption
nameValuePair
optionalAttrs
;
cfg = config.programs.claude-code;
jsonFormat = pkgs.formats.json { }; jsonFormat = pkgs.formats.json { };
packageVersion = if cfg.package == null then null else lib.getVersion cfg.package;
hasPackageVersion = packageVersion != null && packageVersion != "";
# A null package has no detectable version, so assume the latest Claude Code
# and enable version-gated behavior by default. Preserve the legacy wrapper
# for non-null packages without version metadata.
atLeast =
version: cfg.package == null || (hasPackageVersion && lib.versionAtLeast packageVersion version);
supportsPluginDir = !hasPackageVersion || atLeast "2.1.76";
supportsPersonalPlugins = atLeast "2.1.157";
upstreamConfigDir = "${config.home.homeDirectory}/.claude";
isMcpServerEnabled =
server:
let
enabled = server.enabled or null;
disabled = (server.disabled or false) == true;
in
enabled != false && !disabled;
transformMcpServer =
name: server:
lib.hm.mcp.transformMcpServer {
inherit server;
exclude = [ "enabled" ];
extraTransforms = [
lib.hm.mcp.addType
(lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) # envFiles currently still need wrapping https://github.com/anthropics/claude-code/issues/28942
];
};
# Shared MCP servers participate only when both integrations are enabled.
sharedMcpServers = lib.optionalAttrs (
cfg.enableMcpIntegration && config.programs.mcp.enable
) config.programs.mcp.servers;
transformedMcpServers = lib.mapAttrs transformMcpServer sharedMcpServers;
disabledMcpServerNames = lib.attrNames (
lib.filterAttrs (_: server: !(isMcpServerEnabled server)) (sharedMcpServers // cfg.mcpServers)
);
mkContentOption = mkContentOption =
{ {
description, description,
@@ -81,29 +30,8 @@ let
default = null; default = null;
inherit description example; inherit description example;
}; };
in in
{ {
meta.maintainers = [ lib.maintainers.khaneliman ];
imports = [
(mkChangedOptionModule
[ "programs" "claude-code" "memory" "text" ]
[ "programs" "claude-code" "context" ]
(config: lib.getAttrFromPath [ "programs" "claude-code" "memory" "text" ] config)
)
(mkChangedOptionModule
[ "programs" "claude-code" "memory" "source" ]
[ "programs" "claude-code" "context" ]
(config: lib.getAttrFromPath [ "programs" "claude-code" "memory" "source" ] config)
)
(mkChangedOptionModule
[ "programs" "claude-code" "skillsDir" ]
[ "programs" "claude-code" "skills" ]
(config: lib.getAttrFromPath [ "programs" "claude-code" "skillsDir" ] config)
)
];
options.programs.claude-code = { options.programs.claude-code = {
enable = lib.mkEnableOption "Claude Code, Anthropic's official CLI"; enable = lib.mkEnableOption "Claude Code, Anthropic's official CLI";
@@ -138,7 +66,7 @@ in
configDir = mkOption { configDir = mkOption {
type = lib.types.str; type = lib.types.str;
default = upstreamConfigDir; default = "${config.home.homeDirectory}/.claude";
defaultText = literalExpression ''"''${config.home.homeDirectory}/.claude"''; defaultText = literalExpression ''"''${config.home.homeDirectory}/.claude"'';
example = literalExpression ''"''${config.xdg.configHome}/claude"''; example = literalExpression ''"''${config.xdg.configHome}/claude"'';
description = '' description = ''
@@ -582,278 +510,4 @@ in
}; };
}; };
}; };
config =
let
mkSourceEntry = content: if lib.isPath content then { source = content; } else { text = content; };
mkMarkdownEntries =
subdir: attrs:
lib.mapAttrs' (
name: content: nameValuePair "${cfg.configDir}/${subdir}/${name}.md" (mkSourceEntry content)
) attrs;
mkHookEntries =
attrs:
lib.mapAttrs' (
name: content:
nameValuePair "${cfg.configDir}/hooks/${name}" {
text = content;
executable = true;
}
) attrs;
mkRecursiveDirAttrs =
subdir: dir:
optionalAttrs (dir != null) {
"${cfg.configDir}/${subdir}" = {
source = dir;
recursive = true;
};
};
mkSkillEntry =
name: content:
if lib.hm.strings.isPathLike content && lib.pathIsDirectory content then
nameValuePair "${cfg.configDir}/skills/${name}" {
source = content;
recursive = true;
}
else
nameValuePair "${cfg.configDir}/skills/${name}/SKILL.md" (
if lib.hm.strings.isPathLike content then { source = content; } else { text = content; }
);
mkMarketplaceEntry = _name: content: {
source = {
source = "directory";
path = content;
};
};
mkInstalledMarketplaceEntry =
name: content:
(mkMarketplaceEntry name content)
// {
installLocation = content;
lastUpdated = "1970-01-01T00:00:00Z";
};
mergedMcpServers =
transformedMcpServers
// lib.mapAttrs (_: server: removeAttrs (lib.hm.mcp.addType server) [ "enabled" ]) cfg.mcpServers;
generatedPluginFiles =
lib.optional (mergedMcpServers != { }) {
name = ".mcp.json";
path = jsonFormat.generate "claude-code-mcp.json" { mcpServers = mergedMcpServers; };
}
++ lib.optional (cfg.lspServers != { }) {
name = ".lsp.json";
path = jsonFormat.generate "claude-code-lsp.json" cfg.lspServers;
};
generatedPlugin = pkgs.runCommand "claude-code-hm-plugin" { } (
''
install -Dm644 ${
jsonFormat.generate "claude-code-plugin.json" {
name = "claude-code-home-manager";
}
} $out/.claude-plugin/plugin.json
''
+ lib.concatLines (
map (pluginFile: "install -Dm644 ${pluginFile.path} $out/${pluginFile.name}") generatedPluginFiles
)
);
mkPluginEntry =
plugin:
let
name = builtins.unsafeDiscardStringContext (
lib.strings.sanitizeDerivationName (baseNameOf (toString plugin))
);
in
{
inherit name;
source = pkgs.symlinkJoin {
name = "claude-code-${name}";
paths = [ plugin ];
postBuild = ''
if [[ ! -e $out/.claude-plugin/plugin.json ]]; then
install -Dm644 ${
jsonFormat.generate "claude-code-${name}.json" {
inherit name;
}
} $out/.claude-plugin/plugin.json
fi
'';
};
};
pluginEntries =
lib.optional (generatedPluginFiles != [ ]) {
name = "claude-code-home-manager";
source = generatedPlugin;
}
++ map mkPluginEntry cfg.plugins;
legacyPluginPaths = lib.optional (generatedPluginFiles != [ ]) generatedPlugin ++ cfg.plugins;
hasManagedPlugins = legacyPluginPaths != [ ];
useLegacyPluginWrapper = hasManagedPlugins && !supportsPersonalPlugins;
legacyWrapperArgs = lib.flatten (
map (plugin: [
"--plugin-dir"
"${plugin}"
]) legacyPluginPaths
);
legacyFinalPackage = pkgs.symlinkJoin {
name = "claude-code";
paths = [ cfg.package ];
postBuild = ''
mv $out/bin/claude $out/bin/.claude-wrapped
cat > $out/bin/claude <<EOF
#! ${pkgs.bash}/bin/bash -e
exec -a "\$0" "$out/bin/.claude-wrapped" ${lib.escapeShellArgs legacyWrapperArgs} "\$@"
EOF
chmod +x $out/bin/claude
'';
inherit (cfg.package) meta;
};
pluginNames = map (plugin: plugin.name) pluginEntries;
skillNames =
if builtins.isAttrs cfg.skills then
lib.attrNames cfg.skills
else if lib.hm.strings.isPathLike cfg.skills && lib.pathIsDirectory cfg.skills then
lib.attrNames (builtins.readDir cfg.skills)
else
[ ];
pluginFileEntries = lib.optionalAttrs supportsPersonalPlugins (
lib.listToAttrs (
map (
plugin:
nameValuePair "${cfg.configDir}/skills/${plugin.name}" {
inherit (plugin) source;
recursive = true;
}
) pluginEntries
)
);
in
lib.mkIf cfg.enable {
warnings = lib.optional (useLegacyPluginWrapper && supportsPluginDir) ''
`programs.claude-code.package` ${
if hasPackageVersion then "version ${packageVersion}" else "has no detectable version and"
}
uses the legacy `--plugin-dir` wrapper. Strict-parser subcommands such
as `claude rc` may reject managed MCP, LSP, or plugin arguments. Upgrade
Claude Code to version 2.1.157 or later to use persistent personal
plugins instead.
'';
assertions =
let
exclusiveInlineDirNames = [
"rules"
"agents"
"commands"
"hooks"
];
mkExclusiveAssertion = inline: {
assertion = !(cfg.${inline} != { } && cfg.${inline + "Dir"} != null);
message = "Cannot specify both `programs.claude-code.${inline}` and `programs.claude-code.${inline}Dir`";
};
in
[
{
assertion = !hasManagedPlugins || supportsPluginDir;
message = "Managed Claude Code MCP, LSP, and plugins require `programs.claude-code.package` version 2.1.76 or later";
}
{
assertion = !lib.hm.strings.isPathLike cfg.skills || lib.pathIsDirectory cfg.skills;
message = "`programs.claude-code.skills` must be a directory when set to a path";
}
{
assertion =
!supportsPersonalPlugins || lib.length pluginNames == lib.length (lib.unique pluginNames);
message = "`programs.claude-code.plugins` entries must resolve to unique personal-plugin directory names";
}
{
assertion = !supportsPersonalPlugins || lib.intersectLists skillNames pluginNames == [ ];
message = "`programs.claude-code.skills` and managed plugins must have unique directory names";
}
]
++ map mkExclusiveAssertion exclusiveInlineDirNames;
programs.claude-code.finalPackage = lib.mkIf (cfg.package != null) (
if useLegacyPluginWrapper then legacyFinalPackage else cfg.package
);
home = {
packages = lib.mkIf (cfg.package != null) [ cfg.finalPackage ];
sessionVariables = lib.mkIf (cfg.configDir != upstreamConfigDir) {
CLAUDE_CONFIG_DIR = cfg.configDir;
};
file = lib.mkMerge [
(lib.mkIf (cfg.settings != { } || cfg.marketplaces != { } || disabledMcpServerNames != [ ]) {
"${cfg.configDir}/settings.json".source = jsonFormat.generate "claude-code-settings.json" (
cfg.settings
// {
"$schema" = "https://json.schemastore.org/claude-code-settings.json";
}
// optionalAttrs (cfg.marketplaces != { }) {
extraKnownMarketplaces = lib.mapAttrs mkMarketplaceEntry cfg.marketplaces;
}
// optionalAttrs (disabledMcpServerNames != [ ]) {
disabledMcpjsonServers = lib.unique (
(cfg.settings.disabledMcpjsonServers or [ ]) ++ disabledMcpServerNames
);
}
);
})
(
if lib.isPath cfg.context then
{
"${cfg.configDir}/CLAUDE.md".source = cfg.context;
}
else
(lib.mkIf (cfg.context != "") {
"${cfg.configDir}/CLAUDE.md".text = cfg.context;
})
)
(lib.mkIf (cfg.marketplaces != { }) {
"${cfg.configDir}/plugins/known_marketplaces.json".source =
jsonFormat.generate "claude-code-known-marketplaces.json" (
lib.mapAttrs mkInstalledMarketplaceEntry cfg.marketplaces
);
})
(mkMarkdownEntries "agents" cfg.agents)
(mkMarkdownEntries "commands" cfg.commands)
(mkMarkdownEntries "rules" cfg.rules)
(mkRecursiveDirAttrs "agents" cfg.agentsDir)
(mkRecursiveDirAttrs "commands" cfg.commandsDir)
(mkRecursiveDirAttrs "hooks" cfg.hooksDir)
(mkRecursiveDirAttrs "rules" cfg.rulesDir)
(lib.mkIf (lib.hm.strings.isPathLike cfg.skills) {
"${cfg.configDir}/skills" = {
source = cfg.skills;
recursive = true;
};
})
pluginFileEntries
(mkHookEntries cfg.hooks)
(lib.optionalAttrs (builtins.isAttrs cfg.skills) (lib.mapAttrs' mkSkillEntry cfg.skills))
(mkMarkdownEntries "output-styles" cfg.outputStyles)
];
};
};
} }