forked from bchanot/claude
feat(21st): replace the magic MCP with the @21st-dev CLI + skill pack
Upstream supersedes `@21st-dev/magic` with `@21st-dev/cli` (bin `21st`): same endpoint, `21st login` in place of an API key, no MCP process loaded into every session. - install-plugins.sh Step 8.7: `npm i -g @21st-dev/cli` (pinned in plugins.lock.json), staged `21st skills install`, TTY-only login offer, pack disabled by default. update-all.sh 7.4 refreshes both. - The documented `21st install-skill` cannot be used: the installer refuses to follow a symlink on the target path and `~/.claude/skills` is one. The install runs under a throwaway HOME and the result moves into skills-external/21st-* (gitignored), symlinked on demand. - toggle-external.sh manages `21st` as a pack (names globbed from skills-external/21st-*, parked under plain names). `magic` is gone. - The 5 design skills join design/web/web-full/full and MANAGED_EXTERNALS; 21st-registry and 21st-design-sync stay parked. MANAGED_MCPS is now empty and profile.sh's dead magic branches are removed. - Design gate: GATE-BLOCK gains `21st` (required-manual, magic's old slot) and `21st-ui-build`; PATH repair extended to the npm global bin. - settings.json: the 4 mcp__magic__* ask entries go; the outward-facing 21st verbs land in autoMode.soft_deny, the tier that holds under auto mode (LRN-153). - Docs: README, CLAUDE.global.md, design-gate.md, profile SKILL.md, .env.example, .gitleaks.toml, link.sh. BDR-093, LRN-158. Tests: profile-set-managed 17/17, make test green except 2 pre-existing gitflow FAILs (gitleaks binary absent on this host), shellcheck clean.
This commit is contained in:
+34
-9
@@ -29,12 +29,13 @@
|
||||
# required-manual required but the profile can't flip it silently (API
|
||||
# key / external install) — the gate STILL trips, names
|
||||
# it, and the remedy is `/profile design` + a manual step.
|
||||
# This is where magic lands: required, never silent.
|
||||
# This is where the `21st` CLI lands: required, never
|
||||
# silent (npm i -g @21st-dev/cli, then 21st login).
|
||||
# Both classes trip the gate. Tools NOT on the GATE-BLOCK allowlist are
|
||||
# ignored entirely (browser/plan/shotgun tooling, graphify).
|
||||
#
|
||||
# disabledMcpServers is NEVER read — unreliable for bi-modal servers
|
||||
# (magic/context7 can appear there yet be active via another channel).
|
||||
# (context7 can appear there yet be active via another channel).
|
||||
#
|
||||
# Exit: 0 = ready · 11 = ready-but-unverified (proceed, say so) · 10 = incomplete (trips) · 2 = error.
|
||||
# Usage: design-tool-gate.sh [profile] (default profile: design)
|
||||
@@ -79,6 +80,30 @@ ensure_claude_on_path() {
|
||||
}
|
||||
ensure_claude_on_path
|
||||
|
||||
# Same sanitized-PATH problem for `21st` (an npm global bin), with a twist:
|
||||
# the repair above only fires when claude ITSELF is unresolvable, and claude
|
||||
# often lives in ~/.local/bin while the npm global bin dir is missing from a
|
||||
# hook's PATH. Probe for the binary directly and prepend the dir that has it,
|
||||
# otherwise a perfectly installed CLI reads as "missing" and trips the gate.
|
||||
ensure_21st_on_path() {
|
||||
command -v 21st >/dev/null 2>&1 && return
|
||||
local cand
|
||||
for cand in \
|
||||
"$HOME/.local/bin/21st" \
|
||||
/usr/local/bin/21st; do
|
||||
[ -x "$cand" ] && { PATH="$(dirname "$cand"):$PATH"; return; }
|
||||
done
|
||||
local m newest matches=()
|
||||
for m in "$HOME"/.nvm/versions/node/*/bin/21st; do
|
||||
[ -x "$m" ] && matches+=("$m")
|
||||
done
|
||||
if [ "${#matches[@]}" -gt 0 ]; then
|
||||
newest="$(printf '%s\n' "${matches[@]}" | sort -V | tail -1)"
|
||||
PATH="$(dirname "$newest"):$PATH"
|
||||
fi
|
||||
}
|
||||
ensure_21st_on_path
|
||||
|
||||
# Gate scope: the "# GATE-BLOCK:" allowlist (one or more lines, concatenated).
|
||||
# Empty => fall back to "every gate-relevant entry is in scope" (coarse).
|
||||
core_set="$(grep '^# GATE-BLOCK:' "$PROFILE_FILE" 2>/dev/null \
|
||||
@@ -143,8 +168,8 @@ done <<< "$plain"
|
||||
# Verdict — three outcomes:
|
||||
# blocking/manual non-empty -> INCOMPLETE (exit 10): the gate trips.
|
||||
# only unverified non-empty -> READY BUT UNVERIFIED (exit 11): fail-VISIBLE.
|
||||
# claude was unreachable, so the plugin/MCP (magic, ui-ux-pro-max) could
|
||||
# not be checked. Never pass this as a silent READY — proceed, but say so.
|
||||
# claude was unreachable, so the plugin channel (ui-ux-pro-max) could not
|
||||
# be checked. Never pass this as a silent READY — proceed, but say so.
|
||||
# nothing pending -> READY (exit 0).
|
||||
if [ "${#blocking[@]}" -gt 0 ] || [ "${#manual[@]}" -gt 0 ]; then
|
||||
echo "design toolchain: INCOMPLETE"
|
||||
@@ -152,9 +177,9 @@ if [ "${#blocking[@]}" -gt 0 ] || [ "${#manual[@]}" -gt 0 ]; then
|
||||
echo " activate with /profile $PROFILE: ${blocking[*]}"
|
||||
fi
|
||||
if [ "${#manual[@]}" -gt 0 ]; then
|
||||
echo " required + manual step (API key / external install): ${manual[*]}"
|
||||
echo " required + manual step (external install / sign-in): ${manual[*]}"
|
||||
case " ${manual[*]} " in
|
||||
*" magic "*) echo " magic needs MAGIC_API_KEY in ~/.claude/.env (/profile $PROFILE runs toggle-external.sh)" ;;
|
||||
*" 21st "*) echo " 21st needs the CLI: npm i -g @21st-dev/cli then 21st login" ;;
|
||||
esac
|
||||
fi
|
||||
if [ "${#unverified[@]}" -gt 0 ]; then
|
||||
@@ -167,9 +192,9 @@ fi
|
||||
if [ "${#unverified[@]}" -gt 0 ]; then
|
||||
echo "design toolchain: READY BUT UNVERIFIED — ${#unverified[@]} tool(s) not checked"
|
||||
echo " unverified (claude CLI unreachable): ${unverified[*]}"
|
||||
echo " the gate could NOT confirm the design plugin/MCP (e.g. magic,"
|
||||
echo " ui-ux-pro-max) are active. Proceed only after checking manually:"
|
||||
echo " claude mcp list claude plugin list"
|
||||
echo " the gate could NOT confirm the design plugin (ui-ux-pro-max) is"
|
||||
echo " active. Proceed only after checking manually:"
|
||||
echo " claude plugin list"
|
||||
exit 11
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user