atagen

roundabout

public
Star0
Watch0
ecc1cf8df0f5b88c6bf2cd07254c98d13827cd26
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git

Blame: src/cli.rs

6f5b84fad3efatagen2026-09-27 03:59 UTC1
use std::{io, os::unix::net::UnixStream, path::PathBuf};
6f5b84fad3efatagen2026-09-27 03:59 UTC2
6f5b84fad3efatagen2026-09-27 03:59 UTC3
use pound::Parse;
6f5b84fad3efatagen2026-09-27 03:59 UTC4
6f5b84fad3efatagen2026-09-27 03:59 UTC5
use crate::{
6f5b84fad3efatagen2026-09-27 03:59 UTC6
    artifacts, client,
6f5b84fad3efatagen2026-09-27 03:59 UTC7
    config::Config,
6f5b84fad3efatagen2026-09-27 03:59 UTC8
    daemon, history, hook, paths,
6f5b84fad3efatagen2026-09-27 03:59 UTC9
    proto::{self, Event, Reader, Request},
6f5b84fad3efatagen2026-09-27 03:59 UTC10
    run,
6f5b84fad3efatagen2026-09-27 03:59 UTC11
    units::{Amount, Bytes, Dur},
6f5b84fad3efatagen2026-09-27 03:59 UTC12
};
6f5b84fad3efatagen2026-09-27 03:59 UTC13
6f5b84fad3efatagen2026-09-27 03:59 UTC14
/// one build at a time, please
6f5b84fad3efatagen2026-09-27 03:59 UTC15
#[derive(Parse)]
6f5b84fad3efatagen2026-09-27 03:59 UTC16
#[pound(name = "rabt", version = env!("CARGO_PKG_VERSION"))]
6f5b84fad3efatagen2026-09-27 03:59 UTC17
pub enum Rabt {
6f5b84fad3efatagen2026-09-27 03:59 UTC18
    /// wait your turn, then run the thing
6f5b84fad3efatagen2026-09-27 03:59 UTC19
    Run {
6f5b84fad3efatagen2026-09-27 03:59 UTC20
        /// preset from config, s m l xl out of the box
6f5b84fad3efatagen2026-09-27 03:59 UTC21
        #[pound(short, long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC22
        size: Option<String>,
6f5b84fad3efatagen2026-09-27 03:59 UTC23
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC24
        ram: Option<Bytes>,
6f5b84fad3efatagen2026-09-27 03:59 UTC25
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC26
        disk: Option<Bytes>,
6f5b84fad3efatagen2026-09-27 03:59 UTC27
        /// count or percentage of the machine
6f5b84fad3efatagen2026-09-27 03:59 UTC28
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC29
        cores: Option<Amount<u32>>,
6f5b84fad3efatagen2026-09-27 03:59 UTC30
        /// how long you reckon it'll take
6f5b84fad3efatagen2026-09-27 03:59 UTC31
        #[pound(short, long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC32
        time: Option<Dur>,
6f5b84fad3efatagen2026-09-27 03:59 UTC33
        /// kill it past this, exit 124
6f5b84fad3efatagen2026-09-27 03:59 UTC34
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC35
        deadline: Option<Dur>,
6f5b84fad3efatagen2026-09-27 03:59 UTC36
        /// stop queueing past this, exit 75
6f5b84fad3efatagen2026-09-27 03:59 UTC37
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC38
        max_wait: Option<Dur>,
6f5b84fad3efatagen2026-09-27 03:59 UTC39
        #[pound(long, hidden, env = "CLAUDE_CODE_SESSION_ID")]
6f5b84fad3efatagen2026-09-27 03:59 UTC40
        session: Option<String>,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC41
        /// exactly the cores asked for, no spares
ecc1cf8df0f5atagen2026-09-27 13:36 UTC42
        #[pound(long)]
ecc1cf8df0f5atagen2026-09-27 13:36 UTC43
        exact: bool,
6f5b84fad3efatagen2026-09-27 03:59 UTC44
        #[pound(trailing)]
6f5b84fad3efatagen2026-09-27 03:59 UTC45
        cmd: Vec<String>,
6f5b84fad3efatagen2026-09-27 03:59 UTC46
    },
ecc1cf8df0f5atagen2026-09-27 13:36 UTC47
    /// say you'll be building soon, so others leave you room
ecc1cf8df0f5atagen2026-09-27 13:36 UTC48
    Expect {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC49
        #[pound(short, long)]
ecc1cf8df0f5atagen2026-09-27 13:36 UTC50
        size: Option<String>,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC51
        #[pound(long)]
ecc1cf8df0f5atagen2026-09-27 13:36 UTC52
        ram: Option<Bytes>,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC53
        #[pound(long)]
ecc1cf8df0f5atagen2026-09-27 13:36 UTC54
        cores: Option<Amount<u32>>,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC55
        /// changed your mind
ecc1cf8df0f5atagen2026-09-27 13:36 UTC56
        #[pound(long)]
ecc1cf8df0f5atagen2026-09-27 13:36 UTC57
        done: bool,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC58
        #[pound(long, hidden, env = "CLAUDE_CODE_SESSION_ID")]
ecc1cf8df0f5atagen2026-09-27 13:36 UTC59
        session: Option<String>,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC60
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC61
    /// who's running, who's waiting
6f5b84fad3efatagen2026-09-27 03:59 UTC62
    Status {
6f5b84fad3efatagen2026-09-27 03:59 UTC63
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC64
        json: bool,
6f5b84fad3efatagen2026-09-27 03:59 UTC65
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC66
    /// a size guess from past runs
6f5b84fad3efatagen2026-09-27 03:59 UTC67
    Suggest {
6f5b84fad3efatagen2026-09-27 03:59 UTC68
        #[pound(trailing)]
6f5b84fad3efatagen2026-09-27 03:59 UTC69
        cmd: Vec<String>,
6f5b84fad3efatagen2026-09-27 03:59 UTC70
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC71
    /// stop a lease's build
6f5b84fad3efatagen2026-09-27 03:59 UTC72
    Kill { lease: u64 },
6f5b84fad3efatagen2026-09-27 03:59 UTC73
    /// buy an overdue build more time
6f5b84fad3efatagen2026-09-27 03:59 UTC74
    Extend { lease: u64, by: Dur },
6f5b84fad3efatagen2026-09-27 03:59 UTC75
    /// exit when a lease goes overdue or finishes
6f5b84fad3efatagen2026-09-27 03:59 UTC76
    Watch { lease: u64 },
6f5b84fad3efatagen2026-09-27 03:59 UTC77
    /// build output and how much of it there is
6f5b84fad3efatagen2026-09-27 03:59 UTC78
    Artifacts {
6f5b84fad3efatagen2026-09-27 03:59 UTC79
        #[pound(subcommand)]
6f5b84fad3efatagen2026-09-27 03:59 UTC80
        action: Artifacts,
6f5b84fad3efatagen2026-09-27 03:59 UTC81
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC82
    /// claude code hook entrypoint, json on stdin
6f5b84fad3efatagen2026-09-27 03:59 UTC83
    Hook {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC84
        /// pre-tool-use post-tool-use prompt-submit stop subagent-stop session-end
6f5b84fad3efatagen2026-09-27 03:59 UTC85
        event: String,
6f5b84fad3efatagen2026-09-27 03:59 UTC86
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC87
    #[pound(hidden)]
6f5b84fad3efatagen2026-09-27 03:59 UTC88
    Daemon,
6f5b84fad3efatagen2026-09-27 03:59 UTC89
}
6f5b84fad3efatagen2026-09-27 03:59 UTC90
6f5b84fad3efatagen2026-09-27 03:59 UTC91
#[derive(Parse)]
6f5b84fad3efatagen2026-09-27 03:59 UTC92
pub enum Artifacts {
6f5b84fad3efatagen2026-09-27 03:59 UTC93
    /// sizes against the caps, exits 1 when over
6f5b84fad3efatagen2026-09-27 03:59 UTC94
    Check {
6f5b84fad3efatagen2026-09-27 03:59 UTC95
        /// re-measure instead of trusting the cache
6f5b84fad3efatagen2026-09-27 03:59 UTC96
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC97
        fresh: bool,
6f5b84fad3efatagen2026-09-27 03:59 UTC98
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC99
    /// everything tracked, biggest first
6f5b84fad3efatagen2026-09-27 03:59 UTC100
    List,
6f5b84fad3efatagen2026-09-27 03:59 UTC101
    /// delete idle artifacts
6f5b84fad3efatagen2026-09-27 03:59 UTC102
    Gc {
6f5b84fad3efatagen2026-09-27 03:59 UTC103
        /// keep going, oldest first, until under the caps
6f5b84fad3efatagen2026-09-27 03:59 UTC104
        #[pound(long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC105
        cap: bool,
6f5b84fad3efatagen2026-09-27 03:59 UTC106
        #[pound(short = 'n', long)]
6f5b84fad3efatagen2026-09-27 03:59 UTC107
        dry_run: bool,
6f5b84fad3efatagen2026-09-27 03:59 UTC108
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC109
    /// never gc this
6f5b84fad3efatagen2026-09-27 03:59 UTC110
    Pin {
6f5b84fad3efatagen2026-09-27 03:59 UTC111
        path: PathBuf,
6f5b84fad3efatagen2026-09-27 03:59 UTC112
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC113
    Unpin {
6f5b84fad3efatagen2026-09-27 03:59 UTC114
        path: PathBuf,
6f5b84fad3efatagen2026-09-27 03:59 UTC115
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC116
}
6f5b84fad3efatagen2026-09-27 03:59 UTC117
6f5b84fad3efatagen2026-09-27 03:59 UTC118
fn artifacts(cfg: &Config, action: Artifacts) -> i32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC119
    let cwd = std::env::current_dir().unwrap_or_else(|_| "/".into());
6f5b84fad3efatagen2026-09-27 03:59 UTC120
    let root = paths::project_root(&cwd);
6f5b84fad3efatagen2026-09-27 03:59 UTC121
    match action {
6f5b84fad3efatagen2026-09-27 03:59 UTC122
        Artifacts::Check { fresh } => {
6f5b84fad3efatagen2026-09-27 03:59 UTC123
            let r = artifacts::check(cfg, Some(&root), fresh);
6f5b84fad3efatagen2026-09-27 03:59 UTC124
            if let Some((root, size)) = &r.project {
6f5b84fad3efatagen2026-09-27 03:59 UTC125
                println!(
6f5b84fad3efatagen2026-09-27 03:59 UTC126
                    "{}: {size} of {}",
6f5b84fad3efatagen2026-09-27 03:59 UTC127
                    root.display(),
6f5b84fad3efatagen2026-09-27 03:59 UTC128
                    cfg.artifacts.project_cap
6f5b84fad3efatagen2026-09-27 03:59 UTC129
                );
6f5b84fad3efatagen2026-09-27 03:59 UTC130
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC131
            println!("everything: {} of {}", r.global, cfg.artifacts.global_cap);
6f5b84fad3efatagen2026-09-27 03:59 UTC132
            if r.over() {
6f5b84fad3efatagen2026-09-27 03:59 UTC133
                println!("over. `rabt artifacts gc --cap`");
6f5b84fad3efatagen2026-09-27 03:59 UTC134
                1
6f5b84fad3efatagen2026-09-27 03:59 UTC135
            } else {
6f5b84fad3efatagen2026-09-27 03:59 UTC136
                0
6f5b84fad3efatagen2026-09-27 03:59 UTC137
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC138
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC139
        Artifacts::List => {
6f5b84fad3efatagen2026-09-27 03:59 UTC140
            let r = artifacts::check(cfg, Some(&root), false);
6f5b84fad3efatagen2026-09-27 03:59 UTC141
            let mut rows: Vec<_> = r.entries.iter().collect();
6f5b84fad3efatagen2026-09-27 03:59 UTC142
            rows.sort_by_key(|(_, e)| std::cmp::Reverse(e.size));
6f5b84fad3efatagen2026-09-27 03:59 UTC143
            for (path, e) in rows {
6f5b84fad3efatagen2026-09-27 03:59 UTC144
                println!(
6f5b84fad3efatagen2026-09-27 03:59 UTC145
                    "{:>10}  {}{}",
6f5b84fad3efatagen2026-09-27 03:59 UTC146
                    e.size.to_string(),
6f5b84fad3efatagen2026-09-27 03:59 UTC147
                    path.display(),
6f5b84fad3efatagen2026-09-27 03:59 UTC148
                    if e.pinned { "  (pinned)" } else { "" }
6f5b84fad3efatagen2026-09-27 03:59 UTC149
                );
6f5b84fad3efatagen2026-09-27 03:59 UTC150
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC151
            0
6f5b84fad3efatagen2026-09-27 03:59 UTC152
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC153
        Artifacts::Gc { cap, dry_run } => {
6f5b84fad3efatagen2026-09-27 03:59 UTC154
            let g = artifacts::gc(cfg, cap, dry_run);
6f5b84fad3efatagen2026-09-27 03:59 UTC155
            for p in &g.removed {
6f5b84fad3efatagen2026-09-27 03:59 UTC156
                println!(
6f5b84fad3efatagen2026-09-27 03:59 UTC157
                    "{}{}",
6f5b84fad3efatagen2026-09-27 03:59 UTC158
                    if dry_run { "would remove " } else { "removed " },
6f5b84fad3efatagen2026-09-27 03:59 UTC159
                    p.display()
6f5b84fad3efatagen2026-09-27 03:59 UTC160
                );
6f5b84fad3efatagen2026-09-27 03:59 UTC161
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC162
            for f in &g.failed {
6f5b84fad3efatagen2026-09-27 03:59 UTC163
                eprintln!("rabt: {f}");
6f5b84fad3efatagen2026-09-27 03:59 UTC164
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC165
            println!(
6f5b84fad3efatagen2026-09-27 03:59 UTC166
                "{} {}",
6f5b84fad3efatagen2026-09-27 03:59 UTC167
                if dry_run { "would free" } else { "freed" },
6f5b84fad3efatagen2026-09-27 03:59 UTC168
                g.freed
6f5b84fad3efatagen2026-09-27 03:59 UTC169
            );
6f5b84fad3efatagen2026-09-27 03:59 UTC170
            i32::from(!g.failed.is_empty())
6f5b84fad3efatagen2026-09-27 03:59 UTC171
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC172
        Artifacts::Pin { path } | Artifacts::Unpin { path } if !path.exists() => {
6f5b84fad3efatagen2026-09-27 03:59 UTC173
            eprintln!("rabt: {} doesn't exist", path.display());
6f5b84fad3efatagen2026-09-27 03:59 UTC174
            1
6f5b84fad3efatagen2026-09-27 03:59 UTC175
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC176
        Artifacts::Pin { path } => report(artifacts::pin(&path, true)),
6f5b84fad3efatagen2026-09-27 03:59 UTC177
        Artifacts::Unpin { path } => report(artifacts::pin(&path, false)),
6f5b84fad3efatagen2026-09-27 03:59 UTC178
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC179
}
6f5b84fad3efatagen2026-09-27 03:59 UTC180
6f5b84fad3efatagen2026-09-27 03:59 UTC181
fn report(r: Result<(), String>) -> i32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC182
    r.map_or_else(
6f5b84fad3efatagen2026-09-27 03:59 UTC183
        |e| {
6f5b84fad3efatagen2026-09-27 03:59 UTC184
            eprintln!("rabt: {e}");
6f5b84fad3efatagen2026-09-27 03:59 UTC185
            1
6f5b84fad3efatagen2026-09-27 03:59 UTC186
        },
6f5b84fad3efatagen2026-09-27 03:59 UTC187
        |()| 0,
6f5b84fad3efatagen2026-09-27 03:59 UTC188
    )
6f5b84fad3efatagen2026-09-27 03:59 UTC189
}
6f5b84fad3efatagen2026-09-27 03:59 UTC190
6f5b84fad3efatagen2026-09-27 03:59 UTC191
fn ask(req: &Request) -> io::Result<Option<Event>> {
6f5b84fad3efatagen2026-09-27 03:59 UTC192
    let Some(mut s) = client::connect() else {
6f5b84fad3efatagen2026-09-27 03:59 UTC193
        return Ok(None);
6f5b84fad3efatagen2026-09-27 03:59 UTC194
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC195
    proto::send(&mut s, req)?;
6f5b84fad3efatagen2026-09-27 03:59 UTC196
    Reader::new(&s)?.recv()
6f5b84fad3efatagen2026-09-27 03:59 UTC197
}
6f5b84fad3efatagen2026-09-27 03:59 UTC198
6f5b84fad3efatagen2026-09-27 03:59 UTC199
fn reply(ev: io::Result<Option<Event>>) -> i32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC200
    match ev {
6f5b84fad3efatagen2026-09-27 03:59 UTC201
        Ok(Some(Event::Ok)) => 0,
6f5b84fad3efatagen2026-09-27 03:59 UTC202
        Ok(Some(Event::Err { msg })) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC203
            eprintln!("rabt: {msg}");
6f5b84fad3efatagen2026-09-27 03:59 UTC204
            1
6f5b84fad3efatagen2026-09-27 03:59 UTC205
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC206
        Ok(None) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC207
            eprintln!("rabt: nothing running");
6f5b84fad3efatagen2026-09-27 03:59 UTC208
            1
6f5b84fad3efatagen2026-09-27 03:59 UTC209
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC210
        Ok(Some(_)) => 1,
6f5b84fad3efatagen2026-09-27 03:59 UTC211
        Err(e) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC212
            eprintln!("rabt: {e}");
6f5b84fad3efatagen2026-09-27 03:59 UTC213
            1
6f5b84fad3efatagen2026-09-27 03:59 UTC214
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC215
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC216
}
6f5b84fad3efatagen2026-09-27 03:59 UTC217
ecc1cf8df0f5atagen2026-09-27 13:36 UTC218
fn expect(cfg: &Config, session: Option<String>, done: bool, a: &run::Args) -> i32 {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC219
    let Some(session) = session.filter(|s| !s.is_empty()) else {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC220
        eprintln!("rabt: no CLAUDE_CODE_SESSION_ID, so nobody to expect on behalf of");
ecc1cf8df0f5atagen2026-09-27 13:36 UTC221
        return 2;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC222
    };
ecc1cf8df0f5atagen2026-09-27 13:36 UTC223
    let need = if done {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC224
        None
ecc1cf8df0f5atagen2026-09-27 13:36 UTC225
    } else {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC226
        match run::need(cfg, a) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC227
            Ok(n) => Some(n),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC228
            Err(e) => {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC229
                eprintln!("rabt: {e}");
ecc1cf8df0f5atagen2026-09-27 13:36 UTC230
                return 2;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC231
            }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC232
        }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC233
    };
ecc1cf8df0f5atagen2026-09-27 13:36 UTC234
    let mut s = match client::connect_or_spawn() {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC235
        Ok(s) => s,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC236
        Err(e) => {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC237
            eprintln!("rabt: can't reach the daemon: {e}");
ecc1cf8df0f5atagen2026-09-27 13:36 UTC238
            return 1;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC239
        }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC240
    };
ecc1cf8df0f5atagen2026-09-27 13:36 UTC241
    if let Err(e) = proto::send(&mut s, &Request::Expect { session, need }) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC242
        eprintln!("rabt: {e}");
ecc1cf8df0f5atagen2026-09-27 13:36 UTC243
        return 1;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC244
    }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC245
    reply(Reader::new(&s).and_then(|mut r| r.recv()))
ecc1cf8df0f5atagen2026-09-27 13:36 UTC246
}
ecc1cf8df0f5atagen2026-09-27 13:36 UTC247
6f5b84fad3efatagen2026-09-27 03:59 UTC248
fn status(json: bool) -> i32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC249
    let Ok(Some(Event::Status(st))) = ask(&Request::Status) else {
6f5b84fad3efatagen2026-09-27 03:59 UTC250
        if json {
6f5b84fad3efatagen2026-09-27 03:59 UTC251
            println!("null");
6f5b84fad3efatagen2026-09-27 03:59 UTC252
        } else {
6f5b84fad3efatagen2026-09-27 03:59 UTC253
            println!("idle");
6f5b84fad3efatagen2026-09-27 03:59 UTC254
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC255
        return 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC256
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC257
    if json {
6f5b84fad3efatagen2026-09-27 03:59 UTC258
        println!("{}", serde_json::to_string(&st).unwrap_or_default());
6f5b84fad3efatagen2026-09-27 03:59 UTC259
        return 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC260
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC261
    println!(
6f5b84fad3efatagen2026-09-27 03:59 UTC262
        "ram {} free of {}, cores {} free of {}",
6f5b84fad3efatagen2026-09-27 03:59 UTC263
        st.ram_free, st.ram_budget, st.cores_free, st.cores
6f5b84fad3efatagen2026-09-27 03:59 UTC264
    );
6f5b84fad3efatagen2026-09-27 03:59 UTC265
    for l in &st.leases {
6f5b84fad3efatagen2026-09-27 03:59 UTC266
        let state = if l.running {
6f5b84fad3efatagen2026-09-27 03:59 UTC267
            format!(
6f5b84fad3efatagen2026-09-27 03:59 UTC268
                "running {}{}",
6f5b84fad3efatagen2026-09-27 03:59 UTC269
                Dur::secs(l.elapsed),
6f5b84fad3efatagen2026-09-27 03:59 UTC270
                if l.overdue { " OVERDUE" } else { "" }
6f5b84fad3efatagen2026-09-27 03:59 UTC271
            )
6f5b84fad3efatagen2026-09-27 03:59 UTC272
        } else {
6f5b84fad3efatagen2026-09-27 03:59 UTC273
            format!(
6f5b84fad3efatagen2026-09-27 03:59 UTC274
                "#{} {}",
6f5b84fad3efatagen2026-09-27 03:59 UTC275
                l.position,
6f5b84fad3efatagen2026-09-27 03:59 UTC276
                l.waiting.map(|r| r.to_string()).unwrap_or_default()
6f5b84fad3efatagen2026-09-27 03:59 UTC277
            )
6f5b84fad3efatagen2026-09-27 03:59 UTC278
        };
ecc1cf8df0f5atagen2026-09-27 13:36 UTC279
        let cores = if l.granted > l.need.cores {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC280
            format!("{}+{}", l.need.cores, l.granted - l.need.cores)
ecc1cf8df0f5atagen2026-09-27 13:36 UTC281
        } else {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC282
            l.need.cores.to_string()
ecc1cf8df0f5atagen2026-09-27 13:36 UTC283
        };
6f5b84fad3efatagen2026-09-27 03:59 UTC284
        println!(
6f5b84fad3efatagen2026-09-27 03:59 UTC285
            "{:>4} {}{} {} ram {} cores {} rss {}  {}",
6f5b84fad3efatagen2026-09-27 03:59 UTC286
            l.id,
6f5b84fad3efatagen2026-09-27 03:59 UTC287
            if l.heavy { "H " } else { "" },
6f5b84fad3efatagen2026-09-27 03:59 UTC288
            state,
6f5b84fad3efatagen2026-09-27 03:59 UTC289
            Dur(l.need.time),
6f5b84fad3efatagen2026-09-27 03:59 UTC290
            l.need.ram,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC291
            cores,
6f5b84fad3efatagen2026-09-27 03:59 UTC292
            l.rss,
6f5b84fad3efatagen2026-09-27 03:59 UTC293
            l.cmd.join(" ")
6f5b84fad3efatagen2026-09-27 03:59 UTC294
        );
6f5b84fad3efatagen2026-09-27 03:59 UTC295
    }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC296
    for i in &st.intents {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC297
        println!(
ecc1cf8df0f5atagen2026-09-27 13:36 UTC298
            "     expect {} ram {} cores {}, {} left",
ecc1cf8df0f5atagen2026-09-27 13:36 UTC299
            &i.session[..i.session.len().min(8)],
ecc1cf8df0f5atagen2026-09-27 13:36 UTC300
            i.need.ram,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC301
            i.need.cores,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC302
            Dur::secs(i.expires_in)
ecc1cf8df0f5atagen2026-09-27 13:36 UTC303
        );
ecc1cf8df0f5atagen2026-09-27 13:36 UTC304
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC305
    0
6f5b84fad3efatagen2026-09-27 03:59 UTC306
}
6f5b84fad3efatagen2026-09-27 03:59 UTC307
6f5b84fad3efatagen2026-09-27 03:59 UTC308
fn suggest(cfg: &Config, cmd: &[String]) -> i32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC309
    let cwd = std::env::current_dir().unwrap_or_else(|_| "/".into());
6f5b84fad3efatagen2026-09-27 03:59 UTC310
    let key = history::key(&paths::project_root(&cwd), cmd);
6f5b84fad3efatagen2026-09-27 03:59 UTC311
    let Some(rec) = history::lookup(&key) else {
6f5b84fad3efatagen2026-09-27 03:59 UTC312
        println!("never seen it, pick a --size");
6f5b84fad3efatagen2026-09-27 03:59 UTC313
        return 1;
6f5b84fad3efatagen2026-09-27 03:59 UTC314
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC315
    let base = match run::base(cfg) {
6f5b84fad3efatagen2026-09-27 03:59 UTC316
        Ok(b) => b,
6f5b84fad3efatagen2026-09-27 03:59 UTC317
        Err(e) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC318
            eprintln!("rabt: {e}");
6f5b84fad3efatagen2026-09-27 03:59 UTC319
            return 2;
6f5b84fad3efatagen2026-09-27 03:59 UTC320
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC321
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC322
    let n = history::suggest(&rec, base, cmd);
6f5b84fad3efatagen2026-09-27 03:59 UTC323
    println!(
6f5b84fad3efatagen2026-09-27 03:59 UTC324
        "--ram {} --cores {} --time {} --disk {}",
6f5b84fad3efatagen2026-09-27 03:59 UTC325
        n.ram,
6f5b84fad3efatagen2026-09-27 03:59 UTC326
        n.cores,
6f5b84fad3efatagen2026-09-27 03:59 UTC327
        Dur(n.time),
6f5b84fad3efatagen2026-09-27 03:59 UTC328
        n.disk
6f5b84fad3efatagen2026-09-27 03:59 UTC329
    );
6f5b84fad3efatagen2026-09-27 03:59 UTC330
    0
6f5b84fad3efatagen2026-09-27 03:59 UTC331
}
6f5b84fad3efatagen2026-09-27 03:59 UTC332
6f5b84fad3efatagen2026-09-27 03:59 UTC333
fn watch(lease: u64) -> i32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC334
    let Some(mut s): Option<UnixStream> = client::connect() else {
6f5b84fad3efatagen2026-09-27 03:59 UTC335
        return 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC336
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC337
    if proto::send(&mut s, &Request::Watch { lease }).is_err() {
6f5b84fad3efatagen2026-09-27 03:59 UTC338
        return 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC339
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC340
    if matches!(
6f5b84fad3efatagen2026-09-27 03:59 UTC341
        Reader::new(&s).and_then(|mut r| r.recv::<Event>()),
6f5b84fad3efatagen2026-09-27 03:59 UTC342
        Ok(Some(Event::Fired { overdue: true }))
6f5b84fad3efatagen2026-09-27 03:59 UTC343
    ) {
6f5b84fad3efatagen2026-09-27 03:59 UTC344
        println!("lease {lease} is overdue, `rabt kill {lease}` or `rabt extend {lease} <dur>`");
6f5b84fad3efatagen2026-09-27 03:59 UTC345
        3
6f5b84fad3efatagen2026-09-27 03:59 UTC346
    } else {
6f5b84fad3efatagen2026-09-27 03:59 UTC347
        println!("lease {lease} is done");
6f5b84fad3efatagen2026-09-27 03:59 UTC348
        0
6f5b84fad3efatagen2026-09-27 03:59 UTC349
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC350
}
6f5b84fad3efatagen2026-09-27 03:59 UTC351
6f5b84fad3efatagen2026-09-27 03:59 UTC352
pub fn dispatch(cfg: &Config, cmd: Rabt) -> i32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC353
    match cmd {
6f5b84fad3efatagen2026-09-27 03:59 UTC354
        Rabt::Run {
6f5b84fad3efatagen2026-09-27 03:59 UTC355
            size,
6f5b84fad3efatagen2026-09-27 03:59 UTC356
            ram,
6f5b84fad3efatagen2026-09-27 03:59 UTC357
            disk,
6f5b84fad3efatagen2026-09-27 03:59 UTC358
            cores,
6f5b84fad3efatagen2026-09-27 03:59 UTC359
            time,
6f5b84fad3efatagen2026-09-27 03:59 UTC360
            deadline,
6f5b84fad3efatagen2026-09-27 03:59 UTC361
            max_wait,
6f5b84fad3efatagen2026-09-27 03:59 UTC362
            session,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC363
            exact,
6f5b84fad3efatagen2026-09-27 03:59 UTC364
            cmd,
6f5b84fad3efatagen2026-09-27 03:59 UTC365
        } => run::run(
6f5b84fad3efatagen2026-09-27 03:59 UTC366
            cfg,
6f5b84fad3efatagen2026-09-27 03:59 UTC367
            &run::Args {
6f5b84fad3efatagen2026-09-27 03:59 UTC368
                size,
6f5b84fad3efatagen2026-09-27 03:59 UTC369
                ram,
6f5b84fad3efatagen2026-09-27 03:59 UTC370
                disk,
6f5b84fad3efatagen2026-09-27 03:59 UTC371
                cores,
6f5b84fad3efatagen2026-09-27 03:59 UTC372
                time,
6f5b84fad3efatagen2026-09-27 03:59 UTC373
                deadline,
6f5b84fad3efatagen2026-09-27 03:59 UTC374
                max_wait,
6f5b84fad3efatagen2026-09-27 03:59 UTC375
                session,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC376
                exact,
6f5b84fad3efatagen2026-09-27 03:59 UTC377
                cmd,
6f5b84fad3efatagen2026-09-27 03:59 UTC378
            },
6f5b84fad3efatagen2026-09-27 03:59 UTC379
        ),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC380
        Rabt::Expect {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC381
            size,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC382
            ram,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC383
            cores,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC384
            done,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC385
            session,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC386
        } => expect(
ecc1cf8df0f5atagen2026-09-27 13:36 UTC387
            cfg,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC388
            session,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC389
            done,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC390
            &run::Args {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC391
                size,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC392
                ram,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC393
                cores,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC394
                ..run::Args::default()
ecc1cf8df0f5atagen2026-09-27 13:36 UTC395
            },
ecc1cf8df0f5atagen2026-09-27 13:36 UTC396
        ),
6f5b84fad3efatagen2026-09-27 03:59 UTC397
        Rabt::Status { json } => status(json),
6f5b84fad3efatagen2026-09-27 03:59 UTC398
        Rabt::Suggest { cmd } => suggest(cfg, &cmd),
6f5b84fad3efatagen2026-09-27 03:59 UTC399
        Rabt::Kill { lease } => reply(ask(&Request::Kill { lease })),
6f5b84fad3efatagen2026-09-27 03:59 UTC400
        Rabt::Extend { lease, by } => reply(ask(&Request::Extend { lease, by })),
6f5b84fad3efatagen2026-09-27 03:59 UTC401
        Rabt::Watch { lease } => watch(lease),
6f5b84fad3efatagen2026-09-27 03:59 UTC402
        Rabt::Artifacts { action } => artifacts(cfg, action),
6f5b84fad3efatagen2026-09-27 03:59 UTC403
        Rabt::Hook { event } => hook::main(cfg, &event),
6f5b84fad3efatagen2026-09-27 03:59 UTC404
        Rabt::Daemon => daemon::main(Config::load().unwrap_or_default()),
6f5b84fad3efatagen2026-09-27 03:59 UTC405
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC406
}