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