Developers

Plugins

Plugin Reference

This is the authoritative reference for the plugin.json manifest format and the Nebo plugin system. A plugin is a managed native binary that multiple skills share.

Manifest Schema

The plugin.json file is stored at <data_dir>/nebo/plugins/<slug>/<version>/plugin.json. All fields use camelCase.

Core Fields

Field Type Required Default Description
id string Yes -- NeboLoop artifact ID. Assigned when you create the plugin
slug string Yes -- URL-safe identifier. Must match what skills reference in plugins[].name. Kebab-case
name string Yes -- Human-readable display name
version string Yes -- Semver version string (e.g., "1.2.3")
description string No "" Brief description of what the plugin does
author string No "" Publisher name
platforms object Yes -- Map of platform key to PlatformBinary. See Platform Binaries below
signingKeyId string No "" ED25519 signing key ID used to sign binaries
envVar string No "" Custom environment variable name. If empty, defaults to {SLUG}_BIN
auth object No null Authentication configuration. See Authentication below
events array No null Event declarations. See Events below

Platform Binaries

The platforms field maps platform keys to binary metadata. Valid keys:

Key OS Architecture
darwin-arm64 macOS Apple Silicon
darwin-amd64 macOS Intel
linux-arm64 Linux ARM64
linux-amd64 Linux x86_64
windows-arm64 Windows ARM64
windows-amd64 Windows x86_64

Each platform entry has these fields:

Field Type Description
binaryName string Filename of the binary (e.g., "gws", "gws.exe")
sha256 string SHA256 hex hash for integrity verification
signature string ED25519 signature (base64 encoded)
size number File size in bytes
downloadUrl string URL to download the binary (CDN or API path)

Environment Variables

Plugin binaries are injected into skill scripts as environment variables.

Naming Convention

{SLUG}_BIN

The slug is uppercased and hyphens become underscores:

Plugin Slug Env Var
gws GWS_BIN
ffmpeg FFMPEG_BIN
my-tool MY_TOOL_BIN
nebo-office NEBO_OFFICE_BIN

If envVar is set in the manifest, the custom name is used instead.

Authentication

Plugins that require user credentials declare an auth object. Nebo provides a built-in auth UI with WebSocket-driven OAuth flow.

Auth Schema

Field Type Required Default Description
type string Yes -- Auth type identifier (e.g., "oauth_cli")
label string Yes -- Human-readable label for UI button (e.g., "Google Account")
description string No "" Description shown during auth step
commands object Yes -- CLI subcommands for auth lifecycle
env object No {} Environment variables injected when running auth commands

Auth Commands

Field Type Required Description
commands.login string Yes CLI args for login. Appended to binary path (e.g., "auth login")
commands.status string No CLI args for status check. Exit code 0 = authenticated. May output JSON
commands.logout string No CLI args to clear credentials (e.g., "auth logout")

Auth Flow

  1. Status check: Nebo runs <binary> <status_cmd> on startup. Exit code 0 = authenticated
  2. Login: User clicks "Connect". Nebo spawns <binary> <login_cmd> in background
  3. OAuth URL: Nebo scans stderr/stdout for HTTP(S) URLs, opens them in the browser
  4. Completion: Process exits with code 0 = success. Nebo broadcasts plugin_auth_complete
  5. Logout: <binary> <logout_cmd> runs synchronously

Auth Example

{
  "auth": {
    "type": "oauth_cli",
    "label": "Google Account",
    "description": "Authenticate with your Google Workspace account.",
    "commands": {
      "login": "auth login",
      "status": "auth status",
      "logout": "auth logout"
    },
    "env": {
      "GOOGLE_CLIENT_ID": "123456.apps.googleusercontent.com",
      "GOOGLE_CLIENT_SECRET": "secret"
    }
  }
}

Auth HTTP Endpoints

Endpoint Method Description Response
/api/v1/plugins/{slug}/auth/status GET Run status command { "authenticated": bool, ...plugin_data }
/api/v1/plugins/{slug}/auth/login POST Spawn login command { "started": true }
/api/v1/plugins/{slug}/auth/logout POST Run logout command { "success": true }

Auth WebSocket Events

Event Payload Description
plugin_auth_started { plugin, label } Login command spawned
plugin_auth_url { plugin, url } OAuth URL discovered in process output
plugin_auth_complete { plugin } Login succeeded (exit code 0)
plugin_auth_error { plugin, error } Login failed or errored

Events

Plugins can declare event-producing capabilities. A long-running watch process outputs NDJSON to stdout, and Nebo auto-emits each line into the EventBus. Other agents subscribe by event name without knowing plugin internals.

Event Schema

Field Type Required Default Description
name string Yes -- Event name. Prefixed with plugin slug at runtime (e.g., "email.new" becomes "gws.email.new")
description string No "" Human-readable description of what triggers this event
command string Yes -- CLI args for the watch process. Supports {{key}} template substitution
multiplexed bool No false If true, NDJSON lines may contain an "event" field for multiplexing

Event Example

{
  "events": [
    {
      "name": "email.new",
      "description": "Fires when a new email arrives in Gmail",
      "command": "gmail +watch --format ndjson --project {{gcp_project}}"
    },
    {
      "name": "calendar.event",
      "description": "Fires on calendar event changes",
      "command": "calendar +watch --format ndjson",
      "multiplexed": true
    }
  ]
}

NDJSON Protocol

Your watch command must output one JSON object per line to stdout.

Single event type (multiplexed: false):

Every line becomes the payload, emitted under the declared event source.

{"messageId": "123", "from": "alice@example.com", "subject": "Hello"}

Nebo emits: Event { source: "gws.email.new", payload: <entire line> }

Multiplexed (multiplexed: true):

Lines may contain an "event" field that selects the event type. The field is stripped from the payload.

{"event": "email.new", "messageId": "123", "from": "alice@example.com"}
{"event": "email.read", "messageId": "456"}

Nebo emits:

  • gws.email.new with payload {"messageId": "123", "from": "alice@example.com"}
  • gws.email.read with payload {"messageId": "456"}

Lines without an event field fall back to the declared event name.

Template Substitution

Event commands support {{key}} placeholders. These are replaced with values from the agent's input_values at runtime.

"command": "gmail +watch --format ndjson --project {{gcp_project}}"

If the agent has input_values: { "gcp_project": "my-project-123" }, the resolved command becomes gmail +watch --format ndjson --project my-project-123.

Agent Watch Trigger Integration

Agents consume plugin events via watch triggers in agent.json:

{
  "email-watcher": {
    "trigger": {
      "type": "watch",
      "plugin": "gws",
      "event": "email.new",
      "restart_delay_secs": 5
    },
    "description": "React to new emails",
    "activities": [
      {
        "id": "triage",
        "intent": "Triage the incoming email",
        "steps": ["Classify urgency", "Draft response if needed"]
      }
    ]
  }
}

When event is set:

  • The command is resolved from the plugin manifest (no need to hardcode it)
  • NDJSON output auto-emits into the EventBus as gws.email.new
  • If activities are defined, the inline workflow also runs (dual mode)
  • Watches without activities are valid -- they only auto-emit into the EventBus

Event Discovery Endpoints

Endpoint Method Description
/api/v1/plugins/events GET All events across all installed plugins
/api/v1/plugins/{slug}/events GET Events for a specific plugin

Response format:

{
  "events": [
    {
      "plugin": "gws",
      "name": "email.new",
      "source": "gws.email.new",
      "description": "Fires when a new email arrives in Gmail",
      "multiplexed": false
    }
  ],
  "total": 1
}

SKILL.md Plugin Dependency

Skills declare plugin dependencies in their frontmatter:

---
name: gmail
description: Send and read Gmail messages
plugins:
  - name: gws
    version: ">=1.2.0"
  - name: ffmpeg
    version: ">=5.0.0"
    optional: true
---

Dependency Fields

Field Type Required Default Description
name string Yes -- Plugin slug (must match plugin.json's slug field)
version string No "*" Semver range. See Version Ranges below
optional bool No false If true, skill loads even if plugin isn't installed

Version Ranges

Range Meaning
"*" Any installed version
">=1.2.0" Version 1.2.0 or higher
"^1.0.0" Compatible with 1.x.x (>=1.0.0, <2.0.0)
"~1.2.0" Patch updates only (>=1.2.0, <1.3.0)
"=1.2.0" Exact version

Nebo resolves to the highest installed version matching the range.

HTTP Endpoints

Complete list of plugin-related REST endpoints:

Endpoint Method Description Response
/api/v1/plugins GET List installed plugins (deduped, highest version) { plugins: [...], total }
/api/v1/plugins/events GET List all plugin events { events: [...], total }
/api/v1/plugins/{slug} DELETE Remove plugin and all versions { message }
/api/v1/plugins/{slug}/events GET List events for one plugin { plugin, events: [...], total }
/api/v1/plugins/{slug}/auth/status GET Check auth status { authenticated, ...data }
/api/v1/plugins/{slug}/auth/login POST Start auth login { started }
/api/v1/plugins/{slug}/auth/logout POST Clear auth credentials { success }

GET /plugins Response

{
  "plugins": [
    {
      "slug": "gws",
      "version": "1.2.3",
      "name": "Google Workspace CLI",
      "description": "Google Workspace integration",
      "author": "NeboLoop Inc.",
      "hasAuth": true,
      "authLabel": "Google Account",
      "hasEvents": true,
      "eventCount": 2,
      "source": "installed"
    }
  ],
  "total": 1
}

WebSocket Events

All events broadcast during plugin operations:

Event Payload When
plugin_installing { plugin, platform } Before download starts
plugin_installed { plugin } After successful install
plugin_error { plugin, error } Download or verify failure
plugin_auth_started { plugin, label } Auth login begins
plugin_auth_url { plugin, url } OAuth URL discovered
plugin_auth_complete { plugin } Auth login succeeded
plugin_auth_error { plugin, error } Auth login failed

.napp Archive Format

Plugins can be distributed as .napp sealed archives that bundle the binary with documentation and embedded skills:

my-plugin.napp
├── manifest.json       # Package identity: { type, name, version }
├── plugin.json         # Full plugin manifest
├── PLUGIN.md           # Plugin documentation
├── my-plugin           # Native binary (platform-specific)
└── skills/             # Optional embedded skills
    ├── my-skill-a/
    │   └── SKILL.md
    └── my-skill-b/
        └── SKILL.md

After installation, the archive is extracted to <data_dir>/nebo/plugins/<slug>/<version>/. Embedded skills are discovered by the skill loader automatically.

Storage Layout

<data_dir>/nebo/plugins/
  gws/
    1.2.3/
      manifest.json    # Package identity
      plugin.json      # Plugin manifest
      PLUGIN.md        # Documentation
      gws              # Binary (chmod 755)
      skills/          # Embedded skills
        gws-gmail/
          SKILL.md
    1.2.2/
      ...              # Older versions coexist
  ffmpeg/
    5.1.0/
      ...

Platform-Specific Base Paths

Platform Base Path
macOS ~/Library/Application Support/nebo/plugins/
Linux ~/.local/share/nebo/plugins/
Windows %APPDATA%\nebo\plugins\

Install Codes

Published plugins receive a unique install code:

PLUG-XXXX-XXXX
  • Format: PLUG- prefix + two groups of 4 Crockford Base32 characters
  • Case-insensitive (Crockford Base32 excludes ambiguous characters I, L, O, U)
  • Users paste the code into Nebo to install
  • More commonly, plugins install automatically as skill dependencies

Publishing Tool Actions

All plugin management via the NeboLoop MCP plugin tool:

Action Description Requires Developer Account
plugin(action: list) List your plugins No
plugin(action: get, id: "...") Get plugin details No
plugin(action: create, name: "...", category: "...") Create plugin No
plugin(action: update, id: "...") Update metadata No
plugin(action: delete, id: "...") Delete plugin No
plugin(action: submit, id: "...", version: "...") Submit for review Yes
plugin(action: list-binaries, id: "...") List uploaded binaries Yes
plugin(action: binary-token, id: "...") Generate upload token (5min expiry) Yes
plugin(action: delete-binary, id: "...") Delete a binary Yes

Security

  • SHA256 verification on every download -- mismatch rejects the binary
  • ED25519 signatures verified when signing key is available
  • Quarantine on revocation -- binary deleted, .quarantined marker written, dependent skills dropped
  • Offline resolution -- after first install, everything works without network
  • Garbage collection -- unreferenced plugin directories are periodically removed

Complete Example

A full plugin.json with authentication and events:

{
  "id": "abc-123",
  "slug": "gws",
  "name": "Google Workspace CLI",
  "version": "1.2.3",
  "description": "Google Workspace integration for email, calendar, and drive",
  "author": "NeboLoop Inc.",
  "platforms": {
    "darwin-arm64": {
      "binaryName": "gws",
      "sha256": "a1b2c3d4e5f6...",
      "signature": "base64signature...",
      "size": 45678900,
      "downloadUrl": "https://cdn.neboloop.com/plugins/gws/1.2.3/darwin-arm64/gws"
    },
    "linux-amd64": {
      "binaryName": "gws",
      "sha256": "f6e5d4c3b2a1...",
      "signature": "base64signature...",
      "size": 42000000,
      "downloadUrl": "https://cdn.neboloop.com/plugins/gws/1.2.3/linux-amd64/gws"
    }
  },
  "signingKeyId": "key-001",
  "envVar": "",
  "auth": {
    "type": "oauth_cli",
    "label": "Google Account",
    "description": "Authenticate with your Google Workspace account.",
    "commands": {
      "login": "auth login",
      "status": "auth status",
      "logout": "auth logout"
    },
    "env": {}
  },
  "events": [
    {
      "name": "email.new",
      "description": "Fires when a new email arrives in Gmail",
      "command": "gmail +watch --format ndjson",
      "multiplexed": false
    },
    {
      "name": "calendar.event",
      "description": "Fires on calendar event changes",
      "command": "calendar +watch --format ndjson",
      "multiplexed": true
    }
  ]
}