systemprompt_cli/commands/cloud/
mod.rs1pub mod auth;
8pub mod db;
9mod deploy;
10pub mod dockerfile;
11mod domain;
12mod init;
13pub mod profile;
14mod restart;
15mod secrets;
16mod status;
17pub mod sync;
18pub mod templates;
19pub mod tenant;
20pub mod types;
21
22pub use systemprompt_cloud::{Environment, OAuthProvider};
23
24use crate::cli_settings::CliConfig;
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(
61 long,
62 help = "Skip pre-deploy sync from cloud (WARNING: may lose runtime files)"
63 )]
64 no_sync: bool,
65
66 #[arg(short = 'y', long, help = "Skip confirmation prompts")]
67 yes: bool,
68
69 #[arg(long, help = "Preview sync without deploying")]
70 dry_run: bool,
71 },
72
73 #[command(about = "Check cloud deployment status")]
74 Status,
75
76 #[command(about = "Restart tenant machine")]
77 Restart {
78 #[arg(long)]
79 tenant: Option<String>,
80
81 #[arg(short = 'y', long, help = "Skip confirmation prompts")]
82 yes: bool,
83 },
84
85 #[command(
86 subcommand_required = false,
87 about = "Sync between local and cloud environments"
88 )]
89 Sync {
90 #[command(subcommand)]
91 command: Option<sync::SyncCommands>,
92 },
93
94 #[command(subcommand, about = "Manage secrets for cloud tenant")]
95 Secrets(secrets::SecretsCommands),
96
97 #[command(about = "Generate Dockerfile based on discovered extensions")]
98 Dockerfile,
99
100 #[command(subcommand, about = "Cloud database operations")]
101 Db(db::CloudDbCommands),
102
103 #[command(subcommand, about = "Manage custom domain and TLS certificates")]
104 Domain(domain::DomainCommands),
105}
106
107impl DescribeCommand for CloudCommands {
108 fn descriptor(&self) -> CommandDescriptor {
109 match self {
110 Self::Deploy { .. } => CommandDescriptor::PROFILE_AND_SECRETS,
111 Self::Sync { command: Some(_) } | Self::Secrets { .. } => {
112 CommandDescriptor::PROFILE_AND_SECRETS
113 },
114 Self::Status | Self::Restart { .. } | Self::Domain { .. } => {
115 CommandDescriptor::PROFILE_ONLY
116 },
117 _ => CommandDescriptor::NONE,
118 }
119 }
120}
121
122impl CloudCommands {
123 pub const fn requires_profile(&self) -> bool {
124 matches!(
125 self,
126 Self::Sync { command: Some(_) }
127 | Self::Status
128 | Self::Restart { .. }
129 | Self::Secrets { .. }
130 | Self::Domain { .. }
131 )
132 }
133
134 pub const fn requires_secrets(&self) -> bool {
135 matches!(self, Self::Sync { command: Some(_) } | Self::Secrets { .. })
136 }
137}
138
139pub async fn execute(cmd: CloudCommands, config: &CliConfig) -> Result<()> {
140 match cmd {
141 CloudCommands::Auth(cmd) => auth::execute(cmd, config).await,
142 CloudCommands::Init { force } => init::execute(force, config),
143 CloudCommands::Tenant { command } => tenant::execute(command, config).await,
144 CloudCommands::Profile { command } => profile::execute(command, config).await,
145 CloudCommands::Deploy {
146 skip_push,
147 profile,
148 no_sync,
149 yes,
150 dry_run,
151 } => {
152 deploy::execute(
153 deploy::DeployArgs {
154 skip_push,
155 profile_name: profile,
156 no_sync,
157 yes,
158 dry_run,
159 },
160 config,
161 )
162 .await
163 },
164 CloudCommands::Status => {
165 let result = status::execute(config).await?;
166 crate::shared::render_result(&result);
167 Ok(())
168 },
169 CloudCommands::Restart { tenant, yes } => {
170 let result = restart::execute(tenant, yes, config).await?;
171 crate::shared::render_result(&result);
172 Ok(())
173 },
174 CloudCommands::Sync { command } => sync::execute(command, config).await,
175 CloudCommands::Secrets(cmd) => secrets::execute(cmd, config).await,
176 CloudCommands::Dockerfile => execute_dockerfile(config),
177 CloudCommands::Db(cmd) => db::execute(cmd, config).await,
178 CloudCommands::Domain(cmd) => domain::execute(cmd, config).await,
179 }
180}
181
182fn execute_dockerfile(config: &CliConfig) -> Result<()> {
183 use crate::shared::project::ProjectRoot;
184 use types::DockerfileOutput;
185
186 let project = ProjectRoot::discover().map_err(|e| anyhow::anyhow!("{}", e))?;
187 let content = dockerfile::generate_dockerfile_content(project.as_path());
188
189 let output = DockerfileOutput {
190 content: content.clone(),
191 };
192
193 if config.is_json_output() {
194 crate::shared::render_result(
195 &crate::shared::CommandResult::copy_paste(output).with_title("Dockerfile"),
196 );
197 } else {
198 systemprompt_logging::CliService::output(&content);
199 }
200
201 Ok(())
202}