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

@mayurathavale18/agent-bridge

v0.3.1

Published

One event contract, many agent harnesses, many chat channels. Drive Command Code, Claude Code, Codex, Pi or your own agent from WhatsApp.

Readme

agent-bridge

WhatsApp now supports direct model/harness commands, named threads and file exchange. See chat controls and private context imports for commands, export import instructions and clarification transport limits.

CI License: MIT Node Runtime dependencies

One event contract. Many agent harnesses. Many chat channels.

A harness-agnostic bridge for driving coding agents (Command Code, Claude Code, Codex, Pi, Hermes, or your own) from any chat medium — starting with WhatsApp. The bridge does not implement an agent loop; it normalizes whatever a harness emits into one event language and lets channels render it.

Technical WhatsApp self-chat workflow: prompt your agent, approve a command, and receive a labelled reply

Technical cmd workflow. Accepted prompts receive a 👾 reaction; agent replies name the harness. Chat approvals require the approval gate to be enabled.

WhatsApp ──► OpenWA ──webhook──►  ┌──────────────────────────────┐        ┌─ cmd
                                  │           BRIDGE             │        ├─ claude
   CLI ─────────────────────────► │  normalize · route · approve │ ─────► ├─ codex
                                  │  queue · session · sandbox    │        ├─ http (any lang)
                                  └──────────────────────────────┘        └─ native plugin

The contract lives in docs/harness-spec.md.

Motivation

The agent ecosystem is fragmenting in a way that works against the people using it. I run Command Code daily, Hermes for WhatsApp, Codex for some things — and each one arrives with its own transport, its own session model, and its own assumptions about being driven from a terminal. None of them are reachable from my phone, which is where half my actual thinking happens.

Writing a WhatsApp integration per agent is a losing game: every integration re-solves the same problems — webhook verification, duplicate suppression, reply chunking, keeping the agent's own messages from re-triggering it — and none of it is the interesting part. The interesting part is the contract: if every harness normalizes to one event language, a channel written once works with every agent, past and future.

The second motivation is safety. A chat message is remote code execution with casual UX: it arrives while you're away, in a medium designed for quick replies. The approval transport here exists because I wanted to be asked, in the same chat, before an agent touched something destructive — and I wanted the failure mode of an unanswered approval to be silence, not a default yes.

The third is ownership. Agents are only as private as the credentials they hold, so this bridge is built to deploy onto my own cluster, next to my own data, with no relay in the middle.

Status

The cmd adapter, WhatsApp channel, config dashboard, queue, session routing and chat approval transport are implemented. The Contabo k3s deployment has been verified with live model replies and a shell request that paused for approval and respected a denial.

| Piece | State | | --- | --- | | Normalized event model (AgentEvent) | done | | AgentRunner interface + capabilities | done | | harness.json manifest + validation | done | | cmd adapter (Command Code NDJSON) | done, verified live | | http adapter (generic wire) | done | | mock adapter (deterministic, no cost) | done | | CLI channel | done | | OpenWA / WhatsApp channel | done | | Config dashboard (schema-driven) | done | | Approval transport over chat | done | | Per-chat queue (single-flight) | done | | Session routing (resume across messages) | done — persists across restarts |

One chat, different harnesses and models

Technical switch from Hermes to Command Code while retaining the same WhatsApp self-chat and OpenWA session

Switching Hermes ↔ Command Code changes the active OpenWA webhook on the server. Only one receives new messages; each harness retains its own history. Use the Contabo selector commands for this deployment.

Technical Hermes model selection using @me /model in WhatsApp self-chat

With Hermes selected, send @me /model to see available models, then @me /model <model-id> with an actual provider model ID. Availability and cost depend on your provider. For the cmd bridge, change its model in the dashboard or AGENT_BRIDGE_CMD__MODEL, then restart. Harness switching is server-side; there is no /harness chat command.

Install

Start with the minimal setup: one authenticated CLI and Node.js. Codex (--harness codex) and Claude Code (--harness claude-code) support streamed progress and session resume. Their native permission policies apply; chat approval round-trips currently belong to cmd. See contributing for development.

From npm (ships a compiled dist/, so any Node ≥ 22.6 works):

npm install -g @mayurathavale18/agent-bridge
agent-bridge --list
agent-bridge-serve          # the WhatsApp channel server

Or straight from source — no build step, Node ≥ 22.6 strips the types:

npm install          # only for typecheck; running needs no deps
node src/index.ts --list

cmd runs in headless mode, which blocks file writes and shell commands by default. Pass --permission-mode accept-edits (or yolo) only inside a sandboxed workspace.

On Windows the installed CLI is a .cmd shim, which cannot be executed without a shell — and the bridge deliberately never uses a shell, because the prompt is untrusted input. Point the adapter at the package's JavaScript entry instead:

node src/index.ts --harness cmd \
  --config binary="$(npm root -g)/command-code/dist/index.mjs" \
  "summarize this repo"

The adapter runs any .js/.mjs/.cjs config value through the current Node, so this works identically on every platform.

Run it from source

npm run list         # show harnesses and their capabilities
npm run demo         # run the mock harness (no model, no cost)

# a real harness
node src/index.ts --harness cmd --workspace . --model claude-sonnet-5 "summarize this repo"

# any HTTP harness
node src/index.ts --harness http --url http://localhost:8787 "do the thing"

# load a plugin from a manifest
node src/index.ts --manifest examples/harness.cmd.json "say hi"

npm run typecheck
npm test

Drive it from WhatsApp

Start the channel, then point an OpenWA webhook at it (message.received and message.sent):

OPENWA_API_KEY=owa_k1_... \
OPENWA_WEBHOOK_SECRET=... \
AGENT_BRIDGE_HARNESS=cmd \
AGENT_BRIDGE_CMD__MODEL=gpt-6-luna \
npm run serve

| Env | Meaning | | --- | --- | | OPENWA_API_KEY | required — the gateway API key | | OPENWA_BASE_URL | default http://127.0.0.1:2785 | | OPENWA_WEBHOOK_SECRET | HMAC secret; unset disables signature verification (warned at boot) | | OPENWA_SESSION_ID | pin the sending session instead of trusting the payload | | AGENT_BRIDGE_HARNESS | pins the active harness (cmd default), overriding the dashboard | | AGENT_BRIDGE_WORKSPACE | working directory for runs (default cwd) | | AGENT_BRIDGE_CONFIG_FILE | JSON file the dashboard writes (active harness + per-harness values) | | AGENT_BRIDGE_SESSION_FILE | JSON file persisting chat → session, so a restart keeps the thread | | AGENT_BRIDGE_<ID>__<KEY> | pins one harness setting, e.g. AGENT_BRIDGE_CMD__MODEL=gpt-6-luna | | AGENT_BRIDGE_APPROVAL_TIMEOUT_MS | how long to wait for an approval reply (default 120000) | | WA_PORT / WA_HOST | default 8788 / 127.0.0.1 | | DASHBOARD_PORT / DASHBOARD_HOST | default 8789 / 127.0.0.1 |

Then message yourself in WhatsApp, tagging your own number (or writing @me):

@me list the repos in this workspace

The bridge replies in the self-chat with a working... message that it edits as tools run, then replaces with the answer.

Four things keep this safe, all enforced in src/channels/whatsapp/:

  1. Signature — every webhook must carry a valid X-OpenWA-Signature (HMAC-SHA256 over the raw body).
  2. Idempotency — OpenWA retries; the idempotencyKey is deduped so a retry cannot run twice.
  3. Self-chat only — sent by you, not a group, with both endpoints matching your configured self identities. Phone JIDs and LIDs can differ; configure both in OPENWA_SELF_JID.
  4. Mention-gated + echo guard — a run needs a real self-mention (or @me); replies the bridge posts carry neither, so it can never trigger itself.

Approvals from the chat

When a harness wants to do something destructive it emits approval_request; the run parks and the chat message becomes:

approval needed: Run "git push --force" on main?
reply "yes" to approve, "no" to deny, or "cancel" to stop the run

Your reply is matched to the waiting run, delivered through AgentRunner.respondApproval, and the run continues. Three deliberate behaviours, each covered by a test:

  • Ambiguous replies never decide. Anything unrecognized re-prompts and the run stays parked — guessing on an approval is exactly how an unintended command runs.
  • Unanswered approvals deny. After AGENT_BRIDGE_APPROVAL_TIMEOUT_MS the approval resolves to deny, so a forgotten prompt can never become a silent yes.
  • cancel denies and aborts — the harness receives the deny and its AbortSignal fires, so a real harness kills the running child process.

How it works for cmd (approvals: true, or the dashboard's Gate tools behind chat approval): Command Code's PreToolUse hook is the gate. The adapter installs one into the workspace's .commandcode/settings.json for the run — merging with whatever is already there and restoring the exact previous bytes afterwards — points it at a loopback callback server, and each shell/write/edit call arrives in the chat as SHELL: rm -rf build and waits for your answer. The hook fails closed: if the bridge is unreachable it denies rather than allowing.

There are two independent gates, and this is the subtle part: headless mode denies shell/write/edit outright regardless of hooks, so without lifting that, every "approve" would silently do nothing. The adapter therefore applies --yolo only after the hook is installed successfully — if the hook cannot be written, the blanket denial stays in force.

Any other harness can implement the same round-trip by implementing respondApproval (capabilities().approvals === true); see the contract in docs/harness-spec.md.

Conversation continuity

A chat keeps its context. Each run reports a session id, which is stored per chat and handed back on the next message (cmd --resume <id>), so a second message continues the first:

you: @me remember the number 42
you: @me what number did I ask you to remember?      → 42
  • new (or /new, reset) forgets the chat's session so the next message starts clean.
  • session (or /session, status) reports the current session id.
  • AGENT_BRIDGE_SESSION_FILE persists the map, so a restart does not lose the thread.

The session id is announced once, when it begins — after that, continuity is silent. A harness whose capabilities().resume is false is never handed a session id; every message is a fresh run.

The config dashboard

Deployed bridge dashboard with environment-pinned settings

For the Contabo deployment, see dashboard tunnel access.

The bridge also serves a schema-driven config UI (default http://127.0.0.1:8789):

AGENT_BRIDGE_CONFIG_FILE=./data/config.json npm run serve
# open http://127.0.0.1:8789

Pick a harness and its form is generated from that manifest's config JSON Schema — no dashboard code knows about models, binaries or timeouts. Add a property to a manifest and a control appears.

Settings resolve in one order: manifest defaults < what you saved < environment, so a container or CI can pin a value regardless of the UI. When the environment pins a key the field is badged set by env — a saved value that silently loses is the most confusing failure mode of this design, so it is surfaced rather than hidden.

Harness and value changes apply on restart (the running runner is built once at boot); the header reads changed — restart to apply while something is waiting.

A field left blank is saved as unset, not as an empty value — an untouched dropdown shows unset and never pins a choice you did not make. Save writes the whole visible form, so a manifest default you never touched is stored alongside your edit; that pins it if the plugin later changes its own default. Sending only changed fields (merge instead of replace) is the obvious refinement.

The dashboard is unauthenticated and bound to loopback. It exposes configuration — binding it anywhere else is an explicit decision.

Layout

src/core/                events.ts · runner.ts · manifest.ts · registry.ts · loader.ts · ndjson.ts · async-queue.ts · session-store.ts · config-store.ts · config-schema.ts
src/harnesses/           cmd.ts · http.ts · mock.ts · cmd-hook.ts · catalog.ts   (adapters)
src/harnesses/hooks/     cmd-approval-hook.mjs                      (PreToolUse gate)
src/dashboard/           server.ts · page.ts                        (schema-driven config UI)
src/channels/            cli.ts                                (channel)
src/channels/whatsapp/   channel.ts · client.ts · trigger.ts · renderer.ts · signature.ts · approval.ts · commands.ts · types.ts
src/index.ts             one-shot CLI
src/serve.ts             WhatsApp channel server
docs/                    harness-spec.md · hermes-integration.md   (contract · Hermes recon)
examples/                harness.cmd.json · harness.http.json

Roadmap

  1. Hermes channel plugin — recon done: see docs/hermes-integration.md. Hermes already ships WhatsApp (Baileys + Cloud API), so this is not a gap in the usual sense — but a number already paired to OpenWA cannot also be used by Hermes' built-in adapter. The finding is that this ships as a standalone Python plugin repo (zero core changes), and that most of this bridge is redundant once Hermes is the target: the transferable artifact is the OpenWA transport, not the bridge.

Design rules

  • Model choice belongs to the harness, never the bridge. The bridge passes --model/--config through and lets each harness own it.
  • The bridge is model- and vendor-agnostic: no model ids in core code.
  • Channels never import harnesses; harnesses never import channels. Everything meets in the normalized event stream.
  • Anything talking to a chat channel runs isolated, with capped egress and approval gates.

Contributing

npm install --include=dev   # dev tooling only; running needs no deps
npm run typecheck
npm test

CI runs typecheck and the full suite on Node 22 and 24. Before opening a PR, please read docs/harness-spec.md — most changes are either a new adapter (implement AgentRunner), a new channel (consume AgentEvent), or an additive evolution of the contract (additive-only within a major version; consumers MUST ignore unknown event types).

Security-sensitive changes (anything touching the webhook path, the approval transport, or how an untrusted prompt reaches a harness) should say so in the PR description.

License

MIT.