systemprompt-cli 0.1.22

systemprompt.io OS - CLI for agent orchestration, AI operations, and system management
Documentation
use crate::cli_settings::CliConfig;
use crate::shared::CommandResult;
use anyhow::{Context, Result};
use std::sync::Arc;
use systemprompt_agent::AgentState;
use systemprompt_agent::services::agent_orchestration::AgentOrchestrator;
use systemprompt_agent::services::registry::AgentRegistry;
use systemprompt_logging::CliService;
use systemprompt_mcp::services::McpManager;
use systemprompt_models::ProfileBootstrap;
use systemprompt_oauth::JwtValidationProviderImpl;
use systemprompt_runtime::AppContext;
use systemprompt_scheduler::{ProcessCleanup, ServiceManagementService};

use super::start::ServiceTarget;
use super::types::{StopIndividualOutput, StopServiceOutput};

const DEFAULT_API_PORT: u16 = 8080;

fn get_api_port() -> u16 {
    ProfileBootstrap::get().map_or(DEFAULT_API_PORT, |p| p.server.port)
}

pub async fn execute(
    target: ServiceTarget,
    force: bool,
    config: &CliConfig,
) -> Result<CommandResult<StopServiceOutput>> {
    let ctx = Arc::new(AppContext::new().await?);
    let service_mgmt = ServiceManagementService::new(ctx.db_pool())?;

    let mcp_stopped = if target.mcp {
        if !config.is_json_output() {
            CliService::section("Stopping MCP Servers");
        }
        stop_mcp_servers(&service_mgmt, force, config.is_json_output()).await?
    } else {
        0
    };

    let agents_stopped = if target.agents {
        if !config.is_json_output() {
            CliService::section("Stopping Agents");
        }
        stop_agents(&service_mgmt, force, config.is_json_output()).await?
    } else {
        0
    };

    let api_stopped = if target.api {
        if !config.is_json_output() {
            CliService::section("Stopping API Server");
        }
        stop_api(force, config.is_json_output()).await?;
        true
    } else {
        false
    };

    let message = "All requested services stopped".to_string();
    if !config.is_json_output() {
        CliService::success(&message);
    }

    let output = StopServiceOutput {
        api_stopped,
        agents_stopped,
        mcp_stopped,
        message,
    };

    Ok(CommandResult::card(output).with_title("Stop Services"))
}

async fn stop_api(force: bool, quiet: bool) -> Result<()> {
    let port = get_api_port();

    if let Some(pid) = ProcessCleanup::check_port(port) {
        if !quiet {
            CliService::info(&format!("Stopping API server (PID: {})...", pid));
        }
        if force {
            ProcessCleanup::kill_process(pid);
        } else {
            ProcessCleanup::terminate_gracefully(pid, 100).await;
        }
    }

    ProcessCleanup::kill_port(port);
    ProcessCleanup::wait_for_port_free(port, 5, 200).await?;

    if !quiet {
        CliService::success("API server stopped");
    }
    Ok(())
}

async fn stop_agents(
    service_mgmt: &ServiceManagementService,
    force: bool,
    quiet: bool,
) -> Result<usize> {
    let agents = service_mgmt.get_services_by_type("agent").await?;

    if agents.is_empty() {
        if !quiet {
            CliService::info("No agents running");
        }
        return Ok(0);
    }

    let mut stopped = 0usize;
    for agent in &agents {
        if !quiet {
            CliService::info(&format!("Stopping {}...", agent.name));
        }
        service_mgmt.stop_service(agent, force).await?;
        stopped += 1;
    }

    if !quiet {
        CliService::success(&format!("Stopped {} agents", stopped));
    }
    Ok(stopped)
}

async fn stop_mcp_servers(
    service_mgmt: &ServiceManagementService,
    force: bool,
    quiet: bool,
) -> Result<usize> {
    let servers = service_mgmt.get_services_by_type("mcp").await?;

    if servers.is_empty() {
        if !quiet {
            CliService::info("No MCP servers running");
        }
        return Ok(0);
    }

    let mut stopped = 0usize;
    for server in &servers {
        if !quiet {
            CliService::info(&format!("Stopping {}...", server.name));
        }
        service_mgmt.stop_service(server, force).await?;
        stopped += 1;
    }

    if !quiet {
        CliService::success(&format!("Stopped {} MCP servers", stopped));
    }
    Ok(stopped)
}

async fn resolve_agent_name(agent_identifier: &str) -> Result<String> {
    let registry = AgentRegistry::new()?;
    let agent = registry.get_agent(agent_identifier).await?;
    Ok(agent.name)
}

pub async fn execute_individual_agent(
    ctx: &Arc<AppContext>,
    agent_id: &str,
    force: bool,
    config: &CliConfig,
) -> Result<CommandResult<StopIndividualOutput>> {
    if !config.is_json_output() {
        CliService::section(&format!("Stopping Agent: {}", agent_id));
    }

    let jwt_provider = Arc::new(
        JwtValidationProviderImpl::from_config().context("Failed to create JWT provider")?,
    );
    let agent_state = Arc::new(AgentState::new(
        Arc::clone(ctx.db_pool()),
        Arc::new(ctx.config().clone()),
        jwt_provider,
    ));
    let orchestrator = AgentOrchestrator::new(agent_state, None)
        .await
        .context("Failed to initialize agent orchestrator")?;

    let name = resolve_agent_name(agent_id).await?;

    if force {
        orchestrator.delete_agent(&name).await?;
    } else {
        orchestrator.disable_agent(&name).await?;
    }

    let message = format!("Agent {} stopped successfully", agent_id);
    if !config.is_json_output() {
        CliService::success(&message);
    }

    let output = StopIndividualOutput {
        service_type: "agent".to_string(),
        service_name: agent_id.to_string(),
        stopped: true,
        message,
    };

    Ok(CommandResult::card(output).with_title("Stop Agent"))
}

pub async fn execute_individual_mcp(
    ctx: &Arc<AppContext>,
    server_name: &str,
    _force: bool,
    config: &CliConfig,
) -> Result<CommandResult<StopIndividualOutput>> {
    if !config.is_json_output() {
        CliService::section(&format!("Stopping MCP Server: {}", server_name));
    }

    let manager =
        McpManager::new(Arc::clone(ctx.db_pool())).context("Failed to initialize MCP manager")?;

    manager.stop_services(Some(server_name.to_string())).await?;

    let message = format!("MCP server {} stopped successfully", server_name);
    if !config.is_json_output() {
        CliService::success(&message);
    }

    let output = StopIndividualOutput {
        service_type: "mcp".to_string(),
        service_name: server_name.to_string(),
        stopped: true,
        message,
    };

    Ok(CommandResult::card(output).with_title("Stop MCP Server"))
}