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

threadloom

v0.1.0

Published

Loom — local-first control plane for coding agents: one CLI, shared memory, baton handoffs and routes across Claude Code, OpenCode, and friends.

Readme

Loom

ci npm license

One CLI for all your coding agents. Loom weaves Claude Code, OpenCode — and bridges like Antigravity — into a single shared thread per project: one conversation, one shared memory, one baton.

        iOS app  ── tailnet ──┐          (v1.5)
        laptop CLI ───────────┤          (v1 · now)
                              ▼
                        loom daemon
                              │
              ┌───────────────┼───────────────┐
          project A        project B       project C     ← independent batons
              │
      ┌───────┴────────┐
      │ event log (SoT)│   ← every message/tool call/edit, streamed live
      └───────┬────────┘
        projections on handoff
     ┌────────┼─────────────┐
 [adapter]  [adapter]    [bridge]
 Claude     OpenCode     Antigravity
 Code       serve API    debug port (read-only)
     └── one working tree · baton = write lock ──┘

Why

Every coding agent keeps its own brain. Claude Code's memory can't be read by Antigravity; OpenCode doesn't know what you decided with Claude an hour ago. Switching tools means re-explaining your project, every time.

Loom fixes the seam:

  • Shared thread — one conversation; agents take turns.
  • Shared memory — an append-only event log is the source of truth; on every handoff it's projected into the next agent's native context (namespaced — your own CLAUDE.md/AGENTS.md are never touched).
  • The baton — exactly one agent holds the write lock per project. Handoffs are explicit, confirmed, and interrupt-safe.
  • Roles — declare a planner / executor / reviewer; Loom suggests handoffs at natural boundaries ("plan looks complete — hand to the executor?"). You confirm.
  • Routes — or let Loom drive the chain: loom route ship "add dark mode" runs plan → execute → review as one command, pausing (and notifying you) whenever an agent has a question, resuming when you answer.
  • Fire-and-notify — agents run in the background across many projects; Loom notifies you when one finishes or needs input.
  • Phone-ready — bind the daemon to your Tailscale interface, pair a device with a single-use QR token, and every surface talks to the same API. (Native iOS app: v1.5.)

Install

Requires Node ≥ 22.5 (Loom's event log uses the built-in node:sqlite).

npm install -g threadloom          # → `loom` on your PATH

Other paths:

# one-liner from source (clones ~/.loom-src, builds, links; re-run to update)
curl -fsSL https://raw.githubusercontent.com/nickthelegend/loom/main/scripts/install.sh | bash

# straight from git
npm install -g github:nickthelegend/loom

# hackable checkout
git clone https://github.com/nickthelegend/loom.git && cd loom
npm install && npm run build && npm link

Then verify the setup:

loom doctor        # checks node, agents, tailscale, daemon, and your project

The native app lives in app/cd app && npx expo install && npx expo start, scan with Expo Go. The daemon-served web app needs no install at all (loom pair → scan).

Quickstart

cd your-project
loom init          # detects installed agents (claude, opencode), assigns roles
loom               # opens the TUI — one full-screen thread over every agent
  ██      ▄████▄  ▄████▄  ▄█▄▄█▄
  ██      ██  ██  ██  ██  ██▀▀██
  ██      ██  ██  ██  ██  ██  ██
  ██████  ▀████▀  ▀████▀  ██  ██
        one thread · every agent

  10:44 claude-code  here's the plan: …
   ⟶ baton: claude-code → opencode
  10:45 opencode     implementing step 1 …

 ╭──────────────────────────────────────────────╮
 │ › Ask anything… "/route ship: add dark mode" │
 │ opencode · executor ⟵ baton                  │
 ╰──────────────────────────────────────────────╯
   tab shift agent · /help · esc interrupt        loom 0.1.0
   ~ my-project · baton opencode  ➤ ship 2/3

tab shifts the active agent/IDE — claude-code → opencode → back. The handoff (interrupt-safe, memory projected, briefing armed) happens when you hit enter, so switching is one keystroke, not a ceremony. ctrl+p opens the command palette (fuzzy-filtered: shift to any agent, launch a named route, decision, interrupt, pair…). esc interrupts, /help lists the slash commands.

Prefer plain line-mode (SSH, scripts)? loom chat is the same thread as a classic REPL, and every action also exists as a one-shot command (loom send, loom handoff, …).

Routing — multi-hop pipelines

Handoffs are unlimited and manual by default. Routes automate a chain of them:

loom route auto "add a dark-mode toggle"          # DYNAMIC: an LLM picks each hop
loom route ship "add a dark-mode toggle"          # named pipeline from config
loom route planner,executor "fix the flaky test"  # ad-hoc: roles…
loom route claude-code,opencode,claude-code "…"   # …or agent ids, any length

auto is dynamic routing: after every hop, a router looks at the task, the hop history, and the last replies, then picks the next agent — or declares the task done. The router is Claude (headless, small model, JSON out) with a deterministic plan→execute→review rules engine as automatic fallback, so routes never stall on a router failure. Every decision is logged with its reason (➤ hop 2 → opencode (plan ready — execute it)), a hop budget caps runaways (--max-hops, default 8), and --router rules skips the LLM entirely.

What happens per hop: interrupt-safe handoff → shared-memory projectionbriefing → the step's role instruction. Then:

  • step finishes cleanly → Loom advances to the next agent automatically;
  • the agent asks a question → the route pauses (waiting_human), you get a notification, loom route --status and the board show the question; you answer in the shared thread (loom send "…") and the route resumes by itself;
  • an agent errors or a step times out (45 min default) → the route fails loudly;
  • you always outrank the route: any manual handoff/interrupt cancels it, and loom route --abort stops it and interrupts the in-flight turn.

--detach returns immediately (fire-and-notify); following with Ctrl-C also leaves the route running server-side. One route per project at a time (the baton is one write lock); run routes across different projects in parallel freely.

Define named pipelines in .loom/config.json — steps are roles or agent ids, and any step can carry its own focus:

"routes": {
  "ship": ["planner", "executor", "reviewer"],
  "api-only": [
    { "step": "planner",  "instruction": "design the endpoint contract only" },
    { "step": "executor", "instruction": "only touch src/api — no schema changes" },
    "reviewer"
  ]
}

Per-step instructions are appended to the role guidance for exactly that step — the next hop never sees them. loom init seeds a ship route automatically when it detects at least two roles.

Commands

| Command | What it does | |---|---| | loom | The TUI — full-screen thread, tab shifts agents, /-commands inline | | loom init | Make the current directory a Loom project (auto-detects agents) | | loom chat | Same thread as a plain line REPL (/handoff, /interrupt, @agent) | | loom send <text> | One-shot message (-a <agent> to address someone specific) | | loom handoff <agent> | Pass the baton — interrupts, projects memory, briefs the target | | loom route <spec> "<task>" | Run a pipeline (name, or a,b,c ids/roles); --status / --abort / --detach | | loom routes | List named pipelines defined for this project | | loom interrupt | Stop the current holder's turn (cancels an active route) | | loom decision <text> | Record a decision into shared memory | | loom log [-f] | Show (or follow) the project event log | | loom costs | Project spend: total + per-agent turns, $ and agent time | | loom agents / loom projects / loom status | Who's who, board of projects, daemon health | | loom up [--tailnet] [--restart] / loom down / loom daemon | Daemon lifecycle (--tailnet binds to your Tailscale IP) | | loom pair | QR deep link that pairs a phone (single-use token) | | loom clients [--revoke <id>] [--ping] | Paired devices: list, revoke, or send a test push | | loom doctor | Diagnose env, daemon, binding, and project config — with fixes |

Supported agents

| Agent | Tier | Transport | Status | |---|---|---|---| | Claude Code | adapter (full-duplex) | headless CLI, stream-json, --resume, briefing via --append-system-prompt | ✅ verified against 2.1.83 | | OpenCode | adapter (full-duplex) | opencode serve HTTP + SSE (/prompt, /interrupt, /event) | ✅ verified against 1.17.20 | | Echo | adapter (demo/tests) | in-process | ✅ | | Antigravity | bridge (read-mostly) | Chromium debug port — presence + memory projections only | 🔶 experimental |

Adapters implement the full contract (send / stream / injectMemory / interrupt / diff) and may hold the baton. Bridges only observe and receive shared-memory projections — they never hold the write lock. That's a design decision, not a gap: GUI agents without a stable API can't be trusted with interrupt-safe writes. See docs/integration-notes.md for the verified surfaces.

How it works

  • Event log (.loom/log.db, SQLite via node:sqlite, JSONL fallback) — every message, tool call, file edit, decision, and handoff, appended in order. The log is the project's memory; everything else is a view of it.
  • Projection — on handoff, Loom distills the log into .loom/memory/<agent>.md (persistent, namespaced) and arms a short one-shot briefing injected with the target's next turn (system-prompt append for Claude Code, delimited preamble for OpenCode). Two renderers behind one interface:
    • template (default) — deterministic, instant, free;
    • llm — a small Claude model distills the recent log into a dense doc (mission / current state / decisions / risks / next moves). Opt in per project: "projection": { "mode": "llm", "model": "haiku" }. Any failure or timeout falls back to the template — a broken Claude never blocks a handoff. Bridges always get template views (no N×LLM waste per hop).
  • Baton — persisted per project (.loom/state.json). Messages route to the holder; addressing a non-holder returns 409 not_holder and the surface asks you to confirm a handoff. Ghost holders (agent removed from config) self-heal. Every handoff snapshots the outgoing agent's working-tree state (dirty flag + git status) into the log.
  • Decisionsloom decision <text> pins a fact, and any agent line starting Decision: … is captured automatically. Decisions ride every future projection.
  • Cost telemetry — agents that report per-turn cost (Claude Code, OpenCode) feed a live ledger: loom costs breaks it down per agent, the board/TUI/phone app show the project total, and every route logs exactly what it spent (✔ route completed (3 steps) · $0.0421). Totals rehydrate from the event log, so they survive restarts.
  • Daemon — one process, many projects. REST for commands, WebSocket for the live stream. Config edits hot-reload when the project is quiet.

Your phone (Android today, over Tailscale)

The daemon serves a full phone app at /app — board, live thread, agent chips, routes. No app store, no build step; it ships inside Loom.

loom up --tailnet     # daemon binds to your Tailscale IP (never 0.0.0.0)
loom pair             # QR appears in the terminal

Scan the QR with your phone camera (phone must be on your tailnet — install the Tailscale app and sign in). The link opens …/app#pair=<token>; the app claims the single-use, 10-minute pairing token from the URL fragment (fragments never hit the network log) and exchanges it for its own client token. Then:

  • Board — every project, needs-input dots, baton holder, live route progress.
  • Thread — the same shared conversation, streaming over WebSocket.
  • Agent chips — tap opencode, hit send: baton shifts (projection + briefing included), exactly like tab in the TUI.
  • Routes — the ➤ button opens a picker: choose auto (LLM picks each hop), any named pipeline, or custom steps, type the task, go. Live banner with hop progress and reasons, an abort button, and when a route pauses on a question you answer right there and it resumes.
  • Chrome menu → Add to Home screen installs it like an app.

Push notifications come with the native app (app/): open it once after pairing and it registers its Expo push token with the daemon. From then on your phone buzzes when an agent needs input, when a route completes or fails, and when a solo turn finishes — route hops are deliberately silent (a 5-step pipeline buzzes once, not five times). Verify with loom clients --ping.

Security model

  • The daemon binds to 127.0.0.1 by default, or your Tailscale interface with --tailnet — never 0.0.0.0. The tailnet is the trust boundary: device auth and E2E encryption come from Tailscale.
  • Every request needs a bearer token (~/.loom/daemon.json, mode 0600).
  • Pairing: loom pair mints a short-lived (10 min), single-use token, rendered as a QR. The device exchanges it for a long-lived client token. Secrets never ride in URLs.
  • Paired clients are not admins: they can't mint new pairing tokens.

Adapter SDK

Add an agent in ~40 lines — implement the contract, register the kind:

import { AdapterBase, registerAgentKind, type SendInput } from "loom-agents/sdk";

class MyAgentAdapter extends AdapterBase {
  async available() { return true; }
  async start() {}
  async stop() {}
  async send(input: SendInput) {
    this._busy = true;
    try {
      // …drive your agent; stream progress:
      this.emit({ kind: "message", payload: { text: "done!" } });
      this.emit({ kind: "run_complete", payload: {} });
    } finally { this._busy = false; }
  }
  async interrupt() {}
}

registerAgentKind("my-agent", (cfg, dir) => new MyAgentAdapter(cfg.id, "my-agent", dir));

Full guide: docs/adapters.md. Design rationale and every decision with its why: ARCHITECTURE.md.

Configuration

.loom/config.json (created by loom init, hot-reloaded on edit):

{
  "name": "my-project",
  "agents": [
    { "id": "claude-code", "kind": "claude-code", "role": "planner" },
    { "id": "opencode",    "kind": "opencode",    "role": "executor",
      "options": {} },
    { "id": "antigravity", "kind": "antigravity", "role": "general",
      "options": { "debugPort": 9222 } }
  ],
  "defaultAgent": "claude-code",
  "routes": { "ship": ["planner", "executor", "planner"] }
}

Roles: planner · executor · reviewer · general. Claude Code options: permissionMode (default acceptEdits), model. OpenCode options: model ("providerID/modelID", e.g. "opencode/minimax-m2.5"set this: headless sessions don't inherit your TUI default), agent, baseUrl to reuse a running server.

Development

npm test          # 37 tests: unit + full HTTP/WS end-to-end
npm run build     # tsc → dist/
npm run dev       # run the CLI from source (tsx)

Roadmap

  • Native mobile app (push notifications on needs input / done) on the same daemon API the served app already uses.
  • LLM-synthesized projections behind the same interface.
  • More adapters/bridges via the SDK — contributions welcome.

License

MIT © Nivesh Gajengi