use std::path::{Path, PathBuf};
use anyhow::Result;
use tokio::fs;
use crate::plugins::PluginManifest;
pub struct CommandsHandler;
impl CommandsHandler {
pub async fn process_commands(
plugin_path: &Path,
manifest_commands: Option<Vec<String>>,
) -> Result<Vec<PathBuf>> {
let mut command_files = Vec::new();
if let Some(manifest_paths) = manifest_commands {
for path in manifest_paths {
let full_path = plugin_path.join(&path);
if full_path.exists() && full_path.is_file() {
command_files.push(full_path);
}
}
}
let default_commands_dir = plugin_path.join("commands");
if default_commands_dir.exists() && default_commands_dir.is_dir() {
let mut entries = fs::read_dir(&default_commands_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "md") {
command_files.push(path);
}
}
}
Ok(command_files)
}
}
pub struct AgentsHandler;
impl AgentsHandler {
pub async fn process_agents(
plugin_path: &Path,
manifest_agents: Option<Vec<String>>,
) -> Result<Vec<PathBuf>> {
let mut agent_files = Vec::new();
if let Some(manifest_paths) = manifest_agents {
for path in manifest_paths {
let full_path = plugin_path.join(&path);
if full_path.exists() && full_path.is_file() {
agent_files.push(full_path);
}
}
}
let default_agents_dir = plugin_path.join("agents");
if default_agents_dir.exists() && default_agents_dir.is_dir() {
let mut entries = fs::read_dir(&default_agents_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "md") {
agent_files.push(path);
}
}
}
Ok(agent_files)
}
}
pub struct SkillsHandler;
impl SkillsHandler {
pub async fn process_skills(
plugin_path: &Path,
manifest_skills: Option<Vec<String>>,
) -> Result<Vec<PathBuf>> {
let mut skill_dirs = Vec::new();
if let Some(manifest_paths) = manifest_skills {
for path in manifest_paths {
let full_path = plugin_path.join(&path);
if full_path.exists() && full_path.is_dir() {
skill_dirs.push(full_path);
}
}
}
let default_skills_dir = plugin_path.join("skills");
if default_skills_dir.exists() && default_skills_dir.is_dir() {
let mut entries = fs::read_dir(&default_skills_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.is_dir() {
let skill_md = path.join("SKILL.md");
if skill_md.exists() && skill_md.is_file() {
skill_dirs.push(path);
}
}
}
}
Ok(skill_dirs)
}
}
pub struct HooksHandler;
impl HooksHandler {
pub async fn process_hooks(
plugin_path: &Path,
manifest_hooks: Option<serde_json::Value>,
) -> Result<Option<PathBuf>> {
if let Some(hooks_config) = manifest_hooks {
if let Some(path_str) = hooks_config.as_str() {
let hooks_path = plugin_path.join(path_str);
if hooks_path.exists() && hooks_path.is_file() {
return Ok(Some(hooks_path));
}
}
}
let default_hooks_path = plugin_path.join("hooks/hooks.json");
if default_hooks_path.exists() && default_hooks_path.is_file() {
return Ok(Some(default_hooks_path));
}
Ok(None)
}
}
pub struct McpServersHandler;
impl McpServersHandler {
pub async fn process_mcp_servers(
plugin_path: &Path,
manifest_mcp: Option<serde_json::Value>,
) -> Result<Option<PathBuf>> {
if let Some(mcp_config) = manifest_mcp {
if let Some(path_str) = mcp_config.as_str() {
let mcp_path = plugin_path.join(path_str);
if mcp_path.exists() && mcp_path.is_file() {
return Ok(Some(mcp_path));
}
}
}
let default_mcp_path = plugin_path.join(".mcp.json");
if default_mcp_path.exists() && default_mcp_path.is_file() {
return Ok(Some(default_mcp_path));
}
Ok(None)
}
}
pub struct LspServersHandler;
impl LspServersHandler {
pub async fn process_lsp_servers(
plugin_path: &Path,
manifest_lsp: Option<serde_json::Value>,
) -> Result<Option<PathBuf>> {
if let Some(lsp_config) = manifest_lsp {
if let Some(path_str) = lsp_config.as_str() {
let lsp_path = plugin_path.join(path_str);
if lsp_path.exists() && lsp_path.is_file() {
return Ok(Some(lsp_path));
}
}
}
let default_lsp_path = plugin_path.join(".lsp.json");
if default_lsp_path.exists() && default_lsp_path.is_file() {
return Ok(Some(default_lsp_path));
}
Ok(None)
}
}
pub struct PluginComponentsHandler;
impl PluginComponentsHandler {
pub async fn process_all_components<P: AsRef<Path>>(
plugin_path: P,
manifest: &PluginManifest,
) -> Result<PluginComponents> {
let path_buf = plugin_path.as_ref().to_path_buf();
let commands =
CommandsHandler::process_commands(&path_buf, manifest.commands.clone()).await?;
let agents = AgentsHandler::process_agents(&path_buf, manifest.agents.clone()).await?;
let skills = SkillsHandler::process_skills(&path_buf, manifest.skills.clone()).await?;
let hooks = HooksHandler::process_hooks(
&path_buf,
manifest.hooks.as_ref().map(|h| match h {
crate::plugins::manifest::HookConfig::Path(path) => {
serde_json::Value::String(path.clone())
}
crate::plugins::manifest::HookConfig::Inline(_) => serde_json::Value::Null, }),
)
.await?;
let mcp_servers = McpServersHandler::process_mcp_servers(
&path_buf,
manifest.mcp_servers.as_ref().map(|m| match m {
crate::plugins::manifest::McpServerConfig::Path(path) => {
serde_json::Value::String(path.clone())
}
crate::plugins::manifest::McpServerConfig::Inline(_) => serde_json::Value::Null, }),
)
.await?;
let lsp_servers = LspServersHandler::process_lsp_servers(
&path_buf,
manifest.lsp_servers.as_ref().map(|l| match l {
crate::plugins::manifest::LspServerConfig::Path(path) => {
serde_json::Value::String(path.clone())
}
crate::plugins::manifest::LspServerConfig::Inline(_) => serde_json::Value::Null, }),
)
.await?;
Ok(PluginComponents {
commands,
agents,
skills,
hooks,
mcp_servers,
lsp_servers,
})
}
}
pub struct PluginComponents {
pub commands: Vec<PathBuf>,
pub agents: Vec<PathBuf>,
pub skills: Vec<PathBuf>,
pub hooks: Option<PathBuf>,
pub mcp_servers: Option<PathBuf>,
pub lsp_servers: Option<PathBuf>,
}