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>
352 lines
11 KiB
Bash
352 lines
11 KiB
Bash
#!/usr/bin/env bash
|
||
# ============================================================
|
||
# Claude Code — Update all components
|
||
# Pulls latest config, updates submodules, refreshes symlinks,
|
||
# and runs doctor to verify.
|
||
# ============================================================
|
||
set -euo pipefail
|
||
|
||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||
ok() { echo -e "${GREEN}✓${NC} $1"; }
|
||
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
||
info() { echo -e "${BLUE}→${NC} $1"; }
|
||
|
||
REPO="$(cd "$(dirname "$0")" && pwd)"
|
||
VERSION=$(cat "$REPO/version.txt" 2>/dev/null || echo "unknown")
|
||
|
||
# Load shared detection library
|
||
# shellcheck source=lib/detect-plugins.sh
|
||
source "$REPO/lib/detect-plugins.sh"
|
||
|
||
echo ""
|
||
echo "═══ claude-config update (v${VERSION}) ═══"
|
||
echo ""
|
||
|
||
# ── 0. Update Claude Code CLI ──
|
||
echo "── Updating Claude Code CLI..."
|
||
if command -v claude &>/dev/null; then
|
||
CURRENT_VER=$(claude --version 2>/dev/null | head -1 || echo "unknown")
|
||
info "Current: $CURRENT_VER"
|
||
if npm install -g @anthropic-ai/claude-code@latest 2>/dev/null; then
|
||
NEW_VER=$(claude --version 2>/dev/null | head -1 || echo "unknown")
|
||
if [ "$CURRENT_VER" = "$NEW_VER" ]; then
|
||
ok "Claude Code already up to date ($NEW_VER)"
|
||
else
|
||
ok "Claude Code updated: $CURRENT_VER → $NEW_VER"
|
||
fi
|
||
else
|
||
warn "Claude Code update failed — try manually: npm install -g @anthropic-ai/claude-code@latest"
|
||
fi
|
||
else
|
||
warn "Claude Code not found — install first with: make install"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ── 1. Pull latest config ──
|
||
echo "── Pulling latest config..."
|
||
cd "$REPO"
|
||
if git pull --rebase 2>/dev/null; then
|
||
ok "Config repo updated"
|
||
else
|
||
warn "git pull failed — check for uncommitted changes"
|
||
fi
|
||
|
||
# ── 2. Update GStack submodule ──
|
||
echo ""
|
||
echo "── Updating GStack submodule..."
|
||
warn "GStack tracks branch = main (no commit hash). Review upstream commits before updating."
|
||
echo ""
|
||
printf " Proceed with GStack update? [y/N] "
|
||
read -r _gstack_confirm
|
||
if [[ "$_gstack_confirm" =~ ^[Yy]$ ]]; then
|
||
# Capture gstack state before the update so we can restore it after
|
||
# ./setup runs (setup re-creates every symlink; without this, an
|
||
# update would silently re-enable a tool the user had disabled).
|
||
_gstack_state="unknown"
|
||
if [ -x "$REPO/lib/toggle-external.sh" ]; then
|
||
_gstack_state=$(bash "$REPO/lib/toggle-external.sh" status gstack 2>/dev/null || echo "unknown")
|
||
fi
|
||
|
||
if git submodule update --remote skills-external/gstack 2>/dev/null; then
|
||
if [ -d "skills-external/gstack" ]; then
|
||
if [ -x "skills-external/gstack/setup" ]; then
|
||
if (cd skills-external/gstack && ./setup) 2>/dev/null; then
|
||
ok "GStack updated"
|
||
else
|
||
warn "GStack ./setup failed — submodule updated but setup did not complete"
|
||
fi
|
||
else
|
||
warn "GStack ./setup not found or not executable — skipping"
|
||
ok "GStack submodule pointer updated"
|
||
fi
|
||
fi
|
||
else
|
||
warn "GStack submodule update failed — run: git submodule update --init"
|
||
fi
|
||
|
||
# Restore prior enabled/disabled state
|
||
if [ "$_gstack_state" = "disabled" ] && [ -x "$REPO/lib/toggle-external.sh" ]; then
|
||
bash "$REPO/lib/toggle-external.sh" disable gstack >/dev/null
|
||
info "gstack was disabled before update — restored to disabled"
|
||
fi
|
||
else
|
||
info "GStack update skipped"
|
||
fi
|
||
|
||
# ── 3. Update RTK (if pinned version available) ──
|
||
echo ""
|
||
echo "── Updating RTK..."
|
||
if command -v cargo &>/dev/null; then
|
||
RTK_VERSION=""
|
||
if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then
|
||
RTK_VERSION=$(python3 -c "
|
||
import json
|
||
with open('$REPO/plugins.lock.json') as f:
|
||
d = json.load(f)
|
||
print(d.get('rtk',{}).get('version',''))
|
||
" 2>/dev/null || true)
|
||
fi
|
||
|
||
if [ -n "$RTK_VERSION" ] && [ "$RTK_VERSION" != "latest" ]; then
|
||
info "Pinned version: $RTK_VERSION"
|
||
info "Compiling from source — this may take a few minutes..."
|
||
if cargo install --git https://github.com/rtk-ai/rtk --tag "$RTK_VERSION" --force; then
|
||
ok "RTK updated to $RTK_VERSION"
|
||
else
|
||
warn "RTK update failed"
|
||
fi
|
||
else
|
||
info "No pinned version — installing latest"
|
||
info "Compiling from source — this may take a few minutes..."
|
||
if cargo install --git https://github.com/rtk-ai/rtk --force; then
|
||
ok "RTK updated (latest)"
|
||
else
|
||
warn "RTK update failed"
|
||
fi
|
||
fi
|
||
else
|
||
warn "Cargo not available — skipping RTK"
|
||
fi
|
||
|
||
# ── 4. Update GSD v2 ──
|
||
echo ""
|
||
echo "── Updating GSD v2 (gsd-pi)..."
|
||
if command -v gsd &>/dev/null; then
|
||
GSD_VER=""
|
||
if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then
|
||
GSD_VER=$(python3 -c "
|
||
import json
|
||
with open('$REPO/plugins.lock.json') as f:
|
||
d = json.load(f)
|
||
print(d.get('gsd',{}).get('version',''))
|
||
" 2>/dev/null || true)
|
||
fi
|
||
|
||
if [ -n "$GSD_VER" ] && [ "$GSD_VER" != "latest" ]; then
|
||
info "Pinned version: $GSD_VER"
|
||
if npm install -g "gsd-pi@${GSD_VER}" 2>/dev/null; then
|
||
ok "GSD v2 updated to $GSD_VER"
|
||
else
|
||
warn "GSD v2 update failed"
|
||
fi
|
||
else
|
||
info "No pinned version — installing latest"
|
||
if npm install -g gsd-pi 2>/dev/null; then
|
||
ok "GSD v2 updated (latest)"
|
||
else
|
||
warn "GSD v2 update failed"
|
||
fi
|
||
fi
|
||
else
|
||
warn "GSD v2 not installed — skipping (run: npm install -g gsd-pi)"
|
||
fi
|
||
|
||
# ── 5. Update Context7 CLI ──
|
||
echo ""
|
||
echo "── Updating Context7 CLI..."
|
||
if command -v ctx7 &>/dev/null; then
|
||
CTX7_VER=""
|
||
if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then
|
||
CTX7_VER=$(python3 -c "
|
||
import json
|
||
with open('$REPO/plugins.lock.json') as f:
|
||
d = json.load(f)
|
||
print(d.get('ctx7',{}).get('version',''))
|
||
" 2>/dev/null || true)
|
||
fi
|
||
|
||
if [ -n "$CTX7_VER" ] && [ "$CTX7_VER" != "latest" ]; then
|
||
info "Pinned version: $CTX7_VER"
|
||
if npm install -g "ctx7@${CTX7_VER}" 2>/dev/null; then
|
||
ok "ctx7 updated to $CTX7_VER"
|
||
else
|
||
warn "ctx7 update failed"
|
||
fi
|
||
else
|
||
if npm install -g ctx7@latest 2>/dev/null; then
|
||
ok "ctx7 updated (latest)"
|
||
else
|
||
warn "ctx7 update failed"
|
||
fi
|
||
fi
|
||
else
|
||
info "ctx7 not installed — skipping"
|
||
fi
|
||
|
||
# ── 6. Update Graphifyy ──
|
||
echo ""
|
||
echo "── Updating Graphifyy..."
|
||
if command -v graphify &>/dev/null; then
|
||
if pipx upgrade graphifyy 2>/dev/null; then
|
||
ok "graphifyy updated"
|
||
else
|
||
warn "graphifyy update failed — try: pipx upgrade graphifyy"
|
||
fi
|
||
else
|
||
info "graphifyy not installed — skipping"
|
||
fi
|
||
|
||
# ── 7. Update Emil Design Engineering skill ──
|
||
echo ""
|
||
echo "── Updating Emil Design Engineering..."
|
||
EMIL_DIR="$REPO/skills-external/emil-design-eng"
|
||
EMIL_URL="https://raw.githubusercontent.com/emilkowalski/skill/main/skills/emil-design-eng/SKILL.md"
|
||
if [ -d "$EMIL_DIR" ]; then
|
||
info "Fetching latest SKILL.md from emilkowalski/skill..."
|
||
if curl -fsSL "$EMIL_URL" -o "$EMIL_DIR/SKILL.md.tmp" \
|
||
&& mv "$EMIL_DIR/SKILL.md.tmp" "$EMIL_DIR/SKILL.md"; then
|
||
ok "emil-design-eng updated"
|
||
else
|
||
warn "emil-design-eng update failed"
|
||
fi
|
||
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)..."
|
||
if command -v npx &>/dev/null; then
|
||
NPX_SKILLS=(
|
||
"alchaincyf/darwin-skill"
|
||
"alchaincyf/find-skills"
|
||
)
|
||
for _src in "${NPX_SKILLS[@]}"; do
|
||
_name="${_src##*/}"
|
||
if [ ! -d "$HOME/.agents/skills/$_name" ]; then
|
||
info "$_name not installed — skipping (run: make plugin)"
|
||
continue
|
||
fi
|
||
# `skills add` is idempotent and pulls latest from the source repo,
|
||
# which is the closest thing to an update operation the CLI exposes.
|
||
if npx -y skills add "$_src" 2>/dev/null; then
|
||
ok "$_name refreshed from $_src"
|
||
else
|
||
warn "$_name refresh failed — run manually: npx -y skills add $_src"
|
||
fi
|
||
done
|
||
else
|
||
info "npx not available — skipping external skills"
|
||
fi
|
||
|
||
# ── 8. Update marketplace plugins ──
|
||
echo ""
|
||
echo "── Updating marketplace plugins..."
|
||
if command -v claude &>/dev/null; then
|
||
_plugins=$(claude plugin list 2>/dev/null \
|
||
| grep -oP '(?<=❯ )\S+' || true)
|
||
if [ -n "$_plugins" ]; then
|
||
while IFS= read -r _p; do
|
||
_name="${_p%%@*}"
|
||
info "Updating $_name..."
|
||
# Pass the full "name@marketplace" spec — the CLI rejects
|
||
# the bare name when several marketplaces are registered.
|
||
if claude plugin update "$_p" 2>/dev/null; then
|
||
ok "$_name updated"
|
||
else
|
||
warn "$_name update failed"
|
||
fi
|
||
done <<< "$_plugins"
|
||
else
|
||
info "No marketplace plugins installed — skipping"
|
||
fi
|
||
else
|
||
warn "Claude Code not found — skipping plugin update"
|
||
fi
|
||
|
||
# ── 9. Update shellcheck ──
|
||
echo ""
|
||
echo "── Updating shellcheck..."
|
||
if command -v shellcheck &>/dev/null; then
|
||
# Detect OS for package manager update
|
||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||
if brew upgrade shellcheck 2>/dev/null; then
|
||
ok "shellcheck updated"
|
||
else
|
||
ok "shellcheck already up to date"
|
||
fi
|
||
elif command -v apt-get &>/dev/null; then
|
||
if sudo apt-get install -y --only-upgrade shellcheck 2>/dev/null; then
|
||
ok "shellcheck updated"
|
||
else
|
||
ok "shellcheck already up to date"
|
||
fi
|
||
elif command -v dnf &>/dev/null; then
|
||
if sudo dnf upgrade -y shellcheck 2>/dev/null; then
|
||
ok "shellcheck updated"
|
||
else
|
||
ok "shellcheck already up to date"
|
||
fi
|
||
elif command -v pacman &>/dev/null; then
|
||
if sudo pacman -S --noconfirm shellcheck 2>/dev/null; then
|
||
ok "shellcheck updated"
|
||
else
|
||
ok "shellcheck already up to date"
|
||
fi
|
||
else
|
||
info "shellcheck installed via binary — update manually"
|
||
fi
|
||
else
|
||
info "shellcheck not installed — skipping (run: make plugin)"
|
||
fi
|
||
|
||
# ── 10. Refresh symlinks ──
|
||
echo ""
|
||
echo "── Refreshing symlinks..."
|
||
bash "$REPO/link.sh"
|
||
|
||
# ── 11. Run doctor ──
|
||
echo ""
|
||
bash "$REPO/doctor.sh"
|