use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginCapability {
Skill,
Hook,
McpServer,
Channel,
Tool,
Custom,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginManifest {
pub name: String,
pub version: String,
pub description: String,
pub capabilities: Vec<String>,
pub entry_point: Option<String>,
pub dependencies: Vec<String>,
}
pub trait Plugin: Send + Sync {
fn id(&self) -> &str;
fn name(&self) -> &str;
fn capabilities(&self) -> Vec<PluginCapability>;
fn version(&self) -> &str;
fn init(&mut self) -> Result<(), String> {
Ok(())
}
fn shutdown(&mut self) -> Result<(), String> {
Ok(())
}
}
pub struct PluginRegistry {
plugins: Vec<Box<dyn Plugin>>,
by_name: std::collections::HashMap<String, usize>,
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
by_name: std::collections::HashMap::new(),
}
}
pub fn register(&mut self, plugin: Box<dyn Plugin>) {
let idx = self.plugins.len();
self.by_name.insert(plugin.id().to_string(), idx);
self.plugins.push(plugin);
}
pub fn scan_dir(&mut self, dir: &PathBuf) -> std::io::Result<usize> {
if !dir.exists() {
return Ok(0);
}
let mut count = 0;
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if !path.is_dir() {
continue;
}
let manifest = if path.join("manifest.json").exists() {
let content = std::fs::read_to_string(path.join("manifest.json"))?;
serde_json::from_str::<PluginManifest>(&content).ok()
} else {
continue;
};
if let Some(manifest) = manifest {
self.register(Box::new(RegisteredPlugin::new(manifest)));
count += 1;
}
}
Ok(count)
}
pub fn get(&self, id: &str) -> Option<&dyn Plugin> {
self.by_name.get(id).map(|&i| self.plugins[i].as_ref())
}
pub fn list(&self) -> Vec<&dyn Plugin> {
self.plugins.iter().map(|p| p.as_ref()).collect()
}
pub fn init_all(&mut self) -> Vec<Result<(), String>> {
self.plugins.iter_mut().map(|p| p.init()).collect()
}
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
struct RegisteredPlugin {
manifest: PluginManifest,
}
impl RegisteredPlugin {
fn new(manifest: PluginManifest) -> Self {
Self { manifest }
}
}
impl Plugin for RegisteredPlugin {
fn id(&self) -> &str {
&self.manifest.name
}
fn name(&self) -> &str {
&self.manifest.name
}
fn capabilities(&self) -> Vec<PluginCapability> {
self.manifest
.capabilities
.iter()
.map(|c| match c.as_str() {
"skill" => PluginCapability::Skill,
"hook" => PluginCapability::Hook,
"mcp" => PluginCapability::McpServer,
"channel" => PluginCapability::Channel,
"tool" => PluginCapability::Tool,
_ => PluginCapability::Custom,
})
.collect()
}
fn version(&self) -> &str {
&self.manifest.version
}
}