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