Skip to main content

systemprompt_cli/commands/admin/config/
runtime.rs

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