feat: add AwaitPrompt command for shell prompt detection#708
Open
MattieTK wants to merge 1 commit intocharmbracelet:mainfrom
Open
feat: add AwaitPrompt command for shell prompt detection#708MattieTK wants to merge 1 commit intocharmbracelet:mainfrom
MattieTK wants to merge 1 commit intocharmbracelet:mainfrom
Conversation
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>
b564fd7 to
96cc4ad
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new
AwaitPromptcommand 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
Relationship to
WaitPlain
Wait(without+Lineor+Screen) already does something similar – it defaults toWait+Linewith 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.
Waitmatches visible text in the terminal buffer, which means:>(compilation output, markdown,git log, etc.),Waitreturns early before the command has actually finished>, plainWaitstops working unless the user also setsWaitPatternWait+Screenhas a known bug with scrolled content (Wait+Screenregex fails to match text in the viewport after scrolling #659);Wait+Lineis less affected but still reads from the bufferAwaitPromptavoids 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
Waitbe reworked instead?Rather than adding a separate command, the OSC 7777 marker mechanism could potentially be folded into
Waititself – making plainWait(with no regex) use prompt detection by default, whileWait /pattern/,Wait+Line, andWait+Screencontinue to use regex matching as they do today.This would mean existing tapes that use bare
Waitwould 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
AwaitPromptas a distinct command to make the behaviour explicit and avoid changing existingWaitsemantics.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_COMMANDin cassette_deck.The mechanism:
ESC ] 7777 ; BEL) is embedded in each shell's prompt stringterm.write()term.write()scans for\x1b]7777;in the raw data before xterm.js processes it, incrementing a counter each timeAwaitPromptrecords 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:
\e]7777;\ainPS1\[...\]to mark as non-printing\e]7777;\ainPROMPT%{...%}to mark as non-printingprintf '\e]7777;\a'infish_prompt[Console]::Write(ESC + ']7777;' + BEL)promptfunction$E]7777;$E\inpromptESC \) instead of BEL, since cmd.exe'spromptcommand has no BEL codeprint -nwith marker inPROMPT_COMMANDPS1format\033]7777;\007inPROMPTA test (
TestShellPromptMarker) verifies that all 9 shells include the7777marker.Motivation / Related discussions
PROMPT_COMMANDapproachWaitpartially addressed this but requires knowing the expected output patternPROMPT_COMMANDleaking into VHS recordingsTest plan
AwaitPrompt, with@timeout(seconds, milliseconds, minutes)go test ./...)Note
This PR was authored with AI assistance (Claude Code).