//! Skill registry //! //! Manage loaded skills and their execution. use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::RwLock; use zclaw_types::{Result, SkillId}; use super::{Skill, SkillContext, SkillManifest, SkillMode, SkillResult}; use crate::loader; use crate::runner::{PromptOnlySkill, ShellSkill}; /// Skill registry pub struct SkillRegistry { skills: RwLock>>, manifests: RwLock>, skill_dirs: RwLock>, } impl SkillRegistry { pub fn new() -> Self { Self { skills: RwLock::new(HashMap::new()), manifests: RwLock::new(HashMap::new()), skill_dirs: RwLock::new(Vec::new()), } } /// Add a skill directory to scan pub async fn add_skill_dir(&self, dir: PathBuf) -> Result<()> { if !dir.exists() { return Err(zclaw_types::ZclawError::NotFound(format!("Directory not found: {}", dir.display()))); } { let mut dirs = self.skill_dirs.write().await; if !dirs.contains(&dir) { dirs.push(dir.clone()); } } // Scan for skills let skill_paths = loader::discover_skills(&dir)?; for skill_path in skill_paths { self.load_skill_from_dir(&skill_path).await?; } Ok(()) } /// Load a skill from directory async fn load_skill_from_dir(&self, dir: &PathBuf) -> Result<()> { let md_path = dir.join("SKILL.md"); let toml_path = dir.join("skill.toml"); let manifest = if md_path.exists() { loader::load_skill_md(&md_path)? } else if toml_path.exists() { loader::load_skill_toml(&toml_path)? } else { return Err(zclaw_types::ZclawError::NotFound( format!("No SKILL.md or skill.toml found in {}", dir.display()) )); }; // Create skill instance let skill: Arc = match &manifest.mode { SkillMode::PromptOnly => { let prompt = std::fs::read_to_string(&md_path).unwrap_or_default(); Arc::new(PromptOnlySkill::new(manifest.clone(), prompt)) } SkillMode::Shell => { let cmd = std::fs::read_to_string(dir.join("command.sh")) .unwrap_or_else(|_| "echo 'Shell skill not configured'".to_string()); Arc::new(ShellSkill::new(manifest.clone(), cmd)) } _ => { let prompt = std::fs::read_to_string(&md_path).unwrap_or_default(); Arc::new(PromptOnlySkill::new(manifest.clone(), prompt)) } }; // Register (use async write instead of blocking_write) let mut skills = self.skills.write().await; let mut manifests = self.manifests.write().await; skills.insert(manifest.id.clone(), skill); manifests.insert(manifest.id.clone(), manifest); Ok(()) } /// Get a skill by ID pub async fn get(&self, id: &SkillId) -> Option> { let skills = self.skills.read().await; skills.get(id).cloned() } /// Get skill manifest pub async fn get_manifest(&self, id: &SkillId) -> Option { let manifests = self.manifests.read().await; manifests.get(id).cloned() } /// List all skills pub async fn list(&self) -> Vec { let manifests = self.manifests.read().await; manifests.values().cloned().collect() } /// Execute a skill pub async fn execute( &self, id: &SkillId, context: &SkillContext, input: serde_json::Value, ) -> Result { let skill = self.get(id).await .ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Skill not found: {}", id)))?; skill.execute(context, input).await } /// Remove a skill pub async fn remove(&self, id: &SkillId) { let mut skills = self.skills.write().await; let mut manifests = self.manifests.write().await; skills.remove(id); manifests.remove(id); } /// Register a skill directly pub async fn register(&self, skill: Arc, manifest: SkillManifest) { let mut skills = self.skills.write().await; let mut manifests = self.manifests.write().await; skills.insert(manifest.id.clone(), skill); manifests.insert(manifest.id.clone(), manifest); } } impl Default for SkillRegistry { fn default() -> Self { Self::new() } }