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    Config {
13        #[command(subcommand)]
14        command: super::config::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 = "Config")]
58    Config(super::config::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 = "Config")]
96    Config(super::config::ResponseItem),
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::Config(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::Config { command } =>
149                Ok(Request::Config(super::config::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::Config(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
197#[cfg(feature = "cli-executor")]
198pub async fn execute<E: super::CommandExecutor>(
199    executor: &E,
200    request: Request,
201
202        agent_arguments: Option<&crate::cli::command::AgentArguments>,
203    ) -> Result<
204    std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>>,
205    E::Error,
206> {
207    use futures::StreamExt;
208    let stream: std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>> =
209        match request {
210            Request::Agents(req) => {
211                let inner = super::agents::execute(executor, req, agent_arguments).await?;
212                Box::pin(inner.map(|r| r.map(ResponseItem::Agents)))
213            }
214            Request::Config(req) => {
215                let inner = super::config::execute(executor, req, agent_arguments).await?;
216                Box::pin(inner.map(|r| r.map(ResponseItem::Config)))
217            }
218            Request::Db(req) => {
219                let inner = super::db::execute(executor, req, agent_arguments).await?;
220                Box::pin(inner.map(|r| r.map(ResponseItem::Db)))
221            }
222            Request::Functions(req) => {
223                let inner = super::functions::execute(executor, req, agent_arguments).await?;
224                Box::pin(inner.map(|r| r.map(ResponseItem::Functions)))
225            }
226            Request::Mcp(req) => {
227                let inner = super::mcp::execute(executor, req, agent_arguments).await?;
228                Box::pin(inner.map(|r| r.map(ResponseItem::Mcp)))
229            }
230            Request::Plugins(req) => {
231                let inner = super::plugins::execute(executor, req, agent_arguments).await?;
232                Box::pin(inner.map(|r| r.map(ResponseItem::Plugins)))
233            }
234            Request::Swarms(req) => {
235                let inner = super::swarms::execute(executor, req, agent_arguments).await?;
236                Box::pin(inner.map(|r| r.map(ResponseItem::Swarms)))
237            }
238            Request::Tasks(req) => {
239                let inner = super::tasks::execute(executor, req, agent_arguments).await?;
240                Box::pin(inner.map(|r| r.map(ResponseItem::Tasks)))
241            }
242            Request::Tools(req) => {
243                let inner = super::tools::execute(executor, req, agent_arguments).await?;
244                Box::pin(inner.map(|r| r.map(ResponseItem::Tools)))
245            }
246            Request::Update(req) => {
247                let inner = super::update::execute(executor, req, agent_arguments).await?;
248                Box::pin(inner.map(|r| r.map(ResponseItem::Update)))
249            }
250            Request::UpdateRequestSchema(req) => {
251                let value = super::update::request_schema::execute(executor, req, agent_arguments).await?;
252                Box::pin(super::StreamOnce::new(Ok(ResponseItem::UpdateRequestSchema(value))))
253            }
254            Request::UpdateResponseSchema(req) => {
255                let value = super::update::response_schema::execute(executor, req, agent_arguments).await?;
256                Box::pin(super::StreamOnce::new(Ok(ResponseItem::UpdateResponseSchema(value))))
257            }
258            Request::Viewer(req) => {
259                let inner = super::viewer::execute(executor, req, agent_arguments).await?;
260                Box::pin(inner.map(|r| r.map(ResponseItem::Viewer)))
261            }
262        };
263    Ok(stream)
264}
265
266#[cfg(feature = "cli-executor")]
267pub async fn execute_jq<E: super::CommandExecutor>(
268    executor: &E,
269    request: Request,
270    jq: String,
271
272        agent_arguments: Option<&crate::cli::command::AgentArguments>,
273    ) -> Result<
274    std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>>,
275    E::Error,
276> {
277    let stream: std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>> =
278        match request {
279            Request::Agents(req) => {
280                let inner = super::agents::execute_jq(executor, req, jq, agent_arguments).await?;
281                Box::pin(inner)
282            }
283            Request::Config(req) => {
284                let inner = super::config::execute_jq(executor, req, jq, agent_arguments).await?;
285                Box::pin(inner)
286            }
287            Request::Db(req) => {
288                let inner = super::db::execute_jq(executor, req, jq, agent_arguments).await?;
289                Box::pin(inner)
290            }
291            Request::Functions(req) => {
292                let inner = super::functions::execute_jq(executor, req, jq, agent_arguments).await?;
293                Box::pin(inner)
294            }
295            Request::Mcp(req) => {
296                let inner = super::mcp::execute_jq(executor, req, jq, agent_arguments).await?;
297                Box::pin(inner)
298            }
299            Request::Plugins(req) => {
300                let inner = super::plugins::execute_jq(executor, req, jq, agent_arguments).await?;
301                Box::pin(inner)
302            }
303            Request::Swarms(req) => {
304                let inner = super::swarms::execute_jq(executor, req, jq, agent_arguments).await?;
305                Box::pin(inner)
306            }
307            Request::Tasks(req) => {
308                let inner = super::tasks::execute_jq(executor, req, jq, agent_arguments).await?;
309                Box::pin(inner)
310            }
311            Request::Tools(req) => {
312                let inner = super::tools::execute_jq(executor, req, jq, agent_arguments).await?;
313                Box::pin(inner)
314            }
315            Request::Update(req) => {
316                let inner = super::update::execute_jq(executor, req, jq, agent_arguments).await?;
317                Box::pin(inner)
318            }
319            Request::UpdateRequestSchema(req) => {
320                let value = super::update::request_schema::execute_jq(executor, req, jq, agent_arguments).await?;
321                Box::pin(super::StreamOnce::new(Ok(value)))
322            }
323            Request::UpdateResponseSchema(req) => {
324                let value = super::update::response_schema::execute_jq(executor, req, jq, agent_arguments).await?;
325                Box::pin(super::StreamOnce::new(Ok(value)))
326            }
327            Request::Viewer(req) => {
328                let inner = super::viewer::execute_jq(executor, req, jq, agent_arguments).await?;
329                Box::pin(inner)
330            }
331        };
332    Ok(stream)
333}
334
335/// Parse an argv slice into a typed [`Request`]. Accepts either
336/// shape:
337///
338/// - With a program-name prefix (`["objectiveai", "agents", "list"]`)
339///   — matches `std::env::args()` and binary entry-point usage.
340/// - Without one (`["agents", "list"]`) — matches
341///   [`super::CommandRequest::into_command`]'s output shape and the
342///   "command-only" shape MCP callers send.
343///
344/// If `args[0]` is `"objectiveai"` we pass it straight through;
345/// otherwise we prepend so clap (which always treats argv[0] as the
346/// program name) sees a well-formed argv. Hides clap behind the SDK
347/// boundary so downstream crates can dispatch arbitrary argv without
348/// taking a clap dep themselves.
349pub fn parse_request(args: &[String]) -> Result<Request, ParseError> {
350    let command = if args.first().map(String::as_str) == Some("objectiveai") {
351        <Command as clap::Parser>::try_parse_from(args)?
352    } else {
353        let argv = std::iter::once("objectiveai".to_string())
354            .chain(args.iter().cloned());
355        <Command as clap::Parser>::try_parse_from(argv)?
356    };
357    Ok(Request::try_from(command)?)
358}
359
360/// Error from [`parse_request`]. Either clap rejected the argv
361/// ([`ParseError::Clap`] — `--help`, unknown subcommand, missing
362/// required arg, etc.) or the typed `Command` couldn't be lowered
363/// into a `Request` ([`ParseError::FromArgs`] — inline body JSON
364/// failed to parse, etc.).
365#[derive(Debug)]
366pub enum ParseError {
367    Clap(clap::Error),
368    FromArgs(super::FromArgsError),
369}
370
371impl std::fmt::Display for ParseError {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        match self {
374            ParseError::Clap(e) => write!(f, "{e}"),
375            ParseError::FromArgs(e) => write!(f, "{e}"),
376        }
377    }
378}
379
380impl std::error::Error for ParseError {}
381
382impl From<clap::Error> for ParseError {
383    fn from(e: clap::Error) -> Self {
384        ParseError::Clap(e)
385    }
386}
387
388impl From<super::FromArgsError> for ParseError {
389    fn from(e: super::FromArgsError) -> Self {
390        ParseError::FromArgs(e)
391    }
392}