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