Skip to main content

systemprompt_cli/commands/admin/config/
runtime.rs

1use 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}