-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathskills.py
More file actions
113 lines (86 loc) · 3.76 KB
/
Copy pathskills.py
File metadata and controls
113 lines (86 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
"""Skills discovery and loading system.
Scans a skills directory for SKILL.md manifest files and maintains a registry
of available skills that can be loaded by the agent at runtime.
"""
from pathlib import Path
from config import get_logger
from utils import parse_frontmatter
from wcode_errors import ToolError
logger = get_logger(__name__)
class SkillRegistry:
"""In-memory registry of available skills.
Scans *skills_dir* for ``SKILL.md`` manifests at construction time.
Skills are keyed by their ``name`` frontmatter field (falling back to
the directory name).
Usage::
registry = SkillRegistry(Path("skills"))
print(registry.list_skills())
content = registry.load_skill("my-skill")
"""
def __init__(self, skills_dir: Path) -> None:
self._skills_dir = skills_dir
self._registry: dict[str, dict] = {}
self._scan()
# ── Public API ──────────────────────────────────────────────────────
def list_skills(self) -> str:
"""Return a formatted markdown string listing all registered skills."""
if not self._registry:
return ""
lines = ["## Available Skills"]
for skill in self._registry.values():
lines.append(f"- **{skill['name']}**: {skill['description']}")
return "\n".join(lines)
def load_skill(self, name: str) -> str:
"""Load the full SKILL.md content for a registered skill.
Parameters
----------
name : str
The name of the skill to load (as shown in ``list_skills``).
Returns
-------
str
The raw SKILL.md content.
Raises
------
ToolError
If the skill name is not found in the registry.
"""
skill = self._registry.get(name)
if not skill:
raise ToolError(f"Skill '{name}' not found")
return skill["content"]
# ── Internal ────────────────────────────────────────────────────────
def _scan(self) -> None:
"""Scan *self._skills_dir* and populate the registry."""
if not self._skills_dir.exists():
logger.warning(f"Skills directory {self._skills_dir} does not exist.")
return
for d in sorted(self._skills_dir.iterdir()):
if not d.is_dir():
continue
manifest = d / "SKILL.md"
if manifest.exists():
raw = manifest.read_text()
meta, content = parse_frontmatter(raw)
name = meta.get("name", d.name)
desc = meta.get("description", raw.split("\n")[0].lstrip("#").strip())
self._registry[name] = {
"name": name,
"description": desc,
"content": raw,
}
# ── Module-level convenience (delegates to a default registry) ────────────
_default_registry: SkillRegistry | None = None
def _get_default_registry() -> SkillRegistry:
"""Lazily create and return a default SkillRegistry for the workspace."""
global _default_registry
if _default_registry is None:
from config import SKILLS_DIR
_default_registry = SkillRegistry(SKILLS_DIR)
return _default_registry
def list_skills() -> str:
"""Module-level shim — delegates to the default registry."""
return _get_default_registry().list_skills()
def run_load_skill(name: str) -> str:
"""Module-level shim for tool registration — delegates to the default registry."""
return _get_default_registry().load_skill(name)