cc_switch/
claude_settings.rs

1use anyhow::{Context, Result};
2use std::collections::BTreeMap;
3use std::fs;
4
5use crate::config::types::{ClaudeSettings, Configuration};
6use crate::utils::get_claude_settings_path;
7
8impl ClaudeSettings {
9    /// Load Claude settings from disk
10    ///
11    /// Reads the JSON file from the configured Claude settings directory
12    /// Returns default empty settings if file doesn't exist
13    /// Creates the file with default structure if it doesn't exist
14    ///
15    /// # Arguments
16    /// * `custom_dir` - Optional custom directory for Claude settings
17    ///
18    /// # Errors
19    /// Returns error if file exists but cannot be read or parsed
20    pub fn load(custom_dir: Option<&str>) -> Result<Self> {
21        let path = get_claude_settings_path(custom_dir)?;
22
23        if !path.exists() {
24            // Create default settings file if it doesn't exist
25            let default_settings = ClaudeSettings::default();
26            default_settings.save(custom_dir)?;
27            return Ok(default_settings);
28        }
29
30        let content = fs::read_to_string(&path)
31            .with_context(|| format!("Failed to read Claude settings from {}", path.display()))?;
32
33        // Parse with better error handling for missing env field
34        let mut settings: ClaudeSettings = if content.trim().is_empty() {
35            ClaudeSettings::default()
36        } else {
37            serde_json::from_str(&content)
38                .with_context(|| "Failed to parse Claude settings JSON")?
39        };
40
41        // Ensure env field exists (handle case where it might be missing from JSON)
42        if settings.env.is_empty() && !content.contains("\"env\"") {
43            settings.env = BTreeMap::new();
44        }
45
46        Ok(settings)
47    }
48
49    /// Save Claude settings to disk
50    ///
51    /// Writes the current state to the configured Claude settings directory
52    /// Creates the directory structure if it doesn't exist
53    /// Ensures the env field is properly serialized
54    ///
55    /// # Arguments
56    /// * `custom_dir` - Optional custom directory for Claude settings
57    ///
58    /// # Errors
59    /// Returns error if directory cannot be created or file cannot be written
60    pub fn save(&self, custom_dir: Option<&str>) -> Result<()> {
61        let path = get_claude_settings_path(custom_dir)?;
62
63        // Create directory if it doesn't exist
64        if let Some(parent) = path.parent() {
65            fs::create_dir_all(parent)
66                .with_context(|| format!("Failed to create directory {}", parent.display()))?;
67        }
68
69        // The custom Serialize implementation handles env field inclusion automatically
70        let settings_to_save = self;
71
72        let json = serde_json::to_string_pretty(&settings_to_save)
73            .with_context(|| "Failed to serialize Claude settings")?;
74
75        fs::write(&path, json).with_context(|| format!("Failed to write to {}", path.display()))?;
76
77        Ok(())
78    }
79
80    /// Switch to a specific API configuration
81    ///
82    /// Updates the environment variables with the provided configuration
83    /// Ensures env field exists before updating
84    ///
85    /// # Arguments
86    /// * `config` - Configuration containing token, URL, and optional model settings to apply
87    pub fn switch_to_config(&mut self, config: &Configuration) {
88        // Ensure env field exists
89        if self.env.is_empty() {
90            self.env = BTreeMap::new();
91        }
92
93        // First remove all Anthropic environment variables to ensure clean state
94        self.env.remove("ANTHROPIC_AUTH_TOKEN");
95        self.env.remove("ANTHROPIC_BASE_URL");
96        self.env.remove("ANTHROPIC_MODEL");
97        self.env.remove("ANTHROPIC_SMALL_FAST_MODEL");
98
99        // Set required environment variables
100        self.env
101            .insert("ANTHROPIC_AUTH_TOKEN".to_string(), config.token.clone());
102        self.env
103            .insert("ANTHROPIC_BASE_URL".to_string(), config.url.clone());
104
105        // Set model configurations only if provided (don't set empty values)
106        if let Some(model) = &config.model
107            && !model.is_empty()
108        {
109            self.env
110                .insert("ANTHROPIC_MODEL".to_string(), model.clone());
111        }
112
113        if let Some(small_fast_model) = &config.small_fast_model
114            && !small_fast_model.is_empty()
115        {
116            self.env.insert(
117                "ANTHROPIC_SMALL_FAST_MODEL".to_string(),
118                small_fast_model.clone(),
119            );
120        }
121    }
122
123    /// Remove Anthropic environment variables
124    ///
125    /// Clears all Anthropic-related environment variables from settings
126    /// Used to reset to default Claude behavior
127    pub fn remove_anthropic_env(&mut self) {
128        // Ensure env field exists
129        if self.env.is_empty() {
130            self.env = BTreeMap::new();
131        }
132
133        self.env.remove("ANTHROPIC_AUTH_TOKEN");
134        self.env.remove("ANTHROPIC_BASE_URL");
135        self.env.remove("ANTHROPIC_MODEL");
136        self.env.remove("ANTHROPIC_SMALL_FAST_MODEL");
137    }
138}