| name | utilsfind-claude-plugin-root |
| description | 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. Use when this capability is needed. |
| metadata | {"author":"aaronbassett"} |
Find Claude Plugin Root
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.
Problem It Solves
${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
Solution
Generate a Python script that:
- Accepts plugin name as argument
- Tries
${CLAUDE_PLUGIN_ROOT} first (backwards compatible)
- Reads installed_plugins.json and searches for exact match
- If no exact match, finds similar plugin names (fuzzy matching)
- Outputs the plugin installation path to stdout
- Saves to
/tmp/cpr.py (ephemeral, no project pollution)
Usage
Invoke this skill before executing plugin scripts:
Skill(skill="utils:find-claude-plugin-root")
PLUGIN_ROOT=$(python3 /tmp/cpr.py readme-and-co)
python "$PLUGIN_ROOT/scripts/populate_license.py" --license MIT
Implementation
Step 1: Create the CPR resolver Python script
cat > /tmp/cpr.py << 'CPREOF'
"""
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
"""
env_root = os.environ.get('CLAUDE_PLUGIN_ROOT')
if env_root and os.path.isdir(env_root):
return env_root.rstrip('/'), 'env_var'
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
for key, value in plugins.items():
if plugin_name.lower() in key.lower():
if isinstance(value, list) and len(value) > 0:
value = value[0]
install_path = value.get(, ).rstrip()
install_path and os.path.isdir(install_path):
install_path,
matches = []
key, value plugins.items():
key_parts = key.split()
plugin_part = key_parts[-1] key_parts key
plugin_part = plugin_part.split()[0]
ratio = similarity(plugin_name, plugin_part)
ratio > 0.6:
isinstance(value, list) and len(value) > 0:
value = value[0]
install_path = value.get(, ).rstrip()
install_path and os.path.isdir(install_path):
matches.append((ratio, install_path, key))
matches:
matches.sort(reverse=True, key=lambda x: x[0])
best_match = matches[0]
best_match[1],
None, None
def main():
len(sys.argv) < 2:
(, file=sys.stderr)
(, file=sys.stderr)
sys.exit(1)
plugin_name = sys.argv[1]
plugin_root, match_type = find_plugin_root(plugin_name)
plugin_root:
(plugin_root)
sys.exit(0)
:
(f, file=sys.stderr)
(f, file=sys.stderr)
sys.exit(1)
__name__ == :
main()
CPREOF
+x /tmp/cpr.py
Step 2: Verify the script works
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
Examples
Find and use readme-and-co plugin
Skill(skill="utils:find-claude-plugin-root")
PLUGIN_ROOT=$(python3 /tmp/cpr.py readme-and-co)
python "$PLUGIN_ROOT/scripts/detect_project_info.py"
Find and use any plugin
PLUGIN_ROOT=$(python3 /tmp/cpr.py my-plugin)
node "$PLUGIN_ROOT/tools/analyzer.js"
Benefits
- No project pollution - Script saved to /tmp, not in project
- Backwards compatible - Tries ${CLAUDE_PLUGIN_ROOT} first
- Fuzzy matching - Finds plugins even if name doesn't exactly match
- Pure Python - No external dependencies (jq not needed)
- Reusable - One skill for all plugins
- Ephemeral - /tmp/cpr.py cleaned up on reboot
Limitations
- Recreated on each system reboot (since /tmp is ephemeral)
- Requires Python 3 (standard on all modern systems)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.