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