1use crate::Agent;
2use std::{
3 io,
4 path::{Path, PathBuf},
5};
6
7#[derive(Debug, Clone, Default)]
8pub struct Roots {
9 pub claude: Option<PathBuf>,
10 pub codex: Option<PathBuf>,
11}
12impl Roots {
13 pub fn from_home(home: &Path) -> Self {
14 Self {
15 claude: Some(home.join(".claude")),
16 codex: Some(home.join(".codex")),
17 }
18 }
19 pub fn from_env() -> io::Result<Self> {
20 Ok(Self {
21 claude: Self::from_env_for(Agent::ClaudeCode)?.claude,
22 codex: Self::from_env_for(Agent::Codex)?.codex,
23 })
24 }
25 pub fn from_env_for(agent: Agent) -> io::Result<Self> {
28 let home = std::env::var_os("HOME")
29 .filter(|v| !v.is_empty())
30 .map(PathBuf::from)
31 .filter(|p| !cfg!(windows) || p.is_absolute())
32 .or_else(std::env::home_dir);
33 let (key, suffix) = match agent {
34 Agent::ClaudeCode => ("CLAUDE_CONFIG_DIR", ".claude"),
35 Agent::Codex => ("CODEX_HOME", ".codex"),
36 _ => {
37 return Err(io::Error::new(
38 io::ErrorKind::Unsupported,
39 "use discover_directory with an explicit native session root",
40 ));
41 }
42 };
43 let path = match std::env::var_os(key) {
44 Some(v) if v.is_empty() => {
45 return Err(io::Error::new(
46 io::ErrorKind::InvalidInput,
47 format!("{key} is empty"),
48 ));
49 }
50 Some(v) => Some(PathBuf::from(v)),
51 None => home.map(|h| h.join(suffix)),
52 };
53 Ok(match agent {
54 Agent::ClaudeCode => Self {
55 claude: path,
56 codex: None,
57 },
58 Agent::Codex => Self {
59 claude: None,
60 codex: path,
61 },
62 _ => unreachable!("unsupported agents returned above"),
63 })
64 }
65}