systemprompt_cli/runner/
mod.rs1pub mod args;
11mod bootstrap;
12mod db_url;
13mod profile_routing;
14mod routing;
15mod structured_output;
16
17#[doc(hidden)]
18pub mod test_api;
19
20use anyhow::{Context, Result, bail};
21use clap::Parser;
22use systemprompt_logging::set_startup_mode;
23use systemprompt_runtime::DatabaseContext;
24
25use crate::cli_settings::{CliConfig, OutputFormat};
26use crate::commands::{admin, analytics, cloud, core, infrastructure, plugins, web};
27use crate::context::CommandContext;
28use crate::descriptor::{CommandDescriptor, DescribeCommand};
29use crate::env_overrides::EnvOverrides;
30
31pub async fn run() -> Result<()> {
32 let outcome = Box::pin(run_inner()).await;
33 structured_output::finalize(&outcome);
34 outcome
35}
36
37async fn run_inner() -> Result<()> {
38 let cli = args::Cli::parse();
39
40 set_startup_mode(cli.command.is_none());
41
42 let env = EnvOverrides::from_process_env();
43 let cli_config = args::build_cli_config(&cli, &env);
44 systemprompt_logging::set_structured_output(cli_config.output_format() != OutputFormat::Table);
45
46 if cli.display.no_color || !cli_config.should_use_color() {
47 console::set_colors_enabled(false);
48 }
49
50 if let Some(database_url) = cli.database.database_url.clone() {
51 match cli.command.as_ref().map(args::Commands::db_url_routing) {
52 Some(db_url::DbUrlRouting::Direct) => {
53 return Box::pin(run_with_database_url(
54 cli.command,
55 cli_config,
56 env,
57 &database_url,
58 ))
59 .await;
60 },
61 Some(db_url::DbUrlRouting::Unsupported) => bail!(
62 "This command cannot run with --database-url; it requires full profile \
63 initialization. Remove --database-url."
64 ),
65 Some(db_url::DbUrlRouting::ProfileDriven) | None => {},
66 }
67 }
68
69 let desc = cli
70 .command
71 .as_ref()
72 .map_or(CommandDescriptor::FULL, DescribeCommand::descriptor);
73
74 if !desc.database() {
75 let effective_level = resolve_log_level(&cli_config, &env);
76 systemprompt_logging::init_console_logging_with_level(effective_level.as_deref());
77 }
78
79 if desc.profile()
80 && let Some(external_db_url) =
81 profile_routing::bootstrap_profile(&cli, &desc, &cli_config, &env).await?
82 {
83 return Box::pin(run_with_database_url(
84 cli.command,
85 cli_config,
86 env,
87 &external_db_url,
88 ))
89 .await;
90 }
91
92 let ctx = CommandContext::new(cli_config, env);
93 Box::pin(dispatch_command(cli.command, &ctx)).await
94}
95
96fn resolve_log_level(cli_config: &CliConfig, env: &EnvOverrides) -> Option<String> {
97 if env.rust_log.is_some() {
98 return None;
99 }
100
101 if let Some(level) = cli_config.verbosity.as_tracing_filter() {
102 return Some(level.to_owned());
103 }
104
105 if let Ok(profile_path) =
106 bootstrap::resolve_profile(cli_config.profile_override.as_deref(), env)
107 && let Some(log_level) = bootstrap::try_load_log_level(&profile_path)
108 {
109 return Some(log_level.as_tracing_filter().to_owned());
110 }
111
112 Some("warn".to_owned())
113}
114
115async fn dispatch_command(command: Option<args::Commands>, ctx: &CommandContext) -> Result<()> {
116 if ctx.is_database_scoped() {
117 match &command {
118 Some(
119 args::Commands::Core(_)
120 | args::Commands::Infra(_)
121 | args::Commands::Admin(_)
122 | args::Commands::Analytics(_),
123 ) => {},
124 Some(_) => bail!(
125 "This command requires full profile initialization. Remove --database-url flag."
126 ),
127 None => bail!("No subcommand provided. Use --help to see available commands."),
128 }
129 }
130
131 match command {
132 Some(args::Commands::Core(cmd)) => core::execute(cmd, ctx).await?,
133 Some(args::Commands::Infra(cmd)) => infrastructure::execute(cmd, ctx).await?,
134 Some(args::Commands::Admin(cmd)) => Box::pin(admin::execute(cmd, ctx)).await?,
135 Some(args::Commands::Cloud(cmd)) => cloud::execute(cmd, ctx).await?,
136 Some(args::Commands::Analytics(cmd)) => analytics::execute(cmd, ctx).await?,
137 Some(args::Commands::Web(cmd)) => web::execute(cmd, ctx)?,
138 Some(args::Commands::Plugins(cmd)) => Box::pin(plugins::execute(cmd, ctx)).await?,
139 Some(args::Commands::Build(cmd)) => {
140 crate::commands::build::execute(cmd, ctx)?;
141 },
142 None => {
143 args::Cli::parse_from(["systemprompt", "--help"]);
144 },
145 }
146
147 Ok(())
148}
149
150async fn run_with_database_url(
151 command: Option<args::Commands>,
152 cli_config: CliConfig,
153 env: EnvOverrides,
154 database_url: &str,
155) -> Result<()> {
156 let db_ctx = DatabaseContext::from_url(database_url)
157 .await
158 .context("Failed to connect to database")?;
159
160 systemprompt_logging::init_logging(db_ctx.db_pool_arc());
161
162 let ctx = CommandContext::with_database(cli_config, env, db_ctx, database_url.to_owned());
163 Box::pin(dispatch_command(command, &ctx)).await
164}