claude/lib/detect-plugins.sh
bastien e4f4edc121 feat(caveman): full install — plugin + standalone hooks + MCP scaffold
Wires JuliusBrussee/caveman into the always-on tier alongside
security-guidance and superpowers. Caveman compresses Claude's output
tokens (~75%) by speaking like a caveman while keeping technical
substance. Three layers:

  1. Plugin (caveman@caveman, marketplace JuliusBrussee/caveman)
     — adds /caveman, /caveman-commit, /caveman-review, /caveman-stats,
       /caveman-help, /cavecrew, /compress + 3 cavecrew agents +
       SessionStart/UserPromptSubmit hooks from the plugin path.
  2. Standalone hooks (statusline + stats badge) deployed by
     caveman's own hooks/install.sh into ~/.claude/hooks/. Paths in
     settings.json normalized to ~/.claude/hooks/... so this user's
     home dir doesn't leak across machines.
  3. caveman-shrink MCP proxy — NOT auto-registered. The bare proxy
     fails health checks because it requires an upstream MCP server
     to wrap. install-plugins.sh STEP 5.5 prints a snippet showing how
     to register a wrapped entry (e.g. caveman-shrink-fs) when the user
     decides which upstream to compress.

New helper enable_plugin() for explicit always-on activation —
'claude plugin install' only copies into cache, doesn't write
enabledPlugins. Idempotent via Python json check.

doctor.sh adds detect_caveman / detect_caveman_hooks / detect_caveman_shrink
checks plus a 300t passive-cost adder. update-all.sh refreshes hook
files via the upstream installer's --force mode.

.gitignore covers caveman runtime files materialized into hooks/
because ~/.claude/hooks is symlinked to this repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:02:47 +02:00

126 lines
3.9 KiB
Bash

#!/usr/bin/env bash
# ============================================================
# lib/detect-plugins.sh — Single source of truth for plugin detection
# Sourced by: session-start.sh, doctor.sh, install-plugins.sh
#
# Each function returns 0 (detected) or 1 (not detected).
# No output — callers handle messaging.
# ============================================================
# --- Always-on plugins ---
detect_rtk() {
command -v rtk &>/dev/null
}
detect_superpowers() {
# Fast check: filesystem (plugin cache)
local cache_dir="$HOME/.claude/plugins/cache"
if [ -d "$cache_dir" ]; then
compgen -G "$cache_dir"/*superpowers* &>/dev/null && return 0
fi
# Slow fallback: CLI (only if fast check fails)
claude plugin list 2>/dev/null | grep -qi "superpowers" && return 0
return 1
}
detect_security_guidance() {
local cache_dir="$HOME/.claude/plugins/cache"
[ -d "$cache_dir" ] && compgen -G "$cache_dir"/*security-guidance* &>/dev/null
}
# --- Toggle plugins ---
detect_gstack() {
# gstack is exposed via per-skill symlinks (browse, canary, qa, …);
# the legacy top-level symlink was removed to avoid duplicate entries.
# Detect by checking any of its individual skills.
[ -L "$HOME/.claude/skills/browse" ] || [ -L "$HOME/.claude/skills/qa" ]
}
detect_gsd() {
# GSD v2 (gsd-pi) is a standalone CLI, not a Claude Code plugin.
# Detection: check for 'gsd' binary in PATH.
command -v gsd &>/dev/null
}
detect_plugin_dev() {
# plugin-dev replaces the old "skill-creator" reference
local cache_dir="$HOME/.claude/plugins/cache"
[ -d "$cache_dir" ] && compgen -G "$cache_dir"/*plugin-dev* &>/dev/null
}
detect_uiux_pro_max() {
local cache_dir="$HOME/.claude/plugins/cache"
[ -d "$cache_dir" ] && compgen -G "$cache_dir"/*ui-ux-pro-max* &>/dev/null
}
detect_context7() {
# Context7 CLI (ctx7) — installed globally via npm
command -v ctx7 &>/dev/null
}
detect_graphifyy() {
# Graphifyy — codebase knowledge graph, installed via pipx
command -v graphify &>/dev/null
}
detect_caveman() {
# Caveman — output-token compression via caveman-speak (marketplace plugin)
local cache_dir="$HOME/.claude/plugins/cache"
[ -d "$cache_dir" ] && compgen -G "$cache_dir"/*caveman* &>/dev/null
}
# True if a plugin is registered as enabled in settings.json's
# enabledPlugins map. Filesystem only (no subprocess to claude CLI).
# Argument is the full "name@marketplace" key.
plugin_enabled() {
local key="$1"
[ -f "$HOME/.claude/settings.json" ] || return 1
grep -qE "\"${key}\"[[:space:]]*:[[:space:]]*true" "$HOME/.claude/settings.json"
}
detect_caveman_hooks() {
# Standalone hooks (statusline + stats) deployed by caveman hooks/install.sh
[ -f "$HOME/.claude/hooks/caveman-statusline.sh" ]
}
detect_caveman_shrink() {
# caveman-shrink is a proxy — only valid when registered with an
# upstream wrapper (e.g. caveman-shrink-fs:, caveman-shrink-github:).
# Bare 'caveman-shrink:' fails health checks and is treated as missing.
command -v claude &>/dev/null \
&& claude mcp list 2>/dev/null | grep -q '^caveman-shrink-'
}
# --- Plan detection ---
detect_plan() {
# Detect Claude plan: max, pro, or free.
# Checks ~/.claude.json for model access hints.
# Returns plan name on stdout, always exits 0.
local claude_json="$HOME/.claude.json"
if [ -f "$claude_json" ]; then
# Max plan: has opus model access or max flag
if grep -q '"planType".*"max"' "$claude_json" 2>/dev/null; then
echo "max"; return 0
fi
# Check cached features for max indicators
if grep -q '"tengu_cobalt_compass": true' "$claude_json" 2>/dev/null \
&& grep -q '"tengu_harbor": true' "$claude_json" 2>/dev/null; then
echo "max"; return 0
fi
fi
# Fallback: check if claude CLI reports plan
local plan
plan=$(claude config get planType 2>/dev/null || true)
case "$plan" in
max|Max|MAX) echo "max" ;;
pro|Pro|PRO) echo "pro" ;;
free|Free|FREE) echo "free" ;;
*) echo "pro" ;; # default assumption
esac
}