systemprompt_logging/services/cli/
service.rs1use std::io::Write;
10use std::time::Duration;
11
12use crate::models::LoggingError;
13pub(super) type Result<T> = std::result::Result<T, LoggingError>;
14use indicatif::{ProgressBar, ProgressStyle};
15use serde::Serialize;
16use systemprompt_traits::LogEventLevel;
17
18use super::display::{CollectionDisplay, Display, DisplayUtils};
19use super::module::{BatchModuleOperations, ModuleDisplay, ModuleInstall, ModuleUpdate};
20use super::output::{mark_structured_emitted, publish_log};
21use super::prompts::{PromptBuilder, Prompts};
22use super::summary::{OperationResult, ProgressSummary, ValidationSummary};
23use super::theme::{EmphasisType, ItemStatus, MessageLevel, ModuleType, Theme};
24
25#[derive(Copy, Clone, Debug)]
26pub struct CliService;
27
28impl CliService {
29 pub fn success(message: &str) {
30 publish_log(LogEventLevel::Info, "cli", message);
31 DisplayUtils::message(MessageLevel::Success, message);
32 }
33
34 pub fn warning(message: &str) {
35 publish_log(LogEventLevel::Warn, "cli", message);
36 DisplayUtils::message(MessageLevel::Warning, message);
37 }
38
39 pub fn error(message: &str) {
40 publish_log(LogEventLevel::Error, "cli", message);
41 DisplayUtils::message(MessageLevel::Error, message);
42 }
43
44 pub fn info(message: &str) {
45 publish_log(LogEventLevel::Info, "cli", message);
46 DisplayUtils::message(MessageLevel::Info, message);
47 }
48
49 pub fn debug(message: &str) {
50 let debug_msg = format!("DEBUG: {message}");
51 publish_log(LogEventLevel::Debug, "cli", &debug_msg);
52 DisplayUtils::message(MessageLevel::Info, &debug_msg);
53 }
54
55 pub fn verbose(message: &str) {
56 publish_log(LogEventLevel::Debug, "cli", message);
57 DisplayUtils::message(MessageLevel::Info, message);
58 }
59
60 pub fn fatal(message: &str, exit_code: i32) -> ! {
61 let fatal_msg = format!("FATAL: {message}");
62 DisplayUtils::message(MessageLevel::Error, &fatal_msg);
63 std::process::exit(exit_code);
64 }
65
66 pub fn section(title: &str) {
67 DisplayUtils::section_header(title);
68 }
69
70 pub fn subsection(title: &str) {
71 DisplayUtils::subsection_header(title);
72 }
73
74 pub fn clear_screen() {
75 let mut stderr = std::io::stderr();
76 if let Err(e) = write!(stderr, "\x1B[2J\x1B[1;1H") {
77 tracing::debug!(error = %e, "cli clear_screen write failed");
78 }
79 }
80
81 pub fn output(content: &str) {
82 let mut stdout = std::io::stdout();
83 if let Err(e) = writeln!(stdout, "{content}") {
84 tracing::debug!(error = %e, "cli output write failed");
85 }
86 }
87
88 pub fn json<T: Serialize>(value: &T) {
89 mark_structured_emitted();
90 match serde_json::to_string_pretty(value) {
91 Ok(json) => {
92 let mut stdout = std::io::stdout();
93 if let Err(e) = writeln!(stdout, "{json}") {
94 tracing::debug!(error = %e, "cli json write failed");
95 }
96 },
97 Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
98 }
99 }
100
101 pub fn json_compact<T: Serialize>(value: &T) {
102 mark_structured_emitted();
103 match serde_json::to_string(value) {
104 Ok(json) => {
105 let mut stdout = std::io::stdout();
106 if let Err(e) = writeln!(stdout, "{json}") {
107 tracing::debug!(error = %e, "cli json_compact write failed");
108 }
109 },
110 Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
111 }
112 }
113
114 pub fn yaml<T: Serialize>(value: &T) {
115 mark_structured_emitted();
116 match serde_yaml::to_string(value) {
117 Ok(yaml) => {
118 let mut stdout = std::io::stdout();
119 if let Err(e) = write!(stdout, "{yaml}") {
120 tracing::debug!(error = %e, "cli yaml write failed");
121 }
122 },
123 Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
124 }
125 }
126
127 pub fn key_value(label: &str, value: &str) {
128 let mut stderr = std::io::stderr();
129 if let Err(e) = writeln!(
130 stderr,
131 "{}: {}",
132 Theme::color(label, EmphasisType::Bold),
133 Theme::color(value, EmphasisType::Highlight)
134 ) {
135 tracing::debug!(error = %e, "cli key_value write failed");
136 }
137 }
138
139 pub fn status_line(label: &str, value: &str, status: ItemStatus) {
140 let mut stderr = std::io::stderr();
141 if let Err(e) = writeln!(
142 stderr,
143 "{} {}: {}",
144 Theme::icon(status),
145 Theme::color(label, EmphasisType::Bold),
146 Theme::color(value, status)
147 ) {
148 tracing::debug!(error = %e, "cli status_line write failed");
149 }
150 }
151
152 pub fn spinner(message: &str) -> ProgressBar {
153 let pb = ProgressBar::new_spinner();
154 pb.set_style(
155 ProgressStyle::default_spinner()
156 .template("{spinner:.cyan} {msg}")
157 .unwrap_or_else(|_| ProgressStyle::default_spinner()),
158 );
159 pb.set_message(message.to_owned());
160 pb.enable_steady_tick(Duration::from_millis(100));
161 pb
162 }
163
164 pub fn progress_bar(total: u64) -> ProgressBar {
165 let pb = ProgressBar::new(total);
166 pb.set_style(
167 ProgressStyle::default_bar()
168 .template(
169 "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
170 )
171 .unwrap_or_else(|_| ProgressStyle::default_bar())
172 .progress_chars("#>-"),
173 );
174 pb
175 }
176
177 pub fn timed<F, R>(label: &str, f: F) -> R
178 where
179 F: FnOnce() -> R,
180 {
181 let start = std::time::Instant::now();
182 let result = f();
183 let duration = start.elapsed();
184 let duration_secs = duration.as_secs_f64();
185 let info_msg = format!("{label} completed in {duration_secs:.2}s");
186 Self::info(&info_msg);
187 result
188 }
189
190 pub fn prompt_schemas(module_name: &str, schemas: &[(String, String)]) -> Result<bool> {
191 ModuleDisplay::prompt_apply_schemas(module_name, schemas)
192 }
193
194 pub fn prompt_seeds(module_name: &str, seeds: &[(String, String)]) -> Result<bool> {
195 ModuleDisplay::prompt_apply_seeds(module_name, seeds)
196 }
197
198 pub fn prompt_install(modules: &[String]) -> Result<bool> {
199 Prompts::confirm_install(modules)
200 }
201
202 pub fn prompt_update(updates: &[(String, String, String)]) -> Result<bool> {
203 Prompts::confirm_update(updates)
204 }
205
206 pub fn confirm(question: &str) -> Result<bool> {
207 Prompts::confirm(question, false)
208 }
209
210 pub fn confirm_default_yes(question: &str) -> Result<bool> {
211 Prompts::confirm(question, true)
212 }
213
214 pub fn display_validation_summary(summary: &ValidationSummary) {
215 summary.display();
216 }
217
218 pub fn display_result(result: &OperationResult) {
219 result.display();
220 }
221
222 pub fn display_progress(progress: &ProgressSummary) {
223 progress.display();
224 }
225
226 pub fn prompt_builder(message: &str) -> PromptBuilder {
227 PromptBuilder::new(message)
228 }
229
230 pub fn collection<T: Display>(title: &str, items: Vec<T>) -> CollectionDisplay<T> {
231 CollectionDisplay::new(title, items)
232 }
233
234 pub fn module_status(module_name: &str, message: &str) {
235 DisplayUtils::module_status(module_name, message);
236 }
237
238 pub fn relationship(from: &str, to: &str, status: ItemStatus, module_type: ModuleType) {
239 DisplayUtils::relationship(module_type, from, to, status);
240 }
241
242 pub fn item(status: ItemStatus, name: &str, detail: Option<&str>) {
243 DisplayUtils::item(status, name, detail);
244 }
245
246 pub fn batch_install(modules: &[ModuleInstall]) -> Result<bool> {
247 BatchModuleOperations::prompt_install_multiple(modules)
248 }
249
250 pub fn batch_update(updates: &[ModuleUpdate]) -> Result<bool> {
251 BatchModuleOperations::prompt_update_multiple(updates)
252 }
253
254 pub fn table(headers: &[&str], rows: &[Vec<String>]) {
255 super::table::render_table(headers, rows);
256 }
257}