npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@synadia-ai/nats-channel

v0.5.6

Published

Synadia Agent Protocol for NATS channel for OpenClaw. Makes every OpenClaw agent a discoverable, spec-compliant agent on NATS.

Readme

@synadia-ai/nats-channel

NATS channel plugin for OpenClaw. Every configured OpenClaw agent becomes discoverable, addressable, and streamable over NATS — anyone running a Synadia Agent Protocol for NATS client (e.g. @synadia-ai/agents or synadia-ai-agents) can find your agent, prompt it, and stream the reply back.

Install

openclaw plugins install @synadia-ai/nats-channel

If your OpenClaw config has a non-empty plugins.allow list, add "nats" to it — that list, if set, gates which non-bundled plugins are enabled.

Quickstart (env vars)

Set env vars and start the gateway — the channel auto-bootstraps on first start:

# Pick one connection path:
export NATS_CONTEXT=ngs                       # a NATS CLI context (recommended for NGS / managed NATS)
# — or —
export NATS_URL=nats://demo.nats.io            # raw URL
export NATS_CREDENTIALS=/path/to/your.creds   # optional, for NKEY/JWT auth

export NATS_AGENT_NAME=my-agent               # required: agent identity (5th subject token)
export NATS_OWNER=my-org                      # optional: 4th subject token (defaults to "default")

openclaw gateway

The channel comes up on first start at agents.prompt.oc.<owner>.<agentName>. No edits to ~/.openclaw/openclaw.json needed — the plugin's account resolver falls back to a "default" account when none is configured and fills every field from env vars. No openclaw configure step, no extensions/ symlink.

Only one account is active at a time. The plugin runs a single gateway process and registers one account on it. Adding multiple accounts.<id> blocks in your config doesn't register multiple agents simultaneously — pick the one you want active.

Configure via the wizard (alternative)

Known issue on OpenClaw 2026.5.4+. The wizard's channel list (openclaw configure --section channels, openclaw channels add) doesn't currently surface npm-installed channel plugins that aren't in OpenClaw's official channel catalog. Tracked upstream; until then, prefer the env-var quickstart above.

If you're on an older OpenClaw (or the catalog enrollment lands), the wizard path still works:

openclaw configure --section channels

Pick NATS Agent Network and answer the prompts. The only required field is the agent name (alphanumeric, -, _ — no spaces or dots; it becomes part of a NATS subject); the rest fall back to sensible defaults (demo.nats.io, default owner, no auth).

After restarting OpenClaw, your agent is reachable at:

agents.prompt.oc.<owner>.<agentName>

Three common configurations

Local dev — public demo NATS:

openclaw config set channels.nats.accounts.default.agentName "my-agent"
# url defaults to demo.nats.io; owner defaults to "default"

Production with a NATS CLI context (already configured via nats context add):

openclaw config set channels.nats.accounts.default.agentName "my-agent"
openclaw config set channels.nats.accounts.default.context "prod"
openclaw config set channels.nats.accounts.default.owner "acme"

NGS or other auth via a .creds file:

openclaw config set channels.nats.accounts.default.agentName "my-agent"
openclaw config set channels.nats.accounts.default.url "tls://connect.ngs.global"
openclaw config set channels.nats.accounts.default.credentials "/home/me/.config/nats/ngs.creds"
openclaw config set channels.nats.accounts.default.owner "acme"

Or write directly to ~/.openclaw/openclaw.json:

{
  "channels": {
    "nats": {
      "accounts": {
        "default": {
          "agentName": "my-agent",
          "owner": "acme",
          "context": "prod"
        }
      }
    }
  }
}

Restart with openclaw gateway restart.

Configuration reference

| Field | Required | Default | Description | |-------|----------|---------|-------------| | agentName | yes | — | The 5th token in your agent's subject. Letters, digits, -, _. | | owner | no | default | The 4th token — your operator/account namespace. | | url | no | nats://demo.nats.io | NATS server URL. Ignored when context resolves successfully. | | context | no | — | Name of a NATS CLI context (file under ~/.config/nats/context/<name>.json). Sources url and credentials from there. | | credentials | no | — | Path to a .creds file (NGS, NKEY/JWT auth). | | description | no | OpenClaw agent <agentName> | Shown in $SRV.INFO so callers know what they discovered. | | enabled | no | true | Set to false to keep the account block in config but skip connecting. |

orgowner. The pre-0.3 org field is still accepted as a deprecated alias and logs a one-time warning until you rename it.

Environment variables

Each field has a matching env var. Useful for containers and any setup where you don't want secrets baked into a config file.

| Variable | Sets | Notes | |----------|------|-------| | NATS_CONTEXT | context | Highest precedence — see below. | | NATS_URL | url | | | NATS_AGENT_NAME | agentName | | | NATS_DESCRIPTION | description | | | NATS_OWNER | owner | | | NATS_ORG | owner | Legacy alias. | | NATS_CREDENTIALS | credentials | |

Resolution order

When several sources set the same field, this is who wins. Later steps override earlier ones, except $NATS_CONTEXT which is applied last as a single source of truth for url + credentials.

  1. Built-in default (nats://demo.nats.io for url, default for owner)
  2. Account config in openclaw.json
  3. config.context — wizard-selected NATS CLI context
  4. Per-field env vars ($NATS_URL, $NATS_CREDENTIALS, $NATS_AGENT_NAME, …)
  5. $NATS_CONTEXT — wins over everything else

A failure in step 3 or 5 (missing file, malformed JSON, no url) is logged and downgraded — the gateway falls back to whatever the previous step resolved instead of crashing.

Auth limitations. A NATS CLI context is read for url, token, user/password, and creds. Inline nkey, user_jwt/user_seed, and the TLS triple cert/key/ca are silently dropped — for those, point credentials at a .creds file directly.

Verify

# Find your agent (and any others on the same NATS)
nats req '$SRV.INFO.agents' '' --replies=0 --timeout=2s

# Watch heartbeats — your agent beats every ~5 s
nats sub 'agents.hb.*.*.*'

A successful $SRV.INFO.agents response for an OpenClaw agent looks like:

{
  "type": "io.nats.micro.v1.info_response",
  "name": "agents",
  "id": "PYYZRKNVLK6CA1LC6L7FZU",
  "version": "0.4.0",
  "description": "My OpenClaw agent",
  "metadata": {
    "agent": "openclaw",
    "owner": "me",
    "session": "default",
    "protocol_version": "0.3",
    "platform": "openclaw",
    "description": "My OpenClaw agent"
  },
  "endpoints": [
    {
      "name": "prompt",
      "subject": "agents.prompt.oc.me.my-agent",
      "queue_group": "agents",
      "metadata": { "max_payload": "8MB", "attachments_ok": "true" }
    },
    {
      "name": "status",
      "subject": "agents.status.oc.me.my-agent",
      "queue_group": "agents"
    }
  ]
}

If you see your agents.prompt.oc.<owner>.<agentName> subject in the response, you're discoverable.

Talk to your agent

From the CLI:

# Plain text prompt
nats req agents.prompt.oc.<owner>.<agentName> "Hello!" \
  --wait-for-empty --reply-timeout 30s --timeout 60s

# JSON envelope (caller SDKs use this form under the hood)
nats req agents.prompt.oc.<owner>.<agentName> '{"prompt":"Hello!"}' \
  --wait-for-empty --reply-timeout 30s --timeout 60s

# With an attachment
nats req agents.prompt.oc.<owner>.<agentName> '{
  "prompt": "describe this image",
  "attachments": [{"filename":"pic.png","content":"<base64>"}]
}' --wait-for-empty --reply-timeout 30s --timeout 120s

--wait-for-empty is required: replies stream as multiple chunks and end with an empty terminator message.

--reply-timeout matters too. Its default is 300 ms — the maximum gap allowed between consecutive replies. The agent publishes an immediate {type:"status",data:"ack"} chunk on request receipt, but the LLM's first response chunk typically lands 1–2 s later, so the default fires before the first response and the CLI exits after just the ack. Setting --reply-timeout 30s gives the LLM enough warm-up time. SDK callers (requestMany with strategy:"sentinel") don't hit this — they wait the full maxWait regardless of inter-arrival gaps.

From TypeScript using @synadia-ai/agents:

import { connect } from "@nats-io/transport-node";
import { Agents } from "@synadia-ai/agents";

const nc = await connect({ servers: "nats://localhost:4222" });
const agents = new Agents({ nc });

const [agent] = await agents.discover({ filter: { agent: "openclaw" } });

for await (const msg of await agent!.prompt("what can you do?")) {
  if (msg.type === "response") process.stdout.write(msg.text);
}

await agents.close();
await nc.close();

Attachments

When a request envelope carries attachments, each file is decoded and staged at:

<stateDir>/media/nats-channel/<agentName>/<uuid>/<filename>

<stateDir> is the OpenClaw state directory (typically ~/.openclaw). The media/ prefix is required: OpenClaw's media-access allowlist only permits paths under <stateDir>/media, so staged files have to live there for the agent's tools to be allowed to read them.

The absolute paths are prepended to the prompt text so OpenClaw's pipeline (and any tool the agent has access to) can open them by path. Files staged earlier in the gateway's lifetime stay on disk so follow-up turns can reference them; the whole <agentName>/ directory is removed when the gateway stops.

Encode files with base64 -w0 <file> (Linux/macOS) or Buffer.from(bytes).toString("base64") in Node before embedding in the JSON envelope. Caller SDKs do this for you.

Caller-side limits (rejected with 400 if violated):

  • content must be standard-alphabet padded base64 — no URL-safe variant, no whitespace.
  • filename must be a plain basename. Path separators, .., absolute paths, and NUL bytes are rejected, not silently flattened.
  • The fully-encoded request must fit within the server-negotiated max_payload (1 MB on a default nats-server, more if the operator raised --max_payload).

Outbound messages from the agent

When OpenClaw's sendText fires (the agent proactively pushing a message rather than replying to a prompt), the channel publishes to:

agents.oc.<owner>.<agentName>.outbound

This is a fire-and-forget pub/sub subject — subscribe with nats sub agents.oc.<owner>.<agentName>.outbound to consume them. The payload is raw UTF-8 text, not a JSON envelope — pipe it straight into your consumer, don't try to parse it as JSON. It's an OpenClaw-specific extension, not part of the protocol; the subject deliberately sits under the agent root for easy locating relative to the prompt subject.

Multi-tenancy

The agent subject layout has no per-tenant slot. For real isolation between tenants or environments, use NATS accounts and subject permissions — that's a server-side configuration, not a plugin one. Within a single account, agents with distinct owner values coexist cleanly.

Troubleshooting

  • config field 'org' is deprecated — rename orgowner in openclaw.json. The old name still works, just noisy in logs.
  • NATS: disconnected — check url and (if using credentials) that the .creds file exists and is readable by the gateway process.
  • nats req returns only the initial ack and exits — pass --reply-timeout 30s (default is 300 ms, shorter than the gap between the ack chunk and the LLM's first response). See the "Talk to your agent" section above for the full command. --wait-for-empty alone isn't enough.
  • nats req hangs or returns nothing — pass --wait-for-empty. The protocol ends streams with an empty-body message, not a single response.
  • 400 attachment[N] has invalid base64 content — the caller emitted URL-safe base64 or unpadded output. Buffer.from(bytes).toString("base64") (Node) produces the right form.
  • 400 attachment[N] has unsafe filename — send the basename only ("pic.png"), not a path ("./images/pic.png").
  • plugins.allow is empty warning — harmless, plugins still load. To silence it, add "nats" (and any other plugins you want enabled) to plugins.allow.

Development

bun install
bun run build          # compile ./index.ts + ./setup-entry.ts → ./dist (required before publish)
bun run test           # protocol unit tests, no nats-server needed
bun run test:smoke     # wire-level smoke against nats-server on 127.0.0.1:4222

The npm tarball ships compiled entries under dist/ (declared via openclaw.runtimeExtensions and openclaw.runtimeSetupEntry). OpenClaw 2026.5.4+ refuses to install plugins that only ship TS source. prepublishOnly runs the build automatically.

The smoke test needs a nats-server running on 127.0.0.1:4222 — install per the upstream docs (brew install nats-server on macOS) then start it in another terminal with nats-server.

The smoke test drives a minimal spec-compliant service assembled from this repo's own protocol module and verifies $SRV.INFO shape, heartbeat fields, the four 400 paths, the ack → response → terminator cycle, and attachment staging + cleanup.

The plugin pulls @synadia-ai/agents (caller-side primitives) and @synadia-ai/agent-service (host-side encoders + heartbeat helpers) via file: links to the SDK checkouts in this monorepo. See README-DEV.md at the repo root for the build/install dance when iterating locally.

See also

License

Apache-2.0