use std::path::{Path, PathBuf};
use std::sync::LazyLock;
pub static HOME: LazyLock<PathBuf> =
LazyLock::new(|| dirs::home_dir().unwrap_or_else(|| PathBuf::from("/")));
pub static CLAUDE_CONFIG_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
std::env::var_os("CLAUDE_CONFIG_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| HOME.join(".claude"))
});
pub static CLAUDE_PROJECTS_ROOT: LazyLock<PathBuf> =
LazyLock::new(|| CLAUDE_CONFIG_DIR.join("projects"));
pub static CODEX_HOME: LazyLock<PathBuf> = LazyLock::new(|| {
std::env::var_os("CODEX_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| HOME.join(".codex"))
});
pub static CODEX_SESSIONS_ROOT: LazyLock<PathBuf> = LazyLock::new(|| CODEX_HOME.join("sessions"));
pub static CURSOR_PROJECTS_ROOT: LazyLock<PathBuf> = LazyLock::new(|| {
std::env::var_os("CURSOR_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| HOME.join(".cursor"))
.join("projects")
});
pub static PI_AGENT_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
std::env::var_os("PI_CODING_AGENT_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| HOME.join(".pi").join("agent"))
});
pub static PI_SESSIONS_ROOT: LazyLock<PathBuf> = LazyLock::new(|| {
std::env::var_os("PI_CODING_AGENT_SESSION_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| PI_AGENT_DIR.join("sessions"))
});
pub static OPENCODE_DATA_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
std::env::var_os("OPENCODE_DATA_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| {
dirs::data_dir()
.unwrap_or_else(|| HOME.join(".local").join("share"))
.join("opencode")
})
});
pub static CLAUDE_MAC_COWORK_ROOT: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
cfg!(target_os = "macos").then(|| {
HOME.join("Library")
.join("Application Support")
.join("Claude")
.join("local-agent-mode-sessions")
})
});
pub static CLAUDE_MAC_CODE_ROOT: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
cfg!(target_os = "macos").then(|| {
HOME.join("Library")
.join("Application Support")
.join("Claude")
.join("claude-code-sessions")
})
});
pub static CACHE_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
dirs::cache_dir()
.unwrap_or_else(|| HOME.join(".cache"))
.join("cctop")
});
pub static COST_CACHE_FILE: LazyLock<PathBuf> = LazyLock::new(|| CACHE_DIR.join("cost-cache.json"));
pub static PRICING_CACHE_FILE: LazyLock<PathBuf> =
LazyLock::new(|| CACHE_DIR.join("litellm-pricing.json"));
pub static UI_PREFS_FILE: LazyLock<PathBuf> = LazyLock::new(|| CACHE_DIR.join("ui-prefs.json"));
pub const LITELLM_URL: &str =
"https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
pub const PRICING_CACHE_MAX_AGE_SECS: u64 = 24 * 60 * 60;
pub static COMPACT_THRESHOLD: LazyLock<f64> = LazyLock::new(|| {
std::env::var("CLAUDE_AUTOCOMPACT_PCT_OVERRIDE")
.ok()
.and_then(|v| v.parse::<f64>().ok())
.map(|p| p / 100.0)
.unwrap_or(0.835)
});
pub const MAX_JSONL_LINE_BYTES: usize = 512 * 1024;
pub const MAX_TOOL_DETAILS: usize = 200;
pub const MAX_DIFF_LINES: usize = 60;
pub const CLAUDE_DEFAULT_CTX: u64 = 200_000;
pub const CLAUDE_1M_CTX: u64 = 1_048_576;
pub const CODEX_DEFAULT_CTX: u64 = 258_400;
pub fn is_full_uuid(s: &str) -> bool {
let b = s.as_bytes();
if b.len() != 36 {
return false;
}
for (i, c) in b.iter().enumerate() {
match i {
8 | 13 | 18 | 23 => {
if *c != b'-' {
return false;
}
}
_ => {
if !c.is_ascii_hexdigit() || c.is_ascii_uppercase() {
return false;
}
}
}
}
true
}
pub fn trailing_uuid(stem: &str) -> Option<&str> {
if stem.len() < 36 {
return None;
}
let tail = &stem[stem.len() - 36..];
is_full_uuid(tail).then_some(tail)
}
pub fn dir_exists(p: &Path) -> bool {
p.is_dir()
}
pub fn list_dir(p: &Path) -> Vec<String> {
let Ok(rd) = std::fs::read_dir(p) else {
return Vec::new();
};
let mut out: Vec<String> = rd
.flatten()
.filter_map(|e| e.file_name().into_string().ok())
.collect();
out.sort();
out
}
pub fn rglob(dir: &Path, ext: &str) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut stack = vec![dir.to_path_buf()];
while let Some(d) = stack.pop() {
let Ok(rd) = std::fs::read_dir(&d) else {
continue;
};
for entry in rd.flatten() {
let Ok(ft) = entry.file_type() else { continue };
let path = entry.path();
if ft.is_dir() {
stack.push(path);
} else if ft.is_file() && path.to_string_lossy().ends_with(ext) {
out.push(path);
}
}
}
out
}
pub fn file_mtime_ms(p: &Path) -> u64 {
std::fs::metadata(p)
.and_then(|m| m.modified())
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uuid_validation() {
assert!(is_full_uuid("7026d578-8cba-4880-b464-9700f1b77b71"));
assert!(!is_full_uuid("7026D578-8CBA-4880-B464-9700F1B77B71")); assert!(!is_full_uuid("7026d578-8cba-4880-b464-9700f1b77b7")); assert!(!is_full_uuid("7026d5788cba4880b4649700f1b77b71")); }
#[test]
fn uuid_extraction() {
let stem = "rollout-2026-06-29T10-59-07-019f1075-3f22-7ad0-b496-73dcda6a7a25";
assert_eq!(
trailing_uuid(stem),
Some("019f1075-3f22-7ad0-b496-73dcda6a7a25")
);
}
}