use std::path::PathBuf;
pub struct WorkEntry {
pub ts: u64,
pub session: String,
pub tab: String,
pub verdict: String,
pub failed: bool,
pub dur_secs: Option<u64>,
}
pub fn worklog_path() -> Option<PathBuf> {
if let Some(p) = std::env::var_os("MARS_WORKLOG") {
return Some(PathBuf::from(p));
}
std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".mars").join("worklog.jsonl"))
}
fn mission_path() -> Option<PathBuf> {
worklog_path().map(|p| p.with_file_name("mission.json"))
}
pub fn record(e: &WorkEntry) {
let Some(path) = worklog_path() else { return };
if let Some(dir) = path.parent() {
let _ = std::fs::create_dir_all(dir);
}
let line = serde_json::json!({
"ts": e.ts,
"session": e.session,
"tab": e.tab,
"verdict": e.verdict,
"failed": e.failed,
"dur_secs": e.dur_secs,
});
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&path) {
let _ = writeln!(f, "{line}");
}
}
pub fn recent(session: &str, limit: usize) -> Vec<WorkEntry> {
let Some(path) = worklog_path() else { return Vec::new() };
let Ok(content) = std::fs::read_to_string(&path) else { return Vec::new() };
let mut out: Vec<WorkEntry> = content
.lines()
.filter_map(|l| serde_json::from_str::<serde_json::Value>(l).ok())
.filter(|j| j["session"].as_str() == Some(session))
.map(|j| WorkEntry {
ts: j["ts"].as_u64().unwrap_or(0),
session: session.to_string(),
tab: j["tab"].as_str().unwrap_or("").to_string(),
verdict: j["verdict"].as_str().unwrap_or("").to_string(),
failed: j["failed"].as_bool().unwrap_or(false),
dur_secs: j["dur_secs"].as_u64(),
})
.collect();
let skip = out.len().saturating_sub(limit);
out.drain(..skip);
out
}
pub fn save_mission(session: &str, mission: &str, as_of: u64) {
let Some(path) = mission_path() else { return };
let mut map: serde_json::Map<String, serde_json::Value> = path
.exists()
.then(|| std::fs::read_to_string(&path).ok())
.flatten()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
map.insert(
session.to_string(),
serde_json::json!({ "mission": mission, "as_of": as_of }),
);
if let Some(dir) = path.parent() {
let _ = std::fs::create_dir_all(dir);
}
if let Ok(s) = serde_json::to_string_pretty(&map) {
let _ = std::fs::write(&path, s);
}
}
pub fn load_mission(session: &str) -> Option<(String, u64)> {
let path = mission_path()?;
let s = std::fs::read_to_string(path).ok()?;
let j: serde_json::Value = serde_json::from_str(&s).ok()?;
let m = &j[session];
Some((m["mission"].as_str()?.to_string(), m["as_of"].as_u64().unwrap_or(0)))
}