partiri-cli 0.2.0

Partiri CLI — Deploy and manage services on Partiri Cloud
use owo_colors::OwoColorize;

use crate::client::ApiClient;
use crate::config::{validate_config, PartiriConfig};
use crate::error::{CliError, Result};
use crate::output::{ctx, print_success_with};

pub fn run(client: &ApiClient, mut config: PartiriConfig) -> Result<()> {
    if config.id.is_some() {
        return Err(Box::new(
            CliError::new(
                "validation",
                format!(
                    "Service already created (id: {}).",
                    config.id.as_deref().unwrap()
                ),
            )
            .with_hint("Use 'partiri service push' to update it.")
            .enriched(),
        ));
    }

    // Validate before sending
    let results = validate_config(&config);
    let errors: Vec<_> = results.iter().filter(|r| !r.ok).collect();
    if !errors.is_empty() {
        if !ctx().json {
            for e in &errors {
                eprintln!("  {} {}: {}", "".red(), e.field.red(), e.message);
            }
        }
        let causes: Vec<String> = errors
            .iter()
            .map(|e| format!("{}: {}", e.field, e.message))
            .collect();
        let mut err = CliError::new(
            "validation",
            "Config validation failed. Run 'partiri validate' for details.",
        )
        .with_hint("Fix the listed fields, then retry. 'partiri llm next' suggests the next step.");
        err.likely_causes = causes;
        return Err(Box::new(err.enriched()));
    }

    let service =
        client.create_service(&config.service, &config.fk_project, &config.fk_workspace)?;

    // Persist the assigned ID
    config.id = Some(service.id.clone());
    config.save()?;

    // JSON envelope carries plain strings (no ANSI). Trailing tip lines are
    // human-only — gate them on !ctx().json so we keep the "exactly one
    // structured result per invocation" stdout contract.
    print_success_with(
        &format!("Service created — ID: {}", service.id),
        &serde_json::json!({
            "id": service.id,
            "external_sd_url": service.external_sd_url,
        }),
    );
    if !ctx().json {
        if let Some(url) = &service.external_sd_url {
            println!("  External URL: {}", url.cyan());
        }
        println!("\n  Run {} to deploy.", "'partiri service deploy'".bold());
    }

    Ok(())
}