1use std::env;
8use std::path::PathBuf;
9
10pub fn agent_dir() -> PathBuf {
17 agent_dir_with(|name| env::var(name).ok())
18}
19
20pub fn extensions_manifest_path(agent_dir: &std::path::Path) -> std::path::PathBuf {
22 agent_dir.join("extensions.toml")
23}
24
25pub fn agent_dir_with<F>(lookup: F) -> PathBuf
28where
29 F: Fn(&str) -> Option<String>,
30{
31 if let Some(raw) = lookup("CAPO_AGENT_DIR") {
32 return expand_tilde(&raw, &lookup);
33 }
34 if let Some(home) = lookup("HOME") {
35 return PathBuf::from(home).join(".capo").join("agent");
36 }
37 PathBuf::from(".capo").join("agent")
38}
39
40fn expand_tilde<F>(raw: &str, lookup: &F) -> PathBuf
41where
42 F: Fn(&str) -> Option<String>,
43{
44 let trimmed = raw.trim();
45 if trimmed == "~" {
46 return lookup("HOME")
47 .map(PathBuf::from)
48 .unwrap_or_else(|| PathBuf::from("~"));
49 }
50 if let Some(rest) = trimmed.strip_prefix("~/") {
51 if let Some(home) = lookup("HOME") {
52 return PathBuf::from(home).join(rest);
53 }
54 }
55 PathBuf::from(trimmed)
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use std::collections::HashMap;
62
63 fn lookup(map: HashMap<&'static str, &'static str>) -> impl Fn(&str) -> Option<String> {
64 move |name| map.get(name).map(|v| (*v).to_string())
65 }
66
67 #[test]
68 fn agent_dir_uses_capo_agent_dir_env() {
69 let env = HashMap::from([("CAPO_AGENT_DIR", "/tmp/x")]);
70 assert_eq!(agent_dir_with(lookup(env)), PathBuf::from("/tmp/x"));
71 }
72
73 #[test]
74 fn agent_dir_expands_tilde_in_capo_agent_dir() {
75 let env = HashMap::from([("CAPO_AGENT_DIR", "~/.test-capo"), ("HOME", "/Users/wade")]);
76 assert_eq!(
77 agent_dir_with(lookup(env)),
78 PathBuf::from("/Users/wade/.test-capo")
79 );
80 }
81
82 #[test]
83 fn agent_dir_defaults_to_home_capo_agent() {
84 let env = HashMap::from([("HOME", "/Users/wade")]);
85 assert_eq!(
86 agent_dir_with(lookup(env)),
87 PathBuf::from("/Users/wade/.capo/agent")
88 );
89 }
90}