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"]
}
]
}idis lowercase alphanumeric/dash and namespaces every node as"<pluginId>/<nodeId>"(e.g.jira/create-issue).commandis 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/outputsare JSON Schema, declared by the plugin author (a plugin node owns its contract; unlike project nodes, it is not discovered byscan).scopeis the plugin's distribution tier(s) — who may discover and install it. One or more ofuser,group,org,public. Defaults to["user"](private). See Marketplace below.permissionsare 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:
- Project —
./octonode_plugins/<plugin>/(committed alongside a project). - 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 userPrivate 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 listThe 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 abodyproperty.outputs—{ status, body }, wherebodyis the operation's2xxresponse schema.- Auth — the spec's security schemes map to env vars (e.g. a bearer scheme on
the
petstoreplugin →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
| Flag | Meaning |
|---|---|
--lang ts | Node language (default ts). |
--id <plugin-id> | Plugin id (default: a slug of the spec title). |
--name, --icon, --description, --version | Branding not present in the spec. |
--base-url <url> | Override the server URL baked into the manifest. |
--tag a,b | Only operations carrying these OpenAPI tags (comma-separated). |
--include a,b / --exclude a,b | Glob filters on operationId / METHOD path. |
--dir, --force | Output parent dir; overwrite an existing folder. |
--dry-run | Print 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
| Flag | Meaning |
|---|---|
--id, --name, --icon, --description, --version | Branding; default to the package's registry metadata. |
--include a,b* / --exclude a,b | Glob filters on export names. |
--dir, --force | Output parent dir; overwrite an existing folder. |
--dry-run | Print 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.