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

@earendil-works/pi-client

v0.84.0

Published

Transport-neutral client for remote pi sessions over framed CBOR bytes

Readme

@earendil-works/pi-client

Transport-neutral client for remote pi sessions. PiClient exchanges length-prefixed CBOR messages through a small ByteTransport interface. The package has no Node-specific imports.

import { PiClient, type ByteTransportFactory } from "@earendil-works/pi-client";

const transportFactory: ByteTransportFactory = async (handlers) => {
  // Connect using WebSocket, Unix socket, or another ordered byte transport.
  return {
    async send(chunk) {
      // Deliver chunks in invocation order and honor backpressure.
    },
    close() {},
  };
};

const client = new PiClient({ transportFactory });
await client.connect();
const session = await client.createSession({ cwd: "/workspace" });
const unsubscribe = session.subscribe((snapshot) => render(snapshot));
await session.prompt("Inspect this project");
unsubscribe();

Call handlers.onData(chunk) for inbound bytes, handlers.onClose() for an orderly terminal close, and handlers.onError(error) for transport failures. A factory must create a fresh transport for every connection attempt and complete any transport-specific authentication before resolving. For example, a WebSocket factory can provide credentials in its upgrade request.

PiClient does not reconnect automatically. Call reconnect() after disconnection. One connection can attach several sessions. Requests are correlated by ID. Server snapshots and successful response snapshots are authoritative, while progress events do not mutate snapshot state optimistically. Read cached session metadata from client.snapshot?.sessions; call listSessions() to request refreshed durable metadata from the server. Runtime state is available after acquiring a session.

acquireSession() returns an independent SessionLease; leases cannot be constructed directly. Use { mode: "exclusive" } for a lifecycle or mutation coordinator and { mode: "shared" } when multiple low-level consumers intentionally share the session. Exclusive acquisition fails with PiSessionOwnershipError while any lease exists, and shared acquisition fails while an exclusive lease exists. attachSession() is a shared-acquisition convenience method. createSession() returns an exclusive lease for the newly created session.

Calling dispose() or detach() releases only that lease. A lease rejects commands as soon as release begins. The client sends the protocol detach request after the final lease is released. If explicit detach() fails, the lease becomes active again for retry. If cleanup-oriented dispose() fails, it reports the protocol error but relinquishes local ownership; PiClient reconciles the failed protocol cleanup before the next acquisition. A released lease becomes unavailable without affecting other shared leases. Server removal or disconnection invalidates every lease for the affected attachment, and disposing an invalidated lease is a no-op. Commands fail with PiDisconnectedError while the client is disconnected and PiSessionDetachedError when the client is connected but a lease is releasing, released, or invalidated. Leases implement AsyncDisposable.

subscribe() observes authoritative snapshots. onEvent() observes protocol events. Both return an unsubscribe function. Structured errors returned by the server are exposed as PiServerError.

Limits and security

PiClientOptions.maxFrameLength bounds inbound and outbound CBOR payloads. Configure matching limits on the client and server. Transports should separately bound queued outbound bytes and preserve send order.

Treat peers as untrusted. Use a secure transport with appropriate access controls and authenticate during transport establishment.

Subscriber exceptions are isolated from protocol state. Set onListenerError in PiClientOptions to report them to application logging or diagnostics.

Unix-domain sockets

Node.js and Bun consumers can use the separately exported Unix-domain socket transport:

import { PiClient } from "@earendil-works/pi-client";
import { createUnixTransportFactory } from "@earendil-works/pi-client/unix";

const client = new PiClient({
  transportFactory: createUnixTransportFactory({
    path: "/tmp/pi.sock",
  }),
});

await client.connect();

maxPendingBytes bounds queued outbound data. It defaults to four times the protocol frame limit. The transport preserves send order and waits for socket backpressure before resolving each send.

The @earendil-works/pi-client root remains transport- and runtime-neutral. Importing the Node-compatible transport requires the explicit @earendil-works/pi-client/unix subpath.