use std::sync::Arc;
use crate::tool::{param_string, ToolSpec};
pub fn skill_tools(store: Option<Arc<std::sync::Mutex<crate::memory::store::MemoryStore>>>) -> Vec<ToolSpec> {
vec![
create_skill_tool(store.clone()),
get_skill_tool(store.clone()),
update_skill_tool(store.clone()),
delete_skill_tool(store.clone()),
list_skills_tool(store),
]
}
fn get_store(store: &Option<Arc<std::sync::Mutex<crate::memory::store::MemoryStore>>>) -> Result<std::sync::MutexGuard<'_, crate::memory::store::MemoryStore>, String> {
store.as_ref().and_then(|s| s.lock().ok()).ok_or_else(|| "Memory is not initialized.".into())
}
fn create_skill_tool(store: Option<Arc<std::sync::Mutex<crate::memory::store::MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[
("name", param_string("Short unique name (lowercase, hyphens)")),
("description", param_string("Brief summary of when to use this skill")),
("content", param_string("Full markdown with steps, examples, and pitfalls")),
("category", serde_json::json!({"type": "string", "description": "Grouping category", "default": "general"})),
]);
ToolSpec::new("create_skill", "Create a new reusable skill/procedure", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("");
let description = args.get("description").and_then(|v| v.as_str()).unwrap_or("");
let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");
let category = args.get("category").and_then(|v| v.as_str()).unwrap_or("general");
if let Ok(Some(existing)) = guard.get_skill(name) {
return Ok(format!("Skill '{}' already exists (v{}). Use update_skill to modify it.", name, existing.version));
}
let sid = guard.save_skill(name, description, content, category).map_err(|e| format!("Failed to create skill: {}", e))?;
Ok(format!("Skill '{}' created (v1, id={}).", name, sid))
}),
)
}
fn get_skill_tool(store: Option<Arc<std::sync::Mutex<crate::memory::store::MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[("name", param_string("The skill's name"))]);
ToolSpec::new("get_skill", "Retrieve the full content of a saved skill", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("");
match guard.get_skill(name).map_err(|e| format!("Error: {}", e))? {
Some(s) => Ok(format!("# {} (v{}) [{}]\n{}\n\n---\n{}", s.name, s.version, s.category, s.description, s.content)),
None => {
let available: Vec<String> = guard.list_skills(None).unwrap_or_default().iter().map(|s| s.name.clone()).collect();
let avail_str = if available.is_empty() { "(none)".into() } else { available.join(", ") };
Ok(format!("Skill '{}' not found. Available skills: {}", name, avail_str))
}
}
}),
)
}
fn update_skill_tool(store: Option<Arc<std::sync::Mutex<crate::memory::store::MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[
("name", param_string("The skill's name")),
("description", serde_json::json!({"type": "string", "description": "New description", "default": ""})),
("content", serde_json::json!({"type": "string", "description": "New full content", "default": ""})),
("category", serde_json::json!({"type": "string", "description": "New category", "default": ""})),
]);
ToolSpec::new("update_skill", "Update an existing skill. Only provided fields are changed.", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("");
let description = args.get("description").and_then(|v| v.as_str()).filter(|s| !s.is_empty());
let content = args.get("content").and_then(|v| v.as_str()).filter(|s| !s.is_empty());
let category = args.get("category").and_then(|v| v.as_str()).filter(|s| !s.is_empty());
if description.is_none() && content.is_none() && category.is_none() {
return Err("Nothing to update. Provide at least one field.".into());
}
if guard.update_skill(name, description, content, category).map_err(|e| format!("Failed to update: {}", e))? {
if let Ok(Some(s)) = guard.get_skill(name) { return Ok(format!("Skill '{}' updated (now v{}).", name, s.version)); }
Ok(format!("Skill '{}' updated.", name))
} else { Ok(format!("Skill '{}' not found.", name)) }
}),
)
}
fn delete_skill_tool(store: Option<Arc<std::sync::Mutex<crate::memory::store::MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[("name", param_string("The skill's name"))]);
ToolSpec::new("delete_skill", "Delete a skill by name", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("");
if guard.delete_skill(name).map_err(|e| format!("Failed to delete: {}", e))? {
Ok(format!("Skill '{}' deleted.", name))
} else { Ok(format!("Skill '{}' not found.", name)) }
}),
)
}
fn list_skills_tool(store: Option<Arc<std::sync::Mutex<crate::memory::store::MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[("category", serde_json::json!({"type": "string", "description": "Optional category filter", "default": ""}))]);
ToolSpec::new("list_skills", "List all available skills, optionally filtered by category", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let category = args.get("category").and_then(|v| v.as_str()).filter(|s| !s.is_empty());
let results = guard.list_skills(category).map_err(|e| format!("Failed to list skills: {}", e))?;
if results.is_empty() {
let suffix = category.map(|c| format!(" in category \"{}\"", c)).unwrap_or_default();
return Ok(format!("No skills found{}.", suffix));
}
let suffix = category.map(|c| format!(" [{}]", c)).unwrap_or_default();
let mut lines = vec![format!("Skills{}:", suffix)];
for s in &results {
let desc_short = if s.description.len() > 100 { format!("{}...", &s.description[..100]) } else { s.description.clone() };
lines.push(format!(" 📋 {} (v{}) [{}]", s.name, s.version, s.category));
lines.push(format!(" {}", desc_short));
}
Ok(lines.join("\n"))
}),
)
}