//! 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<String>,
}
fn default_api_url() -> String {
"https://api.makima.jp".to_string()
}
impl CliConfig {
/// Get the config directory path (~/.makima)
pub fn config_dir() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".makima"))
}
/// Get the config file path (~/.makima/config.toml)
pub fn config_path() -> Option<PathBuf> {
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<dyn std::error::Error + Send + Sync>> {
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<String> {
// 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()
}
}
}