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(long, help = "Run database migrations after setup")]
119 pub migrate: bool,
120
121 #[arg(
122 long,
123 conflicts_with = "migrate",
124 help = "Skip migrations (non-interactive default)"
125 )]
126 pub no_migrate: bool,
127
128 #[arg(long, help = "Preview setup without creating files or making changes")]
129 pub dry_run: bool,
130
131 #[arg(short = 'y', long, help = "Skip confirmation prompts")]
132 pub yes: bool,
133
134 #[arg(
135 long,
136 help = "Overwrite existing profile/secrets files (default: preserve them)"
137 )]
138 pub force: bool,
139}
140
141impl SetupArgs {
142 pub fn effective_db_user(&self, env_name: &str) -> String {
143 self.db_user
144 .clone()
145 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
146 }
147
148 pub fn effective_db_name(&self, env_name: &str) -> String {
149 self.db_name
150 .clone()
151 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
152 }
153
154 pub const fn has_ai_provider(&self) -> bool {
155 self.gemini_key.is_some() || self.anthropic_key.is_some() || self.openai_key.is_some()
156 }
157
158 #[must_use]
159 fn normalized(mut self) -> Self {
160 self.gemini_key = none_if_blank(self.gemini_key);
161 self.anthropic_key = none_if_blank(self.anthropic_key);
162 self.openai_key = none_if_blank(self.openai_key);
163 self.github_token = none_if_blank(self.github_token);
164 self.default_provider = none_if_blank(self.default_provider);
165 self
166 }
167}
168
169pub async fn execute(
170 args: SetupArgs,
171 ctx: &crate::context::CommandContext,
172) -> Result<CommandOutput> {
173 wizard::execute(args.normalized(), ctx.prompter(), &ctx.cli).await
174}