use std::path::PathBuf;
fn home() -> PathBuf {
PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into()))
}
fn base(xdg_env: &str, xdg_default: &str, root_sub: &str) -> PathBuf {
if let Ok(root) = std::env::var("ROSTER_ROOT") {
return PathBuf::from(root).join(root_sub);
}
std::env::var(xdg_env)
.map(PathBuf::from)
.unwrap_or_else(|_| home().join(xdg_default))
.join("roster")
}
pub fn config_root() -> PathBuf {
base("XDG_CONFIG_HOME", ".config", "config")
}
pub fn data_root() -> PathBuf {
base("XDG_DATA_HOME", ".local/share", "data")
}
pub fn state_root() -> PathBuf {
base("XDG_STATE_HOME", ".local/state", "state")
}
pub fn short_worker(worker: &str) -> &str {
worker.rsplit('/').next().unwrap_or(worker)
}
pub fn org_file() -> PathBuf {
config_root().join("org.toml")
}
pub fn providers_file() -> PathBuf {
config_root().join("providers.toml")
}
pub fn connections_dir() -> PathBuf {
config_root().join("connections")
}
pub fn workers_dir() -> PathBuf {
config_root().join("workers")
}
pub fn worker_dir(name: &str) -> PathBuf {
workers_dir().join(short_worker(name))
}
pub fn vault_dir() -> PathBuf {
if let Ok(dir) = std::env::var("ROSTER_VAULT_DIR") {
return PathBuf::from(dir);
}
data_root().join("vault")
}
pub fn ca_dir() -> PathBuf {
if let Ok(dir) = std::env::var("ROSTER_CA_DIR") {
return PathBuf::from(dir);
}
data_root().join("ca")
}
pub fn workers_data_dir() -> PathBuf {
data_root().join("workers")
}
pub fn worker_data_dir(worker: &str) -> PathBuf {
workers_data_dir().join(short_worker(worker))
}
pub fn worker_queue_dir(worker: &str) -> PathBuf {
worker_data_dir(worker).join("queue")
}
pub fn worker_tasks_file(worker: &str) -> PathBuf {
worker_data_dir(worker).join("tasks").join("tasks.json")
}
pub fn worker_tasks_journal(worker: &str) -> PathBuf {
worker_data_dir(worker).join("tasks").join("journal.jsonl")
}
pub fn tms_view_file(worker: &str) -> PathBuf {
state_root()
.join("tms-view")
.join(format!("{}.json", short_worker(worker)))
}
pub fn gateway_state_file() -> PathBuf {
state_root().join("gateway.json")
}
pub fn tms_cursors_file() -> PathBuf {
state_root().join("tms-cursors.json")
}
pub fn worker_journal_file(worker: &str) -> PathBuf {
worker_data_dir(worker).join("journal").join("events.jsonl")
}
pub fn worker_store_dir(worker: &str) -> PathBuf {
worker_data_dir(worker).join("store")
}
pub fn worker_store_snapshots_dir(worker: &str) -> PathBuf {
worker_data_dir(worker).join("store-snapshots")
}
pub fn worker_channel_stores_dir(worker: &str) -> PathBuf {
worker_data_dir(worker).join("channel-stores")
}
pub fn worker_channel_store_dir(worker: &str, channel_id: &str) -> PathBuf {
worker_channel_stores_dir(worker).join(channel_id)
}
pub fn worker_knowledge_dir(worker: &str) -> PathBuf {
worker_data_dir(worker).join("knowledge")
}
pub fn worker_gates_pending_dir(worker: &str) -> PathBuf {
worker_data_dir(worker).join("gates").join("pending")
}
pub fn worker_gates_resolved_dir(worker: &str) -> PathBuf {
worker_data_dir(worker).join("gates").join("resolved")
}
pub fn channels_dir() -> PathBuf {
data_root().join("channels")
}
pub fn channel_dir(channel_id: &str) -> PathBuf {
channels_dir().join(channel_id)
}
pub fn channel_meta_file(channel_id: &str) -> PathBuf {
channel_dir(channel_id).join("meta.json")
}
fn audit_dir() -> PathBuf {
data_root().join("audit")
}
pub fn decisions_log() -> PathBuf {
audit_dir().join("decisions.jsonl")
}
#[cfg_attr(test, allow(dead_code))]
pub fn usage_log() -> PathBuf {
audit_dir().join("usage.jsonl")
}
pub fn credentials_log() -> PathBuf {
audit_dir().join("credentials.jsonl")
}
pub fn messages_log() -> PathBuf {
audit_dir().join("messages.jsonl")
}
pub fn runs_dir() -> PathBuf {
state_root().join("runs")
}
pub fn worker_runs_dir(worker: &str) -> PathBuf {
runs_dir().join(short_worker(worker))
}
pub fn new_run_dir(worker: &str, run_id: &str) -> PathBuf {
worker_runs_dir(worker).join(run_id)
}
pub fn run_dir(run_id: &str) -> PathBuf {
let root = runs_dir();
if let Ok(entries) = std::fs::read_dir(&root) {
for e in entries.flatten() {
let cand = e.path().join(run_id);
if cand.is_dir() {
return cand;
}
}
}
root.join(run_id)
}
pub fn identity_dir() -> PathBuf {
state_root().join("identity")
}
pub fn locks_dir() -> PathBuf {
state_root().join("locks")
}
pub fn lock_file(name: &str) -> PathBuf {
locks_dir().join(format!("{name}.flock"))
}
pub fn worker_listener_lock(worker: &str) -> PathBuf {
locks_dir().join(format!("{}.lock", short_worker(worker)))
}
pub fn outbox_dir() -> PathBuf {
state_root().join("outbox")
}
pub fn talk_history_file() -> PathBuf {
state_root().join("talk-history")
}
pub fn talk_seen_file(channel_id: &str) -> PathBuf {
state_root()
.join("talk-seen")
.join(format!("{channel_id}.txt"))
}
pub fn image_pulls_file() -> PathBuf {
state_root().join("image-pulls.json")
}
pub fn egress_note_marker() -> PathBuf {
state_root().join("egress-note-shown")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn worker_handles_normalize() {
assert_eq!(worker_data_dir("dobby"), worker_data_dir("org/dobby"));
assert_eq!(worker_queue_dir("org/dobby").file_name().unwrap(), "queue");
}
}