feat: added providers management settings with ability to add or remove providers (#76)

* feat: implement adding opencode providers in openchamber settings

* feat: implement provider authentication management with removal functionality
This commit is contained in:
Bohdan Triapitsyn
2025-12-27 02:22:59 +02:00
committed by GitHub
parent ff874cc33d
commit 8f9facb561
14 changed files with 1469 additions and 10 deletions
@@ -0,0 +1,116 @@
use anyhow::{anyhow, Result};
use log::info;
use serde_json::Value;
use std::path::PathBuf;
use tokio::fs;
/// Get OpenCode data directory path (~/.local/share/opencode)
fn get_data_dir() -> PathBuf {
dirs::home_dir()
.expect("Cannot determine home directory")
.join(".local")
.join("share")
.join("opencode")
}
/// Get auth file path
fn get_auth_file() -> PathBuf {
get_data_dir().join("auth.json")
}
/// Ensure data directory exists
async fn ensure_data_dir() -> Result<()> {
let data_dir = get_data_dir();
fs::create_dir_all(&data_dir).await?;
Ok(())
}
/// Read auth.json file
pub async fn read_auth() -> Result<Value> {
let auth_file = get_auth_file();
if !auth_file.exists() {
return Ok(Value::Object(serde_json::Map::new()));
}
let content = fs::read_to_string(&auth_file).await?;
let trimmed = content.trim();
if trimmed.is_empty() {
return Ok(Value::Object(serde_json::Map::new()));
}
serde_json::from_str(trimmed).map_err(|e| anyhow!("Failed to parse auth file: {}", e))
}
/// Write auth.json file with backup
pub async fn write_auth(auth: &Value) -> Result<()> {
ensure_data_dir().await?;
let auth_file = get_auth_file();
// Create backup before writing
if auth_file.exists() {
let file_name = auth_file
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| anyhow!("Invalid auth file name"))?;
let backup_path = auth_file.with_file_name(format!("{file_name}.openchamber.backup"));
fs::copy(&auth_file, &backup_path).await?;
info!("Created auth backup: {}", backup_path.display());
}
let json_string = serde_json::to_string_pretty(auth)?;
fs::write(&auth_file, json_string).await?;
info!("Successfully wrote auth file");
Ok(())
}
/// Remove provider auth entry from auth.json
pub async fn remove_provider_auth(provider_id: &str) -> Result<bool> {
if provider_id.is_empty() {
return Err(anyhow!("Provider ID is required"));
}
let mut auth = read_auth().await?;
let auth_obj = auth
.as_object_mut()
.ok_or_else(|| anyhow!("Auth file is not a valid JSON object"))?;
if !auth_obj.contains_key(provider_id) {
info!(
"Provider {} not found in auth file, nothing to remove",
provider_id
);
return Ok(false);
}
auth_obj.remove(provider_id);
write_auth(&auth).await?;
info!("Removed provider auth: {}", provider_id);
Ok(true)
}
/// Get provider auth entry
pub async fn get_provider_auth(provider_id: &str) -> Result<Option<Value>> {
let auth = read_auth().await?;
Ok(auth
.as_object()
.and_then(|obj| obj.get(provider_id))
.cloned())
}
/// List all provider IDs with auth
pub async fn list_provider_auths() -> Result<Vec<String>> {
let auth = read_auth().await?;
Ok(auth
.as_object()
.map(|obj| obj.keys().cloned().collect())
.unwrap_or_default())
}