use serde::Deserialize;
use std::path::Path;
use crate::paths::{agent_toml_path, agents_dir};
#[path = "claude_code/setup.rs"]
mod claude_code;
#[path = "codex/setup.rs"]
mod codex;
#[path = "hermes/setup.rs"]
mod hermes;
#[derive(Debug, Clone, Deserialize)]
pub struct AgentCommand {
pub command: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub setup: Option<String>,
}
pub fn load_agent_command(name: &str) -> Option<AgentCommand> {
let text = std::fs::read_to_string(agent_toml_path(name)).ok()?;
toml::from_str(&text).ok()
}
const DEFAULT_AGENT_CONFIGS: &[(&str, &str)] = &[
(claude_code::NAME, claude_code::CONFIG),
(codex::NAME, codex::CONFIG),
(hermes::NAME, hermes::CONFIG),
];
fn builtin_agent_names() -> Vec<String> {
DEFAULT_AGENT_CONFIGS
.iter()
.map(|(n, _)| n.to_string())
.collect()
}
pub fn available_agents() -> Vec<String> {
available_agents_in(&agents_dir())
}
pub(crate) fn available_agents_in(dir: &Path) -> Vec<String> {
let Ok(entries) = std::fs::read_dir(dir) else {
return builtin_agent_names();
};
let mut names: Vec<String> = entries
.filter_map(|entry| entry.ok())
.filter_map(|entry| {
let path = entry.path();
(path.extension()? == "toml")
.then(|| path.file_stem()?.to_str().map(str::to_string))
.flatten()
})
.collect();
if names.is_empty() {
return builtin_agent_names();
}
names.sort();
names
}
pub fn ensure_default_agents() {
ensure_default_agents_in(&agents_dir());
}
pub(crate) fn ensure_default_agents_in(dir: &Path) {
if let Err(err) = std::fs::create_dir_all(dir) {
log::warn!("ensure_default_agents: failed to create {dir:?}: {err}");
return;
}
for (name, contents) in DEFAULT_AGENT_CONFIGS {
let path = dir.join(format!("{name}.toml"));
if path.exists() {
continue;
}
if let Err(err) = std::fs::write(&path, contents) {
log::warn!("ensure_default_agents: failed to write {path:?}: {err}");
}
}
}
#[cfg(test)]
#[path = "agents_tests.rs"]
mod agents_tests;