Skip to main content

claudex_cli/commands/
models.rs

1use anyhow::Result;
2
3use crate::cli::ResolvedFilter;
4use crate::ui;
5use claudex::index::IndexStore;
6use claudex::providers::enabled_default;
7use claudex::types::ModelPricing;
8
9pub fn run(project: Option<&str>, json: bool, filter: &ResolvedFilter) -> Result<()> {
10    let providers = enabled_default()?;
11    let mut idx = IndexStore::open()?;
12    idx.ensure_fresh(&providers)?;
13
14    let rows = idx.query_model_usage(project, filter)?;
15
16    if json {
17        let output: Vec<_> = rows
18            .iter()
19            .map(|r| {
20                serde_json::json!({
21                    "model": r.model,
22                    "model_family": ModelPricing::name(Some(&r.model)),
23                    "session_count": r.session_count,
24                    "input_tokens": r.input_tokens,
25                    "output_tokens": r.output_tokens,
26                    "cache_creation_tokens": r.cache_creation_tokens,
27                    "cache_read_tokens": r.cache_read_tokens,
28                    "avg_cost_per_session_usd": r.avg_cost_per_session_usd,
29                    "avg_tokens_per_session": r.avg_tokens_per_session,
30                    "service_tiers": r.service_tiers,
31                    "inference_geos": r.inference_geos,
32                    "avg_speed": r.avg_speed,
33                    "total_iterations": r.total_iterations,
34                    "cost_usd": r.cost_usd,
35                })
36            })
37            .collect();
38        println!("{}", serde_json::to_string_pretty(&output)?);
39        return Ok(());
40    }
41
42    if rows.is_empty() {
43        println!("No model usage data found.");
44        return Ok(());
45    }
46
47    let mut table = ui::table();
48    table.set_header(ui::header([
49        "Model",
50        "Sessions",
51        "Input",
52        "Output",
53        "Cache Write",
54        "Cache Read",
55        "Avg/Session",
56        "Avg Tokens",
57        "Cost (USD)",
58    ]));
59    ui::right_align(&mut table, &[1, 2, 3, 4, 5, 6, 7, 8]);
60    let mut total_sessions = 0i64;
61    let mut total_input = 0i64;
62    let mut total_output = 0i64;
63    let mut total_cache_creation = 0i64;
64    let mut total_cache_read = 0i64;
65    let mut total_cost = 0.0f64;
66    for r in &rows {
67        let family = ModelPricing::name(Some(&r.model));
68        let display = if r.model.is_empty() {
69            family.to_string()
70        } else {
71            format!("{} ({})", family, r.model.trim_start_matches("claude-"))
72        };
73        table.add_row([
74            ui::cell_model(&display),
75            ui::cell_count(r.session_count as u64),
76            ui::cell_count(r.input_tokens as u64),
77            ui::cell_count(r.output_tokens as u64),
78            ui::cell_count(r.cache_creation_tokens as u64),
79            ui::cell_count(r.cache_read_tokens as u64),
80            ui::cell_cost(r.avg_cost_per_session_usd),
81            ui::cell_count(r.avg_tokens_per_session.round() as u64),
82            ui::cell_cost(r.cost_usd),
83        ]);
84        total_sessions += r.session_count;
85        total_input += r.input_tokens;
86        total_output += r.output_tokens;
87        total_cache_creation += r.cache_creation_tokens;
88        total_cache_read += r.cache_read_tokens;
89        total_cost += r.cost_usd;
90    }
91    table.add_row(ui::total_row([
92        "TOTAL".to_string(),
93        ui::fmt_count(total_sessions as u64),
94        ui::fmt_count(total_input as u64),
95        ui::fmt_count(total_output as u64),
96        ui::fmt_count(total_cache_creation as u64),
97        ui::fmt_count(total_cache_read as u64),
98        String::new(),
99        String::new(),
100        ui::fmt_cost(total_cost),
101    ]));
102    println!("{table}");
103    Ok(())
104}