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

@averagejoeslab/tui

v1.0.1

Published

Terminal user interface framework based on the Elm Architecture

Readme

@puppuccino/tui

Terminal UI framework using the Elm Architecture.

Installation

npm install @puppuccino/tui

Or install from GitHub:

npm install github:averagejoeslab/tui

Features

  • Elm Architecture - Model-Update-View pattern for predictable state management
  • Message-Based Updates - Type-safe messages drive all state changes
  • Command System - Handle async operations cleanly
  • Built-in Messages - Key, mouse, window resize, focus, paste events
  • Input Handling - Full keyboard support with modifier keys
  • Terminal Features - Alternate screen, mouse mode, bracketed paste

Quick Start

import { run, Model, Cmd, Key, KeyMsg, isKeyMsg, quit } from '@puppuccino/tui';

// Define your message types
type Msg = KeyMsg | { type: 'tick' };

// Define your model
class Counter implements Model<Msg> {
  constructor(private count: number = 0) {}

  init(): Cmd<Msg> {
    return null;  // No initial command
  }

  update(msg: Msg): [Model<Msg>, Cmd<Msg>] {
    if (isKeyMsg(msg)) {
      if (msg.key === 'q' || msg.isCtrl('c')) {
        return [this, quit()];
      }
      if (msg.key === Key.Up) {
        return [new Counter(this.count + 1), null];
      }
      if (msg.key === Key.Down) {
        return [new Counter(this.count - 1), null];
      }
    }
    return [this, null];
  }

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

// Run the program
run(new Counter());

Core Concepts

Model

The Model interface defines your application's state and behavior:

interface Model<M extends Msg> {
  init(): Cmd<M>;                         // Initial command
  update(msg: M): [Model<M>, Cmd<M>];     // State transition
  view(): string;                          // Render to string
}

Messages

Messages are events that trigger state updates:

import {
  KeyMsg,          // Keyboard input
  MouseMsg,        // Mouse events
  WindowSizeMsg,   // Terminal resize
  FocusMsg,        // Focus gained/lost
  PasteMsg,        // Pasted text
  TickMsg,         // Timer tick
  CustomMsg,       // User-defined messages
} from '@puppuccino/tui';

// Check message types
if (isKeyMsg(msg)) {
  console.log(`Key pressed: ${msg.key}`);
  console.log(`Modifiers: ctrl=${msg.ctrl} alt=${msg.alt} shift=${msg.shift}`);
}

Commands

Commands handle side effects and async operations:

import {
  quit,           // Exit the program
  none,           // Do nothing
  tick,           // Timer that sends a message
  tickMsg,        // Timer with TickMsg
  timeout,        // One-shot timer
  exec,           // Execute async function
  batch,          // Run multiple commands
} from '@puppuccino/tui';

// Timer example
update(msg: Msg): [Model<Msg>, Cmd<Msg>] {
  if (msg.type === 'start') {
    return [this, tickMsg(1000, 'timer-id')];
  }
  return [this, null];
}

// Async operation
const loadData: Cmd<DataMsg> = exec(
  () => fetch('/api/data').then(r => r.json()),
  (data) => ({ type: 'dataLoaded', data }),
  (error) => ({ type: 'error', error })
);

Built-in Messages

KeyMsg

import { KeyMsg, Key, isKeyMsg } from '@puppuccino/tui';

// In your update function
if (isKeyMsg(msg)) {
  // Check specific keys
  if (msg.key === Key.Enter) { /* ... */ }
  if (msg.key === Key.Escape) { /* ... */ }
  if (msg.key === Key.Up) { /* ... */ }

  // Check with modifiers
  if (msg.isCtrl('c')) { /* Ctrl+C */ }
  if (msg.isAlt('x')) { /* Alt+X */ }

  // Check modifiers directly
  if (msg.ctrl && msg.key === 's') { /* Ctrl+S */ }
}

Available keys:

  • Control: Enter, Tab, Backspace, Escape, Space, Delete
  • Arrows: Up, Down, Left, Right
  • Navigation: Home, End, PageUp, PageDown, Insert
  • Function: F1 - F12

WindowSizeMsg

import { WindowSizeMsg, isWindowSizeMsg } from '@puppuccino/tui';

if (isWindowSizeMsg(msg)) {
  console.log(`Terminal size: ${msg.width}x${msg.height}`);
}

PasteMsg

import { PasteMsg, isPasteMsg } from '@puppuccino/tui';

if (isPasteMsg(msg)) {
  console.log(`Pasted text: ${msg.text}`);
}

Program Options

import { run, ProgramOptions } from '@puppuccino/tui';

const options: ProgramOptions = {
  altScreen: true,       // Use alternate screen buffer
  mouse: true,           // Enable mouse support
  bracketedPaste: true,  // Enable bracketed paste mode
  reportFocus: true,     // Report focus changes
  fps: 60,               // Render frame rate
  title: 'My App',       // Window title
};

run(model, options);

Advanced Usage

Custom Messages

import { CustomMsg, isCustomMsg } from '@puppuccino/tui';

// Send custom messages
const cmd = send('userLoaded', { id: 1, name: 'Alice' });

// Check for custom messages
if (isCustomMsg<User>(msg, 'userLoaded')) {
  console.log(msg.payload.name);  // Type-safe access
}

Batch Commands

import { batch, tick, exec } from '@puppuccino/tui';

// Run multiple commands together
const cmd = batch(
  tick(100, () => ({ type: 'tick' })),
  exec(loadData, onSuccess, onError)
);

Mapping Messages

import { map } from '@puppuccino/tui';

// Transform a command's message type
const mappedCmd = map(
  childCmd,
  (childMsg) => ({ type: 'childMessage', msg: childMsg })
);

Interval Timer

import { every } from '@puppuccino/tui';

// Create a repeating timer
const [cmd, cancel] = every(1000, (time) => ({ type: 'tick', time }));

// Later, cancel the timer
cancel();

Complete Example

import {
  run,
  Model,
  Cmd,
  Key,
  KeyMsg,
  TickMsg,
  isKeyMsg,
  isTickMsg,
  tickMsg,
  batch,
  quit,
} from '@puppuccino/tui';

type Msg = KeyMsg | TickMsg;

class App implements Model<Msg> {
  constructor(
    private time: Date = new Date(),
    private running: boolean = true
  ) {}

  init(): Cmd<Msg> {
    return tickMsg(1000, 'clock');
  }

  update(msg: Msg): [Model<Msg>, Cmd<Msg>] {
    if (isKeyMsg(msg)) {
      if (msg.key === 'q') return [this, quit()];
      if (msg.key === ' ') {
        return [new App(this.time, !this.running), null];
      }
    }

    if (isTickMsg(msg) && msg.id === 'clock') {
      const nextCmd = this.running ? tickMsg(1000, 'clock') : null;
      return [new App(new Date(), this.running), nextCmd];
    }

    return [this, null];
  }

  view(): string {
    const status = this.running ? '▶ Running' : '⏸ Paused';
    return `
  ╭─────────────────────────╮
  │  ${this.time.toLocaleTimeString().padEnd(19)} │
  │  ${status.padEnd(21)} │
  ╰─────────────────────────╯

  Space: pause/resume  Q: quit
`;
  }
}

run(new App(), { altScreen: true });

API Reference

Core

  • run(model, options?) - Run a TUI program
  • create(model, options?) - Create program without running
  • Program - Program class with run(), send(), quit() methods

Types

  • Model<M> - Model interface
  • Cmd<M> - Command type
  • Msg - Base message type
  • ProgramOptions - Configuration options

Messages

  • QuitMsg - Quit the program
  • KeyMsg - Keyboard input
  • WindowSizeMsg - Terminal resize
  • FocusMsg - Focus change
  • MouseMsg - Mouse event
  • PasteMsg - Pasted text
  • TickMsg - Timer tick
  • ErrorMsg - Error occurred
  • CustomMsg<T> - Custom message

Commands

  • quit() - Exit program
  • none() - No operation
  • tick(ms, fn) - Timer
  • tickMsg(ms, id?) - Timer with TickMsg
  • timeout(ms, msg) - One-shot timer
  • every(ms, fn) - Repeating timer
  • exec(fn, success, error?) - Async operation
  • send(tag, payload) - Send custom message
  • batch(...cmds) - Combine commands
  • map(cmd, fn) - Transform message

Keys

  • Key - Key name constants
  • parseKey(data) - Parse key from input
  • matches(key, name, modifiers?) - Match key

License

MIT