systemprompt_cli/commands/admin/setup/
mod.rs1pub mod ai_config;
12pub mod catalog;
13pub mod common;
14pub mod ddl;
15pub mod docker;
16pub mod docker_compose;
17mod docker_database;
18pub mod postgres;
19mod profile;
20mod profile_sections;
21pub mod secrets;
22mod types;
23pub mod wizard;
24mod wizard_dry_run;
25mod wizard_prompts;
26
27use crate::shared::CommandOutput;
28use anyhow::Result;
29use clap::Args;
30use systemprompt_models::none_if_blank;
31
32pub use secrets::SecretsData;
33pub use types::*;
34
35#[derive(Debug, Args)]
36#[expect(
37 clippy::struct_excessive_bools,
38 reason = "each bool is an independent clap CLI flag, not a state machine"
39)]
40pub struct SetupArgs {
41 #[arg(
42 short,
43 long,
44 help = "Target environment name (e.g., dev, staging, prod)"
45 )]
46 pub environment: Option<String>,
47
48 #[arg(
49 long,
50 help = "Use Docker for PostgreSQL (default: use existing installation)"
51 )]
52 pub docker: bool,
53
54 #[arg(
55 long,
56 env = "SYSTEMPROMPT_DB_HOST",
57 default_value = "localhost",
58 help = "PostgreSQL host"
59 )]
60 pub db_host: String,
61
62 #[arg(
63 long,
64 env = "SYSTEMPROMPT_DB_PORT",
65 default_value = "5432",
66 help = "PostgreSQL port"
67 )]
68 pub db_port: u16,
69
70 #[arg(
71 long,
72 default_value = "0",
73 help = "Shift every locally-bound MCP and agent port by this amount"
74 )]
75 pub port_offset: u16,
76
77 #[arg(
78 long,
79 env = "SYSTEMPROMPT_DB_USER",
80 help = "PostgreSQL user (default: systemprompt_`<env>`)"
81 )]
82 pub db_user: Option<String>,
83
84 #[arg(
85 long,
86 env = "SYSTEMPROMPT_DB_PASSWORD",
87 help = "PostgreSQL password (auto-generated if not provided)"
88 )]
89 pub db_password: Option<String>,
90
91 #[arg(
92 long,
93 env = "SYSTEMPROMPT_DB_NAME",
94 help = "PostgreSQL database name (default: systemprompt_`<env>`)"
95 )]
96 pub db_name: Option<String>,
97
98 #[arg(long, env = "GEMINI_API_KEY", help = "Google AI (Gemini) API key")]
99 pub gemini_key: Option<String>,
100
101 #[arg(long, env = "ANTHROPIC_API_KEY", help = "Anthropic (Claude) API key")]
102 pub anthropic_key: Option<String>,
103
104 #[arg(long, env = "OPENAI_API_KEY", help = "OpenAI (GPT) API key")]
105 pub openai_key: Option<String>,
106
107 #[arg(long, env = "GITHUB_TOKEN", help = "GitHub token (optional)")]
108 pub github_token: Option<String>,
109
110 #[arg(
111 long,
112 env = "SYSTEMPROMPT_DEFAULT_PROVIDER",
113 help = "Provider to make the default (gemini | anthropic | openai); must have a key. \
114 In interactive mode the selected provider is used instead."
115 )]
116 pub default_provider: Option<String>,
117
118 #[arg(
119 long,
120 env = "SYSTEMPROMPT_ADMIN_EMAIL",
121 help = "Email identifying the platform admin on sign-in and consent screens; required non-interactively"
122 )]
123 pub admin_email: Option<String>,
124
125 #[arg(long, help = "Run database migrations after setup")]
126 pub migrate: bool,
127
128 #[arg(
129 long,
130 conflicts_with = "migrate",
131 help = "Skip migrations (non-interactive default)"
132 )]
133 pub no_migrate: bool,
134
135 #[arg(long, help = "Preview setup without creating files or making changes")]
136 pub dry_run: bool,
137
138 #[arg(short = 'y', long, help = "Skip confirmation prompts")]
139 pub yes: bool,
140
141 #[arg(
142 long,
143 help = "Overwrite existing profile/secrets files (default: preserve them)"
144 )]
145 pub force: bool,
146}
147
148impl SetupArgs {
149 pub fn effective_db_user(&self, env_name: &str) -> String {
150 self.db_user
151 .clone()
152 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
153 }
154
155 pub fn effective_db_name(&self, env_name: &str) -> String {
156 self.db_name
157 .clone()
158 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
159 }
160
161 pub const fn has_ai_provider(&self) -> bool {
162 self.gemini_key.is_some() || self.anthropic_key.is_some() || self.openai_key.is_some()
163 }
164
165 #[must_use]
166 fn normalized(mut self) -> Self {
167 self.gemini_key = none_if_blank(self.gemini_key);
168 self.anthropic_key = none_if_blank(self.anthropic_key);
169 self.openai_key = none_if_blank(self.openai_key);
170 self.github_token = none_if_blank(self.github_token);
171 self.default_provider = none_if_blank(self.default_provider);
172 self.admin_email = none_if_blank(self.admin_email);
173 self
174 }
175}
176
177pub async fn execute(
178 args: SetupArgs,
179 ctx: &crate::context::CommandContext,
180) -> Result<CommandOutput> {
181 wizard::execute(args.normalized(), ctx.prompter(), &ctx.cli).await
182}