claude/link.sh
bastien ec14261c82 feat(toggle): enable/disable non-marketplace tools via lib/toggle-external.sh
`claude plugin enable|disable` only toggles marketplace plugins.
Tools living as symlinks (gstack per-skill entries, emil-design-eng,
darwin-skill, find-skills) had no lever — users had to edit symlinks
by hand. The new script moves symlinks in/out of skills-disabled/
so Claude Code stops or starts scanning them.

Also removes the legacy global `skills/gstack` symlink that shadowed
per-skill entries with a duplicate top-level "gstack" skill (same
description as "browse"). gstack detection in detect-plugins.sh now
probes an individual skill instead.

plugin-advisor reads the new script's `list` command when gathering
state and emits its `enable|disable` commands in recommendations.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-21 13:50:40 +02:00

91 lines
2.9 KiB
Bash

#!/usr/bin/env bash
# Symlink this repo into ~/.claude/
set -euo pipefail
REPO="$(cd "$(dirname "$0")" && pwd)"
CLAUDE="$HOME/.claude"
CHANGED=0
mkdir -p "$CLAUDE"
link_file() {
local src="$1" dst="$2"
if [ -L "$dst" ] && [ "$(readlink "$dst")" = "$src" ]; then
return # already correct
fi
ln -sf "$src" "$dst"
CHANGED=$((CHANGED + 1))
}
link_file "$REPO/CLAUDE.md" "$CLAUDE/CLAUDE.md"
link_file "$REPO/settings.json" "$CLAUDE/settings.json"
for item in hooks agents skills lib templates; do
target="$CLAUDE/$item"
if [ -L "$target" ]; then
if [ "$(readlink "$target")" = "$REPO/$item" ]; then
continue # already correct
fi
rm -f "$target"
elif [ -d "$target" ]; then
echo "⚠️ ~/.claude/$item is a real directory. Rename or remove it, then re-run link.sh."
continue
fi
ln -sf "$REPO/$item" "$target"
CHANGED=$((CHANGED + 1))
done
# GStack is exposed via per-skill symlinks under skills/ (browse,
# canary, autoplan, design-review, …) created by gstack's own
# `./setup`. A global `skills/gstack -> skills-external/gstack/`
# symlink duplicated the top-level gstack SKILL.md alongside those
# individual skills, producing two entries with the same description
# ("Fast headless browser for QA testing…"). Remove any stale global
# link — only per-skill entries remain.
if [ -L "$REPO/skills/gstack" ] || [ -L "$CLAUDE/skills/gstack" ]; then
rm -f "$REPO/skills/gstack" "$CLAUDE/skills/gstack"
CHANGED=$((CHANGED + 1))
fi
if [ ! -d "$REPO/skills-external/gstack" ]; then
echo "⚠️ GStack submodule not found — run: git submodule update --init"
fi
if [ -d "$REPO/skills-external/emil-design-eng" ]; then
if [ -L "$CLAUDE/skills/emil-design-eng" ] && [ "$(readlink "$CLAUDE/skills/emil-design-eng")" = "$REPO/skills-external/emil-design-eng" ]; then
: # already correct
else
ln -sf "$REPO/skills-external/emil-design-eng" "$CLAUDE/skills/emil-design-eng"
CHANGED=$((CHANGED + 1))
fi
else
echo "⚠️ emil-design-eng not found — run: make plugin"
fi
# External skills installed via `npx skills add` live under
# $HOME/.agents/skills/. We symlink them into $REPO/skills/ with
# absolute paths so the link stays valid regardless of where the
# repo is cloned (relative ../../ paths broke on repos deeper than
# one level below $HOME).
NPX_EXTERNAL_SKILLS=(darwin-skill find-skills)
for _ext in "${NPX_EXTERNAL_SKILLS[@]}"; do
_target="$HOME/.agents/skills/$_ext"
_link="$REPO/skills/$_ext"
if [ ! -d "$_target" ]; then
echo "⚠️ $_ext not installed at $_target — run: make plugin"
continue
fi
if [ -L "$_link" ] && [ "$(readlink "$_link")" = "$_target" ]; then
continue
fi
rm -f "$_link"
ln -sf "$_target" "$_link"
CHANGED=$((CHANGED + 1))
done
if [ "$CHANGED" -eq 0 ]; then
echo "✅ All symlinks already up to date."
else
echo "$CHANGED symlink(s) updated in ~/.claude/"
fi
echo " Next: bash install-plugins.sh"