Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 94 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ pilotty examples # Show end-to-end workflow example
pilotty snapshot # Full JSON with text
pilotty snapshot --format compact # JSON without text field
pilotty snapshot --format text # Plain text with cursor indicator

# Wait for screen to change before returning (no more manual sleep!)
pilotty snapshot --await-change $HASH # Block until hash differs
pilotty snapshot --await-change $HASH --settle 100 # Then wait for stability
```

### Input
Expand Down Expand Up @@ -155,6 +159,11 @@ pilotty resize 120 40 # Resize terminal to 120x40
pilotty wait-for "Ready" # Wait for text to appear
pilotty wait-for "Error" --regex # Wait for regex pattern
pilotty wait-for "Done" -t 5000 # Wait with 5s timeout

# Wait for screen changes (preferred over sleep)
HASH=$(pilotty snapshot | jq '.content_hash')
pilotty key Enter
pilotty snapshot --await-change $HASH --settle 50 # Wait for change + 50ms stability
```

## Snapshot Output
Expand Down Expand Up @@ -204,9 +213,57 @@ pilotty automatically detects interactive UI elements in terminal applications.
| `focused` | Whether element has focus (only present if true) |
| `checked` | Toggle state (only present for toggles) |

### Change Detection
### Wait for Screen Changes

The `--await-change` flag solves the fundamental problem of TUI automation: **"How long should I wait after an action?"**

Instead of guessing sleep durations (too short = race condition, too long = slow), wait for the screen to actually change:

```bash
# Capture baseline hash
HASH=$(pilotty snapshot | jq '.content_hash')

# Perform action
pilotty key Enter

# Wait for screen to change (blocks until hash differs)
pilotty snapshot --await-change $HASH

# Or wait for screen to stabilize (useful for apps that render progressively)
pilotty snapshot --await-change $HASH --settle 100 # Wait 100ms after last change
```

**Flags:**
- `--await-change <HASH>`: Block until `content_hash` differs from this value
- `--settle <MS>`: After change detected, wait for screen to be stable for this many ms
- `-t, --timeout <MS>`: Maximum wait time (default: 30000)

**Why this matters:**
- No more flaky automation due to race conditions
- No more slow scripts due to conservative sleep values
- Works regardless of how fast/slow the target app is
- The `--settle` flag handles apps that render progressively

### Streaming AI Responses

For AI-powered TUIs that stream responses (opencode, etc.), use longer settle times:

```bash
HASH=$(pilotty snapshot -s ai | jq -r '.content_hash')
pilotty type -s ai "explain this code"
pilotty key -s ai Enter

# Wait for streaming to complete: 3s settle, 60s timeout
pilotty snapshot -s ai --await-change "$HASH" --settle 3000 -t 60000
```

- Use `--settle 2000-3000` because AI responses pause between chunks
- Extend timeout with `-t 60000` for longer generations
- Long responses may scroll; use `pilotty scroll up` to see the full output

### Manual Change Detection

The `content_hash` field enables screen change detection between snapshots:
For manual polling, use `content_hash` directly:

```bash
# Get initial snapshot
Expand Down Expand Up @@ -389,18 +446,47 @@ pilotty spawn vim myfile.txt
# 2. Wait for it to be ready
pilotty wait-for "myfile.txt"

# 3. Take a snapshot to understand the screen
pilotty snapshot
# 3. Take a snapshot to understand the screen and capture hash
HASH=$(pilotty snapshot | jq '.content_hash')

# 4. Navigate using keyboard commands
pilotty key i # Enter insert mode
pilotty type "Hello, World!"
pilotty key Escape
pilotty type ":wq"
pilotty key Enter

# 5. Re-snapshot after screen changes
pilotty snapshot
# 5. Wait for screen to update, then save (no manual sleep needed!)
pilotty snapshot --await-change $HASH --settle 50
pilotty key "Escape : w q Enter" # vim :wq sequence

# 6. Verify vim exited
pilotty list-sessions
```

### Example: AI TUI Interaction

For AI-powered terminal apps that stream responses:

```bash
# 1. Spawn the AI app
pilotty spawn --name ai opencode

# 2. Wait for prompt
pilotty wait-for -s ai "Ask anything" -t 15000

# 3. Capture baseline hash, type prompt, submit
HASH=$(pilotty snapshot -s ai | jq -r '.content_hash')
pilotty type -s ai "write a haiku about rust"
pilotty key -s ai Enter

# 4. Wait for streaming response (3s settle, 60s timeout)
pilotty snapshot -s ai --await-change "$HASH" --settle 3000 -t 60000 --format text

# 5. Scroll up if response is long
pilotty scroll -s ai up 10
pilotty snapshot -s ai --format text

# 6. Clean up
pilotty kill -s ai
```

## Key Combinations
Expand Down
20 changes: 19 additions & 1 deletion crates/pilotty-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ Examples:
pilotty snapshot # Snapshot default session (full JSON)
pilotty snapshot --format compact # JSON without text field
pilotty snapshot --format text # Plain text with cursor indicator
pilotty snapshot -s editor # Snapshot a specific session")]
pilotty snapshot -s editor # Snapshot a specific session

Wait for change:
HASH=$(pilotty snapshot | jq -r '.content_hash')
pilotty key Enter
pilotty snapshot --await-change $HASH # Block until screen changes
pilotty snapshot --await-change $HASH --settle 100 # Wait for 100ms stability")]
Snapshot(SnapshotArgs),

/// Type text at the current cursor position
Expand Down Expand Up @@ -147,6 +153,18 @@ pub struct SnapshotArgs {

#[arg(short, long, help = SESSION_HELP)]
pub session: Option<String>,

/// Block until content_hash differs from this value
#[arg(long, value_name = "HASH")]
pub await_change: Option<u64>,

/// Wait for screen to stabilize for this many ms before returning
#[arg(long, default_value_t = 0, value_name = "MS")]
pub settle: u64,

/// Total timeout in milliseconds for await-change and settle combined (default: 30s)
#[arg(short, long, default_value_t = 30000)]
pub timeout: u64,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
Expand Down
Loading
Loading