Skip to main content

systemprompt_cli/commands/admin/setup/
mod.rs

1//! Interactive and non-interactive setup wizard for a local environment.
2//!
3//! Drives `PostgreSQL` provisioning, secret collection, profile generation, and
4//! optional migrations. [`SetupArgs`] captures the CLI flags and environment
5//! overrides; [`execute`] dispatches to the wizard, which writes a profile and
6//! secrets file under `.systemprompt/`.
7
8pub mod ai_config;
9pub mod catalog;
10pub mod common;
11pub mod ddl;
12mod docker;
13pub mod docker_compose;
14mod docker_database;
15pub mod postgres;
16mod profile;
17mod profile_sections;
18mod secrets;
19mod types;
20mod wizard;
21mod wizard_dry_run;
22mod wizard_prompts;
23
24use crate::shared::CommandOutput;
25use anyhow::Result;
26use clap::Args;
27
28pub use secrets::SecretsData;
29pub use types::*;
30
31#[derive(Debug, Args)]
32#[expect(
33    clippy::struct_excessive_bools,
34    reason = "each bool is an independent clap CLI flag, not a state machine"
35)]
36pub struct SetupArgs {
37    #[arg(
38        short,
39        long,
40        help = "Target environment name (e.g., dev, staging, prod)"
41    )]
42    pub environment: Option<String>,
43
44    #[arg(
45        long,
46        help = "Use Docker for PostgreSQL (default: use existing installation)"
47    )]
48    pub docker: bool,
49
50    #[arg(
51        long,
52        env = "SYSTEMPROMPT_DB_HOST",
53        default_value = "localhost",
54        help = "PostgreSQL host"
55    )]
56    pub db_host: String,
57
58    #[arg(
59        long,
60        env = "SYSTEMPROMPT_DB_PORT",
61        default_value = "5432",
62        help = "PostgreSQL port"
63    )]
64    pub db_port: u16,
65
66    #[arg(
67        long,
68        env = "SYSTEMPROMPT_DB_USER",
69        help = "PostgreSQL user (default: systemprompt_`<env>`)"
70    )]
71    pub db_user: Option<String>,
72
73    #[arg(
74        long,
75        env = "SYSTEMPROMPT_DB_PASSWORD",
76        help = "PostgreSQL password (auto-generated if not provided)"
77    )]
78    pub db_password: Option<String>,
79
80    #[arg(
81        long,
82        env = "SYSTEMPROMPT_DB_NAME",
83        help = "PostgreSQL database name (default: systemprompt_`<env>`)"
84    )]
85    pub db_name: Option<String>,
86
87    #[arg(long, env = "GEMINI_API_KEY", help = "Google AI (Gemini) API key")]
88    pub gemini_key: Option<String>,
89
90    #[arg(long, env = "ANTHROPIC_API_KEY", help = "Anthropic (Claude) API key")]
91    pub anthropic_key: Option<String>,
92
93    #[arg(long, env = "OPENAI_API_KEY", help = "OpenAI (GPT) API key")]
94    pub openai_key: Option<String>,
95
96    #[arg(long, env = "GITHUB_TOKEN", help = "GitHub token (optional)")]
97    pub github_token: Option<String>,
98
99    #[arg(
100        long,
101        env = "SYSTEMPROMPT_DEFAULT_PROVIDER",
102        help = "Provider to make the default (gemini | anthropic | openai); must have a key. \
103                In interactive mode the selected provider is used instead."
104    )]
105    pub default_provider: Option<String>,
106
107    #[arg(long, help = "Run database migrations after setup")]
108    pub migrate: bool,
109
110    #[arg(
111        long,
112        conflicts_with = "migrate",
113        help = "Skip migrations (non-interactive default)"
114    )]
115    pub no_migrate: bool,
116
117    #[arg(long, help = "Preview setup without creating files or making changes")]
118    pub dry_run: bool,
119
120    #[arg(short = 'y', long, help = "Skip confirmation prompts")]
121    pub yes: bool,
122
123    #[arg(
124        long,
125        help = "Overwrite existing profile/secrets files (default: preserve them)"
126    )]
127    pub force: bool,
128}
129
130impl SetupArgs {
131    pub fn effective_db_user(&self, env_name: &str) -> String {
132        self.db_user
133            .clone()
134            .unwrap_or_else(|| format!("systemprompt_{}", env_name))
135    }
136
137    pub fn effective_db_name(&self, env_name: &str) -> String {
138        self.db_name
139            .clone()
140            .unwrap_or_else(|| format!("systemprompt_{}", env_name))
141    }
142
143    pub const fn has_ai_provider(&self) -> bool {
144        self.gemini_key.is_some() || self.anthropic_key.is_some() || self.openai_key.is_some()
145    }
146}
147
148pub async fn execute(
149    args: SetupArgs,
150    ctx: &crate::context::CommandContext,
151) -> Result<CommandOutput> {
152    wizard::execute(args, ctx.prompter(), &ctx.cli).await
153}