tandem-core 0.4.16

Core types and helpers for the Tandem engine
Documentation
use std::path::PathBuf;
use uuid::Uuid;

use crate::resolve_shared_paths;

const TOKEN_SERVICE: &str = "ai.frumu.tandem";
const TOKEN_ACCOUNT: &str = "engine_api_token";

#[derive(Debug, Clone)]
pub struct EngineApiTokenMaterial {
    pub token: String,
    pub backend: String,
    pub file_path: PathBuf,
}

pub fn engine_api_token_file_path() -> PathBuf {
    if let Ok(paths) = resolve_shared_paths() {
        return paths
            .canonical_root
            .join("security")
            .join("engine_api_token");
    }
    if let Some(data_dir) = dirs::data_dir() {
        return data_dir
            .join("tandem")
            .join("security")
            .join("engine_api_token");
    }
    dirs::home_dir()
        .map(|home| {
            home.join(".tandem")
                .join("security")
                .join("engine_api_token")
        })
        .unwrap_or_else(|| PathBuf::from("engine_api_token"))
}

fn new_token() -> String {
    format!("tk_{}", Uuid::new_v4().simple())
}

fn keyring_entry() -> Option<keyring::Entry> {
    keyring::Entry::new(TOKEN_SERVICE, TOKEN_ACCOUNT).ok()
}

fn read_file_token(path: &PathBuf) -> Option<String> {
    let existing = std::fs::read_to_string(path).ok()?;
    let token = existing.trim();
    if token.is_empty() {
        None
    } else {
        Some(token.to_string())
    }
}

fn write_file_token(path: &PathBuf, token: &str) -> bool {
    if let Some(parent) = path.parent() {
        if std::fs::create_dir_all(parent).is_err() {
            return false;
        }
    }
    std::fs::write(path, token).is_ok()
}

pub fn load_or_create_engine_api_token() -> EngineApiTokenMaterial {
    let file_path = engine_api_token_file_path();

    if let Some(entry) = keyring_entry() {
        if let Ok(token) = entry.get_password() {
            let token = token.trim().to_string();
            if !token.is_empty() {
                return EngineApiTokenMaterial {
                    token,
                    backend: "keychain".to_string(),
                    file_path,
                };
            }
        }
    }

    if let Some(token) = read_file_token(&file_path) {
        if let Some(entry) = keyring_entry() {
            let _ = entry.set_password(&token);
        }
        return EngineApiTokenMaterial {
            token,
            backend: "file".to_string(),
            file_path,
        };
    }

    let token = new_token();
    if let Some(entry) = keyring_entry() {
        if entry.set_password(&token).is_ok() {
            return EngineApiTokenMaterial {
                token,
                backend: "keychain".to_string(),
                file_path,
            };
        }
    }

    let wrote_file = write_file_token(&file_path, &token);
    EngineApiTokenMaterial {
        token,
        backend: if wrote_file {
            "file".to_string()
        } else {
            "memory".to_string()
        },
        file_path,
    }
}