Skip to main content

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

1//! `tools 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.tools.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.tools.get.Path")]
18pub enum Path {
19    #[serde(rename = "tools/get")]
20    ToolsGet,
21}
22
23impl CommandRequest for Request {
24    fn into_command(&self) -> Vec<String> {
25        let mut argv = vec![
26            "tools".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
48/// Per-OS exec command for a tool. The current platform's vector is
49/// the program plus its leading arguments; the caller's `--args` are
50/// appended, and the result runs with CWD = the tool's version folder's
51/// `cli/` subdir (`objectiveai.json` lives in the version folder).
52#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
53#[schemars(rename = "cli.command.tools.get.Exec")]
54pub struct Exec {
55    pub windows: Vec<String>,
56    pub linux: Vec<String>,
57    pub macos: Vec<String>,
58}
59
60impl Exec {
61    pub fn is_empty(&self) -> bool {
62        self.windows.is_empty() && self.linux.is_empty() && self.macos.is_empty()
63    }
64}
65
66/// Wire response for `tools get` — a lean projection of the on-disk
67/// manifest. `exec` is required (a tool always has a command). The
68/// on-disk-only fields (`cli_zip`, `source`) are intentionally absent;
69/// the CLI owns the full on-disk shape in its own `filesystem::tools`
70/// manifest types.
71#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
72#[schemars(rename = "cli.command.tools.get.ResponseManifest")]
73pub struct ResponseManifest {
74    pub owner: String,
75    pub name: String,
76    pub version: String,
77    pub description: String,
78    pub exec: Exec,
79}
80
81impl ResponseManifest {
82    /// LLM-visible MCP tool name for this tool:
83    /// `tool_{owner}_{name}_{version}`, with every `.` in the version
84    /// substituted to `-` so the result stays within the Anthropic
85    /// tool-name regex (`^[a-zA-Z0-9_-]{1,128}$`). `objectiveai-mcp`
86    /// advertises each tool under this name.
87    pub fn tool_name(&self) -> String {
88        format!(
89            "tool_{}_{}_{}",
90            self.owner,
91            self.name,
92            self.version.replace('.', "-")
93        )
94    }
95}
96
97pub type Response = Option<ResponseManifest>;
98
99#[derive(clap::Args)]
100pub struct Args {
101    /// Tool owner (GitHub `<owner>` segment). Required.
102    #[arg(long)]
103    pub owner: String,
104    /// Tool name (repository segment). Required.
105    #[arg(long)]
106    pub name: String,
107    /// Tool version. Required.
108    #[arg(long)]
109    pub version: String,
110    #[command(flatten)]
111    pub base: crate::cli::command::RequestBaseArgs,
112}
113
114#[derive(clap::Args)]
115#[command(args_conflicts_with_subcommands = true)]
116pub struct Command {
117    #[command(flatten)]
118    pub args: Args,
119    #[command(subcommand)]
120    pub schema: Option<Schema>,
121}
122
123#[derive(clap::Subcommand)]
124pub enum Schema {
125    /// Emit the JSON Schema for this leaf's `Request` type and exit.
126    RequestSchema(request_schema::Args),
127    /// Emit the JSON Schema for this leaf's `Response` type and exit.
128    ResponseSchema(response_schema::Args),
129}
130
131impl TryFrom<Args> for Request {
132    type Error = crate::cli::command::FromArgsError;
133    fn try_from(args: Args) -> Result<Self, Self::Error> {
134        Ok(Self {
135            path_type: Path::ToolsGet,
136            owner: args.owner,
137            name: args.name,
138            version: args.version,
139            base: args.base.into(),
140        })
141    }
142}
143
144#[cfg(feature = "cli-executor")]
145pub async fn execute<E: crate::cli::command::CommandExecutor>(
146    executor: &E,
147    mut request: Request,
148
149        agent_arguments: Option<&crate::cli::command::AgentArguments>,
150    ) -> Result<Response, E::Error> {
151    request.base.clear_transform();
152    executor.execute_one(request, agent_arguments).await
153}
154
155#[cfg(feature = "cli-executor")]
156pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
157    executor: &E,
158    mut request: Request,
159    transform: crate::cli::command::Transform,
160
161        agent_arguments: Option<&crate::cli::command::AgentArguments>,
162    ) -> Result<serde_json::Value, E::Error> {
163    request.base.set_transform(transform);
164    executor.execute_one(request, agent_arguments).await
165}
166
167#[cfg(feature = "mcp")]
168impl crate::cli::command::CommandResponse for ResponseManifest {
169    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
170        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
171    }
172}
173
174pub mod request_schema;
175
176
177pub mod response_schema;