offline-cli 0.3.3

The Offline Protocol developer CLI
//! `offline setup`: provision org, application, and default API key.

use anyhow::Result;
use clap::Args;
use serde_json::json;

use crate::commands::provision::{email_login_required, ensure_provisioned, ProvisionOptions};
use crate::config::CliConfig;
use crate::output;

#[derive(Args)]
pub struct SetupArgs {
    /// Application name when creating a new application
    #[arg(long, default_value = "My Offline App")]
    pub app_name: String,
    /// Organization name when creating a new organization
    #[arg(long)]
    pub org_name: Option<String>,
    /// API key alias
    #[arg(long, default_value = "CLI default")]
    pub key_alias: String,
    /// Key product scope: all | offline_id | mesh_sdk | proof_of_location
    #[arg(long, default_value = "all")]
    pub product: String,
    #[arg(long)]
    pub json: bool,
}

pub async fn run(args: SetupArgs) -> Result<()> {
    let mut config = CliConfig::load()?;
    crate::commands::provision::ensure_logged_in_for_provision(&config, !args.json).await?;
    config = CliConfig::load()?;
    if !config.has_api_credentials() && config.effective_access_token().is_none() {
        return Err(email_login_required("`offline setup`", &config));
    }

    ensure_provisioned(
        &mut config,
        ProvisionOptions {
            app_name: &args.app_name,
            org_name: args.org_name.as_deref(),
            key_alias: Some(&args.key_alias),
            product: &args.product,
        },
    )
    .await?;

    if args.json {
        output::json(&json!({
            "orgId": config.org_id,
            "appId": config.app_id,
            "email": config.email,
            "hasApiKey": config.effective_api_key().is_some(),
        }))?;
    } else {
        output::success("Offline Protocol account is ready for CLI use");
        if let Some(org_id) = &config.org_id {
            output::note(&format!("organization: {org_id}"));
        }
        if let Some(app_id) = &config.app_id {
            output::note(&format!("application: {app_id}"));
        }
        output::note("Run `offline create` to scaffold an app, or `offline whoami --verify` to check credentials");
    }
    Ok(())
}

pub async fn setup_for_create(
    config: &mut CliConfig,
    app_name: &str,
    mesh_enabled: bool,
    pol_enabled: bool,
    interactive: bool,
) -> Result<()> {
    if config.has_api_credentials() {
        return Ok(());
    }
    crate::commands::provision::ensure_logged_in_for_provision(config, interactive).await?;
    let config_reloaded = CliConfig::load()?;
    *config = config_reloaded;
    ensure_provisioned(
        config,
        ProvisionOptions {
            app_name,
            org_name: None,
            key_alias: Some("CLI default"),
            product: crate::commands::provision::product_for_create(mesh_enabled, pol_enabled),
        },
    )
    .await
}