use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use crate::spec::AgentName;
use super::frontmatter::{FrontMatter, join_frontmatter};
use super::permission::Permission;
use super::{Agent, Skill};
pub trait FrontMatterWriter {
fn format_permission(&self, perm: &Permission) -> Option<String>;
fn format_frontmatter(&self, fm: &FrontMatter) -> String;
}
pub trait SkillsWriter {
fn name(&self) -> AgentName;
fn write_skill(&self, root: &Path, skill: &Skill) -> anyhow::Result<PathBuf>;
}
pub trait AgentsWriter {
fn name(&self) -> AgentName;
fn write_agent(&self, root: &Path, agent: &Agent) -> anyhow::Result<PathBuf>;
}
pub fn format_standard_frontmatter(fm: &FrontMatter, writer: &dyn FrontMatterWriter) -> String {
let mut map: BTreeMap<String, serde_yml::Value> = BTreeMap::new();
if let Some(name) = &fm.name {
map.insert("name".into(), serde_yml::Value::String(name.clone()));
}
if let Some(desc) = &fm.description {
map.insert("description".into(), serde_yml::Value::String(desc.clone()));
}
let tokens: Vec<String> = fm
.allowed_tools
.iter()
.filter_map(|p| writer.format_permission(p))
.collect();
if !tokens.is_empty() {
map.insert(
"allowed-tools".into(),
serde_yml::Value::String(tokens.join(" ")),
);
}
for (k, v) in &fm.extra {
map.insert(k.clone(), v.clone());
}
if map.is_empty() {
return String::new();
}
serde_yml::to_string(&map).unwrap_or_default()
}
pub fn write_standard_skill(
dir: &Path,
skill: &Skill,
writer: &dyn FrontMatterWriter,
) -> anyhow::Result<PathBuf> {
let skill_dir = dir.join(&skill.name);
fs::create_dir_all(&skill_dir)?;
let file = skill_dir.join("SKILL.md");
let yaml = writer.format_frontmatter(&skill.front_matter);
let content = if yaml.is_empty() {
skill.body.clone()
} else {
join_frontmatter(&yaml, &skill.body)
};
fs::write(&file, &content)?;
Ok(file)
}
pub fn write_standard_agent(
dir: &Path,
agent: &Agent,
writer: &dyn FrontMatterWriter,
) -> anyhow::Result<PathBuf> {
fs::create_dir_all(dir)?;
let file = dir.join(format!("{}.agent.md", agent.name));
let yaml = writer.format_frontmatter(&agent.front_matter);
let content = if yaml.is_empty() {
agent.body.clone()
} else {
join_frontmatter(&yaml, &agent.body)
};
fs::write(&file, &content)?;
Ok(file)
}