Awesome-omni-skill utils:find-claude-plugin-root
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.
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/ai-agents/utils-find-claude-plugin-root" ~/.claude/skills/diegosouzapw-awesome-omni-skill-utils-find-claude-plugin-root && rm -rf "$T"
manifest:
skills/ai-agents/utils-find-claude-plugin-root/SKILL.mdsource content
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
first (backwards compatible)${CLAUDE_PLUGIN_ROOT} - 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
(ephemeral, no project pollution)/tmp/cpr.py
Usage
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
Implementation
Step 1: Create the CPR resolver Python script
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
Step 2: Verify the script works
# 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
Examples
Find and use readme-and-co plugin
# 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"
Find and use any plugin
# The resolver works for 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)