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 dialoguer::theme::ColorfulTheme;
use dialoguer::{Confirm, Input};
use std::process::Command;
use systemprompt_cloud::constants::docker::{COMPOSE_PATH, container_name};
use systemprompt_logging::CliService;

use super::SetupArgs;
use super::common::{PostgresConfig, enable_extensions, generate_password, test_connection};
use super::docker_compose::{
    create_compose_files_if_missing, is_compose_available, is_container_running,
    is_docker_available, start_compose, wait_for_postgres_ready,
};
use super::docker_database::create_database_in_docker;

pub async fn setup_docker_postgres_non_interactive(
    config: &PostgresConfig,
    env_name: &str,
) -> Result<PostgresConfig> {
    if !is_docker_available() {
        anyhow::bail!("Docker is not installed or not in PATH.");
    }
    if !is_compose_available() {
        anyhow::bail!("Docker Compose is not available.");
    }

    let compose_dir = std::env::current_dir()?.join(COMPOSE_PATH);
    let container = container_name(env_name);
    create_compose_files_if_missing(&compose_dir, &container, config.port)?;

    if is_container_running(&container) {
        if !test_connection(config).await {
            create_database_in_docker(config, &container).await?;
        }
        enable_extensions(config).await?;
        return Ok(config.clone());
    }

    start_compose(config, &compose_dir, &container)?;
    wait_for_postgres_ready(config, &container);
    enable_extensions(config).await?;

    Ok(config.clone())
}

pub async fn setup_docker_postgres_interactive(
    args: &SetupArgs,
    env_name: &str,
) -> Result<PostgresConfig> {
    CliService::info("Setting up PostgreSQL with Docker...");

    if !is_docker_available() {
        anyhow::bail!(
            "Docker is not installed or not in PATH.\nInstall Docker: https://docs.docker.com/get-docker/"
        );
    }

    if !is_compose_available() {
        anyhow::bail!(
            "Docker Compose is not available.\nEnsure Docker Desktop is installed or install \
             docker-compose."
        );
    }

    CliService::success("Docker and Docker Compose are available");

    let default_user = args.effective_db_user(env_name);
    let user: String = Input::with_theme(&ColorfulTheme::default())
        .with_prompt("Database user")
        .default(default_user)
        .interact_text()?;

    let password = args.db_password.clone().unwrap_or_else(generate_password);
    CliService::success(&format!("Generated password: {}", password));

    let default_db = args.effective_db_name(env_name);
    let database: String = Input::with_theme(&ColorfulTheme::default())
        .with_prompt("Database name")
        .default(default_db)
        .interact_text()?;

    let port: u16 = Input::with_theme(&ColorfulTheme::default())
        .with_prompt("PostgreSQL port")
        .default(args.db_port)
        .interact_text()?;

    let config = PostgresConfig {
        host: "localhost".to_string(),
        port,
        user,
        password,
        database,
    };

    let compose_dir = std::env::current_dir()?.join(COMPOSE_PATH);
    let container = container_name(env_name);
    create_compose_files_if_missing(&compose_dir, &container, port)?;
    if is_container_running(&container) {
        CliService::info(&format!(
            "PostgreSQL container '{}' is already running",
            container
        ));

        let reuse = Confirm::with_theme(&ColorfulTheme::default())
            .with_prompt("Use existing container?")
            .default(true)
            .interact()?;

        if reuse {
            if !test_connection(&config).await {
                CliService::info("Creating database and user in existing container...");
                create_database_in_docker(&config, &container).await?;
            }
            enable_extensions(&config).await?;
            return Ok(config);
        }

        CliService::info("Stopping existing container...");
        if let Err(e) = Command::new("docker").args(["stop", &container]).output() {
            tracing::warn!(container = %container, error = %e, "docker stop failed");
        }
        if let Err(e) = Command::new("docker").args(["rm", &container]).output() {
            tracing::warn!(container = %container, error = %e, "docker rm failed");
        }
    }

    start_compose(&config, &compose_dir, &container)?;

    wait_for_postgres_ready(&config, &container);

    enable_extensions(&config).await?;

    Ok(config)
}