minion_engine/cli/
display.rs1#![allow(dead_code)]
3
4use std::time::Duration;
5
6use colored::Colorize;
7use indicatif::{ProgressBar, ProgressStyle};
8
9#[derive(Debug, Clone, PartialEq, Eq, Default)]
11pub enum OutputMode {
12 #[default]
13 Normal,
14 Quiet,
15 Verbose,
16 Json,
17}
18
19pub fn step_start(name: &str, step_type: &str) -> ProgressBar {
20 let pb = ProgressBar::new_spinner();
21 pb.set_style(
22 ProgressStyle::default_spinner()
23 .template("{spinner:.cyan} {msg}")
24 .unwrap(),
25 );
26 pb.set_message(format!("{} [{}]", name, step_type.dimmed()));
27 pb.enable_steady_tick(Duration::from_millis(100));
28 pb
29}
30
31pub fn step_ok(pb: &ProgressBar, name: &str, duration: Duration) {
32 pb.finish_and_clear();
33 println!(
34 " {} {} {}",
35 "✓".green(),
36 name,
37 format!("({:.1}s)", duration.as_secs_f64()).dimmed()
38 );
39}
40
41pub fn step_fail(pb: &ProgressBar, name: &str, message: &str) {
42 pb.finish_and_clear();
43 println!(" {} {} — {}", "✗".red(), name, message.red());
44}
45
46pub fn step_skip(pb: &ProgressBar, name: &str, message: &str) {
47 pb.finish_and_clear();
48 println!(" {} {} {}", "→".yellow(), name, message.dimmed());
49}
50
51pub fn iteration(current: usize, max: usize) {
52 println!(
53 " {} Iteration {}/{}",
54 "↻".cyan(),
55 current + 1,
56 max
57 );
58}
59
60pub fn agent_progress(text: &str) {
61 if !text.is_empty() {
62 for line in text.lines().take(3) {
63 println!(" {}", line.dimmed());
64 }
65 }
66}
67
68pub fn tool_use(tool: &str, _input: &str) {
69 println!(" {} [tool: {}]", "⚙".blue(), tool);
70}
71
72pub fn workflow_start(name: &str) {
73 println!("{} {}", "▶".cyan().bold(), name.bold());
74}
75
76pub fn workflow_done(duration: Duration, step_count: usize) {
77 println!(
78 "\n{} Done — {} steps in {:.1}s",
79 "✓".green().bold(),
80 step_count,
81 duration.as_secs_f64()
82 );
83}
84
85pub fn workflow_failed(step_name: &str, message: &str) {
86 println!(
87 "\n{} Failed at step '{}': {}",
88 "✗".red().bold(),
89 step_name,
90 message
91 );
92}
93
94pub fn map_item(current: usize, total: usize, name: &str) {
96 println!(
97 " {} Item {}/{}: {}",
98 "◆".cyan(),
99 current,
100 total,
101 name.bold()
102 );
103}
104
105pub fn parallel_step(name: &str) {
107 println!(" {} {}", "⟶".blue(), name);
108}
109
110pub fn workflow_summary(
112 steps: usize,
113 duration: Duration,
114 input_tokens: u64,
115 output_tokens: u64,
116 cost_usd: f64,
117) {
118 println!(
119 "\n{} Summary — {} steps in {:.1}s",
120 "✓".green().bold(),
121 steps,
122 duration.as_secs_f64()
123 );
124 println!(
125 " {} Tokens: {} in / {} out",
126 "·".dimmed(),
127 input_tokens,
128 output_tokens
129 );
130 println!(
131 " {} Cost: ${:.4}",
132 "·".dimmed(),
133 cost_usd
134 );
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_output_mode_default_is_normal() {
143 let mode = OutputMode::default();
144 assert_eq!(mode, OutputMode::Normal);
145 }
146
147 #[test]
148 fn test_output_mode_variants() {
149 let modes = [
150 OutputMode::Normal,
151 OutputMode::Quiet,
152 OutputMode::Verbose,
153 OutputMode::Json,
154 ];
155 for m in &modes {
157 assert_eq!(m, &m.clone());
158 }
159 }
160
161 #[test]
162 fn test_map_item_does_not_panic() {
163 map_item(1, 5, "some_file.rs");
165 map_item(5, 5, "last_file.rs");
166 }
167
168 #[test]
169 fn test_parallel_step_does_not_panic() {
170 parallel_step("compile");
171 parallel_step("lint");
172 }
173
174 #[test]
175 fn test_workflow_summary_does_not_panic() {
176 workflow_summary(
177 10,
178 Duration::from_secs(42),
179 1234,
180 567,
181 0.0023,
182 );
183 }
184}