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