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 fs::metadata(&full_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
command_files.push(full_path);
}
}
}
let default_commands_dir = plugin_path.join("commands");
if fs::metadata(&default_commands_dir)
.await
.map(|metadata| metadata.is_dir())
.unwrap_or(false)
{
let mut entries = fs::read_dir(&default_commands_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if fs::metadata(&path).await.map(|metadata| metadata.is_file()).unwrap_or(false)
&& 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 fs::metadata(&full_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
agent_files.push(full_path);
}
}
}
let default_agents_dir = plugin_path.join("agents");
if fs::metadata(&default_agents_dir)
.await
.map(|metadata| metadata.is_dir())
.unwrap_or(false)
{
let mut entries = fs::read_dir(&default_agents_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if fs::metadata(&path).await.map(|metadata| metadata.is_file()).unwrap_or(false)
&& 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 fs::metadata(&full_path)
.await
.map(|metadata| metadata.is_dir())
.unwrap_or(false)
{
skill_dirs.push(full_path);
}
}
}
let default_skills_dir = plugin_path.join("skills");
if fs::metadata(&default_skills_dir)
.await
.map(|metadata| metadata.is_dir())
.unwrap_or(false)
{
let mut entries = fs::read_dir(&default_skills_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if fs::metadata(&path).await.map(|metadata| metadata.is_dir()).unwrap_or(false) {
let skill_md = path.join("SKILL.md");
if fs::metadata(&skill_md)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
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 fs::metadata(&hooks_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
return Ok(Some(hooks_path));
}
}
}
let default_hooks_path = plugin_path.join("hooks/hooks.json");
if fs::metadata(&default_hooks_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
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 fs::metadata(&mcp_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
return Ok(Some(mcp_path));
}
}
}
let default_mcp_path = plugin_path.join(".mcp.json");
if fs::metadata(&default_mcp_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
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 fs::metadata(&lsp_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
return Ok(Some(lsp_path));
}
}
}
let default_lsp_path = plugin_path.join(".lsp.json");
if fs::metadata(&default_lsp_path)
.await
.map(|metadata| metadata.is_file())
.unwrap_or(false)
{
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>,
}