objectiveai-cli 2.0.8

ObjectiveAI command-line interface and embeddable library
# Authoring a Local Plugin

You're authoring a plugin under `~/.objectiveai/plugins/` by hand. The
CLI does not install anything on this path — it only hands you these
instructions. Follow the steps in order.

## 1. Fetch the manifest schema

Don't guess the manifest fields. The CLI ships the live JSON Schema:

    objectiveai schemas filesystem plugins Manifest get

Read all of it before writing anything to disk. For the optional
`viewer_routes` field, also fetch:

    objectiveai schemas filesystem plugins ViewerRoute get

## 2. Write the manifest

Choose a plugin name. The name is both the directory under
`~/.objectiveai/plugins/` and the sidecar JSON filename. Use lowercase
ASCII letters, digits, `.`, `_`, or `-` — the same character set
GitHub allows for repository names.

Create the manifest at:

    ~/.objectiveai/plugins/<name>.json

Required fields are `description` and `version` (semver-style string,
e.g. `0.1.0`). Everything else is optional. **`binaries` is for
GitHub-install plugins only** — local plugins ship the binary directly
in the plugin directory (next step), so leave `binaries` out.

## 3. Place the plugin binary

Drop the binary at:

    ~/.objectiveai/plugins/<name>/<filename>

`<filename>` can be any of:

- `plugin` (canonical on Linux / macOS)
- `plugin.exe` (canonical on Windows)
- `plugin.<any-extension>` (e.g. `plugin.sh`, `plugin.py`,
  `plugin.bat`, `plugin.js`) — the CLI's plugin resolver scans for any
  file with stem `plugin` and falls back through three tiers. On
  Windows: `plugin.exe``plugin` → first matching `plugin.*`. On
  other platforms: `plugin``plugin.exe` → first matching `plugin.*`.

The file can be a compiled binary OR a script. The CLI calls
`Command::new(path)`, and the OS picks the right runner via shebang
(Unix) or extension association (Windows). For a shell script with a
shebang, set the executable bit on Unix:

    chmod +x ~/.objectiveai/plugins/<name>/plugin

## 4. Set up the viewer (optional)

If your plugin has a UI tab in the viewer, pick ONE of the two
sources. They're mutually exclusive — setting both makes the manifest
invalid.

### Option A — `viewer_url` (recommended for development)

Run your viewer dev server (Vite, Next.js, whatever) on a local port,
e.g. `http://localhost:5173`. Set the URL in the manifest:

    "viewer_url": "http://localhost:5173"

The viewer loads it directly into the iframe — your dev server's hot
reload propagates straight in. Edit-save-see-update with no plugin
reinstall. This is the loop you want during development.

Allowed schemes: `https://`, `http://localhost*`, `http://127.0.0.1*`.
Anything else (raw `http://` on a public hostname, `ftp://`, …) is
rejected at manifest-parse time.

### Option B — bundled `viewer/` directory

For a production-ish local plugin without a dev server, place
pre-built static assets (an `index.html` plus JS / CSS / images)
directly under:

    ~/.objectiveai/plugins/<name>/viewer/

Leave `viewer_url` AND `viewer_zip` unset in the manifest. The
viewer's `plugin://` URI handler serves files out of that directory;
the iframe loads `plugin://localhost/<name>/index.html`. No hot
reload — rebuild + restart the viewer to see changes.

### `viewer_routes`

Either viewer source can declare `viewer_routes` — HTTP endpoints on
the viewer's embedded axum server that emit postMessage events into
your plugin's iframe. See the `ViewerRoute` schema (fetched in step
1) for the exact field shape. A typical entry:

    { "path": "/say", "method": "POST", "type": "say_request" }

…registers `/plugin/<name>/say` (POST). Hits on it deliver an
`inbound` event with `sub_type: "say_request"` to the iframe; the
plugin code subscribes via `listen("say_request", handler)` from
`@objectiveai/sdk`.

## 5. Restart the viewer

The viewer scans `~/.objectiveai/plugins/` on startup. New plugins
and manifest changes are picked up at restart, not live.