use std::ffi::OsString;
use std::path::PathBuf;
pub fn home_from(home: Option<OsString>, userprofile: Option<OsString>) -> Option<PathBuf> {
[home, userprofile]
.into_iter()
.flatten()
.find(|v| !v.is_empty())
.map(PathBuf::from)
}
pub struct Paths {
pub home: PathBuf,
}
impl Paths {
pub fn from_home(home: PathBuf) -> Paths {
Paths { home }
}
pub fn detect() -> anyhow::Result<Paths> {
let home = home_from(std::env::var_os("HOME"), std::env::var_os("USERPROFILE"))
.ok_or_else(|| anyhow::anyhow!("cannot find your home directory: set HOME or USERPROFILE"))?;
Ok(Paths::from_home(home))
}
pub fn user_profiles_dir(&self) -> PathBuf {
self.home.join(".claude-profiles")
}
pub fn claude_skills_dir(&self) -> PathBuf {
self.home.join(".claude").join("skills")
}
pub fn claude_settings_path(&self) -> PathBuf {
self.home.join(".claude").join("settings.json")
}
pub fn statusline_backups_dir(&self) -> PathBuf {
self.user_profiles_dir().join("statusline-backups")
}
#[allow(dead_code)]
pub fn locks_dir(&self) -> PathBuf {
self.user_profiles_dir().join("locks")
}
pub fn index_cache_dir(&self) -> PathBuf {
self.user_profiles_dir().join(".index-cache")
}
pub fn index_file(&self) -> PathBuf {
self.index_cache_dir().join("index.json")
}
pub fn index_repos_dir(&self) -> PathBuf {
self.index_cache_dir().join("repos")
}
pub fn marketplaces_seed_file(&self) -> PathBuf {
self.user_profiles_dir().join("marketplaces.txt")
}
pub fn store_dir(&self) -> PathBuf {
self.user_profiles_dir().join("store")
}
pub fn marketplace_clone_dir(&self, name: &str) -> PathBuf {
self.store_dir().join("marketplaces").join(name)
}
pub fn external_marketplace_dir(&self, owner: &str, repo: &str) -> PathBuf {
self.store_dir().join("marketplaces").join("_external").join(format!("{owner}--{repo}"))
}
pub fn profile_vendor_dir(&self, profile_key: &str) -> PathBuf {
self.store_dir().join(profile_key).join("vendor")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn home_prefers_home_over_userprofile() {
let got = home_from(Some("/home/a".into()), Some(r"C:\Users\a".into()));
assert_eq!(got, Some(PathBuf::from("/home/a")));
}
#[test]
fn home_falls_back_to_userprofile() {
assert_eq!(home_from(None, Some(r"C:\Users\a".into())), Some(PathBuf::from(r"C:\Users\a")));
}
#[test]
fn empty_home_falls_back_to_userprofile() {
assert_eq!(home_from(Some("".into()), Some(r"C:\Users\a".into())), Some(PathBuf::from(r"C:\Users\a")));
}
#[test]
fn no_home_at_all_is_none() {
assert_eq!(home_from(None, None), None);
assert_eq!(home_from(Some("".into()), Some("".into())), None);
}
#[test]
fn derives_paths_from_home() {
let p = Paths::from_home(PathBuf::from("/h"));
assert_eq!(p.user_profiles_dir(), PathBuf::from("/h/.claude-profiles"));
assert_eq!(p.claude_skills_dir(), PathBuf::from("/h/.claude/skills"));
assert_eq!(p.claude_settings_path(), PathBuf::from("/h/.claude/settings.json"));
assert_eq!(p.locks_dir(), PathBuf::from("/h/.claude-profiles/locks"));
}
#[test]
fn derives_index_paths_from_home() {
let p = Paths::from_home(PathBuf::from("/h"));
assert_eq!(p.index_cache_dir(), PathBuf::from("/h/.claude-profiles/.index-cache"));
assert_eq!(p.index_file(), PathBuf::from("/h/.claude-profiles/.index-cache/index.json"));
assert_eq!(p.index_repos_dir(), PathBuf::from("/h/.claude-profiles/.index-cache/repos"));
assert_eq!(p.marketplaces_seed_file(), PathBuf::from("/h/.claude-profiles/marketplaces.txt"));
}
#[test]
fn derives_store_and_vendor_paths() {
let p = Paths::from_home(PathBuf::from("/h"));
assert_eq!(p.store_dir(), PathBuf::from("/h/.claude-profiles/store"));
assert_eq!(
p.marketplace_clone_dir("superpowers-marketplace"),
PathBuf::from("/h/.claude-profiles/store/marketplaces/superpowers-marketplace")
);
assert_eq!(
p.external_marketplace_dir("fuzzyalej", "openpowers"),
PathBuf::from("/h/.claude-profiles/store/marketplaces/_external/fuzzyalej--openpowers")
);
assert_eq!(
p.profile_vendor_dir("rust-developer"),
PathBuf::from("/h/.claude-profiles/store/rust-developer/vendor")
);
}
#[test]
fn derives_statusline_backups_dir() {
let p = Paths::from_home(PathBuf::from("/h"));
assert_eq!(
p.statusline_backups_dir(),
PathBuf::from("/h/.claude-profiles/statusline-backups")
);
}
}