Skip to content

Instantly share code, notes, and snippets.

@BrunoMiguelMonteiro
Last active July 6, 2026 06:31
Show Gist options
  • Select an option

  • Save BrunoMiguelMonteiro/9a60d4c792fb5b0c3f79c2e4fcb2c5e0 to your computer and use it in GitHub Desktop.

Select an option

Save BrunoMiguelMonteiro/9a60d4c792fb5b0c3f79c2e4fcb2c5e0 to your computer and use it in GitHub Desktop.
Check for Claude Code plugin updates - compares installed versions with marketplace repositories
#!/bin/bash
# Check for Claude Code plugin updates
# Compares installed plugin versions with latest available in marketplaces
set -euo pipefail
CLAUDE_DIR="$HOME/.claude"
PLUGINS_FILE="$CLAUDE_DIR/plugins/installed_plugins.json"
MARKETPLACES_DIR="$CLAUDE_DIR/plugins/marketplaces"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
BOLD='\033[1m'
# Check dependencies
if ! command -v jq &> /dev/null; then
echo -e "${RED}Error: jq is required but not installed.${NC}"
echo "Install jq:"
echo " macOS: brew install jq"
echo " Ubuntu/Debian: sudo apt-get install jq"
echo " Fedora: sudo dnf install jq"
echo " Windows: choco install jq"
exit 1
fi
if [[ ! -f "$PLUGINS_FILE" ]]; then
echo -e "${RED}Error: Plugin file not found at $PLUGINS_FILE${NC}"
exit 1
fi
echo -e "${BOLD}Checking Claude Code plugin updates...${NC}\n"
# Check if marketplaces directory exists and has content
if [[ ! -d "$MARKETPLACES_DIR" ]] || [[ -z "$(ls -A "$MARKETPLACES_DIR" 2>/dev/null)" ]]; then
echo -e "${YELLOW}Warning: No marketplaces found at $MARKETPLACES_DIR${NC}"
echo "Install plugins first with: claude plugin install <plugin>@<marketplace>"
exit 0
fi
# Fetch latest from all marketplaces; track failures to warn about stale data
echo -e "${BLUE}Fetching latest from marketplaces...${NC}"
fetch_failed=()
for marketplace_dir in "$MARKETPLACES_DIR"/*/; do
if [[ -d "$marketplace_dir/.git" ]]; then
marketplace_name=$(basename "$marketplace_dir")
if ! git -C "$marketplace_dir" fetch origin --quiet 2>/dev/null; then
fetch_failed+=("$marketplace_name")
fi
fi
done
if [[ ${#fetch_failed[@]} -gt 0 ]]; then
echo -e "${YELLOW}Warning: Could not reach remote for: ${fetch_failed[*]}${NC}"
echo -e "${YELLOW}Results below may reflect cached data from last successful fetch.${NC}"
fi
echo ""
updates_available=0
up_to_date=0
unknown_version=0
cannot_compare=0
print_update_header() {
echo -e "${YELLOW}⬆${NC} ${BOLD}$1${NC}@$2"
}
# Parse installed plugins — quoted via <<< to prevent word-splitting and glob expansion
plugins=$(jq -r '.plugins | keys[]' "$PLUGINS_FILE") || {
echo -e "${RED}Error: Could not parse plugin data from $PLUGINS_FILE${NC}"
exit 1
}
while IFS= read -r plugin_key; do
[[ -z "$plugin_key" ]] && continue
# Extract plugin name and marketplace (handles @scope/plugin@marketplace format)
marketplace="${plugin_key##*@}"
plugin_name="${plugin_key%@*}"
# Warn and skip keys with no '@marketplace' suffix
if [[ "$marketplace" == "$plugin_key" ]]; then
echo -e "${YELLOW}?${NC} ${BOLD}$plugin_key${NC} — no @marketplace suffix, skipping"
continue
fi
# Get installed info — single jq call with --arg to avoid injection
IFS=$'\t' read -r installed_version installed_sha is_local < <(
jq -r --arg k "$plugin_key" \
'.plugins[$k][0] | [(.version // "unknown"), (.gitCommitSha // ""), (.isLocal // false | tostring)] | @tsv' \
"$PLUGINS_FILE"
)
marketplace_dir="$MARKETPLACES_DIR/$marketplace"
if [[ ! -d "$marketplace_dir/.git" ]]; then
continue
fi
# Get latest SHA from marketplace using git -C to avoid subshell cd
latest_sha=$(git -C "$marketplace_dir" rev-parse origin/main 2>/dev/null || \
git -C "$marketplace_dir" rev-parse origin/master 2>/dev/null || \
echo "")
if [[ -z "$latest_sha" || -z "$installed_sha" ]]; then
continue
fi
# Check if there are commits between installed and latest
if [[ "$installed_sha" != "$latest_sha" ]]; then
commits_ahead=$(git -C "$marketplace_dir" rev-list --count "$installed_sha..$latest_sha" 2>/dev/null || echo "?")
if [[ "$commits_ahead" == "?" ]]; then
echo -e "${BLUE}?${NC} ${BOLD}$plugin_name${NC}@$marketplace"
echo -e " Cannot compare (installed SHA not found in remote history)"
(( ++cannot_compare ))
continue
fi
if [[ "$commits_ahead" != "0" ]]; then
# Try to find version info from commit messages (-F for fixed string, not regex)
latest_version=$(git -C "$marketplace_dir" log --oneline "$installed_sha..$latest_sha" 2>/dev/null \
| grep -iF "$plugin_name" | head -1 \
| grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")
print_update_header "$plugin_name" "$marketplace"
if [[ -n "$latest_version" ]]; then
echo -e " ${RED}$installed_version${NC} → ${GREEN}$latest_version${NC} ($commits_ahead commits)"
elif [[ "$is_local" == "true" || "$installed_version" == "unknown" ]]; then
echo -e " $commits_ahead commits behind (local plugin, no version tracking)"
(( ++unknown_version ))
continue
else
echo -e " ${RED}$installed_version${NC} → ${GREEN}newer available${NC} ($commits_ahead commits)"
fi
(( ++updates_available ))
else
(( ++up_to_date ))
fi
else
(( ++up_to_date ))
fi
done <<< "$plugins"
echo ""
echo -e "${BOLD}Summary:${NC}"
if [[ $updates_available -gt 0 ]]; then
echo -e " ${YELLOW}$updates_available plugin(s) with updates available${NC}"
fi
if [[ $unknown_version -gt 0 ]]; then
echo -e " ${BLUE}$unknown_version local plugin(s) with possible updates${NC}"
fi
echo -e " ${GREEN}$up_to_date plugin(s) up to date${NC}"
if [[ $cannot_compare -gt 0 ]]; then
echo -e " ${BLUE}$cannot_compare plugin(s) could not be compared${NC}"
fi
if [[ $updates_available -gt 0 || $unknown_version -gt 0 ]]; then
echo ""
echo -e "${BOLD}To update a plugin:${NC}"
echo -e " claude plugin update <plugin-name>@<marketplace>"
echo ""
echo -e "${BOLD}Example:${NC}"
echo -e " claude plugin update superpowers@superpowers-marketplace"
fi
@MrCsabaToth

Copy link
Copy Markdown

I'm getting Error: Plugin file not found at /Users/[myname]/.claude/plugins/installed_plugins_v2.json

@MrCsabaToth

Copy link
Copy Markdown

For me it's just simply /Users/[myname]/.claude/plugins/installed_plugins.json, and in the JSON I see "version": 2

@BrunoMiguelMonteiro

Copy link
Copy Markdown
Author

@MrCsabaToth Thanks for catching this! You were right — the file is installed_plugins.json, not installed_plugins_v2.json. Just pushed the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment