//! CLI configuration management. //! //! Handles loading and saving CLI configuration from ~/.makima/config.toml. //! This is separate from daemon configuration and is used for interactive CLI commands. use clap::Args; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; /// Arguments for setting the API key #[derive(Args, Debug, Clone)] pub struct SetKeyArgs { /// The API key to save pub api_key: String, } /// Arguments for setting the API URL #[derive(Args, Debug, Clone)] pub struct SetUrlArgs { /// The API URL to save pub api_url: String, } /// CLI configuration stored in ~/.makima/config.toml #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct CliConfig { /// API URL for the makima server #[serde(default = "default_api_url")] pub api_url: String, /// API key for authentication #[serde(default)] pub api_key: Option, } fn default_api_url() -> String { "https://api.makima.jp".to_string() } impl CliConfig { /// Get the config directory path (~/.makima) pub fn config_dir() -> Option { dirs::home_dir().map(|h| h.join(".makima")) } /// Get the config file path (~/.makima/config.toml) pub fn config_path() -> Option { Self::config_dir().map(|d| d.join("config.toml")) } /// Load CLI config from ~/.makima/config.toml /// Returns default config if file doesn't exist pub fn load() -> Self { let Some(path) = Self::config_path() else { return Self::default(); }; if !path.exists() { return Self::default(); } match fs::read_to_string(&path) { Ok(contents) => { toml::from_str(&contents).unwrap_or_else(|e| { eprintln!("Warning: Failed to parse {}: {}", path.display(), e); Self::default() }) } Err(e) => { eprintln!("Warning: Failed to read {}: {}", path.display(), e); Self::default() } } } /// Save CLI config to ~/.makima/config.toml pub fn save(&self) -> Result<(), Box> { let Some(dir) = Self::config_dir() else { return Err("Could not determine home directory".into()); }; let Some(path) = Self::config_path() else { return Err("Could not determine config path".into()); }; // Create config directory if it doesn't exist if !dir.exists() { fs::create_dir_all(&dir)?; } let contents = toml::to_string_pretty(self) .map_err(|e| format!("Failed to serialize config: {}", e))?; fs::write(&path, contents)?; Ok(()) } /// Get API key, preferring environment variable over config file pub fn get_api_key(&self) -> Option { // Environment variable takes precedence if let Ok(key) = std::env::var("MAKIMA_API_KEY") { if !key.is_empty() { return Some(key); } } // Fall back to config file self.api_key.clone() } /// Get API URL, preferring environment variable over config file pub fn get_api_url(&self) -> String { // Environment variable takes precedence if let Ok(url) = std::env::var("MAKIMA_API_URL") { if !url.is_empty() { return url; } } // Fall back to config file, or default if empty if self.api_url.is_empty() { default_api_url() } else { self.api_url.clone() } } }