CheddahBot/cheddahbot/skills/__init__.py

64 lines
1.7 KiB
Python

"""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}"