Skip to main content

rz_cli/
status.rs

1//! Surface status summary for the `status` subcommand.
2
3use crate::cmux::SurfaceInfo;
4use rz_agent_protocol::SENTINEL;
5
6/// Per-surface status line.
7pub struct SurfaceStatus {
8    pub surface_id: String,
9    pub title: String,
10    pub command: String,
11    pub running: bool,
12    pub message_count: usize,
13}
14
15/// Summary of all surfaces.
16pub struct StatusSummary {
17    pub total: usize,
18    pub running: usize,
19    pub exited: usize,
20    pub surfaces: Vec<SurfaceStatus>,
21}
22
23/// Count `@@RZ:` lines in a scrollback string.
24fn count_messages(scrollback: &str) -> usize {
25    scrollback.lines().filter(|l| l.contains(SENTINEL)).count()
26}
27
28/// Build a [`StatusSummary`] from a list of surfaces and a function that provides
29/// each surface's scrollback.
30///
31/// The caller supplies `get_scrollback` so the function stays testable without
32/// hitting real cmux — in production, pass `|id| rz::cmux::dump(id)`.
33pub fn summarize(
34    surfaces: &[SurfaceInfo],
35    get_scrollback: impl Fn(&str) -> Option<String>,
36) -> StatusSummary {
37    let mut running = 0usize;
38    let exited = 0usize;
39    let mut statuses = Vec::with_capacity(surfaces.len());
40
41    for surface in surfaces {
42        // cmux surfaces are always running (no exit tracking)
43        running += 1;
44
45        let msg_count = get_scrollback(&surface.id)
46            .map(|s| count_messages(&s))
47            .unwrap_or(0);
48
49        statuses.push(SurfaceStatus {
50            surface_id: surface.id.clone(),
51            title: surface.title.clone(),
52            command: "-".to_string(),
53            running: true,
54            message_count: msg_count,
55        });
56    }
57
58    StatusSummary {
59        total: surfaces.len(),
60        running,
61        exited,
62        surfaces: statuses,
63    }
64}
65
66/// Format the summary as a human-readable string.
67pub fn format_summary(summary: &StatusSummary) -> String {
68    let mut out = format!(
69        "{} surfaces ({} running, {} exited)\n",
70        summary.total, summary.running, summary.exited,
71    );
72
73    for s in &summary.surfaces {
74        let state = if s.running {
75            "running".to_string()
76        } else {
77            "exited".to_string()
78        };
79        out.push_str(&format!(
80            "  {} | {} | {} | {} | {} msgs\n",
81            s.surface_id, s.title, s.command, state, s.message_count,
82        ));
83    }
84
85    out
86}