#!/usr/bin/env bash # Install skills// into ~/.claude/skills// (or $CLAUDE_SKILLS_DIR) # Usage: install.sh [--prune] [name...] # no args = install all skills/* into target # --prune = after install, remove target// dirs that are NOT in skills/* # (prune always scans full target, ignores name filter — global cleanup) set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SRC="$ROOT/skills" TARGET="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" prune=0 positional=() for arg in "$@"; do case "$arg" in --prune) prune=1 ;; *) positional+=("$arg") ;; esac done if [ "${#positional[@]}" -eq 0 ]; then # Portable across bash 3.2 (stock macOS) and bash 4+ (Linux, git-bash): # avoid `mapfile` (bash 4+) and `find -printf` (GNU find only). names=() for d in "$SRC"/*/; do [ -d "$d" ] || continue names+=("$(basename "$d")") done IFS=$'\n' names=($(printf '%s\n' "${names[@]}" | sort)) unset IFS else names=("${positional[@]}") fi mkdir -p "$TARGET" for name in "${names[@]}"; do src_dir="$SRC/$name" dst_dir="$TARGET/$name" if [ ! -d "$src_dir" ]; then echo "skip: $name (not found in skills/)" >&2 continue fi if [ ! -f "$src_dir/SKILL.md" ]; then echo "skip: $name (missing SKILL.md)" >&2 continue fi rm -rf "$dst_dir" cp -R "$src_dir" "$dst_dir" echo "installed: $name → $dst_dir" done if [ "$prune" -eq 1 ]; then for d in "$TARGET"/*/; do [ -d "$d" ] || continue name="$(basename "$d")" if [ ! -d "$SRC/$name" ]; then echo "pruning: $name (not in skills/) → $d" rm -rf "$d" fi done fi