> ## Documentation Index
> Fetch the complete documentation index at: https://onecli.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin Protocol: Build Custom Plugins

> Build custom OneCLI plugins. The binary interface, manifest format, lifecycle hooks, and how plugins integrate with the gateway.

This page describes the protocol between oc (the core CLI) and plugins (standalone binaries). If you're using oc to call plugins, this explains what happens under the hood. If you're building a plugin, this is the contract you need to implement.

## Discovery

Plugins are executable binaries stored in `~/.onecli/plugins/` with the naming convention `oc-{name}`. When you run `oc google calendar list-events`, oc looks for a binary at `~/.onecli/plugins/oc-google`.

The plugin directory can be overridden with `ONECLI_PLUGIN_DIR`.

## Manifest

Every plugin must respond to the `manifest` subcommand with a JSON object describing its capabilities:

```bash theme={null}
oc-google manifest
```

```json theme={null}
{
  "name": "google",
  "version": "v1.0.1",
  "description": "Gmail, Calendar, Drive...",
  "auth": {
    "required": true,
    "local_flow": "oauth",
    "scopes": ["calendar.readonly", "gmail.readonly"]
  },
  "commands": [
    {
      "name": "calendar list-events",
      "description": "List upcoming calendar events",
      "args": [
        {
          "name": "--limit",
          "required": false,
          "description": "Maximum number of events to return"
        }
      ]
    }
  ]
}
```

### Manifest fields

| Field                           | Type      | Description                                                |
| ------------------------------- | --------- | ---------------------------------------------------------- |
| `name`                          | string    | Plugin name (matches binary name without `oc-` prefix)     |
| `version`                       | string    | Semantic version                                           |
| `description`                   | string    | Short description of what the plugin provides              |
| `auth.required`                 | boolean   | Whether credentials are needed to run commands             |
| `auth.local_flow`               | string    | Auth flow type (e.g., `"oauth"`). Optional.                |
| `auth.scopes`                   | string\[] | OAuth scopes or permission descriptions. Optional.         |
| `commands`                      | array     | Available commands with descriptions and arguments         |
| `commands[].name`               | string    | Command name (can include subcommands separated by spaces) |
| `commands[].description`        | string    | What the command does                                      |
| `commands[].args`               | array     | Available arguments                                        |
| `commands[].args[].name`        | string    | Argument name                                              |
| `commands[].args[].required`    | boolean   | Whether the argument is required                           |
| `commands[].args[].description` | string    | What the argument does                                     |

## Auth interface

Plugins that require authentication must respond to the `auth` subcommand. The auth flow uses newline-delimited JSON (NDJSON) on stdout:

```bash theme={null}
oc-google auth
```

```
{"status": "pending", "url": "https://accounts.google.com/o/oauth2/auth?...", "code": "ABCD-1234"}
{"status": "ok", "credentials": {"access_token": "ya29...", "refresh_token": "1//...", "token_type": "Bearer", "expires_at": "2025-06-01T00:00:00Z"}}
```

oc reads the stdout stream and stores the first response where `status` is `"ok"` and `credentials.access_token` is non-empty.

### Auth response fields

| Field                       | Type   | Description                                            |
| --------------------------- | ------ | ------------------------------------------------------ |
| `status`                    | string | `"pending"`, `"ok"`, `"authenticated"`, or `"expired"` |
| `credentials.access_token`  | string | The primary authentication token                       |
| `credentials.refresh_token` | string | Optional refresh token                                 |
| `credentials.token_type`    | string | Token type (usually `"Bearer"`)                        |
| `credentials.expires_at`    | string | ISO 8601 expiration timestamp                          |
| `url`                       | string | URL the user should visit (for device/OAuth flows)     |
| `code`                      | string | Code to display to the user (for device flows)         |
| `message`                   | string | Human-readable status message                          |

## Command execution

When oc runs a plugin command, it:

1. **Resolves credentials** from the vault or local storage
2. **Injects credentials as environment variables** into the plugin's process
3. **Executes the plugin binary** as a subprocess with the remaining arguments
4. **Passes stdin/stdout/stderr** directly between the caller and the plugin

### Environment variable injection

Credentials are passed as environment variables with the naming convention `OC_{PLUGINNAME}_{FIELD}`:

| Variable                  | Source                      |
| ------------------------- | --------------------------- |
| `OC_GOOGLE_ACCESS_TOKEN`  | `credentials.access_token`  |
| `OC_GOOGLE_REFRESH_TOKEN` | `credentials.refresh_token` |
| `OC_GOOGLE_TOKEN_TYPE`    | `credentials.token_type`    |
| `OC_GOOGLE_EXPIRES_AT`    | `credentials.expires_at`    |

The plugin name is uppercased. All four fields are set if available.

### Execution model

```
oc google calendar list-events --limit 5
│
├── Finds: ~/.onecli/plugins/oc-google
├── Resolves credentials for "google"
├── Sets env: OC_GOOGLE_ACCESS_TOKEN=..., etc.
├── Executes: oc-google calendar list-events --limit 5
│
└── Plugin writes JSON to stdout, oc passes it through
```

The plugin inherits the caller's stdin, stdout, and stderr. oc does not modify the plugin's output; it passes through directly.

### Meta-commands

Two subcommands are special and run **without credentials**:

* `manifest` returns the plugin's manifest JSON
* `auth` runs the plugin's authentication flow

All other subcommands go through credential resolution first.

## Archive format

Plugin releases follow the goreleaser naming convention:

```
oc-{name}_{version}_{os}_{arch}.tar.gz
```

Example: `oc-google_1.0.1_darwin_arm64.tar.gz`

Each release must include a `checksums.txt` file with SHA256 hashes:

```
a1b2c3d4...  oc-google_1.0.1_darwin_arm64.tar.gz
e5f6g7h8...  oc-google_1.0.1_linux_amd64.tar.gz
```
