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

@letientai299/term-recorder

v0.1.3

Published

Scriptable terminal recordings — write TypeScript to drive tmux sessions and produce asciicast files

Downloads

190

Readme

term-recorder CI npm

Scriptable terminal recordings. Write TypeScript to drive tmux sessions and produce asciicast files you can play back with asciinema or embed on the web. Below is a demo of term-recorder recording itself.

Inception

Requirements

  • tmux 3.4+ (session management)
  • asciinema 3.0+ (recording)
  • Node.js 20+, Bun, or Deno (runtime)

Install

npm install @letientai299/term-recorder

Quick start

Create a script file (e.g. demos.ts):

import { defineConfig, main, record } from "@letientai299/term-recorder";

const config = defineConfig();

await main(config, [
  record("hello", (s) => {
    s.send("echo 'Hello from term-recorder!'");
    s.send("ls -la");
  }),
]);

Run it:

bun demos.ts        # Bun
npx tsx demos.ts    # Node.js

Output lands in ./casts/hello.cast by default. Play it back:

asciinema play casts/hello.cast

CLI flags

All flags are optional — defaults work for most cases. Pass --help to see the full list:

USAGE bun demos.ts [OPTIONS]

OPTIONS

              -h, --help    Show this help message
              --headless    No visible terminal; auto-parallel at cpus/2
      -p, --parallel=<N>    Max concurrent recordings (default: 1, or cpus/2 if headless)
  -o, --output-dir=<DIR>    Output directory (default: ./casts)
    -f, --filter=<REGEX>    Run only recordings whose name matches this regex
              --cols=<N>    Terminal columns (default: 120)
              --rows=<N>    Terminal rows (default: 30)
        --load-tmux-conf    Use your tmux.conf instead of a clean config
               --dry-run    Print recording names and exit
   --trailing-delay=<MS>    Idle time before ending so the last frame stays visible (default: 1000)
             --pace=<MS>    Delay after each pane action (default: 1000, 0 to disable)
     --typing-delay=<MS>    Per-character delay for type() actions (default: 30)
     --action-delay=<MS>    Auto-pause inserted between queued actions (default: 200)

API

The record() callback receives a Session (the main pane) and a RunnerConfig with resolved settings. All methods are chainable and queue actions — nothing executes until main() drains the queue. See the TSDoc on each method for details.

The table below maps APIs to examples that demonstrate them:

| API | Examples | | -------------------------------- | ---------------------------------- | | type, enter, key | simple, fzf, interactive-tui | | run, reply | interactive-tui, render-test | | send | ruler, fzf | | splitH, splitV | simple | | waitForText, waitForIdle | agents, simple | | detectPrompt, waitForPrompt | ruler, interactive-tui | | pace, sleep | render-test, interactive-tui | | RunnerConfig (second callback) | ruler | | defineConfig (shell, env, cwd) | ruler, interactive-tui |

How it works

The core idea is to combine two tools that each do one thing well:

  • tmux provides a scriptable terminal multiplexer. It gives us a real pty that programs interact with normally — shell prompts, escape sequences, cursor movement, split panes — all work as they would in a real terminal. Its control mode (tmux -CC) exposes a structured protocol over stdin/stdout: we send commands and receive push notifications (%output, %subscription-changed) without polling.
  • asciinema records pty output into asciicast files with accurate timing. It attaches to the tmux session via asciinema rec -c 'tmux attach ...', capturing everything the terminal emits.

You write a TypeScript script that describes terminal actions (type text, press keys, wait for output, split panes). The library queues those actions, then drains them one by one against the tmux session while asciinema captures the result.

flowchart TD
    A[User script] --> B[Session / Pane proxy]
    B -->|push actions| C[ActionQueue]
    C -->|drain| D[TmuxServer]
    D -->|control mode| E[tmux server]
    E -->|pty I/O| F[Shell / program]

    D -->|spawn before drain| G[asciinema rec]
    G -->|tmux attach| E
    G -->|write| H[.cast file]

    D -.->|%output| C
    D -.->|%subscription-changed| I[wait module]
    I -.->|resolve| C

Key design choices

  • Queue-then-execute. The script callback runs synchronously to build an action queue. Actual tmux I/O happens only when drain() is called. This keeps the scripting API simple and chainable.
  • Isolated tmux sockets. Each recording gets its own tmux server via tmux -L <unique-name>, so parallel recordings and the user's tmux sessions never collide.
  • Control mode over subprocesses. After connect(), all commands go through a persistent control mode connection instead of spawning individual tmux processes. This is faster and enables push-based %output notifications for efficient waiting.
  • Clean tmux by default. tmux -f /dev/null prevents user config from affecting reproducibility. Pass --load-tmux-conf to use your own theme and status bar.
  • Headful vs headless. Headful mode runs asciinema in the foreground terminal (sequential only). The tmux window is 1 smaller in each dimension so tmux draws a visible border within the cast frame. Headless mode uses asciinema rec --headless, auto-parallelizes to cpus / 2, and produces borderless output. Both modes produce casts at the configured dimensions, but usable area differs: --cols 120 --rows 30 gives scripts 120×30 in headless and 119×29 in headful.

Tips

Clean shell prompt for demos

Your personal shell prompt (starship, oh-my-zsh, etc.) can leak personal info and distract from the demo content. Use shell and env to start a bare zsh with a minimal prompt:

const config = defineConfig({
  shell: "exec zsh --no-rcs",
  env: { PS1: "%F{cyan}%~%f\n$ " },
});

The two zsh invocations are intentional: the outer one runs -c to set PS1, then exec zsh --no-rcs replaces it with an interactive shell that inherits the variable.

  • %~ shows the full path from home, %F{cyan} adds color.
  • --no-rcs skips all zsh startup files so nothing overrides the prompt.

Session-level environment variables

Use env to set environment variables visible to all panes (including splits):

const config = defineConfig({
  env: { EDITOR: "vim", TERM: "xterm-256color" },
});

Variables set via env are applied at the tmux session level, so every pane inherits them automatically. This is useful for controlling tool behavior (EDITOR, PAGER), ensuring color support (TERM, COLORTERM), or hiding personal details (HOME, USER).

Converting casts to GIFs

The .cast format is great for playback with copyable text content. But, it's not widely supported. Use agg to convert to GIF for sharing:

agg casts/hello.cast casts/hello.gif

For SVG output, use svg-term instead.

This project includes a scripts/cast-to-gif.ts script that batch-converts all casts with FiraCode Nerd Font and symbol fallbacks — see docs/contributing.md for the mise gif task that wraps it.

Development

See docs/contributing.md.

Limitations

  • External tool dependency. Requires tmux 3.4+ and asciinema 3.0+ installed on the host. Contributors can use mise install to get both automatically.
  • No per-action error recovery. If a wait action times out, the entire recording is aborted. There is no way to catch and retry individual actions.
  • Serialized tmux commands. The control mode mutex means no two tmux commands run simultaneously, even across different panes. Multi-pane scripts are sequential at the I/O layer.
  • Polling-based wait APIs. waitForText, waitForPrompt, and waitForIdle poll capture-pane snapshots. Text that appears and disappears between polls can be missed, and waitForIdle uses a short silence heuristic that may resolve too early under bursty output.