rusty_commit/config/
accounts.rs1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(tag = "type")]
10pub enum AuthMethod {
11 #[serde(rename = "api_key")]
12 ApiKey { key_id: String },
13 #[serde(rename = "oauth")]
14 OAuth {
15 provider: String,
16 account_id: String,
17 },
18 #[serde(rename = "env_var")]
19 EnvVar { name: String },
20 #[serde(rename = "bearer")]
21 Bearer { token_id: String },
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct AccountConfig {
27 pub alias: String,
28 pub provider: String,
29 pub api_url: Option<String>,
30 pub model: Option<String>,
31 pub auth: AuthMethod,
32 pub tokens_max_input: Option<usize>,
33 pub tokens_max_output: Option<u32>,
34 #[serde(default)]
35 pub is_default: bool,
36}
37
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
40pub struct AccountsConfig {
41 pub active_account: Option<String>,
42 pub accounts: HashMap<String, AccountConfig>,
43}
44
45#[allow(dead_code)]
46impl AccountsConfig {
47 fn accounts_file_path() -> Result<PathBuf> {
49 let config_dir = if let Ok(config_home) = std::env::var("RCO_CONFIG_HOME") {
50 PathBuf::from(config_home)
51 } else {
52 let home = dirs::home_dir().context("Could not find home directory")?;
53 home.join(".config").join("rustycommit")
54 };
55
56 if !config_dir.exists() {
58 fs::create_dir_all(&config_dir).context("Failed to create config directory")?;
59 }
60
61 Ok(config_dir.join("accounts.toml"))
62 }
63
64 pub fn load() -> Result<Option<Self>> {
66 let path = Self::accounts_file_path()?;
67
68 if !path.exists() {
69 return Ok(None);
70 }
71
72 let contents = fs::read_to_string(&path).context("Failed to read accounts file")?;
73
74 let config: AccountsConfig =
75 toml::from_str(&contents).context("Failed to parse accounts file")?;
76
77 Ok(Some(config))
78 }
79
80 pub fn save(&self) -> Result<()> {
82 let path = Self::accounts_file_path()?;
83
84 let toml_content = toml::to_string_pretty(self).context("Failed to serialize accounts")?;
85
86 fs::write(&path, toml_content).context("Failed to write accounts file")?;
87
88 #[cfg(unix)]
90 {
91 use std::os::unix::fs::PermissionsExt;
92 let mut perms = fs::metadata(&path)?.permissions();
93 perms.set_mode(0o600);
94 fs::set_permissions(&path, perms).context("Failed to set accounts file permissions")?;
95 }
96
97 Ok(())
98 }
99
100 pub fn get_account(&self, alias: &str) -> Option<&AccountConfig> {
102 self.accounts.get(alias)
103 }
104
105 pub fn get_account_mut(&mut self, alias: &str) -> Option<&mut AccountConfig> {
107 self.accounts.get_mut(alias)
108 }
109
110 pub fn add_account(&mut self, account: AccountConfig) {
112 self.accounts.insert(account.alias.clone(), account);
113 }
114
115 pub fn remove_account(&mut self, alias: &str) -> bool {
117 self.accounts.remove(alias).is_some()
118 }
119
120 pub fn list_accounts(&self) -> Vec<&AccountConfig> {
122 self.accounts.values().collect()
123 }
124
125 pub fn set_active_account(&mut self, alias: &str) -> Result<()> {
127 if !self.accounts.contains_key(alias) {
128 anyhow::bail!("Account '{}' not found", alias);
129 }
130 self.active_account = Some(alias.to_string());
131 Ok(())
132 }
133
134 pub fn get_active_account(&self) -> Option<&AccountConfig> {
136 if let Some(alias) = &self.active_account {
137 self.accounts.get(alias)
138 } else {
139 None
140 }
141 }
142
143 pub fn get_active_alias(&self) -> Option<&str> {
145 self.active_account.as_deref()
146 }
147
148 pub fn get_key_id(account_alias: &str, auth_type: &str) -> String {
150 format!("rco_{}_{}", account_alias, auth_type)
151 }
152}
153
154#[allow(dead_code)]
156pub fn account_storage_key(account_alias: &str, key_type: &str) -> String {
157 format!("rco_account_{}_{}", account_alias, key_type)
158}
159
160#[allow(dead_code)]
162pub fn delete_account_storage(_account_alias: &str) {
163 #[cfg(feature = "secure-storage")]
164 {
165 use crate::config::secure_storage;
166
167 for key_type in ["access_token", "refresh_token", "api_key", "bearer_token"] {
168 let key = account_storage_key(_account_alias, key_type);
169 let _ = secure_storage::delete_secret(&key);
170 }
171 }
172 }