From e4f4edc121d0319df8594191c4ec9c21e84ed777 Mon Sep 17 00:00:00 2001 From: bastien Date: Sun, 3 May 2026 23:02:47 +0200 Subject: [PATCH] =?UTF-8?q?feat(caveman):=20full=20install=20=E2=80=94=20p?= =?UTF-8?q?lugin=20+=20standalone=20hooks=20+=20MCP=20scaffold?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .gitignore | 12 ++++ README.md | 1 + doctor.sh | 17 ++++++ install-plugins.sh | 132 ++++++++++++++++++++++++++++++++++++++++++ lib/detect-plugins.sh | 28 +++++++++ plugins.lock.json | 9 +++ settings.json | 31 +++++++++- update-all.sh | 30 ++++++++++ 8 files changed, 259 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 870a7cd..6ba1f81 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,18 @@ skills/find-skills # Staging area used by lib/toggle-external.sh when disabling a tool skills-disabled/ +# Caveman runtime hook files — deployed by caveman hooks/install.sh into +# ~/.claude/hooks/ (which is a symlink to this repo's hooks/), so they +# materialize here. They are user-scope state, not repo source. +hooks/caveman-activate.js +hooks/caveman-config.js +hooks/caveman-mode-tracker.js +hooks/caveman-stats.js +hooks/caveman-statusline.sh +hooks/package.json +hooks/package-lock.json +hooks/node_modules/ + # Local project config (per-machine, not shared) .claude/* !.claude/tasks/ diff --git a/README.md b/README.md index ea95cd9..ffa91fe 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Install output is logged to `install-YYYYMMDD-HHMMSS.log`. | **Context7** | Plugin (toggle) | Fast-evolving libs doc lookup (Next.js, React, Prisma...). Requires a free account + API key (see install). | [context7.com](https://context7.com/) | | **pr-review-toolkit** | Plugin (toggle) | Multi-agent PR review. | [anthropics/claude-code](https://github.com/anthropics/claude-code) | | **Graphify** | Python CLI | Codebase → knowledge graph → navigable wiki. Helps Claude map and search projects efficiently. | [pypi: graphifyy](https://pypi.org/project/graphifyy/) | +| **Caveman** | Plugin (always on) + hooks | Output token compression (~75%) via caveman-speak. Full install = plugin (`/caveman`, cavecrew, caveman-commit, caveman-review, caveman-stats) + standalone hooks (statusline + stats badge). The optional `caveman-shrink` MCP proxy compresses upstream-server prose — manual config required (it wraps another MCP server). See `install-plugins.sh` STEP 5.5 for the snippet. | [JuliusBrussee/caveman](https://github.com/JuliusBrussee/caveman) | Versions are pinned in `plugins.lock.json`. To update: edit the file, then re-run `install-plugins.sh`. diff --git a/doctor.sh b/doctor.sh index 2aa55b8..2121d8c 100644 --- a/doctor.sh +++ b/doctor.sh @@ -182,6 +182,22 @@ else info "Graphifyy not installed (optional — codebase knowledge graph: pipx install graphifyy)" fi +if detect_caveman; then + pass "Caveman plugin installed" + if detect_caveman_hooks; then + pass "Caveman standalone hooks (statusline + stats) installed" + else + warn "Caveman plugin OK but standalone hooks missing — re-run install-plugins.sh" + fi + if detect_caveman_shrink; then + pass "caveman-shrink MCP wrapper registered (custom upstream)" + else + info "caveman-shrink MCP not wrapped — manual setup (proxy, needs upstream): see install-plugins.sh STEP 5.5" + fi +else + info "Caveman not installed (optional — output compression ~75%: run install-plugins.sh)" +fi + echo "" # ──────────────────────────────────────────────────────────── @@ -247,6 +263,7 @@ if detect_gstack 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 2750)); if detect_uiux_pro_max 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 400)); fi if detect_context7 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 200)); fi if detect_graphifyy 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 300)); fi +if detect_caveman 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 300)); fi TOTAL_TOKENS=$((CLAUDE_MD_TOKENS + SKILL_DESC_TOKENS + PLUGIN_TOKENS)) SESSION_BUDGET=11000 diff --git a/install-plugins.sh b/install-plugins.sh index 8a65c37..92c59f2 100644 --- a/install-plugins.sh +++ b/install-plugins.sh @@ -353,6 +353,35 @@ install_plugin() { fi } +# Enable a marketplace plugin in user scope. `claude plugin install` only +# copies the plugin into ~/.claude/plugins/cache — it does NOT register +# it in settings.json's enabledPlugins map. Without an explicit enable, +# the plugin sits dormant. Use this for plugins that should be ALWAYS ON +# (security-guidance, superpowers, caveman). Idempotent: skips if already +# present in enabledPlugins. +enable_plugin() { + local name="$1" + local source="$2" + local key="${name}@${source}" + if [ -f "$HOME/.claude/settings.json" ] && command -v python3 &>/dev/null; then + if python3 -c " +import json, sys +with open('$HOME/.claude/settings.json') as f: + d = json.load(f) +sys.exit(0 if d.get('enabledPlugins', {}).get('$key') else 1) +" 2>/dev/null; then + ok "$name (already enabled)" + return + fi + fi + info "Enabling $name..." + if claude plugin enable "$key" 2>/dev/null; then + ok "$name enabled" + else + err "$name enable failed — run manually: claude plugin enable $key" + fi +} + # Anthropic bundled plugins (from anthropics/claude-code repo) # These are NOT in claude-plugins-official — they require the claude-code marketplace info "Adding Anthropic bundled plugins marketplace..." @@ -383,6 +412,108 @@ install_plugin "ui-ux-pro-max" "ui-ux-pro-max-skill" echo "" +# ============================================================ +# STEP 5.5 — CAVEMAN (full: plugin + standalone hooks + MCP shrink) +# ============================================================ +# Caveman compresses output tokens (~75%) via caveman-speak. The "full" +# install layers three things on top of each other: +# 1. Plugin — /caveman command, cavecrew subagents, mode tracker hooks +# 2. Hooks — statusline + stats badge written into ~/.claude/ +# 3. MCP shrink — caveman-shrink proxy that compresses tool input tokens +# Per-repo rule files (--with-init / --all) are skipped — they would litter +# this config repo with caveman-rules.md noise meant for project repos. +echo "── Step 5.5: Caveman (full: plugin + hooks + MCP shrink) ────" +echo "" + +info "Adding Caveman marketplace..." +claude plugin marketplace add JuliusBrussee/caveman 2>/dev/null || true +install_plugin "caveman" "caveman" +enable_plugin "caveman" "caveman" + +# Standalone hooks (statusline + stats badge). The plugin already wires +# SessionStart + UserPromptSubmit hooks from its own path; this installer +# adds the statusLine config and ~/.claude/hooks/caveman-stats.js that +# the plugin doesn't carry. +CAVEMAN_HOOKS_URL="https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.sh" +if [ -f "$HOME/.claude/hooks/caveman-statusline.sh" ] \ + && grep -q 'caveman-statusline' "$HOME/.claude/settings.json" 2>/dev/null; then + ok "Caveman standalone hooks already installed" +else + info "Installing Caveman standalone hooks (statusline + stats)..." + CAVEMAN_HOOKS_TMP="$(mktemp -t caveman-hooks-XXXXXX.sh)" + if curl -fsSL "$CAVEMAN_HOOKS_URL" -o "$CAVEMAN_HOOKS_TMP" \ + && bash "$CAVEMAN_HOOKS_TMP"; then + ok "Caveman hooks installed" + # Caveman's hooks installer hardcodes the absolute home path + # ($HOME/.claude/hooks/caveman-*.js) into settings.json. The repo's + # settings.json is symlinked to ~/.claude/settings.json — committing + # the absolute path would leak this user's username to every machine + # that clones the repo. Rewrite to portable ~/.claude/hooks/... form. + if [ -f "$HOME/.claude/settings.json" ] && command -v python3 &>/dev/null; then + python3 - "$HOME/.claude/settings.json" "$HOME" <<'PY' +import json, sys, re +path, home = sys.argv[1], sys.argv[2] +with open(path) as f: + data = json.load(f) +def rewrite(node): + if isinstance(node, dict): + for k, v in node.items(): + if k == "command" and isinstance(v, str) and "caveman" in v: + node[k] = re.sub(rf'"?{re.escape(home)}/.claude/hooks/(caveman-[^"\s]+)"?', + r'~/.claude/hooks/\1', v) + else: + rewrite(v) + elif isinstance(node, list): + for item in node: + rewrite(item) +rewrite(data) +with open(path, "w") as f: + json.dump(data, f, indent=2) +PY + ok "Caveman hook paths normalized to ~/.claude/hooks/... (portable)" + fi + else + err "Caveman hooks install failed — re-run manually: bash <(curl -fsSL $CAVEMAN_HOOKS_URL)" + fi + rm -f "$CAVEMAN_HOOKS_TMP" +fi + +# MCP shrink — caveman-shrink is a *proxy* that wraps an upstream MCP +# server and compresses prose fields in its responses. It cannot run +# standalone (it errors with "missing upstream command"). We don't auto- +# register it: the user must pick an upstream MCP server to wrap (e.g. +# the filesystem server, the GitHub server, …) and add a wrapped entry +# to ~/.claude.json manually. Print the snippet so they can copy-paste. +if claude mcp list 2>/dev/null | grep -q '^caveman-shrink-'; then + ok "caveman-shrink wrapper already registered (custom upstream)" +else + info "caveman-shrink MCP — manual setup needed (it's a proxy, needs an upstream):" + cat <<'EOF' + Add a wrapped MCP entry to ~/.claude.json under "mcpServers", e.g. + to compress filesystem-server responses: + + { + "mcpServers": { + "caveman-shrink-fs": { + "command": "npx", + "args": [ + "-y", "caveman-shrink", + "npx", "-y", "@modelcontextprotocol/server-filesystem", + "/path/to/dir" + ] + } + } + } + + Or via CLI (replace upstream with your target server): + claude mcp add caveman-shrink-fs --scope user -- \ + npx -y caveman-shrink npx -y @modelcontextprotocol/server-filesystem /path +EOF + warn "caveman-shrink not auto-registered (would fail health check without upstream)" +fi + +echo "" + # ============================================================ # STEP 6 — CONTEXT7 CLI (ctx7) # ============================================================ @@ -593,6 +724,7 @@ echo " ALWAYS ON (installed at user scope):" echo " ✅ security-guidance — PreToolUse security hook (0 tokens) [claude-code-plugins]" echo " ✅ rtk — token compression hook (0 tokens)" echo " ✅ superpowers — brainstorm/plan/implement/debug workflow" +echo " ✅ caveman — output compression (~75%) + caveman-shrink MCP (input)" echo "" echo " TOGGLE (installed but start OFF — /plugin-check recommends when needed):" echo " 🔄 gstack — disabled by default (toggle: lib/toggle-external.sh enable gstack)" diff --git a/lib/detect-plugins.sh b/lib/detect-plugins.sh index 052bfba..2b01159 100644 --- a/lib/detect-plugins.sh +++ b/lib/detect-plugins.sh @@ -66,6 +66,34 @@ detect_graphifyy() { 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 --- diff --git a/plugins.lock.json b/plugins.lock.json index 853fb7e..a205c5e 100644 --- a/plugins.lock.json +++ b/plugins.lock.json @@ -31,5 +31,14 @@ "path": "skills/emil-design-eng/SKILL.md", "managed_by": "curl", "note": "Emil Kowalski's design engineering skill — UI polish, animations, component craft. Downloaded to skills-external/emil-design-eng/, symlinked by link.sh." + }, + "caveman": { + "source": "https://github.com/JuliusBrussee/caveman", + "marketplace": "JuliusBrussee/caveman", + "plugin_ref": "caveman@caveman", + "managed_by": "claude plugin + standalone hooks installer + claude mcp", + "extras": ["hooks (statusline + stats)", "caveman-shrink MCP proxy"], + "version": "latest", + "note": "Caveman — ~75% output token compression via caveman-speak. Full install = plugin + standalone hooks (statusline + stats badge) + caveman-shrink MCP (input compression). Updates pull via 'claude plugin update'; hooks installer is idempotent." } } diff --git a/settings.json b/settings.json index b71ce24..c7d083b 100644 --- a/settings.json +++ b/settings.json @@ -195,6 +195,16 @@ "command": "bash ~/.claude/hooks/session-start.sh" } ] + }, + { + "hooks": [ + { + "type": "command", + "command": "node ~/.claude/hooks/caveman-activate.js", + "timeout": 5, + "statusMessage": "Loading caveman mode..." + } + ] } ], "PreToolUse": [ @@ -207,6 +217,18 @@ } ] } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "node ~/.claude/hooks/caveman-mode-tracker.js", + "timeout": 5, + "statusMessage": "Tracking caveman mode..." + } + ] + } ] }, "statusLine": { @@ -215,7 +237,8 @@ }, "enabledPlugins": { "example-skills@anthropic-agent-skills": true, - "ui-ux-pro-max@ui-ux-pro-max-skill": true + "ui-ux-pro-max@ui-ux-pro-max-skill": true, + "caveman@caveman": true }, "extraKnownMarketplaces": { "claude-code-plugins": { @@ -241,6 +264,12 @@ "source": "github", "repo": "anthropics/skills" } + }, + "caveman": { + "source": { + "source": "github", + "repo": "JuliusBrussee/caveman" + } } }, "effortLevel": "xhigh", diff --git a/update-all.sh b/update-all.sh index de50870..28fc73f 100644 --- a/update-all.sh +++ b/update-all.sh @@ -224,6 +224,36 @@ else info "emil-design-eng not installed — skipping (run: make plugin)" fi +# ── 7.4. Update Caveman (hooks + MCP shrink) ── +# Plugin updates are handled by the marketplace plugin update loop below +# (step 8). This step refreshes the standalone hook files (statusline + +# stats badge) that live outside the plugin in ~/.claude/hooks/. +echo "" +echo "── Updating Caveman extras (hooks + MCP shrink)..." +if [ -f "$HOME/.claude/hooks/caveman-statusline.sh" ]; then + CAVEMAN_HOOKS_URL="https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.sh" + CAVEMAN_HOOKS_TMP="$(mktemp -t caveman-hooks-XXXXXX.sh)" + if curl -fsSL "$CAVEMAN_HOOKS_URL" -o "$CAVEMAN_HOOKS_TMP" \ + && bash "$CAVEMAN_HOOKS_TMP" --force 2>/dev/null; then + ok "Caveman hooks refreshed" + else + warn "Caveman hooks refresh failed — re-run install-plugins.sh" + fi + rm -f "$CAVEMAN_HOOKS_TMP" +else + info "Caveman hooks not installed — skipping (run: make plugin)" +fi +# MCP shrink uses 'npx -y caveman-shrink' which always fetches latest from +# npm at invocation time — no explicit update needed. We only check for +# user-defined wrappers (caveman-shrink-*), since the bare proxy fails +# health checks without an upstream. +if command -v claude &>/dev/null \ + && claude mcp list 2>/dev/null | grep -q '^caveman-shrink-'; then + ok "caveman-shrink wrappers detected (auto-update via npx -y)" +else + info "caveman-shrink not wrapped — manual setup (see install-plugins.sh STEP 5.5)" +fi + # ── 7.5. Update external skills (npx skills) ── echo "" echo "── Updating external skills (npx skills)..."