loader.py 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. from __future__ import annotations
  2. import re
  3. from pathlib import Path
  4. from supply_agent.types import SkillInfo
  5. class SkillLoader:
  6. """Load skills from SKILL.md files (Cursor-compatible format)."""
  7. SKILL_FILENAME = "SKILL.md"
  8. def __init__(self, skills_dir: Path | str) -> None:
  9. self.skills_dir = Path(skills_dir)
  10. def discover(self) -> list[Path]:
  11. """Find all SKILL.md files in the skills directory."""
  12. if not self.skills_dir.exists():
  13. return []
  14. return sorted(self.skills_dir.rglob(self.SKILL_FILENAME))
  15. def load(self, skill_path: Path) -> SkillInfo:
  16. """Load a single skill from a SKILL.md file."""
  17. content = skill_path.read_text(encoding="utf-8")
  18. name, description = self._parse_frontmatter(content, skill_path)
  19. body = self._strip_frontmatter(content)
  20. return SkillInfo(
  21. name=name,
  22. description=description,
  23. content=body.strip(),
  24. path=str(skill_path),
  25. )
  26. def load_all(self) -> list[SkillInfo]:
  27. """Load all discovered skills."""
  28. return [self.load(p) for p in self.discover()]
  29. def _parse_frontmatter(self, content: str, path: Path) -> tuple[str, str]:
  30. """Extract name and description from YAML frontmatter or heading."""
  31. name = path.parent.name
  32. description = ""
  33. fm_match = re.match(r"^---\s*\n(.*?)\n---", content, re.DOTALL)
  34. if fm_match:
  35. fm = fm_match.group(1)
  36. for line in fm.splitlines():
  37. if line.startswith("name:"):
  38. name = line.split(":", 1)[1].strip().strip('"').strip("'")
  39. elif line.startswith("description:"):
  40. description = line.split(":", 1)[1].strip().strip('"').strip("'")
  41. return name, description
  42. # Fallback: use directory name; extract description from first paragraph if present
  43. return name, description
  44. def _strip_frontmatter(self, content: str) -> str:
  45. return re.sub(r"^---\s*\n.*?\n---\s*\n?", "", content, count=1, flags=re.DOTALL)