Skip to main content

systemprompt_cli/commands/cloud/auth/
mod.rs

1//! `cloud auth` subcommands: login, logout, whoami, and admin-user.
2//!
3//! Dispatches the [`AuthCommands`] enum to the per-command modules that manage
4//! the locally stored cloud credentials and authenticated-user state,
5//! including projecting the authenticated cloud user as an admin into local
6//! profile databases.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11pub mod admin_user;
12mod login;
13mod logout;
14mod whoami;
15
16use anyhow::anyhow;
17use systemprompt_logging::CliService;
18
19pub use login::{build_login_output, complete_login};
20
21use crate::context::CommandContext;
22use crate::shared::render_result;
23use anyhow::Result;
24use clap::{Args, Subcommand};
25
26use super::Environment;
27
28#[derive(Debug, Subcommand)]
29pub enum AuthCommands {
30    #[command(about = "Authenticate with systemprompt.io Cloud via OAuth")]
31    Login {
32        #[arg(value_enum, default_value_t = Environment::default(), hide = true)]
33        environment: Environment,
34    },
35
36    #[command(about = "Clear saved cloud credentials")]
37    Logout(LogoutArgs),
38
39    #[command(
40        about = "Show current authenticated user and token status",
41        alias = "status"
42    )]
43    Whoami,
44
45    #[command(about = "Sync cloud user as admin to local profile databases")]
46    AdminUser(AdminUserSyncArgs),
47}
48
49#[derive(Debug, Args)]
50pub struct AdminUserSyncArgs {
51    #[arg(short, long, help = "Show detailed discovery information")]
52    pub verbose: bool,
53
54    #[arg(long, help = "Specific profile to sync (default: all profiles)")]
55    pub profile: Option<String>,
56
57    #[arg(long, help = "Override database URL (requires --profile)")]
58    pub database_url: Option<String>,
59}
60
61#[derive(Debug, Clone, Copy, Args)]
62pub struct LogoutArgs {
63    #[arg(short = 'y', long, help = "Skip confirmation prompts")]
64    pub yes: bool,
65}
66
67pub async fn execute(cmd: AuthCommands, ctx: &CommandContext) -> Result<()> {
68    match cmd {
69        AuthCommands::Login { environment } => {
70            let result = login::execute(environment, ctx.prompter(), &ctx.cli).await?;
71            render_result(&result, &ctx.cli);
72            Ok(())
73        },
74        AuthCommands::Logout(args) => {
75            let result = logout::execute(args, ctx.prompter(), &ctx.cli).await?;
76            render_result(&result, &ctx.cli);
77            Ok(())
78        },
79        AuthCommands::Whoami => {
80            let result = whoami::execute(&ctx.cli).await?;
81            render_result(&result, &ctx.cli);
82            Ok(())
83        },
84        AuthCommands::AdminUser(args) => execute_admin_user_sync(args).await,
85    }
86}
87
88async fn execute_admin_user_sync(args: AdminUserSyncArgs) -> Result<()> {
89    CliService::section("Admin User Sync");
90
91    let cloud_user = admin_user::CloudUser::from_credentials()?
92        .ok_or_else(|| anyhow!("Not logged in. Run 'systemprompt cloud auth login' first."))?;
93
94    CliService::key_value("Cloud User", &cloud_user.email);
95
96    if let Some(profile_name) = &args.profile {
97        let database_url = if let Some(url) = &args.database_url {
98            url.clone()
99        } else {
100            let discovery = admin_user::discover_profiles()?;
101            discovery
102                .profiles
103                .into_iter()
104                .find(|p| &p.name == profile_name)
105                .and_then(|p| p.database_url)
106                .ok_or_else(|| {
107                    anyhow!(
108                        "Profile '{}' not found or has no database_url",
109                        profile_name
110                    )
111                })?
112        };
113
114        let result =
115            admin_user::sync_admin_to_database(&cloud_user, &database_url, profile_name).await;
116        admin_user::print_sync_results(&[result]);
117    } else {
118        if args.database_url.is_some() {
119            return Err(anyhow!("--database-url requires --profile"));
120        }
121
122        let results = admin_user::sync_admin_to_all_profiles(&cloud_user, args.verbose).await;
123        admin_user::print_sync_results(&results);
124    }
125
126    Ok(())
127}