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

restty

v0.1.35

Published

Browser terminal rendering library powered by WASM, WebGPU/WebGL2, and TypeScript text shaping.

Readme

restty

Version Downloads Package Size CI Publish Demo

GitHub Twitter Email Discord Support me

Powerful, lightweight browser terminal. Batteries included.

Live demo: https://restty.pages.dev/

Powered by:

  • libghostty-vt (WASM terminal core)
  • WebGPU (with WebGL2 fallback)
  • text-shaper (shaping + raster)

Recent additions:

  • programming ligatures with cross-cell shaping
  • desktop double-click word selection and triple-click line selection
  • standalone ESM bundles at restty/esm, restty/esm/internal, and restty/esm/xterm

Release Status

restty is in an early release stage.

  • Known issue: kitty image protocol handling can still fail in some edge cases.
  • API note: high-level APIs are usable now, but some APIs may still change to improve DX.

If you hit an issue, please open one on GitHub with repro steps.

Install

npm i restty

Quick Start

<div id="terminal"></div>
import { Restty } from "restty";

const restty = new Restty({
  root: document.getElementById("terminal") as HTMLElement,
});

restty.connectPty("ws://localhost:8787/pty");

That is the primary API: new Restty(...). restty creates pane DOM, canvas, and hidden IME input for you.

Common Tasks

Apply a built-in theme

import { getBuiltinTheme } from "restty";

const theme = getBuiltinTheme("Aizen Dark");
if (theme) restty.applyTheme(theme);

Parse and apply a Ghostty theme file

import { parseGhosttyTheme } from "restty";

const theme = parseGhosttyTheme(`
foreground = #c0caf5
background = #1a1b26
cursor-color = #c0caf5
`);

restty.applyTheme(theme, "inline");

Split panes and operate per pane

restty.splitActivePane("vertical");
restty.splitActivePane("horizontal");

for (const pane of restty.panes()) {
  pane.connectPty("ws://localhost:8787/pty");
}

Use active-pane convenience methods

restty.setFontSize(15);
restty.setLigatures(true);
restty.sendInput("ls -la\n");
restty.copySelectionToClipboard();
restty.selectWordAtClientPoint(120, 48);

Provide custom fonts

By default, restty uses a local-first font preset with CDN fallback. To fully control fonts, disable the preset and pass fontSources.

const restty = new Restty({
  root: document.getElementById("terminal") as HTMLElement,
  appOptions: {
    fontPreset: "none",
  },
  fontSources: [
    {
      type: "url",
      url: "https://cdn.jsdelivr.net/gh/JetBrains/[email protected]/fonts/ttf/JetBrainsMono-Regular.ttf",
      label: "JetBrains Mono",
    },
    {
      type: "local",
      matchers: ["jetbrains mono nerd font", "fira code nerd font"],
      label: "Local fallback",
    },
  ],
});

Update fonts at runtime (all panes):

await restty.setFontSources([
  { type: "local", matchers: ["sf mono"], required: true },
  {
    type: "url",
    url: "https://cdn.jsdelivr.net/gh/ryanoasis/[email protected]/patched-fonts/NerdFontsSymbolsOnly/SymbolsNerdFontMono-Regular.ttf",
  },
]);

Use standalone single-file ESM bundles

When you want self-contained browser ESM artifacts instead of the split package entrypoints:

import { Restty } from "restty/esm";
import { Terminal } from "restty/esm/xterm";

Package outputs:

  • restty/esm -> dist/restty.esm.js
  • restty/esm/internal -> dist/internal.esm.js
  • restty/esm/xterm -> dist/xterm.esm.js

Touch behavior (pan-first by default)

On touch devices, restty defaults to pan-first scrolling with long-press selection.

const restty = new Restty({
  root: document.getElementById("terminal") as HTMLElement,
  appOptions: {
    // "long-press" (default) | "drag" | "off"
    touchSelectionMode: "long-press",
    // Optional tuning knobs:
    touchSelectionLongPressMs: 450,
    touchSelectionMoveThresholdPx: 10,
  },
});

Plugin system (native)

Use plugins when you want to extend restty behavior without patching core.

import type { ResttyPlugin } from "restty";

const metricsPlugin: ResttyPlugin = {
  id: "example/metrics",
  apiVersion: 1,
  activate(ctx) {
    const paneCreated = ctx.on("pane:created", ({ paneId }) => {
      console.log("pane created", paneId);
    });
    const outgoing = ctx.addInputInterceptor(({ text }) => text.replace(/\t/g, "  "));
    const lifecycle = ctx.addLifecycleHook(({ phase, action }) => {
      console.log("lifecycle", phase, action);
    });
    const stage = ctx.addRenderStage({
      id: "metrics/tint",
      mode: "after-main",
      uniforms: [0.12],
      shader: {
        wgsl: `
fn resttyStage(color: vec4f, uv: vec2f, time: f32, params0: vec4f, params1: vec4f) -> vec4f {
  return vec4f(min(vec3f(1.0), color.rgb + vec3f(params0.x, 0.0, 0.0)), color.a);
}
`,
      },
    });
    return () => {
      paneCreated.dispose();
      outgoing.dispose();
      lifecycle.dispose();
      stage.dispose();
    };
  },
};

await restty.use(metricsPlugin, { sampleRate: 1 });
console.log(restty.pluginInfo("example/metrics"));
restty.unuse("example/metrics");

Declarative loading (manifest + registry):

await restty.loadPlugins(
  [{ id: "example/metrics", options: { sampleRate: 1 } }],
  {
    "example/metrics": () => metricsPlugin,
  },
);

See docs/plugins.md for full plugin authoring details.

Shader stages

Shader stages let you extend the final frame pipeline with WGSL/GLSL passes.

Global stages:

restty.setShaderStages([
  {
    id: "app/crt-lite",
    mode: "after-main",
    backend: "both",
    uniforms: [0.24, 0.12],
    shader: {
      wgsl: `
fn resttyStage(color: vec4f, uv: vec2f, time: f32, params0: vec4f, params1: vec4f) -> vec4f {
  let v = clamp(params0.x, 0.0, 0.8);
  let centered = (uv - vec2f(0.5, 0.5)) * 2.0;
  let vignette = max(0.0, 1.0 - v * dot(centered, centered));
  return vec4f(color.rgb * vignette, color.a);
}
`,
    },
  },
]);

const stage = restty.addShaderStage({
  id: "app/mono",
  mode: "after-main",
  uniforms: [1.0],
  shader: {
    wgsl: `
fn resttyStage(color: vec4f, uv: vec2f, time: f32, params0: vec4f, params1: vec4f) -> vec4f {
  let l = dot(color.rgb, vec3f(0.2126, 0.7152, 0.0722));
  return vec4f(l * 0.12, l * 0.95, l * 0.35, color.a);
}
`,
  },
});

stage.setEnabled(false);
restty.removeShaderStage("app/mono");

xterm compatibility layer

For migration from xterm.js-style app code, use restty/xterm:

import { Terminal } from "restty/xterm";

const term = new Terminal({ cols: 100, rows: 30 });
term.open(document.getElementById("terminal") as HTMLElement);

term.onData((data) => console.log("input", data));
term.onResize(({ cols, rows }) => console.log("resize", cols, rows));

term.write("hello");
term.writeln(" world");
term.resize(120, 40);
term.loadAddon({
  activate() {},
  dispose() {},
});

Compatibility scope:

  • Good for common embed/migration flows.
  • Not full xterm internals parity (buffer/parser/marker ecosystem APIs are not all implemented).
  • Prefer native Restty API for long-term integrations.

API Snapshot

Primary class:

  • new Restty({ root, ...options })
  • createRestty(options)

Xterm compatibility:

  • import { Terminal } from "restty/xterm"
  • Supports open, write, writeln, resize, focus, blur, clear, reset, onData, onResize, options, loadAddon, dispose

Pane access:

  • panes() / pane(id) / activePane() / focusedPane() / forEachPane(visitor)
  • splitActivePane("vertical" | "horizontal") / splitPane(id, direction) / closePane(id)

Active-pane convenience:

  • connectPty(url) / disconnectPty() / isPtyConnected()
  • setRenderer("auto" | "webgpu" | "webgl2")
  • setFontSize(number) / setLigatures(boolean) / setFontSources([...])
  • applyTheme(theme) / resetTheme()
  • setMouseMode("auto" | "on" | "off")
  • sendInput(text) / sendKeyInput(text)
  • copySelectionToClipboard() / pasteFromClipboard()
  • selectWordAtClientPoint(x, y)
  • resize(cols, rows) / focus() / blur()
  • updateSize(force?)
  • destroy()

Plugin host:

  • use(plugin, options?) / loadPlugins(manifest, registry) / unuse(pluginId) / plugins() / pluginInfo(pluginId?)
  • plugin context supports on(...), addInputInterceptor(...), addOutputInterceptor(...), addLifecycleHook(...), addRenderHook(...), addRenderStage(...)

Shader stages:

  • setShaderStages(stages) / getShaderStages()
  • addShaderStage(stage) / removeShaderStage(id)

Advanced / Internal Modules

Use these only when you need lower-level control:

  • restty/internal: full internal barrel (unstable; includes low-level modules like WASM/input/pty helpers)
  • restty/esm, restty/esm/internal, restty/esm/xterm: standalone bundled browser ESM entrypoints

Local Development

git clone https://github.com/wiedymi/restty.git
cd restty
git submodule update --init --recursive
bun install
bun run build:themes
bun run playground

Open http://localhost:5173.

Code Layout

  • src/surface/: public API (Restty), pane manager orchestration, plugin host, xterm shim.
  • src/runtime/: terminal runtime/render loop implementation.
  • src/renderer/, src/input/, src/pty/, src/fonts/, src/theme/, src/wasm/, src/selection/: subsystem modules.
  • src/app/: compatibility re-export layer while internals are refactored.

Repository Commands

bun run build         # build package output
bun run test          # full tests
bun run test:ci       # CI-safe test target
bun run lint          # lint
bun run format:check  # formatting check
bun run build:assets  # static playground bundle (playground/public/playground.js)
bun run playground    # one-command local dev (PTY + playground dev server)
bun run pty           # PTY websocket server only

Documentation

  • docs/README.md - docs index
  • docs/usage.md - practical integration guide
  • docs/xterm-compat.md - xterm migration shim
  • docs/how-it-works.md - runtime flow
  • docs/internals/ - implementation notes and architecture
  • THIRD_PARTY_NOTICES.md - third-party credits and notices