systemprompt_cli/commands/admin/config/
runtime.rs1use anyhow::{Context, Result, bail};
10use clap::{Args, Subcommand};
11use std::fs;
12use systemprompt_config::ProfileBootstrap;
13use systemprompt_logging::CliService;
14use systemprompt_models::Profile;
15use systemprompt_models::profile::{Environment, LogLevel, OutputFormat as ProfileOutputFormat};
16
17use super::types::{RuntimeConfigOutput, RuntimeSetOutput};
18use crate::CliConfig;
19use crate::cli_settings::OutputFormat;
20use crate::shared::{CommandOutput, render_result};
21
22#[derive(Debug, Subcommand)]
23pub enum RuntimeCommands {
24 #[command(about = "Show runtime configuration", alias = "list")]
25 Show,
26
27 #[command(about = "Set runtime configuration value")]
28 Set(SetArgs),
29}
30
31#[derive(Debug, Clone, Args)]
32pub struct SetArgs {
33 #[arg(long, help = "Environment: development, test, staging, production")]
34 pub environment: Option<String>,
35
36 #[arg(long, help = "Log level: quiet, normal, verbose, debug")]
37 pub log_level: Option<String>,
38
39 #[arg(long, help = "Output format: text, json, yaml")]
40 pub output_format: Option<String>,
41
42 #[arg(long, help = "Disable colored output")]
43 pub no_color: Option<bool>,
44}
45
46pub fn execute(command: RuntimeCommands, config: &CliConfig) -> Result<()> {
47 match command {
48 RuntimeCommands::Show => execute_show(config),
49 RuntimeCommands::Set(args) => execute_set(args, config),
50 }
51}
52
53pub(super) fn execute_show(config: &CliConfig) -> Result<()> {
54 let profile = ProfileBootstrap::get()?;
55
56 let output = RuntimeConfigOutput {
57 environment: profile.runtime.environment.to_string(),
58 log_level: profile.runtime.log_level.to_string(),
59 output_format: profile.runtime.output_format.to_string(),
60 no_color: profile.runtime.no_color,
61 non_interactive: profile.runtime.non_interactive,
62 };
63
64 render_result(
65 &CommandOutput::card_value("Runtime Configuration", &output),
66 config,
67 );
68
69 Ok(())
70}
71
72pub(super) fn execute_set(args: SetArgs, config: &CliConfig) -> Result<()> {
73 if args.environment.is_none()
74 && args.log_level.is_none()
75 && args.output_format.is_none()
76 && args.no_color.is_none()
77 {
78 bail!(
79 "Must specify at least one option: --environment, --log-level, --output-format, \
80 --no-color"
81 );
82 }
83
84 let profile_path = ProfileBootstrap::get_path()?;
85 let mut profile = load_profile(profile_path)?;
86
87 let mut changes: Vec<RuntimeSetOutput> = Vec::new();
88
89 if let Some(env_str) = args.environment {
90 let env: Environment = env_str.parse().map_err(|e: String| anyhow::anyhow!(e))?;
91 let old = profile.runtime.environment.to_string();
92 profile.runtime.environment = env;
93 changes.push(RuntimeSetOutput {
94 field: "environment".to_owned(),
95 old_value: old,
96 new_value: env.to_string(),
97 message: format!("Updated environment to {}", env),
98 });
99 }
100
101 if let Some(level_str) = args.log_level {
102 let level: LogLevel = level_str.parse().map_err(|e: String| anyhow::anyhow!(e))?;
103 let old = profile.runtime.log_level.to_string();
104 profile.runtime.log_level = level;
105 changes.push(RuntimeSetOutput {
106 field: "log_level".to_owned(),
107 old_value: old,
108 new_value: level.to_string(),
109 message: format!("Updated log_level to {}", level),
110 });
111 }
112
113 if let Some(format_str) = args.output_format {
114 let format: ProfileOutputFormat =
115 format_str.parse().map_err(|e: String| anyhow::anyhow!(e))?;
116 let old = profile.runtime.output_format.to_string();
117 profile.runtime.output_format = format;
118 changes.push(RuntimeSetOutput {
119 field: "output_format".to_owned(),
120 old_value: old,
121 new_value: format.to_string(),
122 message: format!("Updated output_format to {}", format),
123 });
124 }
125
126 if let Some(no_color) = args.no_color {
127 let old = profile.runtime.no_color;
128 profile.runtime.no_color = no_color;
129 changes.push(RuntimeSetOutput {
130 field: "no_color".to_owned(),
131 old_value: old.to_string(),
132 new_value: no_color.to_string(),
133 message: format!("Updated no_color to {}", no_color),
134 });
135 }
136
137 save_profile(&profile, profile_path)?;
138
139 for change in &changes {
140 render_result(
141 &CommandOutput::card_value("Runtime Updated", change),
142 config,
143 );
144 }
145
146 if config.output_format() == OutputFormat::Table {
147 CliService::warning("Restart services for changes to take effect");
148 }
149
150 Ok(())
151}
152
153fn load_profile(path: &str) -> Result<Profile> {
154 let content =
155 fs::read_to_string(path).with_context(|| format!("Failed to read profile: {}", path))?;
156 let profile: Profile = serde_yaml::from_str(&content)
157 .with_context(|| format!("Failed to parse profile: {}", path))?;
158 Ok(profile)
159}
160
161pub(super) fn save_profile(profile: &Profile, path: &str) -> Result<()> {
162 let content = serde_yaml::to_string(profile).context("Failed to serialize profile")?;
163 fs::write(path, content).with_context(|| format!("Failed to write profile: {}", path))?;
164 Ok(())
165}