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, Serialize, Deserialize, Default)]
14pub struct Preferences {
15    #[serde(default)]
16    pub ui: UiPreferences,
17
18    #[serde(default)]
19    pub tools: ToolPreferences,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, Default)]
23pub struct UiPreferences {
24    pub default_model: Option<String>,
25    pub theme: Option<String>,
26    pub notifications: NotificationPreferences,
27    pub history_limit: Option<usize>,
28    pub provider_priority: Option<Vec<String>>,
29    #[serde(default)]
30    pub editing_mode: EditingMode,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct NotificationPreferences {
35    pub sound: bool,
36    pub desktop: bool,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, Default)]
40pub struct ToolPreferences {
41    pub pre_approved: Vec<String>,
42}
43
44impl Default for NotificationPreferences {
45    fn default() -> Self {
46        Self {
47            sound: true,
48            desktop: true,
49        }
50    }
51}
52
53impl Preferences {
54    /// Get the path to the preferences file
55    pub fn config_path() -> Result<PathBuf, crate::error::Error> {
56        let config_dir = dirs::config_dir().ok_or_else(|| {
57            crate::error::Error::Configuration("Could not determine config directory".to_string())
58        })?;
59        Ok(config_dir.join("steer").join("preferences.toml"))
60    }
61
62    /// Load preferences from disk, or return defaults if not found
63    pub fn load() -> Result<Self, crate::error::Error> {
64        let path = Self::config_path()?;
65
66        if path.exists() {
67            let contents = std::fs::read_to_string(&path)?;
68            match toml::from_str(&contents) {
69                Ok(prefs) => Ok(prefs),
70                Err(e) => {
71                    tracing::warn!(
72                        "Failed to parse preferences file at {:?}: {}. Using defaults.",
73                        path,
74                        e
75                    );
76                    Ok(Self::default())
77                }
78            }
79        } else {
80            Ok(Self::default())
81        }
82    }
83
84    /// Save preferences to disk
85    pub fn save(&self) -> Result<(), crate::error::Error> {
86        let path = Self::config_path()?;
87
88        // Create parent directory if it doesn't exist
89        if let Some(parent) = path.parent() {
90            std::fs::create_dir_all(parent)?;
91        }
92
93        let contents = toml::to_string_pretty(self).map_err(|e| {
94            crate::error::Error::Configuration(format!("Failed to serialize preferences: {e}"))
95        })?;
96
97        std::fs::write(&path, contents)?;
98
99        Ok(())
100    }
101}