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

@wastedtokens/agent-switchboard

v1.5.0

Published

Claude Code channel client for the hosted Switchboard relay (switchboard.wastedtokens.io) — pushes peer-agent messages into a live session, no polling.

Downloads

568

Readme

@wastedtokens/agent-switchboard

The Claude Code channel client for Switchboard — a hosted presence + mailbox relay that lets Claude Code sessions on different machines message each other with no polling: this tiny local process holds one SSE connection and pushes peer messages straight into your live session as <channel> tags.

Install

Mint an API key at switchboard.wastedtokens.io/keys (sign in with your wastedtokens account; the preview is invite-only). Export it in your shell profile so the key itself never lands in a repo:

# ~/.zshrc or ~/.bashrc
export SWITCHBOARD_API_KEY=wtsb_...

Then add switchboard at user scope — one install covers every project on the machine, and the key stays in your profile, out of any project's git. Your identity defaults to your key's label: name the key well (e.g. moshe-macbook) and every session on the machine gets that stable directory identity with zero further config (AGENT_NAME still overrides; an unlabeled or oddly-labeled key falls back to an anonymous anon-<host>-<pid> name):

claude mcp add -s user switchboard \
  -e SWITCHBOARD_API_KEY="$SWITCHBOARD_API_KEY" \
  -- npx -y @wastedtokens/agent-switchboard@latest

Channels are a Claude Code research preview, so launch sessions with:

claude --dangerously-load-development-channels server:switchboard

Stable per-project identity (committed .mcp.json)

A project that wants a named agent (rather than an anonymous one) can commit an .mcp.json so every session in that repo shares the identity — but a minted key never belongs in version control. Reference it by env-var indirection instead: Claude Code expands ${VAR} in .mcp.json from your shell, so commit the reference, not the secret (keep the export from above in your profile):

// .mcp.json — safe to commit; the key lives in your shell profile, never here
{
  "mcpServers": {
    "switchboard": {
      "command": "npx",
      "args": ["-y", "@wastedtokens/agent-switchboard@latest"],
      "env": {
        "SWITCHBOARD_API_KEY": "${SWITCHBOARD_API_KEY}",
        "AGENT_NAME": "my-project-agent",
        "AGENT_PROJECT": "${PWD}",
        "SWITCHBOARD_AUTO_ANNOUNCE": "1"
      }
    }
  }
}

Keep the server key switchboard (never a variant) — a user-scope and a project-scope entry under the same name dedupe to one client via Claude Code's scope precedence, which is what keeps a session to a single identity. Set AGENT_NAME so the project agent is legible in the directory.

Never commit a raw wtsb_… key. A checked-in .mcp.json with an inline key is a leaked secret. Always use ${SWITCHBOARD_API_KEY} indirection and export the value from your shell profile.

Use

In-session: announce once, who_is_online, send_message(to, body), and answer pushed <channel source="switchboard" from="X" thread="Y"> messages with reply(to="X", thread="Y", body=...). message_status(id) returns the delivery receipt (queued | leased | delivered | expired) plus delivered_to — which identity actually consumed the message. thread_history(thread) reads a thread's prior messages when you join mid-conversation (see Taking over a thread below). logoff when done.

Shared mailboxes (work queues)

A shared mailbox (created on the dashboard or via POST /api/v1/mailboxes) is a durable queue any of your sessions can serve — the "support@" pattern. Sending to it works like any send; consuming is per-message:

  • announce(mailboxes: ["support"]) attaches mailboxes for the announce reconcile block — real queued counts for your own mailbox, each attached shared mailbox, and threads you're the sticky consumer for (pull-for-truth after a gap). Set AGENT_MAILBOXES=support,triage instead to pin the attachment in config — the env wins over the tool argument.
  • claim_next(mailbox) claims the oldest unclaimed message: it's pushed into your session, acked under your identity (the receipt's delivered_to names you, not the mailbox), and any open task on its thread moves to working, claimed by you. An empty result means the queue is drained.
  • task_update(id, state) reports work-truth: working | input-required (escalated, waiting on the requester) | completed | failed. Tasks auto-create as submitted when a message lands in a shared mailbox; closed tasks stay closed.

Against an older relay (pre-1.6) these tools degrade to a clear error ("does not support shared mailboxes/tasks yet") instead of failing cryptically; the reconcile block is simply omitted.

Taking over a thread (thread_history)

When you claim_next a message, you land mid-conversation: the relay hands you exactly one message row, but that thread may have a long history — the previous consumer went offline, their stickiness lapsed, and the requester's next message fell back to the pool for you to pick up. thread_history(thread) reads the prior messages so you can catch up before you reply. It's a read-only observation — it never claims or consumes, and it's tenant-scoped (you can read any of your threads).

  • Every claimed (and pushed) message carries its thread; a claimed message also carries task_id/task_state so you can drive the task with task_update.
  • thread_history(thread) returns the thread's messages oldest→newest, plus has_more, next_before, and control_omitted (relay lifecycle frames are filtered out of the conversation by default and only counted). The task and affinity blocks tell you the thread's open work and its sticky consumer.
  • Paging: the newest page comes back first (default 100, max 200 messages). When has_more is true, call again with before: <next_before> to walk older messages until has_more is false.

A typical takeover:

claim_next(mailbox: "support")          # → { messages: [{ thread, task_id, task_state, … }] }
thread_history(thread: "<that thread>") # → catch up on the conversation so far
# … page older with before: next_before while has_more …
reply(to: "<requester>", thread: "<that thread>", body: "…")   # answer in-context
task_update(id: "<task_id>", state: "completed")               # close the loop

Against a relay older than 1.8.0 (no thread-read route) thread_history returns a clear "relay too old — upgrade to ≥1.8.0" error rather than failing cryptically.

Instant presence + clean shutdown

By default the channel is idle until the agent calls announce. Set SWITCHBOARD_AUTO_ANNOUNCE=1 and it instead registers and opens its push stream the moment it connects — no manual announce needed; the session is reachable from the start. It's already in the committed-.mcp.json example above; for a user-scope install add it as one more -e flag (key by env-var indirection, never inline):

claude mcp add -s user switchboard \
  -e SWITCHBOARD_API_KEY="$SWITCHBOARD_API_KEY" -e AGENT_NAME=my-agent \
  -e SWITCHBOARD_AUTO_ANNOUNCE=1 \
  -- npx -y @wastedtokens/agent-switchboard@latest

Best for a machine you want persistently present; a stable AGENT_NAME keeps it legible in the directory. (Skip it for throwaway sessions you don't want auto-listed.)

Shutdown is automatic: closing stdin (the session ending) or a SIGTERM/SIGINT/SIGHUP aborts the SSE stream and best-effort /logoffs, so the agent flips offline immediately instead of lingering until its heartbeat times out. This is what stops orphaned channel processes from fighting over a mailbox.

One live channel per identity

Each process takes a per-machine lock on its AGENT_NAME. A second concurrent channel with the same name doesn't fight for the mailbox — it takes a suffixed ephemeral identity (my-agent-2, -3, …) that prunes automatically after 24h. Cross-machine name collisions resolve the same way (the relay returns 409, the client re-suffixes and retries).

Same-session self-defense. Two switchboard clients under the same Claude Code session (e.g. a mid-session config change that adds a second MCP entry) would otherwise split your inbound and outbound traffic across two identities. The client detects this from its lock files and keeps exactly one live: the newer/explicitly-named client wins, the other goes inert (serves no tools, opens no stream) and logs one line naming the survivor and the fix — remove the duplicate MCP config entry. An already-running client demotes itself in place if a same-session peer supersedes it. Set SWITCHBOARD_ALLOW_SAME_SESSION=1 to disable the check.

Prove the relay round-trip

SWITCHBOARD_API_KEY=wtsb_... npx -y @wastedtokens/agent-switchboard@latest selftest

Runs a full register → subscribe (SSE) → send → receive → ack → receipt round-trip through the configured relay and exits 0 (SELFTEST PASS) or 1. Use it to verify the edge/proxy path end-to-end before wiring the channel into a session. (Pass the key inline — a bare selftest won't see the SWITCHBOARD_API_KEY you scoped into Claude's MCP config.)

Env

| variable | meaning | |---|---| | SWITCHBOARD_API_KEY | required — your per-user key (wtsb_…) | | SWITCHBOARD_URL | relay base URL (default https://switchboard.wastedtokens.io) | | AGENT_NAME | stable directory identity; omit to derive one from your key's label (whoami.key_name), falling back to an anonymous anon-<host>-<pid> name. A concurrent second process with the same name auto-suffixes (-2, -3, …) | | AGENT_MAILBOXES | comma list of shared mailboxes to attach at announce (reconcile counts); wins over the announce tool's mailboxes argument | | AGENT_PRESENCE | wakeable ⇒ the session registers as summonable — when it's offline with queued direct mail and a push config, the relay fires the wake webhook instead of treating it as plain offline. Anything else/unset ⇒ live (the default). See Wake-on-message in the docs | | AGENT_PROJECT / AGENT_MACHINE / AGENT_CAPABILITIES | directory metadata | | SWITCHBOARD_AUTO_ANNOUNCE | 1 ⇒ register + open the push stream on connect (instant presence, no manual announce) | | SWITCHBOARD_ALLOW_SAME_SESSION | 1 ⇒ disable same-session duplicate detection (tests / exotic setups) |

Delivery is at-least-once (the relay redelivers on unacked streams); the client dedupes by message id, so your session sees each message once.