Skip to main content

systemprompt_logging/services/cli/
banners.rs

1//! Banner and session-context rendering helpers for [`CliService`].
2//!
3//! These methods own the side-effect of writing branded headers and footers to
4//! stderr. They are co-located with [`CliService`] but split out for cohesion.
5//!
6//! Copyright (c) systemprompt.io — Business Source License 1.1.
7//! See <https://systemprompt.io> for licensing details.
8
9use std::io::Write;
10use std::time::Duration;
11
12use indicatif::{ProgressBar, ProgressStyle};
13use systemprompt_traits::LogEventLevel;
14
15use super::output::publish_log;
16use super::service::CliService;
17use super::startup::{
18    render_phase_header, render_phase_info, render_phase_success, render_phase_warning,
19    render_startup_banner,
20};
21use super::table::render_startup_complete;
22use super::theme::{EmphasisType, Theme};
23
24impl CliService {
25    pub fn startup_banner(subtitle: Option<&str>) {
26        render_startup_banner(subtitle);
27    }
28
29    pub fn phase(name: &str) {
30        publish_log(LogEventLevel::Info, "cli", &format!("Phase: {}", name));
31        render_phase_header(name);
32    }
33
34    pub fn phase_success(message: &str, detail: Option<&str>) {
35        publish_log(LogEventLevel::Info, "cli", message);
36        render_phase_success(message, detail);
37    }
38
39    pub fn phase_info(message: &str, detail: Option<&str>) {
40        publish_log(LogEventLevel::Info, "cli", message);
41        render_phase_info(message, detail);
42    }
43
44    pub fn phase_warning(message: &str, detail: Option<&str>) {
45        publish_log(LogEventLevel::Warn, "cli", message);
46        render_phase_warning(message, detail);
47    }
48
49    pub fn service_spinner(service_name: &str, port: Option<u16>) -> ProgressBar {
50        let msg = port.map_or_else(
51            || format!("Starting {}", service_name),
52            |p| format!("Starting {} on :{}", service_name, p),
53        );
54        let pb = ProgressBar::new_spinner();
55        let spinner_template = concat!("{spinner:.208}", " {msg}");
56        pb.set_style(
57            ProgressStyle::default_spinner()
58                .template(spinner_template)
59                .unwrap_or_else(|_| ProgressStyle::default_spinner()),
60        );
61        pb.set_message(msg);
62        pb.enable_steady_tick(Duration::from_millis(80));
63        pb
64    }
65
66    pub fn startup_complete(duration: Duration, api_url: &str) {
67        publish_log(
68            LogEventLevel::Info,
69            "cli",
70            &format!("Startup complete in {:.1}s", duration.as_secs_f64()),
71        );
72        render_startup_complete(duration, api_url);
73    }
74
75    pub fn session_context(
76        profile: &str,
77        session_id: &systemprompt_identifiers::SessionId,
78        tenant: Option<&str>,
79    ) {
80        Self::session_context_with_url(profile, session_id, tenant, None);
81    }
82
83    pub fn session_context_with_url(
84        profile: &str,
85        session_id: &systemprompt_identifiers::SessionId,
86        tenant: Option<&str>,
87        api_url: Option<&str>,
88    ) {
89        let session_str = session_id.as_str();
90        let truncated_session = session_str
91            .get(..12)
92            .map_or_else(|| session_str.to_owned(), |s| format!("{}...", s));
93
94        let tenant_info = tenant.map_or_else(String::new, |t| format!(" | tenant: {}", t));
95
96        let url_info = api_url.map_or_else(String::new, |u| format!(" | {}", u));
97
98        let banner = format!(
99            "[profile: {} | session: {}{}{}]",
100            profile, truncated_session, tenant_info, url_info
101        );
102
103        let mut stderr = std::io::stderr();
104        writeln!(stderr, "{}", Theme::color(&banner, EmphasisType::Dim)).ok();
105    }
106
107    pub fn profile_banner(profile_name: &str, is_cloud: bool, tenant: Option<&str>) {
108        let target_label = if is_cloud { "cloud" } else { "local" };
109        let tenant_info = tenant.map_or_else(String::new, |t| format!(" | tenant: {}", t));
110        let banner = format!(
111            "[profile: {} ({}){}]",
112            profile_name, target_label, tenant_info
113        );
114        let mut stderr = std::io::stderr();
115        writeln!(stderr, "{}", Theme::color(&banner, EmphasisType::Dim)).ok();
116    }
117}