Help us improve
Share bugs, ideas, or general feedback.
From utils
This skill should be used when the user needs to locate a plugin's installation path, when ${CLAUDE_PLUGIN_ROOT} doesn't expand in markdown files, or when invoked via /utils:find-claude-plugin-root. Generates a CPR resolver script at /tmp/cpr.py.
npx claudepluginhub aaronbassett/agent-foundry --plugin utilsHow this skill is triggered — by the user, by Claude, or both
Slash command
/utils:find-claude-plugin-rootThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill generates a Python resolver script at `/tmp/cpr.py` that locates a plugin's installation directory by reading `~/.claude/plugins/installed_plugins.json`.
This skill should be used when the user needs to locate a plugin's installation path, find where a plugin is installed, resolve plugin root directory, or when CLAUDE_PLUGIN_ROOT does not expand in markdown files. It generates a Python resolver script at /tmp/cpr.py that reads installed_plugins.json and supports fuzzy plugin name matching.
Dynamically locates files in Claude Code plugins cache (~/.claude/plugins/cache), auto-resolving latest versions. Useful for slash commands and orchestrators finding tools, skills, or resources without hardcoding paths.
Guides creation, validation, and troubleshooting of Claude Code plugin.json files including schema, best practices, and field rules. Use for plugin setup and configuration.
Share bugs, ideas, or general feedback.
This skill generates a Python resolver script at /tmp/cpr.py that locates a plugin's installation directory by reading ~/.claude/plugins/installed_plugins.json.
${CLAUDE_PLUGIN_ROOT} environment variable doesn't expand in markdown command files, making it impossible to reference plugin scripts and resources. This is a known issue: https://github.com/anthropics/claude-code/issues/9354
Generate a Python script that:
${CLAUDE_PLUGIN_ROOT} first (backwards compatible)/tmp/cpr.py (ephemeral, no project pollution)Invoke this skill before executing plugin scripts:
# Generate the resolver
Skill(skill="utils:find-claude-plugin-root")
# Use it to find a plugin and execute its scripts
PLUGIN_ROOT=$(python3 /tmp/cpr.py readme-and-co)
python "$PLUGIN_ROOT/scripts/populate_license.py" --license MIT
cat > /tmp/cpr.py << 'CPREOF'
#!/usr/bin/env python3
"""
Claude Plugin Root (CPR) Resolver
Usage: python3 /tmp/cpr.py <plugin-name>
Returns: absolute path to plugin installation directory
Searches for plugins in ~/.claude/plugins/installed_plugins.json with fuzzy matching.
"""
import json
import os
import sys
from pathlib import Path
from difflib import SequenceMatcher
def similarity(a, b):
"""Calculate similarity ratio between two strings."""
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
def find_plugin_root(plugin_name):
"""
Find plugin installation directory.
Returns: (plugin_root_path, match_type)
match_type: 'env_var', 'exact', 'fuzzy', or None
"""
# Try CLAUDE_PLUGIN_ROOT first (backwards compatible)
env_root = os.environ.get('CLAUDE_PLUGIN_ROOT')
if env_root and os.path.isdir(env_root):
return env_root.rstrip('/'), 'env_var'
# Read installed_plugins.json
plugins_file = Path.home() / '.claude' / 'plugins' / 'installed_plugins.json'
if not plugins_file.exists():
return None, None
try:
with open(plugins_file, 'r') as f:
data = json.load(f)
plugins = data.get('plugins', {})
except (OSError, json.JSONDecodeError):
return None, None
# Try exact match first (case-insensitive)
for key, value in plugins.items():
if plugin_name.lower() in key.lower():
# Handle list or dict value
if isinstance(value, list) and len(value) > 0:
value = value[0]
install_path = value.get('installPath', '').rstrip('/')
if install_path and os.path.isdir(install_path):
return install_path, 'exact'
# Try fuzzy matching if no exact match
matches = []
for key, value in plugins.items():
# Extract just the plugin name from key (e.g., "owner/plugin-name" -> "plugin-name")
key_parts = key.split('/')
plugin_part = key_parts[-1] if key_parts else key
# Also handle @ separator (e.g., "plugin-name@plugin-name")
plugin_part = plugin_part.split('@')[0]
ratio = similarity(plugin_name, plugin_part)
if ratio > 0.6: # 60% similarity threshold
# Handle list or dict value
if isinstance(value, list) and len(value) > 0:
value = value[0]
install_path = value.get('installPath', '').rstrip('/')
if install_path and os.path.isdir(install_path):
matches.append((ratio, install_path, key))
if matches:
# Return best match
matches.sort(reverse=True, key=lambda x: x[0])
best_match = matches[0]
return best_match[1], 'fuzzy'
return None, None
def main():
if len(sys.argv) < 2:
print("Usage: python3 /tmp/cpr.py <plugin-name>", file=sys.stderr)
print("Example: python3 /tmp/cpr.py readme-and-co", file=sys.stderr)
sys.exit(1)
plugin_name = sys.argv[1]
plugin_root, match_type = find_plugin_root(plugin_name)
if plugin_root:
# Output just the path to stdout (for command substitution)
print(plugin_root)
sys.exit(0)
else:
print(f"Error: Could not locate plugin '{plugin_name}'", file=sys.stderr)
print(f"Checked: $CLAUDE_PLUGIN_ROOT, ~/.claude/plugins/installed_plugins.json", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
CPREOF
chmod +x /tmp/cpr.py
# Test finding the utils plugin itself
if PLUGIN_ROOT=$(python3 /tmp/cpr.py utils); then
echo "✓ CPR resolver created at /tmp/cpr.py"
echo "✓ Test lookup succeeded: $PLUGIN_ROOT"
else
echo "❌ CPR resolver test failed" >&2
exit 1
fi
# Invoke this skill first
Skill(skill="utils:find-claude-plugin-root")
# Then use the resolver - run script and capture output
PLUGIN_ROOT=$(python3 /tmp/cpr.py readme-and-co)
python "$PLUGIN_ROOT/scripts/detect_project_info.py"
# The resolver works for any plugin
PLUGIN_ROOT=$(python3 /tmp/cpr.py my-plugin)
node "$PLUGIN_ROOT/tools/analyzer.js"