vtcode_core/skills/loader/
resources.rs1use std::fs;
2use std::path::Path;
3
4use anyhow::Result;
5
6pub fn load_skill_resources(skill_path: &Path) -> Result<Vec<crate::skills::types::SkillResource>> {
7 let mut resources = Vec::new();
8 let resource_dir = skill_path.join("scripts");
9
10 if resource_dir.exists() {
11 for entry in fs::read_dir(&resource_dir)? {
12 let entry = entry?;
13 let path = entry.path();
14
15 if path.is_file() {
16 let rel_path = path
17 .strip_prefix(skill_path)
18 .map(|p| p.to_string_lossy().to_string())
19 .unwrap_or_default();
20
21 let resource_type = match path.extension().and_then(|e| e.to_str()) {
22 Some("py") | Some("sh") | Some("bash") => crate::skills::types::ResourceType::Script,
23 Some("md") => crate::skills::types::ResourceType::Markdown,
24 Some("json") | Some("yaml") | Some("yml") => crate::skills::types::ResourceType::Reference,
25 _ => crate::skills::types::ResourceType::Other(format!("{:?}", path.extension())),
26 };
27
28 resources.push(crate::skills::types::SkillResource { path: rel_path, resource_type, content: None });
29 }
30 }
31 }
32
33 let references_dir = skill_path.join("references");
35 if references_dir.exists() {
36 for entry in fs::read_dir(&references_dir)? {
37 let entry = entry?;
38 let path = entry.path();
39
40 if path.is_file() {
41 let rel_path = path
42 .strip_prefix(skill_path)
43 .map(|p| p.to_string_lossy().to_string())
44 .unwrap_or_default();
45
46 let resource_type = match path.extension().and_then(|e| e.to_str()) {
47 Some("md") => crate::skills::types::ResourceType::Reference,
48 Some("json") | Some("yaml") | Some("yml") | Some("txt") | Some("csv") => {
49 crate::skills::types::ResourceType::Reference
50 }
51 _ => crate::skills::types::ResourceType::Other(format!("{:?}", path.extension())),
52 };
53
54 resources.push(crate::skills::types::SkillResource { path: rel_path, resource_type, content: None });
55 }
56 }
57 }
58
59 let assets_dir = skill_path.join("assets");
61 if assets_dir.exists() {
62 for entry in fs::read_dir(&assets_dir)? {
63 let entry = entry?;
64 let path = entry.path();
65
66 if path.is_file() {
67 let rel_path = path
68 .strip_prefix(skill_path)
69 .map(|p| p.to_string_lossy().to_string())
70 .unwrap_or_default();
71
72 let resource_type = match path.extension().and_then(|e| e.to_str()) {
73 Some("png") | Some("jpg") | Some("jpeg") | Some("gif") | Some("svg") => {
74 crate::skills::types::ResourceType::Asset
75 }
76 Some("json") | Some("yaml") | Some("yml") | Some("txt") | Some("csv") => {
77 crate::skills::types::ResourceType::Asset
78 }
79 _ => crate::skills::types::ResourceType::Asset,
80 };
81
82 resources.push(crate::skills::types::SkillResource { path: rel_path, resource_type, content: None });
83 }
84 }
85 }
86
87 Ok(resources)
88}
89
90pub(crate) fn is_executable_file(path: &Path) -> bool {
91 #[cfg(unix)]
92 {
93 use std::os::unix::fs::PermissionsExt;
94 if let Ok(meta) = path.metadata() {
95 return meta.permissions().mode() & 0o111 != 0;
96 }
97 }
98 #[cfg(windows)]
99 {
100 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
101 return matches!(ext.to_lowercase().as_str(), "exe" | "bat" | "cmd");
102 }
103 }
104 false
105}