1use crate::{Skill, SkillRegistry, SkillTier};
7use compact_str::CompactString;
8use serde::Deserialize;
9use std::{collections::BTreeMap, path::Path};
10use wcore::utils::split_yaml_frontmatter;
11
12#[derive(Debug, Deserialize)]
14struct SkillFrontmatter {
15 name: String,
16 #[serde(default)]
17 description: String,
18 #[serde(default)]
19 license: Option<String>,
20 #[serde(default)]
21 compatibility: Option<String>,
22 #[serde(default)]
23 metadata: BTreeMap<String, String>,
24 #[serde(default, rename = "allowed-tools")]
25 allowed_tools: Option<String>,
26}
27
28pub fn parse_skill_md(content: &str) -> anyhow::Result<Skill> {
30 let (frontmatter, body) = split_yaml_frontmatter(content)?;
31 let fm: SkillFrontmatter = serde_yaml::from_str(frontmatter)?;
32
33 let allowed_tools = fm
34 .allowed_tools
35 .map(|s| {
36 s.split_whitespace()
37 .map(CompactString::from)
38 .collect::<Vec<_>>()
39 })
40 .unwrap_or_default();
41
42 let metadata = fm
43 .metadata
44 .into_iter()
45 .map(|(k, v)| (CompactString::from(k), v))
46 .collect();
47
48 Ok(Skill {
49 name: CompactString::from(fm.name),
50 description: fm.description,
51 license: fm.license.map(CompactString::from),
52 compatibility: fm.compatibility.map(CompactString::from),
53 metadata,
54 allowed_tools,
55 body: body.to_owned(),
56 })
57}
58
59pub fn load_skills_dir(path: impl AsRef<Path>, tier: SkillTier) -> anyhow::Result<SkillRegistry> {
62 let path = path.as_ref();
63 let mut registry = SkillRegistry::new();
64
65 let entries = std::fs::read_dir(path)
66 .map_err(|e| anyhow::anyhow!("failed to read skill directory {}: {e}", path.display()))?;
67
68 for entry in entries {
69 let entry = entry?;
70 let entry_path = entry.path();
71 if !entry_path.is_dir() {
72 continue;
73 }
74
75 let skill_file = entry_path.join("SKILL.md");
76 if !skill_file.exists() {
77 continue;
78 }
79
80 let content = std::fs::read_to_string(&skill_file)
81 .map_err(|e| anyhow::anyhow!("failed to read {}: {e}", skill_file.display()))?;
82
83 let skill = parse_skill_md(&content)?;
84 registry.add(skill, tier);
85 }
86
87 Ok(registry)
88}