install-plugins.sh 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. #!/usr/bin/env bash
  2. # ============================================================
  3. # Claude Code — Plugin installer
  4. # Run this after a fresh clone to reinstall all plugins
  5. # and their prerequisites on a new machine.
  6. #
  7. # Supports: Linux (apt/dnf/pacman), macOS (brew)
  8. # ============================================================
  9. set -euo pipefail
  10. RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
  11. ok() { echo -e "${GREEN}✓${NC} $1"; }
  12. warn() { echo -e "${YELLOW}⚠${NC} $1"; }
  13. info() { echo -e "${BLUE}→${NC} $1"; }
  14. err() { echo -e "${RED}✗${NC} $1"; }
  15. REPO="$(cd "$(dirname "$0")" && pwd)"
  16. # Log to file for post-mortem debugging (terminal output unchanged)
  17. LOG_FILE="$REPO/install-$(date +%Y%m%d-%H%M%S).log"
  18. if touch "$LOG_FILE" 2>/dev/null; then
  19. exec > >(tee -a "$LOG_FILE") 2>&1
  20. info "Logging to $LOG_FILE"
  21. else
  22. warn "Cannot write log to $REPO — continuing without log file"
  23. fi
  24. # Load shared detection library
  25. # shellcheck source=lib/detect-plugins.sh
  26. source "$REPO/lib/detect-plugins.sh"
  27. # Read pinned version from plugins.lock.json
  28. # Usage: pinned_version "rtk" → prints version string or "latest"
  29. pinned_version() {
  30. local key="$1"
  31. if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then
  32. python3 -c "
  33. import json, sys
  34. with open('$REPO/plugins.lock.json') as f:
  35. d = json.load(f)
  36. v = d.get('$key', {}).get('version', 'latest')
  37. print(v)
  38. " 2>/dev/null || echo "latest"
  39. else
  40. echo "latest"
  41. fi
  42. }
  43. # ============================================================
  44. # DETECT OS
  45. # ============================================================
  46. OS="unknown"
  47. if [[ "$OSTYPE" == "darwin"* ]]; then
  48. OS="macos"
  49. elif command -v apt-get &>/dev/null; then
  50. OS="linux-apt"
  51. elif command -v dnf &>/dev/null; then
  52. OS="linux-dnf"
  53. elif command -v pacman &>/dev/null; then
  54. OS="linux-pacman"
  55. fi
  56. echo ""
  57. echo "╔══════════════════════════════════════════════════════════╗"
  58. echo "║ Claude Code — Plugin & Tool Installer ║"
  59. echo "╚══════════════════════════════════════════════════════════╝"
  60. echo ""
  61. info "OS: $OS | Repo: $REPO"
  62. echo ""
  63. # ============================================================
  64. # STEP 1 — PREREQUISITES
  65. # ============================================================
  66. echo "── Step 1: Prerequisites ───────────────────────────────────"
  67. echo ""
  68. # --- git ---
  69. if command -v git &>/dev/null; then
  70. ok "git $(git --version | awk '{print $3}')"
  71. else
  72. info "Installing git..."
  73. case $OS in
  74. macos) brew install git ;;
  75. linux-apt) sudo apt-get install -y git ;;
  76. linux-dnf) sudo dnf install -y git ;;
  77. linux-pacman) sudo pacman -S --noconfirm git ;;
  78. *) err "Cannot auto-install git on $OS — install manually"; exit 1 ;;
  79. esac
  80. ok "git installed"
  81. fi
  82. # --- Node.js (>=18) ---
  83. NODE_OK=false
  84. if command -v node &>/dev/null; then
  85. NODE_VER=$(node --version | sed 's/v//' | cut -d. -f1)
  86. if [ "$NODE_VER" -ge 22 ]; then
  87. ok "Node.js $(node --version)"; NODE_OK=true
  88. else
  89. warn "Node.js $(node --version) is too old (need >=22 — GSD v2 requires it)"
  90. fi
  91. fi
  92. if [ "$NODE_OK" = false ]; then
  93. info "Installing Node.js 22 LTS..."
  94. case $OS in
  95. macos)
  96. brew install node@22
  97. export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
  98. ;;
  99. linux-apt)
  100. curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
  101. sudo apt-get install -y nodejs
  102. ;;
  103. linux-dnf)
  104. curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash -
  105. sudo dnf install -y nodejs
  106. ;;
  107. linux-pacman)
  108. sudo pacman -S --noconfirm nodejs npm
  109. ;;
  110. *) warn "Cannot auto-install Node.js on $OS — install from https://nodejs.org" ;;
  111. esac
  112. if command -v node &>/dev/null; then
  113. ok "Node.js $(node --version)"
  114. else
  115. err "Node.js install failed"
  116. fi
  117. fi
  118. # --- Rust + Cargo (for RTK) ---
  119. if command -v cargo &>/dev/null; then
  120. ok "Rust/Cargo $(cargo --version | awk '{print $2}')"
  121. else
  122. info "Installing Rust (rustup)..."
  123. curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
  124. source "$HOME/.cargo/env"
  125. ok "Rust installed: $(cargo --version)"
  126. fi
  127. # --- Python 3 ---
  128. if command -v python3 &>/dev/null; then
  129. ok "Python $(python3 --version)"
  130. else
  131. info "Installing Python 3..."
  132. case $OS in
  133. macos) brew install python3 ;;
  134. linux-apt) sudo apt-get install -y python3 ;;
  135. linux-dnf) sudo dnf install -y python3 ;;
  136. linux-pacman) sudo pacman -S --noconfirm python ;;
  137. *) warn "Cannot auto-install Python on $OS" ;;
  138. esac
  139. fi
  140. # --- pipx (for Graphifyy) ---
  141. if command -v pipx &>/dev/null; then
  142. ok "pipx $(pipx --version 2>/dev/null)"
  143. else
  144. info "Installing pipx..."
  145. case $OS in
  146. macos) brew install pipx ;;
  147. linux-apt) sudo apt-get install -y pipx ;;
  148. linux-dnf) sudo dnf install -y pipx ;;
  149. linux-pacman) sudo pacman -S --noconfirm python-pipx ;;
  150. *) warn "Cannot auto-install pipx on $OS" ;;
  151. esac
  152. pipx ensurepath 2>/dev/null || true
  153. fi
  154. # --- shellcheck ---
  155. if command -v shellcheck &>/dev/null; then
  156. ok "shellcheck $(shellcheck --version 2>/dev/null | grep '^version:' | awk '{print $2}')"
  157. else
  158. info "Installing shellcheck..."
  159. case $OS in
  160. macos) brew install shellcheck ;;
  161. linux-apt) sudo apt-get install -y shellcheck ;;
  162. linux-dnf) sudo dnf install -y shellcheck ;;
  163. linux-pacman) sudo pacman -S --noconfirm shellcheck ;;
  164. *)
  165. # Binary fallback for systems without package manager access
  166. ARCH=$(uname -m)
  167. if curl -sL "https://github.com/koalaman/shellcheck/releases/download/v0.10.0/shellcheck-v0.10.0.linux.${ARCH}.tar.xz" | tar -xJ --strip-components=1 -C "$HOME/.local/bin" "shellcheck-v0.10.0/shellcheck" 2>/dev/null; then
  168. chmod +x "$HOME/.local/bin/shellcheck"
  169. ok "shellcheck installed (binary fallback)"
  170. else
  171. warn "Cannot auto-install shellcheck on $OS"
  172. fi
  173. ;;
  174. esac
  175. if command -v shellcheck &>/dev/null; then
  176. ok "shellcheck installed"
  177. else
  178. warn "shellcheck install failed"
  179. fi
  180. fi
  181. # --- Claude Code CLI ---
  182. if command -v claude &>/dev/null; then
  183. ok "Claude Code $(claude --version 2>/dev/null | head -1)"
  184. else
  185. err "Claude Code not installed. Install from https://code.claude.com then re-run."
  186. exit 1
  187. fi
  188. echo ""
  189. # ============================================================
  190. # STEP 2 — GSTACK SUBMODULE
  191. # ============================================================
  192. echo "── Step 2: GStack submodule ─────────────────────────────────"
  193. echo ""
  194. # Note: GStack is managed as a git submodule in this repo.
  195. # It lives at skills-external/gstack/ and is symlinked to ~/.claude/skills/gstack/
  196. # by link.sh. Never clone it separately — use the submodule.
  197. #
  198. # First-time setup:
  199. # git submodule update --init --recursive
  200. # Update to latest:
  201. # git submodule update --remote skills-external/gstack
  202. # cd skills-external/gstack && ./setup
  203. # git add skills-external/gstack && git commit -m "chore: update gstack"
  204. GSTACK_DIR="$REPO/skills-external/gstack"
  205. if [ ! -d "$GSTACK_DIR/.git" ] && [ ! -f "$GSTACK_DIR/.git" ]; then
  206. info "Initializing GStack submodule..."
  207. cd "$REPO"
  208. git submodule update --init --recursive
  209. cd - > /dev/null
  210. fi
  211. if [ -d "$GSTACK_DIR" ]; then
  212. # --- bun (required by GStack ./setup) ---
  213. if ! command -v bun &>/dev/null; then
  214. info "Installing bun (required by GStack)..."
  215. BUN_VERSION="1.3.10"
  216. tmpfile=$(mktemp)
  217. curl -fsSL "https://bun.sh/install" -o "$tmpfile"
  218. BUN_VERSION="$BUN_VERSION" bash "$tmpfile" && rm -f "$tmpfile"
  219. export PATH="$HOME/.bun/bin:$PATH"
  220. if command -v bun &>/dev/null; then
  221. ok "bun $(bun --version)"
  222. else
  223. err "bun install failed"
  224. fi
  225. else
  226. ok "bun $(bun --version)"
  227. fi
  228. info "Running GStack setup..."
  229. if [ -x "$GSTACK_DIR/setup" ]; then
  230. if (cd "$GSTACK_DIR" && ./setup); then
  231. : # setup succeeded
  232. else
  233. warn "GStack ./setup failed — check output above"
  234. fi
  235. else
  236. warn "GStack ./setup not found or not executable — skipping"
  237. fi
  238. # Default policy: gstack is installed but DISABLED — enable on demand
  239. # via `bash lib/toggle-external.sh enable gstack`. Rationale: gstack
  240. # ships ~40 skills that all load into context; keep them off until
  241. # the user signals a project where they matter (browser QA, deploy).
  242. if [ -x "$REPO/lib/toggle-external.sh" ] \
  243. && [ "$(bash "$REPO/lib/toggle-external.sh" status gstack 2>/dev/null)" = "enabled" ]; then
  244. info "Disabling gstack by default (no context cost until enabled)..."
  245. bash "$REPO/lib/toggle-external.sh" disable gstack >/dev/null
  246. ok "gstack installed, disabled — enable with: bash lib/toggle-external.sh enable gstack"
  247. else
  248. ok "GStack ready (submodule initialized, symlinks staged)"
  249. fi
  250. else
  251. warn "GStack submodule directory not found after init — check .gitmodules"
  252. fi
  253. echo ""
  254. # ============================================================
  255. # STEP 3 — RTK
  256. # ============================================================
  257. echo "── Step 3: RTK — Rust Token Killer ─────────────────────────"
  258. echo ""
  259. if command -v rtk &>/dev/null; then
  260. ok "rtk already installed ($(rtk --version 2>/dev/null | head -1))"
  261. else
  262. RTK_VER=$(pinned_version "rtk")
  263. if [ "$RTK_VER" != "latest" ]; then
  264. info "Installing RTK $RTK_VER (pinned in plugins.lock.json)..."
  265. cargo install --git https://github.com/rtk-ai/rtk --tag "$RTK_VER"
  266. else
  267. info "Installing RTK (latest — consider pinning in plugins.lock.json)..."
  268. cargo install --git https://github.com/rtk-ai/rtk
  269. fi
  270. fi
  271. # Only init if not already configured (avoids overwriting custom RTK config)
  272. if ! grep -q "rtk" "$HOME/.claude/settings.json" 2>/dev/null; then
  273. info "Configuring RTK PreToolUse hook (global)..."
  274. rtk init -g --auto-patch
  275. ok "RTK configured"
  276. else
  277. ok "RTK hook already present in settings.json — skipping init"
  278. fi
  279. echo ""
  280. # ============================================================
  281. # STEP 4 — GSD v2
  282. # ============================================================
  283. # GSD v2 (gsd-pi) is a standalone CLI built on the Pi SDK.
  284. # It is NOT a Claude Code plugin — it runs as an external process ('gsd' command).
  285. # Usage: run 'gsd' in your terminal from a project directory.
  286. # Slash commands (/gsd auto, /gsd status, etc.) are internal to a GSD session.
  287. echo "── Step 4: GSD v2 — gsd-pi ─────────────────────────────────"
  288. echo ""
  289. if command -v gsd &>/dev/null; then
  290. ok "gsd already installed ($(gsd --version 2>/dev/null | head -1 || echo 'installed'))"
  291. else
  292. GSD_VER=$(pinned_version "gsd")
  293. if [ "$GSD_VER" != "latest" ]; then
  294. info "Installing gsd-pi@${GSD_VER} (pinned in plugins.lock.json)..."
  295. npm install -g "gsd-pi@${GSD_VER}"
  296. else
  297. info "Installing gsd-pi@latest (consider pinning in plugins.lock.json)..."
  298. npm install -g gsd-pi
  299. fi
  300. if command -v gsd &>/dev/null; then
  301. ok "GSD v2 installed ($(gsd --version 2>/dev/null | head -1))"
  302. else
  303. err "GSD v2 install failed — check npm output above"
  304. fi
  305. fi
  306. echo ""
  307. # ============================================================
  308. # STEP 5 — MARKETPLACE PLUGINS (user scope, explicit)
  309. # ============================================================
  310. # All claude plugin install commands use --scope user to ensure
  311. # they install to ~/.claude/plugins/ regardless of working directory.
  312. echo "── Step 5: Marketplace plugins (scope: user) ────────────────"
  313. echo ""
  314. install_plugin() {
  315. local name="$1"
  316. local source="$2"
  317. if claude plugin list 2>/dev/null | grep -qi "$name"; then
  318. ok "$name (already installed)"
  319. return
  320. fi
  321. info "Installing $name..."
  322. if claude plugin install --scope user "$name@$source" 2>/dev/null; then
  323. ok "$name"
  324. else
  325. err "$name — FAILED (run manually: claude plugin install --scope user $name@$source)"
  326. fi
  327. }
  328. # Enable a marketplace plugin in user scope. `claude plugin install` only
  329. # copies the plugin into ~/.claude/plugins/cache — it does NOT register
  330. # it in settings.json's enabledPlugins map. Without an explicit enable,
  331. # the plugin sits dormant. Use this for plugins that should be ALWAYS ON
  332. # (security-guidance, superpowers, caveman). Idempotent: skips if already
  333. # present in enabledPlugins.
  334. enable_plugin() {
  335. local name="$1"
  336. local source="$2"
  337. local key="${name}@${source}"
  338. if [ -f "$HOME/.claude/settings.json" ] && command -v python3 &>/dev/null; then
  339. if python3 -c "
  340. import json, sys
  341. with open('$HOME/.claude/settings.json') as f:
  342. d = json.load(f)
  343. sys.exit(0 if d.get('enabledPlugins', {}).get('$key') else 1)
  344. " 2>/dev/null; then
  345. ok "$name (already enabled)"
  346. return
  347. fi
  348. fi
  349. info "Enabling $name..."
  350. if claude plugin enable "$key" 2>/dev/null; then
  351. ok "$name enabled"
  352. else
  353. err "$name enable failed — run manually: claude plugin enable $key"
  354. fi
  355. }
  356. # Anthropic bundled plugins (from anthropics/claude-code repo)
  357. # These are NOT in claude-plugins-official — they require the claude-code marketplace
  358. info "Adding Anthropic bundled plugins marketplace..."
  359. claude plugin marketplace add anthropics/claude-code 2>/dev/null || true
  360. info "Adding Anthropic skills marketplace..."
  361. claude plugin marketplace add anthropics/skills 2>/dev/null || true
  362. install_plugin "security-guidance" "claude-code-plugins"
  363. enable_plugin "security-guidance" "claude-code-plugins"
  364. # skill-creator is in "example-skills" plugin from anthropics/skills marketplace
  365. # (not in claude-code marketplace — it's a separate repo)
  366. install_plugin "example-skills" "anthropic-agent-skills"
  367. install_plugin "pr-review-toolkit" "claude-code-plugins"
  368. install_plugin "plugin-dev" "claude-code-plugins"
  369. echo ""
  370. # Superpowers (always on)
  371. info "Adding Superpowers marketplace..."
  372. claude plugin marketplace add obra/superpowers-marketplace 2>/dev/null || true
  373. install_plugin "superpowers" "superpowers-marketplace"
  374. enable_plugin "superpowers" "superpowers-marketplace"
  375. echo ""
  376. # UI/UX Pro Max (toggle)
  377. info "Adding UI/UX Pro Max marketplace..."
  378. claude plugin marketplace add nextlevelbuilder/ui-ux-pro-max-skill 2>/dev/null || true
  379. install_plugin "ui-ux-pro-max" "ui-ux-pro-max-skill"
  380. echo ""
  381. # ============================================================
  382. # STEP 5.5 — CAVEMAN (full: plugin + standalone hooks + MCP shrink)
  383. # ============================================================
  384. # Caveman compresses output tokens (~75%) via caveman-speak. The "full"
  385. # install layers three things on top of each other:
  386. # 1. Plugin — /caveman command, cavecrew subagents, mode tracker hooks
  387. # 2. Hooks — statusline + stats badge written into ~/.claude/
  388. # 3. MCP shrink — caveman-shrink proxy that compresses tool input tokens
  389. # Per-repo rule files (--with-init / --all) are skipped — they would litter
  390. # this config repo with caveman-rules.md noise meant for project repos.
  391. echo "── Step 5.5: Caveman (full: plugin + hooks + MCP shrink) ────"
  392. echo ""
  393. info "Adding Caveman marketplace..."
  394. claude plugin marketplace add JuliusBrussee/caveman 2>/dev/null || true
  395. install_plugin "caveman" "caveman"
  396. enable_plugin "caveman" "caveman"
  397. # Standalone hooks (statusline + stats badge). The plugin already wires
  398. # SessionStart + UserPromptSubmit hooks from its own path; this installer
  399. # adds the statusLine config and ~/.claude/hooks/caveman-stats.js that
  400. # the plugin doesn't carry.
  401. CAVEMAN_HOOKS_URL="https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.sh"
  402. if [ -f "$HOME/.claude/hooks/caveman-statusline.sh" ] \
  403. && grep -q 'caveman-statusline' "$HOME/.claude/settings.json" 2>/dev/null; then
  404. ok "Caveman standalone hooks already installed"
  405. else
  406. info "Installing Caveman standalone hooks (statusline + stats)..."
  407. CAVEMAN_HOOKS_TMP="$(mktemp -t caveman-hooks-XXXXXX.sh)"
  408. if curl -fsSL "$CAVEMAN_HOOKS_URL" -o "$CAVEMAN_HOOKS_TMP" \
  409. && bash "$CAVEMAN_HOOKS_TMP"; then
  410. ok "Caveman hooks installed"
  411. # Caveman's hooks installer hardcodes the absolute home path
  412. # ($HOME/.claude/hooks/caveman-*.js) into settings.json. The repo's
  413. # settings.json is symlinked to ~/.claude/settings.json — committing
  414. # the absolute path would leak this user's username to every machine
  415. # that clones the repo. Rewrite to portable ~/.claude/hooks/... form.
  416. if [ -f "$HOME/.claude/settings.json" ] && command -v python3 &>/dev/null; then
  417. python3 - "$HOME/.claude/settings.json" "$HOME" <<'PY'
  418. import json, sys, re
  419. path, home = sys.argv[1], sys.argv[2]
  420. with open(path) as f:
  421. data = json.load(f)
  422. def rewrite(node):
  423. if isinstance(node, dict):
  424. for k, v in node.items():
  425. if k == "command" and isinstance(v, str) and "caveman" in v:
  426. node[k] = re.sub(rf'"?{re.escape(home)}/.claude/hooks/(caveman-[^"\s]+)"?',
  427. r'~/.claude/hooks/\1', v)
  428. else:
  429. rewrite(v)
  430. elif isinstance(node, list):
  431. for item in node:
  432. rewrite(item)
  433. rewrite(data)
  434. with open(path, "w") as f:
  435. json.dump(data, f, indent=2)
  436. PY
  437. ok "Caveman hook paths normalized to ~/.claude/hooks/... (portable)"
  438. fi
  439. else
  440. err "Caveman hooks install failed — re-run manually: bash <(curl -fsSL $CAVEMAN_HOOKS_URL)"
  441. fi
  442. rm -f "$CAVEMAN_HOOKS_TMP"
  443. fi
  444. # MCP shrink — caveman-shrink is a *proxy* that wraps an upstream MCP
  445. # server and compresses prose fields in its responses. It cannot run
  446. # standalone (it errors with "missing upstream command"). We don't auto-
  447. # register it: the user must pick an upstream MCP server to wrap (e.g.
  448. # the filesystem server, the GitHub server, …) and add a wrapped entry
  449. # to ~/.claude.json manually. Print the snippet so they can copy-paste.
  450. if claude mcp list 2>/dev/null | grep -q '^caveman-shrink-'; then
  451. ok "caveman-shrink wrapper already registered (custom upstream)"
  452. else
  453. info "caveman-shrink MCP — manual setup needed (it's a proxy, needs an upstream):"
  454. cat <<'EOF'
  455. Add a wrapped MCP entry to ~/.claude.json under "mcpServers", e.g.
  456. to compress filesystem-server responses:
  457. {
  458. "mcpServers": {
  459. "caveman-shrink-fs": {
  460. "command": "npx",
  461. "args": [
  462. "-y", "caveman-shrink",
  463. "npx", "-y", "@modelcontextprotocol/server-filesystem",
  464. "/path/to/dir"
  465. ]
  466. }
  467. }
  468. }
  469. Or via CLI (replace upstream with your target server):
  470. claude mcp add caveman-shrink-fs --scope user -- \
  471. npx -y caveman-shrink npx -y @modelcontextprotocol/server-filesystem /path
  472. EOF
  473. warn "caveman-shrink not auto-registered (would fail health check without upstream)"
  474. fi
  475. echo ""
  476. # ============================================================
  477. # STEP 6 — CONTEXT7 CLI (ctx7)
  478. # ============================================================
  479. echo "── Step 6: Context7 CLI ─────────────────────────────────────"
  480. echo ""
  481. if command -v ctx7 &>/dev/null; then
  482. ok "ctx7 already installed ($(ctx7 --version 2>/dev/null | head -1 || echo 'installed'))"
  483. else
  484. CTX7_VER=$(pinned_version "ctx7")
  485. if [ "$CTX7_VER" != "latest" ]; then
  486. info "Installing ctx7@${CTX7_VER} (pinned in plugins.lock.json)..."
  487. npm install -g "ctx7@${CTX7_VER}"
  488. else
  489. info "Installing ctx7@latest (consider pinning in plugins.lock.json)..."
  490. npm install -g ctx7
  491. fi
  492. if command -v ctx7 &>/dev/null; then
  493. ok "ctx7 installed ($(ctx7 --version 2>/dev/null | head -1))"
  494. else
  495. err "ctx7 install failed — run manually: npm install -g ctx7"
  496. fi
  497. fi
  498. # Suggest setup for Claude Code integration (optional — ctx7 also works standalone)
  499. if command -v ctx7 &>/dev/null; then
  500. info "Run 'ctx7 setup --claude' to configure Context7 for Claude Code"
  501. info "Or use ctx7 standalone: ctx7 docs /vercel/next.js \"middleware\""
  502. info "Free higher rate limits: ctx7 login (OAuth) or --api-key from context7.com/dashboard"
  503. fi
  504. # ============================================================
  505. # STEP 7 — GRAPHIFYY (codebase knowledge graph)
  506. # ============================================================
  507. echo "── Step 7: Graphifyy — Knowledge Graph ──────────────────────"
  508. echo ""
  509. if command -v graphify &>/dev/null; then
  510. ok "graphify already installed"
  511. else
  512. info "Installing graphifyy via pipx..."
  513. if pipx install graphifyy 2>/dev/null; then
  514. ok "graphifyy installed"
  515. else
  516. err "graphifyy install failed — run manually: pipx install graphifyy"
  517. fi
  518. fi
  519. if command -v graphify &>/dev/null; then
  520. info "Running graphify install (dependencies)..."
  521. graphify install 2>/dev/null || warn "graphify install failed — run manually"
  522. info "Configuring Claude Code integration..."
  523. graphify claude install 2>/dev/null || warn "graphify claude install failed — run manually"
  524. ok "Graphifyy configured for Claude Code"
  525. fi
  526. echo ""
  527. # ============================================================
  528. # STEP 8 — EMIL DESIGN ENG (UI polish / animation skill)
  529. # ============================================================
  530. echo "── Step 8: Emil Design Engineering ─────────────────────────"
  531. echo ""
  532. EMIL_DIR="$REPO/skills-external/emil-design-eng"
  533. EMIL_URL="https://raw.githubusercontent.com/emilkowalski/skill/main/skills/emil-design-eng/SKILL.md"
  534. mkdir -p "$EMIL_DIR"
  535. if [ -f "$EMIL_DIR/SKILL.md" ]; then
  536. ok "emil-design-eng already downloaded"
  537. else
  538. info "Downloading SKILL.md from emilkowalski/skill..."
  539. if curl -fsSL "$EMIL_URL" -o "$EMIL_DIR/SKILL.md"; then
  540. ok "emil-design-eng installed"
  541. else
  542. err "emil-design-eng download failed — try: curl -fsSL $EMIL_URL -o $EMIL_DIR/SKILL.md"
  543. fi
  544. fi
  545. # Symlink handled by link.sh
  546. if [ -L "$HOME/.claude/skills/emil-design-eng" ]; then
  547. ok "emil-design-eng symlink OK"
  548. else
  549. info "Symlinking — will be created by link.sh"
  550. fi
  551. echo ""
  552. # ============================================================
  553. # STEP 8.5 — EXTERNAL SKILLS (npx skills add …)
  554. # ============================================================
  555. # Cross-agent skills distributed via the `skills` npm package
  556. # (vercel-labs/skills). Installed into ~/.agents/skills/ and
  557. # symlinked into $REPO/skills/ by link.sh using absolute paths.
  558. echo "── Step 8.5: External skills via npx ──────────────────────"
  559. echo ""
  560. NPX_SKILLS=(
  561. "alchaincyf/darwin-skill"
  562. "alchaincyf/find-skills"
  563. )
  564. if ! command -v npx &>/dev/null; then
  565. warn "npx not available — skipping external skills"
  566. else
  567. for _src in "${NPX_SKILLS[@]}"; do
  568. _name="${_src##*/}"
  569. _dst="$HOME/.agents/skills/$_name"
  570. if [ -d "$_dst" ]; then
  571. ok "$_name already installed ($_dst)"
  572. continue
  573. fi
  574. info "Installing $_name via: npx -y skills add $_src"
  575. if npx -y skills add "$_src" 2>/dev/null; then
  576. if [ -d "$_dst" ]; then
  577. ok "$_name installed"
  578. else
  579. warn "$_name installed but not at expected path $_dst"
  580. fi
  581. else
  582. err "$_name install failed — run manually: npx -y skills add $_src"
  583. fi
  584. done
  585. fi
  586. echo ""
  587. # ============================================================
  588. # STEP 8.7 — MAGIC MCP (21st-dev) — installed but DISABLED by default
  589. # ============================================================
  590. # Magic MCP is a stdio MCP server providing UI component generation
  591. # from 21st.dev. Toggled via lib/toggle-external.sh (same interface as
  592. # gstack, emil-design-eng, etc.). Registered in Claude Code user scope.
  593. #
  594. # Default policy: DISABLED at install time. Rationale: MCP tools load
  595. # into every Claude Code session and consume context tokens. Enable
  596. # only when you're actively using Magic.
  597. #
  598. # API key: read from $REPO/.env (MAGIC_API_KEY=...) — NEVER committed.
  599. # Template: $REPO/.env.example. Get a key at https://21st.dev/magic
  600. echo "── Step 8.7: Magic MCP (21st-dev) ──────────────────────────"
  601. echo ""
  602. if [ -x "$REPO/lib/toggle-external.sh" ]; then
  603. MAGIC_STATUS="$(bash "$REPO/lib/toggle-external.sh" status magic 2>/dev/null || echo missing)"
  604. if [ "$MAGIC_STATUS" = "enabled" ]; then
  605. info "Disabling magic MCP by default (enable on demand)..."
  606. bash "$REPO/lib/toggle-external.sh" disable magic >/dev/null
  607. ok "magic MCP disabled — enable with: bash lib/toggle-external.sh enable magic"
  608. else
  609. ok "magic MCP disabled (default)"
  610. fi
  611. if [ ! -f "$REPO/.env" ] || ! grep -q '^MAGIC_API_KEY=' "$REPO/.env" 2>/dev/null; then
  612. warn "MAGIC_API_KEY not found in $REPO/.env — copy .env.example and set your key before enabling"
  613. fi
  614. else
  615. warn "lib/toggle-external.sh not found or not executable — skipping"
  616. fi
  617. echo ""
  618. # ============================================================
  619. # STEP 9 — SHELL CONFIG (alias + env vars)
  620. # ============================================================
  621. echo "── Step 9: Claude Code shell config (alias + env vars) ─────"
  622. echo ""
  623. # Detect shell profile
  624. SHELL_PROFILE=""
  625. if [ -n "${ZSH_VERSION:-}" ] || [ "$(basename "$SHELL" 2>/dev/null)" = "zsh" ]; then
  626. SHELL_PROFILE="$HOME/.zshrc"
  627. elif [ -n "${BASH_VERSION:-}" ] || [ "$(basename "$SHELL" 2>/dev/null)" = "bash" ]; then
  628. SHELL_PROFILE="$HOME/.bashrc"
  629. fi
  630. # Fallback to .profile (works with sh, dash, etc.)
  631. [ -z "$SHELL_PROFILE" ] && SHELL_PROFILE="$HOME/.profile"
  632. CLAUDE_LINES=(
  633. "alias claude='claude --effort max'"
  634. 'export CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1'
  635. )
  636. # Clean up old CLAUDE_EFFORT env var if present (replaced by alias)
  637. if grep -qF 'export CLAUDE_EFFORT=max' "$SHELL_PROFILE" 2>/dev/null; then
  638. sed -i '/export CLAUDE_EFFORT=max/d' "$SHELL_PROFILE"
  639. # Also remove orphaned comment lines left by previous installs
  640. sed -i '/^# Claude Code — added by install-plugins.sh$/{ N; /^\n$/d; }' "$SHELL_PROFILE"
  641. info "Removed old CLAUDE_EFFORT=max from $SHELL_PROFILE (replaced by alias)"
  642. fi
  643. ADDED=0
  644. for line in "${CLAUDE_LINES[@]}"; do
  645. if grep -qF "$line" "$SHELL_PROFILE" 2>/dev/null; then
  646. ok "$line (already in $SHELL_PROFILE)"
  647. else
  648. {
  649. echo ""
  650. echo "# Claude Code — added by install-plugins.sh"
  651. echo "$line"
  652. } >> "$SHELL_PROFILE"
  653. ok "$line → $SHELL_PROFILE"
  654. ADDED=1
  655. fi
  656. done
  657. if [ "$ADDED" -eq 1 ]; then
  658. info "Restart your shell or run: source $SHELL_PROFILE"
  659. fi
  660. echo ""
  661. # ============================================================
  662. # SUMMARY
  663. # ============================================================
  664. echo ""
  665. echo "╔══════════════════════════════════════════════════════════╗"
  666. echo "║ Install Summary ║"
  667. echo "╚══════════════════════════════════════════════════════════╝"
  668. echo ""
  669. echo " ALWAYS ON (installed at user scope):"
  670. echo " ✅ security-guidance — PreToolUse security hook (0 tokens) [claude-code-plugins]"
  671. echo " ✅ rtk — token compression hook (0 tokens)"
  672. echo " ✅ superpowers — brainstorm/plan/implement/debug workflow"
  673. echo " ✅ caveman — output compression (~75%) + caveman-shrink MCP (input)"
  674. echo ""
  675. echo " TOGGLE (installed but start OFF — /plugin-check recommends when needed):"
  676. echo " 🔄 gstack — disabled by default (toggle: lib/toggle-external.sh enable gstack)"
  677. echo " 🔄 gsd v2 — standalone CLI 'gsd' (gsd-pi, not a Claude Code plugin)"
  678. echo " 🔄 plugin-dev — create plugins/skills (~100 tokens) [claude-code-plugins]"
  679. echo " 🔄 pr-review-toolkit — /pr-review-toolkit:review-pr (~300 tokens) [claude-code-plugins]"
  680. echo " 🔄 ui-ux-pro-max — user scope (~400 tokens)"
  681. echo " 🔄 context7 CLI — ctx7 (npm global, standalone or MCP setup)"
  682. echo " 🔄 graphifyy — codebase knowledge graph (pipx, PreToolUse hook)"
  683. echo " 🔄 emil-design-eng — UI polish, animations, component craft (curl → symlink)"
  684. echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)"
  685. echo " 🔄 find-skills — skill discovery helper (npx skills, ~/.agents/skills/)"
  686. echo " 🔄 magic MCP — 21st-dev UI generation MCP (toggle: lib/toggle-external.sh enable magic)"
  687. echo ""
  688. echo " All plugins installed at: user scope (~/.claude/plugins/)"
  689. echo " GStack skills symlinked individually into ~/.claude/skills/ (→ submodule)"
  690. echo " Emil Design Eng at: ~/.claude/skills/emil-design-eng/ (symlink → skills-external)"
  691. echo " npx skills at: ~/.agents/skills/ (symlinked into ~/.claude/skills/)"
  692. echo ""
  693. echo " → Restart Claude Code — plugins load automatically"
  694. echo ""