systemprompt-cli 0.2.1

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
mod cleanup;
pub mod restart;
pub mod serve;
mod start;
mod status;
mod stop;
mod types;

use crate::cli_settings::CliConfig;
use crate::shared::render_result;
use anyhow::{Context, Result};
use clap::Subcommand;
use std::sync::Arc;
use systemprompt_runtime::AppContext;

#[derive(Debug, Clone, Subcommand)]
pub enum StartTarget {
    Agent { agent: String },
    Mcp { server_name: String },
}

#[derive(Debug, Clone, Subcommand)]
pub enum StopTarget {
    Agent {
        agent: String,
        #[arg(long, help = "Force stop (SIGKILL)")]
        force: bool,
    },
    Mcp {
        server_name: String,
        #[arg(long, help = "Force stop (SIGKILL)")]
        force: bool,
    },
}

#[derive(Debug, Subcommand)]
pub enum ServicesCommands {
    #[command(
        about = "Start API, agents, and MCP servers",
        after_help = "EXAMPLES:\n  systemprompt infra services start\n  systemprompt infra \
                      services start --api\n  systemprompt infra services start --agents --mcp\n  \
                      systemprompt infra services start agent <name>"
    )]
    Start {
        #[command(subcommand)]
        target: Option<StartTarget>,

        #[arg(long, help = "Start all services")]
        all: bool,

        #[arg(long, help = "Start API server only")]
        api: bool,

        #[arg(long, help = "Start agents only")]
        agents: bool,

        #[arg(long, help = "Start MCP servers only")]
        mcp: bool,

        #[arg(long, help = "Run in foreground (default)")]
        foreground: bool,

        #[arg(long, help = "Skip database migrations")]
        skip_migrate: bool,

        #[arg(long, help = "Kill process using the port if occupied")]
        kill_port_process: bool,
    },

    #[command(
        about = "Stop running services gracefully",
        after_help = "EXAMPLES:\n  systemprompt infra services stop\n  systemprompt infra \
                      services stop --api\n  systemprompt infra services stop agent <name> \
                      [--force]"
    )]
    Stop {
        #[command(subcommand)]
        target: Option<StopTarget>,

        #[arg(long, help = "Stop all services")]
        all: bool,

        #[arg(long, help = "Stop API server only")]
        api: bool,

        #[arg(long, help = "Stop agents only")]
        agents: bool,

        #[arg(long, help = "Stop MCP servers only")]
        mcp: bool,

        #[arg(long, help = "Force stop (SIGKILL)")]
        force: bool,
    },

    #[command(about = "Restart services")]
    Restart {
        #[command(subcommand)]
        target: Option<RestartTarget>,

        #[arg(long, help = "Restart only failed services")]
        failed: bool,

        #[arg(long, help = "Restart all agents")]
        agents: bool,

        #[arg(long, help = "Restart all MCP servers")]
        mcp: bool,
    },

    #[command(about = "Show detailed service status")]
    Status {
        #[arg(long, help = "Show detailed information")]
        detailed: bool,

        #[arg(long, help = "Output as JSON")]
        json: bool,

        #[arg(long, help = "Include health check results")]
        health: bool,
    },

    #[command(about = "Clean up orphaned processes and stale entries")]
    Cleanup {
        #[arg(short = 'y', long, help = "Skip confirmation prompt")]
        yes: bool,

        #[arg(long, help = "Preview cleanup without executing")]
        dry_run: bool,
    },

    #[command(about = "Start API server (automatically starts agents and MCP servers)")]
    Serve {
        #[arg(long, help = "Run in foreground mode")]
        foreground: bool,

        #[arg(long, help = "Kill process using the port if occupied")]
        kill_port_process: bool,
    },
}

#[derive(Debug, Clone, Subcommand)]
pub enum RestartTarget {
    Api,
    Agent {
        agent: String,
    },
    Mcp {
        server_name: String,
        #[arg(long, help = "Rebuild the binary before restarting")]
        build: bool,
    },
}

pub async fn execute(command: ServicesCommands, config: &CliConfig) -> Result<()> {
    match command {
        ServicesCommands::Start {
            target,
            all,
            api,
            agents,
            mcp,
            foreground: _,
            skip_migrate,
            kill_port_process,
        } => {
            if let Some(individual) = target {
                let ctx = Arc::new(
                    AppContext::new()
                        .await
                        .context("Failed to initialize application context")?,
                );
                return match individual {
                    StartTarget::Agent { agent } => {
                        start::execute_individual_agent(&ctx, &agent, config).await
                    },
                    StartTarget::Mcp { server_name } => {
                        start::execute_individual_mcp(&ctx, &server_name, config).await
                    },
                };
            }

            let flags = start::ServiceFlags {
                all,
                targets: start::ServiceTargetFlags { api, agents, mcp },
            };
            let service_target = start::ServiceTarget::from_flags(flags);
            let options = start::StartupOptions {
                skip_migrate,
                kill_port_process,
            };
            start::execute(service_target, options, config).await
        },

        ServicesCommands::Stop {
            target,
            all,
            api,
            agents,
            mcp,
            force,
        } => {
            if let Some(individual) = target {
                let ctx = Arc::new(
                    AppContext::new()
                        .await
                        .context("Failed to initialize application context")?,
                );
                return match individual {
                    StopTarget::Agent { agent, force } => {
                        let result =
                            stop::execute_individual_agent(&ctx, &agent, force, config).await?;
                        render_result(&result);
                        Ok(())
                    },
                    StopTarget::Mcp { server_name, force } => {
                        let result =
                            stop::execute_individual_mcp(&ctx, &server_name, force, config).await?;
                        render_result(&result);
                        Ok(())
                    },
                };
            }

            let flags = start::ServiceFlags {
                all,
                targets: start::ServiceTargetFlags { api, agents, mcp },
            };
            let service_target = start::ServiceTarget::from_flags(flags);
            let result = stop::execute(service_target, force, config).await?;
            render_result(&result);
            Ok(())
        },

        ServicesCommands::Restart {
            target,
            failed,
            agents,
            mcp,
        } => {
            let ctx = Arc::new(
                AppContext::new()
                    .await
                    .context("Failed to initialize application context")?,
            );

            let result = if failed {
                restart::execute_failed(&ctx, config).await?
            } else if agents {
                restart::execute_all_agents(&ctx, config).await?
            } else if mcp {
                restart::execute_all_mcp(&ctx, config).await?
            } else {
                match target {
                    Some(RestartTarget::Api) => restart::execute_api(config).await?,
                    Some(RestartTarget::Agent { agent }) => {
                        restart::execute_agent(&ctx, &agent, config).await?
                    },
                    Some(RestartTarget::Mcp { server_name, build }) => {
                        restart::execute_mcp(&ctx, &server_name, build, config).await?
                    },
                    None => {
                        return Err(anyhow::anyhow!(
                            "Must specify target (api, agent, mcp) or use --failed/--agents/--mcp \
                             flag"
                        ));
                    },
                }
            };
            render_result(&result);
            Ok(())
        },

        ServicesCommands::Status {
            detailed,
            json,
            health,
        } => {
            let result = status::execute(detailed, json, health, config).await?;
            render_result(&result);
            Ok(())
        },

        ServicesCommands::Cleanup { yes, dry_run } => {
            let result = cleanup::execute(yes, dry_run, config).await?;
            render_result(&result);
            Ok(())
        },

        ServicesCommands::Serve {
            foreground,
            kill_port_process,
        } => serve::execute(foreground, kill_port_process, config).await,
    }
}

pub fn load_service_configs(
    _ctx: &AppContext,
) -> Result<Vec<systemprompt_scheduler::ServiceConfig>> {
    use systemprompt_loader::ConfigLoader;
    use systemprompt_scheduler::{ServiceConfig, ServiceType};

    let services_config = ConfigLoader::load()?;
    let mut configs = Vec::new();

    for (name, agent) in &services_config.agents {
        configs.push(ServiceConfig {
            name: name.clone(),
            service_type: ServiceType::Agent,
            port: agent.port,
            enabled: agent.enabled,
        });
    }

    for (name, mcp) in &services_config.mcp_servers {
        configs.push(ServiceConfig {
            name: name.clone(),
            service_type: ServiceType::Mcp,
            port: mcp.port,
            enabled: mcp.enabled,
        });
    }

    Ok(configs)
}