use std::path::PathBuf;
use super::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SkillsRoot {
Global {
home: PathBuf,
},
Project {
project_root: PathBuf,
},
}
impl SkillsRoot {
pub fn global() -> Result<SkillsRoot> {
Ok(SkillsRoot::Global { home: home_dir()? })
}
pub fn discovery_root(&self) -> PathBuf {
match self {
SkillsRoot::Global { home } => home.join(".claude"),
SkillsRoot::Project { project_root } => project_root.clone(),
}
}
pub fn cli_scope(global: bool, project_root: PathBuf) -> Result<Vec<SkillsRoot>> {
if global {
Ok(vec![SkillsRoot::global()?])
} else {
Ok(vec![SkillsRoot::Project { project_root }])
}
}
}
pub(crate) fn home_dir() -> Result<PathBuf> {
let var = if cfg!(windows) { "USERPROFILE" } else { "HOME" };
std::env::var_os(var)
.filter(|v| !v.is_empty())
.map(PathBuf::from)
.ok_or_else(|| Error::Io(format!("could not resolve home directory (${var} unset)")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_project_root_anchors_on_the_project_directory() {
let root = SkillsRoot::Project {
project_root: PathBuf::from("/tmp/proj"),
};
assert_eq!(root.discovery_root(), PathBuf::from("/tmp/proj"));
}
#[test]
fn the_global_root_anchors_on_dot_claude_not_home() {
let root = SkillsRoot::Global {
home: PathBuf::from("/home/u"),
};
assert_eq!(root.discovery_root(), PathBuf::from("/home/u/.claude"));
assert_eq!(
root.discovery_root().join("skills"),
PathBuf::from("/home/u/.claude/skills")
);
}
#[test]
fn scopes_are_what_each_surface_reads() {
let project = PathBuf::from("/tmp/proj");
assert_eq!(
SkillsRoot::cli_scope(false, project.clone()).expect("project scope"),
vec![SkillsRoot::Project {
project_root: project.clone()
}]
);
let global = SkillsRoot::global().expect("this machine has a home directory");
assert_eq!(
SkillsRoot::cli_scope(true, project.clone()).expect("global scope"),
vec![global.clone()]
);
}
}