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