{
"title": "filesystem.plugins.ManifestWithNameAndSource",
"description": "A [`Manifest`] enriched with the plugin's identifying `name` and\nthe `source` it was loaded from. Used when listing or describing\ninstalled plugins, where the bare manifest fields are not enough\nto identify which plugin they belong to or where they came from.\n\n`name` sits before the manifest body; `source` sits after. The\n`manifest` field is `#[serde(flatten)]`-ed so the wire shape is\none flat JSON object — `serde_json`'s `preserve_order` feature\nkeeps the declared field order, so consumers see `name` first\nand `source` last.",
"type": "object",
"properties": {
"author": {
"description": "Author or authors of the plugin. Free-form string.",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"omitempty": true
},
"binaries": {
"description": "Release-asset filename per platform — what the cli should\ndownload from the GitHub release tagged `v<version>` to install\nthe plugin's binary on each platform. Values are filenames\n(e.g. `psyops-linux-x86_64`, `psyops-windows-x86_64.exe`), NOT\nURLs; the URL is composed from the repository + tag + asset\nname elsewhere.\n\n**Every platform field is optional.** Declare entries only for\nthe platforms this plugin actually ships a binary for; absent\nplatforms are simply not supported by this release. A plugin\nshipping only Linux x86_64 declares one entry; a plugin\nshipping all six declares six. All-None ↔ field omitted in\nthe wire shape.",
"$ref": "filesystem.plugins.Binaries",
"omitempty": true
},
"description": {
"description": "One-line description of what the plugin does. Surfaced in\nlistings and the plugin's `--help`-equivalent UI.",
"type": "string"
},
"homepage": {
"description": "Homepage or repository URL.",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"omitempty": true
},
"license": {
"description": "SPDX license identifier (or any string).",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"omitempty": true
},
"mobile_ready": {
"description": "Plugin author opts in to mobile viewer support by setting\nthis. Mobile viewer builds only surface plugins with this\nflag true — mobile has no local backend binary, so plugin\nUIs that require a backend will misbehave unless their\nauthors specifically design for \"no-backend\" mode. Defaults\nto false (desktop-only).",
"type": "boolean"
},
"name": {
"description": "The plugin's identifier — the filename it lives under in the\nplugins directory (e.g. `psyops` for `~/.objectiveai/plugins/psyops`).",
"type": "string"
},
"source": {
"description": "Where this manifest came from — e.g. an absolute filesystem path,\na URL, or a registry reference. Free-form string; the host\njust displays it.",
"type": "string"
},
"version": {
"description": "Version string. Semver convention is recommended but not\nenforced — the host just displays whatever's here.",
"type": "string"
},
"viewer_routes": {
"description": "HTTP routes the viewer exposes on behalf of this plugin.\nEach entry registers a handler at\n`/plugin/<repository>/<path>` on the viewer's embedded axum\nserver; a hit emits a `PluginRequest { type, value }` event\nto the React frontend, which dispatches to the plugin's\niframe via the postMessage bridge.",
"type": "array",
"items": {
"$ref": "filesystem.plugins.ViewerRoute"
}
},
"viewer_url": {
"description": "Remote URL the viewer's iframe loads directly, instead of an\non-disk bundle from [`Self::viewer_zip`]. The full URL is used\nas the iframe `src=` verbatim — query string, path, port,\nfragment all pass through. Must use `https://`, or `http://`\ntargeting `localhost` / `127.0.0.1` (development only).\n\nMutually exclusive with [`Self::viewer_zip`]. [`Self::viewer_routes`]\nand [`Self::mobile_ready`] apply to remote-URL viewers the same\nway they apply to zip-bundled viewers — the embedded axum\nserver still hosts the declared routes; the iframe still\nreceives the same postMessage protocol regardless of where\nits HTML/JS loaded from.",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"omitempty": true
},
"viewer_zip": {
"description": "GitHub-release asset filename for the plugin's viewer UI\nbundle (a `.zip` whose root contains `index.html` plus\nassets). When absent, the plugin has no viewer tab from this\nsource. Mutually exclusive with [`Self::viewer_url`] —\nvalidated by [`Self::validate`].",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"omitempty": true
}
}
}