Octonode Playbook

Octonode Plugins

Canonical Octonode repository documentation.

Octonode Plugins

A plugin is a language-agnostic folder that contributes one or more nodes to Octonode. It is fully self-contained and relocatable: drop the folder into a plugin root and its nodes become first-class — runnable via octonode invoke and (once wired into a workflow) octonode run.

A plugin folder must contain an octonode.plugin.json manifest:

{
  "schemaVersion": "1",
  "id": "jira",
  "name": "Jira",
  "version": "0.1.0",
  "description": "Create and query Jira issues.",
  "icon": "ticket",
  "scope": ["org", "public"],
  "integration": { "category": "issue-tracking", "tags": ["jira", "atlassian"] },
  "nodes": [
    {
      "id": "create-issue",
      "command": "node nodes/create_issue.cjs",
      "language": "typescript",
      "description": "Create a Jira issue.",
      "inputs":  { "type": "object", "properties": { "summary": { "type": "string" } } },
      "outputs": { "type": "object", "properties": { "key": { "type": "string" } } },
      "env": ["JIRA_TOKEN"]
    }
  ]
}
  • id is lowercase alphanumeric/dash and namespaces every node as "<pluginId>/<nodeId>" (e.g. jira/create-issue).
  • command is resolved relative to the plugin folder, so a plugin can be copied anywhere without breaking. The node speaks the same IPC envelope protocol as any other Octonode node — read the request from stdin, write the response envelope to stdout, logs to stderr.
  • inputs/outputs are JSON Schema, declared by the plugin author (a plugin node owns its contract; unlike project nodes, it is not discovered by scan).
  • scope is the plugin's distribution tier(s) — who may discover and install it. One or more of user, group, org, public. Defaults to ["user"] (private). See Marketplace below.
  • permissions are the coarse runtime capabilities the plugin requests — objects of { "resource": "project_data" | "secrets" | "network", "access": "read" | "write" | "outbound" }. They are shown on the install consent screen and enforced before a node is handed the resource. Defaults to [] (no access to project data, secrets, or the network).

Discovery

Plugins are discovered from two roots, in precedence order:

  1. Project./octonode_plugins/<plugin>/ (committed alongside a project).
  2. User~/.octonode/plugins/<plugin>/ (installed globally for your machine).

On an id collision the project copy wins. A folder is a plugin iff it contains octonode.plugin.json.

CLI

octonode plugin create <name> --lang <typescript>  # scaffold a runnable plugin
octonode plugin list                       # list installed plugins and their nodes (--json)
octonode plugin from-npm <pkg>[@version]   # wrap an npm package as a plugin (alias: octonode npm)
octonode plugin from-swagger <spec>        # generate plugin from OpenAPI/Swagger spec (alias: octonode swagger)
octonode plugin install <path> [--project] # copy a plugin folder into the user (or project) root; --no-deps skips npm install
octonode plugin install <path> --force     # overwrite an existing install
octonode plugin remove <id>                # uninstall a plugin
octonode invoke <pluginId>/<nodeId> --input '{...}' [--env <name>]

octonode ecosystem is an alias for octonode plugin.

Scaffolding

octonode plugin create my-thing --lang typescript generates a self-contained, immediately-runnable TypeScript plugin (manifest + a starter run node + README). Flags: --dir <parent> (where to create it), --node <id> (starter node id), --force.

Marketplace

Plugins are shared through the hosted marketplace — a Cloudflare Worker backed by D1 (plugin metadata, versions, installs, and usage analytics) and R2 (the packed plugin bundles). Point the tooling at it with:

export OCTONODE_MARKETPLACE_URL=https://<your-worker>.workers.dev
export OCTONODE_MARKETPLACE_TOKEN=<supabase-jwt-or-service-token>

For deployments, OCTONODE_MARKETPLACE_TOKEN is the token expected by Octonode workflows; Wrangler itself does not mint this token. If you need one for Cloudflare API calls (for example, wrangler deploy, wrangler d1 create, etc.), create it in the Cloudflare Dashboard/API token settings and run wrangler login followed by wrangler environment auth in your local shell.

A plugin's scope array decides who may discover it. user is private to its publisher; group, org, and public progressively widen visibility. Creating or installing a plugin from a local folder never uploads it—the hosted marketplace is used only by an explicit publish command.

octonode plugin search <query>          # search the hosted marketplace
octonode plugin search <q> --scope org  # filter to a single tier
octonode plugin install <id>            # download the bundle, install, record the install
octonode plugin publish <path>          # pack the folder → upload bundle to R2, metadata to D1
octonode plugin update <id> | --all     # reinstall when a newer version is published
octonode install                        # restore the plugin store from octonode.lock
                                        # (npm-ci style: exact versions, hash-verified)

For a private npm-backed plugin, generation and publication are separate:

octonode plugin from-npm lodash         # creates ./lodash locally with scope ["user"]
octonode plugin install ./lodash        # local-only user install; no cloud request
octonode plugin publish ./lodash        # optional: private cloud copy for this user

Private cloud copies can be restored on another authenticated machine through the normal marketplace install and octonode.lock flow. Their marketplace identity is qualified by publisher, so different users may privately publish the same manifest id without colliding.

publish packs the plugin folder into a tarball (excluding node_modules, __pycache__, .git), uploads it, and records the manifest — including its permissions — with a content sha256. install shows the plugin's requested permissions, downloads + extracts the bundle, installs through the normal local path, and records the install (with its granted permissions) for analytics. Every install/invoke/error event lands in the marketplace DB; the Studio's Marketplace → Analytics tab shows per-plugin installs, invocations, and errors.

octonode plugin install accepts either a marketplace id or a folder path — a path that exists on disk installs directly; anything else is looked up across the scoped marketplaces.

The Studio's Marketplace view (left rail) renders hosted plugins as cards — search, see which plugins are installed, and Install with one click. Marketplace installs follow the octonode.lock model: the bundle lands in the global content-addressed store (~/.octonode/store/<id>@<version>-<sha8>/, immutable, shared across projects) and the project's committed octonode.lock pins the exact version + content hash — no plugin code is ever copied into the project, and its nodes appear in the editor palette read-only. Local folders in octonode_plugins/ still work for plugin development and shadow lock entries on id collisions (like npm link). The server backs this with GET /api/marketplace[?q=], GET /api/plugins, POST /api/marketplace/:id/install, and POST /api/plugins/:pluginId/nodes/:nodeId/add.

Plugin sources

Published plugin code lives in the hosted marketplace's R2 bucket; its metadata lives in D1. This repository does not duplicate those bundles. For local plugin development, scaffold a plugin and install it by path:

octonode plugin create example-greeter --dir .
octonode plugin install ./example-greeter --project
octonode plugin list

The selected environment's variables (from .octonode, if present) are injected into the plugin node's process, exactly as for project nodes — config reroutes execution, never the plugin's source.

Generating a plugin from an OpenAPI/Swagger spec

Turn any API that ships an OpenAPI 3.x or Swagger 2.0 document into a plugin with one command:

octonode plugin from-swagger <spec> [flags]   # alias: octonode swagger <spec>

<spec> is a local path or a URL, JSON or YAML. Each API operation becomes a self-contained node:

  • inputs — path/query/header params at the top level; the request body is nested under a body property.
  • outputs{ status, body }, where body is the operation's 2xx response schema.
  • Auth — the spec's security schemes map to env vars (e.g. a bearer scheme on the petstore plugin → PETSTORE_TOKEN), injected automatically. With no credentials set, a node returns a labelled dry-run so the plugin is runnable offline. Override the base URL at runtime with <PREFIX>_BASE_URL.

Generated nodes are zero-dependency TypeScript using built-in fetch (ESM).

Flags

FlagMeaning
--lang tsNode language (default ts).
--id <plugin-id>Plugin id (default: a slug of the spec title).
--name, --icon, --description, --versionBranding not present in the spec.
--base-url <url>Override the server URL baked into the manifest.
--tag a,bOnly operations carrying these OpenAPI tags (comma-separated).
--include a,b / --exclude a,bGlob filters on operationId / METHOD path.
--dir, --forceOutput parent dir; overwrite an existing folder.
--dry-runPrint the node list without writing files.

Example

octonode plugin from-swagger https://petstore3.swagger.io/api/v3/openapi.json \
  --id petstore --icon paw --lang ts
octonode plugin install ./petstore --project
PETSTORE_TOKEN= octonode invoke petstore/get-pet --input '{"petId":"7"}'

Generating a plugin from an npm package

Wrap any npm package as a plugin with one command:

octonode plugin from-npm <package>[@version] [flags]   # alias: octonode npm <package>

Each exported function (from the package's TypeScript declarations) becomes a node whose inputs/outputs JSON Schema is derived from the types; a generic call node ({export, args}{result}) is always included as an escape hatch for classes, constants, and untyped corners. Packages without type declarations fall back to runtime export enumeration with permissive schemas. Workflow inputs and outputs must be JSON-compatible; an export that returns a non-serializable value fails with VALIDATION_ERROR instead of being coerced.

The generated manifest records the original package name, resolved version, and install spec. octonode plugin install detects the project's packageManager or lockfile and adds that exact package with npm, Yarn, pnpm, or Bun (skip with --no-deps). The project package.json and native lockfile remain authoritative; the plugin does not get a nested package manifest, lockfile, or node_modules.

All extracted nodes share one generated nodes/npm.cjs IPC adapter. It resolves and calls the original project dependency; it does not copy or reimplement the package. Until the dependency is installed, nodes return a labelled dry-run. Lifecycle scripts are disabled during package installation.

Flags

FlagMeaning
--id, --name, --icon, --description, --versionBranding; default to the package's registry metadata.
--include a,b* / --exclude a,bGlob filters on export names.
--dir, --forceOutput parent dir; overwrite an existing folder.
--dry-runPrint the node list without writing files.

Example

octonode plugin from-npm lodash --include 'chunk,pick,groupBy'
octonode plugin install ./lodash --project
octonode invoke lodash/chunk --input '{"array":[1,2,3,4],"size":2}'

The Studio Marketplace has the same flow: From npm opens a dialog (package, version, export filter) backed by POST /api/plugins/from-npm; the generated plugin installs into the project and its nodes appear in the palette.

First-party collaboration plugin

examples/octonode-collaboration is generated from the Hono OpenAPI document and exposes profiles, chat, notifications, repository/PR review, workflow and code suggestions, explicit GitHub publication, and recovery as ordinary workflow nodes. It contains transport adapters only; all authorization, GitHub credentials, persistence, conflict checks, and idempotency remain in the server. Browser realtime tickets and agent-token creation are excluded deliberately.

Install and invoke it with:

octonode plugin install ./examples/octonode-collaboration --project
OCTONODE_COLLABORATION_BASE_URL=http://127.0.0.1:8787 \
OCTONODE_COLLABORATION_TOKEN=<user-or-agent-token> \
octonode invoke octonode-collaboration/get-api-social-conversations \
  --input '{"workspace":"org:<workspace-id>"}'

Regenerate it after yarn gen with the repository's Swagger generator, using only the Profiles, Agents, Assistant, Social, GitHub, Reviews, Suggestions, and Recovery tag groups and excluding postApiWorkspaceAgents and postApiSocialRealtimeTicket. Generated binary responses use base64, so workflow data remains JSON-compatible.

On this page