Skip to main content

systemprompt_cli/commands/cloud/
mod.rs

1pub mod auth;
2pub mod db;
3mod deploy;
4pub mod dockerfile;
5mod domain;
6mod init;
7pub mod profile;
8mod restart;
9mod secrets;
10mod status;
11pub mod sync;
12pub mod templates;
13pub mod tenant;
14
15pub use systemprompt_cloud::{Environment, OAuthProvider};
16
17use crate::cli_settings::CliConfig;
18use crate::descriptor::{CommandDescriptor, DescribeCommand};
19use anyhow::Result;
20use clap::Subcommand;
21
22#[derive(Debug, Subcommand)]
23pub enum CloudCommands {
24    #[command(subcommand, about = "Authentication (login, logout, whoami)")]
25    Auth(auth::AuthCommands),
26
27    #[command(about = "Initialize project structure")]
28    Init {
29        #[arg(long)]
30        force: bool,
31    },
32
33    #[command(subcommand_required = false, about = "Manage tenants (local or cloud)")]
34    Tenant {
35        #[command(subcommand)]
36        command: Option<tenant::TenantCommands>,
37    },
38
39    #[command(subcommand_required = false, about = "Manage profiles")]
40    Profile {
41        #[command(subcommand)]
42        command: Option<profile::ProfileCommands>,
43    },
44
45    #[command(about = "Deploy to systemprompt.io Cloud")]
46    Deploy {
47        #[arg(long)]
48        skip_push: bool,
49
50        #[arg(long, short = 'p', help = "Profile name to deploy")]
51        profile: Option<String>,
52
53        #[arg(
54            long,
55            help = "Skip pre-deploy sync from cloud (WARNING: may lose runtime files)"
56        )]
57        no_sync: bool,
58
59        #[arg(short = 'y', long, help = "Skip confirmation prompts")]
60        yes: bool,
61
62        #[arg(long, help = "Preview sync without deploying")]
63        dry_run: bool,
64    },
65
66    #[command(about = "Check cloud deployment status")]
67    Status,
68
69    #[command(about = "Restart tenant machine")]
70    Restart {
71        #[arg(long)]
72        tenant: Option<String>,
73
74        #[arg(short = 'y', long, help = "Skip confirmation prompts")]
75        yes: bool,
76    },
77
78    #[command(
79        subcommand_required = false,
80        about = "Sync between local and cloud environments"
81    )]
82    Sync {
83        #[command(subcommand)]
84        command: Option<sync::SyncCommands>,
85    },
86
87    #[command(subcommand, about = "Manage secrets for cloud tenant")]
88    Secrets(secrets::SecretsCommands),
89
90    #[command(about = "Generate Dockerfile based on discovered extensions")]
91    Dockerfile,
92
93    #[command(subcommand, about = "Cloud database operations")]
94    Db(db::CloudDbCommands),
95
96    #[command(subcommand, about = "Manage custom domain and TLS certificates")]
97    Domain(domain::DomainCommands),
98}
99
100impl DescribeCommand for CloudCommands {
101    fn descriptor(&self) -> CommandDescriptor {
102        match self {
103            Self::Sync {
104                command: Some(sync::SyncCommands::Local(_)),
105            } => CommandDescriptor::PROFILE_SECRETS_AND_PATHS,
106            Self::Deploy { .. } => CommandDescriptor::PROFILE_AND_SECRETS,
107            Self::Sync { command: Some(_) } | Self::Secrets { .. } => {
108                CommandDescriptor::PROFILE_AND_SECRETS
109            },
110            Self::Status | Self::Restart { .. } | Self::Domain { .. } => {
111                CommandDescriptor::PROFILE_ONLY
112            },
113            _ => CommandDescriptor::NONE,
114        }
115    }
116}
117
118impl CloudCommands {
119    pub const fn requires_profile(&self) -> bool {
120        matches!(
121            self,
122            Self::Sync { command: Some(_) }
123                | Self::Status
124                | Self::Restart { .. }
125                | Self::Secrets { .. }
126                | Self::Domain { .. }
127        )
128    }
129
130    pub const fn requires_secrets(&self) -> bool {
131        matches!(self, Self::Sync { command: Some(_) } | Self::Secrets { .. })
132    }
133}
134
135pub async fn execute(cmd: CloudCommands, config: &CliConfig) -> Result<()> {
136    match cmd {
137        CloudCommands::Auth(cmd) => auth::execute(cmd, config).await,
138        CloudCommands::Init { force } => init::execute(force, config),
139        CloudCommands::Tenant { command } => tenant::execute(command, config).await,
140        CloudCommands::Profile { command } => profile::execute(command, config).await,
141        CloudCommands::Deploy {
142            skip_push,
143            profile,
144            no_sync,
145            yes,
146            dry_run,
147        } => {
148            deploy::execute(
149                deploy::DeployArgs {
150                    skip_push,
151                    profile_name: profile,
152                    no_sync,
153                    yes,
154                    dry_run,
155                },
156                config,
157            )
158            .await
159        },
160        CloudCommands::Status => status::execute().await,
161        CloudCommands::Restart { tenant, yes } => restart::execute(tenant, yes, config).await,
162        CloudCommands::Sync { command } => sync::execute(command, config).await,
163        CloudCommands::Secrets(cmd) => secrets::execute(cmd, config).await,
164        CloudCommands::Dockerfile => execute_dockerfile(),
165        CloudCommands::Db(cmd) => db::execute(cmd, config).await,
166        CloudCommands::Domain(cmd) => domain::execute(cmd, config).await,
167    }
168}
169
170fn execute_dockerfile() -> Result<()> {
171    use crate::shared::project::ProjectRoot;
172
173    let project = ProjectRoot::discover().map_err(|e| anyhow::anyhow!("{}", e))?;
174    dockerfile::print_dockerfile_suggestion(project.as_path());
175    Ok(())
176}