Skip to main content

fastskill_core/core/
skill_manager.rs

1//! Skill management service implementation
2
3use crate::core::service::{ServiceError, SkillId};
4use async_trait::async_trait;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12// Note: SkillInfoMetadata removed - use skill-project.toml via manifest system instead
13
14/// Source type for skill installation
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum SourceType {
17    GitUrl,
18    LocalPath,
19    ZipFile,
20    Source,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct SkillDefinition {
25    pub id: SkillId,
26    pub name: String,
27    pub description: String,
28    pub version: String,
29    pub author: Option<String>,
30    pub enabled: bool,
31    pub created_at: DateTime<Utc>,
32    pub updated_at: DateTime<Utc>,
33
34    // File references
35    pub skill_file: std::path::PathBuf,
36    pub reference_files: Option<Vec<std::path::PathBuf>>,
37    pub script_files: Option<Vec<std::path::PathBuf>>,
38    pub asset_files: Option<Vec<std::path::PathBuf>>,
39
40    // Execution configuration
41    pub execution_environment: Option<String>,
42    pub dependencies: Option<Vec<String>>,
43    pub timeout: Option<u64>,
44
45    // Source tracking
46    pub source_url: Option<String>,
47    pub source_type: Option<SourceType>,
48    pub source_branch: Option<String>,
49    pub source_tag: Option<String>,
50    pub source_subdir: Option<PathBuf>,
51    pub installed_from: Option<String>,
52    pub commit_hash: Option<String>,
53    pub fetched_at: Option<DateTime<Utc>>,
54    pub editable: bool,
55}
56
57impl SkillDefinition {
58    pub fn new(id: SkillId, name: String, description: String, version: String) -> Self {
59        let now = Utc::now();
60        Self {
61            id: id.clone(),
62            name,
63            description,
64            version,
65            author: None,
66            enabled: true,
67            created_at: now,
68            updated_at: now,
69            skill_file: std::path::PathBuf::from(format!("./skills/{}/SKILL.md", id)),
70            reference_files: None,
71            script_files: None,
72            asset_files: None,
73            execution_environment: None,
74            dependencies: None,
75            timeout: None,
76            source_url: None,
77            source_type: None,
78            source_branch: None,
79            source_tag: None,
80            source_subdir: None,
81            installed_from: None,
82            commit_hash: None,
83            fetched_at: None,
84            editable: false,
85        }
86    }
87
88    // Note: load_skill_info removed - use skill-project.toml via manifest system instead
89}
90
91#[async_trait]
92pub trait SkillManagementService: Send + Sync {
93    async fn register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError>;
94    async fn force_register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError>;
95    async fn get_skill(&self, skill_id: &SkillId) -> Result<Option<SkillDefinition>, ServiceError>;
96    async fn update_skill(
97        &self,
98        skill_id: &SkillId,
99        updates: SkillUpdate,
100    ) -> Result<(), ServiceError>;
101    async fn unregister_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError>;
102    async fn list_skills(
103        &self,
104        filters: Option<SkillFilters>,
105    ) -> Result<Vec<SkillDefinition>, ServiceError>;
106    async fn enable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError>;
107    async fn disable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError>;
108}
109
110#[derive(Debug)]
111pub struct SkillManager {
112    skills: Arc<RwLock<HashMap<SkillId, SkillDefinition>>>,
113}
114
115impl Default for SkillManager {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121impl SkillManager {
122    pub fn new() -> Self {
123        Self {
124            skills: Arc::new(RwLock::new(HashMap::new())),
125        }
126    }
127}
128
129#[derive(Debug, Clone)]
130pub struct SkillFilters {
131    pub enabled: Option<bool>,
132}
133
134#[derive(Debug, Clone, Default)]
135pub struct SkillUpdate {
136    pub name: Option<String>,
137    pub description: Option<String>,
138    pub version: Option<String>,
139    pub author: Option<String>,
140    pub enabled: Option<bool>,
141    pub source_url: Option<String>,
142    pub source_type: Option<SourceType>,
143    pub source_branch: Option<String>,
144    pub source_tag: Option<String>,
145    pub source_subdir: Option<PathBuf>,
146    pub installed_from: Option<String>,
147    pub commit_hash: Option<String>,
148    pub fetched_at: Option<DateTime<Utc>>,
149    pub editable: Option<bool>,
150}
151
152#[async_trait]
153impl SkillManagementService for SkillManager {
154    async fn register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError> {
155        let mut skills = self.skills.write().await;
156
157        // Check if skill already exists
158        if skills.contains_key(&skill.id) {
159            return Err(ServiceError::Custom(format!(
160                "Skill {} already exists",
161                skill.id
162            )));
163        }
164
165        let skill_id = skill.id.clone();
166        skills.insert(skill_id.clone(), skill);
167        Ok(skill_id)
168    }
169
170    async fn force_register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError> {
171        let mut skills = self.skills.write().await;
172
173        let skill_id = skill.id.clone();
174        skills.insert(skill_id.clone(), skill);
175        Ok(skill_id)
176    }
177
178    async fn get_skill(&self, skill_id: &SkillId) -> Result<Option<SkillDefinition>, ServiceError> {
179        let skills = self.skills.read().await;
180        Ok(skills.get(skill_id).cloned())
181    }
182
183    async fn update_skill(
184        &self,
185        skill_id: &SkillId,
186        updates: SkillUpdate,
187    ) -> Result<(), ServiceError> {
188        let mut skills = self.skills.write().await;
189
190        if let Some(skill) = skills.get_mut(skill_id) {
191            if let Some(name) = updates.name {
192                skill.name = name;
193            }
194            if let Some(description) = updates.description {
195                skill.description = description;
196            }
197            if let Some(version) = updates.version {
198                skill.version = version;
199            }
200            if let Some(author) = updates.author {
201                skill.author = Some(author);
202            }
203            if let Some(enabled) = updates.enabled {
204                skill.enabled = enabled;
205            }
206            if let Some(source_url) = updates.source_url {
207                skill.source_url = Some(source_url);
208            }
209            if let Some(source_type) = updates.source_type {
210                skill.source_type = Some(source_type);
211            }
212            if let Some(source_branch) = updates.source_branch {
213                skill.source_branch = Some(source_branch);
214            }
215            if let Some(source_tag) = updates.source_tag {
216                skill.source_tag = Some(source_tag);
217            }
218            if let Some(source_subdir) = updates.source_subdir {
219                skill.source_subdir = Some(source_subdir);
220            }
221            if let Some(installed_from) = updates.installed_from {
222                skill.installed_from = Some(installed_from);
223            }
224            if let Some(commit_hash) = updates.commit_hash {
225                skill.commit_hash = Some(commit_hash);
226            }
227            if let Some(fetched_at) = updates.fetched_at {
228                skill.fetched_at = Some(fetched_at);
229            }
230            if let Some(editable) = updates.editable {
231                skill.editable = editable;
232            }
233            skill.updated_at = Utc::now();
234            Ok(())
235        } else {
236            Err(ServiceError::SkillNotFound(skill_id.to_string()))
237        }
238    }
239
240    async fn unregister_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError> {
241        let mut skills = self.skills.write().await;
242
243        if skills.remove(skill_id).is_some() {
244            Ok(())
245        } else {
246            Err(ServiceError::SkillNotFound(skill_id.to_string()))
247        }
248    }
249
250    async fn list_skills(
251        &self,
252        filters: Option<SkillFilters>,
253    ) -> Result<Vec<SkillDefinition>, ServiceError> {
254        let skills = self.skills.read().await;
255        let mut filtered_skills: Vec<SkillDefinition> = skills.values().cloned().collect();
256
257        if let Some(filters) = filters {
258            // Filter by enabled status
259            if let Some(enabled) = filters.enabled {
260                filtered_skills.retain(|skill| skill.enabled == enabled);
261            }
262        }
263
264        Ok(filtered_skills)
265    }
266
267    async fn enable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError> {
268        self.update_skill(
269            skill_id,
270            SkillUpdate {
271                enabled: Some(true),
272                ..Default::default()
273            },
274        )
275        .await
276    }
277
278    async fn disable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError> {
279        self.update_skill(
280            skill_id,
281            SkillUpdate {
282                enabled: Some(false),
283                ..Default::default()
284            },
285        )
286        .await
287    }
288}