Skip to main content

parley/domain/
config.rs

1use serde::{Deserialize, Serialize};
2
3use crate::domain::ai::AiProvider;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(default)]
7pub struct AppConfig {
8    #[serde(alias = "name", default = "default_user_name")]
9    pub user_name: String,
10    pub theme: String,
11    pub diff_view: DiffViewMode,
12    #[serde(default = "default_log_level")]
13    pub log_level: String,
14    pub ai: AiConfig,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(rename_all = "snake_case")]
19pub enum DiffViewMode {
20    SideBySide,
21    Unified,
22}
23
24impl DiffViewMode {
25    pub fn is_side_by_side(&self) -> bool {
26        matches!(self, Self::SideBySide)
27    }
28}
29
30impl Default for DiffViewMode {
31    fn default() -> Self {
32        Self::SideBySide
33    }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(rename_all = "snake_case")]
38pub enum PromptTransport {
39    Stdin,
40    Argv,
41}
42
43impl Default for PromptTransport {
44    fn default() -> Self {
45        Self::Stdin
46    }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50#[serde(default)]
51pub struct AiProviderConfig {
52    #[serde(alias = "program")]
53    pub client: String,
54    pub model: Option<String>,
55    pub model_arg: Option<String>,
56    pub args: Vec<String>,
57    pub prompt_transport: PromptTransport,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61#[serde(default)]
62pub struct AiConfig {
63    pub timeout_seconds: u64,
64    pub default_provider: AiProvider,
65    pub codex: AiProviderConfig,
66    pub claude: AiProviderConfig,
67    pub opencode: AiProviderConfig,
68}
69
70pub fn default_user_name() -> String {
71    std::env::var("PARLEY_USER_NAME")
72        .ok()
73        .or_else(|| std::env::var("USER").ok())
74        .or_else(|| std::env::var("USERNAME").ok())
75        .filter(|value| !value.trim().is_empty())
76        .unwrap_or_else(|| "User".to_string())
77}
78
79pub fn default_log_level() -> String {
80    "info".to_string()
81}
82
83impl Default for AppConfig {
84    fn default() -> Self {
85        Self {
86            user_name: default_user_name(),
87            theme: "default".to_string(),
88            diff_view: DiffViewMode::default(),
89            log_level: default_log_level(),
90            ai: AiConfig::default(),
91        }
92    }
93}
94
95impl Default for AiProviderConfig {
96    fn default() -> Self {
97        Self {
98            client: String::new(),
99            model: None,
100            model_arg: Some("--model".to_string()),
101            args: Vec::new(),
102            prompt_transport: PromptTransport::Stdin,
103        }
104    }
105}
106
107impl AiProviderConfig {
108    pub fn with_client(client: &str) -> Self {
109        Self {
110            client: client.to_string(),
111            model: None,
112            ..Self::default()
113        }
114    }
115}
116
117impl Default for AiConfig {
118    fn default() -> Self {
119        let mut codex = AiProviderConfig::with_client("codex");
120        codex.args = vec!["exec".to_string()];
121        codex.prompt_transport = PromptTransport::Argv;
122
123        let mut claude = AiProviderConfig::with_client("claude");
124        claude.args = vec!["-p".to_string()];
125        claude.prompt_transport = PromptTransport::Argv;
126
127        let mut opencode = AiProviderConfig::with_client("opencode");
128        opencode.args = vec!["run".to_string()];
129        opencode.model_arg = Some("-m".to_string());
130        opencode.prompt_transport = PromptTransport::Argv;
131        Self {
132            timeout_seconds: 120,
133            default_provider: AiProvider::Opencode,
134            codex,
135            claude,
136            opencode,
137        }
138    }
139}
140
141impl AiConfig {
142    pub fn provider_config(&self, provider: AiProvider) -> &AiProviderConfig {
143        match provider {
144            AiProvider::Codex => &self.codex,
145            AiProvider::Claude => &self.claude,
146            AiProvider::Opencode => &self.opencode,
147        }
148    }
149}