#!/usr/bin/env bash # # Installer for the "docmap" + "undocmap" Claude Code skills. # # Runs safely whether you SOURCE it or EXECUTE it — all real work happens inside a nested # `bash -s`, so sourcing never changes shell options or exits your shell: # # . <(curl docmap.dotz.sh) # source (recommended) # . <(curl docmap.dotz.sh) --project # source, repo-local # . <(curl docmap.dotz.sh) -u # source, uninstall # curl docmap.dotz.sh | bash # pipe # curl docmap.dotz.sh | bash -s -- -u # pipe, uninstall # bash install.sh [--project] [-u] # execute a local copy # # Args after the URL are forwarded to the installer (see --help). # When sourced via `. <(curl ...)`, the sourcing shell can pass the process-substitution # path (e.g. /dev/fd/11) as a positional parameter. Every real option starts with "-", so # forward only those and drop any stray path. Array form works in both bash and zsh. _docmap_args=() for _a in "$@"; do case "$_a" in -*) _docmap_args+=("$_a") ;; esac done bash -s -- "${_docmap_args[@]}" <<'DOCMAP_INSTALLER_EOF' set -euo pipefail VERSION="2.2.0" GIST_ID="55c0de759b451b6eb517a2692dca2dac" GIST_RAW="https://gist.githubusercontent.com/Kinark/${GIST_ID}/raw" GIST_GIT="https://gist.github.com/${GIST_ID}.git" STAMP=".docmap-version" # Skills to install. Gists are flat, so SKILL.md files are namespaced in the gist # (docmap.SKILL.md) and un-namespaced on disk (docmap/SKILL.md). SKILLS=("docmap" "undocmap") declare_files() { case "$1" in docmap) echo "docmap.SKILL.md:SKILL.md docmap.workflow.js:docmap.workflow.js" ;; undocmap) echo "undocmap.SKILL.md:SKILL.md undocmap.workflow.js:undocmap.workflow.js" ;; esac } # --- parse args --------------------------------------------------------------- SCOPE="user" ACTION="install" for arg in "$@"; do case "$arg" in --project|-p) SCOPE="project" ;; --user) SCOPE="user" ;; # explicit alias for the default; documents intent --uninstall|--remove|--delete|-u|-r|-d) ACTION="uninstall" ;; -h|--help) cat <<'USAGE' Usage: install.sh [options] (or: . <(curl docmap.dotz.sh) [options]) (no options) install to ~/.claude/skills/{docmap,undocmap} (user-level, every project) --project, -p target ./.claude/skills/... (this repo only) --user target ~/.claude/skills/... (default; explicit alias) --uninstall, --remove, remove both skills from the chosen scope --delete, -u, -r, -d -h, --help show this help USAGE exit 0 ;; *) echo "Unknown option: $arg" >&2; exit 2 ;; esac done if [ "$SCOPE" = "project" ]; then SKILLS_ROOT="$(pwd)/.claude/skills" else SKILLS_ROOT="${HOME}/.claude/skills" fi # --- uninstall ---------------------------------------------------------------- if [ "$ACTION" = "uninstall" ]; then echo "==> Uninstalling docmap + undocmap skills from: ${SKILLS_ROOT}" any=0 for skill in "${SKILLS[@]}"; do dest="${SKILLS_ROOT}/${skill}" if [ ! -d "$dest" ]; then echo " - ${skill}: not installed, skipping" continue fi # Safety: only remove a dir that actually looks like our install. if [ ! -f "$dest/${skill}.workflow.js" ] || [ ! -f "$dest/SKILL.md" ]; then echo " ! ${skill}: ${dest} does not look like a ${skill} install — refusing to delete." >&2 continue fi rm -rf "$dest" echo " ✅ removed ${dest}" any=1 done [ "$any" = 1 ] || echo " (nothing removed)" echo " Note: this does NOT touch any CLAUDE.md / .claude/rules / .claude/docmap.json that" echo " /docmap generated in your repositories — use /undocmap or delete those per-repo." exit 0 fi # --- install ------------------------------------------------------------------ echo "==> Installing docmap + undocmap skills v${VERSION} to: ${SKILLS_ROOT}" # Fetch the whole gist once (git clone), fall back to per-file curl. TMP="" cleanup() { [ -n "$TMP" ] && rm -rf "$TMP" || true; } trap cleanup EXIT fetch_all_via_git() { command -v git >/dev/null 2>&1 || return 1 TMP="$(mktemp -d)" git clone --depth 1 --quiet "$GIST_GIT" "$TMP" 2>/dev/null || return 1 return 0 } get_file() { # get_file local name="$1" out="$2" if [ -n "$TMP" ] && [ -f "$TMP/$name" ]; then cp "$TMP/$name" "$out"; return 0 fi command -v curl >/dev/null 2>&1 || return 1 curl -fsSL "${GIST_RAW}/${name}" -o "$out" || return 1 } fetch_all_via_git || echo " (git clone unavailable — falling back to curl per file)" for skill in "${SKILLS[@]}"; do dest="${SKILLS_ROOT}/${skill}" mkdir -p "$dest" echo " - ${skill}" for pair in $(declare_files "$skill"); do src="${pair%%:*}"; dst="${pair##*:}" get_file "$src" "$dest/$dst" || { echo "ERROR: failed to fetch $src" >&2; exit 1; } [ -s "$dest/$dst" ] || { echo "ERROR: $dest/$dst is empty" >&2; exit 1; } done # version stamp per skill INSTALLED_AT="$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo unknown)" printf 'docmap-suite v%s\ninstalled: %s\nsource: https://gist.github.com/Kinark/%s\n' \ "$VERSION" "$INSTALLED_AT" "$GIST_ID" > "$dest/$STAMP" # optional syntax check (non-fatal) if command -v node >/dev/null 2>&1; then node --check "$dest/${skill}.workflow.js" 2>/dev/null \ && echo " workflow syntax OK" \ || echo " ! node --check flagged ${skill}.workflow.js (may still run under Claude Code)" >&2 fi done cat </dev/null || true