|
| 1 | +//! `burnwall share` — an opt-in, screenshot-friendly, *signed* value card. |
| 2 | +//! |
| 3 | +//! A zero-telemetry tool produces nothing to share automatically — so virality, |
| 4 | +//! if any, has to be earned: the user chooses to post a card. To keep it honest |
| 5 | +//! (no faked numbers), the card's figures are signed with the local audit key |
| 6 | +//! and can be verified against the printed public key. Nothing leaves the |
| 7 | +//! machine; this just renders text the user may copy. |
| 8 | +
|
| 9 | +use std::io::Write; |
| 10 | + |
| 11 | +use anyhow::Context; |
| 12 | +use clap::Args; |
| 13 | + |
| 14 | +use crate::audit::AuditChain; |
| 15 | +use crate::pricing; |
| 16 | +use crate::providers::TokenUsage; |
| 17 | +use crate::storage::{ModelBreakdown, Storage}; |
| 18 | + |
| 19 | +#[derive(Args, Debug)] |
| 20 | +pub struct ShareArgs { |
| 21 | + /// How many days the card summarizes (default 30). |
| 22 | + #[arg(long, default_value_t = 30)] |
| 23 | + pub days: i64, |
| 24 | + /// Skip signing (no audit key needed) — emits an unsigned card. |
| 25 | + #[arg(long)] |
| 26 | + pub no_sign: bool, |
| 27 | +} |
| 28 | + |
| 29 | +pub fn run_cmd(args: ShareArgs) -> anyhow::Result<()> { |
| 30 | + let storage = Storage::open_default().context("opening storage")?; |
| 31 | + let rows = storage.breakdown_since_days(args.days)?; |
| 32 | + let (spent, saved) = spend_and_savings(&rows); |
| 33 | + let blocked = storage |
| 34 | + .security_events_since_days(args.days)? |
| 35 | + .len(); |
| 36 | + |
| 37 | + // Canonical, signable payload — the exact numbers shown, so a verifier can |
| 38 | + // confirm the card wasn't doctored. |
| 39 | + let payload = format!( |
| 40 | + "burnwall-card|days={}|spent={:.2}|saved={:.2}|blocked={}", |
| 41 | + args.days, spent, saved, blocked |
| 42 | + ); |
| 43 | + |
| 44 | + let signature = if args.no_sign { |
| 45 | + None |
| 46 | + } else { |
| 47 | + match AuditChain::open_default() { |
| 48 | + Ok(chain) => Some((chain.sign_hex(payload.as_bytes()), chain.public_key_hex())), |
| 49 | + Err(_) => None, |
| 50 | + } |
| 51 | + }; |
| 52 | + |
| 53 | + let mut out = std::io::stdout().lock(); |
| 54 | + let line1 = format!("🔥 Burnwall · last {} days", args.days); |
| 55 | + let line2 = format!("💰 ${:.2} spent · ${:.2} saved by caching", spent, saved); |
| 56 | + let line3 = format!("🛡 {blocked} risky action{} blocked", if blocked == 1 { "" } else { "s" }); |
| 57 | + let width = [line1.len(), line2.len(), line3.len()].into_iter().max().unwrap_or(40) + 2; |
| 58 | + let rule = "─".repeat(width); |
| 59 | + |
| 60 | + writeln!(out, "┌{rule}┐")?; |
| 61 | + writeln!(out, " {line1}")?; |
| 62 | + writeln!(out, " {line2}")?; |
| 63 | + writeln!(out, " {line3}")?; |
| 64 | + match &signature { |
| 65 | + Some((sig, pubkey)) => { |
| 66 | + let sig_short = &sig[..sig.len().min(16)]; |
| 67 | + let key_short = &pubkey[..pubkey.len().min(16)]; |
| 68 | + writeln!(out, " 🔐 signed {sig_short}… · key {key_short}…")?; |
| 69 | + } |
| 70 | + None => writeln!(out, " (unsigned — run `burnwall audit seal` once to enable signing)")?, |
| 71 | + } |
| 72 | + writeln!(out, "└{rule}┘")?; |
| 73 | + if let Some((sig, pubkey)) = &signature { |
| 74 | + writeln!(out)?; |
| 75 | + writeln!(out, "verify: payload \"{payload}\"")?; |
| 76 | + writeln!(out, " sig {sig}")?; |
| 77 | + writeln!(out, " key {pubkey}")?; |
| 78 | + } |
| 79 | + Ok(()) |
| 80 | +} |
| 81 | + |
| 82 | +/// Total real spend and cache-captured savings over the rows (USD), using the |
| 83 | +/// same cache-aware math as `burnwall savings`. |
| 84 | +fn spend_and_savings(rows: &[ModelBreakdown]) -> (f64, f64) { |
| 85 | + let mut real = 0.0; |
| 86 | + let mut without = 0.0; |
| 87 | + for r in rows { |
| 88 | + if let Some(p) = pricing::get_pricing(&r.model) { |
| 89 | + let usage = TokenUsage { |
| 90 | + input_tokens: r.input_tokens, |
| 91 | + output_tokens: r.output_tokens, |
| 92 | + cache_creation_tokens: r.cache_creation_tokens, |
| 93 | + cache_read_tokens: r.cache_read_tokens, |
| 94 | + }; |
| 95 | + real += pricing::cost(&usage, p); |
| 96 | + without += pricing::cost_without_cache(&usage, p); |
| 97 | + } |
| 98 | + } |
| 99 | + (real, (without - real).max(0.0)) |
| 100 | +} |
0 commit comments