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

claudemesh

v0.1.1

Published

Local side-channel between Claude Code instances: discover, message, and read other claude CLI sessions on the same machine.

Downloads

249

Readme

claudemesh

Let your local Claude Code sessions find and talk to each other — in realtime, with zero compromises to the Claude Code CLI you already use.

Run claude in three folders. Today they don't know the others exist. Install claudemesh and they do: each session gets a stable ID, can list its peers, send them tasks that wake them up the instant they arrive, and read each other's conversation history — all over a tiny localhost daemon. No cloud, no network exposure, no auth, no behavior changes inside claude itself. Stop using it any time and the original claude binary is exactly as it was.

claudemesh          # launch claude inside the realtime supervisor
claudemesh -c       # claude -c (continue most recent session)
claudemesh --resume # any other claude flag works the same

Anything that isn't a recognized subcommand (list, send, install, …) is forwarded straight to the wrapped claude binary, so claudemesh is a drop-in alternative entrypoint to claude — no alias, no shadow, no plugin install inside Claude. You get every feature of Claude Code (sessions, hooks, plugins, MCP, sub-agents, slash commands, IDE integration) plus the inter-instance side channel.

Status: v0.2 — realtime PTY supervisor + folder-name addressing + idle tracking.

Why

You probably run claude in several folders at once. They have no idea about each other. Claudemesh gives every session a stable identity and a realtime side channel:

  • Find peers: list which other Claudes are running on this machine, where, and for how long.
  • Hand off work: claudemesh send api-service "rerun the migration check" — the recipient wakes up and acts on it within a second if it was launched via claudemesh, or on its next user prompt otherwise.
  • Inspect: peek at another instance's history (recent turns, search, fetch a specific turn) when you need cross-session context.
  • Notice: the receiver's status line ticks up (✉2) when messages are waiting.

Everything is purely additive. Hooks and MCP are installed at the user level (~/.claude/), so existing project-level Claude Code config is untouched. Removing claudemesh is one command (claudemesh uninstall) and leaves no trace inside claude itself.

How it works

There are two layers, and you can use either independently:

   ┌─────────────── one machine ───────────────┐
   │                                            │
   │  ┌── claudemesh (PTY supervisor) ────────┐   │  ← Layer 2: realtime injection
   │  │   ↑↓ proxy user terminal ↔ claude  │   │      (when launched via `claudemesh`)
   │  │   subscribes to daemon /events      │   │
   │  │   writes "[inbox]\n" to PTY master  │   │
   │  └─┬─────────────────────────────────┬─┘   │
   │    │ (PTY pair)                      │     │
   │  ┌─▼──────────────── claude ──────────▼─┐  │  ← Layer 1: hooks + MCP
   │  │   SessionStart/End hooks             │  │      (works with bare `claude` too)
   │  │   UserPromptSubmit hook (drains inbox)│ │
   │  │   Stop hook (blocks on tasks)        │  │
   │  │   MCP server: claudemesh               │  │
   │  └──────────────────────────────────────┘  │
   │                                            │
   │              ┌─────────────────────────┐   │
   │              │  claudemesh daemon        │   │
   │              │  HTTP @ 127.0.0.1:7878  │   │
   │              │  • registry + idle flag │   │
   │              │  • inboxes              │   │
   │              │  • events long-poll     │   │
   │              │  • liveness sweep       │   │
   │              └─────────────────────────┘   │
   └────────────────────────────────────────────┘

The daemon binds to 127.0.0.1 only. State persists to ~/.claudemesh/.

Realtime via the supervisor

claudemesh spawns claude inside a PTY (via node-pty) and proxies your terminal byte-for-byte to/from it — so it looks and feels exactly like running claude directly. Any flag you pass to claudemesh (e.g. claudemesh -c, claudemesh --resume <id>) is forwarded to claude. In parallel the supervisor long-polls the daemon for new inbox messages addressed to this session. When one arrives:

  1. Wait for the daemon's idle flag on this session to be true (the Stop hook sets it when claude finishes a turn; the UserPromptSubmit hook clears it).
  2. Wait for ≥500ms of stdin silence (so we don't corrupt typing).
  3. Write [inbox]\n to the PTY master.

That sentinel is read by claude as if you typed it. UserPromptSubmit fires, the hook drains the inbox, and the messages arrive as additional context attached to that turn — with explicit instructions to act on kind="task" bodies, not just acknowledge them. End-to-end latency from send to claude starting work is sub-second on a healthy machine.

Without the supervisor (bare claude)

If you keep running claude directly, the inbox + hooks still do most of the work:

  • Messages still queue in the inbox.
  • Status line still shows ✉N.
  • The Stop hook still blocks on pending tasks if it happens to fire while messages are queued.
  • UserPromptSubmit still drains messages into the next turn.

What you lose is the wake-up for fully-idle claude: messages that arrive after Stop has already fired sit in the inbox until you submit any prompt. With the supervisor, that gap goes away.

Status line

[abc12345 · 3 peers · ✉2]
  • abc12345 — this session's ID.
  • 3 peers — count of other live Claudes (drops out at zero).
  • ✉2 — unread inbox messages (drops out at zero).

Install

Requires Node ≥ 20. The supervisor needs node-pty (native, prebuilt for darwin/linux/win).

npm i -g @ktamas77/claudemesh
claudemesh install

claudemesh install patches two files (each backed up first):

  • ~/.claude/settings.json — adds the four hooks and the status-line script.
  • ~/.claude.json — adds the claudemesh MCP server under mcpServers.

After this, just use claudemesh everywhere you'd type claude:

claudemesh              # interactive session in the supervisor
claudemesh -c           # resume the most recent session
claudemesh --resume xyz # resume a specific session id

The original claude binary is untouched — both work side by side. Use claudemesh when you want realtime inbox delivery; use claude when you don't (you still get the inbox + ✉ status line, just no auto-wake).

If you'd rather invoke the supervisor under a shorter name, drop a tiny script anywhere on your PATH:

mkdir -p ~/.local/bin
cat > ~/.local/bin/clc <<'EOF'
#!/bin/sh
exec claudemesh "$@"
EOF
chmod +x ~/.local/bin/clc

Now clc -c is claudemesh -c is claude -c-with-supervisor.

claudemesh uninstall   # reverses the install

If node-pty fails to install (older toolchain), bare claudemesh errors out with a fix-it message but the subcommands (claudemesh list, claudemesh send, etc.) still work.

Adding the badge to an existing status line

If you already have a custom statusLine script in ~/.claude/settings.json, claudemesh install detects it and leaves it alone rather than clobbering your customizations. To add the [abc12345 · 3 peers · ✉2] badge yourself, capture stdin once and pipe it to claudemesh statusline:

#!/bin/bash
input=$(cat)

# ... your existing parts ...
out="$host | $model | $ctx | $folder"

# Claudemesh badge (only renders if claudemesh is installed and a session is registered)
if command -v claudemesh >/dev/null 2>&1; then
  badge=$(printf '%s' "$input" | claudemesh statusline 2>/dev/null)
  [ -n "$badge" ] && out="$out | $badge"
fi

printf '%s\n' "$out"

claudemesh statusline reads the same JSON Claude Code already pipes to your script (it needs session_id), prints [id · peers · ✉N], and exits silently with no output if the daemon is down or the session isn't registered yet.

CLI

claudemesh [claude args...]              launch claude inside the realtime supervisor
                                       (e.g. `claudemesh -c`, `claudemesh --resume <id>`)
claudemesh list                          show all live Claude sessions
claudemesh whoami                        this shell's parent claude (best-effort by ppid)
claudemesh send <target> <message>       send a task message; target = id or folder name
claudemesh history <target> [-n N]       tail another session's transcript
claudemesh search <target> <query>       search another session's transcript
claudemesh status                        daemon health + instance count
claudemesh install / uninstall           patch / unpatch ~/.claude config
claudemesh help                          show subcommand help
claudemesh run [claude args...]          explicit form of the default supervisor launch
claudemesh daemon / mcp / hook / statusline   internal (used by Claude Code)

Anything that's not a recognized subcommand from this list is treated as args for the supervisor and forwarded to claude — that's how claudemesh -c and friends work.

CLAUDEMESH_CLAUDE_BIN overrides which binary the supervisor launches (default: claude on PATH). Useful if you have multiple Claude Code builds installed.

Addressing sessions: id or folder name

Anywhere a target is expected — CLI commands and MCP tools alike — you can pass either the 8-char claude_id or the basename of that session's working directory. The resolver:

  1. Tries an exact claude_id match against the live registry.
  2. Falls back to a case-insensitive match on basename(cwd).
# These two are equivalent when one live session is running in /Users/me/dev/api-service
claudemesh send 7k3p9q2x "rerun the migration check"
claudemesh send api-service "rerun the migration check"

If the folder name matches more than one live session (e.g. ~/orgA/api and ~/orgB/api both running), the resolver errors out with both claude_ids and full paths so you can disambiguate by id. If nothing matches, it prints all live sessions.

MCP tools

The claudemesh MCP server exposes the following inside every claude session. Wherever a tool takes a target session, you can pass either an 8-char claude_id or a folder basename (see Addressing sessions).

| Tool | Purpose | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | | whoami | Your own claude_id, cwd, started_at. | | list_instances | All live sessions: id, cwd, last_active, pending count, idle. | | send_message(to, body, kind?) | Send to another session. kind: "task" (default) wakes the recipient via the supervisor / Stop hook; kind: "note" is silent until next prompt. | | read_inbox(drain?) | Pull pending messages for the current session (defaults to draining). | | read_history(id, last_n_turns?, redact?) | Recent turns from another session, redacted by default; pass redact: false for raw. | | search_history(id, query, options?) | Substring/regex search over another session's transcript with surrounding context. | | get_turn(id, turn_index) | Pinpoint full-content fetch of one turn after read_history / search_history flagged it. |

Message delivery

Two kinds:

  • task (default) — receiver acts on the body as if you typed it. Three delivery paths, in priority order:
    1. Supervisor injection (realtime) — if the receiver was launched via claudemesh, the supervisor writes [inbox]\n into the PTY the moment the message arrives and the receiver is idle + not actively typing. Sub-second wake-up.
    2. Stop hook (between turns) — if the receiver is just finishing a turn when the message lands, the Stop hook blocks the stop and feeds the message in as the continuation directive.
    3. UserPromptSubmit (next prompt) — if neither of the above caught it (bare claude, fully idle), the message sits in the inbox until the user submits any prompt to that receiver, at which point it's appended as additional context for that turn.
  • note — surfaces only via path #3 (next prompt). Doesn't block stops, doesn't wake the model.

Inbox is at-least-once and drained on read.

Idle tracking

The daemon stores an idle: boolean per session, kept in sync by the hooks:

  • SessionStart (or first register) → idle: false
  • UserPromptSubmit (user just submitted) → idle: false
  • Stop, when allowing the stop (no inbox tasks pending) → idle: true

The supervisor reads this flag before injecting, ensuring it never types into a session that's mid-turn.

Privacy & security

  • Daemon binds to 127.0.0.1 only — never 0.0.0.0. Single-user assumption matches the rest of ~/.claude.
  • read_history redacts large tool results / file contents above a per-line byte cap by default. Pass redact: false (or use get_turn) for raw.
  • Hooks fail open: any daemon error is logged and the hook exits 0. Claude Code keeps working with no claudemesh integration if the daemon is down.
  • The supervisor never writes to the PTY without (a) idle flag set and (b) ≥500ms stdin silence — so it can't corrupt input you're typing.

Development

git clone [email protected]:ktamas77/claudemesh.git
cd claudemesh
npm install
npm run build       # tsc → dist/ (also chmod +x the bin)
npm run dev         # tsc --watch
npm run lint        # eslint
npm run format      # prettier --write
npm run typecheck   # tsc --noEmit
npm test            # vitest run

Pre-commit (husky) runs typecheck → test → lint-staged. A broken type or failing test blocks the commit.

Layout

src/
├── bin/claudemesh.ts          # entrypoint, dispatches all subcommands
├── cli/
│   ├── run.ts               # PTY supervisor (default `claudemesh` entrypoint)
│   ├── send.ts, history.ts, search.ts, list.ts, whoami.ts, status.ts
│   ├── install.ts, statusline.ts
│   └── ...
├── daemon/
│   ├── server.ts            # HTTP routes incl. /events long-poll, PATCH idle
│   ├── registry.ts          # id ↔ session ↔ pid ↔ cwd, idle flag
│   ├── inbox.ts             # per-recipient jsonl
│   ├── waiters.ts           # pub-sub for the events long-poll
│   └── liveness.ts          # prune dead PIDs
├── mcp/server.ts            # stdio MCP server
├── hooks/                   # session-start, session-end, user-prompt-submit, stop
└── shared/                  # paths, ids, http client, transcript reader, resolver

Roadmap

  • [x] Architecture & repo scaffold (TS + ESLint + Prettier + Husky)
  • [x] Phase 1 — daemon + register/list + status line
  • [x] Phase 2 — MCP tools (whoami, list_instances, send_message, read_inbox)
  • [x] Phase 3 — task-mode wake-up, read_history / search_history / get_turn
  • [x] Phase 4 — liveness sweep + lazy daemon autostart
  • [x] Phase 5 — folder-name addressing, sharper imperative inbox prompts
  • [x] Phase 6 — claudemesh PTY supervisor for realtime injection (with subcommand passthrough)
  • [ ] Real-world soak across many concurrent supervisor sessions
  • [ ] Reply chains: auto-set from when a recipient calls send_message after waking
  • [ ] Optional desktop notification fallback for un-supervised idle sessions
  • [ ] Inject the message body itself instead of an [inbox] sentinel (bracketed paste for multi-line; prepend from <id> · for attribution; preserve existing path as fallback when paste mode unavailable)

License

MIT © Tamas Kalman