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