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