Skip to main content

systemprompt_cli/commands/cloud/profile/
show_display.rs

1//! Terminal rendering of profile sections for `cloud profile show`.
2//!
3//! Each section renders to [`DisplayLine`]s; `print_formatted_config` emits
4//! them through `CliService`.
5//!
6//! Copyright (c) systemprompt.io — Business Source License 1.1.
7//! See <https://systemprompt.io> for licensing details.
8
9use std::collections::HashMap;
10use std::hash::BuildHasher;
11use systemprompt_logging::CliService;
12use systemprompt_models::{
13    AgentConfig, AiConfig, ContentConfigRaw, Deployment, SkillsConfig, WebConfig,
14};
15
16use super::show_types::{EnvironmentConfig, FullConfig, SettingsOutput};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum DisplayLine {
20    Section(String),
21    Info(String),
22    KeyValue(String, String),
23}
24
25impl DisplayLine {
26    fn section(title: impl Into<String>) -> Self {
27        Self::Section(title.into())
28    }
29
30    fn info(text: impl Into<String>) -> Self {
31        Self::Info(text.into())
32    }
33
34    fn key_value(label: impl Into<String>, value: impl Into<String>) -> Self {
35        Self::KeyValue(label.into(), value.into())
36    }
37}
38
39pub(super) fn print_formatted_config(config: &FullConfig) {
40    for line in render_formatted_config(config) {
41        match line {
42            DisplayLine::Section(title) => CliService::section(&title),
43            DisplayLine::Info(text) => CliService::info(&text),
44            DisplayLine::KeyValue(label, value) => CliService::key_value(&label, &value),
45        }
46    }
47}
48
49pub fn render_formatted_config(config: &FullConfig) -> Vec<DisplayLine> {
50    let mut lines = Vec::new();
51    if let Some(env) = &config.environment {
52        lines.extend(render_environment_section(env));
53    }
54    if let Some(settings) = &config.settings {
55        lines.extend(render_settings_section(settings));
56    }
57    if let Some(agents) = &config.agents {
58        lines.extend(render_agents_section(agents));
59    }
60    if let Some(mcp_servers) = &config.mcp_servers {
61        lines.extend(render_mcp_section(mcp_servers));
62    }
63    if let Some(skills) = &config.skills {
64        lines.extend(render_skills_section(skills));
65    }
66    if let Some(ai) = &config.ai {
67        lines.extend(render_ai_section(ai));
68    }
69    if let Some(web) = &config.web {
70        lines.extend(render_web_section(web));
71    }
72    if let Some(content) = &config.content {
73        lines.extend(render_content_section(content));
74    }
75    lines
76}
77
78pub fn render_environment_section(env: &EnvironmentConfig) -> Vec<DisplayLine> {
79    vec![
80        DisplayLine::section("Environment Configuration"),
81        DisplayLine::info("Core Settings:"),
82        DisplayLine::key_value("  sitename", &env.core.sitename),
83        DisplayLine::key_value("  host", &env.core.host),
84        DisplayLine::key_value("  port", env.core.port.to_string()),
85        DisplayLine::key_value("  api_server_url", &env.core.api_server_url),
86        DisplayLine::key_value("  api_external_url", &env.core.api_external_url),
87        DisplayLine::key_value("  use_https", env.core.use_https.to_string()),
88        DisplayLine::info("Database:"),
89        DisplayLine::key_value("  type", &env.database.database_type),
90        DisplayLine::key_value("  url", &env.database.database_url),
91        DisplayLine::info("JWT:"),
92        DisplayLine::key_value("  issuer", &env.jwt.issuer),
93        DisplayLine::key_value("  secret", &env.jwt.secret),
94    ]
95}
96
97pub fn render_settings_section(settings: &SettingsOutput) -> Vec<DisplayLine> {
98    vec![
99        DisplayLine::section("Services Settings"),
100        DisplayLine::key_value(
101            "  agent_port_range",
102            format!(
103                "{}-{}",
104                settings.agent_port_range.0, settings.agent_port_range.1
105            ),
106        ),
107        DisplayLine::key_value(
108            "  mcp_port_range",
109            format!(
110                "{}-{}",
111                settings.mcp_port_range.0, settings.mcp_port_range.1
112            ),
113        ),
114        DisplayLine::key_value(
115            "  auto_start_enabled",
116            settings.auto_start_enabled.to_string(),
117        ),
118    ]
119}
120
121pub fn render_agents_section<S: BuildHasher>(
122    agents: &HashMap<String, AgentConfig, S>,
123) -> Vec<DisplayLine> {
124    let mut lines = vec![DisplayLine::section(format!("Agents ({})", agents.len()))];
125    for (name, agent) in agents {
126        lines.push(DisplayLine::info(format!(
127            "  {} (port: {}, enabled: {})",
128            name, agent.port, agent.enabled
129        )));
130        lines.push(DisplayLine::key_value("    endpoint", &agent.endpoint));
131        lines.push(DisplayLine::key_value(
132            "    display_name",
133            &agent.card.display_name,
134        ));
135    }
136    lines
137}
138
139pub fn render_mcp_section<S: BuildHasher>(
140    mcp_servers: &HashMap<String, Deployment, S>,
141) -> Vec<DisplayLine> {
142    let mut lines = vec![DisplayLine::section(format!(
143        "MCP Servers ({})",
144        mcp_servers.len()
145    ))];
146    for (name, mcp) in mcp_servers {
147        lines.push(DisplayLine::info(format!(
148            "  {} (port: {}, enabled: {})",
149            name, mcp.port, mcp.enabled
150        )));
151        lines.push(DisplayLine::key_value(
152            "    endpoint",
153            mcp.endpoint
154                .as_deref()
155                .unwrap_or("<derived from api_external_url>"),
156        ));
157        lines.push(DisplayLine::key_value("    binary", &mcp.binary));
158    }
159    lines
160}
161
162pub fn render_skills_section(skills: &SkillsConfig) -> Vec<DisplayLine> {
163    let mut lines = vec![
164        DisplayLine::section(format!("Skills ({})", skills.skills.len())),
165        DisplayLine::key_value("  enabled", skills.enabled.to_string()),
166    ];
167    for (name, skill) in &skills.skills {
168        lines.push(DisplayLine::info(format!(
169            "  {} (enabled: {})",
170            name, skill.enabled
171        )));
172        lines.push(DisplayLine::key_value("    id", skill.id.as_str()));
173        lines.push(DisplayLine::key_value("    name", &skill.name));
174    }
175    lines
176}
177
178pub fn render_ai_section(ai: &AiConfig) -> Vec<DisplayLine> {
179    let mut lines = vec![DisplayLine::section("AI Configuration")];
180    if !ai.default_provider.is_empty() {
181        lines.push(DisplayLine::key_value(
182            "  default_provider",
183            &ai.default_provider,
184        ));
185    }
186    for (name, provider) in &ai.providers {
187        lines.push(DisplayLine::info(format!(
188            "  {} (enabled: {})",
189            name, provider.enabled
190        )));
191        if !provider.default_model.is_empty() {
192            lines.push(DisplayLine::key_value(
193                "    default_model",
194                &provider.default_model,
195            ));
196        }
197    }
198    lines
199}
200
201pub fn render_web_section(web: &WebConfig) -> Vec<DisplayLine> {
202    vec![
203        DisplayLine::section("Web Configuration"),
204        DisplayLine::key_value("  site_name", &web.branding.name),
205        DisplayLine::key_value("  title", &web.branding.title),
206    ]
207}
208
209pub fn render_content_section(content: &ContentConfigRaw) -> Vec<DisplayLine> {
210    let mut lines = vec![DisplayLine::section(format!(
211        "Content Sources ({})",
212        content.content_sources.len()
213    ))];
214    for (name, source) in &content.content_sources {
215        lines.push(DisplayLine::info(format!(
216            "  {} (enabled: {})",
217            name, source.enabled
218        )));
219        lines.push(DisplayLine::key_value("    path", &source.path));
220    }
221    lines
222}