135 lines
3.4 KiB
Bash
Executable File
135 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO_SLUG="${1:-${SECURITYAUDIT_REPO:-andydixon/securityaudit-skill}}"
|
|
REF="${2:-${SECURITYAUDIT_REF:-main}}"
|
|
DEFAULT_SKILLS="securityaudit aitm debullshit summarise"
|
|
SKILLS="${SECURITYAUDIT_SKILLS:-${SECURITYAUDIT_SKILL_PATH:-$DEFAULT_SKILLS}}"
|
|
SKILLS="${SKILLS//,/ }"
|
|
CLAUDE_HOME_DIR="${CLAUDE_HOME:-$HOME/.claude}"
|
|
DEST_PARENT="$CLAUDE_HOME_DIR/skills"
|
|
|
|
say() {
|
|
printf '%s\n' "$*"
|
|
}
|
|
|
|
fail() {
|
|
printf 'Error: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
need_command() {
|
|
command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1"
|
|
}
|
|
|
|
validate_skill_name() {
|
|
skill="$1"
|
|
case "$skill" in
|
|
""|*[!A-Za-z0-9_-]*)
|
|
fail "Invalid skill name: $skill"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
print_usage() {
|
|
say "Restart Claude Code if it was already open, then use:"
|
|
for skill in $SKILLS; do
|
|
case "$skill" in
|
|
securityaudit)
|
|
say " /securityaudit"
|
|
say " /securityaudit --fix"
|
|
;;
|
|
*)
|
|
say " /$skill"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
copy_claude_skill() {
|
|
src="$1"
|
|
skill="$2"
|
|
dest="$DEST_PARENT/$skill"
|
|
|
|
validate_skill_name "$skill"
|
|
|
|
[ -f "$src/SKILL.md" ] || fail "Could not find SKILL.md at $src"
|
|
grep -q "^name: $skill$" "$src/SKILL.md" || fail "SKILL.md at $src does not look like the $skill skill"
|
|
|
|
mkdir -p "$DEST_PARENT"
|
|
stage="$(mktemp -d "$DEST_PARENT/.$skill-claude-install.XXXXXX")"
|
|
mkdir -p "$stage/$skill"
|
|
|
|
cp "$src/SKILL.md" "$stage/$skill/SKILL.md"
|
|
for dir in scripts references assets; do
|
|
if [ -d "$src/$dir" ]; then
|
|
cp -R "$src/$dir" "$stage/$skill/$dir"
|
|
fi
|
|
done
|
|
|
|
[ -f "$stage/$skill/SKILL.md" ] || fail "Install staging failed for $skill"
|
|
grep -q "^name: $skill$" "$stage/$skill/SKILL.md" || fail "Install validation failed for $skill"
|
|
|
|
if [ -e "$dest" ]; then
|
|
backup="$dest.backup.$(date +%Y%m%d-%H%M%S)"
|
|
say "Existing Claude $skill skill found. Backing it up to:"
|
|
say "$backup"
|
|
mv "$dest" "$backup"
|
|
fi
|
|
|
|
mv "$stage/$skill" "$dest"
|
|
rmdir "$stage" 2>/dev/null || true
|
|
say "Installed Claude Code $skill skill to $dest"
|
|
}
|
|
|
|
SCRIPT_PATH="${BASH_SOURCE[0]:-}"
|
|
if [ -n "$SCRIPT_PATH" ] && [ -f "$SCRIPT_PATH" ]; then
|
|
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
else
|
|
SCRIPT_DIR=""
|
|
fi
|
|
|
|
if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/securityaudit/SKILL.md" ]; then
|
|
say "Installing Claude Code skills from local checkout..."
|
|
installed_any=false
|
|
for skill in $SKILLS; do
|
|
[ -f "$SCRIPT_DIR/$skill/SKILL.md" ] || fail "Could not find $skill/SKILL.md in local checkout"
|
|
copy_claude_skill "$SCRIPT_DIR/$skill" "$skill"
|
|
installed_any=true
|
|
done
|
|
[ "$installed_any" = true ] || fail "No skills requested"
|
|
else
|
|
need_command curl
|
|
need_command tar
|
|
|
|
tmp="$(mktemp -d)"
|
|
cleanup() {
|
|
rm -rf "$tmp"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
archive="$tmp/repo.tar.gz"
|
|
url="https://github.com/$REPO_SLUG/archive/refs/heads/$REF.tar.gz"
|
|
|
|
say "Downloading Claude Code skills from $REPO_SLUG ($REF)..."
|
|
curl -fsSL "$url" -o "$archive"
|
|
tar -xzf "$archive" -C "$tmp"
|
|
|
|
installed_any=false
|
|
for skill in $SKILLS; do
|
|
src="$(find "$tmp" -path "*/$skill/SKILL.md" -print -quit)"
|
|
[ -n "$src" ] || fail "Could not find $skill/SKILL.md in the downloaded repo"
|
|
copy_claude_skill "$(dirname "$src")" "$skill"
|
|
installed_any=true
|
|
done
|
|
[ "$installed_any" = true ] || fail "No skills requested"
|
|
fi
|
|
|
|
say ""
|
|
say "Installed Claude Code skills:"
|
|
for skill in $SKILLS; do
|
|
say " $skill -> $DEST_PARENT/$skill"
|
|
done
|
|
say ""
|
|
print_usage
|