systemprompt-cli 0.6.1

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
use anyhow::Result;
use systemprompt_cloud::{CloudApiClient, CloudPath, CredentialsBootstrap, get_cloud_paths};
use systemprompt_config::ProfileBootstrap;
use systemprompt_logging::CliService;

use crate::cli_settings::CliConfig;
use crate::cloud::types::{CloudStatusOutput, CredentialsInfo, ProfileInfo, TenantStatusInfo};
use crate::shared::CommandResult;

pub async fn execute(config: &CliConfig) -> Result<CommandResult<CloudStatusOutput>> {
    let (profile_info, tenant_id_from_profile) = load_profile_info();
    let (credentials_info, tenant_statuses) =
        load_credentials_and_tenants(config, tenant_id_from_profile.as_deref()).await?;

    let output = CloudStatusOutput {
        profile: profile_info.clone(),
        credentials: credentials_info.clone(),
        tenants: tenant_statuses.clone(),
    };

    if !config.is_json_output() {
        render_status(profile_info.as_ref(), &credentials_info, &tenant_statuses);
    }

    Ok(CommandResult::card(output).with_title("Cloud Status"))
}

fn load_profile_info() -> (Option<ProfileInfo>, Option<String>) {
    let profile = match ProfileBootstrap::get() {
        Ok(p) => p,
        Err(e) => {
            tracing::debug!(error = %e, "Failed to get profile bootstrap");
            return (None, None);
        },
    };

    let mut info = ProfileInfo {
        name: profile.name.clone(),
        display_name: Some(profile.display_name.clone()),
        database_url: None,
        tenant_id: None,
        validation_mode: None,
        credentials_path: None,
        routing: None,
        is_active: None,
        session_status: None,
    };
    let mut tenant_id_from_profile = None;

    if let Some(cloud) = &profile.cloud {
        let paths = get_cloud_paths();
        let creds_path = paths.resolve(CloudPath::Credentials);
        info.credentials_path = Some(creds_path.display().to_string());
        info.validation_mode = Some(format!("{:?}", cloud.validation));
        if let Some(ref tid) = cloud.tenant_id {
            info.tenant_id = Some(tid.clone());
            tenant_id_from_profile = Some(tid.clone());
        }
    }

    (Some(info), tenant_id_from_profile)
}

async fn load_credentials_and_tenants(
    config: &CliConfig,
    tenant_id_from_profile: Option<&str>,
) -> Result<(CredentialsInfo, Vec<TenantStatusInfo>)> {
    let mut credentials_info = CredentialsInfo {
        authenticated: false,
        user_email: None,
        token_expired: false,
    };
    let mut tenant_statuses = Vec::new();

    let creds = match CredentialsBootstrap::get() {
        Ok(Some(c)) => c,
        Ok(None) => return Ok((credentials_info, tenant_statuses)),
        Err(e) => {
            tracing::debug!(error = %e, "Failed to get credentials bootstrap");
            return Ok((credentials_info, tenant_statuses));
        },
    };

    credentials_info.authenticated = true;
    credentials_info.user_email = Some(creds.user_email.clone());
    credentials_info.token_expired = creds.is_token_expired();

    let api_client = CloudApiClient::new(&creds.api_url, &creds.api_token)?;
    let spinner = (!config.is_json_output()).then(|| CliService::spinner("Fetching tenants..."));
    let json_mode = config.is_json_output();
    let tenants_result = api_client.list_tenants().await;
    if let Some(s) = &spinner {
        s.finish_and_clear();
    }
    let tenants = match tenants_result {
        Ok(t) => t,
        Err(e) => {
            tracing::warn!(error = %e, "Could not fetch tenants");
            return Ok((credentials_info, tenant_statuses));
        },
    };

    for tenant in &tenants {
        let mut status_info = TenantStatusInfo {
            id: tenant.id.clone(),
            name: tenant.name.clone(),
            status: "unknown".to_string(),
            url: None,
            message: None,
            configured_in_profile: tenant_id_from_profile == Some(tenant.id.as_str()),
        };
        match api_client
            .get_tenant_status(&systemprompt_identifiers::TenantId::new(&tenant.id))
            .await
        {
            Ok(status) => {
                status_info.status = status.status;
                status_info.url = status.app_url;
                status_info.message = status.message;
            },
            Err(e) if !json_mode => {
                status_info.status = format!("error: {}", e);
            },
            Err(_) => {},
        }
        tenant_statuses.push(status_info);
    }

    Ok((credentials_info, tenant_statuses))
}

fn render_status(
    profile_info: Option<&ProfileInfo>,
    credentials_info: &CredentialsInfo,
    tenant_statuses: &[TenantStatusInfo],
) {
    CliService::section("systemprompt.io Cloud Status");
    render_profile(profile_info);
    render_credentials(credentials_info, tenant_statuses);
}

fn render_profile(profile_info: Option<&ProfileInfo>) {
    let Some(profile) = profile_info else {
        CliService::key_value("Profile", "Not initialized");
        return;
    };
    CliService::key_value(
        "Profile",
        &format!(
            "{} ({})",
            profile.name,
            profile.display_name.as_deref().unwrap_or("")
        ),
    );
    if let Some(ref creds_path) = profile.credentials_path {
        CliService::key_value("Credentials path", creds_path);
    }
    if let Some(ref validation_mode) = profile.validation_mode {
        CliService::key_value("Validation mode", validation_mode);
    }
    if let Some(ref tid) = profile.tenant_id {
        CliService::key_value("Tenant ID (profile)", tid);
    }
    if profile.credentials_path.is_none() && profile.validation_mode.is_none() {
        CliService::key_value("Cloud config", "Not configured");
    }
}

fn render_credentials(credentials_info: &CredentialsInfo, tenant_statuses: &[TenantStatusInfo]) {
    if !credentials_info.authenticated {
        CliService::key_value("Authenticated", "No (not initialized)");
        CliService::info("Run 'systemprompt cloud login' to authenticate");
        return;
    }
    CliService::key_value("Authenticated", "Yes");
    if let Some(ref email) = credentials_info.user_email {
        CliService::key_value("User", email);
    }
    CliService::key_value(
        "Token expired",
        if credentials_info.token_expired {
            "Yes"
        } else {
            "No"
        },
    );
    if tenant_statuses.is_empty() {
        CliService::info("No tenants found for this account");
        return;
    }
    for tenant in tenant_statuses {
        CliService::section(&format!("Tenant: {} ({})", tenant.name, tenant.id));
        CliService::key_value("Status", &tenant.status);
        if let Some(ref url) = tenant.url {
            CliService::key_value("URL", url);
        }
        if let Some(ref msg) = tenant.message {
            CliService::info(&format!("Message: {}", msg));
        }
        if tenant.configured_in_profile {
            CliService::info("(configured in profile)");
        }
    }
}