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