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