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, and dockerfile subcommands, and declares each command's
5//! profile/secret requirements via [`DescribeCommand`].
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10pub mod auth;
11pub mod backup;
12pub mod deploy;
13pub mod doctor;
14pub mod init;
15pub mod profile;
16mod status;
17pub mod templates;
18pub mod tenant;
19pub mod types;
20
21pub use systemprompt_cloud::{Environment, OAuthProvider};
22
23use crate::cli_settings::CliConfig;
24use crate::context::CommandContext;
25use crate::descriptor::{CommandDescriptor, DescribeCommand};
26use anyhow::Result;
27use clap::Subcommand;
28
29#[derive(Debug, Subcommand)]
30pub enum CloudCommands {
31    #[command(subcommand, about = "Authentication (login, logout, whoami)")]
32    Auth(auth::AuthCommands),
33
34    #[command(about = "Initialize project structure")]
35    Init {
36        #[arg(long)]
37        force: bool,
38    },
39
40    #[command(subcommand_required = false, about = "Manage tenants (local or cloud)")]
41    Tenant {
42        #[command(subcommand)]
43        command: Option<tenant::TenantCommands>,
44    },
45
46    #[command(subcommand_required = false, about = "Manage profiles")]
47    Profile {
48        #[command(subcommand)]
49        command: Option<profile::ProfileCommands>,
50    },
51
52    #[command(about = "Deploy to systemprompt.io Cloud")]
53    Deploy {
54        #[arg(long)]
55        skip_push: bool,
56
57        #[arg(long, short = 'p', help = "Profile name to deploy")]
58        profile: Option<String>,
59
60        #[arg(long, help = "Run the pre-deploy preflight only, without deploying")]
61        check: bool,
62    },
63
64    #[command(about = "Download the tenant's runtime services/ tree to a local directory")]
65    Backup {
66        #[arg(long, short = 'p', help = "Profile name to back up")]
67        profile: Option<String>,
68
69        #[arg(
70            long,
71            short = 'o',
72            help = "Output directory (default: ./systemprompt-backup-<timestamp>)"
73        )]
74        output: Option<std::path::PathBuf>,
75
76        #[arg(long, help = "List remote files without downloading")]
77        list: bool,
78    },
79
80    #[command(about = "Run the pre-deploy preflight for a profile without deploying")]
81    Doctor {
82        #[arg(long, short = 'p', help = "Profile name to check")]
83        profile: Option<String>,
84
85        #[arg(
86            long,
87            help = "Also run the multi-replica checks: identity fingerprints, instance id, \
88                    trusted proxies, write primary, replica lag, readiness"
89        )]
90        distributed: bool,
91    },
92
93    #[command(about = "Check cloud deployment status")]
94    Status,
95
96    #[command(about = "Generate Dockerfile based on discovered extensions")]
97    Dockerfile,
98}
99
100impl DescribeCommand for CloudCommands {
101    fn descriptor(&self) -> CommandDescriptor {
102        match self {
103            Self::Deploy { .. } => CommandDescriptor::PROFILE_AND_SECRETS,
104            Self::Backup { .. } | Self::Status => CommandDescriptor::PROFILE_ONLY,
105            _ => CommandDescriptor::NONE,
106        }
107    }
108}
109
110pub async fn execute(cmd: CloudCommands, ctx: &CommandContext) -> Result<()> {
111    match cmd {
112        CloudCommands::Auth(cmd) => auth::execute(cmd, ctx).await,
113        CloudCommands::Init { force } => init::execute(force, &ctx.cli),
114        CloudCommands::Tenant { command } => tenant::execute(command, ctx).await,
115        CloudCommands::Profile { command } => profile::execute(command, ctx).await,
116        CloudCommands::Deploy {
117            skip_push,
118            profile,
119            check,
120        } => {
121            deploy::execute(
122                deploy::DeployArgs {
123                    skip_push,
124                    profile_name: profile,
125                    check,
126                },
127                ctx.prompter(),
128                &ctx.cli,
129            )
130            .await
131        },
132        CloudCommands::Backup {
133            profile,
134            output,
135            list,
136        } => {
137            backup::execute(
138                backup::BackupArgs {
139                    profile_name: profile,
140                    output,
141                    list,
142                },
143                ctx.prompter(),
144                &ctx.cli,
145            )
146            .await
147        },
148        CloudCommands::Doctor {
149            profile,
150            distributed,
151        } => doctor::execute(profile, distributed, ctx.prompter(), &ctx.cli).await,
152        CloudCommands::Status => {
153            let result = status::execute(&ctx.cli).await?;
154            crate::shared::render_result(&result, &ctx.cli);
155            Ok(())
156        },
157        CloudCommands::Dockerfile => execute_dockerfile(&ctx.cli),
158    }
159}
160
161fn execute_dockerfile(config: &CliConfig) -> Result<()> {
162    use crate::shared::project::ProjectRoot;
163    use types::DockerfileOutput;
164
165    let project = ProjectRoot::discover().map_err(|e| anyhow::anyhow!("{}", e))?;
166    let content = systemprompt_cloud::deploy::generate_dockerfile_content(project.as_path());
167
168    let output = DockerfileOutput {
169        content: content.clone(),
170    };
171
172    if config.is_json_output() {
173        crate::shared::render_result(
174            &crate::shared::CommandOutput::copy_paste_titled("Dockerfile", output.content),
175            config,
176        );
177    } else {
178        systemprompt_logging::CliService::output(&content);
179    }
180
181    Ok(())
182}