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            "  {} ({}, enabled: {})",
149            name,
150            mcp.server_type.as_str(),
151            mcp.enabled
152        )));
153        lines.push(DisplayLine::key_value(
154            "    endpoint",
155            mcp.endpoint
156                .as_deref()
157                .unwrap_or("<derived from api_external_url>"),
158        ));
159        if let Some(port) = mcp.port {
160            lines.push(DisplayLine::key_value("    port", port.to_string()));
161        }
162        if let Some(binary) = mcp.binary.as_deref() {
163            lines.push(DisplayLine::key_value("    binary", binary));
164        }
165    }
166    lines
167}
168
169pub fn render_skills_section(skills: &SkillsConfig) -> Vec<DisplayLine> {
170    let mut lines = vec![
171        DisplayLine::section(format!("Skills ({})", skills.skills.len())),
172        DisplayLine::key_value("  enabled", skills.enabled.to_string()),
173    ];
174    for (name, skill) in &skills.skills {
175        lines.push(DisplayLine::info(format!(
176            "  {} (enabled: {})",
177            name, skill.enabled
178        )));
179        lines.push(DisplayLine::key_value("    id", skill.id.as_str()));
180        lines.push(DisplayLine::key_value("    name", &skill.name));
181    }
182    lines
183}
184
185pub fn render_ai_section(ai: &AiConfig) -> Vec<DisplayLine> {
186    let mut lines = vec![DisplayLine::section("AI Configuration")];
187    if !ai.default_provider.is_empty() {
188        lines.push(DisplayLine::key_value(
189            "  default_provider",
190            &ai.default_provider,
191        ));
192    }
193    for (name, provider) in &ai.providers {
194        lines.push(DisplayLine::info(format!(
195            "  {} (enabled: {})",
196            name, provider.enabled
197        )));
198        if !provider.default_model.is_empty() {
199            lines.push(DisplayLine::key_value(
200                "    default_model",
201                &provider.default_model,
202            ));
203        }
204    }
205    lines
206}
207
208pub fn render_web_section(web: &WebConfig) -> Vec<DisplayLine> {
209    vec![
210        DisplayLine::section("Web Configuration"),
211        DisplayLine::key_value("  site_name", &web.branding.name),
212        DisplayLine::key_value("  title", &web.branding.title),
213    ]
214}
215
216pub fn render_content_section(content: &ContentConfigRaw) -> Vec<DisplayLine> {
217    let mut lines = vec![DisplayLine::section(format!(
218        "Content Sources ({})",
219        content.content_sources.len()
220    ))];
221    for (name, source) in &content.content_sources {
222        lines.push(DisplayLine::info(format!(
223            "  {} (enabled: {})",
224            name, source.enabled
225        )));
226        lines.push(DisplayLine::key_value("    path", &source.path));
227    }
228    lines
229}