Skip to main content

systemprompt_cli/runner/args/
assemble.rs

1//! Assembling the runtime config and the forwarded argument vector.
2//!
3//! Split from the clap definitions next door because these are the only pieces
4//! with behaviour rather than shape: the verbosity/output precedence, and the
5//! reconstruction that decides what a profile-routed subprocess actually
6//! receives.
7//!
8//! The forwarded argv is the operator's argv edited by position: `--profile`
9//! and `--database-url` (both spellings) name local targets and are dropped,
10//! global flags typed before the subcommand move to just after the top-level
11//! group (the remote gateway requires a subcommand first), and every other
12//! token is kept verbatim and in order — a repeated value is never collapsed.
13//!
14//! Copyright (c) systemprompt.io — Business Source License 1.1.
15//! See <https://systemprompt.io> for licensing details.
16
17use super::{Cli, Commands};
18use crate::cli_settings::{CliConfig, ColorMode, OutputFormat, VerbosityLevel};
19use crate::env_overrides::EnvOverrides;
20
21pub fn build_cli_config(cli: &Cli, env: &EnvOverrides) -> CliConfig {
22    let mut cfg = CliConfig::resolve(env);
23
24    if cli.verbosity.debug {
25        cfg = cfg.with_verbosity(VerbosityLevel::Debug);
26    } else if cli.verbosity.verbose {
27        cfg = cfg.with_verbosity(VerbosityLevel::Verbose);
28    } else if cli.verbosity.quiet {
29        cfg = cfg.with_verbosity(VerbosityLevel::Quiet);
30    }
31
32    if cli.output.json {
33        cfg = cfg.with_output_format(OutputFormat::Json);
34    } else if cli.output.yaml {
35        cfg = cfg.with_output_format(OutputFormat::Yaml);
36    }
37
38    if cli.display.no_color {
39        cfg = cfg.with_color_mode(ColorMode::Never);
40    }
41
42    if cli.display.non_interactive {
43        cfg = cfg.with_interactive(false);
44    }
45
46    cfg = cfg.with_profile_override(cli.profile_opts.profile.clone());
47
48    cfg
49}
50
51pub fn reconstruct_args() -> Vec<String> {
52    let original: Vec<String> = std::env::args().skip(1).collect();
53    reconstruct_args_from(&original)
54}
55
56pub fn reconstruct_args_from(original_args: &[String]) -> Vec<String> {
57    let mut leading_globals = Vec::new();
58    let mut tokens = original_args.iter();
59    let mut subcommand = None;
60
61    while let Some(arg) = tokens.next() {
62        if is_local_only_flag(arg) {
63            if takes_separate_value(arg) {
64                tokens.next();
65            }
66            continue;
67        }
68        if arg.starts_with('-') {
69            leading_globals.push(arg.clone());
70            continue;
71        }
72        subcommand = Some(arg.clone());
73        break;
74    }
75
76    let Some(subcommand) = subcommand else {
77        return leading_globals;
78    };
79
80    let mut forwarded = vec![subcommand];
81    forwarded.extend(leading_globals);
82
83    let mut after_terminator = false;
84    while let Some(arg) = tokens.next() {
85        if after_terminator {
86            forwarded.push(arg.clone());
87            continue;
88        }
89        if arg == "--" {
90            after_terminator = true;
91            forwarded.push(arg.clone());
92            continue;
93        }
94        if is_local_only_flag(arg) {
95            if takes_separate_value(arg) {
96                tokens.next();
97            }
98            continue;
99        }
100        forwarded.push(arg.clone());
101    }
102
103    forwarded
104}
105
106const LOCAL_ONLY_FLAGS: [&str; 2] = ["--profile", "--database-url"];
107
108fn takes_separate_value(arg: &str) -> bool {
109    LOCAL_ONLY_FLAGS.contains(&arg)
110}
111
112fn is_local_only_flag(arg: &str) -> bool {
113    LOCAL_ONLY_FLAGS.iter().any(|flag| {
114        arg == *flag
115            || arg
116                .strip_prefix(flag)
117                .is_some_and(|rest| rest.starts_with('='))
118    })
119}
120
121pub fn has_local_export_flag(command: Option<&Commands>) -> bool {
122    let args: Vec<String> = std::env::args().collect();
123    has_local_export_flag_in(command, &args)
124}
125
126pub fn has_local_export_flag_in(command: Option<&Commands>, args: &[String]) -> bool {
127    if !matches!(command, Some(Commands::Analytics(_))) {
128        return false;
129    }
130    args.iter()
131        .any(|arg| arg == "--export" || arg.starts_with("--export="))
132}