#!/usr/bin/env bash # # Install Domna's curated skill set globally (~/.claude). # # Run from the root of the target repo: # curl -fsSL https://raw.githubusercontent.com/Hestia-Homes/agentic-toolkit/main/setup.sh | bash # Or, if you've cloned agentic-toolkit: # bash /path/to/agentic-toolkit/setup.sh # # What this does: # 1. Copies the pinned skills-lock.json from agentic-toolkit to ~/.claude. # 2. Parses it and runs `skills add --global` per source so all skills land # in ~/.claude/skills (user-level, available in every repo). # # Note: `skills experimental_install` would be the natural fit but it only # restores at project scope — confirmed empirically that running it against # a global lock prints "No project skills found" and exits 0, installing # nothing. So we drive `skills add --global` from the lock instead. # # Logs verbose output to $HOME/.claude/agentic-toolkit-setup.log so failures # under postCreateCommand or `| bash` are inspectable after the fact. # set -euo pipefail # --- logging ------------------------------------------------------------------- CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" mkdir -p "$CLAUDE_DIR" LOG_FILE="$CLAUDE_DIR/agentic-toolkit-setup.log" # Tee everything (stdout + stderr) to the log so silent failures aren't silent. exec > >(tee -a "$LOG_FILE") 2>&1 echo "=== agentic-toolkit setup.sh @ $(date -u +%FT%TZ) ===" # Trap so a failure under set -e prints the failing line into the log. trap 'rc=$?; echo "ERROR: setup.sh exited $rc at line $LINENO" >&2; exit $rc' ERR # --- config -------------------------------------------------------------------- LOCK_URL="https://raw.githubusercontent.com/Hestia-Homes/agentic-toolkit/main/skills-lock.json" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || true)" LOCAL_LOCK="${SCRIPT_DIR:+$SCRIPT_DIR/skills-lock.json}" AGENT_TARGET="claude-code" DEST_LOCK="$CLAUDE_DIR/skills-lock.json" # --- guards -------------------------------------------------------------------- if ! command -v npx >/dev/null 2>&1; then echo "error: npx is required (install Node.js >= 20)." >&2 exit 1 fi if ! command -v node >/dev/null 2>&1; then echo "error: node is required to parse skills-lock.json." >&2 exit 1 fi # --- stage skills-lock.json into ~/.claude ------------------------------------ if [[ -n "$LOCAL_LOCK" && -f "$LOCAL_LOCK" ]]; then echo "==> Copying local skills-lock.json -> $DEST_LOCK" cp "$LOCAL_LOCK" "$DEST_LOCK" else echo "==> Fetching skills-lock.json -> $DEST_LOCK" if command -v curl >/dev/null 2>&1; then curl -fsSL "$LOCK_URL" -o "$DEST_LOCK" elif command -v wget >/dev/null 2>&1; then wget -qO "$DEST_LOCK" "$LOCK_URL" else echo "error: need curl or wget to fetch skills-lock.json." >&2 exit 1 fi fi # --- parse lock: write "sourceskill1,skill2,..." to a tempfile ----------- GROUPS_FILE="$(mktemp -t skills-groups.XXXXXX)" trap 'rm -f "$GROUPS_FILE"' EXIT node --input-type=module -e " import fs from 'node:fs'; const lock = JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); const bySource = new Map(); for (const [name, entry] of Object.entries(lock.skills || {})) { if (!entry || !entry.source) continue; if (!bySource.has(entry.source)) bySource.set(entry.source, []); bySource.get(entry.source).push(name); } const out = [...bySource.entries()] .map(([src, names]) => src + '\t' + names.join(',')) .join('\n'); fs.writeFileSync(process.argv[2], out + (out ? '\n' : '')); " "$DEST_LOCK" "$GROUPS_FILE" if [[ ! -s "$GROUPS_FILE" ]]; then echo "error: no skills found in $DEST_LOCK." >&2 exit 1 fi echo "==> Skill groups parsed from lock:" cat "$GROUPS_FILE" # --- install (globally, grouped by source) ------------------------------------- echo "==> Installing skills globally (~/.claude)" # Read plan from fd 3 and redirect npx stdin to /dev/null. The skills CLI reads # stdin even with --yes; without these guards it would consume the remaining # lines of GROUPS_FILE and the loop would only run once (silently dropping # every source after the first). while IFS=$'\t' read -r SOURCE SKILLS <&3; do [[ -z "$SOURCE" ]] && continue echo "==> $SOURCE :: $SKILLS" # The skills CLI expects --skill repeated per name, not a comma-joined string. SKILL_ARGS=() IFS=',' read -ra _NAMES <<< "$SKILLS" for n in "${_NAMES[@]}"; do [[ -z "$n" ]] && continue SKILL_ARGS+=(--skill "$n") done npx --yes skills@latest add "$SOURCE" \ "${SKILL_ARGS[@]}" \ --agent "$AGENT_TARGET" \ --copy \ --global \ --yes < /dev/null done 3< "$GROUPS_FILE" echo "==> Done. Log: $LOG_FILE" cat <<'EOF' Next step: Run /setup-matt-pocock-skills once per repo to record the issue tracker, triage labels, and domain-doc layout. EOF