systemprompt_cli/commands/cloud/
mod.rs1pub 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}