# Builds a dev-shell shellHook that links a project's selected skills into its # own `.claude/skills/` as direct-child symlinks into the Nix store. skills: let storeDir = builtins.storeDir; links = builtins.concatStringsSep "\n" ( map (skill: " _mkskills_link '${skill.skillName}' '${skill}'") skills ); in '' # Places this project's Nix-delivered skills into ./.claude/skills/ on shell # entry. # Each run first drops every direct-child symlink pointing into the store, # then relinks the current selection. # Those store symlinks are unambiguously ours, so this removes deselected # skills for free and needs no manifest. # A real (non-symlink) skill directory is a hand-authored, project-private # skill and is left untouched. # The whole hook runs in a subshell so it leaks no names into the interactive # shell. ( _mkskills_dir=".claude/skills" mkdir -p "$_mkskills_dir" for _mkskills_entry in "$_mkskills_dir"/*; do [ -L "$_mkskills_entry" ] || continue case "$(readlink "$_mkskills_entry")" in ${storeDir}/*) rm -f "$_mkskills_entry" ;; esac done # A real path at a selected skill's name is a hand-authored skill. # It is left in place and left out of the managed set, so the .gitignore # below keeps tracking it rather than the skill that would have been linked. _mkskills_managed="" _mkskills_link() { _mkskills_dest="$_mkskills_dir/$1" if [ -e "$_mkskills_dest" ] && [ ! -L "$_mkskills_dest" ]; then printf 'mkSkillsShellHook: skipping %s: a non-symlink path already exists\n' "$1" >&2 else # At most a non-store symlink remains at this name, so removing it first # keeps the link step to a plain POSIX `ln -s` with no overwrite flag. rm -f "$_mkskills_dest" ln -s "$2" "$_mkskills_dest" _mkskills_managed="$_mkskills_managed $1" fi } ${links} # Regenerate the self-ignoring .gitignore from the current selection, so the # store symlinks stay out of git while hand-authored skills, absent from this # list, remain tracked. { printf '%s\n' "# Generated by mkSkillsShellHook. Nix-delivered skill symlinks, kept out of git." printf '%s\n' "/.gitignore" # Disable pathname expansion so a managed name is never glob-expanded. set -f for _mkskills_name in $_mkskills_managed; do printf '/%s\n' "$_mkskills_name" done } > "$_mkskills_dir/.gitignore" ) ''