vhs
vhs copied to clipboard
Wait for command to finish before continuing
Is it possible to wait for a command to finish before you continue typing.
I know I can use Sleep to wait but I might not know how long the command will take and it could be different every time.

this was discussed a bit in https://github.com/charmbracelet/vhs/issues/14, but I agree we should do something about it
Yep! Thanks for this @jacobtomlinson, we'll definitely introduce something for this since it's probably the most common usecase. It will probably look something like one of these but other suggestions are welcome.
Run "sleep 2 && echo 'First!'"
Exec "sleep 2 && echo 'First!'"
Execute "sleep 2 && echo 'First!'"
I'm working on a PR for this. Will this support shell scripts? E.g.,
Exec "./script.sh"
or
Exec@file "./script.sh"
cc: @caarlos0 @maaslalani
IMHO, exec should:
- type the command
- press enter
- wait for the command to finish
so, IMHO there's no need to handle executing files, its just a Type ./file.sh; Enter
That said, not sure if we should or not support the Type options here too (like speed), but I guess we should
Another alternative is to have a Wait command that way we don't need to add all the options for typing speed and enter delays.
Type@100ms "./file" Sleep 0.5 Enter Wait
I tried implementing this but I am afraid I couldn't find a way to wait. We're using ttyd and AFAICT, there's no way of knowing the state of a process that is running inside ttyd.
Pointers are appreciated.
P.S., any reason we're using ttyd over GoTTY?
@abhijit-hota I had a similar concern. Is this possible with GoTTY? We can switch over to that.
I used ttyd since it looked more active and maintained.
Is this possible with GoTTY?
Doesn't seem so from the documentation.
I used ttyd since it looked more active and maintained.
Fair enough. Just checked that.
I have opened a support issue here: https://github.com/tsl0922/ttyd/issues/1009
My plan is to support both ttyd and GoTTY so we can use whichever is installed on the machine.
As per https://github.com/tsl0922/ttyd/issues/1009#issuecomment-1295718599, ttyd cannot tell us that.
We need to get the pid of the process started inside ttyd when pressing Enter to check the state.
I think we can solve this with pgrep:
If someone wants to Exec "sleep 5" we can pgrep "sleep 5" every few milliseconds (100ms?) to wait for it to finish.
What do you think about the method suggested here: https://github.com/tsl0922/ttyd/issues/1009#issuecomment-1295741037
Not sure we can use $! because we can't really type into the ttyd session since we are recording it. pgrep will let us look at the running processes outside the ttyd session which I think is desirable.
Makes sense. There is a chance of conflict between command when using pgrep.
The other thing I was thinking is could we somehow set how long the command appears to take in the GIF, regardless of how long it actually took.
Some commands vary a lot in execution time, I just did a helm install which took ~30 seconds, but if the cache conditions are right it only takes ~5 seconds.
It would be great to tell vcr to wait until the command has finished, but also perhaps only show a fixed 2 second delay and snip out the rest.
Another alternative would be specifying some condition that is polled until it is true, and until then further execution is blocked. But for "wait for command to finish" this is a bit of a complicated way, and might not always be possible (or at least easily).
Some ideas:
- Wait for the shell prompt to be re-displayed. This would require a setting so that VCR knows what to wait for. This is similar to
expect(1). There could be a default value, or override for specific prompt (eg from a subprocess) - Use the shell's prompt command to send a signal to VCR to continue (this doesn't work for prompts within a subprocess)
Can't this be tracked via the process tree? When Enter is executed a new child process will be created, VCR could just poll until that process has finished.
Would that also work for things like command &?
Would you want it to? Adding the & usually means you want to continue without waiting.
Exactly, but the process would still exist, so wouldn't VHS just keep blocking?
Only if the user calls Wait.
I'm suggesting syntax like Type@100ms "./file" Sleep 0.5 Enter Wait that @maaslalani proposed. If you don't want to wait you just don't add the Wait.
Ah, I see; you would still make it an explicit Wait action. Which of course makes sense, since there are blocking processes where you definitely do want to continue doing things (like opening vim).
I played around with VHS a bit trying to automatically generate small demo videos in CI for our documentation site that are always up to date. Without a way to wait for commands to finish that is not possible, as sleeping for a given time interval is not reliable enough when you try to create a video automatically. So :+1: from my side for this feature request.
I've tried to implement this feature in my implementation using PROMPT_COMMAND env variable to send signal from shell back to the controller, and it works nicely. Maybe it could be used in VHS too?
I think the expect approach @normanr suggested would be the most universal.
It would solve things like:
- controlling an Arduino or CNC controller over serial
- logging into an ssh server
- answering interactive prompts when creating a demo for a tool like
terraform
The most significant difficulty I see is determining when to start waiting for the output. Waiting for a prompt would be intuitive, but it may not be evident that you'd need to expect all the previous "prompt> " lines to get where you want to; so maybe a ResetExpect or StartExpect command too?
I propose something like this:
Set ExpectTimeout 3s # timeout Expect after 3 seconds
Set ExpectTimeout 0 # disable Expect timeout (default)
StartExpect # Signals the start of where we should look for the output for Expect
Expect[@timeout] <string> # wait for a string in output since the most recent StartExpect or Expect (or beginning of tape)
Care should be taken so that Expect is interruptable (i.e., Ctrl+C) in case things go wrong :D.
I would throw in that it isn't entirely universal to wait for some output. You might well want to wait for a process to exit without output or for some other state to have been reached. The latter can be considered out of scope, but the former does seem important to me.
@Airblader hmm, that's a good point. Though when working with a shell, you could wait for the prompt to re-appear. Since it is likely the most common case, maybe we can handle it with a default?
Something like:
Set ExpectPrompt ">" # set's the default expect string (usually the prompt string)
Expect[@timeout] [string]
Though a naked Expect doesn't read as well.
Set ExpectPrompt "$"
StartExpect
Type "sleep 1" Enter
Expect
Maybe it's okay, but here's some other ideas:
Set WaitForDefault "$"
StartWaitFor
Type "sleep 1" Enter
WaitFor
Set WaitOutputDefault "$"
StartWaitOutput
Type "sleep 1" Enter
WaitOutput
Type "echo hi" Enter
WaitOutput "hi"
IMHO: Most universal would be to wait for an exit code.
I can't entirely agree that an exit code is universal since commands can be interactive. For example, SSH may prompt for a password without giving an exit code before the input is required. But an exit code would work for most use cases.
That said, it's also a little challenging to go the exit code route because vhs isn't running the commands; instead, it's typing keys to a browser, which gets funneled to ttyd, and records the rendered canvas image it gets back. In other words, it's emulating a keyboard and screen rather than being a shell interpreter.
Something like what @kotborealis suggested could work but may be fragile, as it would only work if the shell is running on the same machine and only if we are not making a tape for a command line tool that's interactive.
I've been experimenting with this all day, and I'm trying to settle on an API that makes sense. I currently have it as Prompt but I'm thinking of changing it to Match and MatchAny. As I've been testing it, there are just too many edge cases so I went with a regex approach.
Match would allow you to specify a regular expression to match against the current line of the terminal (e.g., ^> or ^password:) and MatchAny would match against any string on the screen, allowing you to work with interactive TUI applications.