use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::embedding::EmbeddingMode;
use crate::Result;
pub fn config_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".totalreclaw")
}
pub fn credentials_path() -> PathBuf {
config_dir().join("credentials.json")
}
pub fn embedding_config_path() -> PathBuf {
config_dir().join("embedding-config.json")
}
#[derive(Serialize, Deserialize)]
pub struct Credentials {
pub recovery_phrase: String,
}
pub fn load_credentials() -> Result<Option<Credentials>> {
let path = credentials_path();
if !path.exists() {
return Ok(None);
}
let data = std::fs::read_to_string(&path)?;
let creds: Credentials =
serde_json::from_str(&data).map_err(|e| crate::Error::Crypto(e.to_string()))?;
Ok(Some(creds))
}
pub fn save_credentials(creds: &Credentials) -> Result<()> {
let dir = config_dir();
std::fs::create_dir_all(&dir)?;
let path = credentials_path();
let data = serde_json::to_string_pretty(creds)
.map_err(|e| crate::Error::Crypto(e.to_string()))?;
std::fs::write(&path, data)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
}
Ok(())
}
#[derive(Serialize, Deserialize)]
pub struct EmbeddingConfig {
pub mode: String,
pub model: Option<String>,
pub dimensions: usize,
pub ollama_url: Option<String>,
pub provider_url: Option<String>,
pub api_key_env: Option<String>,
}
pub fn load_embedding_config() -> Result<Option<EmbeddingConfig>> {
let path = embedding_config_path();
if !path.exists() {
return Ok(None);
}
let data = std::fs::read_to_string(&path)?;
let config: EmbeddingConfig =
serde_json::from_str(&data).map_err(|e| crate::Error::Embedding(e.to_string()))?;
Ok(Some(config))
}
pub fn save_embedding_config(config: &EmbeddingConfig) -> Result<()> {
let dir = config_dir();
std::fs::create_dir_all(&dir)?;
let path = embedding_config_path();
let data = serde_json::to_string_pretty(config)
.map_err(|e| crate::Error::Embedding(e.to_string()))?;
std::fs::write(&path, data)?;
Ok(())
}
pub fn config_to_mode(config: &EmbeddingConfig) -> EmbeddingMode {
match config.mode.as_str() {
"local" => EmbeddingMode::Local {
model_path: config
.model
.clone()
.unwrap_or_else(|| "onnx-community/harrier-oss-v1-270m-ONNX".into()),
},
"ollama" => EmbeddingMode::Ollama {
base_url: config
.ollama_url
.clone()
.unwrap_or_else(|| "http://localhost:11434".into()),
model: config
.model
.clone()
.unwrap_or_else(|| "nomic-embed-text".into()),
},
"zeroclaw" => EmbeddingMode::ZeroClaw {
base_url: config
.provider_url
.clone()
.unwrap_or_default(),
api_key: std::env::var(
config
.api_key_env
.as_deref()
.unwrap_or("ZEROCLAW_EMBEDDING_API_KEY"),
)
.unwrap_or_default(),
},
"llm" | _ => EmbeddingMode::LlmProvider {
base_url: config
.provider_url
.clone()
.unwrap_or_else(|| "https://api.openai.com".into()),
api_key: std::env::var(
config
.api_key_env
.as_deref()
.unwrap_or("OPENAI_API_KEY"),
)
.unwrap_or_default(),
model: config
.model
.clone()
.unwrap_or_else(|| "text-embedding-3-small".into()),
},
}
}
pub fn generate_mnemonic() -> String {
use rand::RngCore;
let mut entropy = [0u8; 16];
rand::thread_rng().fill_bytes(&mut entropy);
let mnemonic = bip39::Mnemonic::from_entropy(&entropy)
.expect("Failed to generate mnemonic from 16 bytes");
mnemonic.to_string()
}
mod dirs {
use std::path::PathBuf;
pub fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
}
}