#pty #automation #scripting #tty #scripting-automation

bin+lib scriptty

A PTY scripting engine for automating interactive terminal sessions

1 unstable release

Uses new Rust 2024

new 0.1.0 Feb 20, 2026

#1427 in Development tools

Apache-2.0

345KB
1K SLoC

CI

Scriptty - Terminal Proxy Demo Engine

Create realistic, reproducible terminal demos for any interactive CLI — without depending on how the application echoes input.

This project runs a command-line program inside a pseudo-terminal (PTY), decouples execution from presentation, and renders scripted interactions as human-like typing.

Perfect for:

  • README demos
  • CLI tutorials

Example demo for rcypher CLI

Full script for the example is in examples/rcypher.script rcypher demo session

Why This Exists

Tools like expect, script, and even asciinema struggle with realistic demos because:

  • Input echo timing is controlled by the target application
  • Keystrokes often appear only after a newline
  • Readline-based apps redraw internally
  • Typing realism is unreliable and brittle

This tool solves that by design.

Instead of relying on the application to echo input, it introduces a proxy that controls what the viewer sees independently from what the program receives.


Installation

Download from the stable release

Build from source

scriptty is written in Rust. You’ll need the Rust toolchain installed.

1. Install Rust

If you don’t have Rust yet, install it with:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Then restart your shell or run:

source "$HOME/.cargo/env"

Verify installation: rustc --version

2. Clone the repository

git clone https://github.com/justpresident/scriptty.git
cd scriptty

3. Build the project

cargo build --release

The binary will be available at: target/release/scriptty

You can run it directly:

./target/release/scriptty --help

Optional: install globally

If you want scriptty in your $PATH:

cargo install --path .

Then you can run:

scriptty --help

Requirements

  • Linux or macOS (PTY support required)

  • Rust 1.70+ (stable)

  • A terminal that supports ANSI escape sequences

Troubleshooting

If the build fails on Linux, make sure you have:

  • build-essential

  • pkg-config

On macOS, make sure Xcode Command Line Tools are installed:

xcode-select --install


Core Idea

Decouple program execution from terminal presentation.

The program receives input instantly and deterministically.
The viewer sees carefully timed output that looks like real typing.

┌────────────┐
│ Program    │ (runs in PTY)
└─────▲──────┘
      │
┌─────┴──────┐
│ Engine     │ (script + timing)
└─────▲──────┘
      │
┌─────┴──────┐
│ Renderer   │ (what the viewer sees)
└────────────┘

Features

  • 🧠 Program-independent typing simulation
  • 🧪 Deterministic, scriptable demos
  • ⌨️ Realistic per-character typing with jitter
  • ⏱️ Controlled pauses and timing
  • 📼 Compatible with asciinema recordings
  • 🔁 Reproducible demos (great for CI)
  • 🧩 Generic — works with any interactive CLI

How It Works

  1. The target program runs inside a PTY
  2. A script drives interaction deterministically
  3. Input is sent immediately to the program
  4. Output is intercepted and re-rendered
  5. Typing is simulated visually, not echoed

This avoids:

  • line buffering
  • terminal echo quirks
  • readline redraw behavior
  • race conditions

Example Script

# Display narration to the viewer
show "=== Configuration Demo ==="

# Wait for bash prompt
expect "$ "

# Type a command with realistic delays
type "put github.username littlejohnny"
key Enter

# Wait for specific output before continuing
expect "saved"

show "Configuration saved! Now retrieving..."

# Small pause
wait 500ms

# Execute another command
type "get github.*"
key Enter

# Wait for the output
expect "littlejohnny"

Event Model

Internally, every script line maps to a command implementing the ScripttyCommand trait:

pub trait ScripttyCommand: 'static {
    fn name(&self) -> &'static str;
    fn parse(args: &str) -> Result<Self> where Self: Sized;
    async fn execute(&self, ctx: &mut Context) -> Result<()>;
}

Program input and user-visible output are separate streams.

Script Commands

Command Syntax Description
wait wait 1s or wait 500ms Pause execution for specified duration
type type "text here" Simulate realistic typing (50-150ms per char), no implicit newline
send send "text here" Send bytes to program instantly (no typing simulation, no implicit newline)
key key Enter, key Ctrl+C, key Alt+Left Send a key press with optional modifiers (Ctrl+, Alt+, Shift+)
show show "message" Display text directly to viewer (narration, comments)
expect expect "pattern" or expect "pattern" 10s Wait for pattern in output (default 5s timeout)

Project Status

🚧 Early stage / design-first

Planned next steps:

  • YAML / JSON script format

  • ANSI escape parsing (vt100)

  • Built-in asciinema exporter

  • Mistyped input simulation

  • Redaction and masking rules

Why Not Just Use Expect?

Because this tool solves a different problem.

Tool Focus
expect Automate interaction
asciinema Record terminals
this project Present interactions realistically

Inspiration

This project grew out of real-world frustration trying to record high-quality demos of readline-based CLI tools where input appeared instant, robotic, or incorrect.

License

Apache 2.0

Contributing

Ideas, feedback, and design discussions are welcome. If you’ve tried to record CLI demos and hit similar limits — you’re exactly the audience.

Dependencies

~5.5–7.5MB
~127K SLoC