pub mod features;
pub mod utils;
pub use features::report::{
axis_label, axis_tokens, bucket_at, bucket_label_of, build_report, by_project, chart_bucket,
drill, nice_max, pie, project_key, short_label, short_name, slice_angles, time_series, Bucket,
Drill, Metric, Pie, Query, ReportRow, RowFilter, Selection, Slice, TimeSeries, ToolFilter,
Window,
};
pub use features::settings::{self, Settings};
pub use features::theme::{
find_theme, theme_by_index, theme_index_by_name, AppTheme, Rgb, THEME_COUNT, THEME_NAMES,
};
pub use features::usage::{Row, Summary, Tool};
use chrono::{DateTime, Utc};
#[derive(Debug, Default, Clone)]
pub struct Collected {
pub rows: Vec<Row>,
pub warnings: Vec<String>,
}
pub fn collect(tool: ToolFilter, start: DateTime<Utc>) -> Collected {
use features::{amp, claude, codex, copilot, gemini, opencode, pi, zed};
type Job = fn(DateTime<Utc>) -> anyhow::Result<Vec<Row>>;
let jobs: [(Tool, Job); 8] = [
(Tool::Pi, pi::collect_pi),
(Tool::Zed, zed::collect_zed),
(Tool::Claude, claude::collect_claude),
(Tool::Codex, codex::collect_codex),
(Tool::Copilot, copilot::collect_copilot),
(Tool::Gemini, gemini::collect_gemini),
(Tool::OpenCode, opencode::collect_opencode),
(Tool::Amp, amp::collect_amp),
];
let results: Vec<(Tool, anyhow::Result<Vec<Row>>)> = std::thread::scope(|scope| {
let handles: Vec<_> = jobs
.iter()
.filter(|(t, _)| tool.includes(*t))
.map(|(t, job)| (*t, scope.spawn(move || job(start))))
.collect();
handles
.into_iter()
.map(|(t, h)| {
let r = h
.join()
.unwrap_or_else(|_| Err(anyhow::anyhow!("collector panicked")));
(t, r)
})
.collect()
});
let mut out = Collected::default();
for (t, r) in results {
match r {
Ok(rows) => out.rows.extend(rows),
Err(e) => out.warnings.push(format!("{}: {e:#}", t.as_str())),
}
}
out.rows.retain(|r| r.ts >= start);
out.rows.sort_by_key(|r| r.ts);
out
}