partiri-cli 0.2.0

Partiri CLI — Deploy and manage services on Partiri Cloud
#![recursion_limit = "512"]

use clap::Parser;

mod cli;
mod client;
mod config;
mod error;
mod modules;
mod output;

use cli::{
    Cli, Commands, LlmCommands, McpCommands, PodCommands, ProjectCommands, RegionCommands,
    ServiceCommands, ServicesCommands, WorkspaceCommands,
};
use client::ApiClient;
use config::PartiriConfig;

fn main() {
    // Use try_parse so we can rewrite clap's exit code (2 by default) to 1 —
    // we reserve exit 2 for user cancellation (Ctrl-C / inquire abort).
    let cli = match Cli::try_parse() {
        Ok(c) => c,
        Err(e) => {
            let _ = e.print();
            // ExitCode is set in clap's Error type; both DisplayHelp and
            // DisplayVersion are non-error early exits (use 0). Everything
            // else (bad flag, missing arg, unknown subcommand, etc.) is a
            // usage error and should be exit 1.
            let code = match e.kind() {
                clap::error::ErrorKind::DisplayHelp
                | clap::error::ErrorKind::DisplayVersion
                | clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => 0,
                _ => 1,
            };
            std::process::exit(code);
        }
    };
    output::init_ctx(output::make_ctx(cli.json, cli.yes, cli.no_input));
    if let Err(e) = run(cli) {
        let code = exit_code_for(&*e);
        output::print_error(&*e);
        std::process::exit(code);
    }
}

/// Cancellation (Ctrl-C, inquire abort) maps to exit code 2; everything else is 1.
fn exit_code_for(err: &(dyn std::error::Error + 'static)) -> i32 {
    if let Some(c) = err.downcast_ref::<error::CliError>() {
        if c.code == "cancelled" {
            return 2;
        }
    }
    if err.to_string().trim() == "Cancelled." {
        return 2;
    }
    1
}

fn run(cli: Cli) -> error::Result<()> {
    match cli.command {
        Commands::Auth {
            key,
            key_stdin,
            force,
        } => {
            modules::auth::run(modules::auth::AuthArgs {
                key,
                key_stdin,
                force,
            })?;
        }

        Commands::Init { template } => {
            modules::init::run(modules::init::InitArgs { template })?;
        }

        Commands::Validate { remote } => {
            let config = PartiriConfig::load()?;
            if remote {
                let client = ApiClient::new()?;
                modules::validate::run_remote(&client, &config)?;
            } else {
                modules::validate::run(&config)?;
            }
        }

        // Pull does not require an existing config file
        Commands::Service(ServiceCommands::Pull) => {
            let client = ApiClient::new()?;
            modules::service::pull::run(&client)?;
        }

        Commands::Service(cmd) => {
            let client = ApiClient::new()?;
            let config = PartiriConfig::load()?;
            match cmd {
                ServiceCommands::Create => modules::service::create::run(&client, config)?,
                ServiceCommands::Push => modules::service::push::run(&client, &config)?,
                ServiceCommands::Metrics => {
                    let refreshed =
                        modules::service::pull::silent_refresh(&client, &config).unwrap_or(config);
                    modules::service::status::run(&client, &refreshed)?
                }
                ServiceCommands::Logs => {
                    let refreshed =
                        modules::service::pull::silent_refresh(&client, &config).unwrap_or(config);
                    modules::service::logs::run(&client, &refreshed)?
                }
                ServiceCommands::Jobs => modules::jobs::run_list(&client, &config)?,
                ServiceCommands::Deploy => modules::service::deploy::run(&client, &config)?,
                ServiceCommands::Pause => modules::service::pause::run(&client, &config)?,
                ServiceCommands::Unpause => modules::service::unpause::run(&client, &config)?,
                ServiceCommands::Kill => modules::service::kill::run(&client, &config)?,
                ServiceCommands::Link {
                    workspace,
                    project,
                    region,
                    pod,
                    token,
                    clear_token,
                } => modules::service::link::run(
                    &client,
                    config,
                    modules::service::link::LinkArgs {
                        workspace,
                        project,
                        region,
                        pod,
                        token,
                        clear_token,
                    },
                )?,
                ServiceCommands::Token { secret, clear } => modules::service::token::run(
                    &client,
                    config,
                    modules::service::token::TokenArgs { secret, clear },
                )?,
                ServiceCommands::Pull => unreachable!(),
            }
        }

        Commands::Projects(ProjectCommands::List { workspace }) => {
            let client = ApiClient::new()?;
            modules::projects::run_list(&client, workspace)?;
        }

        Commands::Projects(ProjectCommands::Create {
            workspace,
            name,
            environment,
        }) => {
            let client = ApiClient::new()?;
            modules::projects::run_create(
                &client,
                modules::projects::CreateArgs {
                    workspace,
                    name,
                    environment,
                },
            )?;
        }

        Commands::Services(ServicesCommands::List { project }) => {
            let client = ApiClient::new()?;
            modules::services::run_list(&client, &project)?;
        }

        Commands::Workspaces(WorkspaceCommands::List) => {
            let client = ApiClient::new()?;
            modules::workspaces::run_list(&client)?;
        }

        Commands::Regions(RegionCommands::List { workspace }) => {
            let client = ApiClient::new()?;
            modules::regions::run_list(&client, &workspace)?;
        }

        Commands::Pods(PodCommands::List { workspace }) => {
            let client = ApiClient::new()?;
            modules::pods::run_list(&client, &workspace)?;
        }

        Commands::Llm(LlmCommands::Guide) => modules::llm::run_guide()?,
        Commands::Llm(LlmCommands::Schema) => modules::llm::run_schema()?,
        Commands::Llm(LlmCommands::Template {
            deploy_type,
            runtime,
            source,
        }) => modules::llm::run_template(modules::llm::TemplateArgs {
            deploy_type,
            runtime,
            source,
        })?,
        Commands::Llm(LlmCommands::Examples) => modules::llm::run_examples()?,
        Commands::Llm(LlmCommands::Capabilities) => modules::llm::run_capabilities()?,
        Commands::Llm(LlmCommands::Errors) => modules::llm::run_errors()?,
        Commands::Llm(LlmCommands::Explain { command }) => modules::llm::run_explain(&command)?,
        Commands::Llm(LlmCommands::Whoami) => {
            let client = ApiClient::new()?;
            modules::llm::run_whoami(&client)?;
        }
        Commands::Llm(LlmCommands::Doctor) => modules::llm::run_doctor()?,
        Commands::Llm(LlmCommands::Context { workspace }) => {
            let client = ApiClient::new()?;
            modules::llm::run_context(&client, workspace)?;
        }
        Commands::Llm(LlmCommands::Next) => modules::llm::run_next()?,

        Commands::Mcp(McpCommands::Install { client }) => {
            modules::mcp::install::run(client.as_deref())?;
        }

        Commands::Mcp(McpCommands::Uninstall { client }) => {
            modules::mcp::uninstall::run(client.as_deref())?;
        }
    }

    Ok(())
}