AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Tui Td

mcp-vurte-tui-td · by vurte

Testing framework for Terminal User Interfaces — capture, drive, screenshot, and assert on TUI applications

No reviews yet
0 installs
12 views
0.0% view→install

Install

$ agentstack add mcp-vurte-tui-td

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-vurte-tui-td)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Tui Td? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

TUI Test Drive

[](https://rubygems.org/gems/tui-td) [](LICENSE.txt)

Testing framework and general-purpose TUI driver. Start a TUI in a PTY, send input, analyze output — as structured JSON, plain text, PNG screenshots, or HTML renders. Use it for testing, automation, or as a bridge between AI agents and terminal apps.

Language-agnostic: JSON tests + CLI + MCP let you drive TUIs from any language (Python, JavaScript, Rust, Go, …). Write a .json test plan, script via tui-td drive, or let an AI agent control a TUI through tui-td serve.

Ruby-native: In the Ruby ecosystem, tui-td integrates with RSpec and Minitest — auto-wait matchers and assertions included.

> New to tui-td? Jump to [Quick Start](docs/quick_start.md).

What tui-td gives you:

  1. Start any TUI in a virtual terminal (PTY) with Driver or JSON test plans
  2. Auto-wait assertions — matchers automatically retry until the condition is met or timeout
  3. Semantic selectorsget_by_role(:button), get_by_role(:dialog), within { } scoping
  4. Multiple output formats — structured JSON, plain text, PNG screenshots, HTML renders
  5. JSON test runner — language-agnostic, 23+ step types, CI-friendly
  6. RSpec matchershave_text, have_fg, have_button, have_dialog, and more
  7. Minitest assertionsassert_text, assert_button, assert_snapshot, and more
  8. MCP server — AI agents can drive TUIs via JSON-RPC over stdio
  9. Pure Ruby rendering — embedded Spleen font + 2766 Unifont glyphs, no native deps required
  10. Video recording — record TUI sessions as MP4 via ffmpeg for demos, debugging, and AI agent playback

Installation

Ruby 3.0+ is required. Install via rbenv or brew install ruby.

gem install tui-td

Quick test:

tui-td capture "echo Hello World"

CLI Usage

# Capture output of any terminal command
tui-td capture "ls -la"

# Capture with custom terminal size
tui-td -r 24 -c 80 capture "vim --version"

# Run a command from a specific directory
tui-td -C /path/to/project capture "make test"

# Output as JSON for scripts
tui-td --json capture "ls -la"

# Save as HTML for browser visualization
tui-td --html output.html capture "htop"

# Save as a PNG screenshot
tui-td --screenshot output.png capture "htop"

# Record a session as video (requires ffmpeg)
tui-td --record demo.mp4 capture "htop" --timeout 10

# Drive a TUI interactively
tui-td drive "htop"
# At the > prompt:
#   state       Show current terminal state as pretty JSON
#   raw         Show raw ANSI output (first 2000 chars)
#   key   Send a special key (enter, up, down, tab, escape, ctrl_c, ...)
#   exit        Quit
#     Sent as text + Enter to the TUI

# Start MCP server for AI integration
tui-td serve

CLI Reference

Usage: tui-td  [options]

Commands:
  serve              Start MCP server (JSON-RPC over stdio)
  capture   Run once, capture and display state
  drive     Drive a TUI interactively
  run       Run a TUI app and show live output
  test    Run JSON test file
  help [topic]       Show this help, or help test / help rspec

Examples:
  tui-td capture "ls -la"
  tui-td --screenshot out.png capture "htop" --timeout 5
  tui-td --html out.html capture "glow README.md"
  tui-td -C /my/project capture "make test"
  tui-td drive "vim file.txt" --rows 24 --cols 80
  tui-td test examples/echo_test.json
  tui-td -vl test examples/vim_hello_world.json
  tui-td test examples/login_form.json       # interactive form test (JSON)
  rspec examples/rspec_interactive_spec.rb    # interactive form test (RSpec)
  ruby examples/minitest_interactive_test.rb  # interactive form test (Minitest)
  tui-td serve

Interactive commands (drive mode):
  state              Show terminal state as pretty JSON
  raw                Show raw ANSI output
  elements           Show detected UI elements (buttons, dialogs, etc.)
  key          Send keystroke (enter, tab, escape, up, down, left, right,
                     backspace, ctrl_c, ctrl_d)
               Send text to the TUI
  exitstatus         Show process exit status (nil if running)
  exit               Quit drive mode

Global options:
    -r, --rows N                     Terminal rows (default: 40)
    -c, --cols N                     Terminal cols (default: 120)
    -t, --timeout SECONDS            Timeout in seconds (default: 30)
    -C, --chdir PATH                 Working directory for the command
        --screenshot PATH            Save screenshot (e.g., output.png)
        --html PATH                  Save HTML render (e.g., output.html)
        --record PATH                Record session as video (MP4/WebM, requires ffmpeg)
        --framerate N                Recording framerate (default: 30)
        --codec NAME                 Video codec: libx264, libx265, libvpx-vp9 (default: libx264)
        --json                       Output state as compact JSON
        --pretty                     Output state as pretty JSON
        --text                       Output state as plain text table
    -v, --verbose                    Show each test step as it runs
    -l, --live                       Show terminal state after each step (screen-refresh)
    -s, --step                       Pause after each test step for confirmation
        --version                    Show version
    -h, --help                       Show help

tui-td --help serves as the full CLI reference. tui-td help test shows all JSON test step types, tui-td help rspec shows all RSpec matchers, and tui-td help minitest shows all Minitest assertions — no need to consult the docs.

Demo

Step-by-step live view of a vim test — type "Hello World", copy the line, replace "World" with "Sun":

tui-td -vls test examples/vim_hello_world.json

Ruby API

Driver — Start, send, capture

require "tui_td"

driver = TUITD::Driver.new("htop", rows: 40, cols: 120, timeout: 30)
driver.start

# Send input
driver.send("hello world\n")
driver.send_keys(:enter)     # :enter, :tab, :escape
driver.send_keys(:up)        # :up, :down, :left, :right
driver.send_keys(:ctrl_c)    # :ctrl_c, :ctrl_d, :backspace

# Wait for expected output
driver.wait_for_text("> ")
driver.wait_for_stable          # Wait until 300ms of silence
driver.wait_for_exit            # Wait until process ends

# Read output
driver.raw_output               # Raw ANSI string
driver.state_data               # Structured Hash with :raw, :rows, :cursor, :size
driver.state_json                # JSON string (includes raw ANSI)
driver.state_json(pretty: true)  # Pretty JSON

# Visual capture
driver.screenshot("screenshot.png")  # PNG renderer
TUITD::HtmlRenderer.new(driver.state_data).render("output.html")  # HTML renderer
html_string = TUITD::HtmlRenderer.new(driver.state_data).to_html   # HTML string

# Video recording (requires ffmpeg)
driver.start_recording("session.mp4", framerate: 30, codec: "libx264")
driver.recording?                   # => true
driver.stop_recording               # => "session.mp4"

driver.close

State — Analyze terminal content

state = TUITD::State.new(driver.state_data)

# Read text
state.plain_text                # "Hello\n> prompt\n"
state.text_at(row, col, length) # Extract substring at position
state.find_text("error")        # [{row: 2, col: 10, text: "error", full_line: "..."}]

# Inspect cells
state.foreground_at(0, 5)       # "cyan"
state.background_at(0, 5)       # "bright_black"
state.style_at(0, 5)            # {bold: true, italic: false, underline: false}

# AI-optimized compact output
state.to_ai_json
# => {
#   size:    {rows: 40, cols: 120},
#   cursor:  {row: 5, col: 12},
#   text:    "> Hello\n...",
#   highlights: [
#     {row: 0, text: "MyApp v1.0.0", bold: true, fg: "cyan"}
#   ],
#   summary: "Cursor at [5,12]. 1 styled row, colors: fg=cyan."
# }

Full example — Test a TUI programmatically

require "tui_td"

driver = TUITD::Driver.new("my_tui_app", rows: 24, cols: 80)
driver.start

# Wait for the initial prompt
driver.wait_for_text("> ", timeout: 10)

# Send a command
driver.send("list files\n")
driver.wait_for_stable

# Analyze output
state = TUITD::State.new(driver.state_data)

if state.find_text("ERROR").any?
  puts "Bug found!"
  driver.screenshot("error_proof.png")
end

# Check colors
welcome_fg = state.foreground_at(0, 0)
raise "Expected cyan header" unless welcome_fg == "cyan"

# Send more commands, inspect, loop...
driver.send("/quit\n")
driver.wait_for_exit
driver.close

Testing

tui-td supports two test formats: JSON for declarative, framework-agnostic tests, and RSpec for Ruby-native tests with custom matchers.

JSON Test Format

Tests are defined as JSON with a sequence of action steps. Each step maps to a tui-td operation.

Run with the tui-td test command:

tui-td test examples/echo_test.json

Format:

{
  "name": "My test",
  "rows": 24,
  "cols": 80,
  "timeout": 10,
  "chdir": "/path/to/project",
  "before_all": [
    { "start": "my_tui_app", "env": { "DATABASE_URL": "test://" } }
  ],
  "steps": [
    { "wait_for_text": "> " },
    { "send": "hello\n" },
    { "assert_text": "hello" },
    { "assert_regex": "hello|world" },
    { "assert_fg": [0, 0], "is": "cyan" }
  ],
  "after_all": [
    { "close": true }
  ]
}

Available steps:

| Step | Key | Description | |------|-----|-------------| | start | "command" | Start a TUI application | | send | "text" | Send text (use \n for Enter) | | send_key | "key" | Send a special key (enter, up, ctrl_c, ...) | | wait_for_text | "text" | Wait until text appears | | wait_for_stable | — | Wait until output is stable | | assert_text | "text" | Assert that text exists on screen | | assert_not_text | "text" | Assert that text does NOT exist on screen | | assert_regex | "pattern" | Assert that regex pattern matches (e.g. "error\|fail") | | assert_fg | [row, col], "is": "color" | Assert foreground color | | assert_bg | [row, col], "is": "color" | Assert background color | | assert_style | [row, col], "bold": true | Assert cell style (bold, italic, underline) | | wait_for_exit | — | Wait until the process exits | | assert_exit | N | Assert the process exit code equals N | | screenshot | "path" | Save PNG screenshot | | html | "path" | Save HTML render for browser viewing | | assert_button | "text" | Assert a button with given text is visible ([ OK ], (Cancel), `) | | assertdialog | — | Assert a dialog (box-drawing region) is visible | | assertcheckbox | "label", "checked": true | Assert a checkbox with given label (and optionally checked state) | | assertrole | ":button", "text": "OK" | Generic role assertion (:button, :checkbox, :dialog, :statusbar, :progress, :input, :label, :menu, :tab) | | assertinput | "text" (optional) | Assert an input field ([____]) is visible | | assertlabel | "text" | Assert a label (text ending with colon) is visible | | assertmenu | "text" (optional) | Assert a menu bar or dropdown item is visible | | asserttab | "text" | Assert a tab ([Tab1]) is visible | | assertstatusbar | — | Assert a status bar (bottom row with background) is visible | | assertprogressbar | "text" (optional) | Assert a progress bar ([####]) is visible | | startrecording | "path", "framerate": 30 | Start video recording (requires ffmpeg) | | stoprecording | — | Stop recording and finalize video | | assert_recording | true / false | Assert recording is active / not active | | close` | — | Close the TUI |

Example with html step for before/after snapshots:

{
  "name": "Visual diff test",
  "rows": 40, "cols": 120,
  "steps": [
    { "start": "my_tui_app" },
    { "wait_for_stable": true },
    { "html": "/tmp/before.html" },
    { "send": "Help\n" },
    { "wait_for_stable": true },
    { "html": "/tmp/after.html" },
    { "close": true }
  ]
}

Ruby API:

plan = File.read("test/example.json")
result = TUITD::TestRunner.new(plan).run
puts result[:passed]  # => true
result[:results].each { |r| puts "#{r[:step]}: #{r[:passed]} - #{r[:message]}" }

RSpec Tests

Use custom matchers for expressive, Ruby-native TUI tests:

require "tui_td"
require "tui_td/matchers"

RSpec.describe "My TUI" do
  before(:all) do
    @driver = TUITD::Driver.new("my_tui_app", rows: 24, cols: 80)
    @driver.start
  end

  after(:all) { @driver&.close }

  let(:state) { TUITD::State.new(@driver.state_data) }

  it "shows welcome message" do
    expect(state).to have_text("Welcome")
  end

  it "has a cyan header" do
    expect(state).to have_fg("cyan").at(0, 0)
  end

  it "has a blue background on row 3" do
    expect(state).to have_bg("blue").at(3, 0)
  end

  it "has bold text on the first line" do
    expect(state).to have_style.at(0, 0).with(bold: true)
  end
end

Matchers:

| Matcher | Usage | |---------|-------| | have_text("...") | Assert text is present on screen | | have_regex(/pattern/) | Assert regex pattern matches anywhere | | have_fg("color").at(row, col) | Assert foreground color at position | | have_bg("color").at(row, col) | Assert background color at position | | have_style.at(row, col).with(bold: true, ...) | Assert cell style | | have_button("OK") | Assert a button with given text is visible | | have_dialog | Assert a dialog (box-drawing region) is visible | | have_checkbox("Label").checked | Assert a checkbox with given label (chain .checked) | | have_role(:button, text: "OK", checked: true, disabled: false) | Generic role assertion with optional text, checked, disabled filters | | have_input | Assert an input field ([____]) is visible | | have_label("Name") | Assert a label (text ending with colon) is visible | | have_menu | Assert a menu bar or dropdown item is visible | | have_tab("File") | Assert a tab is visible | | have_statusbar | Assert a status bar (bottom row with background) is visible | | have_progress_bar("50%") | Assert a progress bar ([####]) is visible | | have_exit_status(N) | Assert the driver process exit status equals N | | be_recording | Assert that the driver is currently recording video | | have_recorded_video("path.mp4") | Assert that a video file was created and has content |

Minitest Assertions

Include the assertions module for native Minitest support (auto-wait included):

require "tui_td/minitest/assertions"

class MyTUITest  "}}}

// 3. Send a command
{"method": "tools/call", "params": {"name": "tui_send", "arguments": {"text": "Write hello.rb\n"}}}

// 4. Wait for output
{"method": "tools/call", "params": {"name": "tui_wait_for_stable", "arguments": {}}}

// 5. Get AI-friendly state
{"method": "tools/call", "params": {"name": "tui_state", "arguments": {"format": "ai"}}}

// 6. Take screenshot if needed
{"method": "tools/call", "params": {"name": "tui_screenshot", "arguments": {"path": "/tmp/proof.png"}}}

// 7. Render as HTML (save to file or get inline)
{"method": "tools/call", "params": {"name": "tui_html_render", "arguments": {"path": "/tmp/proof.html"}}}
// Or without path to get HTML inline:
// {"method": "tools/call", "params": {"name": "tui_html_render", "arguments": {}}}

// 8. Search for text in the terminal
{"method": "tools/call", "params": {"name": "tui_find_text", "arguments": {"pattern": "error|fail"}}}

// 9. Find UI elements by role
{"method": "tools/call", "params": {"name": "tui_find_elements", "arguments": {"role": "button"}}}

// 10. Get actions for an element
{"method": "tools/call", "params": {"name": "tui_element_actions", "arguments": {"role": "button", "text": "OK"}}}

// 11. Check exit status (or wait for exit)
{"method": "tools/call", "params": {"name": "tui_exit_status", "arguments": {}}}

// 12. Clean up
{"method": "tools/call", "params": {

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [vurte](https://github.com/vurte)
- **Source:** [vurte/tui-td](https://github.com/vurte/tui-td)
- **License:** MIT
- **Homepage:** https://rubygems.org/gems/tui-td

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.