pub mod loader;
pub mod parser;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Skill {
pub name: String,
pub description: String,
pub system_prompt: String,
pub tools: Vec<String>,
pub allowed_tools: Option<Vec<String>>,
pub denied_tools: Option<Vec<String>>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
impl Skill {
#[must_use]
pub fn new(name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
system_prompt: system_prompt.into(),
tools: Vec::new(),
allowed_tools: None,
denied_tools: None,
metadata: HashMap::new(),
}
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
#[must_use]
pub fn with_tools(mut self, tools: Vec<String>) -> Self {
self.tools = tools;
self
}
#[must_use]
pub fn with_allowed_tools(mut self, tools: Vec<String>) -> Self {
self.allowed_tools = Some(tools);
self
}
#[must_use]
pub fn with_denied_tools(mut self, tools: Vec<String>) -> Self {
self.denied_tools = Some(tools);
self
}
#[must_use]
pub fn is_tool_allowed(&self, tool_name: &str) -> bool {
if let Some(ref denied) = self.denied_tools
&& denied.iter().any(|t| t == tool_name)
{
return false;
}
if let Some(ref allowed) = self.allowed_tools {
return allowed.iter().any(|t| t == tool_name);
}
true
}
}
pub use loader::{FileSkillLoader, SkillLoader};
pub use parser::parse_skill_file;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skill_builder() {
let skill = Skill::new("test", "You are a test assistant.")
.with_description("A test skill")
.with_tools(vec!["read".into(), "write".into()])
.with_denied_tools(vec!["bash".into()]);
assert_eq!(skill.name, "test");
assert_eq!(skill.description, "A test skill");
assert_eq!(skill.system_prompt, "You are a test assistant.");
assert_eq!(skill.tools, vec!["read", "write"]);
assert_eq!(skill.denied_tools, Some(vec!["bash".into()]));
}
#[test]
fn test_is_tool_allowed_no_restrictions() {
let skill = Skill::new("test", "prompt");
assert!(skill.is_tool_allowed("read"));
assert!(skill.is_tool_allowed("write"));
assert!(skill.is_tool_allowed("bash"));
}
#[test]
fn test_is_tool_allowed_with_denied() {
let skill = Skill::new("test", "prompt").with_denied_tools(vec!["bash".into()]);
assert!(skill.is_tool_allowed("read"));
assert!(skill.is_tool_allowed("write"));
assert!(!skill.is_tool_allowed("bash"));
}
#[test]
fn test_is_tool_allowed_with_whitelist() {
let skill =
Skill::new("test", "prompt").with_allowed_tools(vec!["read".into(), "grep".into()]);
assert!(skill.is_tool_allowed("read"));
assert!(skill.is_tool_allowed("grep"));
assert!(!skill.is_tool_allowed("write"));
assert!(!skill.is_tool_allowed("bash"));
}
#[test]
fn test_is_tool_allowed_denied_takes_precedence() {
let skill = Skill::new("test", "prompt")
.with_allowed_tools(vec!["read".into(), "bash".into()])
.with_denied_tools(vec!["bash".into()]);
assert!(skill.is_tool_allowed("read"));
assert!(!skill.is_tool_allowed("bash")); }
}