use anyhow::{Context, Result};
use directories::{BaseDirs, ProjectDirs};
use std::path::{MAIN_SEPARATOR, PathBuf};
pub fn home() -> Result<PathBuf> {
BaseDirs::new().map(|b| b.home_dir().to_path_buf()).context(
"could not determine the home directory; set it explicitly if your environment is unusual",
)
}
pub fn claude_projects_dir() -> Result<PathBuf> {
Ok(home()?.join(".claude").join("projects"))
}
fn project_dirs() -> Result<ProjectDirs> {
ProjectDirs::from("", "", "tokr")
.context("could not determine the local application data directory")
}
pub fn data_dir() -> Result<PathBuf> {
Ok(project_dirs()?.data_local_dir().to_path_buf())
}
pub fn db_path() -> Result<PathBuf> {
Ok(data_dir()?.join("tokr.db"))
}
pub fn archive_dir() -> Result<PathBuf> {
Ok(data_dir()?.join("archive"))
}
pub fn ensure_data_dir() -> Result<()> {
let dir = data_dir()?;
std::fs::create_dir_all(&dir).with_context(|| format!("creating {}", dir.display()))
}
pub fn project_from_transcript(transcript_path: &str) -> String {
let dir_name = parent_dir_name(transcript_path).unwrap_or("");
decode_project_dir(dir_name)
}
fn parent_dir_name(path: &str) -> Option<&str> {
let mut parts = path
.trim_end_matches(['/', '\\'])
.rsplit(['/', '\\'])
.filter(|part| !part.is_empty());
let _file_name = parts.next()?;
parts.next()
}
pub fn decode_project_dir(name: &str) -> String {
decode_project_dir_with_separator(name, MAIN_SEPARATOR)
}
fn decode_project_dir_with_separator(name: &str, separator: char) -> String {
if name.is_empty() {
return String::new();
}
name.replace('-', &separator.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_project_dir_with_unix_separator() {
assert_eq!(
decode_project_dir_with_separator("-home-skynet-repo", '/'),
"/home/skynet/repo"
);
}
#[test]
fn decodes_project_dir_with_windows_separator() {
assert_eq!(
decode_project_dir_with_separator(r"C:-Users-Alice-repo", '\\'),
r"C:\Users\Alice\repo"
);
}
#[test]
fn extracts_project_dir_from_unix_transcript_path() {
assert_eq!(
project_from_transcript("/tmp/.claude/projects/-home-skynet-repo/session.jsonl"),
format!("{MAIN_SEPARATOR}home{MAIN_SEPARATOR}skynet{MAIN_SEPARATOR}repo")
);
}
#[test]
fn extracts_project_dir_from_windows_transcript_path() {
assert_eq!(
project_from_transcript(
r"C:\Users\Alice\.claude\projects\C:-Users-Alice-repo\session.jsonl"
),
format!("C:{MAIN_SEPARATOR}Users{MAIN_SEPARATOR}Alice{MAIN_SEPARATOR}repo")
);
}
}