albatross-cli 2.1.0

Open source, terminal-first AI coding agent with fully transparent multi-model routing. Local (Ollama, LM Studio, MLX, llama.cpp) or cloud, your keys, one TUI.
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use super::HookState;

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct HookStateFile {
    #[serde(default)]
    pub hooks: BTreeMap<String, HookState>,
}

pub fn hook_state_file_path() -> Option<PathBuf> {
    hook_state_file_path_from_env(
        std::env::var("XDG_CONFIG_HOME").ok().as_deref(),
        std::env::var("HOME").ok().as_deref(),
    )
}

pub fn hook_state_file_path_from_env(xdg: Option<&str>, home: Option<&str>) -> Option<PathBuf> {
    let base = if let Some(xdg) = xdg {
        PathBuf::from(xdg)
    } else {
        PathBuf::from(home?).join(".config")
    };
    Some(base.join("albatross").join("hooks-state.json"))
}

pub fn load_hook_state_file_from(path: &Path) -> Result<HookStateFile> {
    if !path.exists() {
        return Ok(HookStateFile::default());
    }
    let text =
        std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
    if text.trim().is_empty() {
        return Ok(HookStateFile::default());
    }
    serde_json::from_str(&text).with_context(|| format!("parsing {}", path.display()))
}

pub fn save_hook_state_file_to(path: &Path, state: &HookStateFile) -> Result<()> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .with_context(|| format!("creating {}", parent.display()))?;
    }
    let body = serde_json::to_string_pretty(state)? + "\n";
    std::fs::write(path, body).with_context(|| format!("writing {}", path.display()))
}