| 197 | |
| 198 | # === SECTION: skills (s05) === |
| 199 | class SkillLoader: |
| 200 | def __init__(self, skills_dir: Path): |
| 201 | self.skills = {} |
| 202 | if skills_dir.exists(): |
| 203 | for f in sorted(skills_dir.rglob("SKILL.md")): |
| 204 | text = f.read_text() |
| 205 | match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL) |
| 206 | meta, body = {}, text |
| 207 | if match: |
| 208 | for line in match.group(1).strip().splitlines(): |
| 209 | if ":" in line: |
| 210 | k, v = line.split(":", 1) |
| 211 | meta[k.strip()] = v.strip() |
| 212 | body = match.group(2).strip() |
| 213 | name = meta.get("name", f.parent.name) |
| 214 | self.skills[name] = {"meta": meta, "body": body} |
| 215 | |
| 216 | def descriptions(self) -> str: |
| 217 | if not self.skills: return "(no skills)" |
| 218 | return "\n".join(f" - {n}: {s['meta'].get('description', '-')}" for n, s in self.skills.items()) |
| 219 | |
| 220 | def load(self, name: str) -> str: |
| 221 | s = self.skills.get(name) |
| 222 | if not s: return f"Error: Unknown skill '{name}'. Available: {', '.join(self.skills.keys())}" |
| 223 | return f"<skill name=\"{name}\">\n{s['body']}\n</skill>" |
| 224 | |
| 225 | |
| 226 | # === SECTION: compression (s06) === |