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}