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