113 lines
4.5 KiB
Nix
113 lines
4.5 KiB
Nix
# Builds a dev-shell shellHook that links a project's selected skills into its
|
|
# own `.agents/skills/` as direct-child symlinks into the Nix store, with
|
|
# `.claude/skills` as a compatibility symlink when that path is free.
|
|
# Generated ignores live in the project root so agent skill scanners still see
|
|
# the linked skills.
|
|
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 ./.agents/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.
|
|
# When ./.claude/skills is free, it is kept as a compatibility symlink to the
|
|
# canonical ./.agents/skills directory so Claude Code sees the same skills.
|
|
# The whole hook runs in a subshell so it leaks no names into the interactive
|
|
# shell.
|
|
(
|
|
_mkskills_dir=".agents/skills"
|
|
mkdir -p "$_mkskills_dir"
|
|
|
|
_mkskills_claude_skills_managed=0
|
|
mkdir -p .claude
|
|
if [ ! -e .claude/skills ] && [ ! -L .claude/skills ]; then
|
|
ln -s ../.agents/skills .claude/skills
|
|
_mkskills_claude_skills_managed=1
|
|
elif [ -L .claude/skills ]; then
|
|
case "$(readlink .claude/skills)" in
|
|
../.agents/skills) _mkskills_claude_skills_managed=1 ;;
|
|
${storeDir}/*) rm -f .claude/skills && ln -s ../.agents/skills .claude/skills && _mkskills_claude_skills_managed=1 ;;
|
|
esac
|
|
else
|
|
printf 'mkSkillsShellHook: leaving existing non-symlink .claude/skills in place; Claude Code compatibility link not installed\n' >&2
|
|
fi
|
|
|
|
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 generated
|
|
# ignore block 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}
|
|
|
|
if [ -f "$_mkskills_dir/.gitignore" ] \
|
|
&& grep -qx '# Generated by mkSkillsShellHook. Nix-delivered skill symlinks, kept out of git.' "$_mkskills_dir/.gitignore"; then
|
|
rm -f "$_mkskills_dir/.gitignore"
|
|
fi
|
|
|
|
# Regenerate the project-root ignore block from the current selection.
|
|
# Skill scanners read ignore files under .agents/skills, so the block must
|
|
# live above the scanned tree.
|
|
_mkskills_gitignore=".gitignore"
|
|
_mkskills_gitignore_tmp=".gitignore.mkskills.$$"
|
|
if [ -f "$_mkskills_gitignore" ]; then
|
|
awk '
|
|
$0 == "# BEGIN mkSkillsShellHook" { skip = 1; next }
|
|
$0 == "# END mkSkillsShellHook" { skip = 0; next }
|
|
!skip { lines[++n] = $0 }
|
|
END {
|
|
while (n > 0 && lines[n] == "") n--
|
|
for (i = 1; i <= n; i++) print lines[i]
|
|
}
|
|
' "$_mkskills_gitignore" > "$_mkskills_gitignore_tmp"
|
|
else
|
|
: > "$_mkskills_gitignore_tmp"
|
|
fi
|
|
{
|
|
if [ -s "$_mkskills_gitignore_tmp" ]; then
|
|
cat "$_mkskills_gitignore_tmp"
|
|
printf '\n'
|
|
fi
|
|
printf '%s\n' "# BEGIN mkSkillsShellHook"
|
|
printf '%s\n' "# Generated by mkSkillsShellHook. Nix-delivered skill symlinks, kept out of git."
|
|
if [ "$_mkskills_claude_skills_managed" = 1 ]; then
|
|
printf '%s\n' ".claude/skills"
|
|
fi
|
|
# Disable pathname expansion so a managed name is never glob-expanded.
|
|
set -f
|
|
for _mkskills_name in $_mkskills_managed; do
|
|
printf '.agents/skills/%s\n' "$_mkskills_name"
|
|
done
|
|
printf '%s\n' "# END mkSkillsShellHook"
|
|
} > "$_mkskills_gitignore"
|
|
rm -f "$_mkskills_gitignore_tmp"
|
|
)
|
|
''
|