"""Skill registry with @skill decorator and loader.""" from __future__ import annotations import importlib.util import logging from pathlib import Path from typing import Callable log = logging.getLogger(__name__) _SKILLS: dict[str, "SkillDef"] = {} class SkillDef: def __init__(self, name: str, description: str, func: Callable): self.name = name self.description = description self.func = func def skill(name: str, description: str): """Decorator to register a skill.""" def decorator(func: Callable) -> Callable: _SKILLS[name] = SkillDef(name, description, func) return func return decorator def load_skill(path: Path): """Dynamically load a skill from a .py file.""" spec = importlib.util.spec_from_file_location(path.stem, path) if spec and spec.loader: mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) log.info("Loaded skill from %s", path) def discover_skills(skills_dir: Path): """Load all .py files from the skills directory.""" if not skills_dir.exists(): return for path in skills_dir.glob("*.py"): if path.name.startswith("_"): continue try: load_skill(path) except Exception as e: log.warning("Failed to load skill %s: %s", path.name, e) def list_skills() -> list[SkillDef]: return list(_SKILLS.values()) def run_skill(name: str, **kwargs) -> str: if name not in _SKILLS: return f"Unknown skill: {name}" try: result = _SKILLS[name].func(**kwargs) return str(result) if result is not None else "Done." except Exception as e: return f"Skill error: {e}"