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

@m64/nats-pi-bridge

v0.0.4

Published

Standalone headless service that spawns and manages PI coding agent sessions on demand via NATS. Control plane / data plane split, streaming wire protocol, multi-session microservice registration.

Downloads

44

Readme

nats-pi-bridge

Standalone headless service that spawns and manages PI coding agent sessions on demand via NATS.

Zero PI install required. The bridge embeds the full @mariozechner/pi-coding-agent runtime as library code — agent loop, tool execution (read/write/edit/bash), session management, model registry, all in-process. One npx @m64/nats-pi-bridge command gives you a fully operational PI worker addressable over NATS. No global pi binary, no TUI, no interactive CLI. Ideal for Docker containers, CI runners, and remote hosts where a full interactive coding agent would be overkill.

All you need on the host: Node.js ≥20.6, a NATS server (or demo.nats.io), and an API key for any supported model provider.

This bridge exposes session lifecycle + prompting as a pi-exec NATS microservice. Callers send structured JSON to a permanent control endpoint to create, list, and stop sessions. Persistent sessions get their own per-session subject for follow-ups using the standard streaming wire protocol shared with the sibling implementations:

  • nats-pi-channel — PI extension for interactive PI sessions
  • nats-claude-channel — Claude Code MCP channel
  • nats-channel (OpenClaw) — OpenClaw NATS channel

Quick start

No install, no setup files — just Node.js ≥20.6 and the nats CLI. You'll be streaming agent output over NATS in under a minute.

Prerequisite: An API key in ~/.pi/agent/auth.json. Create it directly — PI doesn't need to be installed:

mkdir -p ~/.pi/agent && cat > ~/.pi/agent/auth.json <<'EOF'
{"anthropic": {"type": "api_key", "key": "sk-ant-YOUR-KEY-HERE"}}
EOF

Swap anthropic for any other provider (openrouter, openai, google, …). If you already have PI installed locally, pi /login populates this file for you automatically.

1. Run the bridge

In one terminal:

npx @m64/nats-pi-bridge

That's it. It connects to demo.nats.io by default and registers a pi-exec NATS microservice with a control endpoint on agents.pi-exec.$USER:

pi-exec: connecting to demo.nats.io (context: default)
pi-exec: connected
pi-exec: control registered on agents.pi-exec.yourname

2. Fire a one-shot prompt

In a second terminal:

nats --server demo.nats.io req agents.pi-exec.$USER \
  '{"sessionMode":"run","body":"Write a haiku about NATS","cwd":"/tmp"}' \
  --wait-for-empty --timeout 60s

The agent's reply streams back chunk-by-chunk and an empty message closes the stream. The session is disposed the moment it's done — fire and forget.

3. Keep a session alive across prompts

Now create a persistent session, prove it remembers context with a follow-up, then stop it cleanly:

# Start the session + run the initial prompt
nats --server demo.nats.io req agents.pi-exec.$USER \
  '{"sessionMode":"session","body":"Pick a random 3-digit number and remember it","cwd":"/tmp","sessionId":"demo"}' \
  --wait-for-empty --timeout 120s

# See it in the live session list
nats --server demo.nats.io req agents.pi-exec.$USER \
  '{"sessionMode":"list"}' --timeout 5s

# Follow-up on the per-session subject — proves the session remembers
nats --server demo.nats.io req agents.pi-exec.$USER.demo \
  "What number did you pick?" --wait-for-empty --timeout 60s

# Stop it cleanly — the per-session instance disappears from `nats micro list`
nats --server demo.nats.io req agents.pi-exec.$USER \
  '{"sessionMode":"stop","sessionId":"demo"}' --timeout 5s

Ctrl-C the bridge when you're done. Every active session is disposed during graceful shutdown.

Architecture

The bridge runs as a single Node.js process. The pi-exec service has:

  • Control instance (permanent): agents.pi-exec.<owner> — control plane
  • Per-session instances (dynamic): agents.pi-exec.<owner>.<sessionId> — data plane

Each session is its own NATS microservice instance (the same pattern that nats-pi-channel uses across processes — but here within one process). On stop, the session's instance is removed cleanly via service.stop().

nats micro list
→ pi-exec │ 0.0.4 │ ABC123 │ control
          │       │ DEF456 │ worker-1 — /home/mario/code/nats-zig
          │       │ GHI789 │ sid-gpt — /home/mario/code/sid-gpt

NATS server

The Quick Start uses demo.nats.io because it's zero-setup, but you'll usually want your own server. The bridge picks the server from a NATS CLI context named via the NATS_CONTEXT environment variable.

Localhost

# 1. One-time: create a context pointing at your local NATS
nats context save local --server nats://localhost:4222

# 2. Run the bridge against it
NATS_CONTEXT=local npx @m64/nats-pi-bridge
pi-exec: connecting to nats://localhost:4222 (context: local)
pi-exec: connected
pi-exec: control registered on agents.pi-exec.m64

Don't have a local NATS server yet? The fastest way:

docker run --rm -p 4222:4222 nats:latest

Or grab a binary from the nats-server releases.

Make a context the default

To avoid setting NATS_CONTEXT on every invocation, drop a one-line config file:

mkdir -p ~/.pi-exec
echo '{"context":"local"}' > ~/.pi-exec/config.json

After that, plain npx @m64/nats-pi-bridge connects to your chosen server.

Authenticated and remote servers

The same context format works for any NATS auth scheme — credentials files, NKeys, JWTs, TLS, user/password. Anything nats context save can store, the bridge can use:

nats context save prod \
  --server nats://nats.example.com:4222 \
  --creds ~/.nkeys/creds/synadia/MyAccount/me.creds

NATS_CONTEXT=prod npx @m64/nats-pi-bridge

Context files live at ~/.config/nats/context/<name>.json. See the NATS CLI context docs for the full list of supported fields.

Install

# Run without installing (what the Quick Start shows)
npx @m64/nats-pi-bridge

# Or install globally
npm install -g @m64/nats-pi-bridge
nats-pi-bridge

Requires Node.js ≥20.6 (pulled in via the PI SDK's engine requirement).

Development

git clone <this repo>
cd nats-pi-bridge
npm install

# Run from TypeScript source (tsx)
npm start

# Watch mode
npm run dev

# Build the Node-compatible bundle into dist/
npm run build
node dist/server.js

Configuration

Optional config file: ~/.pi-exec/config.json

{
  "context": "my-nats-context",
  "defaultModel": "anthropic/claude-sonnet-4-5",
  "defaultThinkingLevel": "off",
  "defaultMaxLifetime": 1800
}

Environment overrides:

| Var | Description | |---|---| | NATS_CONTEXT | NATS CLI context name (looks for ~/.config/nats/context/<name>.json). If unset, connects to demo.nats.io. | | PI_EXEC_DEFAULT_MODEL | Default model spec, e.g. anthropic/claude-sonnet-4-5 | | PI_EXEC_DEFAULT_MAX_LIFETIME | Default max session lifetime in seconds |

PI credentials are read from ~/.pi/agent/auth.json (the standard PI agent location). Create the file directly — see the Quick Start prerequisite for the one-liner. If you have PI installed locally, pi /login populates it automatically.

Control protocol

Send structured JSON to agents.pi-exec.<owner>:

type ControlRequest = {
  sessionMode: "run" | "session" | "stop" | "list";
  body?: string;          // prompt text (run/session)
  cwd?: string;           // working directory (run/session) — resolved to absolute
  from?: string;          // caller identity (optional)
  sessionId?: string;     // REQUIRED for session and stop modes
  model?: string;         // e.g. "anthropic/claude-sonnet-4-5"
  thinkingLevel?: string; // off|minimal|low|medium|high|xhigh
  maxLifetime?: number;   // seconds, 0 = no expiry
};

| sessionMode | Required fields | Behaviour | |---|---|---| | run | body, cwd | Ephemeral: create session, prompt, stream chunks, dispose | | session | body, cwd, sessionId | Persistent: create session + per-session NATS instance, prompt initial, stream chunks | | stop | sessionId | Dispose session, remove instance | | list | — | Return all active sessions as JSON |

Per-session prompt subject (agents.pi-exec.<owner>.<sessionId>) accepts the standard wire protocol: plain text or {from, body} JSON, streaming chunks back, empty payload terminates the stream.

Testing

Start the bridge:

npm start          # from source
# or
node dist/server.js   # from the built bundle

In another terminal:

# Discoverability
nats micro list
nats micro info pi-exec

# Single-shot run
nats req agents.pi-exec.$USER \
  '{"sessionMode":"run","body":"What files are here?","cwd":"/tmp"}' \
  --wait-for-empty --timeout 120s

# Create persistent session
nats req agents.pi-exec.$USER \
  '{"sessionMode":"session","body":"List the project structure","cwd":"/path/to/project","sessionId":"worker-1"}' \
  --wait-for-empty --timeout 120s

# Follow-up via per-session subject
nats req agents.pi-exec.$USER.worker-1 "Now fix the failing tests" \
  --wait-for-empty --timeout 120s

# List sessions
nats req agents.pi-exec.$USER '{"sessionMode":"list"}' --timeout 5s

# Inspect a session
nats req agents.pi-exec.$USER.worker-1.inspect "" --timeout 5s

# Stop a session
nats req agents.pi-exec.$USER \
  '{"sessionMode":"stop","sessionId":"worker-1"}' --timeout 5s

Error responses

All error responses follow {"error": "<code>", ...context}. Examples:

{"error":"invalid_json", "message":"..."}
{"error":"invalid_request", "message":"sessionMode required"}
{"error":"invalid_sessionMode", "sessionMode":"explode"}
{"error":"missing_fields", "required":["body","cwd","sessionId"]}
{"error":"invalid_sessionId", "sessionId":"...", "message":"..."}
{"error":"session_exists", "sessionId":"...", "subject":"...", "message":"..."}
{"error":"session_create_failed", "message":"..."}
{"error":"not_found", "sessionId":"..."}
{"error":"shutting_down"}

Streaming modes (run, initial session prompt) emit text chunks + an empty terminator instead of a JSON wrapper. Errors during streaming are emitted as error: <message> text chunks followed by the empty terminator.