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_manifest::Profile;
15use systemprompt_manifest::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()?;
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()?;
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 = format_str.parse()?;
115 let old = profile.runtime.output_format.to_string();
116 profile.runtime.output_format = format;
117 changes.push(RuntimeSetOutput {
118 field: "output_format".to_owned(),
119 old_value: old,
120 new_value: format.to_string(),
121 message: format!("Updated output_format to {}", format),
122 });
123 }
124
125 if let Some(no_color) = args.no_color {
126 let old = profile.runtime.no_color;
127 profile.runtime.no_color = no_color;
128 changes.push(RuntimeSetOutput {
129 field: "no_color".to_owned(),
130 old_value: old.to_string(),
131 new_value: no_color.to_string(),
132 message: format!("Updated no_color to {}", no_color),
133 });
134 }
135
136 save_profile(&profile, profile_path)?;
137
138 for change in &changes {
139 render_result(
140 &CommandOutput::card_value("Runtime Updated", change),
141 config,
142 );
143 }
144
145 if config.output_format() == OutputFormat::Table {
146 CliService::warning("Restart services for changes to take effect");
147 }
148
149 Ok(())
150}
151
152fn load_profile(path: &str) -> Result<Profile> {
153 let content =
154 fs::read_to_string(path).with_context(|| format!("Failed to read profile: {}", path))?;
155 let profile: Profile = serde_yaml::from_str(&content)
156 .with_context(|| format!("Failed to parse profile: {}", path))?;
157 Ok(profile)
158}
159
160pub(super) fn save_profile(profile: &Profile, path: &str) -> Result<()> {
161 let content = serde_yaml::to_string(profile).context("Failed to serialize profile")?;
162 fs::write(path, content).with_context(|| format!("Failed to write profile: {}", path))?;
163 Ok(())
164}