use anyhow::Result;
use serde_json::json;
use crate::tracking::{ModelRow, Period, PeriodRow, Summary, Tracker};
use crate::ui::{self, Tone};
pub struct DaemonView {
pub running: bool,
pub pid: u32,
pub port: u16,
pub uptime: String,
pub uptime_secs: i64,
pub ca_present: bool,
pub port_accepting: bool,
pub env_port: Option<u16>,
pub autostart: bool,
pub restarts: u32,
pub version: Option<String>,
pub binary_version: String,
pub log_path: Option<String>,
pub last_request: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Health {
Healthy,
Degraded,
Stopped,
}
impl Health {
pub fn exit_code(self) -> i32 {
match self {
Health::Healthy => 0,
Health::Stopped => 1,
Health::Degraded => 2,
}
}
pub fn label(self) -> &'static str {
match self {
Health::Healthy => "healthy",
Health::Stopped => "stopped",
Health::Degraded => "degraded",
}
}
}
pub fn health(d: &DaemonView) -> Health {
if d.running {
if d.pid == 0 {
Health::Degraded
} else if d.port_accepting && d.env_port == Some(d.port) && d.ca_present {
Health::Healthy
} else {
Health::Degraded
}
} else if d.env_port.is_some() {
Health::Degraded
} else {
Health::Stopped
}
}
const BENCH_OUTPUT_REDUCTION: f64 = 0.73;
fn projected_saved_usd(saved: f64, out_spend_shaped: f64) -> f64 {
saved + out_spend_shaped * BENCH_OUTPUT_REDUCTION / (1.0 - BENCH_OUTPUT_REDUCTION)
}
fn projected_round_trip_pct(saved: f64, spend: f64, out_spend_shaped: f64) -> f64 {
let projected = projected_saved_usd(saved, out_spend_shaped);
let baseline = spend + projected;
if baseline > 0.0 {
projected / baseline * 100.0
} else {
0.0
}
}
#[derive(Clone, Copy)]
pub struct Cost {
pub saved: f64,
pub spend: f64,
pub net_spend: f64,
pub out_spend: f64,
pub net_saved: f64,
pub out_spend_shaped: f64,
pub live_saved: f64,
}
impl Cost {
fn pct(&self) -> f64 {
let total = self.saved + self.spend;
if total > 0.0 {
self.saved / total * 100.0
} else {
0.0
}
}
fn projected_saved(&self) -> f64 {
projected_saved_usd(self.saved, self.out_spend_shaped)
}
fn projected_pct(&self) -> f64 {
projected_round_trip_pct(self.saved, self.spend, self.out_spend_shaped)
}
}
pub struct ModelView {
pub name: String,
pub events: i64,
pub saved_pct: f64,
pub cost_saved: Option<f64>,
pub spend: Option<f64>,
pub out_spend: Option<f64>,
pub cached: bool,
pub new_pct: Option<f64>,
}
pub struct ModelLine {
pub label: String,
pub requests: String,
pub saved: String,
pub cost: String,
pub is_header: bool,
}
pub fn model_lines(models: &[ModelView]) -> Vec<ModelLine> {
let grouped = models.iter().any(|m| m.cached) && models.iter().any(|m| !m.cached);
let mut out = Vec::new();
for (cached, header) in [(false, "fresh context"), (true, "reused context")] {
let rows: Vec<&ModelView> = models.iter().filter(|m| m.cached == cached).collect();
if rows.is_empty() {
continue;
}
if grouped {
out.push(ModelLine {
label: header.to_string(),
requests: String::new(),
saved: String::new(),
cost: String::new(),
is_header: true,
});
}
for m in rows {
out.push(ModelLine {
label: m.name.clone(),
requests: ui::commas(m.events),
saved: format!("{:.0}%", m.saved_pct),
cost: m.cost_saved.map(|c| format!("${c:.2}")).unwrap_or_default(),
is_header: false,
});
}
}
out
}
fn bar(color: bool, saved: f64, width: usize) -> String {
let cut = ((saved.clamp(0.0, 100.0) / 100.0) * width as f64).round() as usize;
let kept = width.saturating_sub(cut);
format!(
"{}{}",
ui::paint(color, Tone::Accent, &"█".repeat(cut)),
ui::paint(color, Tone::Dim, &"░".repeat(kept)),
)
}
const SHEAR_FIELD_W: usize = 20;
fn pad_field(styled: &str) -> String {
let pad = " ".repeat(SHEAR_FIELD_W.saturating_sub(ui::visible_width(styled)));
format!("{styled}{pad}")
}
fn shear_field(color: bool, before: i64, after: i64) -> String {
pad_field(&format!(
"{} {} {}",
ui::paint(color, Tone::Dim, &ui::human(before)),
ui::paint(color, Tone::Dim, "─✂─▶"),
ui::paint(color, Tone::Bold, &ui::human(after)),
))
}
fn axis(color: bool, name: &str, before: i64, after: i64) -> String {
let pct = ui::saved_pct(before as f64, after as f64);
let pct_str = format!("{:>5}", format!("{:+.0}%", -pct)); let pct_tone = if pct >= 0.0 { Tone::Accent } else { Tone::Warn };
format!(
" {:<7} {} {} {}",
name,
shear_field(color, before, after),
bar(color, pct, 18),
ui::paint(color, pct_tone, &pct_str),
)
}
fn render_header(color: bool, d: &DaemonView) -> String {
let mut o = String::new();
if d.running {
let unmanaged = d.pid == 0;
let mut meta = if unmanaged {
format!(":{}", d.port)
} else {
format!(":{} · up {}", d.port, d.uptime)
};
if let Some(v) = &d.version {
meta.push_str(&format!(" · v{v}"));
}
if d.autostart {
meta.push_str(" · autostart on");
}
let rc = llmtrim_core::config::RuntimeConfig::get();
if let Some(p) = rc.sub.as_deref() {
meta.push_str(&format!(
" · reroute {p} ({})",
if rc.sub_fallback {
"fallback"
} else {
"always"
}
));
#[cfg(feature = "intercept")]
{
let authed = crate::reroute::SubProvider::parse(p)
.map(|sp| {
crate::reroute::auth::auth_status_json(sp)["logged_in"].as_bool()
== Some(true)
})
.unwrap_or(false);
if !authed {
meta.push_str(" (no auth)");
}
}
}
let badge = match health(d) {
Health::Healthy => ui::paint(color, Tone::Accent, "✓ healthy"),
_ => ui::paint(color, Tone::Warn, "⚠ degraded"),
};
o.push_str(&format!(
" {} {} {} {} {}\n",
ui::wordmark(color),
ui::paint(color, Tone::Accent, "●"),
ui::paint(color, Tone::Dim, "running"),
ui::paint(color, Tone::Dim, &format!("· {meta}")),
badge,
));
let log = d.log_path.as_deref().unwrap_or("~/.llmtrim/serve.log");
if !d.port_accepting {
o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(
" {} port :{} not accepting connections — check log: {log}\n",
ui::WARN,
d.port
),
));
}
match d.env_port {
Some(p) if p != d.port => o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(
" {} env points at :{p} but the daemon listens on :{} — run: llmtrim setup\n",
ui::WARN,
d.port
),
)),
None => o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(
" {} env not wired — traffic bypasses llmtrim; run: llmtrim setup\n",
ui::WARN
),
)),
_ => {}
}
if !d.ca_present {
o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(" {} ca missing — run: llmtrim ca\n", ui::WARN),
));
}
if let Some(v) = &d.version
&& *v != d.binary_version
{
o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(
" {} daemon is v{v}, binary is v{} — restart to update: llmtrim start --force\n",
ui::WARN, d.binary_version
),
));
}
if d.restarts > 0 {
o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(
" {} crashed and restarted {}× since start — check log: {log}\n",
ui::WARN,
d.restarts
),
));
}
if unmanaged {
o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(
" {} no pidfile — a proxy is answering on :{}, but llmtrim can't confirm it owns it (pidfile lost to a full disk, or a foreign listener); re-run: llmtrim setup\n",
ui::WARN,
d.port
),
));
}
} else if let Some(p) = d.env_port {
o.push_str(&format!(
" {} {}\n",
ui::paint(color, Tone::Warn, "llmtrim ○ stopped"),
ui::paint(
color,
Tone::Warn,
&format!("— env still points at :{p}; LLM calls will fail until it runs")
),
));
o.push_str(&ui::paint(
color,
Tone::Dim,
" fix: llmtrim start (or llmtrim uninstall to unwire the env)\n",
));
} else {
o.push_str(&format!(
" {} {}\n",
ui::paint(color, Tone::Dim, "llmtrim ○"),
ui::paint(color, Tone::Dim, "stopped — start: llmtrim setup"),
));
if !d.ca_present {
o.push_str(&ui::paint(
color,
Tone::Warn,
&format!(" {} ca missing — run: llmtrim ca\n", ui::WARN),
));
}
}
o
}
#[allow(clippy::too_many_arguments)]
pub fn snapshot(
color: bool,
daemon: Option<&DaemonView>,
s: &Summary,
models: &[ModelView],
hero_saved_usd: Option<f64>,
today_saved_usd: Option<f64>,
trend: &[i64],
cache_included: bool,
split_marker: bool,
) -> String {
let mut o = String::new();
if let Some(d) = daemon {
o.push_str(&render_header(color, d));
}
if s.events == 0 {
let hint = match daemon {
Some(d) if d.running && d.env_port != Some(d.port) => {
"\n proxy is up but nothing routes through it — see the env warning above; \
run `llmtrim setup`, then open a new terminal.\n"
}
Some(d) if d.running => {
"\n wired and waiting for the first request — use your tools as normal.\n"
}
_ => "\n no activity yet — run `llmtrim setup`, then use your tools as normal.\n",
};
o.push_str(&ui::paint(color, Tone::Dim, hint));
return o;
}
o.push('\n');
let (line1, line2) = match hero_saved_usd {
Some(saved) => {
let today = today_saved_usd
.filter(|t| *t >= 0.005)
.map(|t| {
format!(
" {} {}",
ui::paint(color, Tone::Dim, "·"),
ui::paint(color, Tone::Accent, &format!("${t:.2} saved today")),
)
})
.unwrap_or_default();
(
format!(
"{} {}{}",
ui::hero(color, &format!("${saved:.2}")),
ui::paint(color, Tone::Dim, "saved on proxy bills"),
today,
),
format!(
"{} tokens trimmed {} {} requests",
ui::human(s.saved()),
ui::paint(color, Tone::Dim, "·"),
ui::commas(s.events),
),
)
}
None => (
format!(
"{} {}",
ui::hero(color, &format!("-{:.0}%", s.saved_pct())),
ui::paint(color, Tone::Dim, "input tokens trimmed"),
),
format!("{} requests", ui::commas(s.events)),
),
};
o.push_str(&ui::boxed(
color,
&[line1, ui::paint(color, Tone::Dim, &line2)],
));
o.push_str(&format!(" {}\n", ui::ok(color, ui::TAGLINE)));
o.push_str(&ui::paint(color, Tone::Dim, "\n SAVINGS\n"));
let new_before = s.metered_input_before - s.frozen_input_tokens;
let new_after = s.metered_input_after - s.frozen_input_tokens;
if cache_included {
o.push_str(&axis(color, "input", s.input_before, s.input_after));
o.push_str(&ui::paint(color, Tone::Dim, " (all input · cache)"));
o.push('\n');
} else if s.frozen_input_tokens > 0 && new_before > 0 {
o.push_str(&axis(color, "input", new_before, new_after));
o.push_str(&ui::paint(color, Tone::Dim, " (cache excluded)"));
o.push('\n');
} else {
o.push_str(&axis(color, "input", s.input_before, s.input_after));
o.push('\n');
}
if s.output_events > 0 {
let billed = pad_field(&format!(
"{} {}",
ui::paint(color, Tone::Bold, &ui::human(s.output_after)),
ui::paint(color, Tone::Dim, "billed"),
));
o.push_str(&format!(
" {:<7} {} {} {} {}\n",
"output",
billed,
bar(color, BENCH_OUTPUT_REDUCTION * 100.0, 18),
ui::paint(
color,
Tone::Accent,
&format!(
"{:>5}",
format!("~{:+.0}%", -BENCH_OUTPUT_REDUCTION * 100.0)
),
),
ui::paint(color, Tone::Dim, "(estimate)"),
));
}
if trend.len() >= 2 {
o.push_str(&ui::paint(
color,
Tone::Dim,
"\n 7-DAY TREND tokens saved / day\n",
));
let peak = trend.iter().copied().max().unwrap_or(0);
let last = *trend.last().unwrap_or(&0);
let (arrow, word, tone) = match (trend.first(), trend.last()) {
(Some(a), Some(b)) if b > a => ("▲", "up", Tone::Accent),
(Some(a), Some(b)) if b < a => ("▼", "down", Tone::Warn),
_ => ("→", "flat", Tone::Dim),
};
o.push_str(&format!(
" {} {} {} {}\n",
ui::paint(color, Tone::Accent, &ui::sparkline(trend)),
ui::paint(
color,
Tone::Dim,
&format!("peak {} · last {} ·", ui::human(peak), ui::human(last)),
),
ui::paint(color, tone, arrow),
ui::paint(color, Tone::Dim, word),
));
}
if !models.is_empty() {
if split_marker {
o.push('\u{1}'); }
o.push_str(&ui::paint(color, Tone::Dim, "\n BY MODEL\n"));
let mut t = ui::table(color, &["model", "requests", "saved", "$ saved"]);
let grouped = models.iter().any(|m| m.cached) && models.iter().any(|m| !m.cached);
let add_rows = |t: &mut comfy_table::Table, cached: bool| {
if grouped {
let label = if cached {
"reused context"
} else {
"fresh context"
};
t.add_row(vec![comfy_table::Cell::new(ui::paint(
color,
Tone::Dim,
label,
))]);
}
let mut group: Vec<&ModelView> = models.iter().filter(|m| m.cached == cached).collect();
group.sort_by(|a, b| {
b.cost_saved
.unwrap_or(f64::NEG_INFINITY)
.partial_cmp(&a.cost_saved.unwrap_or(f64::NEG_INFINITY))
.unwrap_or(std::cmp::Ordering::Equal)
});
for m in group {
let pct = m.new_pct.unwrap_or(m.saved_pct);
let pct_tone = if pct >= 0.0 { Tone::Accent } else { Tone::Warn };
let name = ui::truncate(&m.name, 28);
let dollars = match m.cost_saved {
Some(c) if c >= 0.005 => ui::paint(color, Tone::Accent, &format!("${c:.2}")),
Some(_) => ui::paint(color, Tone::Dim, "<$0.01"),
None => ui::paint(color, Tone::Dim, "—"),
};
t.add_row(vec![
comfy_table::Cell::new(if grouped { format!(" {name}") } else { name }),
ui::right(ui::commas(m.events)),
ui::right(ui::paint(color, pct_tone, &format!("{:+.0}%", -pct))),
ui::right(dollars),
]);
}
};
add_rows(&mut t, false);
add_rows(&mut t, true);
for line in t.to_string().lines() {
let light = line.replace('╞', "├").replace('╡', "┤").replace('═', "─");
o.push_str(&format!(" {light}\n"));
}
if split_marker {
o.push('\u{1}'); }
}
if let Some(us) = s.avg_compress_micros {
o.push_str(&ui::paint(
color,
Tone::Dim,
&format!(" + ~{:.0} ms/req · compression overhead\n", us / 1000.0),
));
}
o
}
pub fn period_report(color: bool, label: &str, rows: &[PeriodRow]) -> String {
let mut o = format!(
"{} {}\n",
ui::wordmark(color),
ui::paint(color, Tone::Dim, &format!("{label} savings")),
);
if rows.is_empty() {
o.push_str(&ui::paint(color, Tone::Dim, " no activity recorded yet\n"));
return o;
}
let mut t = ui::table(color, &["period", "requests", "input", "saved", "output"]);
for r in rows {
let in_pct = ui::saved_pct(r.input_before as f64, r.input_after as f64);
let out = if r.output_before > 0 {
format!(
"{} ({:+.0}%)",
ui::human(r.output_after),
-ui::saved_pct(r.output_before as f64, r.output_after as f64)
)
} else if r.output_after > 0 {
ui::human(r.output_after)
} else {
"—".to_string()
};
t.add_row(vec![
comfy_table::Cell::new(&r.bucket),
ui::right(ui::commas(r.events)),
ui::right(format!(
"{}→{}",
ui::human(r.input_before),
ui::human(r.input_after)
)),
ui::right(ui::paint(color, Tone::Accent, &format!("{:+.0}%", -in_pct))),
ui::right(out),
]);
}
for line in t.to_string().lines() {
o.push_str(&format!(" {line}\n"));
}
o
}
pub fn money_json(tracker: &Tracker) -> Option<serde_json::Value> {
use llmtrim_ledger::money::{money_coverage, money_totals, today_start_utc};
let c = tracker.connection();
let totals = money_totals(c, None).ok()?;
let today = money_totals(c, Some(&today_start_utc())).ok()?;
let coverage = money_coverage(c).ok()?;
let unavailable = coverage.empty_money_with_traffic() || totals.all_unpriced();
let dollars = |v: f64| {
if unavailable {
serde_json::Value::Null
} else {
json!(v)
}
};
Some(json!({
"source": "breakdown_turns",
"unavailable": unavailable,
"paid_usd": dollars(totals.paid_usd()),
"saved_usd": dollars(totals.saved_usd()),
"would_have_usd": dollars(totals.would_have_usd()),
"saved_today_usd": dollars(today.saved_usd()),
"turns": totals.turns,
"turns_unpriced": totals.turns_unpriced,
"coverage": {
"compressions_events": coverage.compressions_events,
"breakdown_turns": coverage.breakdown_turns,
"ratio": coverage.coverage_ratio,
"note": "heuristic event counts (compressions vs turns), not a per-request join; different retention caps apply",
},
"note": "prefer money over cost.* for spend matching Overview/Sessions; when unavailable is true, dollar fields are null"
}))
}
pub fn hero_money(tracker: &Tracker) -> (Option<f64>, Option<f64>) {
use llmtrim_ledger::money::{money_coverage, money_totals, today_start_utc};
let c = tracker.connection();
let coverage = money_coverage(c).unwrap_or_default();
let totals = money_totals(c, None).unwrap_or_default();
if coverage.empty_money_with_traffic() || totals.turns == 0 || totals.all_unpriced() {
return (None, None);
}
let today = money_totals(c, Some(&today_start_utc())).unwrap_or_default();
let all = (totals.saved_micros > 0 || totals.paid_micros > 0).then_some(totals.saved_usd());
let tod = (today.saved_micros > 0 && today.saved_usd() >= 0.005).then_some(today.saved_usd());
(all, tod)
}
pub fn export_json(
s: &Summary,
models: &[ModelView],
cost: Option<&Cost>,
periods: &[PeriodRow],
daemon: Option<&DaemonView>,
) -> String {
export_json_with_money(s, models, cost, periods, daemon, None)
}
pub fn export_json_with_money(
s: &Summary,
models: &[ModelView],
cost: Option<&Cost>,
periods: &[PeriodRow],
daemon: Option<&DaemonView>,
money: Option<serde_json::Value>,
) -> String {
let reroute = {
let cfg = llmtrim_core::config::RuntimeConfig::get();
match cfg.sub.as_deref() {
Some(p) => json!({
"provider": p,
"mode": if cfg.sub_fallback { "fallback" } else { "always" },
"chain": cfg.sub_chain,
}),
None => serde_json::Value::Null,
}
};
let v = json!({
"daemon": daemon.map(|d| json!({
"running": d.running,
"health": health(d).label(),
"pid": (d.running && d.pid != 0).then_some(d.pid),
"port": d.running.then_some(d.port),
"uptime_secs": (d.running && d.pid != 0).then_some(d.uptime_secs),
"port_accepting": d.port_accepting,
"env_port": d.env_port,
"autostart": d.autostart,
"restarts": d.restarts,
"version": d.version,
"binary_version": d.binary_version,
})),
"reroute": reroute,
"last_request_ts": s.last_ts,
"requests": s.events,
"input": { "before": s.input_before, "after": s.input_after, "saved_pct": s.saved_pct() },
"output": { "before": s.output_before, "after": s.output_after,
"events": s.output_events, "saved_pct": s.output_saved_pct() },
"cost": cost.map(|c| json!({ "saved_usd": c.saved, "spend_usd": c.spend, "round_trip_pct": c.pct(),
"net_saved_usd": c.net_saved, "out_spend_usd": c.out_spend,
"out_spend_shaped_usd": c.out_spend_shaped,
"projected_saved_usd": c.projected_saved(), "projected_round_trip_pct": c.projected_pct(),
"source": "compressions_live_prices" })),
"money": money,
"added_latency_ms": s.avg_compress_micros.map(|us| us / 1000.0),
"cache_read_tokens": s.cache_read_tokens,
"approximate": s.any_approximate,
"by_model": models.iter().map(|m| json!({
"model": m.name, "requests": m.events, "saved_pct": m.saved_pct, "cost_saved_usd": m.cost_saved,
})).collect::<Vec<_>>(),
"by_period": periods.iter().map(|p| json!({
"period": p.bucket, "requests": p.events,
"input_before": p.input_before, "input_after": p.input_after,
"output_before": p.output_before, "output_after": p.output_after,
})).collect::<Vec<_>>(),
});
serde_json::to_string_pretty(&v).unwrap_or_else(|_| "{}".to_string())
}
pub fn export_csv(periods: &[PeriodRow]) -> String {
let mut o =
String::from("period,requests,input_before,input_after,output_before,output_after\n");
for p in periods {
o.push_str(&format!(
"{},{},{},{},{},{}\n",
p.bucket, p.events, p.input_before, p.input_after, p.output_before, p.output_after
));
}
o
}
pub fn stats_json(tracker: &Tracker, daemon: Option<&DaemonView>) -> Result<String> {
let summary = tracker.summary()?;
let models = model_views(tracker)?;
let cost = monitor_cost(tracker);
let periods = tracker.by_period(Period::Day)?;
Ok(export_json_with_money(
&summary,
&models,
cost.as_ref(),
&periods,
daemon,
money_json(tracker),
))
}
pub fn monitor_cost(tracker: &Tracker) -> Option<Cost> {
let models = tracker.by_model().ok()?;
cost_estimate(&models)
}
pub fn model_views(tracker: &Tracker) -> Result<Vec<ModelView>> {
Ok(model_views_from(&tracker.by_model()?))
}
pub fn model_views_from(rows: &[ModelRow]) -> Vec<ModelView> {
let mut models: Vec<ModelView> = rows
.iter()
.filter(|m| m.events > 0)
.map(|m| {
let priced = m.model.as_deref().and_then(llm_prices);
let new_before = m.metered_input_before - m.frozen_input_tokens;
let new_pct = (m.frozen_input_tokens > 0 && new_before > 0).then(|| {
ui::saved_pct(
new_before as f64,
(m.metered_input_after - m.frozen_input_tokens) as f64,
)
});
let cost_saved = priced
.map(|(inp, _)| (m.input_before - m.input_after).max(0) as f64 / 1_000_000.0 * inp);
let out_spend = priced.map(|(_, outp)| m.output_after as f64 / 1_000_000.0 * outp);
let spend = priced.map(|(inp, outp)| {
m.input_after as f64 / 1_000_000.0 * inp
+ m.output_after as f64 / 1_000_000.0 * outp
});
ModelView {
name: m
.model
.clone()
.unwrap_or_else(|| format!("{} · unknown model", m.provider)),
events: m.events,
saved_pct: ui::saved_pct(m.input_before as f64, m.input_after as f64),
cost_saved,
spend,
out_spend,
cached: m.cache_read > 0,
new_pct,
}
})
.collect();
models.sort_unstable_by_key(|b| std::cmp::Reverse(b.events));
models.truncate(8);
models
}
pub fn today_saved_usd(tracker: &Tracker) -> Option<f64> {
use llmtrim_ledger::money::{money_totals, today_start_utc};
let t = money_totals(tracker.connection(), Some(&today_start_utc())).ok()?;
(t.saved_micros > 0).then_some(t.saved_usd())
}
#[cfg(feature = "intercept")]
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct BreakdownRates {
pub input: f64,
pub output: f64,
pub cache_read: f64,
pub cache_write: f64,
}
#[cfg(feature = "intercept")]
pub(crate) fn rates_for(provider: &str, model: Option<&str>) -> BreakdownRates {
let (input, output) = model.and_then(llm_prices).unwrap_or((0.0, 0.0));
let (read_mult, write_mult) = cache_multipliers(provider);
BreakdownRates {
input,
output,
cache_read: input * read_mult,
cache_write: input * write_mult,
}
}
fn cache_multipliers(provider: &str) -> (f64, f64) {
match provider {
"anthropic" => (0.10, 1.25),
"openai" => (0.50, 0.0),
"google" => (0.25, 0.0),
_ => (1.0, 0.0),
}
}
pub fn cost_estimate(models: &[ModelRow]) -> Option<Cost> {
let mut cost = Cost {
saved: 0.0,
spend: 0.0,
net_spend: 0.0,
out_spend: 0.0,
net_saved: 0.0,
out_spend_shaped: 0.0,
live_saved: 0.0,
};
let mut matched = false;
for m in models {
let Some(model_id) = m.model.as_deref() else {
continue;
};
if let Some((input_price, output_price)) = llm_prices(model_id) {
let delta = (m.input_before - m.input_after).max(0) as f64;
cost.saved += delta / 1_000_000.0 * input_price;
let out = m.output_after as f64 / 1_000_000.0 * output_price;
cost.spend += m.input_after as f64 / 1_000_000.0 * input_price + out;
cost.out_spend += out;
cost.out_spend_shaped += m.output_after_shaped as f64 / 1_000_000.0 * output_price;
let (read_mult, write_mult) = cache_multipliers(&m.provider);
let net_bill = (m.fresh_input_est as f64
+ m.cache_write as f64 * write_mult
+ m.cache_read as f64 * read_mult)
/ 1_000_000.0
* input_price;
cost.net_spend += net_bill + out;
let pct = if m.input_before > 0 {
(delta / m.input_before as f64).min(0.95)
} else {
0.0
};
cost.net_saved += net_bill * pct / (1.0 - pct);
let live_used = m.fresh_input_est + m.cache_write;
let live_rate = if live_used > 0 {
(m.fresh_input_est as f64 + m.cache_write as f64 * write_mult.max(1.0))
/ live_used as f64
} else {
1.0
};
cost.live_saved += delta / 1_000_000.0 * input_price * live_rate;
matched = true;
}
}
matched.then_some(cost)
}
#[cfg(feature = "breakdown")]
pub fn overview_data(
tracker: &Tracker,
status: impl FnOnce(&crate::tracking::Summary, bool) -> crate::breakdown::app::StatusLine,
) -> crate::breakdown::app::OverviewData {
use crate::breakdown::app::OverviewData;
use llmtrim_ledger::money::{
money_by_day, money_by_model, money_coverage, money_totals, pad_daily_saved,
today_start_utc,
};
let summary = tracker.summary().unwrap_or_default();
let has_traffic = summary.events > 0;
let status = status(&summary, has_traffic);
let conn = tracker.connection();
let coverage = money_coverage(conn).unwrap_or_default();
let totals = money_totals(conn, None).unwrap_or_default();
let today = money_totals(conn, Some(&today_start_utc())).unwrap_or_default();
let money_unavailable = coverage.empty_money_with_traffic()
|| (totals.turns == 0 && coverage.compressions_events > 0)
|| totals.all_unpriced();
let show_money = totals.turns > 0 && !money_unavailable;
let paid_usd = show_money.then_some(totals.paid_usd());
let saved_usd = show_money.then_some(totals.saved_usd());
let would_have_usd = show_money.then_some(totals.would_have_usd());
let saved_today_usd = (show_money && today.saved_micros > 0 && today.saved_usd() >= 0.005)
.then_some(today.saved_usd());
let by_day = money_by_day(conn, 7).unwrap_or_default();
let trend_daily_usd = pad_daily_saved(&by_day, 7);
let savers: Vec<(String, f64)> = money_by_model(conn, 8)
.unwrap_or_default()
.into_iter()
.filter(|m| m.saved_micros > 0)
.map(|m| (m.model, m.saved_micros as f64 / 1_000_000.0))
.collect();
let new_before = summary.metered_input_before - summary.frozen_input_tokens;
let new_after = summary.metered_input_after - summary.frozen_input_tokens;
let pct_less = if summary.frozen_input_tokens > 0 && new_before > 0 {
(new_before - new_after).max(0) as f64 / new_before as f64
} else if summary.input_before > 0 {
summary.saved() as f64 / summary.input_before as f64
} else {
0.0
};
let pct_less_whole = if summary.frozen_input_tokens > 0 && new_before > 0 {
(new_before - new_after).max(0) as f64 / summary.metered_input_before as f64
} else if summary.input_before > 0 {
summary.saved() as f64 / summary.input_before as f64
} else {
0.0
};
OverviewData {
status,
paid_usd,
would_have_usd,
saved_usd,
saved_today_usd,
pct_less,
pct_less_whole,
added_ms: summary.avg_compress_micros.map(|us| us / 1000.0),
requests: summary.events,
trend_daily_usd,
savers,
input_before: summary.input_before,
input_after: summary.input_after,
output_billed: summary.output_after,
output_est_pct: BENCH_OUTPUT_REDUCTION * 100.0,
has_traffic,
approximate: summary.any_approximate,
sessions: 0,
update_available: crate::update::check(false),
}
}
pub(crate) fn llm_prices(model_id: &str) -> Option<(f64, f64)> {
#[cfg(feature = "intercept")]
for &provider_id in llm_providers::get_providers_data().keys() {
if let Some(model) = llm_providers::get_model_ref(provider_id, model_id) {
return Some((model.input_price, model.output_price));
}
}
snapshot_prices(model_id)
}
fn snapshot_prices(model_id: &str) -> Option<(f64, f64)> {
use crate::bench;
static TABLE: once_cell::sync::Lazy<bench::PriceTable> =
once_cell::sync::Lazy::new(|| bench::load_pricing(include_str!("../bench/pricing.json")));
let p = TABLE.get(model_id).or_else(|| {
let (_, bare) = model_id.split_once('/')?;
TABLE.get(bare)
})?;
let (input, output) = (p.input_per_1k * 1000.0, p.output_per_1k * 1000.0);
(input > 0.0 || output > 0.0).then_some((input, output))
}
#[cfg(test)]
mod tests {
use super::*;
fn summ() -> Summary {
Summary {
events: 1204,
input_before: 2_100_000,
input_after: 1_240_000,
any_approximate: false,
by_provider: vec![],
output_before: 880_000,
output_after: 229_000,
output_events: 1204,
avg_compress_micros: Some(310.0),
cache_read_tokens: 1_200_000,
last_ts: Some("2026-06-10T12:00:00+00:00".into()),
frozen_input_tokens: 0,
metered_input_before: 0,
metered_input_after: 0,
}
}
fn priced() -> Cost {
Cost {
saved: 10.0,
spend: 10.0,
net_spend: 0.0,
out_spend: 0.0,
net_saved: 10.0,
live_saved: 10.0,
out_spend_shaped: 0.0,
}
}
#[test]
fn model_lines_group_cached_and_uncached() {
let mv = |name: &str, cached: bool| ModelView {
name: name.into(),
events: 10,
saved_pct: 50.0,
cost_saved: Some(1.0),
spend: None,
out_spend: None,
cached,
new_pct: None,
};
let lines = model_lines(&[mv("a", false), mv("b", true)]);
assert!(
lines
.iter()
.any(|l| l.is_header && l.label == "fresh context")
);
assert!(
lines
.iter()
.any(|l| l.is_header && l.label == "reused context")
);
assert_eq!(lines.iter().filter(|l| !l.is_header).count(), 2);
assert!(model_lines(&[mv("a", false)]).iter().all(|l| !l.is_header));
}
#[test]
fn cache_toggle_switches_input_axis_basis() {
let mut s = summ();
s.input_before = 1_000_000;
s.input_after = 933_000;
s.frozen_input_tokens = 900_000;
s.metered_input_before = 1_000_000;
s.metered_input_after = 933_000;
let c = priced();
let excluded = snapshot(
false,
None,
&s,
&[],
Some(c.net_saved),
None,
&[],
false,
false,
);
assert!(excluded.contains("(cache excluded)"));
assert!(excluded.contains("-67%"), "cache-excluded axis: {excluded}");
let included = snapshot(
false,
None,
&s,
&[],
Some(c.net_saved),
None,
&[],
true,
false,
);
assert!(included.contains("(all input · cache)"));
assert!(included.contains("-7%"), "cache-included axis: {included}");
}
#[test]
fn trend_section_renders_sparkline_and_direction() {
let c = priced();
let up = snapshot(
false,
None,
&summ(),
&[],
Some(c.net_saved),
None,
&[0, 20, 80],
false,
false,
);
assert!(up.contains("7-DAY TREND"));
assert!(
up.contains('▁') && up.contains('█'),
"sparkline scaled: {up}"
);
assert!(up.contains("peak 80") && up.contains("last 80"));
assert!(up.contains('▲') && up.contains("up"), "rising → up: {up}");
let down = snapshot(
false,
None,
&summ(),
&[],
Some(c.net_saved),
None,
&[80, 10],
false,
false,
);
assert!(
down.contains('▼') && down.contains("down"),
"falling → down: {down}"
);
let one = snapshot(
false,
None,
&summ(),
&[],
Some(c.net_saved),
None,
&[5],
false,
false,
);
assert!(
!one.contains("7-DAY TREND"),
"single bucket hides trend: {one}"
);
}
#[test]
fn by_model_unpriced_row_shows_dash_placeholder() {
let models = vec![ModelView {
name: "mystery-model".into(),
events: 7,
saved_pct: 20.0,
cost_saved: None, spend: None,
out_spend: None,
cached: false,
new_pct: None,
}];
let out = snapshot(
false,
None,
&summ(),
&models,
Some(priced().net_saved),
None,
&[],
false,
false,
);
assert!(
out.contains("mystery-model") && out.contains('—'),
"unpriced $ saved shows a dash: {out}"
);
}
#[test]
fn bar_clamps_and_fills() {
assert_eq!(bar(false, 50.0, 10), "█████░░░░░"); assert_eq!(bar(false, 0.0, 4), "░░░░"); assert_eq!(bar(false, 100.0, 4), "████"); assert_eq!(bar(false, 150.0, 4), "████"); assert_eq!(bar(false, -20.0, 4), "░░░░"); }
#[test]
fn snapshot_plain_has_hero_and_axes() {
let cost = Cost {
saved: 12.47,
spend: 9.0,
net_spend: 0.0,
out_spend: 3.0,
net_saved: 12.47, live_saved: 12.47,
out_spend_shaped: 3.0, };
let models = vec![ModelView {
name: "gpt-4o".into(),
events: 420,
saved_pct: 61.0,
cost_saved: Some(4.10),
spend: Some(6.0),
out_spend: Some(0.0),
cached: false,
new_pct: None,
}];
let out = snapshot(
false,
None,
&summ(),
&models,
Some(cost.net_saved),
None,
&[],
false,
false,
);
assert!(
out.contains("tokens trimmed") && out.contains("$12.47 saved on proxy bills"),
"hero shows measured trimmed tokens + real-bill saving"
);
assert!(out.contains("1,204 requests"), "request count");
assert!(
out.contains("✓ same answers, smaller bill"),
"logo value promise stamped under the hero"
);
assert!(
out.contains("input") && out.contains("2.1M ─✂─▶ 1.2M"),
"input axis"
);
assert!(out.contains("gpt-4o") && out.contains("$4.10"), "model row");
assert!(
out.contains("(estimate)"),
"output projection clearly labeled as estimate"
);
assert!(out.contains("ms/req"), "added-latency line");
assert!(!out.contains('\x1b'), "no ANSI when color=false");
}
#[test]
fn headline_excludes_output_projection() {
let cost = Cost {
saved: 10.0,
spend: 10.0,
net_spend: 0.0,
out_spend: 5.0,
net_saved: 10.0,
live_saved: 10.0,
out_spend_shaped: 5.0,
};
let out = snapshot(
false,
None,
&summ(),
&[],
Some(cost.net_saved),
None,
&[],
false,
false,
);
assert!(
out.contains("$10.00 saved on proxy bills"),
"headline = measured net saving"
);
assert!(
!out.contains(&format!(
"${:.2} saved on proxy bills",
cost.projected_saved()
)),
"projected total ({:.2}) is not the headline",
cost.projected_saved()
);
}
#[test]
fn projects_output_saving_from_benchmark() {
let c = Cost {
saved: 1.0,
spend: 1.0,
net_spend: 0.0,
out_spend: 0.27,
net_saved: 1.0,
live_saved: 1.0,
out_spend_shaped: 0.27, };
assert!((c.projected_saved() - 1.73).abs() < 1e-9);
assert!((c.projected_pct() - 1.73 / 2.73 * 100.0).abs() < 1e-9);
}
#[test]
fn unshaped_output_gets_no_projection() {
let c = Cost {
saved: 10.0,
spend: 10.0,
net_spend: 0.0,
out_spend: 5.0,
net_saved: 10.0,
live_saved: 10.0,
out_spend_shaped: 0.0,
};
assert!((c.projected_saved() - c.saved).abs() < 1e-9);
let out = snapshot(
false,
None,
&summ(),
&[],
Some(c.net_saved),
None,
&[],
false,
false,
);
assert!(!out.contains("more saved by output shaping"));
}
#[test]
fn hero_is_the_measured_real_bill_only() {
let c = Cost {
saved: 100.0,
spend: 50.0,
net_spend: 0.0,
out_spend: 5.0,
net_saved: 25.0,
live_saved: 130.0,
out_spend_shaped: 0.0,
};
let out = snapshot(
false,
None,
&summ(),
&[],
Some(c.net_saved),
None,
&[],
false,
false,
);
assert!(
out.contains("$25.00 saved on proxy bills"),
"hero is the measured real-bill figure: {out}"
);
assert!(
!out.contains("at list") && !out.contains("$100.00") && !out.contains("$130.00"),
"no list-rate / live-zone upside line: {out}"
);
}
#[test]
fn new_content_axis_and_measured_hero() {
let mut s = summ();
s.frozen_input_tokens = 1_000_000;
s.metered_input_before = 1_500_000;
s.metered_input_after = 1_100_000;
let c = Cost {
saved: 100.0,
spend: 50.0,
net_spend: 0.0,
out_spend: 5.0,
net_saved: 25.0,
out_spend_shaped: 0.0,
live_saved: 130.0,
};
let out = snapshot(
false,
None,
&s,
&[],
Some(c.net_saved),
None,
&[],
false,
false,
);
assert!(
out.contains("$25.00 saved on proxy bills"),
"hero is the measured real-bill figure: {out}"
);
assert!(
!out.contains("at list") && !out.contains("$130.00"),
"no list-rate / live-zone upside line: {out}"
);
assert!(
out.contains("500.0K ─✂─▶ 100.0K") && out.contains("(cache excluded)"),
"input axis over the compressible surface: {out}"
);
assert!(
!out.contains("2.1M ─✂─▶ 1.2M"),
"diluted all-input axis replaced, not shown alongside: {out}"
);
let out = snapshot(
false,
None,
&summ(),
&[],
Some(c.net_saved),
None,
&[],
false,
false,
);
assert!(!out.contains("cache excluded"));
assert!(out.contains("2.1M ─✂─▶ 1.2M"), "fallback axis: {out}");
}
#[test]
fn snapshot_empty_ledger_guides_user() {
let out = snapshot(
false,
None,
&Summary::default(),
&[],
None,
None,
&[],
false,
false,
);
assert!(out.contains("no activity yet"));
}
#[test]
fn export_json_roundtrips() {
let out = export_json(&summ(), &[], None, &[], None);
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["requests"], 1204);
assert_eq!(v["cost"], serde_json::Value::Null);
}
#[test]
fn stats_json_reads_the_ledger() {
use crate::tracking::Record;
let tracker = Tracker::open_in_memory().unwrap();
for _ in 0..2 {
tracker
.record(&Record {
provider: "openai".into(),
model: Some("gpt-4o".into()),
tokenizer: "tiktoken".into(),
exact: true,
input_before: 1000,
input_after: 600,
output_before: None,
output_after: None,
compress_micros: None,
cache_read_tokens: None,
fresh_input_tokens: None,
cache_write_tokens: None,
output_shaped: Some(false),
frozen_input_tokens: Some(0),
outcome: None,
})
.unwrap();
}
let out = stats_json(&tracker, None).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["requests"], 2);
assert_eq!(v["daemon"], serde_json::Value::Null);
assert!(v["by_model"].as_array().is_some_and(|m| !m.is_empty()));
}
#[test]
fn overview_money_matches_sum_of_session_bills() {
use crate::tracking::{BreakdownTurn, Tracker};
use llmtrim_ledger::money::money_totals;
let tracker = Tracker::open_in_memory().unwrap();
let rates = (3.0, 15.0, 0.3, 3.75);
let bill1 = (50_000.0_f64 * 3.0 + 100.0 * 15.0).round() as i64;
let bill2 = (20_000.0_f64 * 3.0).round() as i64;
let turn = |session: &str, bill: i64, before: i64, after: i64, fresh: i64, out: i64| {
BreakdownTurn {
session_id: session.into(),
cc_session_id: None,
agent: "claude-code".into(),
project: Some("/p".into()),
session_name: None,
provider: "anthropic".into(),
model: Some("claude-sonnet-4".into()),
sub_provider: None,
window: 200_000,
fresh_input: fresh,
cache_read: 0,
cache_write: 0,
output_tok: out,
input_rate: rates.0,
output_rate: rates.1,
cache_read_rate: rates.2,
cache_write_rate: rates.3,
bill_micros: bill,
input_before: before,
input_after: after,
}
};
tracker
.record_breakdown(&turn("s1", bill1, 100_000, 50_000, 50_000, 100), &[])
.unwrap();
tracker
.record_breakdown(&turn("s2", bill2, 40_000, 20_000, 20_000, 0), &[])
.unwrap();
let money = money_totals(tracker.connection(), None).unwrap();
assert_eq!(money.paid_micros, bill1 + bill2);
assert_eq!(
money.would_have_micros,
money.paid_micros + money.saved_micros
);
let ov = overview_data(&tracker, |_, _| {
crate::breakdown::app::StatusLine::default()
});
assert!(!ov.money_unavailable());
assert_eq!(ov.paid_usd, Some(money.paid_usd()));
assert_eq!(ov.saved_usd, Some(money.saved_usd()));
assert_eq!(ov.would_have_usd, Some(money.would_have_usd()));
let v: serde_json::Value =
serde_json::from_str(&stats_json(&tracker, None).unwrap()).unwrap();
assert_eq!(v["money"]["source"], "breakdown_turns");
assert_eq!(v["money"]["unavailable"], false);
assert_eq!(v["money"]["paid_usd"].as_f64().unwrap(), money.paid_usd());
let t2 = Tracker::open_in_memory().unwrap();
t2.record(&crate::tracking::Record {
provider: "openai".into(),
model: Some("gpt-4o".into()),
tokenizer: "tiktoken".into(),
exact: true,
input_before: 1000,
input_after: 600,
output_before: None,
output_after: None,
compress_micros: None,
cache_read_tokens: None,
fresh_input_tokens: None,
cache_write_tokens: None,
output_shaped: Some(false),
frozen_input_tokens: Some(0),
outcome: None,
})
.unwrap();
let v2: serde_json::Value = serde_json::from_str(&stats_json(&t2, None).unwrap()).unwrap();
assert_eq!(v2["money"]["unavailable"], true);
assert!(v2["money"]["paid_usd"].is_null());
let (hero, today) = hero_money(&t2);
assert!(hero.is_none() && today.is_none());
let ov2 = overview_data(&t2, |_, _| crate::breakdown::app::StatusLine::default());
assert!(ov2.money_unavailable());
assert!(ov2.paid_usd.is_none());
}
#[test]
fn overview_data_reports_both_savings_bases() {
use crate::tracking::Record;
let tracker = Tracker::open_in_memory().unwrap();
tracker
.record(&Record {
provider: "openai".into(),
model: Some("gpt-4o".into()),
tokenizer: "tiktoken".into(),
exact: true,
input_before: 1000,
input_after: 600,
output_before: None,
output_after: None,
compress_micros: None,
cache_read_tokens: None,
fresh_input_tokens: None,
cache_write_tokens: None,
output_shaped: Some(false),
frozen_input_tokens: Some(600),
outcome: None,
})
.unwrap();
let ov = overview_data(&tracker, |_, _| {
crate::breakdown::app::StatusLine::default()
});
assert!(
(ov.pct_less_whole - 0.40).abs() < 1e-6,
"{}",
ov.pct_less_whole
);
assert!(
ov.pct_less >= ov.pct_less_whole,
"{} {}",
ov.pct_less,
ov.pct_less_whole
);
}
#[test]
fn whole_prompt_basis_never_exceeds_new_content_on_a_mixed_ledger() {
use crate::tracking::Record;
let rec = |before, after, frozen| Record {
provider: "openai".into(),
model: Some("gpt-4o".into()),
tokenizer: "tiktoken".into(),
exact: true,
input_before: before,
input_after: after,
output_before: None,
output_after: None,
compress_micros: None,
cache_read_tokens: None,
fresh_input_tokens: None,
cache_write_tokens: None,
output_shaped: Some(false),
frozen_input_tokens: frozen,
outcome: None,
};
let tracker = Tracker::open_in_memory().unwrap();
tracker.record(&rec(400, 400, Some(100))).unwrap();
tracker.record(&rec(600, 200, None)).unwrap();
let ov = overview_data(&tracker, |_, _| {
crate::breakdown::app::StatusLine::default()
});
assert!(ov.pct_less.abs() < 1e-6, "{}", ov.pct_less);
assert!(ov.pct_less_whole.abs() < 1e-6, "{}", ov.pct_less_whole);
assert!(ov.pct_less_whole <= ov.pct_less + 1e-9);
}
#[test]
fn export_csv_has_header_and_rows() {
let rows = vec![PeriodRow {
bucket: "2026-06-07".into(),
events: 10,
input_before: 100,
input_after: 60,
output_before: 0,
output_after: 20,
}];
let out = export_csv(&rows);
assert!(out.starts_with("period,requests,"));
assert!(out.contains("2026-06-07,10,100,60,0,20"));
}
fn dv() -> DaemonView {
DaemonView {
running: true,
pid: 4242,
port: 8788,
uptime: "33m45s".into(),
uptime_secs: 2025,
ca_present: true,
port_accepting: true,
env_port: Some(8788),
autostart: true,
restarts: 0,
version: Some("0.1.0".into()),
binary_version: "0.1.0".into(),
log_path: Some("/home/u/.llmtrim/serve.log".into()),
last_request: Some("4s ago".into()),
}
}
#[test]
fn health_matrix() {
assert_eq!(health(&dv()), Health::Healthy);
for broken in [
DaemonView {
port_accepting: false,
..dv()
},
DaemonView {
env_port: None,
..dv()
},
DaemonView {
env_port: Some(8787),
..dv()
},
DaemonView {
ca_present: false,
..dv()
},
] {
assert_eq!(health(&broken), Health::Degraded);
}
let stopped_wired = DaemonView {
running: false,
..dv()
};
assert_eq!(health(&stopped_wired), Health::Degraded);
let stopped_clean = DaemonView {
running: false,
env_port: None,
..dv()
};
assert_eq!(health(&stopped_clean), Health::Stopped);
assert_eq!(Health::Healthy.exit_code(), 0);
assert_eq!(Health::Stopped.exit_code(), 1);
assert_eq!(Health::Degraded.exit_code(), 2);
}
#[test]
fn unmanaged_live_proxy_is_running_but_degraded() {
let unmanaged = DaemonView {
pid: 0,
uptime: String::new(),
uptime_secs: 0,
version: None,
..dv()
};
assert_eq!(health(&unmanaged), Health::Degraded);
let out = render_header(false, &unmanaged);
assert!(out.contains("running"), "should report running: {out}");
assert!(!out.contains("stopped"), "must not say stopped: {out}");
assert!(
!out.contains("pid 0"),
"must not print the bogus pid 0: {out}"
);
assert!(out.contains("no pidfile"), "should explain the gap: {out}");
assert!(
out.contains("degraded"),
"unmanaged proxy reads as degraded, not healthy: {out}"
);
}
#[test]
fn header_healthy_collapses_to_one_calm_line() {
let out = render_header(false, &dv());
assert!(out.contains("running") && out.contains("✓ healthy"));
assert!(!out.contains("✓ port") && !out.contains("last request"));
assert!(out.contains(":8788") && out.contains("autostart on") && out.contains("v0.1.0"));
assert!(!out.contains('⚠'), "healthy header has no warnings");
}
#[test]
fn header_warns_per_broken_link() {
let out = render_header(
false,
&DaemonView {
port_accepting: false,
..dv()
},
);
assert!(out.contains("not accepting connections"));
assert!(out.contains("serve.log"), "points at the log");
let out = render_header(
false,
&DaemonView {
env_port: None,
..dv()
},
);
assert!(out.contains("env not wired"));
assert!(out.contains("llmtrim setup"));
let out = render_header(
false,
&DaemonView {
env_port: Some(8787),
..dv()
},
);
assert!(
out.contains(":8787") && out.contains(":8788"),
"names both ports"
);
}
#[test]
fn header_stopped_but_wired_is_loud() {
let out = render_header(
false,
&DaemonView {
running: false,
..dv()
},
);
assert!(out.contains("LLM calls will fail"));
assert!(out.contains("llmtrim start"));
let out = render_header(
false,
&DaemonView {
running: false,
env_port: None,
..dv()
},
);
assert!(out.contains("stopped — start: llmtrim setup"));
assert!(!out.contains("will fail"));
}
#[test]
fn header_flags_version_skew_and_restarts() {
let out = render_header(
false,
&DaemonView {
version: Some("0.0.9".into()),
..dv()
},
);
assert!(out.contains("daemon is v0.0.9, binary is v0.1.0"));
assert!(out.contains("llmtrim start --force"));
let out = render_header(
false,
&DaemonView {
restarts: 3,
..dv()
},
);
assert!(out.contains("restarted 3×"));
let out = render_header(
false,
&DaemonView {
version: None,
..dv()
},
);
assert!(!out.contains("restart to update"));
}
#[test]
fn empty_ledger_diagnoses_why() {
let unwired = DaemonView {
env_port: None,
..dv()
};
let out = snapshot(
false,
Some(&unwired),
&Summary::default(),
&[],
None,
None,
&[],
false,
false,
);
assert!(out.contains("nothing routes through it"));
let out = snapshot(
false,
Some(&dv()),
&Summary::default(),
&[],
None,
None,
&[],
false,
false,
);
assert!(out.contains("waiting for the first request"));
let off = DaemonView {
running: false,
env_port: None,
..dv()
};
let out = snapshot(
false,
Some(&off),
&Summary::default(),
&[],
None,
None,
&[],
false,
false,
);
assert!(out.contains("no activity yet"));
}
#[test]
fn hero_shows_today_when_priced() {
let cost = Cost {
saved: 100.0,
spend: 50.0,
net_spend: 0.0,
out_spend: 0.0,
net_saved: 100.0,
live_saved: 100.0,
out_spend_shaped: 0.0,
};
let out = snapshot(
false,
None,
&summ(),
&[],
Some(cost.net_saved),
Some(1.84),
&[],
false,
false,
);
assert!(out.contains("$1.84 saved today"));
let out = snapshot(
false,
None,
&summ(),
&[],
Some(cost.net_saved),
Some(0.0),
&[],
false,
false,
);
assert!(!out.contains("saved today"));
let out = snapshot(
false,
None,
&summ(),
&[],
Some(cost.net_saved),
None,
&[],
false,
false,
);
assert!(!out.contains("saved today"));
}
#[test]
fn export_json_carries_daemon_health() {
let out = export_json(&summ(), &[], None, &[], Some(&dv()));
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["daemon"]["running"], true);
assert_eq!(v["daemon"]["health"], "healthy");
assert_eq!(v["daemon"]["port"], 8788);
assert_eq!(v["daemon"]["env_port"], 8788);
assert_eq!(v["daemon"]["autostart"], true);
assert_eq!(v["daemon"]["restarts"], 0);
assert_eq!(v["last_request_ts"], "2026-06-10T12:00:00+00:00");
let stopped = DaemonView {
running: false,
env_port: None,
..dv()
};
let out = export_json(&summ(), &[], None, &[], Some(&stopped));
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["daemon"]["running"], false);
assert_eq!(v["daemon"]["health"], "stopped");
assert_eq!(v["daemon"]["pid"], serde_json::Value::Null);
}
}