aether_cli/
settings_args.rs1use aether_project::{AetherSettings, AetherSettingsSource, AgentCatalog, SettingsError, SettingsFileSource};
2use std::path::{Path, PathBuf};
3use thiserror::Error;
4
5#[derive(Clone, Debug, Default, clap::Args)]
6pub struct SettingsSourceArgs {
7 #[arg(long = "settings-json", conflicts_with = "settings_file")]
8 pub settings_json: Option<String>,
9
10 #[arg(long = "settings-file", conflicts_with = "settings_json")]
11 pub settings_file: Option<PathBuf>,
12}
13
14#[derive(Debug, Error)]
16#[error("settings and settingsFile cannot both be supplied")]
17pub struct ConflictingSettingsSources;
18
19impl SettingsSourceArgs {
20 pub fn from_json_options(
21 settings: Option<AetherSettings>,
22 settings_file: Option<PathBuf>,
23 ) -> Result<Self, ConflictingSettingsSources> {
24 if settings.is_some() && settings_file.is_some() {
25 return Err(ConflictingSettingsSources);
26 }
27 Ok(Self {
28 settings_json: settings.map(|settings| serde_json::to_string(&settings).expect("settings serialize")),
29 settings_file,
30 })
31 }
32
33 pub fn source(&self, root: &Path) -> Option<AetherSettingsSource> {
34 if let Some(json) = &self.settings_json {
35 Some(AetherSettingsSource::Json(json.clone()))
36 } else {
37 self.settings_file
38 .as_ref()
39 .map(|path| AetherSettingsSource::File(SettingsFileSource::new(path.clone(), root)))
40 }
41 }
42
43 pub fn load_settings(&self, cwd: &Path) -> Result<AetherSettings, SettingsError> {
44 match self.source(cwd) {
45 Some(source) => AetherSettings::load(cwd, [source]),
46 None => AetherSettings::load_default(cwd),
47 }
48 }
49
50 pub fn load_agent_catalog(&self, cwd: &Path) -> Result<AgentCatalog, SettingsError> {
51 let settings = self.load_settings(cwd)?;
52 if settings.agents.is_empty() {
53 Ok(AgentCatalog::empty(cwd.to_path_buf()))
54 } else {
55 AgentCatalog::from_settings(cwd, settings)
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn settings_json_maps_to_json_source() {
66 let args = SettingsSourceArgs { settings_json: Some("{\"agents\":[]}".to_string()), settings_file: None };
67
68 let Some(AetherSettingsSource::Json(json)) = args.source(Path::new(".")) else {
69 panic!("expected JSON settings source");
70 };
71 assert_eq!(json, "{\"agents\":[]}");
72 }
73
74 #[test]
75 fn from_json_options_serializes_inline_settings() {
76 let args = SettingsSourceArgs::from_json_options(Some(AetherSettings::default()), None).unwrap();
77
78 assert!(args.settings_json.is_some());
79 assert!(args.settings_file.is_none());
80 }
81
82 #[test]
83 fn from_json_options_rejects_both_settings_and_file() {
84 let error = SettingsSourceArgs::from_json_options(
85 Some(AetherSettings::default()),
86 Some(PathBuf::from("settings.json")),
87 );
88
89 assert!(matches!(error, Err(ConflictingSettingsSources)));
90 }
91
92 #[test]
93 fn settings_file_maps_to_file_source() {
94 let args = SettingsSourceArgs { settings_json: None, settings_file: Some(PathBuf::from("settings.json")) };
95
96 let Some(AetherSettingsSource::File(source)) = args.source(Path::new("/workspace")) else {
97 panic!("expected file settings source");
98 };
99 assert_eq!(source, SettingsFileSource::new("settings.json", "/workspace"));
100 }
101}