Skip to main content

seher/config/
mod.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5#[derive(Debug, Deserialize, Clone)]
6pub struct Settings {
7    pub agents: Vec<AgentConfig>,
8}
9
10#[derive(Debug, Deserialize, Clone)]
11pub struct AgentConfig {
12    pub command: String,
13    #[serde(default)]
14    pub args: Vec<String>,
15    #[serde(default)]
16    pub models: Option<HashMap<String, String>>,
17}
18
19impl Default for Settings {
20    fn default() -> Self {
21        Self {
22            agents: vec![AgentConfig {
23                command: "claude".to_string(),
24                args: vec![],
25                models: None,
26            }],
27        }
28    }
29}
30
31impl Settings {
32    pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
33        let path = Self::settings_path()?;
34        if !path.exists() {
35            return Ok(Settings::default());
36        }
37        let content = std::fs::read_to_string(&path)?;
38        let settings: Settings = serde_json::from_str(&content)?;
39        Ok(settings)
40    }
41
42    fn settings_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
43        let home = dirs::home_dir().ok_or("HOME directory not found")?;
44        Ok(home.join(".seher").join("settings.json"))
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    fn sample_settings_path() -> PathBuf {
53        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
54            .join("examples")
55            .join("settings.json")
56    }
57
58    #[test]
59    fn test_parse_sample_settings() {
60        let content = std::fs::read_to_string(sample_settings_path())
61            .expect("examples/settings.json not found");
62        let settings: Settings = serde_json::from_str(&content).expect("failed to parse settings");
63
64        assert_eq!(settings.agents.len(), 2);
65    }
66
67    #[test]
68    fn test_sample_settings_claude_agent() {
69        let content = std::fs::read_to_string(sample_settings_path()).unwrap();
70        let settings: Settings = serde_json::from_str(&content).unwrap();
71
72        let claude = &settings.agents[0];
73        assert_eq!(claude.command, "claude");
74        assert_eq!(
75            claude.args,
76            [
77                "--permission-mode",
78                "bypassPermissions",
79                "--model",
80                "{model}"
81            ]
82        );
83
84        let models = claude.models.as_ref().expect("models should be present");
85        assert_eq!(models.get("high").map(String::as_str), Some("opus"));
86        assert_eq!(models.get("low").map(String::as_str), Some("sonnet"));
87    }
88
89    #[test]
90    fn test_sample_settings_copilot_agent() {
91        let content = std::fs::read_to_string(sample_settings_path()).unwrap();
92        let settings: Settings = serde_json::from_str(&content).unwrap();
93
94        let copilot = &settings.agents[1];
95        assert_eq!(copilot.command, "copilot");
96        assert_eq!(copilot.args, ["--model", "{model}", "--yolo"]);
97
98        let models = copilot.models.as_ref().expect("models should be present");
99        assert_eq!(
100            models.get("high").map(String::as_str),
101            Some("claude-opus-4.5")
102        );
103        assert_eq!(
104            models.get("low").map(String::as_str),
105            Some("claude-sonnet-4.5")
106        );
107    }
108
109    #[test]
110    fn test_parse_minimal_settings_without_models() {
111        let json = r#"{"agents": [{"command": "claude"}]}"#;
112        let settings: Settings =
113            serde_json::from_str(json).expect("failed to parse minimal settings");
114
115        assert_eq!(settings.agents.len(), 1);
116        assert_eq!(settings.agents[0].command, "claude");
117        assert!(settings.agents[0].args.is_empty());
118        assert!(settings.agents[0].models.is_none());
119    }
120
121    #[test]
122    fn test_parse_settings_with_args_no_models() {
123        let json = r#"{"agents": [{"command": "claude", "args": ["--permission-mode", "bypassPermissions"]}]}"#;
124        let settings: Settings = serde_json::from_str(json).unwrap();
125
126        assert_eq!(
127            settings.agents[0].args,
128            ["--permission-mode", "bypassPermissions"]
129        );
130        assert!(settings.agents[0].models.is_none());
131    }
132}