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;
#[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),
];
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(|e| e.ok())
.filter_map(|e| {
let path = e.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(e) = std::fs::create_dir_all(dir) {
log::warn!("ensure_default_agents: failed to create {dir:?}: {e}");
return;
}
for (name, contents) in DEFAULT_AGENT_CONFIGS {
let path = dir.join(format!("{name}.toml"));
if path.exists() {
continue;
}
if let Err(e) = std::fs::write(&path, contents) {
log::warn!("ensure_default_agents: failed to write {path:?}: {e}");
}
}
}