use std::path::{Path, PathBuf};
#[derive(Clone)]
pub struct Paths {
home: PathBuf, claude_dir: PathBuf, codex_dir: PathBuf, gemini_dir: PathBuf, data: PathBuf, }
impl Paths {
pub fn rooted(root: &Path) -> Paths {
Paths {
home: root.to_path_buf(),
claude_dir: root.join(".claude"),
codex_dir: root.join(".codex"),
gemini_dir: root.join(".gemini"),
data: root.join(".local/share/swapdex"),
}
}
pub fn resolve() -> anyhow::Result<Paths> {
use anyhow::Context;
if let Some(root) = std::env::var_os("SWAPDEX_ROOT") {
return Ok(Paths::rooted(Path::new(&root)));
}
let home = dirs::home_dir().context("cannot determine home dir")?;
let claude_dir = std::env::var_os("CLAUDE_CONFIG_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| home.join(".claude"));
let codex_dir = std::env::var_os("CODEX_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| home.join(".codex"));
let data = dirs::data_dir()
.context("cannot determine data dir")?
.join("swapdex");
let gemini_dir = home.join(".gemini");
Ok(Paths {
home,
claude_dir,
codex_dir,
gemini_dir,
data,
})
}
pub fn claude_credentials(&self) -> PathBuf {
self.claude_dir.join(".credentials.json")
}
pub fn claude_config_json(&self) -> PathBuf {
self.home.join(".claude.json")
}
pub fn codex_auth(&self) -> PathBuf {
self.codex_dir.join("auth.json")
}
pub fn gemini_oauth(&self) -> PathBuf {
self.gemini_dir.join("oauth_creds.json")
}
pub fn gemini_accounts(&self) -> PathBuf {
self.gemini_dir.join("google_accounts.json")
}
pub fn store_dir(&self) -> PathBuf {
self.data.clone()
}
pub fn claude_projects(&self) -> PathBuf {
self.claude_dir.join("projects")
}
pub fn codex_sessions(&self) -> PathBuf {
self.codex_dir.join("sessions")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rooted_redirects_every_path_under_the_temp_root() {
let dir = tempfile::tempdir().unwrap();
let p = Paths::rooted(dir.path());
for path in [
p.claude_credentials(),
p.claude_config_json(),
p.codex_auth(),
p.store_dir(),
] {
assert!(path.starts_with(dir.path()), "{path:?} escaped the root");
}
assert_eq!(p.claude_config_json(), dir.path().join(".claude.json"));
assert!(p
.claude_credentials()
.starts_with(dir.path().join(".claude")));
}
}