vtcode_core/plugins/
components.rs1use std::path::{Path, PathBuf};
11
12use anyhow::Result;
13use tokio::fs;
14
15use crate::plugins::PluginManifest;
16
17pub struct CommandsHandler;
19
20impl CommandsHandler {
21 pub async fn process_commands(
23 plugin_path: &Path,
24 manifest_commands: Option<Vec<String>>,
25 ) -> Result<Vec<PathBuf>> {
26 let mut command_files = Vec::new();
27
28 if let Some(manifest_paths) = manifest_commands {
30 for path in manifest_paths {
31 let full_path = plugin_path.join(&path);
32 if full_path.exists() && full_path.is_file() {
33 command_files.push(full_path);
34 }
35 }
36 }
37
38 let default_commands_dir = plugin_path.join("commands");
40 if default_commands_dir.exists() && default_commands_dir.is_dir() {
41 let mut entries = fs::read_dir(&default_commands_dir).await?;
42 while let Some(entry) = entries.next_entry().await? {
43 let path = entry.path();
44 if path.is_file() && path.extension().is_some_and(|ext| ext == "md") {
45 command_files.push(path);
46 }
47 }
48 }
49
50 Ok(command_files)
51 }
52}
53
54pub struct AgentsHandler;
56
57impl AgentsHandler {
58 pub async fn process_agents(
60 plugin_path: &Path,
61 manifest_agents: Option<Vec<String>>,
62 ) -> Result<Vec<PathBuf>> {
63 let mut agent_files = Vec::new();
64
65 if let Some(manifest_paths) = manifest_agents {
67 for path in manifest_paths {
68 let full_path = plugin_path.join(&path);
69 if full_path.exists() && full_path.is_file() {
70 agent_files.push(full_path);
71 }
72 }
73 }
74
75 let default_agents_dir = plugin_path.join("agents");
77 if default_agents_dir.exists() && default_agents_dir.is_dir() {
78 let mut entries = fs::read_dir(&default_agents_dir).await?;
79 while let Some(entry) = entries.next_entry().await? {
80 let path = entry.path();
81 if path.is_file() && path.extension().is_some_and(|ext| ext == "md") {
82 agent_files.push(path);
83 }
84 }
85 }
86
87 Ok(agent_files)
88 }
89}
90
91pub struct SkillsHandler;
93
94impl SkillsHandler {
95 pub async fn process_skills(
97 plugin_path: &Path,
98 manifest_skills: Option<Vec<String>>,
99 ) -> Result<Vec<PathBuf>> {
100 let mut skill_dirs = Vec::new();
101
102 if let Some(manifest_paths) = manifest_skills {
104 for path in manifest_paths {
105 let full_path = plugin_path.join(&path);
106 if full_path.exists() && full_path.is_dir() {
107 skill_dirs.push(full_path);
108 }
109 }
110 }
111
112 let default_skills_dir = plugin_path.join("skills");
114 if default_skills_dir.exists() && default_skills_dir.is_dir() {
115 let mut entries = fs::read_dir(&default_skills_dir).await?;
116 while let Some(entry) = entries.next_entry().await? {
117 let path = entry.path();
118 if path.is_dir() {
119 let skill_md = path.join("SKILL.md");
121 if skill_md.exists() && skill_md.is_file() {
122 skill_dirs.push(path);
123 }
124 }
125 }
126 }
127
128 Ok(skill_dirs)
129 }
130}
131
132pub struct HooksHandler;
134
135impl HooksHandler {
136 pub async fn process_hooks(
138 plugin_path: &Path,
139 manifest_hooks: Option<serde_json::Value>,
140 ) -> Result<Option<PathBuf>> {
141 if let Some(hooks_config) = manifest_hooks {
143 if let Some(path_str) = hooks_config.as_str() {
145 let hooks_path = plugin_path.join(path_str);
146 if hooks_path.exists() && hooks_path.is_file() {
147 return Ok(Some(hooks_path));
148 }
149 }
150 }
151
152 let default_hooks_path = plugin_path.join("hooks/hooks.json");
154 if default_hooks_path.exists() && default_hooks_path.is_file() {
155 return Ok(Some(default_hooks_path));
156 }
157
158 Ok(None)
159 }
160}
161
162pub struct McpServersHandler;
164
165impl McpServersHandler {
166 pub async fn process_mcp_servers(
168 plugin_path: &Path,
169 manifest_mcp: Option<serde_json::Value>,
170 ) -> Result<Option<PathBuf>> {
171 if let Some(mcp_config) = manifest_mcp {
173 if let Some(path_str) = mcp_config.as_str() {
175 let mcp_path = plugin_path.join(path_str);
176 if mcp_path.exists() && mcp_path.is_file() {
177 return Ok(Some(mcp_path));
178 }
179 }
180 }
181
182 let default_mcp_path = plugin_path.join(".mcp.json");
184 if default_mcp_path.exists() && default_mcp_path.is_file() {
185 return Ok(Some(default_mcp_path));
186 }
187
188 Ok(None)
189 }
190}
191
192pub struct LspServersHandler;
194
195impl LspServersHandler {
196 pub async fn process_lsp_servers(
198 plugin_path: &Path,
199 manifest_lsp: Option<serde_json::Value>,
200 ) -> Result<Option<PathBuf>> {
201 if let Some(lsp_config) = manifest_lsp {
203 if let Some(path_str) = lsp_config.as_str() {
205 let lsp_path = plugin_path.join(path_str);
206 if lsp_path.exists() && lsp_path.is_file() {
207 return Ok(Some(lsp_path));
208 }
209 }
210 }
211
212 let default_lsp_path = plugin_path.join(".lsp.json");
214 if default_lsp_path.exists() && default_lsp_path.is_file() {
215 return Ok(Some(default_lsp_path));
216 }
217
218 Ok(None)
219 }
220}
221
222pub struct PluginComponentsHandler;
224
225impl PluginComponentsHandler {
226 pub async fn process_all_components<P: AsRef<Path>>(
228 plugin_path: P,
229 manifest: &PluginManifest,
230 ) -> Result<PluginComponents> {
231 let path_buf = plugin_path.as_ref().to_path_buf();
232 let commands =
233 CommandsHandler::process_commands(&path_buf, manifest.commands.clone()).await?;
234
235 let agents = AgentsHandler::process_agents(&path_buf, manifest.agents.clone()).await?;
236
237 let skills = SkillsHandler::process_skills(&path_buf, manifest.skills.clone()).await?;
238
239 let hooks = HooksHandler::process_hooks(
240 &path_buf,
241 manifest.hooks.as_ref().map(|h| match h {
242 crate::plugins::manifest::HookConfig::Path(path) => {
243 serde_json::Value::String(path.clone())
244 }
245 crate::plugins::manifest::HookConfig::Inline(_) => serde_json::Value::Null, }),
247 )
248 .await?;
249
250 let mcp_servers = McpServersHandler::process_mcp_servers(
251 &path_buf,
252 manifest.mcp_servers.as_ref().map(|m| match m {
253 crate::plugins::manifest::McpServerConfig::Path(path) => {
254 serde_json::Value::String(path.clone())
255 }
256 crate::plugins::manifest::McpServerConfig::Inline(_) => serde_json::Value::Null, }),
258 )
259 .await?;
260
261 let lsp_servers = LspServersHandler::process_lsp_servers(
262 &path_buf,
263 manifest.lsp_servers.as_ref().map(|l| match l {
264 crate::plugins::manifest::LspServerConfig::Path(path) => {
265 serde_json::Value::String(path.clone())
266 }
267 crate::plugins::manifest::LspServerConfig::Inline(_) => serde_json::Value::Null, }),
269 )
270 .await?;
271
272 Ok(PluginComponents {
273 commands,
274 agents,
275 skills,
276 hooks,
277 mcp_servers,
278 lsp_servers,
279 })
280 }
281}
282
283pub struct PluginComponents {
285 pub commands: Vec<PathBuf>,
286 pub agents: Vec<PathBuf>,
287 pub skills: Vec<PathBuf>,
288 pub hooks: Option<PathBuf>,
289 pub mcp_servers: Option<PathBuf>,
290 pub lsp_servers: Option<PathBuf>,
291}