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