mod cache;
mod client;
mod config;
mod prompt;
pub use cache::{cache_dir, delete_cache, read_cache, write_cache};
pub use client::call_haiku_api;
pub use config::{load_api_config, LlmApiConfig};
pub use prompt::build_prompt;
use std::fs;
use std::path::Path;
use std::time::UNIX_EPOCH;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LlmReportType {
All,
Monthly,
Yearly,
}
pub fn compute_source_mtime(claude_dir: &Path) -> u64 {
let mut max_mtime: u64 = 0;
let mut check = |path: &Path| {
if let Ok(meta) = fs::metadata(path) {
if let Ok(modified) = meta.modified() {
if let Ok(dur) = modified.duration_since(UNIX_EPOCH) {
max_mtime = max_mtime.max(dur.as_secs());
}
}
}
};
check(&claude_dir.join("stats-cache.json"));
check(&claude_dir.join("history.jsonl"));
let projects_dir = claude_dir.join("projects");
if let Ok(entries) = fs::read_dir(&projects_dir) {
for entry in entries.flatten() {
let proj_path = entry.path();
if !proj_path.is_dir() {
continue;
}
if let Ok(sessions) = fs::read_dir(&proj_path) {
for s in sessions.flatten() {
let sp = s.path();
if sp.extension().map(|e| e == "jsonl").unwrap_or(false) {
check(&sp);
}
}
}
}
}
max_mtime
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_llm_report_type_equality() {
assert_eq!(LlmReportType::All, LlmReportType::All);
assert_eq!(LlmReportType::Monthly, LlmReportType::Monthly);
assert_eq!(LlmReportType::Yearly, LlmReportType::Yearly);
assert_ne!(LlmReportType::All, LlmReportType::Monthly);
assert_ne!(LlmReportType::Monthly, LlmReportType::Yearly);
}
#[test]
fn test_compute_source_mtime_no_files() {
let mtime = compute_source_mtime(Path::new("/nonexistent/path"));
assert_eq!(mtime, 0);
}
#[test]
fn test_compute_source_mtime_real() {
let home = dirs::home_dir().unwrap_or_default();
let claude_dir = home.join(".claude");
if claude_dir.exists() {
let mtime = compute_source_mtime(&claude_dir);
assert!(mtime > 0, "Real claude dir should have files with mtime > 0");
}
}
}