- /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>
285 lines
8.1 KiB
Python
Executable File
285 lines
8.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Show information about available agents.
|
|
Usage: python3 agent-info.py [--tree] [name]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
CLAUDE_DIR = Path.home() / ".claude"
|
|
AGENTS_DIR = CLAUDE_DIR / "agents"
|
|
REGISTRY_PATH = CLAUDE_DIR / "state" / "component-registry.json"
|
|
|
|
|
|
# Agent hierarchy (from CLAUDE.md)
|
|
HIERARCHY = {
|
|
"personal-assistant": {
|
|
"supervisor": None,
|
|
"subordinates": ["master-orchestrator"]
|
|
},
|
|
"master-orchestrator": {
|
|
"supervisor": "personal-assistant",
|
|
"subordinates": ["linux-sysadmin", "k8s-orchestrator", "programmer-orchestrator"]
|
|
},
|
|
"linux-sysadmin": {
|
|
"supervisor": "master-orchestrator",
|
|
"subordinates": []
|
|
},
|
|
"k8s-orchestrator": {
|
|
"supervisor": "master-orchestrator",
|
|
"subordinates": ["k8s-diagnostician", "argocd-operator", "prometheus-analyst", "git-operator"]
|
|
},
|
|
"k8s-diagnostician": {
|
|
"supervisor": "k8s-orchestrator",
|
|
"subordinates": []
|
|
},
|
|
"argocd-operator": {
|
|
"supervisor": "k8s-orchestrator",
|
|
"subordinates": []
|
|
},
|
|
"prometheus-analyst": {
|
|
"supervisor": "k8s-orchestrator",
|
|
"subordinates": []
|
|
},
|
|
"git-operator": {
|
|
"supervisor": "k8s-orchestrator",
|
|
"subordinates": []
|
|
},
|
|
"programmer-orchestrator": {
|
|
"supervisor": "master-orchestrator",
|
|
"subordinates": ["code-planner", "code-implementer", "code-reviewer"]
|
|
},
|
|
"code-planner": {
|
|
"supervisor": "programmer-orchestrator",
|
|
"subordinates": []
|
|
},
|
|
"code-implementer": {
|
|
"supervisor": "programmer-orchestrator",
|
|
"subordinates": []
|
|
},
|
|
"code-reviewer": {
|
|
"supervisor": "programmer-orchestrator",
|
|
"subordinates": []
|
|
}
|
|
}
|
|
|
|
|
|
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_agent_files() -> List[Path]:
|
|
"""Find all agent markdown files."""
|
|
if not AGENTS_DIR.exists():
|
|
return []
|
|
|
|
return [f for f in AGENTS_DIR.glob("*.md")
|
|
if f.name != "README.md"]
|
|
|
|
|
|
def parse_agent_md(path: Path) -> Dict:
|
|
"""Parse an agent markdown file for metadata."""
|
|
try:
|
|
content = path.read_text()
|
|
|
|
result = {
|
|
"name": path.stem,
|
|
"path": str(path.relative_to(CLAUDE_DIR)),
|
|
"description": "",
|
|
"model": "unknown",
|
|
"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 == "name":
|
|
result["name"] = value
|
|
elif key == "description":
|
|
result["description"] = value
|
|
elif key == "model":
|
|
result["model"] = value
|
|
elif key == "tools":
|
|
result["tools"] = [t.strip() for t in value.split(",")]
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
return {"name": path.stem, "error": str(e)}
|
|
|
|
|
|
def get_model_emoji(model: str) -> str:
|
|
"""Get emoji for model type."""
|
|
return {
|
|
"opus": "🔷",
|
|
"sonnet": "🔶",
|
|
"haiku": "🔸"
|
|
}.get(model.lower(), "○")
|
|
|
|
|
|
def list_agents():
|
|
"""List all available agents."""
|
|
registry = load_registry()
|
|
reg_agents = registry.get("agents", {})
|
|
|
|
print(f"\n🤖 Available Agents ({len(reg_agents)})\n")
|
|
|
|
# Group by model
|
|
by_model = {"opus": [], "sonnet": [], "haiku": [], "unknown": []}
|
|
|
|
for name, info in reg_agents.items():
|
|
model = info.get("model", "unknown")
|
|
by_model.get(model, by_model["unknown"]).append({
|
|
"name": name,
|
|
"description": info.get("description", "No description"),
|
|
"triggers": info.get("triggers", [])
|
|
})
|
|
|
|
for model in ["opus", "sonnet", "haiku"]:
|
|
agents = by_model[model]
|
|
if not agents:
|
|
continue
|
|
|
|
emoji = get_model_emoji(model)
|
|
print(f"=== {model.title()} {emoji} ===")
|
|
|
|
for agent in sorted(agents, key=lambda a: a["name"]):
|
|
print(f" {agent['name']}")
|
|
print(f" {agent['description']}")
|
|
if agent['triggers']:
|
|
print(f" Triggers: {', '.join(agent['triggers'][:3])}")
|
|
|
|
print("")
|
|
|
|
|
|
def show_tree():
|
|
"""Show agent hierarchy as a tree."""
|
|
print(f"\n🌳 Agent Hierarchy\n")
|
|
|
|
def print_tree(name: str, prefix: str = "", is_last: bool = True):
|
|
info = HIERARCHY.get(name, {})
|
|
registry = load_registry()
|
|
reg_info = registry.get("agents", {}).get(name, {})
|
|
model = reg_info.get("model", "?")
|
|
emoji = get_model_emoji(model)
|
|
|
|
connector = "└── " if is_last else "├── "
|
|
print(f"{prefix}{connector}{name} {emoji} ({model})")
|
|
|
|
new_prefix = prefix + (" " if is_last else "│ ")
|
|
subordinates = info.get("subordinates", [])
|
|
|
|
for i, sub in enumerate(subordinates):
|
|
print_tree(sub, new_prefix, i == len(subordinates) - 1)
|
|
|
|
# Start from root
|
|
print_tree("personal-assistant")
|
|
print("")
|
|
|
|
print("Legend: 🔷 opus 🔶 sonnet 🔸 haiku")
|
|
print("")
|
|
|
|
|
|
def show_agent(name: str):
|
|
"""Show details for a specific agent."""
|
|
registry = load_registry()
|
|
reg_agents = registry.get("agents", {})
|
|
|
|
# Find matching agent
|
|
matches = [n for n in reg_agents.keys() if name.lower() in n.lower()]
|
|
|
|
if not matches:
|
|
print(f"Agent '{name}' not found.")
|
|
print("\nAvailable agents:")
|
|
for n in sorted(reg_agents.keys()):
|
|
print(f" - {n}")
|
|
return
|
|
|
|
if len(matches) > 1 and name not in matches:
|
|
print(f"Multiple matches for '{name}':")
|
|
for m in matches:
|
|
print(f" - {m}")
|
|
return
|
|
|
|
agent_name = name if name in matches else matches[0]
|
|
reg_info = reg_agents[agent_name]
|
|
|
|
print(f"\n🤖 Agent: {agent_name}\n")
|
|
|
|
model = reg_info.get("model", "unknown")
|
|
print(f"Model: {model} {get_model_emoji(model)}")
|
|
print(f"Description: {reg_info.get('description', 'No description')}")
|
|
|
|
# Triggers
|
|
triggers = reg_info.get("triggers", [])
|
|
if triggers:
|
|
print(f"\nTriggers:")
|
|
for t in triggers:
|
|
print(f" - {t}")
|
|
|
|
# Hierarchy
|
|
hier = HIERARCHY.get(agent_name, {})
|
|
supervisor = hier.get("supervisor")
|
|
subordinates = hier.get("subordinates", [])
|
|
|
|
print(f"\nHierarchy:")
|
|
if supervisor:
|
|
print(f" Supervisor: {supervisor}")
|
|
else:
|
|
print(f" Supervisor: (top-level)")
|
|
|
|
if subordinates:
|
|
print(f" Subordinates:")
|
|
for sub in subordinates:
|
|
sub_info = reg_agents.get(sub, {})
|
|
sub_model = sub_info.get("model", "?")
|
|
print(f" - {sub} ({sub_model})")
|
|
|
|
# Check for agent file
|
|
agent_file = AGENTS_DIR / f"{agent_name}.md"
|
|
if agent_file.exists():
|
|
print(f"\nFile: agents/{agent_name}.md")
|
|
file_info = parse_agent_md(agent_file)
|
|
if file_info.get("tools"):
|
|
print(f"Tools: {', '.join(file_info['tools'])}")
|
|
|
|
print("")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Show agent information")
|
|
parser.add_argument("name", nargs="?", help="Agent name to show details")
|
|
parser.add_argument("--tree", "-t", action="store_true",
|
|
help="Show agent hierarchy tree")
|
|
parser.add_argument("--list", "-l", action="store_true", help="List all agents")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.tree:
|
|
show_tree()
|
|
elif args.name and not args.list:
|
|
show_agent(args.name)
|
|
else:
|
|
list_agents()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|