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