How VCR# Uses ttyd
Overview
This document describes how VCR# internally uses ttyd for terminal emulation. Most ttyd parameters are hardcoded in VCR# and not user-configurable.
ttyd is a terminal server that exposes terminal sessions over HTTP/WebSocket. VCR# uses ttyd version 1.7.2 or later to provide browser-based terminal rendering via xterm.js (the same terminal emulator used in VS Code).
User-Controllable Settings:
Shell- Set viaSet Shell "bash"in tape filesWorkingDirectory- Set viaSet WorkingDirectory "/path"in tape files- Terminal dimensions (
Cols,Rows,Width,Height,FontSize) - Affect xterm.js configuration
All Other Parameters: Hardcoded in VCR# (port selection, binding, timeouts, flags)
Requirement: ttyd must be available in system PATH.
Command VCR# Invokes
ttyd -p -W -i 127.0.0.1 [-w ]
Parameters VCR# Uses
| Option | Value | Source | Rationale |
|---|---|---|---|
-p |
Random port (8000-9000 range) | Hardcoded | Avoids port conflicts, allows concurrent recordings |
-W |
Always set | Hardcoded | Enables non-interactive startup (ttyd doesn't wait for browser) |
-i |
127.0.0.1 |
Hardcoded | Security: localhost-only binding prevents network exposure |
-w |
User's WorkingDirectory or current dir |
Tape file Set WorkingDirectory |
Allows users to control shell start location |
<shell> |
User's Shell or platform default |
Tape file Set Shell or auto-detected |
Supports user's preferred shell |
Why These Choices?
Random port selection: VCR# requests an available port from the OS in the 8000-9000 range. This prevents conflicts if multiple VCR# instances run concurrently and avoids colliding with common development servers (3000, 8080, etc.).
-W flag: Without this flag, ttyd waits for a browser connection before starting the shell. VCR# needs the shell to
start immediately so Playwright can connect on its own schedule.
Localhost-only binding (-i 127.0.0.1): Security measure. ttyd's HTTP server is unauthenticated by default. Binding
to localhost ensures the terminal session isn't exposed to the local network or internet.
Working directory from user: Different recordings often need different start locations (project root, specific
subdirectory, etc.). Making this user-controllable via Set WorkingDirectory is essential.
Shell from user or auto-detect: Users have shell preferences (Bash vs Zsh, PowerShell vs CMD). VCR# respects
Set Shell but falls back to platform defaults when not specified.
Shell Selection
VCR#'s shell auto-detection when Set Shell is not specified in tape files:
Platform Defaults
| Platform | Detection Priority | Rationale |
|---|---|---|
| Windows | pwsh → powershell → cmd | Modern PowerShell (pwsh) preferred, falls back to Windows PowerShell, then CMD |
| Linux | bash | Universal Unix shell, almost always available |
| macOS | bash | Standard macOS shell (Zsh now default but Bash more predictable for recordings) |
How User's Set Shell Maps to ttyd
Set Shell "pwsh" # VCR# invokes: ttyd [...] pwsh
Set Shell "bash" # VCR# invokes: ttyd [...] bash
Set Shell "zsh" # VCR# invokes: ttyd [...] zsh
Set Shell "cmd" # VCR# invokes: ttyd [...] cmd
Set Shell "/bin/bash" # VCR# invokes: ttyd [...] /bin/bash
VCR# passes the shell string directly to ttyd. No validation or path resolution - ttyd will fail if the shell isn't available.
Process Lifecycle
Startup Sequence
- VCR# spawns ttyd process with specified options
- ttyd binds to assigned port
- ttyd starts shell subprocess
- HTTP endpoint becomes available
- WebSocket server starts
- Shell startup files execute (.bashrc, profile.ps1, etc.)
Communication
Browser → ttyd → Shell:
- Keyboard input via WebSocket
- Terminal commands via xterm.js
Shell → ttyd → Browser:
- Terminal output via WebSocket
- Display updates via xterm.js
Shutdown
- VCR# sends termination signal
- ttyd terminates shell subprocess
- ttyd process exits
- Port released
- Resources cleaned up
xterm.js Configuration
VCR# injects configuration into ttyd's xterm.js frontend via Playwright:
| Setting | Value | Source | Rationale |
|---|---|---|---|
| Columns | User's Cols or calculated |
Tape Set Cols or Width/FontSize |
Controls terminal width in characters |
| Rows | User's Rows or calculated |
Tape Set Rows or Height/FontSize |
Controls terminal height in lines |
| Font Size | User's FontSize (default 22px) |
Tape Set FontSize |
User-controllable for readability |
| Font Family | monospace |
Hardcoded | Generic monospace ensures cross-platform consistency |
| Theme Colors | User's Theme |
Tape Set Theme |
User-controllable color schemes |
| Cursor Style | block |
Hardcoded | Most visible cursor style for recordings |
How Configuration Injection Works
- VCR# connects Playwright to ttyd's URL
- Playwright injects CSS into the page (themes, fonts)
- Playwright calls xterm.js JavaScript APIs to configure terminal dimensions
- Terminal renders with injected configuration
Why CSS injection? ttyd doesn't support configuration files. VCR# must inject styling and configuration at runtime via the browser automation layer.
Working Directory
How user's WorkingDirectory setting affects ttyd invocation:
Set WorkingDirectory "C:\\Projects\\MyApp"
# VCR# invokes: ttyd [...] -w "C:\Projects\MyApp" pwsh
Set WorkingDirectory "/home/user/projects"
# VCR# invokes: ttyd [...] -w "/home/user/projects" bash
If WorkingDirectory is not set, VCR# omits the -w flag and ttyd starts the shell in its current working directory (
typically where vcr-sharp was invoked).
Environment Variables
User's Env commands set environment variables for the shell:
Env PATH "/custom/bin:$PATH"
Env NODE_ENV "production"
VCR# passes these to the shell subprocess (via ttyd), not to ttyd itself. They're available to commands executed in the recording.
Terminal Type
ttyd automatically sets TERM=xterm-256color in the shell environment:
- Indicates 256-color terminal capability
- Compatible with most terminal applications
- VCR# does not override this (hardcoded by ttyd)
Port Selection
VCR#'s port allocation strategy:
- Requests random available port from OS (typically 8000-9000 range)
- Binds to localhost (127.0.0.1) only via
-iflag - Protocol: HTTP for initial page load, WebSocket for terminal I/O
Why random ports? Allows multiple VCR# instances to run concurrently without port conflicts. The specific port number doesn't matter since Playwright connects programmatically.
Shell Startup Behavior
ttyd launches shells as interactive login shells, meaning your shell configuration files execute:
What This Means
- Shell configuration files sourced (
.bashrc,profile.ps1, etc.) - Profile scripts execute
- Environment variables from profile loaded
- Shell prompt customization applies
- Aliases and functions available
Why this matters: Your recordings reflect your actual shell environment. If your .bashrc sets custom prompts or
aliases, they'll appear in recordings. This is usually desirable but can cause issues if startup scripts are slow or
produce unexpected output.
Platform-Specific Configuration Files
| Shell | Windows | Linux/macOS |
|---|---|---|
| PowerShell | $PROFILE paths |
~/.config/powershell/profile.ps1 |
| Bash | N/A (Windows doesn't use Bash by default) | ~/.bashrc, ~/.bash_profile |
| Zsh | N/A | ~/.zshrc |
| CMD | N/A (limited startup configuration) | N/A |
Security Model
Why Localhost-Only Binding?
VCR# hardcodes -i 127.0.0.1 for ttyd:
- No external network access: Terminal session not accessible from other machines
- Not visible to local network: Other devices on your network cannot connect
- Only accessible from local machine: Playwright running locally can connect
ttyd's HTTP server has no authentication. Binding to localhost prevents accidentally exposing terminal sessions to the network.
Process Isolation
Each VCR# recording creates an isolated ttyd process:
- No persistent terminal servers: ttyd starts and stops per recording
- Process terminates after recording: No lingering background processes
- No shared state: Each recording gets a fresh shell environment
Why isolation? Ensures recordings are reproducible and don't interfere with each other.
Exit Code Handling
VCR# monitors ttyd exit codes to detect failures:
| Exit Code | Meaning | VCR# Behavior |
|---|---|---|
| 0 | Success | Normal termination, continue to encoding |
| 1 | Generic error | Log error from ttyd stderr, abort recording |
| 127 | Command not found | Display "Shell not found" error, abort recording |
| Other | Various errors | Log error, abort recording |
Why VCR# cares: ttyd failures usually indicate missing dependencies (ttyd not installed, shell not found) or port binding issues. VCR# captures stderr output to provide debugging information.
HTTP Endpoints (For Reference)
ttyd exposes these endpoints (VCR# uses / and /ws):
| Path | Method | VCR# Usage |
|---|---|---|
/ |
GET | Playwright navigates here to access xterm.js terminal |
/token |
POST | Not used (ttyd authentication feature VCR# doesn't need) |
/ws |
WebSocket | Terminal I/O stream (keyboard input → shell, shell output → browser) |
WebSocket Protocol (For Reference)
ttyd's WebSocket communication format (transparent to VCR# users):
| Direction | Format | Content |
|---|---|---|
| Browser → ttyd | JSON | Input events (key presses, paste, resize) |
| ttyd → Browser | Binary | Terminal output (ANSI escape sequences, colors, cursor movements) |
Playwright handles this protocol automatically. VCR# doesn't interact with WebSocket directly.
Timeout Configuration
VCR#'s hardcoded timeouts for ttyd operations:
| Operation | Timeout | Source | Rationale |
|---|---|---|---|
| ttyd startup | 10s default | User's StartWaitTimeout or default |
Shell startup files can be slow (loading profile, etc.) |
| HTTP endpoint ready | Included in startup | Part of StartWaitTimeout |
ttyd must bind port and start HTTP server |
| WebSocket connection | 5s | Hardcoded | Connecting to already-running ttyd should be fast |
| Process termination | 5s | Hardcoded | Graceful shutdown should complete quickly |
User control: Only StartWaitTimeout is configurable via Set StartWaitTimeout 15s. Other timeouts are hardcoded
based on expected operation durations.
ttyd Version Requirements
VCR#'s ttyd version compatibility:
| ttyd Version | Status | Notes |
|---|---|---|
| < 1.7.2 | Not supported | Missing features VCR# depends on |
| 1.7.2 - 1.7.x | Fully supported | Minimum required version, tested extensively |
| 2.x | Expected to work | Not extensively tested but API is stable |
Why 1.7.2 minimum? Earlier versions have WebSocket protocol incompatibilities and missing command-line options that VCR# requires.