capo-agent 0.1.0

Coding-agent library built on motosan-agent-loop. Composable, embeddable.
Documentation
//! Filesystem path helpers for capo's per-user agent directory.
//!
//! Mirrors pi's layout: `~/.capo/agent/` is the global app dir
//! (overridable via `CAPO_AGENT_DIR`), and everything else
//! (sessions, settings, auth, permissions) lives underneath.

use std::env;
use std::path::PathBuf;

/// Resolve the per-user agent directory.
///
/// Priority:
/// 1. `$CAPO_AGENT_DIR` env var if set (leading `~/` expanded to `$HOME`).
/// 2. `$HOME/.capo/agent`.
/// 3. `./.capo/agent` as a last-resort fallback if `$HOME` is missing.
pub fn agent_dir() -> PathBuf {
    agent_dir_with(|name| env::var(name).ok())
}

/// Test seam: lookup env vars through a closure. Production calls
/// [`agent_dir`] which delegates to `env::var`.
pub fn agent_dir_with<F>(lookup: F) -> PathBuf
where
    F: Fn(&str) -> Option<String>,
{
    if let Some(raw) = lookup("CAPO_AGENT_DIR") {
        return expand_tilde(&raw, &lookup);
    }
    if let Some(home) = lookup("HOME") {
        return PathBuf::from(home).join(".capo").join("agent");
    }
    PathBuf::from(".capo").join("agent")
}

fn expand_tilde<F>(raw: &str, lookup: &F) -> PathBuf
where
    F: Fn(&str) -> Option<String>,
{
    let trimmed = raw.trim();
    if trimmed == "~" {
        return lookup("HOME")
            .map(PathBuf::from)
            .unwrap_or_else(|| PathBuf::from("~"));
    }
    if let Some(rest) = trimmed.strip_prefix("~/") {
        if let Some(home) = lookup("HOME") {
            return PathBuf::from(home).join(rest);
        }
    }
    PathBuf::from(trimmed)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    fn lookup(map: HashMap<&'static str, &'static str>) -> impl Fn(&str) -> Option<String> {
        move |name| map.get(name).map(|v| (*v).to_string())
    }

    #[test]
    fn agent_dir_uses_capo_agent_dir_env() {
        let env = HashMap::from([("CAPO_AGENT_DIR", "/tmp/x")]);
        assert_eq!(agent_dir_with(lookup(env)), PathBuf::from("/tmp/x"));
    }

    #[test]
    fn agent_dir_expands_tilde_in_capo_agent_dir() {
        let env = HashMap::from([("CAPO_AGENT_DIR", "~/.test-capo"), ("HOME", "/Users/wade")]);
        assert_eq!(
            agent_dir_with(lookup(env)),
            PathBuf::from("/Users/wade/.test-capo")
        );
    }

    #[test]
    fn agent_dir_defaults_to_home_capo_agent() {
        let env = HashMap::from([("HOME", "/Users/wade")]);
        assert_eq!(
            agent_dir_with(lookup(env)),
            PathBuf::from("/Users/wade/.capo/agent")
        );
    }
}