2026-05-11 00:53:48 +03:00
|
|
|
|
#!/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=""
|
2026-05-11 15:00:39 +03:00
|
|
|
|
install_target=""
|
2026-05-11 00:53:48 +03:00
|
|
|
|
|
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
--marketplaces=*) marketplaces="${1#--marketplaces=}" ;;
|
|
|
|
|
|
--manifest=*) manifest="${1#--manifest=}" ;;
|
2026-05-11 15:00:39 +03:00
|
|
|
|
--install-target=*) install_target="${1#--install-target=}" ;;
|
2026-05-11 00:53:48 +03:00
|
|
|
|
*) log_error "Unknown arg: $1"; exit 1 ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
shift
|
|
|
|
|
|
done
|
|
|
|
|
|
|
2026-05-11 15:00:39 +03:00
|
|
|
|
# Backwards-compat: если флаг не передан, default 'user' с warning
|
|
|
|
|
|
if [ -z "$install_target" ]; then
|
|
|
|
|
|
log_warn "--install-target not specified, defaulting to 'user' (deprecated path)"
|
|
|
|
|
|
install_target="user"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Validate
|
|
|
|
|
|
case "$install_target" in
|
|
|
|
|
|
user|consumer) ;;
|
|
|
|
|
|
*) log_error "--install-target must be 'user' or 'consumer', got: $install_target"; exit 1 ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
2026-05-11 00:53:48 +03:00
|
|
|
|
[ -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
|
|
|
|
|
|
|
2026-05-11 15:00:39 +03:00
|
|
|
|
# Install plugins (filtered by target ENUM per --install-target)
|
|
|
|
|
|
case "$install_target" in
|
|
|
|
|
|
user)
|
|
|
|
|
|
filter='.plugins | to_entries[] | select(.value[0].target == "user-level" or .value[0].target == "user-level+consumer") | .key'
|
|
|
|
|
|
;;
|
|
|
|
|
|
consumer)
|
|
|
|
|
|
filter='.plugins | to_entries[] | select(.value[0].target == "consumer" or .value[0].target == "user-level+consumer") | .key'
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
|
|
for plugin in $(jq -r "$filter" "$manifest"); do
|
2026-05-11 00:53:48 +03:00
|
|
|
|
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
|