use std::path::{Path, PathBuf};
use std::sync::OnceLock;
static MX_HOME: OnceLock<PathBuf> = OnceLock::new();
fn resolve_mx_home_with(env_val: Option<&str>) -> PathBuf {
if let Some(val) = env_val
&& !val.is_empty()
{
return PathBuf::from(val);
}
dirs::home_dir()
.expect("Could not determine home directory")
.join(".mx")
}
pub fn mx_home() -> &'static PathBuf {
MX_HOME.get_or_init(|| resolve_mx_home_with(std::env::var("MX_HOME").ok().as_deref()))
}
pub fn emit_mx_home_note() {
if std::env::var("MX_HOME").map_or(true, |v| v.is_empty()) {
eprintln!(
"note: Using default {}. Set MX_HOME to customize.",
mx_home().display()
);
}
}
pub fn schemas_dir() -> PathBuf {
mx_home().join("schemas")
}
pub fn swap_dir() -> PathBuf {
mx_home().join("swap")
}
pub fn sync_cache_dir(repo: &str) -> PathBuf {
let repo_slug = repo.replace('/', "-");
mx_home().join("cache").join("sync").join(repo_slug)
}
pub fn artifacts_dir() -> PathBuf {
mx_home().join("artifacts")
}
pub fn agents_dir() -> PathBuf {
mx_home().join("agents")
}
fn codex_dir_with(env_val: Option<&str>, home: &Path) -> PathBuf {
if let Some(path) = env_val
&& !path.is_empty()
{
return PathBuf::from(path);
}
home.join("codex")
}
pub fn codex_dir() -> PathBuf {
codex_dir_with(std::env::var("MX_CODEX_PATH").ok().as_deref(), mx_home())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mx_home_default_when_unset() {
let result = resolve_mx_home_with(None);
let expected = dirs::home_dir().unwrap().join(".mx");
assert_eq!(result, expected);
}
#[test]
fn mx_home_respects_env_var() {
let result = resolve_mx_home_with(Some("/tmp/test-mx-home"));
assert_eq!(result, PathBuf::from("/tmp/test-mx-home"));
}
#[test]
fn mx_home_empty_env_is_default() {
let result = resolve_mx_home_with(Some(""));
let expected = dirs::home_dir().unwrap().join(".mx");
assert_eq!(result, expected);
}
#[test]
fn derived_dirs_under_mx_home() {
let home = mx_home();
let schemas = schemas_dir();
assert!(schemas.starts_with(home), "schemas_dir not under mx_home");
assert_eq!(schemas.file_name().unwrap(), "schemas");
let swap = swap_dir();
assert!(swap.starts_with(home), "swap_dir not under mx_home");
assert_eq!(swap.file_name().unwrap(), "swap");
let agents = agents_dir();
assert!(agents.starts_with(home), "agents_dir not under mx_home");
assert_eq!(agents.file_name().unwrap(), "agents");
let artifacts = artifacts_dir();
assert!(
artifacts.starts_with(home),
"artifacts_dir not under mx_home"
);
assert_eq!(artifacts.file_name().unwrap(), "artifacts");
let codex = codex_dir_with(None, home);
assert!(codex.starts_with(home), "codex_dir not under mx_home");
assert_eq!(codex.file_name().unwrap(), "codex");
let sync = sync_cache_dir("owner/repo");
assert!(sync.starts_with(home), "sync_cache_dir not under mx_home");
}
#[test]
fn codex_dir_respects_override() {
let home = mx_home().clone();
let result = codex_dir_with(Some("/custom/codex"), &home);
assert_eq!(result, PathBuf::from("/custom/codex"));
}
#[test]
fn codex_dir_empty_override_is_default() {
let home = mx_home().clone();
let result = codex_dir_with(Some(""), &home);
assert_eq!(result, home.join("codex"));
}
#[test]
fn codex_dir_none_override_is_default() {
let home = mx_home().clone();
let result = codex_dir_with(None, &home);
assert_eq!(result, home.join("codex"));
}
#[test]
fn swap_dir_is_under_mx_home() {
let swap = swap_dir();
assert!(swap.starts_with(mx_home()));
}
#[test]
fn sync_cache_dir_slugifies_repo() {
let dir = sync_cache_dir("owner/repo");
assert!(dir.to_string_lossy().contains("owner-repo"));
assert!(dir.starts_with(mx_home()));
}
}