mirror of
https://github.com/nix-community/home-manager.git
synced 2026-09-15 03:09:21 +00:00
126 lines
4.2 KiB
Nix
126 lines
4.2 KiB
Nix
{ lib, pkgs }:
|
|
let
|
|
jsonFormat = pkgs.formats.json { };
|
|
in
|
|
{
|
|
mkHelpers =
|
|
{ configDir }:
|
|
let
|
|
mkSourceEntry =
|
|
content: if lib.hm.strings.isPathLike 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}" ((mkSourceEntry content) // { executable = true; })
|
|
) attrs;
|
|
|
|
mkInstalledMarketplaceEntry =
|
|
name: content:
|
|
(mkMarketplaceEntry name content)
|
|
// {
|
|
installLocation = content;
|
|
lastUpdated = "1970-01-01T00:00:00Z";
|
|
autoUpdate = false;
|
|
};
|
|
|
|
mkMarkdownEntries =
|
|
subdir: attrs:
|
|
lib.mapAttrs' (
|
|
name: content: lib.nameValuePair "${configDir}/${subdir}/${name}.md" (mkSourceEntry content)
|
|
) attrs;
|
|
|
|
# Legacy list entries have no user-supplied name, so derive one from the
|
|
# source's base name. Store paths yield unstable names like
|
|
# `bxa1s0m3h4sh-source`, which is why the attribute set form is preferred.
|
|
derivePluginName =
|
|
plugin:
|
|
builtins.unsafeDiscardStringContext (
|
|
lib.strings.sanitizeDerivationName (baseNameOf (toString plugin))
|
|
);
|
|
|
|
# Wraps a plugin so a manifest can be synthesized for sources that lack
|
|
# one. Only top-level entries are linked, leaving each component
|
|
# directory a single symlink to the original. `pkgs.symlinkJoin` cannot
|
|
# be used here: it mirrors directories and symlinks the leaf files, and
|
|
# Claude Code's `agents/` and `commands/` scanners accept only regular
|
|
# files, so every agent and command would be dropped.
|
|
mkPluginEntry = name: plugin: {
|
|
inherit name;
|
|
source = pkgs.runCommand "claude-code-${name}" { } ''
|
|
mkdir -p "$out"
|
|
|
|
shopt -s dotglob nullglob
|
|
for entry in "${plugin}"/*; do
|
|
ln -s "$entry" "$out/$(basename "$entry")"
|
|
done
|
|
|
|
if [[ ! -e $out/.claude-plugin/plugin.json ]]; then
|
|
# Replace the linked manifest directory with a real one so the
|
|
# generated manifest can sit beside any existing contents.
|
|
rm -f "$out/.claude-plugin"
|
|
mkdir -p "$out/.claude-plugin"
|
|
for entry in "${plugin}"/.claude-plugin/*; do
|
|
ln -s "$entry" "$out/.claude-plugin/$(basename "$entry")"
|
|
done
|
|
install -m644 ${
|
|
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.isPath content && lib.pathIsDirectory content then
|
|
lib.nameValuePair "${configDir}/skills/${name}" {
|
|
source = content;
|
|
recursive = true;
|
|
}
|
|
else if lib.isPath content then
|
|
lib.nameValuePair "${configDir}/skills/${name}/SKILL.md" { source = content; }
|
|
else if lib.hm.strings.isPathLike content then
|
|
lib.nameValuePair "${configDir}/skills/${name}" {
|
|
source = pkgs.runCommandLocal "claude-code-skill-${lib.strings.sanitizeDerivationName name}" { } ''
|
|
source=${lib.escapeShellArg "${content}"}
|
|
if [[ -d "$source" ]]; then
|
|
ln -s "$source" "$out"
|
|
elif [[ -f "$source" ]]; then
|
|
mkdir -p "$out"
|
|
ln -s "$source" "$out/SKILL.md"
|
|
else
|
|
echo "Claude Code skill source '$source' is neither a file nor a directory" >&2
|
|
exit 1
|
|
fi
|
|
'';
|
|
recursive = true;
|
|
}
|
|
else
|
|
lib.nameValuePair "${configDir}/skills/${name}/SKILL.md" {
|
|
text = content;
|
|
};
|
|
};
|
|
}
|