Files
claude-code/automation/skill-info.py
OpenCode Test 4169f5b9a4 Add /workflow, /skill-info, and /agent-info commands
- /workflow command to list and describe available workflows
  - Filter by category (health, deploy, incidents, sysadmin)
  - Show workflow steps and triggers
- /skill-info command for skill introspection
  - List scripts, triggers, and allowed tools
  - Show references and documentation
- /agent-info command with hierarchy visualization
  - Tree view of agent relationships
  - Model assignments (opus/sonnet/haiku) with visual indicators
  - Supervisor and subordinate information
- Updated shell completions with 19 aliases total
- Test suite now covers 27 tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 19:02:42 -08:00

226 lines
6.5 KiB
Python
Executable File

#!/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()