Skip to main content

systemprompt_cli/
cli_settings.rs

1//! CLI-wide runtime settings: output format, verbosity, colour, and
2//! interactivity.
3//!
4//! [`CliConfig`] holds the resolved presentation options for a CLI invocation:
5//! defaults, overridden by the [`crate::env_overrides::EnvOverrides`] snapshot
6//! ([`CliConfig::resolve`]), overridden by command-line flags. The
7//! [`OutputFormat`], [`VerbosityLevel`], and [`ColorMode`] enums express the
8//! individual axes. The resolved config travels explicitly on
9//! [`crate::context::CommandContext`]; there is no process-global instance.
10//!
11//! Copyright (c) systemprompt.io — Business Source License 1.1.
12//! See <https://systemprompt.io> for licensing details.
13
14use std::io::IsTerminal;
15
16use crate::env_overrides::EnvOverrides;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum OutputFormat {
20    Table,
21    Json,
22    Yaml,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub enum VerbosityLevel {
27    Quiet,
28    Normal,
29    Verbose,
30    Debug,
31}
32
33impl VerbosityLevel {
34    pub const fn as_tracing_filter(&self) -> Option<&'static str> {
35        match self {
36            Self::Quiet => Some("error"),
37            Self::Normal => None,
38            Self::Verbose => Some("debug"),
39            Self::Debug => Some("trace"),
40        }
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ColorMode {
46    Auto,
47    Always,
48    Never,
49}
50
51#[derive(Debug, Clone)]
52pub struct CliConfig {
53    pub output_format: OutputFormat,
54    pub verbosity: VerbosityLevel,
55    pub color_mode: ColorMode,
56    pub interactive: bool,
57    pub assume_terminal: bool,
58    pub profile_override: Option<String>,
59}
60
61impl Default for CliConfig {
62    fn default() -> Self {
63        Self {
64            output_format: OutputFormat::Table,
65            verbosity: VerbosityLevel::Normal,
66            color_mode: ColorMode::Auto,
67            interactive: true,
68            assume_terminal: false,
69            profile_override: None,
70        }
71    }
72}
73
74impl CliConfig {
75    #[must_use]
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    #[must_use]
81    pub fn resolve(env: &EnvOverrides) -> Self {
82        let mut config = Self::default();
83        config.apply_env(env);
84        config
85    }
86
87    pub const fn with_output_format(mut self, format: OutputFormat) -> Self {
88        self.output_format = format;
89        self
90    }
91
92    pub const fn with_verbosity(mut self, level: VerbosityLevel) -> Self {
93        self.verbosity = level;
94        self
95    }
96
97    pub const fn with_color_mode(mut self, mode: ColorMode) -> Self {
98        self.color_mode = mode;
99        self
100    }
101
102    pub const fn with_interactive(mut self, interactive: bool) -> Self {
103        self.interactive = interactive;
104        self
105    }
106
107    pub const fn with_assume_terminal(mut self, assume: bool) -> Self {
108        self.assume_terminal = assume;
109        self
110    }
111
112    pub fn with_profile_override(mut self, profile: Option<String>) -> Self {
113        self.profile_override = profile;
114        self
115    }
116
117    fn apply_env(&mut self, env: &EnvOverrides) {
118        if let Some(format) = &env.output_format {
119            self.output_format = match format.to_lowercase().as_str() {
120                "json" => OutputFormat::Json,
121                "yaml" => OutputFormat::Yaml,
122                "table" => OutputFormat::Table,
123                _ => self.output_format,
124            };
125        }
126
127        if let Some(level) = &env.log_level {
128            self.verbosity = match level.to_lowercase().as_str() {
129                "quiet" => VerbosityLevel::Quiet,
130                "normal" => VerbosityLevel::Normal,
131                "verbose" => VerbosityLevel::Verbose,
132                "debug" => VerbosityLevel::Debug,
133                _ => self.verbosity,
134            };
135        }
136
137        if env.no_color {
138            self.color_mode = ColorMode::Never;
139        }
140
141        if env.non_interactive {
142            self.interactive = false;
143        }
144    }
145
146    pub fn should_use_color(&self) -> bool {
147        match self.color_mode {
148            ColorMode::Always => true,
149            ColorMode::Never => false,
150            ColorMode::Auto => std::io::stdout().is_terminal(),
151        }
152    }
153
154    pub fn is_json_output(&self) -> bool {
155        self.output_format == OutputFormat::Json
156    }
157
158    pub fn should_show_verbose(&self) -> bool {
159        self.verbosity >= VerbosityLevel::Verbose
160    }
161
162    pub fn is_interactive(&self) -> bool {
163        self.interactive
164            && (self.assume_terminal
165                || (std::io::stdin().is_terminal() && std::io::stdout().is_terminal()))
166    }
167
168    pub const fn output_format(&self) -> OutputFormat {
169        self.output_format
170    }
171}