Skip to main content

objectiveai_sdk/cli/command/plugins/get/
mod.rs

1//! `plugins get` — async handler stub.
2
3use crate::cli::command::CommandRequest;
4
5#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
6#[schemars(rename = "cli.command.plugins.get.Request")]
7pub struct Request {
8    pub path_type: Path,
9    pub owner: String,
10    pub name: String,
11    pub version: String,
12    #[serde(flatten)]
13    pub base: crate::cli::command::RequestBase,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
17#[schemars(rename = "cli.command.plugins.get.Path")]
18pub enum Path {
19    #[serde(rename = "plugins/get")]
20    PluginsGet,
21}
22
23impl CommandRequest for Request {
24    fn into_command(&self) -> Vec<String> {
25        let mut argv = vec![
26            "plugins".to_string(),
27            "get".to_string(),
28            "--owner".to_string(),
29            self.owner.clone(),
30            "--name".to_string(),
31            self.name.clone(),
32            "--version".to_string(),
33            self.version.clone(),
34        ];
35        self.base.push_flags(&mut argv);
36        argv
37    }
38
39    fn request_base(&self) -> &crate::cli::command::RequestBase {
40        &self.base
41    }
42
43    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
44        Some(&mut self.base)
45    }
46}
47
48pub type Response = Option<ResponseManifest>;
49
50#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
51#[schemars(rename = "cli.command.plugins.get.ResponseManifest")]
52pub struct ResponseManifest {
53    pub name: String,
54    pub description: String,
55    pub version: String,
56    pub owner: String,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    #[schemars(extend("omitempty" = true))]
59    pub author: Option<String>,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    #[schemars(extend("omitempty" = true))]
62    pub homepage: Option<String>,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    #[schemars(extend("omitempty" = true))]
65    pub license: Option<String>,
66    /// Per-OS exec argv for the plugin's cli side, run with CWD =
67    /// `<plugin dir>/cli/` — the same shape tools use. Empty when
68    /// the plugin is viewer-only.
69    #[serde(
70        default,
71        skip_serializing_if = "crate::cli::command::tools::get::Exec::is_empty"
72    )]
73    pub exec: crate::cli::command::tools::get::Exec,
74    /// GitHub-release asset filename for the plugin's cli bundle — a
75    /// zip extracted into `<plugin dir>/cli/` at install time, like
76    /// `viewer_zip` → `viewer/`.
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    #[schemars(extend("omitempty" = true))]
79    pub cli_zip: Option<String>,
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    #[schemars(extend("omitempty" = true))]
82    pub viewer_zip: Option<String>,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    #[schemars(extend("omitempty" = true))]
85    pub viewer_url: Option<String>,
86    #[serde(default, skip_serializing_if = "Vec::is_empty")]
87    pub viewer_routes: Vec<ResponseViewerRoute>,
88    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
89    pub mobile_ready: bool,
90    #[serde(default, skip_serializing_if = "Vec::is_empty")]
91    pub mcp_servers: Vec<ResponseMcpServer>,
92    pub source: String,
93}
94
95impl ResponseManifest {
96    /// LLM-visible MCP tool name for this plugin:
97    /// `plugin_{owner}_{name}_{version}`, with every `.` in the
98    /// version substituted to `-` so the result stays within the
99    /// Anthropic tool-name regex (`^[a-zA-Z0-9_-]{1,128}$`).
100    /// `objectiveai-mcp` advertises each plugin under this name.
101    pub fn tool_name(&self) -> String {
102        format!(
103            "plugin_{}_{}_{}",
104            self.owner,
105            self.name,
106            self.version.replace('.', "-")
107        )
108    }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
112#[schemars(rename = "cli.command.plugins.get.ResponseViewerRoute")]
113pub struct ResponseViewerRoute {
114    pub path: String,
115    pub method: ResponseHttpMethod,
116    pub r#type: String,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
120#[serde(rename_all = "UPPERCASE")]
121#[schemars(rename = "cli.command.plugins.get.ResponseHttpMethod")]
122pub enum ResponseHttpMethod {
123    Get,
124    Post,
125    Put,
126    Patch,
127    Delete,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
131#[schemars(rename = "cli.command.plugins.get.ResponseMcpServer")]
132pub struct ResponseMcpServer {
133    pub name: String,
134    pub url: String,
135    pub authorization: bool,
136}
137
138#[derive(clap::Args)]
139pub struct Args {
140    /// Plugin owner (GitHub `<owner>` segment). Required.
141    #[arg(long)]
142    pub owner: String,
143    /// Plugin name (repository segment). Required.
144    #[arg(long)]
145    pub name: String,
146    /// Plugin version. Required.
147    #[arg(long)]
148    pub version: String,
149    #[command(flatten)]
150    pub base: crate::cli::command::RequestBaseArgs,
151}
152
153#[derive(clap::Args)]
154#[command(args_conflicts_with_subcommands = true)]
155pub struct Command {
156    #[command(flatten)]
157    pub args: Args,
158    #[command(subcommand)]
159    pub schema: Option<Schema>,
160}
161
162#[derive(clap::Subcommand)]
163pub enum Schema {
164    /// Emit the JSON Schema for this leaf's `Request` type and exit.
165    RequestSchema(request_schema::Args),
166    /// Emit the JSON Schema for this leaf's `Response` type and exit.
167    ResponseSchema(response_schema::Args),
168}
169
170impl TryFrom<Args> for Request {
171    type Error = crate::cli::command::FromArgsError;
172    fn try_from(args: Args) -> Result<Self, Self::Error> {
173        Ok(Self {
174            path_type: Path::PluginsGet,
175            owner: args.owner,
176            name: args.name,
177            version: args.version,
178            base: args.base.into(),
179        })
180    }
181}
182
183#[cfg(feature = "cli-executor")]
184pub async fn execute<E: crate::cli::command::CommandExecutor>(
185    executor: &E,
186    mut request: Request,
187
188        agent_arguments: Option<&crate::cli::command::AgentArguments>,
189    ) -> Result<Response, E::Error> {
190    request.base.clear_transform();
191    executor.execute_one(request, agent_arguments).await
192}
193
194#[cfg(feature = "cli-executor")]
195pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
196    executor: &E,
197    mut request: Request,
198    transform: crate::cli::command::Transform,
199
200        agent_arguments: Option<&crate::cli::command::AgentArguments>,
201    ) -> Result<serde_json::Value, E::Error> {
202    request.base.set_transform(transform);
203    executor.execute_one(request, agent_arguments).await
204}
205
206#[cfg(feature = "mcp")]
207impl crate::cli::command::CommandResponse for ResponseManifest {
208    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
209        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
210    }
211}
212
213pub mod request_schema;
214
215
216pub mod response_schema;