Skip to main content

steer_core/
preferences.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3use strum::Display;
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default, Display)]
6#[strum(serialize_all = "kebab-case")]
7pub enum EditingMode {
8    #[default]
9    Simple, // Default - no modal editing
10    Vim, // Full vim keybindings
11}
12
13#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, Display)]
14#[serde(rename_all = "kebab-case")]
15#[strum(serialize_all = "kebab-case")]
16pub enum NotificationTransport {
17    #[default]
18    Auto,
19    Osc9,
20    Off,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, Default)]
24pub struct Preferences {
25    pub default_model: Option<String>,
26
27    #[serde(default)]
28    pub ui: UiPreferences,
29
30    #[serde(default)]
31    pub tools: ToolPreferences,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
35pub struct UiPreferences {
36    pub theme: Option<String>,
37    #[serde(default)]
38    pub notifications: NotificationPreferences,
39    pub history_limit: Option<usize>,
40    pub provider_priority: Option<Vec<String>>,
41    #[serde(default)]
42    pub editing_mode: EditingMode,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct NotificationPreferences {
47    #[serde(default)]
48    pub transport: NotificationTransport,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, Default)]
52pub struct ToolPreferences {
53    pub pre_approved: Vec<String>,
54}
55
56impl Default for NotificationPreferences {
57    fn default() -> Self {
58        Self {
59            transport: NotificationTransport::Auto,
60        }
61    }
62}
63
64impl Preferences {
65    /// Get the path to the preferences file
66    pub fn config_path() -> Result<PathBuf, crate::error::Error> {
67        let config_dir = dirs::config_dir().ok_or_else(|| {
68            crate::error::Error::Configuration("Could not determine config directory".to_string())
69        })?;
70        Ok(config_dir.join("steer").join("preferences.toml"))
71    }
72
73    /// Load preferences from disk, or return defaults if not found
74    pub fn load() -> Result<Self, crate::error::Error> {
75        let path = Self::config_path()?;
76
77        if path.exists() {
78            let contents = std::fs::read_to_string(&path)?;
79            match toml::from_str(&contents) {
80                Ok(prefs) => Ok(prefs),
81                Err(e) => {
82                    tracing::warn!(
83                        "Failed to parse preferences file at {:?}: {}. Using defaults.",
84                        path,
85                        e
86                    );
87                    Ok(Self::default())
88                }
89            }
90        } else {
91            Ok(Self::default())
92        }
93    }
94
95    /// Save preferences to disk
96    pub fn save(&self) -> Result<(), crate::error::Error> {
97        let path = Self::config_path()?;
98
99        // Create parent directory if it doesn't exist
100        if let Some(parent) = path.parent() {
101            std::fs::create_dir_all(parent)?;
102        }
103
104        let contents = toml::to_string_pretty(self).map_err(|e| {
105            crate::error::Error::Configuration(format!("Failed to serialize preferences: {e}"))
106        })?;
107
108        std::fs::write(&path, contents)?;
109
110        Ok(())
111    }
112}