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