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