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::time::Duration;
10
11use indicatif::{ProgressBar, ProgressStyle};
12
13use super::service::CliService;
14use super::sink::stderr_writeln;
15use super::startup::{
16    render_phase_header, render_phase_info, render_phase_success, render_phase_warning,
17    render_startup_banner,
18};
19use super::table::render_startup_complete;
20use super::theme::{EmphasisType, Theme};
21
22impl CliService {
23    pub fn startup_banner(subtitle: Option<&str>) {
24        render_startup_banner(subtitle);
25    }
26
27    pub fn phase(name: &str) {
28        render_phase_header(name);
29    }
30
31    pub fn phase_success(message: &str, detail: Option<&str>) {
32        render_phase_success(message, detail);
33    }
34
35    pub fn phase_info(message: &str, detail: Option<&str>) {
36        render_phase_info(message, detail);
37    }
38
39    pub fn phase_warning(message: &str, detail: Option<&str>) {
40        render_phase_warning(message, detail);
41    }
42
43    pub fn service_spinner(service_name: &str, port: Option<u16>) -> ProgressBar {
44        let msg = port.map_or_else(
45            || format!("Starting {}", service_name),
46            |p| format!("Starting {} on :{}", service_name, p),
47        );
48        let pb = ProgressBar::new_spinner();
49        let spinner_template = concat!("{spinner:.208}", " {msg}");
50        pb.set_style(
51            ProgressStyle::default_spinner()
52                .template(spinner_template)
53                .unwrap_or_else(|_| ProgressStyle::default_spinner()),
54        );
55        pb.set_message(msg);
56        pb.enable_steady_tick(Duration::from_millis(80));
57        pb
58    }
59
60    pub fn startup_complete(duration: Duration, api_url: &str) {
61        render_startup_complete(duration, api_url);
62    }
63
64    pub fn session_context(
65        profile: &str,
66        session_id: &systemprompt_identifiers::SessionId,
67        tenant: Option<&str>,
68    ) {
69        Self::session_context_with_url(profile, session_id, tenant, None);
70    }
71
72    pub fn session_context_with_url(
73        profile: &str,
74        session_id: &systemprompt_identifiers::SessionId,
75        tenant: Option<&str>,
76        api_url: Option<&str>,
77    ) {
78        let session_str = session_id.as_str();
79        let truncated_session = session_str
80            .get(..12)
81            .map_or_else(|| session_str.to_owned(), |s| format!("{}...", s));
82
83        let tenant_info = tenant.map_or_else(String::new, |t| format!(" | tenant: {}", t));
84
85        let url_info = api_url.map_or_else(String::new, |u| format!(" | {}", u));
86
87        let banner = format!(
88            "[profile: {} | session: {}{}{}]",
89            profile, truncated_session, tenant_info, url_info
90        );
91
92        stderr_writeln(format_args!("{}", Theme::color(&banner, EmphasisType::Dim)));
93    }
94
95    pub fn profile_banner(profile_name: &str, is_cloud: bool, tenant: Option<&str>) {
96        let target_label = if is_cloud { "cloud" } else { "local" };
97        let tenant_info = tenant.map_or_else(String::new, |t| format!(" | tenant: {}", t));
98        let banner = format!(
99            "[profile: {} ({}){}]",
100            profile_name, target_label, tenant_info
101        );
102        stderr_writeln(format_args!("{}", Theme::color(&banner, EmphasisType::Dim)));
103    }
104}