//! Local configuration (~/.config/codecraft/config.toml, readable only by the user). use anyhow::{Context, Result}; use codecraft_core::config::ProviderConfig; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerLogin { pub url: String, pub token: String, #[serde(default)] pub username: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Config { /// Signed-in CodeCraft server (models, modes and rules come from there). #[serde(default, skip_serializing_if = "Option::is_none")] pub server: Option, /// Model used for new sessions ("provider/model", server models as "server:provider/model"). #[serde(default, skip_serializing_if = "Option::is_none")] pub default_model: Option, /// Permission mode for new sessions (default: auto). #[serde(default, skip_serializing_if = "Option::is_none")] pub permission_mode: Option, /// UI language (en, de); default from the server or $LANG. #[serde(default, skip_serializing_if = "Option::is_none")] pub lang: Option, /// Theme override, e.g. "nord" and "dark" (default: your server theme). #[serde(default, skip_serializing_if = "Option::is_none")] pub theme: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub theme_mode: Option, /// Own model providers (standalone mode), same format as the server config. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub providers: BTreeMap, } pub fn config_dir() -> PathBuf { if let Some(x) = std::env::var_os("CODECRAFT_CONFIG_DIR") { return PathBuf::from(x); } #[cfg(windows)] if let Some(x) = std::env::var_os("APPDATA") { return PathBuf::from(x).join("codecraft"); } let base = std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from).unwrap_or_else(|| home().join(".config")); base.join("codecraft") } pub fn data_dir() -> PathBuf { if let Some(x) = std::env::var_os("CODECRAFT_DATA_DIR") { return PathBuf::from(x); } #[cfg(windows)] if let Some(x) = std::env::var_os("LOCALAPPDATA") { return PathBuf::from(x).join("codecraft"); } let base = std::env::var_os("XDG_DATA_HOME").map(PathBuf::from).unwrap_or_else(|| home().join(".local").join("share")); base.join("codecraft") } pub fn home() -> PathBuf { std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")).map(PathBuf::from).unwrap_or_else(|| PathBuf::from(".")) } fn path() -> PathBuf { config_dir().join("config.toml") } impl Config { pub fn load() -> Result { let p = path(); match std::fs::read_to_string(&p) { Ok(text) => toml::from_str(&text).with_context(|| format!("invalid {}", p.display())), Err(_) => Ok(Self::default()), } } pub fn save(&self) -> Result<()> { let p = path(); std::fs::create_dir_all(p.parent().unwrap())?; let text = format!( "# CodeCraft terminal client – edit with `codecraft setup` or by hand.\n# API keys may be written as \"${{ENV_VAR}}\".\n\n{}", toml::to_string_pretty(self)? ); std::fs::write(&p, text)?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o600))?; } Ok(()) } /// Nothing set up yet: the setup wizard runs first. pub fn is_empty(&self) -> bool { self.server.is_none() && self.providers.is_empty() } }