use std::env;
use std::fs;
use std::path::PathBuf;
use serde::Deserialize;
use crate::Result;
use crate::cli::Mode;
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
opencode: PresetConfig,
#[serde(default)]
claude: PresetConfig,
#[serde(default)]
codex: PresetConfig,
}
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct PresetConfig {
#[serde(default)]
args: Vec<String>,
}
impl Config {
pub fn load() -> Result<Self> {
let Some(path) = config_path() else {
return Ok(Self::default());
};
let contents = match fs::read_to_string(&path) {
Ok(contents) => contents,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Ok(Self::default());
}
Err(err) => {
return Err(format!("failed to read config {}: {err}", path.display()).into());
}
};
toml::from_str(&contents)
.map_err(|err| format!("failed to parse config {}: {err}", path.display()).into())
}
pub fn preset_args(&self, mode: Mode) -> &[String] {
match mode {
Mode::Basic => &[],
Mode::Opencode => &self.opencode.args,
Mode::Claude => &self.claude.args,
Mode::Codex => &self.codex.args,
}
}
}
fn config_path() -> Option<PathBuf> {
let base = match env::var_os("XDG_CONFIG_HOME").filter(|v| !v.is_empty()) {
Some(dir) => PathBuf::from(dir),
None => PathBuf::from(env::var_os("HOME")?).join(".config"),
};
Some(base.join("agent-locker").join("config.toml"))
}