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

opencode-huddle

v0.2.5

Published

Huddle v2 — share a live OpenCode session with friends through the TUI or a temporary browser link.

Readme

OpenCode Huddle

Huddle v2 — share a live OpenCode session with friends through the TUI or a temporary browser link.

A huddle lets a host invite participants to watch the current OpenCode session's transcript and submit messages to it. Huddles run as an OpenCode plugin inside the host's process, open a local HTTP + WebSocket server, and expose it through a temporary Cloudflare Quick Tunnel.

Two participant modes exist:

  • TUI peers (default, v2) — other OpenCode CLI users on their own machines. They join with a single huddle join <url> <password> command and are admitted only after the host approves them.
  • Web guests (v1, kept) — anyone with a browser and the invite link. They join https://…/h/<room>#token=… and enter the join password.

Warning — full agent access. Anyone admitted to the huddle gets access to the host's OpenCode agent. Their messages are processed by the host's agent with all of the host's permissions — including running tools and editing files. Only share an invite with people you trust, and only from a machine whose agent you are comfortable letting them drive.

Install

The plugin ships two cooperating halves, and each loads from its own OpenCode config file:

  • Server half (package root export, opencode-huddle) — runs the huddle HTTP + WebSocket server, owns the huddle lifecycle, and publishes the server result toasts. It loads from the plugin array of opencode.json.
  • TUI half (opencode-huddle/tui export) — registers the /huddle* slash commands and the TUI statusline/route. It loads from the plugin array of tui.json.

Both halves must be configured. A single config entry loads only the TUI half: the commands appear in the slash menu and Control-P, but nothing executes because the server half never runs — dispatch is a silent no-op.

Global configuration (applies to every workspace; edit both files):

~/.config/opencode/opencode.json:

{
  "plugin": ["opencode-huddle"]
}

~/.config/opencode/tui.json:

{
  "plugin": ["opencode-huddle"]
}

To pin a specific release, use "opencode-huddle@<version>" in both files. OpenCode resolves the package from the npm registry on the next launch. Requires Bun as the runtime and OpenCode >= 1.18.11 (the plugin is pinned against @opencode-ai/plugin / @opencode-ai/sdk 1.18.11).

Usage

The plugin registers transcript-free TUI actions (OpenCode 1.18.11's supported slash/command-palette API). They run instantly, produce no model turn, and never put invite details in the transcript.

| Command | Action | | ----------------------- | ------------------------------------------------------------------------------- | | /huddle | Start a TUI-mode huddle for the current session (peers join on other machines) | | /huddle web (huddle-web) | Start a browser-mode huddle for web guests | | /huddle-status | Show the active huddle's invite, participant count, busy state, queue length | | /huddle-close | Stop the huddle, invalidate the invite, and disconnect all participants | | /huddle-approve | Approve a TUI peer waiting for host approval | | /huddle-deny | Deny a TUI peer waiting for host approval | | /huddle-join | On a peer's machine: paste the host's huddle join <url> <password> invite |

Single-token slash names (huddle-web, huddle-status, huddle-close, huddle-approve, huddle-deny, huddle-join) exist because the 1.18.11 TUI slash matcher only matches space-free command tokens; /huddle web is the user-facing name for the huddle-web token. Everything is also discoverable through the command palette (Ctrl+P).

There is no pause/resume. To stop sharing, run /huddle-close; to share again, start a new huddle.

Running a command

The commands appear in both the / slash menu and the command palette (Ctrl+P). opencode 1.18.x cannot separate the two surfaces: they draw from the same command namespace, and hidden: true hides a command from both. Commands showing up in both places is by design in 1.18.x.

/huddle and /huddle start are the same command — the slash name is huddle, with alias start.

A command runs only when selected from the slash popover or Control-P. Typing /huddle (or /huddle start) and pressing Enter without selecting the popover entry does not run the command — opencode submits the typed text as an ordinary chat message. Always pick the command entry first, then press Enter.

Feedback model

Every command dispatch produces two toasts:

  1. An instant optimistic toast — e.g. "Starting huddle…" — shown the moment the command is selected. It is produced entirely by the TUI half and appears even if the server half never receives the command.
  2. A server result toast — e.g. "Huddle started" — once the server half handles the round-trip over the OpenCode event bus and publishes the result back.

If the optimistic toast appears but the result toast never does, the server half is not loaded — see Install.

Starting a huddle

Running /huddle (or /huddle web for a browser-mode huddle):

  1. Resolves the session to share — the current session, or the most recently updated session for the directory.
  2. Starts the local huddle server (bound to 127.0.0.1) and a Cloudflare Quick Tunnel that gives it a temporary public *.trycloudflare.com URL.
  3. Generates an invite token and a random join password.
  4. Copies the invite to the host's clipboard (pbcopy on macOS; wl-copy/xclip/xsel on Linux) and shows it in a toast. TUI-mode huddles copy a single join command — huddle join <url> <password> — while web-mode huddles copy the invite URL plus password. If the clipboard copy fails, the invite is shown in the toast instead.

The invite and password are delivered only through the clipboard and a host toast. The huddle actions never consume a model turn and the credentials are never sent to the model, the transcript, or the browser.

First start / restart

The plugin runs two cooperating parts: the server plugin (package root export, loaded from opencode.json) owns the huddle lifecycle and the TUI plugin (opencode-huddle/tui export, loaded from tui.json) registers the /huddle* actions. The TUI actions are routed to the connected server instance over OpenCode's supported event bus: the TUI publishes a tui.command.execute event containing a strict huddle:<action>[:<sessionID>[:web]] command string, and the server plugin's event hook parses only that namespace and runs the huddle lifecycle there. The server instance publishes the result toast back over the same channel (tui.toast.show). No cross-process filesystem state or extra servers are involved — the channel is exactly the one the TUI and server plugins already share, and it targets the correct instance/workspace by construction. The huddle host must be an OpenCode session running in the same directory.

Joining a huddle (TUI peers)

A peer joins from their own OpenCode TUI on their machine:

  1. The peer installs the same opencode-huddle plugin (the peer's huddle join command carries its plugin and OpenCode versions; a join is rejected before any data if they do not match the host's expectations — see below).
  2. The peer runs /huddle-join and pastes the host's join command: huddle join https://…trycloudflare.com/h/<room>#token=… <password>. The URL and password are both required; neither alone is sufficient.
  3. The host sees a Peer join request toast and a statusline pending approval marker; the /huddle-status toast shows participants, busy state, and queue length (pending peers are not counted there). The host runs /huddle-approve (or /huddle-deny) to admit or reject the peer.
  4. Once approved, the peer sees the huddle route: the projected transcript, host session title, run status, participants, and queue position, and can send messages, interrupt the run, approve/deny host tool permission requests, and run a small whitelist of host commands.

The peer must match the host's plugin version exactly and run OpenCode >= 1.18.11; any mismatch is rejected with an explanatory message before any transcript data is sent. Pending approvals expire after HUDDLE_PEER_APPROVAL_TTL_MS.

Since 0.2.3, connection failures surface within seconds instead of hanging indefinitely: a stale or dead invite produces an actionable error toast within about 10 seconds, a mid-session drop reconnects automatically with up to 10 attempts, and hosts evict silent peers (roughly 30s without a frame or keepalive pong) and clean up their registries. A reconnecting peer lands back in the approval queue, so the host should approve it again.

Troubleshooting

A peer never waits forever — every pre-live phase is watchdog-bounded, and each expiry names the cause:

  • Connecting: 10s window (HUDDLE_PEER_CONNECT_TIMEOUT_MS) — stale invite → "Couldn't reach the host — the invite may be stale. Ask the host to copy a current join command."
  • Waiting for approval: 60s window (HUDDLE_PEER_APPROVAL_TTL_MS, matching the host's approval TTL) — the host never responded → "The host didn't respond to your join request in time. Ask the host to approve the join request."
  • Post-approval (waiting for the session snapshot) — terminates if the snapshot never arrives; the host approved but the snapshot wasn't delivered → "The host approved your join but the session snapshot never arrived. The host session may be busy — ask the host to close and restart the huddle."

"Connecting to the host…" / "Waiting for the host to approve your join…" never resolves. The invite is stale — the host's tunnel restarted, so the old *.trycloudflare.com hostname is dead (it no longer has DNS). Since 0.2.4 the peer rejects itself within the watchdog window with the stale-invite message above, and the host gets a Peer approval warning toast instead of a silent success. Fix: on the host run /huddle once more, copy the fresh huddle join <url> <password> command, and have the peer run /huddle-join again with it.

The peer is stuck on "Connecting to the host…" after the host approved. Checklist:

  1. Both sessions run the same plugin version — 0.2.5 required on host and peer; a 0.2.5 host rejects 0.2.3 peers loudly by design instead of hanging.
  2. Restart both sessions to load the new plugin — OpenCode caches plugins in ~/.cache/opencode/packages/, so an already-running session keeps its old version until restarted.
  3. Host shows "The join snapshot timed out." — the host session may be busy; a stalled transcript fetch now times out loudly instead of hanging. Retry the join, or close and restart the huddle.

Host-side loud approval failures. Approval always settles with a visible outcome: "The join snapshot timed out." (the transcript fetch stalled — the host session may be busy), "Couldn't prepare the join snapshot for the peer." (preparation failed), or "The peer left before the join could be delivered." / "Join request no longer pending (the peer disconnected)" (the peer's socket was already gone — it disconnected or was evicted as stale). An evicted peer auto-reconnects and reappears as a new pending request the host can approve; if it does not, re-invite it.

Joining a huddle (web guests)

A guest opens the invite URL in a browser only. The URL embeds the invite token in its fragment (/h/#token=...). The guest must also enter the join password from the invite. Both are required; neither alone is sufficient.

Closing a huddle

/huddle-close (or plugin teardown, a failed tunnel, or a deleted session):

  • Invalidates the invite token and password.
  • Closes every participant WebSocket with close code 1001; TUI peers and web guests see a "Session ended" screen.
  • Stops the local server and the cloudflared tunnel.
  • The invite can never be reused. There is no persistence: huddles live in memory only and disappear when the hosting OpenCode process stops.

Statusline

The bottom bar shows huddle state: ● Huddle live — N guests — idle — <title> while a huddle is active (the run status reads idle or responding, and N pending approval is appended when TUI peers await the host's decision), and Huddle off otherwise.

It reflects reality on both roles: the host reads its state file (polled every 2s, treated as stale after 60s) and the peer reads its live connection — phase live shows the live huddle, while rejected/closed/disconnected shows Huddle off. Polling is continuous on a fixed interval, so the statusline can no longer get permanently stuck on Huddle off; toast markers ("Huddle started"/"Huddle closed"/"Huddle failed") only accelerate updates.

What TUI peers see

Approved peers receive a snapshot on join containing the full projected history of the shared session, then live events as the agent streams. The peer projection is deliberately limited:

  • Included: text parts, reasoning summaries (length-capped, no raw reasoning), tool summaries (tool name + length-capped input/output summaries, never full tool I/O), participant list, queue positions, run status, and a reduced assistant error (name/message/isRetryable).
  • Excluded: raw reasoning, file contents, session paths, tokens, costs, and full error objects.

Peers can send attributed text messages (FIFO-ordered with the host's own turns), interrupt the run, respond to host tool permission requests, and run a small whitelist of host commands (interrupt, compact, new session, status) — anything else is rejected with "command not allowed".

What web guests see

Web guests receive a snapshot on join containing the full projected history of the shared session, then live events as the agent streams. The projection is deliberately limited:

  • Included: text parts, file placeholders (filename + MIME type only), a reduced assistant error (name/message/isRetryable), participant list, queue positions, and run status.
  • Excluded: tool calls, reasoning parts, file contents, session paths, tokens, costs, and full error objects.

Web guests can read the transcript, see live streaming updates, see the queue position and agent busy state, and send text messages. They cannot run tools or shell commands directly, browse the filesystem, access environment variables, switch sessions, or change models or configuration. Their messages are submitted to the host's session as ordinary user messages (tagged [Name via Huddle] with attribution metadata), so they are subject to the same OpenCode permission model as any user message.

Projection safety caps (not configurable): MAX_PROJECTED_TEXT_LENGTH (1,000,000 characters per part) and MAX_TRANSCRIPT_MESSAGES (10,000 messages per snapshot).

Limits

| Limit | Default | Env var | | --------------------------------------- | ------- | -------------------------------- | | Participants per huddle | 3 | HUDDLE_PARTICIPANT_LIMIT | | Concurrent WebSocket connections | 8 | HUDDLE_MAX_CONNECTIONS | | Queued messages (oldest dropped) | 20 | HUDDLE_MAX_QUEUE | | Message length (characters) | 4096 | HUDDLE_MAX_MESSAGE_LENGTH | | Sends per participant per minute | 10 | HUDDLE_SEND_RATE_PER_PARTICIPANT | | Sends per huddle per minute | 30 | HUDDLE_SEND_RATE_HUDDLE | | Join password attempts per IP per 60s | 5 | HUDDLE_PASSWORD_ATTEMPTS | | Tunnel ready timeout | 30000ms | HUDDLE_TUNNEL_READY_TIMEOUT_MS | | Tunnel DNS propagation wait | 120000ms| HUDDLE_TUNNEL_DNS_WAIT_MS | | Peer approval TTL | 60000ms | HUDDLE_PEER_APPROVAL_TTL_MS | | Peer connect timeout | 10000ms | HUDDLE_PEER_CONNECT_TIMEOUT_MS | | Host keepalive ping interval | 10000ms | HUDDLE_PEER_PING_INTERVAL_MS | | Peer idle (stale-socket) eviction | 30000ms | HUDDLE_PEER_IDLE_TIMEOUT_MS | | Peer command length (characters) | 512 | HUDDLE_PEER_MAX_COMMAND_LENGTH | | Peer tool summary length (characters) | 512 | HUDDLE_PEER_TOOL_SUMMARY_LENGTH | | Peer reasoning summary length (chars) | 128 | HUDDLE_PEER_REASONING_SUMMARY_LENGTH |

Env vars are read at plugin load. Values must be digits-only positive integers (>= 1); an invalid value throws loudly and prevents the plugin from loading rather than silently falling back. The server's effective limits are authoritative over the browser's baked-in defaults. If you override these values, keep HUDDLE_PEER_IDLE_TIMEOUT_MS at least HUDDLE_PEER_PING_INTERVAL_MS, or healthy peers can be falsely evicted before they answer a keepalive ping.

Known limitations

  • Commands appear in both the / slash menu and Control-P (opencode 1.18.x). opencode cannot separate the slash menu from the Control-P palette — both draw from the same command namespace, and hidden: true hides a command from both. This is by design in 1.18.x; see Running a command.
  • A command runs only when selected. /huddle and /huddle start are aliases of the same command; pressing Enter on the typed text without a selection submits it as an ordinary chat message instead of running the command.
  • Single-entry config loads only half the plugin. The server half (opencode.json) and the TUI half (tui.json) must both be configured, or the commands appear but silently do nothing. See Install and Feedback model.
  • No pause/resume, no persistence — see Closing a huddle.

Platform support

  • macOS and Linux only (x64 or arm64). No Windows.
  • Requires a Bun.serve-capable Bun runtime and network access to the npm registry and GitHub releases (for the tunnel binary).
  • No persistence: huddles are ephemeral and process-bound.

Cloudflare Quick Tunnel

On first start, the plugin needs a cloudflared binary. If cloudflared is already on PATH it is used as-is. Otherwise the plugin downloads a pinned release (CLOUDFLARED_VERSION in src/cloudflared.ts, currently 2026.7.3) from the official GitHub releases and verifies its SHA-256 against pinned checksums (CLOUDFLARED_SHA256). The verified binary is cached in ~/.cache/opencode-huddle/ (re-verified on every use); there is no background auto-update.

The tunnel is a Cloudflare Quick Tunnel: cloudflared tunnel --url http://127.0.0.1:<port> --no-autoupdate creates a temporary public URL routed through Cloudflare's edge. Notes:

  • The URL is public and network-unauthenticated; access control is entirely the app's own token + password gate, a host approval gate for TUI peers, plus a browser Origin allowlist for the WebSocket.
  • DNS-safe health gate: a fresh Quick Tunnel hostname can appear on cloudflared's stderr several seconds before the public DNS record propagates, and macOS's resolver can negative-cache that NXDOMAIN beyond the ready timeout. The plugin therefore never asks the system resolver about the fresh host. It waits for the record to appear via Cloudflare's DNS-over-HTTPS JSON API (fixed endpoint, bounded response), validates the resolved A/AAAA addresses, then performs a real HTTPS /healthz probe that connects to a validated address while keeping the URL hostname, Host header, TLS SNI, and certificate verification intact. The gate is two-phase: DNS failures get their own window (HUDDLE_TUNNEL_DNS_WAIT_MS, default 120s) and do not consume the health budget, so slow DNS propagation can no longer exhaust the ready timeout. Once the hostname resolves, the health phase is bounded by HUDDLE_TUNNEL_READY_TIMEOUT_MS (default 30s); if either window elapses the tunnel is killed and the start fails closed.
  • The tunnel is health-checked continuously. If it stops responding, the huddle fails closed: participants are disconnected and the invite is invalidated.
  • Quick Tunnel URLs are ephemeral and *.trycloudflare.com — treat them as sensitive; anyone with the URL (and the password, and host approval for TUI peers) can participate.

Updating the pinned cloudflared

  1. Bump CLOUDFLARED_VERSION in src/cloudflared.ts to the new release tag.
  2. Download the new assets for darwin-amd64, darwin-arm64, linux-amd64, and linux-arm64 from the GitHub release, and update the CLOUDFLARED_SHA256 entries with their SHA-256 hashes (cross-check against the release's cloudflared-checksums.txt).
  3. macOS checksum warning: the pinned checksum for macOS is the SHA-256 of the downloaded .tgz archive (macOS assets ship as archives), while Linux assets are raw binaries and their checksum is the hash of the binary itself. If you hash the binary extracted from the macOS archive and compare it to the pinned value, it will not match. Always verify the archive bytes. (After extraction the plugin stores a separate sidecar hash of the extracted binary for cache validation; that is not the pin.)
  4. Rebuild, run the test suite, and re-test a tunnel start.

Security

  • Invite token, join password, and full invite URLs are never logged, printed, or sent to the browser. Logs only ever contain sha256 fingerprints (12-hex prefixes) of hostnames/IDs.
  • TUI peers receive only the rich projection described above — text, capped reasoning summaries, and capped tool summaries — never raw reasoning, tool I/O, file contents, session paths, tokens, or costs. Web guests receive the browser projection — never tool calls, reasoning, file contents, session paths, tokens, or costs.
  • TUI peers are admitted through an explicit host approval gate (toast + statusline + /huddle-approve); a join is rejected before any data on plugin-version mismatch, an old OpenCode, or a missing token/password.
  • All WebSocket frames and HTTP requests are validated and rate-limited at the boundary (frame-size caps, message/queue/rate limits, password-attempt throttling, strict zod-validated protocol messages).
  • Security responses are fail-closed: CSP default-src 'none', script-src 'self', frame-ancestors 'none', Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff, and static asset path traversal protection.

Reporting a vulnerability

Report security issues at https://github.com/sebastianlungu/opencode-huddle/issues (or privately to the maintainer, the npm account that publishes this package), with a repro and the versions involved. Do not include live invite tokens, passwords, or URLs in the report.

Architecture

OpenCode server (host, macOS/Linux)
├── plugin (opencode-huddle)
│   ├── TUI actions   /huddle · /huddle web · /huddle-status · /huddle-close · /huddle-approve · /huddle-deny · /huddle-join
│   ├── control       tui.command.execute events on the OpenCode event bus (no cross-process files)
│   ├── commands      huddle lifecycle: start/status/close, peer approvals, toasts, clipboard copy
│   ├── adapter       subscribes to the host session's events, submits participant messages
│   ├── gate + queue  FIFO serialization of all human turns (one outstanding lease)
│   ├── server        local Bun.serve HTTP + WebSocket on 127.0.0.1 (peer + web admission)
│   └── tunnel        cloudflared Quick Tunnel → public https URL (DNS-safe health gate)
├── TUI peers (3 max, host-approved) — projected transcript, messages, interrupt, permission replies, whitelisted commands
└── web guests (3 max) — projected transcript, messages, queue/status

The shared session is the host's current OpenCode session (command session or most recently updated session for the directory). Participant messages are serialized into one FIFO so only one turn is ever in flight, regardless of whether it originated from the host TUI, a TUI peer, or a web guest.

Development

bun install          # install pinned deps
bun run check        # type-check (server + web + e2e configs)
bun test             # unit/integration tests
bun run build        # bundle dist/index.js + web/protocol.js + web/app.js
bun run test:e2e     # build + Playwright E2E suite (Chromium)
bun run test:e2e:install  # install Playwright browsers

All dependencies are pinned to exact versions. dist/, web/protocol.js, and web/app.js are generated artifacts; regenerate with bun run build. See AGENTS.md for repository conventions.

License

MIT — see LICENSE.