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

@agent-ops/gemini-nats-channel

v0.8.0

Published

NATS channel bridge for gemini-cli's native ACP mode — exposes a Gemini CLI session on agents.prompt.<machine>.<project>.<session>.

Readme

gemini-nats-channel

NATS channel bridge for gemini-cli's native ACP mode. Exposes a Gemini CLI session as a discoverable, addressable agent on a NATS mesh, implementing the Synadia Agent Protocol for NATS v0.3.

The bridge spawns gemini --acp as a child process and runs an ink-based terminal UI that displays inbound NATS prompts, streams Gemini responses, prompts for permission decisions, and lets the operator type local prompts that flow into the same Gemini session. The wire protocol is bit-compatible with the rest of the Synadia Agent Protocol ecosystem — any caller using @synadia-ai/agents (TypeScript) or synadia-ai-agents (Python) can discover, prompt, and stream from this bridge the same way it can talk to the reference claude-code, pi, or openclaw channels.


Prerequisites

  • Bun — the bridge runs on Bun. Install with curl -fsSL https://bun.sh/install | bash and make sure bun is on your PATH. The package ships TypeScript source directly (no compiled dist).
  • gemini-cli on PATH. Override the launch command with GEMINI_ACP_COMMAND if your binary lives elsewhere or you want extra flags (gemini --yolo --acp, etc.).
  • NATS CLI — for managing contexts and quick wire tests.
  • A NATS server reachable from the bridge (defaults to nats://127.0.0.1:4222; configure via a saved CLI context or NATS_URL).
  • A Gemini API key — set GEMINI_API_KEY or GOOGLE_API_KEY in the bridge's env. The bridge forwards both to the spawned gemini --acp child verbatim.

Quick Setup

1. Install globally (publishes are under the @agent-ops scope; for local development use bun install --global <path>):

bun add -g @agent-ops/gemini-nats-channel
# or, from a local checkout:
bun install --global /path/to/gemini-nats-channel

2. Run interactively (defaults: owner = $USER, session = the current directory's basename, NATS at nats://127.0.0.1:4222):

gemini-nats-channel

The TUI shows a header with the resolved subject + session, a streaming event log, and an input field at the bottom. Type to send a local prompt to the same Gemini session a remote caller would reach over NATS.

3. Or run headless for systemd / tmux deployments — you must declare an explicit permission policy:

gemini-nats-channel --headless --auto-approve-permissions
# or:
gemini-nats-channel --headless --deny-permissions

The bridge refuses to start in headless mode without one of these flags. There is no silent default.


Protocol compliance

This channel implements the Synadia Agent Protocol for NATS v0.3 end-to-end:

  • Registers as an agents NATS micro service (§3.1 — the bare subject-safe token).
  • Service metadata includes agent: "gemini", owner, session, and protocol_version: "0.3" (§3.2).
  • prompt endpoint declares max_payload (advertised in \d+(B|KB|MB|GB) form per §2.1) and attachments_ok: "true". Queue group "agents" (§3.3).
  • Accepts plain-text shorthand and JSON envelopes with optional base64-encoded attachments (§5.1, §5.2, §5.3). Inbound attachments split:
    • Images (png, jpg, jpeg, gif, webp) — kept in memory as base64 and forwarded as ACP image content blocks. No disk I/O.
    • Everything else — staged to <STATE_DIR>/attachments/<request_id>/<filename> and forwarded as ACP resource_link content blocks with a file:// URI. Cleaned up when the prompt completes.
  • Rejects malformed envelopes, empty payloads, oversize requests, and invalid base64 with Nats-Service-Error-Code: 400 (§9).
  • Emits typed response chunks {"type":"response","data":"..."} (§6.3) terminated by an empty headerless message (§6.5). Large responses are split into UTF-8-safe slices that each fit under the advertised max_payload.
  • Publishes heartbeats at agents.hb.<machine>.<project>.<session> with the §8.3 payload including instance_id (§8).
  • status endpoint at agents.status.<machine>.<project>.<session> replies with the same payload as a heartbeat (§8.7).
  • Permission requests from gemini-cli are surfaced according to mode:
    • TUI mode (default): interactive y / n prompt rendered in the bridge's terminal UI.
    • Headless mode: every request resolves to the policy declared via --auto-approve-permissions or --deny-permissions and logs to stderr.

v0.4 (additive — both versions served on the same registration)

This channel also advertises A2A protocol v0.4 capability via the shared @sesh-channels/sdk wrapper. The v0.3 path above is untouched — v0.4 is purely additive:

  • Service metadata adds sesh.protocol_version: "0.4" and sesh.v04_capabilities: "messages,artifacts,cards" alongside the v0.3 protocol_version: "0.3". The sesh shim reads these to decide whether to publish v2 envelopes to this adapter.
  • The prompt endpoint listens on agents.prompt.<machine>.<project>.<session>, registered via the SDK's AgentServiceOptions.extraEndpoints mechanism. machine / project / session come from $SESH_MACHINE / $SESH_PROJECT / $SESH_SESSION (sanitized for subject safety); defaults are os.hostname() and the working-directory basename if those env vars are unset. $SESH_ROLE is advertised as metadata.role, not as a subject token.
  • v2 envelopes are JSON Messages (per the A2A v0.4 design) — text parts are flattened and forwarded into the same ACP pipeline as v1, so streaming, permission prompts, and the TUI all behave identically. Response chunks stream back via the standard SDK encoder; the v2 reply terminates with an empty headerless message.
  • An L3 AgentCard is registered on agents.card.<machine>.<session>.<name> advertising one skill (gemini.code). The card is byte-identical on every request; the shim merges this L3 contribution with its L1+L2 defaults before signing and serving over HTTPS.
  • The ACP runtime is opaque to v0.4 wiring (parent plan open question #4): the SDK only touches NATS subjects + metadata, so gemini's ACP-vs-MCP architecture has no impact on v0.4 parity.

Session names

The prompt subject is agents.prompt.<machine>.<project>.<session>. Heartbeats go to agents.hb.<machine>.<project>.<session> and the status endpoint replies on agents.status.<machine>.<project>.<session>.

  • Default: sanitized basename of the working directory the bridge was launched from (e.g., my-project).
  • Canonical override: set SESH_SESSION env var — the sesh contract, populated automatically by sesh up --exec / orch-spawn. Also resolved via the .sesh/sessions/<label>.json state-walk when unset.
  • Other overrides (in precedence order): pass --session <name> on the CLI (wins over everything), set GEMINI_SESSION_NAME env (legacy, kept for back-compat), or persist sessionName in config.json.
  • Multiple sessions: if the resolved name is already taken by another gemini instance owned by the same user, the bridge auto-appends -2, -3, etc.

Discover running sessions via the protocol's discovery subjects:

nats req '$SRV.INFO.agents' '' --replies=0 --timeout=2s
nats req '$SRV.PING.agents' '' --replies=0 --timeout=2s
nats micro ls
nats micro info agents

Talking to the running bridge

Plain text prompt:

nats req agents.prompt."$SESH_MACHINE".my-project.my-session \
  --replies=0 --reply-timeout=30s --timeout=5m \
  "explain the README in three sentences"

With the @synadia-ai/agents TypeScript SDK (or its @agent-ops equivalent in this repo's vendored copy):

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 gemini = (await agents.discover()).find((a) => a.agent === "gemini");
for await (const msg of await gemini!.prompt("hello gemini")) {
  if (msg.type === "response") process.stdout.write(msg.text);
}
await agents.close();
await nc.close();

Permissions

The bridge does not see your code or files directly — gemini-cli does. When Gemini wants to invoke a tool (read a file, execute a command, etc.), it sends an ACP session/request_permission to the bridge. The bridge surfaces this differently depending on mode:

| Mode | Behavior | | --- | --- | | TUI (default — operator sits in front of the terminal) | Interactive y / n prompt in the bridge UI. y selects the first allow_once option from the ACP request (falls back to allow_always, then first option); n returns cancelled. | | --headless --auto-approve-permissions | Every request resolves to the first allow option. Use only when the harness's own auto-approve (gemini --yolo) and your environment are sufficiently sandboxed. | | --headless --deny-permissions | Every request resolves to cancelled. Suitable when you've pre-configured gemini-cli to skip the permission prompts entirely (e.g., via --yolo), so this path is just a defensive backstop. |

The bridge refuses to start in headless mode without an explicit policy flag — there is no silent default. This is deliberate: an unattended Gemini session deciding what to do without your guidance is exactly the situation worth being explicit about.


Configuration

State lives in ~/.gemini/channels/nats/ (override with GEMINI_STATE_DIR):

| File / dir | Purpose | | --- | --- | | config.json | Persisted NATS context, owner, session-name override | | attachments/<request_id>/ | Per-request staged non-image attachments; auto-cleaned on prompt completion |

NATS CLI contexts live in ~/.config/nats/context/<name>.json — listed with nats context ls.

config.json

{
  "context": "my-nats-context",
  "owner": "alice",
  "sessionName": "demo"
}

| Field | Purpose | | --- | --- | | context | NATS CLI context name passed to the bridge's connect resolver. | | owner | Default 4th subject token. Useful for headless / systemd where $USER may be nobody. CLI flag / env var override. | | sessionName | Default 5th subject token. CLI flag / env var override. |

CLI flags

| Flag | Notes | | --- | --- | | --owner <name> | Override the 4th subject token. | | --session <name> | Override the 5th subject token. | | --cwd <path> | Working directory passed to the gemini --acp child. | | --nats-url <url> | Direct NATS URL. Highest precedence. | | --nats-context <name> | Saved NATS CLI context name. | | --headless | Disable the TUI; logs to stderr. Requires one of the next two flags. | | --auto-approve-permissions | Headless: resolve every permission request as "allow". | | --deny-permissions | Headless: resolve every permission request as "deny". |

Environment variables

| Variable | Overrides | Default | | --- | --- | --- | | GEMINI_OWNER | Owner | $USER (or config owner) | | SESH_SESSION | Session name; canonical sesh contract — wins over GEMINI_SESSION_NAME. Also resolved via .sesh/sessions/<label>.json state-walk when unset. | (unset) | | GEMINI_SESSION_NAME | Legacy operator override for session name. | sanitized basename of CWD | | GEMINI_CWD | ACP child's working directory | ${TMPDIR}/gemini-agent/<session>/ | | GEMINI_ACP_COMMAND | ACP child launch command | gemini --acp | | GEMINI_STATE_DIR | Bridge state dir | ~/.gemini/channels/nats | | NATS_URL | NATS connection URL | nats://127.0.0.1:4222 | | GEMINI_API_KEY / GOOGLE_API_KEY | Forwarded to gemini --acp | (whatever you set; both honored by the harness) | | SESH_ROLE | Free-form role token (^[a-z0-9_-]+$, 1–63 chars). Identifies the function this agent plays in the swarm — e.g. implementer, verifier, spy. Surfaced as metadata.role on the NATS Micro service and as role in sesh's session manifest. | worker | | SESH_CLASS | One of active or observer. Coordination-subject routing keys on this: active agents subscribe to workers.*, observer agents subscribe to spies.*. | active |

Working directories

Three distinct concepts — don't conflate them:

| Concept | Default | Used for | | --- | --- | --- | | Bridge process CWD | wherever you launched gemini-nats-channel from | Determines the default session name (basename of this directory). | | ACP child CWD (--cwd / GEMINI_CWD) | ${TMPDIR}/gemini-agent/<session>/ | Working directory passed to gemini --acp. Where Gemini reads / writes files via its tools. | | State directory (GEMINI_STATE_DIR) | ~/.gemini/channels/nats/ | Per-user config + attachment staging. |


Troubleshooting

  • spawn gemini ENOENT — install gemini-cli (npm install -g @google/gemini-cli or equivalent) so gemini is on the bridge's PATH, or set GEMINI_ACP_COMMAND to a locally installed binary.
  • GEMINI_API_KEY required in the child's stderr — the bridge forwards GEMINI_API_KEY / GOOGLE_API_KEY from its own environment. Export one before launching the bridge.
  • No response chunks — confirm the harness spawned (look for acp-client: initialized and session ready lines on stderr). gemini-cli writes its own diagnostics to stderr; the bridge mirrors them.
  • connection refused at startup — the bridge tried to connect to NATS and got nothing. Confirm a NATS server is up (nats server ping) or point the bridge elsewhere via --nats-url / --nats-context.
  • --headless requires an explicit permission policy — pass --auto-approve-permissions or --deny-permissions. The bridge will not run headless with an implicit policy.
  • Multiple instances colliding on the same (owner, session) subject — they don't, automatically. The bridge polls $SRV.INFO.agents at startup and auto-suffixes the session name with -2, -3, … so two bridges in the same working directory get distinct subjects.

License

Apache-2.0.