Skip to main content

systemprompt_cli/commands/infrastructure/services/
mod.rs

1mod cleanup;
2pub mod restart;
3pub mod serve;
4mod start;
5mod status;
6mod stop;
7
8use crate::cli_settings::CliConfig;
9use anyhow::{Context, Result};
10use clap::Subcommand;
11use std::sync::Arc;
12use systemprompt_runtime::AppContext;
13
14#[derive(Debug, Clone, Subcommand)]
15pub enum StartTarget {
16    Agent { agent_id: String },
17    Mcp { server_name: String },
18}
19
20#[derive(Debug, Clone, Subcommand)]
21pub enum StopTarget {
22    Agent {
23        agent_id: String,
24        #[arg(long, help = "Force stop (SIGKILL)")]
25        force: bool,
26    },
27    Mcp {
28        server_name: String,
29        #[arg(long, help = "Force stop (SIGKILL)")]
30        force: bool,
31    },
32}
33
34#[derive(Debug, Subcommand)]
35pub enum ServicesCommands {
36    #[command(
37        about = "Start API, agents, and MCP servers",
38        after_help = "EXAMPLES:\n  systemprompt infra services start\n  systemprompt infra \
39                      services start --api\n  systemprompt infra services start --agents --mcp\n  \
40                      systemprompt infra services start agent <name>"
41    )]
42    Start {
43        #[command(subcommand)]
44        target: Option<StartTarget>,
45
46        #[arg(long, help = "Start all services")]
47        all: bool,
48
49        #[arg(long, help = "Start API server only")]
50        api: bool,
51
52        #[arg(long, help = "Start agents only")]
53        agents: bool,
54
55        #[arg(long, help = "Start MCP servers only")]
56        mcp: bool,
57
58        #[arg(long, help = "Run in foreground (default)")]
59        foreground: bool,
60
61        #[arg(long, help = "Skip database migrations")]
62        skip_migrate: bool,
63
64        #[arg(long, help = "Kill process using the port if occupied")]
65        kill_port_process: bool,
66    },
67
68    #[command(
69        about = "Stop running services gracefully",
70        after_help = "EXAMPLES:\n  systemprompt infra services stop\n  systemprompt infra \
71                      services stop --api\n  systemprompt infra services stop agent <name> \
72                      [--force]"
73    )]
74    Stop {
75        #[command(subcommand)]
76        target: Option<StopTarget>,
77
78        #[arg(long, help = "Stop all services")]
79        all: bool,
80
81        #[arg(long, help = "Stop API server only")]
82        api: bool,
83
84        #[arg(long, help = "Stop agents only")]
85        agents: bool,
86
87        #[arg(long, help = "Stop MCP servers only")]
88        mcp: bool,
89
90        #[arg(long, help = "Force stop (SIGKILL)")]
91        force: bool,
92    },
93
94    #[command(about = "Restart services")]
95    Restart {
96        #[command(subcommand)]
97        target: Option<RestartTarget>,
98
99        #[arg(long, help = "Restart only failed services")]
100        failed: bool,
101
102        #[arg(long, help = "Restart all agents")]
103        agents: bool,
104
105        #[arg(long, help = "Restart all MCP servers")]
106        mcp: bool,
107    },
108
109    #[command(about = "Show detailed service status")]
110    Status {
111        #[arg(long, help = "Show detailed information")]
112        detailed: bool,
113
114        #[arg(long, help = "Output as JSON")]
115        json: bool,
116
117        #[arg(long, help = "Include health check results")]
118        health: bool,
119    },
120
121    #[command(about = "Clean up orphaned processes and stale entries")]
122    Cleanup {
123        #[arg(short = 'y', long, help = "Skip confirmation prompt")]
124        yes: bool,
125
126        #[arg(long, help = "Preview cleanup without executing")]
127        dry_run: bool,
128    },
129
130    #[command(about = "Start API server (automatically starts agents and MCP servers)")]
131    Serve {
132        #[arg(long, help = "Run in foreground mode")]
133        foreground: bool,
134
135        #[arg(long, help = "Kill process using the port if occupied")]
136        kill_port_process: bool,
137    },
138}
139
140#[derive(Debug, Clone, Subcommand)]
141pub enum RestartTarget {
142    Api,
143    Agent {
144        agent_id: String,
145    },
146    Mcp {
147        server_name: String,
148        #[arg(long, help = "Rebuild the binary before restarting")]
149        build: bool,
150    },
151}
152
153pub async fn execute(command: ServicesCommands, config: &CliConfig) -> Result<()> {
154    match command {
155        ServicesCommands::Start {
156            target,
157            all,
158            api,
159            agents,
160            mcp,
161            foreground: _,
162            skip_migrate,
163            kill_port_process,
164        } => {
165            if let Some(individual) = target {
166                let ctx = Arc::new(
167                    AppContext::new()
168                        .await
169                        .context("Failed to initialize application context")?,
170                );
171                return match individual {
172                    StartTarget::Agent { agent_id } => {
173                        start::execute_individual_agent(&ctx, &agent_id, config).await
174                    },
175                    StartTarget::Mcp { server_name } => {
176                        start::execute_individual_mcp(&ctx, &server_name, config).await
177                    },
178                };
179            }
180
181            let flags = start::ServiceFlags {
182                all,
183                targets: start::ServiceTargetFlags { api, agents, mcp },
184            };
185            let service_target = start::ServiceTarget::from_flags(flags);
186            let options = start::StartupOptions {
187                skip_migrate,
188                kill_port_process,
189            };
190            start::execute(service_target, options, config).await
191        },
192
193        ServicesCommands::Stop {
194            target,
195            all,
196            api,
197            agents,
198            mcp,
199            force,
200        } => {
201            if let Some(individual) = target {
202                let ctx = Arc::new(
203                    AppContext::new()
204                        .await
205                        .context("Failed to initialize application context")?,
206                );
207                return match individual {
208                    StopTarget::Agent { agent_id, force } => {
209                        stop::execute_individual_agent(&ctx, &agent_id, force, config).await
210                    },
211                    StopTarget::Mcp { server_name, force } => {
212                        stop::execute_individual_mcp(&ctx, &server_name, force, config).await
213                    },
214                };
215            }
216
217            let flags = start::ServiceFlags {
218                all,
219                targets: start::ServiceTargetFlags { api, agents, mcp },
220            };
221            let service_target = start::ServiceTarget::from_flags(flags);
222            stop::execute(service_target, force, config).await
223        },
224
225        ServicesCommands::Restart {
226            target,
227            failed,
228            agents,
229            mcp,
230        } => {
231            let ctx = Arc::new(
232                AppContext::new()
233                    .await
234                    .context("Failed to initialize application context")?,
235            );
236
237            if failed {
238                restart::execute_failed(&ctx, config).await
239            } else if agents {
240                restart::execute_all_agents(&ctx, config).await
241            } else if mcp {
242                restart::execute_all_mcp(&ctx, config).await
243            } else {
244                match target {
245                    Some(RestartTarget::Api) => restart::execute_api(config).await,
246                    Some(RestartTarget::Agent { agent_id }) => {
247                        restart::execute_agent(&ctx, &agent_id, config).await
248                    },
249                    Some(RestartTarget::Mcp { server_name, build }) => {
250                        restart::execute_mcp(&ctx, &server_name, build, config).await
251                    },
252                    None => Err(anyhow::anyhow!(
253                        "Must specify target (api, agent, mcp) or use --failed/--agents/--mcp flag"
254                    )),
255                }
256            }
257        },
258
259        ServicesCommands::Status {
260            detailed,
261            json,
262            health,
263        } => status::execute(detailed, json, health, config).await,
264
265        ServicesCommands::Cleanup { yes, dry_run } => cleanup::execute(yes, dry_run, config).await,
266
267        ServicesCommands::Serve {
268            foreground,
269            kill_port_process,
270        } => serve::execute(foreground, kill_port_process, config).await,
271    }
272}
273
274pub fn load_service_configs(
275    _ctx: &AppContext,
276) -> Result<Vec<systemprompt_scheduler::ServiceConfig>> {
277    use systemprompt_loader::ConfigLoader;
278    use systemprompt_scheduler::{ServiceConfig, ServiceType};
279
280    let services_config = ConfigLoader::load()?;
281    let mut configs = Vec::new();
282
283    for (name, agent) in &services_config.agents {
284        configs.push(ServiceConfig {
285            name: name.clone(),
286            service_type: ServiceType::Agent,
287            port: agent.port,
288            enabled: agent.enabled,
289        });
290    }
291
292    for (name, mcp) in &services_config.mcp_servers {
293        configs.push(ServiceConfig {
294            name: name.clone(),
295            service_type: ServiceType::Mcp,
296            port: mcp.port,
297            enabled: mcp.enabled,
298        });
299    }
300
301    Ok(configs)
302}