Skip to main content

objectiveai_cli/filesystem/tools/
manifest.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4/// Per-OS exec command. Reused from the SDK so the exec shape stays
5/// identical across the on-disk manifest and the `tools get` wire
6/// response.
7pub use objectiveai_sdk::cli::command::tools::get::Exec;
8
9/// Per-CPU-architecture cli bundle filenames for a single OS. Each entry
10/// is the GitHub-release `.zip` asset filename for that arch (or absent
11/// when not built for it). Arch keys follow the repo-wide convention
12/// (`x86_64` / `aarch64`) — the same names the release workflow uses for
13/// its assets, which is exactly what these values reference.
14#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15#[schemars(rename = "filesystem.tools.CliZipArch")]
16pub struct CliZipArch {
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    #[schemars(extend("omitempty" = true))]
19    pub x86_64: Option<String>,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    #[schemars(extend("omitempty" = true))]
22    pub aarch64: Option<String>,
23}
24
25impl CliZipArch {
26    /// True when no arch entry is present — used by [`CliZip`]'s
27    /// `skip_serializing_if` so an OS with no bundles is omitted (and a
28    /// fully-empty `cli_zip` still serializes as `{}`).
29    pub fn is_empty(&self) -> bool {
30        self.x86_64.is_none() && self.aarch64.is_none()
31    }
32}
33
34/// Per-OS cli bundle: the GitHub-release `.zip` asset filenames for each
35/// platform, split per CPU architecture (`x86_64` / `aarch64`). At
36/// install time the current platform's entry — matched on OS *and* arch —
37/// is downloaded and extracted into the `cli/` working directory. Local
38/// to the CLI's filesystem layer (the on-disk shape); shared by both the
39/// tool and plugin manifests. The `cli_zip` field itself is required;
40/// each per-OS and per-arch entry is optional — absent means nothing to
41/// fetch for that platform (e.g. a hand-placed source-run tool, or a
42/// viewer-only plugin).
43#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
44#[schemars(rename = "filesystem.tools.CliZip")]
45pub struct CliZip {
46    #[serde(default, skip_serializing_if = "CliZipArch::is_empty")]
47    pub windows: CliZipArch,
48    #[serde(default, skip_serializing_if = "CliZipArch::is_empty")]
49    pub linux: CliZipArch,
50    #[serde(default, skip_serializing_if = "CliZipArch::is_empty")]
51    pub macos: CliZipArch,
52}
53
54/// Declarative metadata a local tool ships with — the on-disk
55/// `objectiveai.json` at
56/// `<base_dir>/tools/<owner>/<name>/<version>/objectiveai.json`. The
57/// CLI reads and writes this shape verbatim; the `tools get` wire
58/// response is a lean projection of it (it drops `cli_zip`).
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
60#[schemars(rename = "filesystem.tools.Manifest")]
61pub struct Manifest {
62    /// GitHub-style owner (user or org) of the tool's source repo.
63    pub owner: String,
64    /// The tool's identifier (matches the `<name>` directory segment).
65    pub name: String,
66    /// Version string. Free-form; semver recommended, not enforced.
67    pub version: String,
68    /// One-line description of what the tool does. Surfaced to agents
69    /// that consume the tool.
70    pub description: String,
71    /// Per-OS exec command. The current platform's vector is the
72    /// program plus its leading arguments; the caller's `--args` are
73    /// appended and the whole thing runs with CWD = this version
74    /// folder's `cli/` subdir.
75    pub exec: Exec,
76    /// Per-OS cli bundle zip filenames (required field; each per-OS
77    /// entry optional). Hand-placed tools leave every entry absent.
78    pub cli_zip: CliZip,
79}
80
81impl Manifest {
82    /// Validate fields that serde alone can't enforce. Tools have no
83    /// cross-field invariants (unlike plugins' viewer rules), so this is
84    /// currently a no-op — it exists so the shared install/discovery
85    /// paths can call `validate()` uniformly across manifest kinds.
86    pub fn validate(&self) -> Result<(), &'static str> {
87        Ok(())
88    }
89}
90
91// Projection to the SDK's lean `tools get` wire response — drops the
92// on-disk-only `cli_zip`. `exec` is the same SDK `Exec` type, so it
93// moves across unchanged.
94impl From<Manifest> for objectiveai_sdk::cli::command::tools::get::ResponseManifest {
95    fn from(m: Manifest) -> Self {
96        Self {
97            owner: m.owner,
98            name: m.name,
99            version: m.version,
100            description: m.description,
101            exec: m.exec,
102        }
103    }
104}