Skip to content

Comments

feat: add AwaitPrompt command for shell prompt detection#708

Open
MattieTK wants to merge 1 commit intocharmbracelet:mainfrom
MattieTK:feat/await-prompt
Open

feat: add AwaitPrompt command for shell prompt detection#708
MattieTK wants to merge 1 commit intocharmbracelet:mainfrom
MattieTK:feat/await-prompt

Conversation

@MattieTK
Copy link

@MattieTK MattieTK commented Feb 20, 2026

Summary

Adds a new AwaitPrompt command that waits for the shell to render a new prompt before proceeding. This provides a reliable, timing-independent way to synchronise tape playback with command completion.

Syntax

AwaitPrompt          # wait using default WaitTimeout (15s)
AwaitPrompt@30s      # wait with explicit timeout
AwaitPrompt@2m       # longer timeout for slow commands

Relationship to Wait

Plain Wait (without +Line or +Screen) already does something similar – it defaults to Wait+Line with the pattern />$/, which matches VHS's default > prompt. For the common case of "run a command and wait for the shell to be ready", both commands achieve the same result.

The difference is in reliability. Wait matches visible text in the terminal buffer, which means:

  • False positives: if a command outputs a line ending in > (compilation output, markdown, git log, etc.), Wait returns early before the command has actually finished
  • Custom prompts: if the prompt doesn't end with >, plain Wait stops working unless the user also sets WaitPattern
  • Buffer issues: Wait+Screen has a known bug with scrolled content (Wait+Screen regex fails to match text in the viewport after scrolling #659); Wait+Line is less affected but still reads from the buffer

AwaitPrompt avoids these problems by detecting an invisible marker in the raw terminal stream rather than matching visible text. It works regardless of what the prompt looks like or what the command outputs.

Could Wait be reworked instead?

Rather than adding a separate command, the OSC 7777 marker mechanism could potentially be folded into Wait itself – making plain Wait (with no regex) use prompt detection by default, while Wait /pattern/, Wait+Line, and Wait+Screen continue to use regex matching as they do today.

This would mean existing tapes that use bare Wait would silently become more reliable, with no syntax changes needed. We're happy to rework the implementation in that direction if the maintainers prefer it.

For now, this PR introduces AwaitPrompt as a distinct command to make the behaviour explicit and avoid changing existing Wait semantics.

How it works

A central challenge discussed in #70 is that ttyd has no API to expose the state of child processes (confirmed by the ttyd maintainer in tsl0922/ttyd#1009). VHS communicates with ttyd through a browser – keystrokes go in, rendered canvas frames come out. There is no side channel for process lifecycle information.

This implementation sidesteps that limitation entirely. Rather than querying ttyd about process state, it reads the raw terminal output stream that already flows from ttyd through xterm.js. The approach follows the direction suggested by @normanr in #70 (using shell prompt detection via escape sequences) and is similar to what @kotborealis implemented with PROMPT_COMMAND in cassette_deck.

The mechanism:

  1. An invisible OSC escape sequence (ESC ] 7777 ; BEL) is embedded in each shell's prompt string
  2. When a command finishes and the shell renders a new prompt, this marker flows through: shell → ttyd → websocket → xterm.js → term.write()
  3. A JavaScript hook on term.write() scans for \x1b]7777; in the raw data before xterm.js processes it, incrementing a counter each time
  4. AwaitPrompt records the current counter and polls until it increases (or the timeout expires)

Because the marker is emitted by the shell's own prompt rendering, it fires precisely when the shell is ready for the next command – regardless of how long the previous command took.

Cross-platform shell support

Every shell supported by VHS embeds the OSC 7777 marker in its prompt:

Shell Marker mechanism Notes
bash \e]7777;\a in PS1 Uses \[...\] to mark as non-printing
zsh \e]7777;\a in PROMPT Uses %{...%} to mark as non-printing
fish printf '\e]7777;\a' in fish_prompt Emitted before the visible prompt
PowerShell [Console]::Write(ESC + ']7777;' + BEL) Direct console write in prompt function
pwsh Same as PowerShell Same mechanism
cmd.exe $E]7777;$E\ in prompt Uses ST (ESC \) instead of BEL, since cmd.exe's prompt command has no BEL code
nushell print -n with marker in PROMPT_COMMAND Uses nushell's direct output
osh Same as bash Shares bash's PS1 format
xonsh \033]7777;\007 in PROMPT Uses octal escapes

A test (TestShellPromptMarker) verifies that all 9 shells include the 7777 marker.

Motivation / Related discussions

Test plan

  • Parser tests: bare AwaitPrompt, with @timeout (seconds, milliseconds, minutes)
  • Command count test updated for the new command
  • Shell prompt marker test: all 9 shells verified to include OSC 7777
  • Full test suite passes (go test ./...)
  • Manual testing with real tape files across available shells

Note

This PR was authored with AI assistance (Claude Code).

Add AwaitPrompt, a command that waits for the shell to emit a new
prompt rather than matching terminal content. Each shell's prompt
configuration embeds an invisible OSC 7777 marker; a JavaScript
write hook in xterm.js counts these markers as they flow through
the terminal stream.

Syntax:
  AwaitPrompt        # wait using default WaitTimeout
  AwaitPrompt@30s    # wait up to 30 seconds

Supported shells: bash, zsh, fish, powershell, pwsh, cmd.exe,
nushell, osh, xonsh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MattieTK MattieTK marked this pull request as ready for review February 20, 2026 07:53
@MattieTK MattieTK requested a review from a team as a code owner February 20, 2026 07:53
@MattieTK MattieTK requested review from andreynering and aymanbagabas and removed request for a team February 20, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant