Skip to main content

orch/cli/
cooldown.rs

1//! CLI handlers for `orch cooldown list` and `orch cooldown clear`.
2//!
3//! Cooldowns are persisted to the SQLite KV store so they survive restarts.
4//! The `list` command opens the store, hydrates the in-memory map, then
5//! prints all active entries — accurate even when the service is not running.
6
7use crate::engine::cooldown;
8use std::sync::Arc;
9
10/// List all active agent and model cooldowns.
11pub async fn list() -> anyhow::Result<()> {
12    let store = Arc::new(crate::cli::init_store().await?);
13    // Hydrate in-memory map from persisted KV so we see the full state.
14    cooldown::init_cooldown_store(store).await;
15
16    let entries = cooldown::list_all_cooldowns();
17    if entries.is_empty() {
18        println!("No active cooldowns.");
19        return Ok(());
20    }
21
22    let now = chrono::Utc::now().timestamp();
23    println!("{:<30} {:<12} REASON", "KEY", "REMAINING");
24    println!("{}", "-".repeat(72_usize));
25    for (key, until, reason) in entries {
26        let remaining_secs = until - now;
27        let remaining = format_remaining(remaining_secs);
28        println!("{:<30} {:<12} {}", key, remaining, reason);
29    }
30    Ok(())
31}
32
33/// Clear a specific cooldown by key, or all cooldowns with `--all`.
34pub async fn clear(key: Option<String>, all: bool) -> anyhow::Result<()> {
35    let store = Arc::new(crate::cli::init_store().await?);
36    // Hydrate first so clear operates on the current state.
37    cooldown::init_cooldown_store(store.clone()).await;
38
39    if all {
40        cooldown::clear_cooldown("*", &store).await;
41        println!("Cleared all cooldowns.");
42    } else if let Some(k) = key {
43        cooldown::clear_cooldown(&k, &store).await;
44        println!("Cleared cooldown: {k}");
45    } else {
46        anyhow::bail!("specify a key (e.g. 'claude' or 'claude:sonnet') or use --all");
47    }
48    Ok(())
49}
50
51fn format_remaining(secs: i64) -> String {
52    if secs <= 0 {
53        return "expired".to_string();
54    }
55    if secs < 3600 {
56        format!("{}m", secs / 60)
57    } else if secs < 86400 {
58        let h = secs / 3600;
59        let m = (secs % 3600) / 60;
60        if m == 0 {
61            format!("{h}h")
62        } else {
63            format!("{h}h{m}m")
64        }
65    } else {
66        let d = secs / 86400;
67        let h = (secs % 86400) / 3600;
68        if h == 0 {
69            format!("{d}d")
70        } else {
71            format!("{d}d{h}h")
72        }
73    }
74}