omne_cli/commands/
status.rs1#![allow(dead_code)]
11
12use std::io::{self, Write};
13use std::path::Path;
14
15use clap::Args as ClapArgs;
16
17use crate::error::CliError;
18use crate::event_log;
19use crate::events::ErrorKind;
20use crate::run_state::{self, NodeStatus, PipeState};
21use crate::volume;
22
23#[derive(Debug, ClapArgs)]
24pub struct Args {
25 pub run_id: Option<String>,
27}
28
29pub fn run(args: &Args) -> Result<(), CliError> {
30 let cwd = std::env::current_dir()
31 .map_err(|e| CliError::Io(format!("cannot determine current directory: {e}")))?;
32 run_at_root(&cwd, args, &mut io::stdout())
33}
34
35pub fn run_at_root(start: &Path, args: &Args, out: &mut dyn Write) -> Result<(), CliError> {
38 let root = volume::find_omne_root(start).ok_or(CliError::NotAVolume)?;
39
40 match &args.run_id {
41 Some(run_id) => show_run(&root, run_id, out),
42 None => show_all(&root, out),
43 }
44}
45
46fn show_all(root: &Path, out: &mut dyn Write) -> Result<(), CliError> {
48 let run_ids = event_log::enumerate_runs(root)?;
49 if run_ids.is_empty() {
50 writeln!(out, "No runs found.").map_err(io_err)?;
51 return Ok(());
52 }
53
54 for run_id in &run_ids {
55 let events = match event_log::read_run(root, run_id) {
56 Ok(ev) => ev,
57 Err(e) => {
58 writeln!(out, "{run_id} (read error: {e})").map_err(io_err)?;
59 continue;
60 }
61 };
62 let summary = run_state::summarize(run_id, &events);
63 let state_str = match &summary.state {
64 PipeState::Running => "running",
65 PipeState::Completed => "completed",
66 PipeState::Aborted { .. } => "aborted",
67 };
68 let orphan_marker = if summary.is_orphan { " [ORPHAN?]" } else { "" };
69 let progress = if summary.node_count > 0 {
70 format!(" ({}/{})", summary.completed_count, summary.node_count)
71 } else {
72 String::new()
73 };
74 writeln!(
75 out,
76 "{run_id} {pipe} {state_str}{progress}{orphan_marker} {ts}",
77 pipe = summary.pipe,
78 ts = summary.last_ts,
79 )
80 .map_err(io_err)?;
81 }
82 Ok(())
83}
84
85fn show_run(root: &Path, run_id: &str, out: &mut dyn Write) -> Result<(), CliError> {
87 if !event_log::run_exists(root, run_id) {
88 return Err(CliError::RunNotFound(run_id.to_string()));
89 }
90
91 let events = event_log::read_run(root, run_id)?;
92 let state = run_state::derive(run_id, &events);
93
94 let pipe_status = match &state.state {
95 PipeState::Running => "running".to_string(),
96 PipeState::Completed => "completed".to_string(),
97 PipeState::Aborted { reason } => format!("aborted: {reason}"),
98 };
99 let orphan_marker = if state.is_orphan { " [ORPHAN?]" } else { "" };
100
101 writeln!(out, "run: {}", state.run_id).map_err(io_err)?;
102 writeln!(out, "pipe: {}", state.pipe).map_err(io_err)?;
103 writeln!(out, "status: {pipe_status}{orphan_marker}").map_err(io_err)?;
104 writeln!(out, "last: {}", state.last_ts).map_err(io_err)?;
105
106 if !state.nodes.is_empty() {
107 writeln!(out).map_err(io_err)?;
108 writeln!(out, "nodes:").map_err(io_err)?;
109 for node in &state.nodes {
110 let (glyph, detail) = match &node.status {
111 NodeStatus::Pending => ("\u{2022}", String::new()), NodeStatus::Running => ("\u{25b6}", String::new()), NodeStatus::Completed => ("\u{2713}", String::new()), NodeStatus::Failed { kind, message } => {
115 let kind_str = error_kind_label(*kind);
116 let msg = match message {
117 Some(m) => format!(" {kind_str}: {m}"),
118 None => format!(" {kind_str}"),
119 };
120 ("\u{2717}", msg) }
122 };
123 let kind_tag = node
124 .kind
125 .map(|k| format!(" [{}]", node_kind_label(k)))
126 .unwrap_or_default();
127 writeln!(out, " {glyph} {id}{kind_tag}{detail}", id = node.id).map_err(io_err)?;
128 }
129 }
130 Ok(())
131}
132
133fn node_kind_label(kind: crate::events::NodeKind) -> &'static str {
134 match kind {
135 crate::events::NodeKind::Command => "command",
136 crate::events::NodeKind::Prompt => "prompt",
137 crate::events::NodeKind::Bash => "bash",
138 crate::events::NodeKind::Loop => "loop",
139 }
140}
141
142fn error_kind_label(kind: ErrorKind) -> &'static str {
143 match kind {
144 ErrorKind::HostMissing => "host_missing",
145 ErrorKind::Timeout => "timeout",
146 ErrorKind::Blocked => "blocked",
147 ErrorKind::GateFailed => "gate_failed",
148 ErrorKind::GateTimeout => "gate_timeout",
149 ErrorKind::Crash => "crash",
150 ErrorKind::MaxIterationsExceeded => "max_iterations_exceeded",
151 }
152}
153
154fn io_err(e: io::Error) -> CliError {
155 CliError::Io(format!("stdout write failed: {e}"))
156}