#!/usr/bin/env python3 """ Show information about available skills. Usage: python3 skill-info.py [--scripts] [name] """ import argparse import json import re import sys from pathlib import Path from typing import Dict, List, Optional CLAUDE_DIR = Path.home() / ".claude" SKILLS_DIR = CLAUDE_DIR / "skills" REGISTRY_PATH = CLAUDE_DIR / "state" / "component-registry.json" def load_registry() -> Dict: """Load component registry.""" try: with open(REGISTRY_PATH) as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {} def find_skills() -> List[Path]: """Find all skill directories with SKILL.md.""" if not SKILLS_DIR.exists(): return [] return [d for d in SKILLS_DIR.iterdir() if d.is_dir() and (d / "SKILL.md").exists()] def parse_skill_md(path: Path) -> Dict: """Parse a SKILL.md file for metadata.""" try: content = path.read_text() result = { "name": path.parent.name, "path": str(path.relative_to(CLAUDE_DIR)), "description": "", "allowed_tools": [], } # Parse YAML frontmatter if content.startswith("---"): parts = content.split("---", 2) if len(parts) >= 2: frontmatter = parts[1] for line in frontmatter.strip().split("\n"): if ":" in line: key, value = line.split(":", 1) key = key.strip() value = value.strip() if key == "description": result["description"] = value elif key == "allowed-tools": result["allowed_tools"] = [t.strip() for t in value.split(",")] # Get first paragraph as description if not in frontmatter if not result["description"]: body = content.split("---")[-1] if "---" in content else content lines = body.strip().split("\n\n") for para in lines: if para.strip() and not para.startswith("#"): result["description"] = para.strip()[:200] break return result except Exception as e: return {"name": path.parent.name, "error": str(e)} def get_skill_scripts(skill_dir: Path) -> List[str]: """Get list of scripts in a skill's scripts/ directory.""" scripts_dir = skill_dir / "scripts" if not scripts_dir.exists(): return [] scripts = [] for f in scripts_dir.iterdir(): if f.is_file() and f.suffix in [".py", ".sh"]: scripts.append(f.name) return sorted(scripts) def get_skill_references(skill_dir: Path) -> List[str]: """Get list of reference files in a skill's references/ directory.""" refs_dir = skill_dir / "references" if not refs_dir.exists(): return [] refs = [] for f in refs_dir.iterdir(): if f.is_file(): refs.append(f.name) return sorted(refs) def list_skills(show_scripts: bool = False): """List all available skills.""" registry = load_registry() reg_skills = registry.get("skills", {}) skills = find_skills() if not skills: print("No skills found.") return print(f"\n🎯 Available Skills ({len(skills)})\n") for skill_dir in sorted(skills): name = skill_dir.name skill_info = parse_skill_md(skill_dir / "SKILL.md") reg_info = reg_skills.get(name, {}) desc = skill_info.get("description", reg_info.get("description", "No description")) if len(desc) > 80: desc = desc[:77] + "..." print(f" {name}") print(f" {desc}") if show_scripts: scripts = get_skill_scripts(skill_dir) if scripts: print(f" Scripts: {', '.join(scripts)}") triggers = reg_info.get("triggers", []) if triggers: trigger_str = ", ".join(triggers[:4]) if len(triggers) > 4: trigger_str += f" (+{len(triggers)-4} more)" print(f" Triggers: {trigger_str}") print("") def show_skill(name: str): """Show details for a specific skill.""" # Find matching skill skills = find_skills() matches = [s for s in skills if name.lower() in s.name.lower()] if not matches: print(f"Skill '{name}' not found.") print("\nAvailable skills:") for s in sorted(skills): print(f" - {s.name}") return if len(matches) > 1 and not any(s.name == name for s in matches): print(f"Multiple matches for '{name}':") for s in matches: print(f" - {s.name}") return skill_dir = next((s for s in matches if s.name == name), matches[0]) skill_info = parse_skill_md(skill_dir / "SKILL.md") registry = load_registry() reg_info = registry.get("skills", {}).get(skill_dir.name, {}) print(f"\n🎯 Skill: {skill_dir.name}\n") print(f"Path: {skill_dir.relative_to(CLAUDE_DIR)}/") print(f"Description: {skill_info.get('description', 'No description')}") # Allowed tools allowed = skill_info.get("allowed_tools", []) if allowed: print(f"\nAllowed Tools: {', '.join(allowed)}") # Triggers triggers = reg_info.get("triggers", []) if triggers: print(f"\nTriggers:") for t in triggers: print(f" - {t}") # Scripts scripts = get_skill_scripts(skill_dir) if scripts: print(f"\nScripts:") for s in scripts: script_path = skill_dir / "scripts" / s executable = "✓" if script_path.stat().st_mode & 0o111 else "○" print(f" {executable} {s}") # References refs = get_skill_references(skill_dir) if refs: print(f"\nReferences:") for r in refs: print(f" - {r}") # Registry script if "script" in reg_info: print(f"\nRegistry Script: {reg_info['script']}") print("") def main(): parser = argparse.ArgumentParser(description="Show skill information") parser.add_argument("name", nargs="?", help="Skill name to show details") parser.add_argument("--scripts", "-s", action="store_true", help="Show scripts in listing") parser.add_argument("--list", "-l", action="store_true", help="List all skills") args = parser.parse_args() if args.name and not args.list: show_skill(args.name) else: list_skills(args.scripts) if __name__ == "__main__": main()