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

@oakoliver/bubbletea

v1.2.1

Published

Elm Architecture TUI framework for TypeScript — zero-dependency port of Charmbracelet's Bubbletea

Readme

@oakoliver/bubbletea

Elm Architecture TUI framework for TypeScript. A pure TypeScript port of charmbracelet/bubbletea with zero runtime dependencies. Version 1.2.0 targets API and behavior parity with upstream Bubble Tea v2.0.10. See CHANGELOG.md for what each upstream sync ported.

Read the article: Getting Started with Bubble Tea in TypeScript · Porting Bubble Tea from Go to TypeScript

Larger Examples

Five full-screen programs in examples/ combine Bubble Tea with our ports of Bubbles, Lip Gloss and Glamour. To run them, see Running the larger examples.

chat.ts: a streaming coding-agent chat

A scripted coding agent (no network, no API key) with tool calls, collapsible tool output, three themes and Markdown answers streamed token by token through Glamour. The transcript is rendered the way OpenCode's Bubble Tea TUI did it: each finished part is rendered once and cached under a hash of its inputs, only the part still streaming is re-rendered, and the view follows new output only while you are at the bottom. Walkthrough: How OpenCode Renders Messages in Bubble Tea.

sysmon.ts: an htop-style system monitor

Per-core CPU bars, CPU and memory history, load average and uptime from node:os, and a process table from ps that you can sort, filter, open and fold into a tree. Read-only: it has no kill or signal features. --demo shows invented data, as in the recording.

kanban.ts: a board with dialogs and mouse support

Three columns of cards, a live filter, new, edit and delete dialogs drawn as Lip Gloss layers over the dimmed board, and the mouse: click to select, wheel to scroll a column, drag a card to another column. --file board.json saves the board after every change.

blocks.ts: a falling-blocks game

SRS rotation with wall kicks, a 7-bag randomiser, lock delay, hold, a ghost piece, a three-piece preview, levels and a line-clear animation, all driven by one Tick. --autoplay lets a small heuristic AI play, and --seed N repeats a game.

mission-control.ts: a dashboard

A ports table, test runs with streaming progress bars, bar charts and an event log, on invented data or, with --live, on real checkouts of the Charm ports.

Running the larger examples

They import this repository's source and the sibling ports by name. The ports are not dependencies of bubbletea, so install them without touching package.json:

git clone https://github.com/oakoliver/bubbletea && cd bubbletea
bun install
bun add --no-save @oakoliver/bubbles @oakoliver/lipgloss @oakoliver/glamour
bun examples/chat.ts            # also: sysmon.ts --demo, kanban.ts, blocks.ts --autoplay, mission-control.ts

Keys, flags and details for each one are in examples/README.md.

Features

  • Full Elm Architecture: init, update, view lifecycle
  • Keyboard and mouse input parsing (CSI, SS3, SGR, Kitty protocol)
  • Alt screen, bracketed paste, focus reporting
  • Standard renderer with automatic 60fps flushing
  • Commands: Batch (concurrent), Sequence (ordered), Tick, Every
  • External cancellation via AbortSignal
  • Raw mode management, SIGINT/SIGTERM/SIGWINCH handling
  • Works on Node.js and Bun

Upstream v2.0.10 Surface Map

The TypeScript API keeps its established camel-case lifecycle (init, update, view, run, send) while mapping the complete portable v2.0.10 surface (v2.0.9 and v2.0.10 were fix-only releases on top of v2.0.8):

| Upstream Bubble Tea | TypeScript surface | | --- | --- | | Model, Msg, Cmd, NewProgram, Program.Run/Send/Quit/Kill/Wait | Model, Msg, Cmd, NewProgram, Program.run/send/quit/kill/wait | | View, NewView, View.SetContent, Cursor, NewCursor, ProgressBar, NewProgressBar | View, NewView, View.setContent, Cursor, NewCursor, ProgressBar, NewProgressBar | | View alt-screen, mouse, focus, paste, keyboard-enhancement, title, color, cursor, and progress fields | Corresponding camel-case View fields; string-returning legacy models remain supported | | Key, KeyMsg, press/release messages, modifiers, special/Kitty key constants | Key, KeyMsg, KeyPressMsg, KeyReleaseMsg, KeyMod, KeyCode, ExtendedKeyCode | | Mouse, MouseMsg, button and click/release/wheel/motion messages | Mouse, MouseMsg, MouseButton, and the four concrete message classes | | Focus, paste, window, cursor, mode, color, clipboard, capability, terminal-version, environment, and color-profile reports | Same message names with camel-case fields and methods | | Batch, Sequence, Tick, Every, Quit, Interrupt, Suspend, ClearScreen, Raw, Println, Printf | Same exported command names | | Window/cursor/color/capability/version/clipboard requests | Same exported request and clipboard command names | | Exec, ExecProcess, ExecCommand, ExecCallback | Same concepts; ExecProcess(command, args, callback) creates a Node/Bun child process after terminal release | | WithContext, environment/output/input/filter/FPS/profile/window options, signal/panic/renderer options | Same option names; WithContext and WithAbortSignal accept AbortSignal | | OpenTTY, terminal release/restore, program printing | OpenTTY, Program.releaseTerminal/restoreTerminal/println/printf |

The only omitted exported upstream APIs are LogOptionsSetter, LogToFile, and LogToFileWith. They configure Go's process-wide log.Default() object and return an *os.File; Node and Bun have no equivalent process-wide logger contract to mutate. Use a Node/Bun logger with an append-mode file stream instead. Terminal suspension remains unavailable on Windows, matching upstream's own suspendSupported platform gate.

Install

npm install @oakoliver/bubbletea

Quick Start

import {
  type Msg,
  type Cmd,
  type Model,
  Program,
  QuitMsg,
  KeyPressMsg,
  Quit,
} from '@oakoliver/bubbletea';

class Counter implements Model {
  constructor(public count: number = 0) {}

  init(): Cmd {
    return null;
  }

  update(msg: Msg): [Model, Cmd] {
    if (msg instanceof KeyPressMsg) {
      const key = msg.toString();
      if (key === 'q' || key === 'ctrl+c') return [this, Quit];
      if (key === 'up') return [new Counter(this.count + 1), null];
      if (key === 'down') return [new Counter(this.count - 1), null];
    }
    return [this, null];
  }

  view(): string {
    return `Count: ${this.count}\n\nPress up/down to change, q to quit.`;
  }
}

const p = new Program(new Counter());
await p.run();

Examples

Runnable programs live in examples/:

bun examples/shopping-list.ts  # the Bubble Tea basics tutorial
bun examples/counter.ts        # the Quick Start above
bun examples/countdown.ts      # Tick, Sequence and Println
bun examples/keys.ts           # how KeyPressMsg describes each key
bun examples/window-size.ts    # alt screen + WindowSizeMsg

The full-screen programs are listed under Larger Examples.

The README recordings are made with @oakoliver/vhs from the tapes in assets/tapes/.

The Elm Architecture

Every Bubbletea program revolves around three methods on a Model:

  1. init() — Returns an optional command to run at startup.
  2. update(msg) — Receives a message, returns the new model and an optional command.
  3. view() — Returns the current UI as a string or a declarative View.

Messages (Msg) flow through the event loop. Commands (Cmd) are functions that produce messages asynchronously.

Commands

Commands are functions that return a Msg (or a Promise<Msg>). Bubbletea executes them asynchronously outside the event loop.

import { Batch, Sequence, Tick, Quit } from '@oakoliver/bubbletea';

// Run multiple commands concurrently
const cmd = Batch(cmdA, cmdB, cmdC);

// Run commands sequentially (each waits for the previous)
const cmd = Sequence(cmdA, cmdB, Quit);

// Fire a message after a delay
const cmd = Tick(1000, () => new TickMsg());

Program Options

Configure the program with functional options:

import {
  Program,
  WithInput,
  WithOutput,
  WithFilter,
  WithFPS,
  WithAltScreen,
  WithAbortSignal,
  WithMouseMode,
  WithWindowSize,
  WithoutRenderer,
  WithoutSignalHandler,
  WithoutCatchPanics,
  MouseMode,
} from '@oakoliver/bubbletea';

const p = new Program(
  model,
  WithAltScreen(),                   // Start in alt screen mode
  WithMouseMode(MouseMode.AllMotion), // Enable mouse tracking
  WithFPS(30),                        // Custom frame rate
  WithAbortSignal(controller.signal), // External cancellation
  WithFilter((model, msg) => msg),    // Event filter
);

With input disabled (WithInput(null)), no terminal query is ever sent, since the reply could not be read and would otherwise leak into the shell after exit: RequestBackgroundColor, RequestForegroundColor, RequestCursorColor, RequestCursorPosition, ReadClipboard, ReadPrimaryClipboard, RequestCapability, RequestTerminalVersion, RequestWindowSize (when the output stream has no dimensions), the startup mode probe, and the Kitty keyboard enhancement protocol are all skipped.

Key Input

KeyPressMsg carries the parsed key event:

if (msg instanceof KeyPressMsg) {
  msg.text;       // Printable character (e.g. 'a')
  msg.code;       // KeyCode enum value
  msg.mod;        // Bitmask: KeyMod.Ctrl | KeyMod.Alt | KeyMod.Shift
  msg.toString(); // Human-readable: 'ctrl+a', 'enter', 'f1'
}

Special keys are in the KeyCode enum: Enter, Tab, Escape, Backspace, Up, Down, Left, Right, Home, End, PgUp, PgDown, Insert, Delete, F1-F12.

Mouse Input

When mouse mode is enabled, you receive MouseClickMsg, MouseReleaseMsg, MouseWheelMsg, and MouseMotionMsg:

if (msg instanceof MouseClickMsg) {
  msg.x;      // Column
  msg.y;      // Row
  msg.button; // MouseButton enum
}

Window Size

WindowSizeMsg is sent on startup and whenever the terminal resizes:

if (msg instanceof WindowSizeMsg) {
  msg.width;
  msg.height;
}

External Control

const p = new Program(model);

// Send messages from outside
p.send(new CustomMsg());

// Graceful quit
p.quit();

// Immediate kill (throws ErrProgramKilled)
p.kill();

// Wait for the program to finish
await p.wait();

AbortSignal Integration

const ac = new AbortController();
const p = new Program(model, WithAbortSignal(ac.signal));

const runPromise = p.run().catch(console.error);

// Cancel from outside
ac.abort(); // Program exits with ErrProgramKilled

API

Program

  • new Program(model: Model, ...opts: ProgramOption[]) — Create a program.
  • program.run(): Promise<Model> — Run the event loop. Returns the final model.
  • program.send(msg: Msg): void — Send a message to the event loop.
  • program.quit(): void — Send a QuitMsg.
  • program.kill(): void — Immediately kill the program.
  • program.wait(): Promise<void> — Wait until the program finishes.
  • program.releaseTerminal() / program.restoreTerminal() — Temporarily hand terminal ownership to an interactive process.
  • program.println(...) / program.printf(...) — Print unmanaged lines above an inline view.

Command Helpers

  • Quit: Cmd — Command that sends a QuitMsg.
  • Batch(...cmds): Cmd — Run commands concurrently.
  • Sequence(...cmds): Cmd — Run commands in order.
  • Tick(durationMs, fn): Cmd — Fire after a delay.
  • Every(intervalMs, fn): Cmd — Fire once at the next wall-clock interval boundary; schedule it again from update for repetition.

Message Types

  • QuitMsg — Graceful exit
  • KeyPressMsg / KeyReleaseMsg — Keyboard events
  • MouseClickMsg / MouseReleaseMsg / MouseWheelMsg / MouseMotionMsg — Mouse events
  • WindowSizeMsg — Terminal resize
  • FocusMsg / BlurMsg — Terminal focus
  • InterruptMsg — Ctrl+C / SIGINT
  • SuspendMsg / ResumeMsg — Process suspend/resume
  • ClearScreenMsg / PrintLineMsg — Renderer control
  • EnvMsg / ColorProfileMsg — Startup environment and detected/forced color profile
  • KeyboardEnhancementsMsg / ModeReportMsg — Terminal protocol capability reports
  • ClipboardMsg / color messages / CapabilityMsg / TerminalVersionMsg — Query responses

Errors

  • ErrProgramKilled — Program was killed via kill() or AbortSignal
  • ErrProgramPanic — Unhandled exception in update() or a command
  • ErrInterrupted — SIGINT / Ctrl+C

Attribution

This is a TypeScript port of bubbletea by Charmbracelet, Inc., licensed under MIT.

License

MIT