Skip to main content

systemprompt_logging/services/cli/
service.rs

1//! The [`CliService`] facade over CLI output.
2//!
3//! Aggregates the display, table, and progress helpers into a single entry
4//! point for command code: levelled messages (which also publish a log event),
5//! structured output (`json`/`yaml`), and spinners and progress bars. Output is
6//! the sanctioned stderr/stdout sink, not `tracing`.
7
8use std::io::Write;
9use std::time::Duration;
10
11use indicatif::{ProgressBar, ProgressStyle};
12use serde::Serialize;
13use systemprompt_traits::LogEventLevel;
14
15use super::display::DisplayUtils;
16use super::output::{mark_structured_emitted, publish_log};
17use super::theme::{EmphasisType, ItemStatus, MessageLevel, Theme};
18
19#[derive(Copy, Clone, Debug)]
20pub struct CliService;
21
22impl CliService {
23    pub fn success(message: &str) {
24        publish_log(LogEventLevel::Info, "cli", message);
25        DisplayUtils::message(MessageLevel::Success, message);
26    }
27
28    pub fn warning(message: &str) {
29        publish_log(LogEventLevel::Warn, "cli", message);
30        DisplayUtils::message(MessageLevel::Warning, message);
31    }
32
33    pub fn error(message: &str) {
34        publish_log(LogEventLevel::Error, "cli", message);
35        DisplayUtils::message(MessageLevel::Error, message);
36    }
37
38    pub fn info(message: &str) {
39        publish_log(LogEventLevel::Info, "cli", message);
40        DisplayUtils::message(MessageLevel::Info, message);
41    }
42
43    pub fn debug(message: &str) {
44        let debug_msg = format!("DEBUG: {message}");
45        publish_log(LogEventLevel::Debug, "cli", &debug_msg);
46        DisplayUtils::message(MessageLevel::Info, &debug_msg);
47    }
48
49    pub fn verbose(message: &str) {
50        publish_log(LogEventLevel::Debug, "cli", message);
51        DisplayUtils::message(MessageLevel::Info, message);
52    }
53
54    pub fn fatal(message: &str, exit_code: i32) -> ! {
55        let fatal_msg = format!("FATAL: {message}");
56        DisplayUtils::message(MessageLevel::Error, &fatal_msg);
57        std::process::exit(exit_code);
58    }
59
60    pub fn section(title: &str) {
61        DisplayUtils::section_header(title);
62    }
63
64    pub fn subsection(title: &str) {
65        DisplayUtils::subsection_header(title);
66    }
67
68    pub fn clear_screen() {
69        let mut stderr = std::io::stderr();
70        if let Err(e) = write!(stderr, "\x1B[2J\x1B[1;1H") {
71            tracing::debug!(error = %e, "cli clear_screen write failed");
72        }
73    }
74
75    pub fn output(content: &str) {
76        let mut stdout = std::io::stdout();
77        if let Err(e) = writeln!(stdout, "{content}") {
78            tracing::debug!(error = %e, "cli output write failed");
79        }
80    }
81
82    pub fn json<T: Serialize>(value: &T) {
83        mark_structured_emitted();
84        match serde_json::to_string_pretty(value) {
85            Ok(json) => {
86                let mut stdout = std::io::stdout();
87                if let Err(e) = writeln!(stdout, "{json}") {
88                    tracing::debug!(error = %e, "cli json write failed");
89                }
90            },
91            Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
92        }
93    }
94
95    pub fn json_compact<T: Serialize>(value: &T) {
96        mark_structured_emitted();
97        match serde_json::to_string(value) {
98            Ok(json) => {
99                let mut stdout = std::io::stdout();
100                if let Err(e) = writeln!(stdout, "{json}") {
101                    tracing::debug!(error = %e, "cli json_compact write failed");
102                }
103            },
104            Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
105        }
106    }
107
108    pub fn yaml<T: Serialize>(value: &T) {
109        mark_structured_emitted();
110        match serde_yaml::to_string(value) {
111            Ok(yaml) => {
112                let mut stdout = std::io::stdout();
113                if let Err(e) = write!(stdout, "{yaml}") {
114                    tracing::debug!(error = %e, "cli yaml write failed");
115                }
116            },
117            Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
118        }
119    }
120
121    pub fn key_value(label: &str, value: &str) {
122        let mut stderr = std::io::stderr();
123        if let Err(e) = writeln!(
124            stderr,
125            "{}: {}",
126            Theme::color(label, EmphasisType::Bold),
127            Theme::color(value, EmphasisType::Highlight)
128        ) {
129            tracing::debug!(error = %e, "cli key_value write failed");
130        }
131    }
132
133    pub fn status_line(label: &str, value: &str, status: ItemStatus) {
134        let mut stderr = std::io::stderr();
135        if let Err(e) = writeln!(
136            stderr,
137            "{} {}: {}",
138            Theme::icon(status),
139            Theme::color(label, EmphasisType::Bold),
140            Theme::color(value, status)
141        ) {
142            tracing::debug!(error = %e, "cli status_line write failed");
143        }
144    }
145
146    pub fn spinner(message: &str) -> ProgressBar {
147        let pb = ProgressBar::new_spinner();
148        pb.set_style(
149            ProgressStyle::default_spinner()
150                .template("{spinner:.cyan} {msg}")
151                .unwrap_or_else(|_| ProgressStyle::default_spinner()),
152        );
153        pb.set_message(message.to_owned());
154        pb.enable_steady_tick(Duration::from_millis(100));
155        pb
156    }
157
158    pub fn progress_bar(total: u64) -> ProgressBar {
159        let pb = ProgressBar::new(total);
160        pb.set_style(
161            ProgressStyle::default_bar()
162                .template(
163                    "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
164                )
165                .unwrap_or_else(|_| ProgressStyle::default_bar())
166                .progress_chars("#>-"),
167        );
168        pb
169    }
170
171    pub fn table(headers: &[&str], rows: &[Vec<String>]) {
172        super::table::render_table(headers, rows);
173    }
174}