Skills move to skills/<name>/SKILL.md and the repo gains both manifests, so it is now a marketplace hosting exactly one plugin whose root is the repo root (source: "."). Claude Code installs it from git; no symlinks involved. Why: install.sh wired the library into every consuming repo as an ABSOLUTE symlink to $CHECKOUT_DIR. The checkout moved three times, most recently when local-dev@1966d5a dropped the embedded .skills/ copy, and nothing migrated the links — 243 dangling links across 12 repos, hidden from git by the **/.claude/skills/ ignore rule. Worse, ~/dev spans two path roots (/Users/mathias on the iCloud-shared Macs, /home/mathias on koala), so an absolute link stored there is broken on one side by construction. Re-running the installer cannot fix that; only removing the mechanism can. install.sh keeps the harnesses with no plugin system and drops from 42 links per harness to one directory link, via the convergent ~/.agents/skills convention that Pi and OpenCode both read: ~/.agents/skills -> $CHECKOUT_DIR/skills ~/.config/crush/skills -> ~/.agents/skills ~/.config/opencode/skills -> ~/.agents/skills wire_claude_repo is deleted outright rather than gated behind a flag — on a mixed-root fleet it cannot be made correct. Antigravity and Vibe targets go too; both unused. CHECKOUT_DIR keeps its host-local default deliberately: pointing it into ~/dev would put a git working tree inside iCloud with two concurrent writers. Taskfile drops five near-identical wiring loops (they had already drifted) and delegates to install.sh. `task check` gains frontmatter validation, a stray-root-entry gate, and manifest/tag version parity — the loose secret-hygiene.md sat unloadable for months because nothing checked layout. Verified: claude plugin validate . passes clean; a probe skill loaded as skills:zzprobe through --plugin-dir; task check green at 42 skills. Refs: #23 Bump-Type: minor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
117 lines
4.1 KiB
Bash
Executable File
117 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install.sh — bootstrap installer for the skills library.
|
|
#
|
|
# Usage (one-liner, hosts without Task):
|
|
# curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash
|
|
#
|
|
# Pinned to a specific tag:
|
|
# SKILLS_REF=v0.4.0 bash install.sh
|
|
#
|
|
# Idempotent: re-running pulls the latest changes for the configured ref
|
|
# (default: main) and re-wires the links.
|
|
#
|
|
# Claude Code is NOT wired here. It installs this repo as a plugin from the
|
|
# marketplace this repo also hosts:
|
|
#
|
|
# /plugin marketplace add https://git.d-ma.be/mathias/skills.git
|
|
# /plugin install skills@mathias
|
|
#
|
|
# That path carries the hooks and MCP server definitions too, which symlinks
|
|
# cannot. This script covers the harnesses with no plugin system: Crush, Pi
|
|
# and OpenCode.
|
|
#
|
|
# Wiring model — one directory link per harness, never one per skill:
|
|
#
|
|
# ~/.agents/skills -> $CHECKOUT_DIR/skills (canonical; Pi reads it directly)
|
|
# ~/.config/crush/skills -> ~/.agents/skills
|
|
# ~/.config/opencode/skills -> ~/.agents/skills
|
|
#
|
|
# Adding a skill to the repo therefore wires it everywhere with no re-run,
|
|
# and a broken link is one broken link rather than 42.
|
|
#
|
|
# CHECKOUT_DIR deliberately defaults OUTSIDE any file-synced or
|
|
# cross-platform-shared directory. Do not "fix" it to point into ~/dev: on a
|
|
# fleet whose workspace is shared between macOS (/Users/...) and Linux
|
|
# (/home/...), an absolute link stored there is broken on one side by
|
|
# construction. See mathias/skills#23.
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_URL="${SKILLS_REPO_URL:-https://git.d-ma.be/mathias/skills.git}"
|
|
REF="${SKILLS_REF:-main}"
|
|
CHECKOUT_DIR="${SKILLS_CHECKOUT_DIR:-$HOME/.local/share/skills}"
|
|
AGENTS_DIR="${AGENTS_SKILLS_DIR:-$HOME/.agents/skills}"
|
|
CRUSH_DIR="${CRUSH_SKILLS_DIR:-$HOME/.config/crush/skills}"
|
|
OPENCODE_DIR="${OPENCODE_SKILLS_DIR:-$HOME/.config/opencode/skills}"
|
|
|
|
log() { printf '[skills] %s\n' "$*"; }
|
|
|
|
ensure_checkout() {
|
|
# Wiring an existing working checkout (task install) must not fetch or
|
|
# move HEAD — that would discard in-progress work.
|
|
if [ "${SKILLS_SKIP_FETCH:-}" = "1" ]; then
|
|
[ -d "$CHECKOUT_DIR/skills" ] || { log "no skills/ under $CHECKOUT_DIR"; exit 1; }
|
|
log "using $CHECKOUT_DIR as-is (SKILLS_SKIP_FETCH=1)"
|
|
return
|
|
fi
|
|
if [ -d "$CHECKOUT_DIR/.git" ]; then
|
|
log "updating $CHECKOUT_DIR"
|
|
git -C "$CHECKOUT_DIR" fetch --tags --quiet
|
|
git -C "$CHECKOUT_DIR" checkout --quiet "$REF"
|
|
if git -C "$CHECKOUT_DIR" symbolic-ref -q HEAD >/dev/null; then
|
|
# on a branch — fast-forward
|
|
git -C "$CHECKOUT_DIR" pull --ff-only --quiet
|
|
fi
|
|
else
|
|
log "cloning $REPO_URL into $CHECKOUT_DIR (ref=$REF)"
|
|
mkdir -p "$(dirname "$CHECKOUT_DIR")"
|
|
git clone --quiet "$REPO_URL" "$CHECKOUT_DIR"
|
|
git -C "$CHECKOUT_DIR" checkout --quiet "$REF"
|
|
fi
|
|
}
|
|
|
|
count_skills() {
|
|
find "$CHECKOUT_DIR/skills" -mindepth 2 -maxdepth 2 -name SKILL.md 2>/dev/null | wc -l | tr -d ' '
|
|
}
|
|
|
|
# link_dir <link_path> <target_path>
|
|
# Replaces whatever is there — including a directory left behind by the old
|
|
# per-skill wiring — with a single symlink.
|
|
link_dir() {
|
|
local link="$1" target="$2"
|
|
if [ -L "$link" ]; then
|
|
[ "$(readlink "$link")" = "$target" ] && return
|
|
rm -f "$link"
|
|
elif [ -d "$link" ]; then
|
|
# Refuse to discard a directory holding anything but symlinks: that
|
|
# would be someone's real skills, not our old wiring.
|
|
if find "$link" -mindepth 1 -maxdepth 1 ! -type l | grep -q .; then
|
|
log "REFUSING to replace $link — it holds non-symlink entries; move them and re-run"
|
|
return 1
|
|
fi
|
|
find "$link" -mindepth 1 -maxdepth 1 -type l -delete
|
|
rmdir "$link"
|
|
elif [ -e "$link" ]; then
|
|
rm -f "$link"
|
|
fi
|
|
mkdir -p "$(dirname "$link")"
|
|
ln -s "$target" "$link"
|
|
log "linked $link → $target"
|
|
}
|
|
|
|
main() {
|
|
ensure_checkout
|
|
|
|
# Canonical location. Pi reads ~/.agents/skills directly, so it needs no
|
|
# harness-specific link of its own.
|
|
link_dir "$AGENTS_DIR" "$CHECKOUT_DIR/skills"
|
|
|
|
link_dir "$CRUSH_DIR" "$AGENTS_DIR"
|
|
link_dir "$OPENCODE_DIR" "$AGENTS_DIR"
|
|
|
|
log "done — $(count_skills) skill(s) at ref=$REF, 3 links total"
|
|
log "Claude Code: install the plugin instead — /plugin marketplace add $REPO_URL"
|
|
}
|
|
|
|
main "$@"
|