GitHub - remorses/tuistory: Playwright for terminal user interfaces

5 min read Original article ↗

tuistory

Playwright for terminal user interfaces

Write end-to-end tests for terminal applications

Installation

# As a library
bun add tuistory
npm install tuistory

# As a CLI (global)
bun add -g tuistory
npm install -g tuistory

# Or use directly with npx/bunx
npx tuistory --help

CLI Usage

tuistory provides a CLI for interacting with terminal sessions from the command line or shell scripts.

Quick Start

# Launch Claude Code
tuistory launch "claude" -s claude --cols 150 --rows 45

# Wait for it to load
tuistory -s claude wait "Claude Code" --timeout 15000

# Type a prompt
tuistory -s claude type "what is 2+2? reply with just the number"
tuistory -s claude press enter

# Wait for Claude's response using regex (matches any digit)
tuistory -s claude wait "/[0-9]+/" --timeout 30000

# Get terminal snapshot
tuistory -s claude snapshot --trim
# Output:
# ╭─────────────────────────────────────────────────────────────────────────────────╮
# │ ● Claude Code                                                                   │
# ╰─────────────────────────────────────────────────────────────────────────────────╯
#
# > what is 2+2? reply with just the number
#
# 4
#
# ────────────────────────────────────────────────────────────────────────────────────
# > 

# Close the session
tuistory -s claude close

Debugging Node.js with tuistory

# Launch Node.js debugger (assuming app.js has a debugger statement)
tuistory launch "node inspect app.js" -s debug --cols 120

# Wait for debugger to start and continue to breakpoint
tuistory -s debug wait "Break on start"
tuistory -s debug type "cont"
tuistory -s debug press enter
tuistory -s debug wait "break in"
tuistory -s debug snapshot --trim

# Print stack trace with bt (backtrace)
tuistory -s debug type "bt"
tuistory -s debug press enter
tuistory -s debug snapshot --trim
# Output:
# #0 getPrice app.js:13:2
# #1 calculateTotal app.js:7:15
# #2 processOrder app.js:2:16

# Enter REPL mode to inspect variables
tuistory -s debug type "repl"
tuistory -s debug press enter
tuistory -s debug type "item"
tuistory -s debug press enter
tuistory -s debug snapshot --trim
# Output:
# > item
# { name: 'Widget', price: 25, quantity: 2 }

# Clean up
tuistory -s debug close

CLI Commands Reference

tuistory launch <command>     # Start a terminal session
tuistory snapshot             # Get terminal text content
tuistory type <text>          # Type text character by character
tuistory press <key> [keys]   # Press key(s): enter, ctrl c, alt f4
tuistory click <pattern>      # Click on text matching pattern
tuistory wait <pattern>       # Wait for text (supports /regex/)
tuistory wait-idle            # Wait for terminal to stabilize
tuistory scroll <up|down>     # Scroll the terminal
tuistory resize <cols> <rows> # Resize terminal
tuistory close                # Close a session
tuistory sessions             # List active sessions
tuistory daemon-stop          # Stop the background daemon

Options

-s, --session <name>  # Session name (required for most commands)
--cols <n>            # Terminal columns (default: 120)
--rows <n>            # Terminal rows (default: 36)
--env <key=value>     # Environment variable (repeatable)
--timeout <ms>        # Wait timeout in milliseconds
--trim                # Trim whitespace from snapshot
--json                # Output as JSON

Tips for Successful Automation

Run snapshot after every command - Terminal applications are stateful and may show dialogs, prompts, or errors. Always check the current state:

tuistory -s mysession press enter
tuistory -s mysession snapshot --trim  # See what happened

Handle interactive dialogs - Many CLI applications show first-run dialogs (trust prompts, terms acceptance, login screens). You need to navigate these before your automation can proceed:

# Example: Claude Code may show trust/terms dialogs on first run
tuistory launch "claude" -s claude
tuistory -s claude snapshot --trim          # Check current state
tuistory -s claude press enter              # Accept dialog
tuistory -s claude snapshot --trim          # Verify it worked

Ensure applications are authenticated - Some CLIs require login. Run authentication commands first:

tuistory -s claude type "/login"
tuistory -s claude press enter
tuistory -s claude snapshot --trim          # Follow login prompts

Use wait for async operations - Don't assume commands complete instantly:

tuistory -s mysession type "long-running-command"
tuistory -s mysession press enter
tuistory -s mysession wait "Done" --timeout 60000  # Wait for completion
tuistory -s mysession snapshot --trim

Debug with frequent snapshots - When automation fails, add snapshots between each step to see where it went wrong.

Library Usage

Use tuistory programmatically in your tests or scripts:

import { launchTerminal } from 'tuistory'

const session = await launchTerminal({
  command: 'claude',
  args: [],
  cols: 150,
  rows: 45,
})

await session.waitForText('claude', { timeout: 10000 })

const initialText = await session.text()
expect(initialText).toMatchInlineSnapshot(`
  "
  ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
  │ Welcome to Claude Code                                                                           │
  ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
  "
`)

await session.type('/help')
await session.press('enter')

const helpText = await session.text()
expect(helpText).toMatchInlineSnapshot(`
  "
   ▐▛███▜▌   Claude Code v2.0.53
  ▝▜█████▛▘  Opus 4.5 · Claude Max
    ▘▘ ▝▝    ~/my-project

  ────────────────────────────────────────────────────────────────────────────────────────────────────
  > Try "create a util logging.py that..."
  ────────────────────────────────────────────────────────────────────────────────────────────────────
  "
`)

await session.press(['ctrl', 'c'])
session.close()

API

launchTerminal(options)

Launch a terminal session.

const session = await launchTerminal({
  command: 'my-cli',
  args: ['--flag'],
  cols: 120,
  rows: 36,
  cwd: '/path/to/dir',
  env: { MY_VAR: 'value' },
})

session.type(text)

Type a string character by character.

await session.type('hello world')

session.press(keys)

Press a single key or a chord (multiple keys simultaneously).

await session.press('enter')
await session.press('tab')
await session.press(['ctrl', 'c'])
await session.press(['ctrl', 'shift', 'a'])

Available keys: enter, esc, tab, space, backspace, delete, up, down, left, right, home, end, pageup, pagedown

Modifiers: ctrl, alt, shift, meta

session.text(options?)

Get the current terminal text.

const text = await session.text()

// Filter by style
const boldText = await session.text({ only: { bold: true } })
const coloredText = await session.text({ only: { foreground: '#ff0000' } })

session.waitForText(pattern, options?)

Wait for text or regex to appear.

await session.waitForText('Ready')
await session.waitForText(/Loading\.\.\./, { timeout: 10000 })

session.click(pattern, options?)

Click on text matching a pattern.

await session.click('Submit')
await session.click(/Button \d+/, { first: true })

session.close()

Close the terminal session.

Projects using tuistory

  • Termcast: A Raycast API re-implementation for the terminal. Turns raycast extensions into TUIs. Agents use tuistory to autonomously convert Raycast extensions into TUIs and fix any missing termcast APIs.
  • Kimaki: Discord bots agents that can use Tuistory to control TTY processes like opencode, claude code and debuggers.

License

MIT