telos_agent/knowledge/skills/
loader.rs1use crate::knowledge::skills::{Skill, SkillArg, SkillSource};
2use std::path::Path;
3
4impl SkillLoader {
5 pub fn load_bundled_skills() -> Vec<Skill> {
7 let mut skills = Vec::new();
8 let files: &[(&str, &str)] = &[
9 ("verify.md", include_str!("bundled/verify.md")),
10 ("debug.md", include_str!("bundled/debug.md")),
11 ("remember.md", include_str!("bundled/remember.md")),
12 ("brainstorm.md", include_str!("bundled/brainstorm.md")),
13 ("update-config.md", include_str!("bundled/update-config.md")),
14 ("explore.md", include_str!("bundled/explore.md")),
15 ("web-access.md", include_str!("bundled/web-access.md")),
16 ("powershell-use.md", include_str!("bundled/powershell-use.md")),
17 ];
18 for (_name, content) in files {
19 if let Some(skill) = Self::parse_skill(content, SkillSource::Bundled) {
20 skills.push(skill);
21 }
22 }
23 skills
24 }
25}
26
27pub struct SkillLoader;
29
30impl SkillLoader {
31 pub fn load_skill_file(path: &Path, source: SkillSource) -> Option<Skill> {
33 let content = std::fs::read_to_string(path).ok()?;
34 Self::parse_skill(&content, source)
35 }
36
37 pub fn load_from_dir(dir: &Path) -> Result<Vec<Skill>, std::io::Error> {
40 let mut skills = Vec::new();
41 if !dir.exists() {
42 return Ok(skills);
43 }
44 let entries = std::fs::read_dir(dir)?;
45 for entry in entries {
46 let entry = entry?;
47 let path = entry.path();
48 if path.extension().is_none_or(|ext| ext != "md") {
49 continue;
50 }
51 if let Ok(content) = std::fs::read_to_string(&path) {
52 if let Some(skill) = Self::parse_skill(&content, SkillSource::Project) {
53 skills.push(skill);
54 } else {
55 tracing::warn!("failed to parse skill file: {}", path.display());
56 }
57 }
58 }
59 Ok(skills)
60 }
61
62 fn parse_skill(content: &str, source: SkillSource) -> Option<Skill> {
65 let content = content.trim();
66 let rest = content.strip_prefix("---")?;
68 let (frontmatter, body) = rest.split_once("\n---")?;
70 let body = body.trim().to_string();
71
72 let fm: serde_yaml::Value = serde_yaml::from_str(frontmatter).ok()?;
73
74 let name = fm.get("name")?.as_str()?.to_string();
75 let description = fm.get("description")?.as_str()?.to_string();
76 let when_to_use = fm.get("whenToUse").and_then(|v| v.as_str()).map(String::from);
77 let prompt = fm.get("prompt")?.as_str()?.to_string();
78
79 let arguments = fm
80 .get("arguments")
81 .and_then(|v| v.as_sequence())
82 .map(|args| {
83 args.iter()
84 .map(|a| SkillArg {
85 name: a.get("name").and_then(|v| v.as_str()).unwrap_or("").to_string(),
86 description: a
87 .get("description")
88 .and_then(|v| v.as_str())
89 .unwrap_or("")
90 .to_string(),
91 required: a.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
92 })
93 .collect()
94 })
95 .unwrap_or_default();
96
97 Some(Skill { name, description, when_to_use, prompt, arguments, body, source })
98 }
99}