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 let path = match std::env::var_os(key) {
38 Some(v) if v.is_empty() => {
39 return Err(io::Error::new(
40 io::ErrorKind::InvalidInput,
41 format!("{key} is empty"),
42 ));
43 }
44 Some(v) => Some(PathBuf::from(v)),
45 None => home.map(|h| h.join(suffix)),
46 };
47 Ok(match agent {
48 Agent::ClaudeCode => Self {
49 claude: path,
50 codex: None,
51 },
52 Agent::Codex => Self {
53 claude: None,
54 codex: path,
55 },
56 })
57 }
58}