Skip to main content

objectiveai_sdk/cli/command/
command.rs

1//! Root-level clap `Command` plus the matching typed `Request` /
2//! `ResponseItem` aggregators and conversions, mirroring the same
3//! pattern every tier `mod.rs` follows for its own subcommands.
4
5#[derive(clap::Parser)]
6#[command(name = "objectiveai")]
7pub enum Command {
8    Agents {
9        #[command(subcommand)]
10        command: super::agents::Command,
11    },
12    Api {
13        #[command(subcommand)]
14        command: super::api::Command,
15    },
16    Db {
17        #[command(subcommand)]
18        command: super::db::Command,
19    },
20    Functions {
21        #[command(subcommand)]
22        command: super::functions::Command,
23    },
24    /// Kill every process holding a lock anywhere under the configured
25    /// `OBJECTIVEAI_DIR` — the blunt whole-tree sweep.
26    KillAll(super::kill_all::Command),
27    Mcp {
28        #[command(subcommand)]
29        command: super::mcp::Command,
30    },
31    Plugins {
32        #[command(subcommand)]
33        command: super::plugins::Command,
34    },
35    Swarms {
36        #[command(subcommand)]
37        command: super::swarms::Command,
38    },
39    Tasks {
40        #[command(subcommand)]
41        command: super::tasks::Command,
42    },
43    Tools {
44        #[command(subcommand)]
45        command: super::tools::Command,
46    },
47    Update(super::update::Command),
48    Viewer {
49        #[command(subcommand)]
50        command: super::viewer::Command,
51    },
52}
53
54#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
55#[serde(untagged)]
56#[schemars(rename = "cli.command.Request")]
57pub enum Request {
58    #[schemars(title = "Agents")]
59    Agents(super::agents::Request),
60    #[schemars(title = "Api")]
61    Api(super::api::Request),
62    #[schemars(title = "Db")]
63    Db(super::db::Request),
64    #[schemars(title = "Functions")]
65    Functions(super::functions::Request),
66    #[schemars(title = "KillAll")]
67    KillAll(super::kill_all::Request),
68    #[schemars(title = "KillAllRequestSchema")]
69    KillAllRequestSchema(super::kill_all::request_schema::Request),
70    #[schemars(title = "KillAllResponseSchema")]
71    KillAllResponseSchema(super::kill_all::response_schema::Request),
72    #[schemars(title = "Mcp")]
73    Mcp(super::mcp::Request),
74    #[schemars(title = "Plugins")]
75    Plugins(super::plugins::Request),
76    #[schemars(title = "Swarms")]
77    Swarms(super::swarms::Request),
78    #[schemars(title = "Tasks")]
79    Tasks(super::tasks::Request),
80    #[schemars(title = "Tools")]
81    Tools(super::tools::Request),
82    #[schemars(title = "Update")]
83    Update(super::update::Request),
84    #[schemars(title = "UpdateRequestSchema")]
85    UpdateRequestSchema(super::update::request_schema::Request),
86    #[schemars(title = "UpdateResponseSchema")]
87    UpdateResponseSchema(super::update::response_schema::Request),
88    #[schemars(title = "Viewer")]
89    Viewer(super::viewer::Request),
90}
91
92// Exempt from json-schema coverage: the aggregate's transitive
93// expansion spans the whole command tree, which downstream
94// generated TypeScript cannot emit declarations for (TS7056), and
95// no consumer uses the aggregate schema — leaf schemas cover the
96// wire.
97#[objectiveai_sdk_macros::json_schema_ignore]
98#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
99#[schemars(rename = "cli.command.ResponseItem")]
100#[serde(untagged)]
101pub enum ResponseItem {
102    #[schemars(title = "Agents")]
103    Agents(super::agents::ResponseItem),
104    #[schemars(title = "Api")]
105    Api(super::api::Response),
106    #[schemars(title = "Db")]
107    Db(super::db::ResponseItem),
108    #[schemars(title = "Functions")]
109    Functions(super::functions::ResponseItem),
110    #[schemars(title = "KillAll")]
111    KillAll(super::kill_all::Response),
112    #[schemars(title = "KillAllRequestSchema")]
113    KillAllRequestSchema(super::kill_all::request_schema::Response),
114    #[schemars(title = "KillAllResponseSchema")]
115    KillAllResponseSchema(super::kill_all::response_schema::Response),
116    #[schemars(title = "Mcp")]
117    Mcp(super::mcp::Response),
118    #[schemars(title = "Plugins")]
119    Plugins(super::plugins::ResponseItem),
120    #[schemars(title = "Swarms")]
121    Swarms(super::swarms::ResponseItem),
122    #[schemars(title = "Tasks")]
123    Tasks(super::tasks::ResponseItem),
124    #[schemars(title = "Tools")]
125    Tools(super::tools::ResponseItem),
126    #[schemars(title = "Update")]
127    Update(super::update::ResponseItem),
128    #[schemars(title = "UpdateRequestSchema")]
129    UpdateRequestSchema(super::update::request_schema::Response),
130    #[schemars(title = "UpdateResponseSchema")]
131    UpdateResponseSchema(super::update::response_schema::Response),
132    #[schemars(title = "Viewer")]
133    Viewer(super::viewer::Response),
134}
135
136#[cfg(feature = "mcp")]
137impl super::CommandResponse for ResponseItem {
138    fn into_mcp(self) -> super::McpResponseItem {
139        match self {
140            ResponseItem::Agents(v) => v.into_mcp(),
141            ResponseItem::Api(v) => v.into_mcp(),
142            ResponseItem::Db(v) => v.into_mcp(),
143            ResponseItem::Functions(v) => v.into_mcp(),
144            ResponseItem::KillAll(v) => v.into_mcp(),
145            ResponseItem::KillAllRequestSchema(v) => v.into_mcp(),
146            ResponseItem::KillAllResponseSchema(v) => v.into_mcp(),
147            ResponseItem::Mcp(v) => v.into_mcp(),
148            ResponseItem::Plugins(v) => v.into_mcp(),
149            ResponseItem::Swarms(v) => v.into_mcp(),
150            ResponseItem::Tasks(v) => v.into_mcp(),
151            ResponseItem::Tools(v) => v.into_mcp(),
152            ResponseItem::Update(v) => v.into_mcp(),
153            ResponseItem::UpdateRequestSchema(v) => v.into_mcp(),
154            ResponseItem::UpdateResponseSchema(v) => v.into_mcp(),
155            ResponseItem::Viewer(v) => v.into_mcp(),
156        }
157    }
158}
159
160impl TryFrom<Command> for Request {
161    type Error = super::FromArgsError;
162    fn try_from(command: Command) -> Result<Self, Self::Error> {
163        match command {
164            Command::Agents { command } =>
165                Ok(Request::Agents(super::agents::Request::try_from(command)?)),
166            Command::Api { command } =>
167                Ok(Request::Api(super::api::Request::try_from(command)?)),
168            Command::Db { command } =>
169                Ok(Request::Db(super::db::Request::try_from(command)?)),
170            Command::Functions { command } =>
171                Ok(Request::Functions(super::functions::Request::try_from(command)?)),
172            Command::KillAll(cmd) => match cmd.schema {
173                None => Ok(Request::KillAll(super::kill_all::Request::try_from(cmd.args)?)),
174                Some(super::kill_all::Schema::RequestSchema(args)) =>
175                    Ok(Request::KillAllRequestSchema(super::kill_all::request_schema::Request::try_from(args)?)),
176                Some(super::kill_all::Schema::ResponseSchema(args)) =>
177                    Ok(Request::KillAllResponseSchema(super::kill_all::response_schema::Request::try_from(args)?)),
178            },
179            Command::Mcp { command } =>
180                Ok(Request::Mcp(super::mcp::Request::try_from(command)?)),
181            Command::Plugins { command } =>
182                Ok(Request::Plugins(super::plugins::Request::try_from(command)?)),
183            Command::Swarms { command } =>
184                Ok(Request::Swarms(super::swarms::Request::try_from(command)?)),
185            Command::Tasks { command } =>
186                Ok(Request::Tasks(super::tasks::Request::try_from(command)?)),
187            Command::Tools { command } =>
188                Ok(Request::Tools(super::tools::Request::try_from(command)?)),
189            Command::Update(cmd) => match cmd.schema {
190                None => Ok(Request::Update(super::update::Request::try_from(cmd.args)?)),
191                Some(super::update::Schema::RequestSchema(args)) =>
192                    Ok(Request::UpdateRequestSchema(super::update::request_schema::Request::try_from(args)?)),
193                Some(super::update::Schema::ResponseSchema(args)) =>
194                    Ok(Request::UpdateResponseSchema(super::update::response_schema::Request::try_from(args)?)),
195            },
196            Command::Viewer { command } =>
197                Ok(Request::Viewer(super::viewer::Request::try_from(command)?)),
198        }
199    }
200}
201
202impl super::CommandRequest for Request {
203    fn into_command(&self) -> Vec<String> {
204        match self {
205            Request::Agents(inner) => inner.into_command(),
206            Request::Api(inner) => inner.into_command(),
207            Request::Db(inner) => inner.into_command(),
208            Request::Functions(inner) => inner.into_command(),
209            Request::KillAll(inner) => inner.into_command(),
210            Request::KillAllRequestSchema(inner) => inner.into_command(),
211            Request::KillAllResponseSchema(inner) => inner.into_command(),
212            Request::Mcp(inner) => inner.into_command(),
213            Request::Plugins(inner) => inner.into_command(),
214            Request::Swarms(inner) => inner.into_command(),
215            Request::Tasks(inner) => inner.into_command(),
216            Request::Tools(inner) => inner.into_command(),
217            Request::Update(inner) => inner.into_command(),
218            Request::UpdateRequestSchema(inner) => inner.into_command(),
219            Request::UpdateResponseSchema(inner) => inner.into_command(),
220            Request::Viewer(inner) => inner.into_command(),
221        }
222    }
223
224    fn request_base(&self) -> &crate::cli::command::RequestBase {
225        match self {
226            Request::Agents(inner) => inner.request_base(),
227            Request::Api(inner) => inner.request_base(),
228            Request::Db(inner) => inner.request_base(),
229            Request::Functions(inner) => inner.request_base(),
230            Request::KillAll(inner) => inner.request_base(),
231            Request::KillAllRequestSchema(inner) => inner.request_base(),
232            Request::KillAllResponseSchema(inner) => inner.request_base(),
233            Request::Mcp(inner) => inner.request_base(),
234            Request::Plugins(inner) => inner.request_base(),
235            Request::Swarms(inner) => inner.request_base(),
236            Request::Tasks(inner) => inner.request_base(),
237            Request::Tools(inner) => inner.request_base(),
238            Request::Update(inner) => inner.request_base(),
239            Request::UpdateRequestSchema(inner) => inner.request_base(),
240            Request::UpdateResponseSchema(inner) => inner.request_base(),
241            Request::Viewer(inner) => inner.request_base(),
242        }
243    }
244
245    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
246        match self {
247            Request::Agents(inner) => inner.request_base_mut(),
248            Request::Api(inner) => inner.request_base_mut(),
249            Request::Db(inner) => inner.request_base_mut(),
250            Request::Functions(inner) => inner.request_base_mut(),
251            Request::KillAll(inner) => inner.request_base_mut(),
252            Request::KillAllRequestSchema(inner) => inner.request_base_mut(),
253            Request::KillAllResponseSchema(inner) => inner.request_base_mut(),
254            Request::Mcp(inner) => inner.request_base_mut(),
255            Request::Plugins(inner) => inner.request_base_mut(),
256            Request::Swarms(inner) => inner.request_base_mut(),
257            Request::Tasks(inner) => inner.request_base_mut(),
258            Request::Tools(inner) => inner.request_base_mut(),
259            Request::Update(inner) => inner.request_base_mut(),
260            Request::UpdateRequestSchema(inner) => inner.request_base_mut(),
261            Request::UpdateResponseSchema(inner) => inner.request_base_mut(),
262            Request::Viewer(inner) => inner.request_base_mut(),
263        }
264    }
265}
266
267#[cfg(feature = "cli-executor")]
268pub async fn execute<E: super::CommandExecutor>(
269    executor: &E,
270    request: Request,
271
272        agent_arguments: Option<&crate::cli::command::AgentArguments>,
273    ) -> Result<
274    std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>>,
275    E::Error,
276> {
277    use futures::StreamExt;
278    let stream: std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>> =
279        match request {
280            Request::Agents(req) => {
281                let inner = super::agents::execute(executor, req, agent_arguments).await?;
282                Box::pin(inner.map(|r| r.map(ResponseItem::Agents)))
283            }
284            Request::Api(req) => {
285                let inner = super::api::execute(executor, req, agent_arguments).await?;
286                Box::pin(inner.map(|r| r.map(ResponseItem::Api)))
287            }
288            Request::Db(req) => {
289                let inner = super::db::execute(executor, req, agent_arguments).await?;
290                Box::pin(inner.map(|r| r.map(ResponseItem::Db)))
291            }
292            Request::Functions(req) => {
293                let inner = super::functions::execute(executor, req, agent_arguments).await?;
294                Box::pin(inner.map(|r| r.map(ResponseItem::Functions)))
295            }
296            Request::KillAll(req) => {
297                let value = super::kill_all::execute(executor, req, agent_arguments).await?;
298                Box::pin(super::StreamOnce::new(Ok(ResponseItem::KillAll(value))))
299            }
300            Request::KillAllRequestSchema(req) => {
301                let value = super::kill_all::request_schema::execute(executor, req, agent_arguments).await?;
302                Box::pin(super::StreamOnce::new(Ok(ResponseItem::KillAllRequestSchema(value))))
303            }
304            Request::KillAllResponseSchema(req) => {
305                let value = super::kill_all::response_schema::execute(executor, req, agent_arguments).await?;
306                Box::pin(super::StreamOnce::new(Ok(ResponseItem::KillAllResponseSchema(value))))
307            }
308            Request::Mcp(req) => {
309                let inner = super::mcp::execute(executor, req, agent_arguments).await?;
310                Box::pin(inner.map(|r| r.map(ResponseItem::Mcp)))
311            }
312            Request::Plugins(req) => {
313                let inner = super::plugins::execute(executor, req, agent_arguments).await?;
314                Box::pin(inner.map(|r| r.map(ResponseItem::Plugins)))
315            }
316            Request::Swarms(req) => {
317                let inner = super::swarms::execute(executor, req, agent_arguments).await?;
318                Box::pin(inner.map(|r| r.map(ResponseItem::Swarms)))
319            }
320            Request::Tasks(req) => {
321                let inner = super::tasks::execute(executor, req, agent_arguments).await?;
322                Box::pin(inner.map(|r| r.map(ResponseItem::Tasks)))
323            }
324            Request::Tools(req) => {
325                let inner = super::tools::execute(executor, req, agent_arguments).await?;
326                Box::pin(inner.map(|r| r.map(ResponseItem::Tools)))
327            }
328            Request::Update(req) => {
329                let inner = super::update::execute(executor, req, agent_arguments).await?;
330                Box::pin(inner.map(|r| r.map(ResponseItem::Update)))
331            }
332            Request::UpdateRequestSchema(req) => {
333                let value = super::update::request_schema::execute(executor, req, agent_arguments).await?;
334                Box::pin(super::StreamOnce::new(Ok(ResponseItem::UpdateRequestSchema(value))))
335            }
336            Request::UpdateResponseSchema(req) => {
337                let value = super::update::response_schema::execute(executor, req, agent_arguments).await?;
338                Box::pin(super::StreamOnce::new(Ok(ResponseItem::UpdateResponseSchema(value))))
339            }
340            Request::Viewer(req) => {
341                let inner = super::viewer::execute(executor, req, agent_arguments).await?;
342                Box::pin(inner.map(|r| r.map(ResponseItem::Viewer)))
343            }
344        };
345    Ok(stream)
346}
347
348#[cfg(feature = "cli-executor")]
349pub async fn execute_transform<E: super::CommandExecutor>(
350    executor: &E,
351    request: Request,
352    transform: crate::cli::command::Transform,
353
354        agent_arguments: Option<&crate::cli::command::AgentArguments>,
355    ) -> Result<
356    std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>>,
357    E::Error,
358> {
359    let stream: std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>> =
360        match request {
361            Request::Agents(req) => {
362                let inner = super::agents::execute_transform(executor, req, transform, agent_arguments).await?;
363                Box::pin(inner)
364            }
365            Request::Api(req) => {
366                let inner = super::api::execute_transform(executor, req, transform, agent_arguments).await?;
367                Box::pin(inner)
368            }
369            Request::Db(req) => {
370                let inner = super::db::execute_transform(executor, req, transform, agent_arguments).await?;
371                Box::pin(inner)
372            }
373            Request::Functions(req) => {
374                let inner = super::functions::execute_transform(executor, req, transform, agent_arguments).await?;
375                Box::pin(inner)
376            }
377            Request::KillAll(req) => {
378                let value = super::kill_all::execute_transform(executor, req, transform, agent_arguments).await?;
379                Box::pin(super::StreamOnce::new(Ok(value)))
380            }
381            Request::KillAllRequestSchema(req) => {
382                let value = super::kill_all::request_schema::execute_transform(executor, req, transform, agent_arguments).await?;
383                Box::pin(super::StreamOnce::new(Ok(value)))
384            }
385            Request::KillAllResponseSchema(req) => {
386                let value = super::kill_all::response_schema::execute_transform(executor, req, transform, agent_arguments).await?;
387                Box::pin(super::StreamOnce::new(Ok(value)))
388            }
389            Request::Mcp(req) => {
390                let inner = super::mcp::execute_transform(executor, req, transform, agent_arguments).await?;
391                Box::pin(inner)
392            }
393            Request::Plugins(req) => {
394                let inner = super::plugins::execute_transform(executor, req, transform, agent_arguments).await?;
395                Box::pin(inner)
396            }
397            Request::Swarms(req) => {
398                let inner = super::swarms::execute_transform(executor, req, transform, agent_arguments).await?;
399                Box::pin(inner)
400            }
401            Request::Tasks(req) => {
402                let inner = super::tasks::execute_transform(executor, req, transform, agent_arguments).await?;
403                Box::pin(inner)
404            }
405            Request::Tools(req) => {
406                let inner = super::tools::execute_transform(executor, req, transform, agent_arguments).await?;
407                Box::pin(inner)
408            }
409            Request::Update(req) => {
410                let inner = super::update::execute_transform(executor, req, transform, agent_arguments).await?;
411                Box::pin(inner)
412            }
413            Request::UpdateRequestSchema(req) => {
414                let value = super::update::request_schema::execute_transform(executor, req, transform, agent_arguments).await?;
415                Box::pin(super::StreamOnce::new(Ok(value)))
416            }
417            Request::UpdateResponseSchema(req) => {
418                let value = super::update::response_schema::execute_transform(executor, req, transform, agent_arguments).await?;
419                Box::pin(super::StreamOnce::new(Ok(value)))
420            }
421            Request::Viewer(req) => {
422                let inner = super::viewer::execute_transform(executor, req, transform, agent_arguments).await?;
423                Box::pin(inner)
424            }
425        };
426    Ok(stream)
427}
428
429/// Parse an argv slice into a typed [`Request`]. Accepts either
430/// shape:
431///
432/// - With a program-name prefix (`["objectiveai", "agents", "list"]`)
433///   — matches `std::env::args()` and binary entry-point usage.
434/// - Without one (`["agents", "list"]`) — matches
435///   [`super::CommandRequest::into_command`]'s output shape and the
436///   "command-only" shape MCP callers send.
437///
438/// If `args[0]` is `"objectiveai"` we pass it straight through;
439/// otherwise we prepend so clap (which always treats argv[0] as the
440/// program name) sees a well-formed argv. Hides clap behind the SDK
441/// boundary so downstream crates can dispatch arbitrary argv without
442/// taking a clap dep themselves.
443pub fn parse_request(args: &[String]) -> Result<Request, ParseError> {
444    let command = if args.first().map(String::as_str) == Some("objectiveai") {
445        <Command as clap::Parser>::try_parse_from(args)?
446    } else {
447        let argv = std::iter::once("objectiveai".to_string())
448            .chain(args.iter().cloned());
449        <Command as clap::Parser>::try_parse_from(argv)?
450    };
451    Ok(Request::try_from(command)?)
452}
453
454/// Error from [`parse_request`]. Either clap rejected the argv
455/// ([`ParseError::Clap`] — `--help`, unknown subcommand, missing
456/// required arg, etc.) or the typed `Command` couldn't be lowered
457/// into a `Request` ([`ParseError::FromArgs`] — inline body JSON
458/// failed to parse, etc.).
459#[derive(Debug)]
460pub enum ParseError {
461    Clap(clap::Error),
462    FromArgs(super::FromArgsError),
463}
464
465impl std::fmt::Display for ParseError {
466    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467        match self {
468            ParseError::Clap(e) => write!(f, "{e}"),
469            ParseError::FromArgs(e) => write!(f, "{e}"),
470        }
471    }
472}
473
474impl std::error::Error for ParseError {}
475
476impl From<clap::Error> for ParseError {
477    fn from(e: clap::Error) -> Self {
478        ParseError::Clap(e)
479    }
480}
481
482impl From<super::FromArgsError> for ParseError {
483    fn from(e: super::FromArgsError) -> Self {
484        ParseError::FromArgs(e)
485    }
486}