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

@myrialabs/ptykit

v0.0.5

Published

PTY sessions over WebSocket for Node & Bun — collaborative rooms, serialized-scrollback reattach, and a resilient browser client with one typed API.

Readme


PtyKit runs interactive shells server-side and streams them to the browser over a single WebSocket. Output is kept in a headless terminal so a refresh, a dropped connection, or a second viewer all replay the exact screen — no lost bytes, no double output. The PTY backend is auto-detected (bun-pty on Bun, node-pty on Node); you bring the auth.

// Server
import { PtyKitManager, createPtyKitServer } from '@myrialabs/ptykit';

const manager = new PtyKitManager();
const server = createPtyKitServer(manager, {
  path: '/pty',
  authorize: (ctx) => ctx.conn.data.user?.canAccess(ctx.namespace) ?? false,
});

Bun.serve({ port: 3000, fetch: server.fetch, websocket: server.websocket });
// Browser
import { PtyKitClient } from '@myrialabs/ptykit/client';

const client = new PtyKitClient({ url: '/pty', namespace: 'project-42' });
const session = await client.attach('project-42-terminal-1');
session.onData((chunk) => term.write(chunk));
term.onData((data) => session.write(data));

Why PtyKit

  • WebSocket only — one multiplexed control + data channel. No SSE, no transport option, no polling.
  • Collaborative rooms — output broadcasts to a room (default = namespace), so N clients ↔ 1 session. Multiple viewers see the same live terminal, and room-level onSessionCreated/onSessionClosed let clients mirror the live session set (render it as tabs, a list, panes — your call).
  • Reattach that just works — scrollback lives server-side in a headless xterm and replays as a single serialized frame. The server resizes the session to the attaching viewport before replaying, so even full-screen TUIs (vim, htop, coding agents) restore un-garbled. Survives refresh, disconnect, and tab switches with zero data loss and no double output.
  • Batteries-included clientmountTerminal / <PtyTerminal> ship xterm, fit, clipboard, web-links, unicode11, ligatures, a right-click copy/paste menu, a loading spinner, and a set of theme presets (dark, light, solarized-dark, solarized-light, dracula, nord, matrix) on by default — all opt-out / overridable. No addon or ANSI-palette boilerplate to hand-wire; swap themes at runtime with setTheme or the reactive theme prop.
  • Auto-detected backendbun-pty on Bun (the tested path), node-pty on Node (experimental). A Node consumer never builds bun-pty's rust/ffi, and vice-versa — both are optional, lazily loaded.
  • Resilient client — reconnect with exponential backoff, heal-reconnect for "open but dead" sockets, and idempotency-aware resend, all on by default.
  • Bring your own auth — an authorize hook enforces namespace access with anti-hijack ownership checks. The library ships no auth of its own.
  • Quiet, typed core — no stdout/stderr writes, sideEffects: false, JSDoc on every export, runs on Node 18+ and Bun.

Install

bun add @myrialabs/ptykit          # or: npm i @myrialabs/ptykit / pnpm add @myrialabs/ptykit

The PTY backend is an optional dependency resolved at runtime: bun-pty on Bun, node-pty on Node. For the Node WebSocket server, ws is used (also optional). Browser peers (@xterm/xterm, @xterm/addon-fit) and svelte are optional peer dependencies you already have in a frontend.

Entry points

| Import | What | | --- | --- | | @myrialabs/ptykit | Convenience barrel: core session engine (PtyKitManager) + WebSocket server (createPtyKitServer). | | @myrialabs/ptykit/core | Core session engine only (PtyKitManager) — transport-agnostic, no server. | | @myrialabs/ptykit/server | WebSocket transport server only (PtyKitServer, createPtyKitServer). | | @myrialabs/ptykit/client | Framework-agnostic browser client (mountTerminal, PtyKitClient, attachFit). | | @myrialabs/ptykit/svelte | Official Svelte component (<PtyTerminal/>). |

Quick start

| Task | API | | --- | --- | | Create the manager | const m = new PtyKitManager({ scrollback: 5000 }) | | Mount on Bun | Bun.serve({ fetch: server.fetch, websocket: server.websocket }) | | Mount on Node | await server.attach(httpServer) | | Terminal (vanilla) | await mountTerminal(el, { url: '/pty', namespace, sessionId, create: true }) | | Attach (client) | await client.attach(sessionId) | | Create (client) | await client.create({ cols, rows }) | | Stream output | session.onData((chunk) => term.write(chunk)) | | Send keystrokes | session.write(data) | | Resize | attachFit(session, term, fitAddon) | | Svelte | <PtyTerminal {sessionId} url="/pty" namespace="project-42" /> |

Server

createPtyKitServer mounts onto the HTTP server you already have.

Bun — wire fetch + websocket into Bun.serve:

Bun.serve({ port: 3000, fetch: server.fetch, websocket: server.websocket });

Node — attach to an http.Server (uses the optional ws package):

import http from 'node:http';
const httpServer = http.createServer(app);
await server.attach(httpServer);
httpServer.listen(3000);

The PtyKitManager owns the sessions and is transport-agnostic:

const manager = new PtyKitManager({
  scrollback: 5000,         // headless xterm lines
  idleTtl: null,            // sessions live until killed; a number opts into idle reaping
  retainExitedMs: 5 * 60_000, // keep exited sessions this long for reconnect replay
  env: { sanitize: true, inject: { MY_VAR: '1' } }, // strip runtime pollution, inject yours
});

See docs/server.md for the full surface, operations, and events.

Collaborative rooms

Output broadcasts to a room (default = the namespace) and clients filter by sessionId, so any number of clients can attach to the same session and watch the same live terminal. The serialized reattach frame is unicast to the joining client, so existing viewers are never repainted.

const server = createPtyKitServer(manager, {
  room: (ctx) => ctx.namespace, // or group however you like
});

The resilient client

Skip the xterm boilerplate with mountTerminal — the framework-agnostic counterpart to <PtyTerminal/>. Give it a container and a url; it creates the terminal, fits it, opens the session, and wires output⇄input, while staying fully configurable.

import { mountTerminal } from '@myrialabs/ptykit/client';

const { session, terminal, dispose } = await mountTerminal(el, {
  url: '/pty',
  namespace: 'project-42',
  sessionId: 'project-42-terminal-1',
  create: true,
  onStatus: (s) => render(s),
});

Need full control? Drop down to PtyKitClient. Reconnect is on by default — exponential backoff (1s → 30s), heal-reconnect for sockets that are "open but dead", and idempotency-aware resend. On reconnect, every known session is re-attached so the room subscription and scrollback recover.

const client = new PtyKitClient({
  url: '/pty',
  namespace: 'project-42',
  reconnect: { enabled: true, baseDelayMs: 1000, maxDelayMs: 30_000, maxAttempts: 5 },
  persistence: { load, save }, // optional: own the active-session id yourself
});

client.onStatus((s) => render(s)); // 'connected' | 'reconnecting' | 'disconnected'

See docs/client.md for mountTerminal, attachFit, persistence, and the session API.

Svelte

<script>
  import { PtyTerminal } from '@myrialabs/ptykit/svelte';
</script>

<PtyTerminal sessionId="project-42-terminal-1" url="/pty" namespace="project-42" />

The component is fully configurable (theme, font, reconnect, lifecycle callbacks, …). See docs/svelte.md.

Security

The authorize hook defaults to allow-all so the package is friendly to try locally — this is unsafe in production. A network-reachable deployment must provide an authorize implementation that checks the connection's identity (populated by onUpgrade) against the requested namespace. PtyKit also rejects cross-namespace sessionId access (anti-hijack), but it cannot know who your users are — that's your hook's job.

node-pty status

bun-pty is the default, tested backend. The node-pty adapter exists and auto-activates under Node, but is marked experimental until the scale and auto-detect benchmarks gate it. On the benchmark machine (Node 25, macOS arm64), node-pty failed to spawn (posix_spawnp, reproduced with raw node-pty) — see bench-results.md.

Performance

Measured on a dev laptop (Apple M2, Bun 1.3.14). Reproduce with bun bench.ts; full numbers in bench-results.md.

  • Throughput overhead of the wrapped pipeline vs raw bun-pty: ~7% (target <10%) — the cost of persist-to-headless-first + batching.
  • Reattach serialize latency: p50 ~3–9ms, p95 ~10–19ms across 10KB/100KB/1MB buffers; the newest output is always present.
  • Idle footprint: ~0.13 MB of parent-process RSS per session. In-memory scrollback was fine at 100 sessions — no disk spill needed.

Documentation

  • API reference — every export across the three entry points.
  • ServerPtyKit, createPtyKitServer, rooms, authorize.
  • ClientmountTerminal, PtyKitClient, reconnect, persistence, attachFit.
  • Svelte — the <PtyTerminal/> component.
  • Examples — runnable scenarios.

Support

If PtyKit is useful to you, consider supporting its development:

| Method | Address / Link | |--------|----------------| | Bitcoin (BTC) | bc1qd9fyx4r84cce2a9hkjksetah802knadw5msls3 | | Solana (SOL) | Ev3P4KLF1PNC5C9rZYP8M3DdssyBQAQAiNJkvNmPQPVs | | Ethereum (ERC-20) | 0x61D826e5b666AA5345302EEEd485Acca39b1AFCF | | USDT (TRC-20) | TLH49i3EoVKhFyLb6u2JUXZWScK7uzksdC | | Saweria | saweria.co/myrialabs |

License

MIT — see LICENSE.