Skip to main content

systemprompt_cli/commands/cloud/
mod.rs

1//! `cloud` command tree for systemprompt.io Cloud.
2//!
3//! Routes [`CloudCommands`] to the auth, init, tenant, profile, deploy,
4//! backup, status, restart, secrets, dockerfile, db, and domain subcommands,
5//! and declares each command's profile/secret requirements via
6//! [`DescribeCommand`].
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11pub 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
131pub async fn execute(cmd: CloudCommands, ctx: &CommandContext) -> Result<()> {
132    match cmd {
133        CloudCommands::Auth(cmd) => auth::execute(cmd, ctx).await,
134        CloudCommands::Init { force } => init::execute(force, &ctx.cli),
135        CloudCommands::Tenant { command } => tenant::execute(command, ctx).await,
136        CloudCommands::Profile { command } => profile::execute(command, ctx).await,
137        CloudCommands::Deploy {
138            skip_push,
139            profile,
140            check,
141        } => {
142            deploy::execute(
143                deploy::DeployArgs {
144                    skip_push,
145                    profile_name: profile,
146                    check,
147                },
148                ctx.prompter(),
149                &ctx.cli,
150            )
151            .await
152        },
153        CloudCommands::Backup {
154            profile,
155            output,
156            list,
157        } => {
158            backup::execute(
159                backup::BackupArgs {
160                    profile_name: profile,
161                    output,
162                    list,
163                },
164                ctx.prompter(),
165                &ctx.cli,
166            )
167            .await
168        },
169        CloudCommands::Doctor { profile } => {
170            doctor::execute(profile, ctx.prompter(), &ctx.cli).await
171        },
172        CloudCommands::Status => {
173            let result = status::execute(&ctx.cli).await?;
174            crate::shared::render_result(&result, &ctx.cli);
175            Ok(())
176        },
177        CloudCommands::Restart { tenant, yes } => {
178            let result = restart::execute(tenant, yes, ctx.prompter(), &ctx.cli).await?;
179            crate::shared::render_result(&result, &ctx.cli);
180            Ok(())
181        },
182        CloudCommands::Secrets(cmd) => secrets::execute(cmd, ctx).await,
183        CloudCommands::Dockerfile => execute_dockerfile(&ctx.cli),
184        CloudCommands::Db(cmd) => match ctx.database_url() {
185            Some(database_url) => db::execute_with_database_url(cmd, database_url, ctx).await,
186            None => db::execute(cmd, ctx).await,
187        },
188        CloudCommands::Domain(cmd) => domain::execute(cmd, ctx).await,
189    }
190}
191
192fn execute_dockerfile(config: &CliConfig) -> Result<()> {
193    use crate::shared::project::ProjectRoot;
194    use types::DockerfileOutput;
195
196    let project = ProjectRoot::discover().map_err(|e| anyhow::anyhow!("{}", e))?;
197    let content = systemprompt_cloud::deploy::generate_dockerfile_content(project.as_path());
198
199    let output = DockerfileOutput {
200        content: content.clone(),
201    };
202
203    if config.is_json_output() {
204        crate::shared::render_result(
205            &crate::shared::CommandOutput::copy_paste_titled("Dockerfile", output.content),
206            config,
207        );
208    } else {
209        systemprompt_logging::CliService::output(&content);
210    }
211
212    Ok(())
213}