Awesome-omni-skill ts-tui-game-skill

Build beautiful, performant TUI applications and games with TypeScript and blessed. Provides patterns, templates, and best practices for terminal interfaces.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/ts-tui-game-skill-neversight" ~/.claude/skills/diegosouzapw-awesome-omni-skill-ts-tui-game-skill && rm -rf "$T"
manifest: skills/development/ts-tui-game-skill-neversight/SKILL.md
source content

TS TUI Game Skill

Build beautiful, performant TUI applications and games with TypeScript and blessed.

Triggers

Use this skill when:

  • Creating a new TUI application or game
  • Building terminal-based interfaces with blessed
  • Implementing game loops, render schedulers, or input handling in terminals
  • Designing layouts for terminal applications
  • Working with blessed widgets (Box, List, Form, etc.)
  • Optimizing terminal rendering performance
  • Adding keyboard and mouse input handling
  • Creating modals, panels, or complex UI compositions

Keywords: tui, terminal, blessed, ncurses, console, cli game, terminal game, terminal ui, text interface

Core Instructions

When helping with blessed TUI development, follow these principles:

Project Setup

  1. Always use TypeScript with strict mode

  2. Recommended screen options:

    const screen = blessed.screen({
      smartCSR: true,      // Efficient change-scroll-region rendering
      autoPadding: true,   // Automatic border/padding handling
      fullUnicode: true,   // Support for box drawing and unicode
      warnings: true,      // Development warnings
    });
    
  3. Directory structure - Separate game logic from UI:

    src/
      main.ts              # Entrypoint, screen init, lifecycle
      ui/
        app.ts             # Root composition, layout creation
        theme.ts           # Centralized theme tokens
        widgets/           # Custom widgets
        panels/            # Composed windows
        input/
          keymap.ts        # Key binding definitions
          input-router.ts  # Focus-aware dispatch
      game/
        state.ts           # Authoritative state types
        reducer.ts         # Pure state updates
        systems/           # Simulation systems
      engine/
        scheduler.ts       # Tick + render scheduling
        events.ts          # Event bus
    

Rendering Rules

CRITICAL: Never call

screen.render()
directly from event handlers.

Use a render scheduler pattern:

export function createRenderScheduler(screen: blessed.Widgets.Screen, maxFps = 30) {
  let pending = false;
  const frameMs = Math.floor(1000 / maxFps);

  const timer = setInterval(() => {
    if (!pending) return;
    pending = false;
    screen.render();
  }, frameMs);

  return {
    requestRender() { pending = true; },
    stop() { clearInterval(timer); }
  };
}

State Management

  1. Single authoritative state - One state object owned by game core
  2. Pure reducers - Actions in, state out, no side effects
  3. UI dispatches actions - Never mutate state directly from widgets

Layout Guidelines

Standard 3-pane game layout:

  • Main viewport: map/scene (largest, fluid)
  • Right sidebar: stats, inventory (24-32 cols fixed)
  • Bottom log: messages, hints (7-12 rows fixed)
const sidebarWidth = 30;
const logHeight = 9;

const main = blessed.box({
  parent: screen,
  top: 0, left: 0,
  width: `100%-${sidebarWidth}`,
  height: `100%-${logHeight}`,
  border: 'line',
});

Theme System

Centralize all colors and styles:

export const theme = {
  fg: 'white',
  bg: 'black',
  panel: { fg: 'white', bg: 'black', border: { fg: '#888888' } },
  accent: { fg: 'black', bg: '#f4d03f' },
  danger: { fg: 'white', bg: 'red' },
  muted: { fg: '#aaaaaa', bg: 'black' },
};

Rule: Pick 1 accent color and 1 danger color. Everything else muted.

Input Handling

Implement a router pattern for context-aware input:

function onKey(ch: string, key: blessed.Widgets.Events.IKeyEventArg) {
  const name = key.full || ch;

  if (globalKeys[name]) return globalKeys[name]();
  if (state.ui.modal) return modalHandler(ch, key);
  if (state.ui.activePanel === 'map') return mapHandler(ch, key);
}
screen.on('keypress', onKey);

Standard controls:

  • Movement: arrows + hjkl
  • Help:
    ?
  • Cycle focus:
    tab
  • Confirm:
    enter
  • Back/close:
    esc
  • Quit:
    q
    or
    C-c

Performance Rules

Hard rules:

  • Never
    screen.render()
    in tight loops
  • Never rebuild widget trees every frame
  • Never create new elements every tick without destroying them

Soft rules:

  • Update content/styles on existing elements
  • One
    setContent()
    per frame for large viewports
  • Keep tag parsing minimal in grid renders
  • Use
    alwaysScroll
    only where necessary

Modal Pattern

  1. Create semi-transparent overlay covering screen
  2. Create centered modal box on top
  3. Capture input in modal
  4. On close:
    destroy()
    both, restore focus

Cleanup (REQUIRED)

function safeExit(screen: blessed.Widgets.Screen, code = 0) {
  try {
    screen.destroy();
  } finally {
    process.exit(code);
  }
}

screen.key(['escape', 'q', 'C-c'], () => safeExit(screen));

Resources

See the

resources/
directory for:

See the

templates/
directory for starter code.

Best Practices Checklist

When reviewing or creating blessed TUI code, verify:

  • One render scheduler controls
    screen.render()
  • Game core is pure and testable (no terminal dependency)
  • Layout uses stable proportions and resize behavior
  • Theme tokens are centralized
  • Controls are discoverable (
    ?
    help, footer hints)
  • Modals capture input and restore focus
  • No per-tick widget creation
  • Exit path calls
    screen.destroy()
    and clears timers
  • Tags used sparingly in large grid renders
  • Keyboard-first design (mouse is bonus)