systemprompt_cli/commands/cloud/
mod.rs1pub mod auth;
12pub mod backup;
13pub mod db;
14pub mod deploy;
15pub mod doctor;
16pub mod domain;
17pub mod init;
18pub mod profile;
19mod restart;
20pub mod secrets;
21mod status;
22pub mod templates;
23pub mod tenant;
24pub mod types;
25
26pub use systemprompt_cloud::{Environment, OAuthProvider};
27
28use crate::cli_settings::CliConfig;
29use crate::context::CommandContext;
30use crate::descriptor::{CommandDescriptor, DescribeCommand};
31use anyhow::Result;
32use clap::Subcommand;
33
34#[derive(Debug, Subcommand)]
35pub enum CloudCommands {
36 #[command(
37 subcommand,
38 about = "Authentication (login, logout, whoami, admin-user)"
39 )]
40 Auth(auth::AuthCommands),
41
42 #[command(about = "Initialize project structure")]
43 Init {
44 #[arg(long)]
45 force: bool,
46 },
47
48 #[command(subcommand_required = false, about = "Manage tenants (local or cloud)")]
49 Tenant {
50 #[command(subcommand)]
51 command: Option<tenant::TenantCommands>,
52 },
53
54 #[command(subcommand_required = false, about = "Manage profiles")]
55 Profile {
56 #[command(subcommand)]
57 command: Option<profile::ProfileCommands>,
58 },
59
60 #[command(about = "Deploy to systemprompt.io Cloud")]
61 Deploy {
62 #[arg(long)]
63 skip_push: bool,
64
65 #[arg(long, short = 'p', help = "Profile name to deploy")]
66 profile: Option<String>,
67
68 #[arg(long, help = "Run the pre-deploy preflight only, without deploying")]
69 check: bool,
70 },
71
72 #[command(about = "Download the tenant's runtime services/ tree to a local directory")]
73 Backup {
74 #[arg(long, short = 'p', help = "Profile name to back up")]
75 profile: Option<String>,
76
77 #[arg(
78 long,
79 short = 'o',
80 help = "Output directory (default: ./systemprompt-backup-<timestamp>)"
81 )]
82 output: Option<std::path::PathBuf>,
83
84 #[arg(long, help = "List remote files without downloading")]
85 list: bool,
86 },
87
88 #[command(about = "Run the pre-deploy preflight for a profile without deploying")]
89 Doctor {
90 #[arg(long, short = 'p', help = "Profile name to check")]
91 profile: Option<String>,
92 },
93
94 #[command(about = "Check cloud deployment status")]
95 Status,
96
97 #[command(about = "Restart tenant machine")]
98 Restart {
99 #[arg(long)]
100 tenant: Option<String>,
101
102 #[arg(short = 'y', long, help = "Skip confirmation prompts")]
103 yes: bool,
104 },
105
106 #[command(subcommand, about = "Manage secrets for cloud tenant")]
107 Secrets(secrets::SecretsCommands),
108
109 #[command(about = "Generate Dockerfile based on discovered extensions")]
110 Dockerfile,
111
112 #[command(subcommand, about = "Cloud database operations")]
113 Db(db::CloudDbCommands),
114
115 #[command(subcommand, about = "Manage custom domain and TLS certificates")]
116 Domain(domain::DomainCommands),
117}
118
119impl DescribeCommand for CloudCommands {
120 fn descriptor(&self) -> CommandDescriptor {
121 match self {
122 Self::Deploy { .. } | Self::Secrets { .. } => CommandDescriptor::PROFILE_AND_SECRETS,
123 Self::Backup { .. } | Self::Status | Self::Restart { .. } | Self::Domain { .. } => {
124 CommandDescriptor::PROFILE_ONLY
125 },
126 _ => CommandDescriptor::NONE,
127 }
128 }
129}
130
131impl CloudCommands {
132 pub const fn requires_profile(&self) -> bool {
133 matches!(
134 self,
135 Self::Status | Self::Restart { .. } | Self::Secrets { .. } | Self::Domain { .. }
136 )
137 }
138
139 pub const fn requires_secrets(&self) -> bool {
140 matches!(self, Self::Secrets { .. })
141 }
142}
143
144pub async fn execute(cmd: CloudCommands, ctx: &CommandContext) -> Result<()> {
145 match cmd {
146 CloudCommands::Auth(cmd) => auth::execute(cmd, ctx).await,
147 CloudCommands::Init { force } => init::execute(force, &ctx.cli),
148 CloudCommands::Tenant { command } => tenant::execute(command, ctx).await,
149 CloudCommands::Profile { command } => profile::execute(command, ctx).await,
150 CloudCommands::Deploy {
151 skip_push,
152 profile,
153 check,
154 } => {
155 deploy::execute(
156 deploy::DeployArgs {
157 skip_push,
158 profile_name: profile,
159 check,
160 },
161 ctx.prompter(),
162 &ctx.cli,
163 )
164 .await
165 },
166 CloudCommands::Backup {
167 profile,
168 output,
169 list,
170 } => {
171 backup::execute(
172 backup::BackupArgs {
173 profile_name: profile,
174 output,
175 list,
176 },
177 ctx.prompter(),
178 &ctx.cli,
179 )
180 .await
181 },
182 CloudCommands::Doctor { profile } => {
183 doctor::execute(profile, ctx.prompter(), &ctx.cli).await
184 },
185 CloudCommands::Status => {
186 let result = status::execute(&ctx.cli).await?;
187 crate::shared::render_result(&result, &ctx.cli);
188 Ok(())
189 },
190 CloudCommands::Restart { tenant, yes } => {
191 let result = restart::execute(tenant, yes, ctx.prompter(), &ctx.cli).await?;
192 crate::shared::render_result(&result, &ctx.cli);
193 Ok(())
194 },
195 CloudCommands::Secrets(cmd) => secrets::execute(cmd, ctx).await,
196 CloudCommands::Dockerfile => execute_dockerfile(&ctx.cli),
197 CloudCommands::Db(cmd) => match ctx.database_url() {
198 Some(database_url) => db::execute_with_database_url(cmd, database_url, ctx).await,
199 None => db::execute(cmd, ctx).await,
200 },
201 CloudCommands::Domain(cmd) => domain::execute(cmd, ctx).await,
202 }
203}
204
205fn execute_dockerfile(config: &CliConfig) -> Result<()> {
206 use crate::shared::project::ProjectRoot;
207 use types::DockerfileOutput;
208
209 let project = ProjectRoot::discover().map_err(|e| anyhow::anyhow!("{}", e))?;
210 let content = systemprompt_cloud::deploy::generate_dockerfile_content(project.as_path());
211
212 let output = DockerfileOutput {
213 content: content.clone(),
214 };
215
216 if config.is_json_output() {
217 crate::shared::render_result(
218 &crate::shared::CommandOutput::copy_paste_titled("Dockerfile", output.content),
219 config,
220 );
221 } else {
222 systemprompt_logging::CliService::output(&content);
223 }
224
225 Ok(())
226}