feat(scripts): install-plugins.sh — marketplace add + plugin install + SHA verify
Uses corrected Claude CLI subcommand shape: claude plugin marketplace add <repo> (NOT `claude marketplace add`) claude plugin marketplace list claude plugin install <name@marketplace> claude plugin info <name@marketplace> --json Includes fallback for environments where `claude` CLI is not in PATH (VSCode-extension install on Windows): logs manual edit instructions and exits 0 so the parent installer can continue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install Claude Code plugins from brain manifest
|
||||
# Adds marketplaces, installs plugins, warns on SHA mismatch.
|
||||
#
|
||||
# CORRECTION B: real Claude CLI subcommand shape is
|
||||
# `claude plugin marketplace add <repo>` (NOT `claude marketplace add`)
|
||||
# `claude plugin marketplace list`
|
||||
# `claude plugin install <name@marketplace>`
|
||||
# `claude plugin info <name@marketplace> --json`
|
||||
# Phase 0 discovery confirmed: `marketplace` is a child of `plugin`, not a
|
||||
# top-level subcommand. The plan originally had the wrong shape.
|
||||
#
|
||||
# Usage: install-plugins.sh --marketplaces=<path> --manifest=<path>
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
marketplaces=""
|
||||
manifest=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--marketplaces=*) marketplaces="${1#--marketplaces=}" ;;
|
||||
--manifest=*) manifest="${1#--manifest=}" ;;
|
||||
*) log_error "Unknown arg: $1"; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[ -f "$marketplaces" ] || { log_error "Marketplaces file: $marketplaces not found"; exit 1; }
|
||||
[ -f "$manifest" ] || { log_error "Manifest file: $manifest not found"; exit 1; }
|
||||
|
||||
require_cmd jq || exit 1
|
||||
|
||||
# Fallback: if `claude` CLI is not in PATH (common on this Windows machine where
|
||||
# the VSCode-extension binary lives outside %PATH%), log instructions and exit 0
|
||||
# so installer can continue with manual JSON edits.
|
||||
if ! command -v claude >/dev/null 2>&1; then
|
||||
log_warn "claude CLI not found — fallback: edit JSON configs manually and restart"
|
||||
log_info "Required edits:"
|
||||
log_info " 1. Copy $marketplaces -> ~/.claude/plugins/known_marketplaces.json"
|
||||
log_info " 2. Copy $manifest -> ~/.claude/plugins/installed_plugins.json"
|
||||
log_info " 3. Update ~/.claude/settings.json:enabledPlugins"
|
||||
log_info " 4. Restart Claude Code"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Add marketplaces (CORRECTION B: `claude plugin marketplace ...`)
|
||||
for mp in $(jq -r 'keys[]' "$marketplaces"); do
|
||||
repo=$(jq -r ".[\"$mp\"].source.repo" "$marketplaces")
|
||||
if claude plugin marketplace list 2>/dev/null | grep -q "$mp"; then
|
||||
log_info "Marketplace $mp already added"
|
||||
else
|
||||
log_info "Adding marketplace: $repo"
|
||||
claude plugin marketplace add "$repo" || { log_error "plugin marketplace add failed: $repo"; exit 6; }
|
||||
fi
|
||||
done
|
||||
|
||||
# Install plugins
|
||||
for plugin in $(jq -r '.plugins | keys[]' "$manifest"); do
|
||||
expected_sha=$(jq -r ".plugins[\"$plugin\"][0].gitCommitSha" "$manifest")
|
||||
expected_ver=$(jq -r ".plugins[\"$plugin\"][0].version" "$manifest")
|
||||
|
||||
log_info "Installing $plugin (pinned v$expected_ver, SHA $expected_sha)"
|
||||
claude plugin install "$plugin" || { log_error "plugin install failed: $plugin"; exit 6; }
|
||||
|
||||
actual_sha=$(claude plugin info "$plugin" --json 2>/dev/null | jq -r '.gitCommitSha // "unknown"')
|
||||
if [ "$actual_sha" = "$expected_sha" ]; then
|
||||
log_info " SHA match: $actual_sha"
|
||||
else
|
||||
log_warn " SHA mismatch (expected $expected_sha, got $actual_sha) — marketplace may have updated"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
FAILURES=0
|
||||
assert_eq() {
|
||||
if [ "$1" = "$2" ]; then echo "PASS: $3"; else echo "FAIL: $3 (expected '$1', got '$2')"; FAILURES=$((FAILURES + 1)); fi
|
||||
}
|
||||
|
||||
# Setup: temp dir with fake `claude` CLI
|
||||
# CORRECTION B: real subcommand structure is `claude plugin marketplace add` and
|
||||
# `claude plugin install` (NOT `claude marketplace add`). Mock matches real shape.
|
||||
tmpdir=$(mktemp -d)
|
||||
mkdir "$tmpdir/bin"
|
||||
cat > "$tmpdir/bin/claude" <<'CLAUDE_EOF'
|
||||
#!/usr/bin/env bash
|
||||
# Mock claude CLI: records calls to /tmp/claude-calls.log, then dispatches.
|
||||
# Real subcommand shapes used by install-plugins.sh:
|
||||
# claude plugin marketplace list -> stdout: existing marketplaces
|
||||
# claude plugin marketplace add <repo> -> exit 0 on success
|
||||
# claude plugin install <name@marketplace> -> exit 0 on success
|
||||
# claude plugin info <name@marketplace> --json -> stdout: JSON with gitCommitSha
|
||||
echo "claude $*" >> /tmp/claude-calls.log
|
||||
|
||||
# Dispatch on first 3 args (plugin/marketplace family)
|
||||
if [ "$1" = "plugin" ] && [ "$2" = "marketplace" ]; then
|
||||
case "$3" in
|
||||
list) ;; # print nothing (empty marketplace list)
|
||||
add) exit 0 ;; # marketplace add success
|
||||
esac
|
||||
elif [ "$1" = "plugin" ] && [ "$2" = "install" ]; then
|
||||
exit 0 # plugin install success
|
||||
elif [ "$1" = "plugin" ] && [ "$2" = "info" ]; then
|
||||
# Expect: claude plugin info <name> --json
|
||||
echo '{"gitCommitSha": "f2cbfbefebbfef77321e4c9abc9e949826bea9d7"}'
|
||||
fi
|
||||
exit 0
|
||||
CLAUDE_EOF
|
||||
chmod +x "$tmpdir/bin/claude"
|
||||
|
||||
# Create fixtures
|
||||
cat > "$tmpdir/marketplaces.json" <<EOF2
|
||||
{
|
||||
"superpowers-dev": {
|
||||
"source": {"source": "github", "repo": "obra/superpowers"}
|
||||
}
|
||||
}
|
||||
EOF2
|
||||
|
||||
cat > "$tmpdir/plugins-manifest.json" <<EOF3
|
||||
{
|
||||
"version": 2,
|
||||
"plugins": {
|
||||
"superpowers@superpowers-dev": [
|
||||
{
|
||||
"scope": "user",
|
||||
"version": "5.1.0",
|
||||
"gitCommitSha": "f2cbfbefebbfef77321e4c9abc9e949826bea9d7"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF3
|
||||
|
||||
rm -f /tmp/claude-calls.log
|
||||
PATH="$tmpdir/bin:$PATH" bash "$SCRIPT_DIR/lib/install-plugins.sh" \
|
||||
--marketplaces="$tmpdir/marketplaces.json" \
|
||||
--manifest="$tmpdir/plugins-manifest.json"
|
||||
|
||||
# Test 1: marketplace add called (CORRECTION B: `plugin marketplace add`, not `marketplace add`)
|
||||
grep -q "plugin marketplace add obra/superpowers" /tmp/claude-calls.log
|
||||
assert_eq "0" "$?" "plugin marketplace add called"
|
||||
|
||||
# Test 2: plugin install called
|
||||
grep -q "plugin install superpowers@superpowers-dev" /tmp/claude-calls.log
|
||||
assert_eq "0" "$?" "plugin install called"
|
||||
|
||||
rm -rf "$tmpdir" /tmp/claude-calls.log
|
||||
|
||||
echo "---"
|
||||
echo "Failures: $FAILURES"
|
||||
exit $FAILURES
|
||||
Reference in New Issue
Block a user