Showing preview only (633K chars total). Download the full file or copy to clipboard to get everything.
Repository: charmbracelet/vhs
Branch: main
Commit: 67e9d1708bbd
Files: 205
Total size: 586.4 KB
Directory structure:
gitextract_5eziu92j/
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── build.yml
│ ├── dependabot-sync.yml
│ ├── goreleaser.yml
│ ├── lint-sync.yml
│ ├── lint.yml
│ └── nightly.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── THEMES.md
├── command.go
├── command_test.go
├── draw.go
├── embed.go
├── error.go
├── evaluator.go
├── examples/
│ ├── README.md
│ ├── bubbletea/
│ │ ├── altscreen-toggle.tape
│ │ ├── chat.tape
│ │ ├── composable-views.tape
│ │ ├── credit-card-form.tape
│ │ ├── debounce.tape
│ │ ├── exec.tape
│ │ ├── fullscreen.tape
│ │ ├── glamour.tape
│ │ ├── help.tape
│ │ ├── http.tape
│ │ ├── list-default.tape
│ │ ├── list-fancy.tape
│ │ ├── list-simple.tape
│ │ ├── package-manager.tape
│ │ ├── pager.tape
│ │ ├── paginator.tape
│ │ ├── pipe.tape
│ │ ├── progress-animated.tape
│ │ ├── progress-static.tape
│ │ ├── realtime.tape
│ │ ├── result.tape
│ │ ├── send-msg.tape
│ │ ├── sequence.tape
│ │ ├── simple.tape
│ │ ├── spinner.tape
│ │ ├── spinners.tape
│ │ ├── split-editors.tape
│ │ ├── stopwatch.tape
│ │ ├── table.tape
│ │ ├── tabs.tape
│ │ ├── textarea.tape
│ │ ├── textinput.tape
│ │ ├── textinputs.tape
│ │ ├── timer.tape
│ │ ├── tui-daemon-combo.tape
│ │ └── views.tape
│ ├── cli-ui/
│ │ ├── Gemfile
│ │ ├── README.md
│ │ ├── format.tape
│ │ ├── interactive-prompt.tape
│ │ ├── nested-frames.tape
│ │ ├── progress.tape
│ │ ├── spinner.tape
│ │ ├── status-widget.tape
│ │ ├── symbols.tape
│ │ ├── text-prompt.ascii
│ │ └── text-prompt.tape
│ ├── commands/
│ │ ├── README.md
│ │ ├── alt.tape
│ │ ├── arrow.tape
│ │ ├── backspace.tape
│ │ ├── clipboard.tape
│ │ ├── comment.tape
│ │ ├── ctrl.tape
│ │ ├── enter.tape
│ │ ├── hide.tape
│ │ ├── show.tape
│ │ ├── space.tape
│ │ ├── tab.tape
│ │ └── type.tape
│ ├── decorations/
│ │ └── decorations.tape
│ ├── demo.tape
│ ├── demo.webm
│ ├── env/
│ │ └── env.tape
│ ├── errors/
│ │ ├── dimensions.tape
│ │ ├── parser.tape
│ │ └── require.tape
│ ├── fixtures/
│ │ └── all.tape
│ ├── gh-cli/
│ │ ├── README.md
│ │ ├── gh-issue.tape
│ │ └── gh-pr.tape
│ ├── glow/
│ │ ├── CarrotCake.md
│ │ ├── NiHao.md
│ │ ├── README.md
│ │ ├── StewedPeaches.md
│ │ ├── glow-edit.tape
│ │ ├── glow-simple.ascii
│ │ ├── glow-simple.tape
│ │ ├── glow.ascii
│ │ ├── glow.tape
│ │ ├── notes/
│ │ │ ├── Currywurst.md
│ │ │ ├── Kasewurst.md
│ │ │ ├── Spatzle.md
│ │ │ └── Weibwurst.md
│ │ └── to-do/
│ │ ├── Okonomiyaki.md
│ │ └── Takoyaki.md
│ ├── gum/
│ │ ├── README.md
│ │ ├── file.tape
│ │ ├── pager.tape
│ │ ├── src/
│ │ │ ├── id_rsa
│ │ │ ├── id_rsa.pub
│ │ │ ├── lipgloss/
│ │ │ │ ├── README.md
│ │ │ │ ├── align.go
│ │ │ │ ├── borders.go
│ │ │ │ ├── colors.go
│ │ │ │ ├── join.go
│ │ │ │ └── style.go
│ │ │ └── super_secret_message.txt
│ │ ├── superhero.csv
│ │ └── table.tape
│ ├── jqp/
│ │ ├── README.md
│ │ └── jqp.tape
│ ├── meta.tape
│ ├── neofetch/
│ │ ├── README.md
│ │ ├── colorize-ascii.go
│ │ ├── neofetch.tape
│ │ ├── neofetch.webm
│ │ ├── vhs-color.ascii
│ │ ├── vhs.ascii
│ │ └── vhs.conf
│ ├── publish/
│ │ ├── cassette.tape
│ │ └── publish.tape
│ ├── screenshot.tape
│ ├── settings/
│ │ ├── README.md
│ │ ├── height.tape
│ │ ├── set-border-radius.tape
│ │ ├── set-cursor-blink.tape
│ │ ├── set-font-family.tape
│ │ ├── set-font-size-10.tape
│ │ ├── set-font-size-20.tape
│ │ ├── set-font-size-40.tape
│ │ ├── set-letter-spacing.tape
│ │ ├── set-line-height.tape
│ │ ├── set-loop-offset.tape
│ │ ├── set-margin.tape
│ │ ├── set-padding.tape
│ │ ├── set-shell-bash.tape
│ │ ├── set-shell-cmd.tape
│ │ ├── set-shell-custom.tape
│ │ ├── set-shell-fish.tape
│ │ ├── set-shell-nu.tape
│ │ ├── set-shell-osh.tape
│ │ ├── set-shell-pwsh.tape
│ │ ├── set-shell-xonsh.tape
│ │ ├── set-shell-zsh.tape
│ │ ├── set-theme-name.tape
│ │ ├── set-theme.tape
│ │ ├── set-typing-speed.tape
│ │ ├── set-window-bar.tape
│ │ └── width.tape
│ ├── slides/
│ │ ├── README.md
│ │ └── slides.tape
│ ├── split/
│ │ └── split.tape
│ ├── vhs-promo.webm
│ └── welcome.tape
├── ffmpeg.go
├── go.mod
├── go.sum
├── keys.go
├── lexer/
│ ├── lexer.go
│ └── lexer_test.go
├── main.go
├── man.go
├── parser/
│ ├── parser.go
│ └── parser_test.go
├── publish.go
├── record.go
├── record_test.go
├── screenshot.go
├── screenshot_test.go
├── scripts/
│ └── download_theme.sh
├── serve.go
├── serve_unix.go
├── serve_windows.go
├── shell.go
├── style.go
├── syntax.go
├── testing.go
├── themes.go
├── themes.json
├── themes_test.go
├── token/
│ ├── token.go
│ └── token_test.go
├── tty.go
├── tty_unix.go
├── tty_windows.go
├── vhs.go
└── video.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
**/*.gif filter=lfs diff=lfs merge=lfs -text
**/*.mp4 filter=lfs diff=lfs merge=lfs -text
**/*.webm filter=lfs diff=lfs merge=lfs -text
**/*.png filter=lfs diff=lfs merge=lfs -text
*.tape linguist-language=elixir
themes*.json linguist-generated
THEMES.md linguist-generated
================================================
FILE: .github/CODEOWNERS
================================================
* @charmbracelet/everyone
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Setup**
Please complete the following information along with version numbers, if applicable.
- OS [e.g. Ubuntu, macOS]
- Shell [e.g. zsh, fish]
- Terminal Emulator [e.g. kitty, iterm]
- Terminal Multiplexer [e.g. tmux]
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Source Code**
Please include source code if needed to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
Add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
- name: Discord
url: https://charm.sh/discord
about: Chat on our Discord.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
timezone: "America/New_York"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
groups:
all:
patterns:
- "*"
ignore:
- dependency-name: github.com/charmbracelet/bubbletea/v2
versions:
- v2.0.0-beta1
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
timezone: "America/New_York"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
groups:
all:
patterns:
- "*"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
timezone: "America/New_York"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
groups:
all:
patterns:
- "*"
================================================
FILE: .github/workflows/build.yml
================================================
name: build
on: [push, pull_request]
jobs:
build:
uses: charmbracelet/meta/.github/workflows/build.yml@main
snapshot:
uses: charmbracelet/meta/.github/workflows/snapshot.yml@main
secrets:
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
================================================
FILE: .github/workflows/dependabot-sync.yml
================================================
name: dependabot-sync
on:
schedule:
- cron: "0 0 * * 0" # every Sunday at midnight
workflow_dispatch: # allows manual triggering
permissions:
contents: write
pull-requests: write
jobs:
dependabot-sync:
uses: charmbracelet/meta/.github/workflows/dependabot-sync.yml@main
with:
repo_name: ${{ github.event.repository.name }}
secrets:
gh_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
================================================
FILE: .github/workflows/goreleaser.yml
================================================
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: goreleaser
on:
push:
tags:
- v*.*.*
concurrency:
group: goreleaser
cancel-in-progress: true
jobs:
goreleaser:
uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main
secrets:
docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
docker_token: ${{ secrets.DOCKERHUB_TOKEN }}
gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
aur_key: ${{ secrets.AUR_KEY }}
fury_token: ${{ secrets.FURY_TOKEN }}
nfpm_gpg_key: ${{ secrets.NFPM_GPG_KEY }}
nfpm_passphrase: ${{ secrets.NFPM_PASSPHRASE }}
macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }}
macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }}
macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }}
macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }}
================================================
FILE: .github/workflows/lint-sync.yml
================================================
name: lint-sync
on:
schedule:
# every Sunday at midnight
- cron: "0 0 * * 0"
workflow_dispatch: # allows manual triggering
permissions:
contents: write
pull-requests: write
jobs:
lint:
uses: charmbracelet/meta/.github/workflows/lint-sync.yml@main
================================================
FILE: .github/workflows/lint.yml
================================================
name: lint
on:
push:
pull_request:
jobs:
lint:
uses: charmbracelet/meta/.github/workflows/lint.yml@main
with:
golangci_path: .golangci.yml
================================================
FILE: .github/workflows/nightly.yml
================================================
name: nightly
on:
push:
branches:
- main
jobs:
nightly:
uses: charmbracelet/meta/.github/workflows/nightly.yml@main
secrets:
docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
docker_token: ${{ secrets.DOCKERHUB_TOKEN }}
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }}
macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }}
macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }}
macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }}
================================================
FILE: .gitignore
================================================
.DS_Store
.ssh/
captures/
tmp/
frames/
manpages
completions
dist
vhs
.idea/
.vscode/
================================================
FILE: .golangci.yml
================================================
version: "2"
run:
tests: false
linters:
enable:
- bodyclose
- exhaustive
- goconst
- godot
- gomoddirectives
- goprintffuncname
- gosec
- misspell
- nakedret
- nestif
- nilerr
- noctx
- nolintlint
- prealloc
- revive
- rowserrcheck
- sqlclosecheck
- tparallel
- unconvert
- unparam
- whitespace
- wrapcheck
exclusions:
rules:
- text: '(slog|log)\.\w+'
linters:
- noctx
- text: "var-naming"
linters:
- revive
- text: "G705"
linters:
- gosec
- text: "G115"
linters:
- gosec
- text: "os/exec.Command must not be called"
linters:
- noctx
generated: lax
presets:
- common-false-positives
settings:
exhaustive:
default-signifies-exhaustive: true
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- gofumpt
- goimports
exclusions:
generated: lax
================================================
FILE: .goreleaser.yml
================================================
version: 2
includes:
- from_url:
url: charmbracelet/meta/main/goreleaser-vhs.yaml
variables:
main: "."
description: "A tool for recording terminal GIFs"
github_url: "https://github.com/charmbracelet/vhs"
maintainer: "Maas Lalani <maas@charm.sh>"
brew_commit_author_name: "Maas Lalani"
brew_commit_author_email: "maas@charm.sh"
================================================
FILE: Dockerfile
================================================
FROM tsl0922/ttyd:alpine as ttyd
FROM alpine:latest as fontcollector
# Install Fonts
RUN apk add --no-cache \
--repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
--repository=http://dl-cdn.alpinelinux.org/alpine/edge/community \
--repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing \
font-adobe-source-code-pro font-source-code-pro-nerd \
font-dejavu font-dejavu-sans-mono-nerd \
font-fira-code font-fira-code-nerd \
font-hack font-hack-nerd \
font-ibm-plex-mono-nerd \
font-inconsolata font-inconsolata-nerd \
font-jetbrains-mono font-jetbrains-mono-nerd \
font-liberation font-liberation-mono-nerd \
font-noto \
font-roboto-mono \
# font-ubuntu font-ubuntu-mono-nerd \ # Alpine no longer has this font
font-noto-emoji
FROM debian:stable-slim
RUN apt-get update
# Add fonts
COPY --from=fontcollector /usr/share/fonts/ /usr/share/fonts
# Install latest ttyd
COPY --from=ttyd /usr/bin/ttyd /usr/bin/ttyd
# Expose port
EXPOSE 1976
# Create volume
VOLUME /vhs
WORKDIR /vhs
# Install Dependencies
RUN apt-get -y install ffmpeg chromium bash
# Create user
RUN useradd -u 1976 -U -s /bin/false vhs
# Mimic alpine default color option
RUN echo 'alias ls="ls --color"' >> ~/.bashrc
# Install
COPY vhs /usr/bin/
ENV VHS_PORT="1976"
ENV VHS_HOST="0.0.0.0"
ENV VHS_GID="1976"
ENV VHS_UID="1976"
ENV VHS_KEY_PATH="/vhs/vhs"
ENV VHS_AUTHORIZED_KEYS_PATH=""
ENV VHS_NO_SANDBOX="true"
ENTRYPOINT ["/usr/bin/vhs"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022-2023 Charmbracelet, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
themes.json:
# this is the url used by https://windowsterminalthemes.dev/
# See https://github.com/atomcorp/themes/blob/master/app/src/App.tsx#L18
@./scripts/download_theme.sh \
https://2zrysvpla9.execute-api.eu-west-2.amazonaws.com/prod/themes \
themes
THEMES.md:
@go run . themes --markdown 2> THEMES.md
all: themes.json THEMES.md
@echo "Running all"
refresh:
@rm -rf themes.json themes.json THEMES.md
@$(MAKE) all
================================================
FILE: README.md
================================================
# VHS
<p>
<img src="https://user-images.githubusercontent.com/42545625/198402537-12ca2f6c-0779-4eb8-a67c-8db9cb3df13c.png#gh-dark-mode-only" width="500" />
<img src="https://user-images.githubusercontent.com/42545625/198402542-a305f669-a05a-4d91-b18b-ca76e72b655a.png#gh-light-mode-only" width="500" />
<br>
<a href="https://github.com/charmbracelet/vhs/releases"><img src="https://img.shields.io/github/release/charmbracelet/vhs.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/charmbracelet/vhs?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="Go Docs"></a>
<a href="https://github.com/charmbracelet/vhs/actions"><img src="https://github.com/charmbracelet/vhs/workflows/build/badge.svg" alt="Build Status"></a>
</p>
Write terminal GIFs as code for integration testing and demoing your CLI tools.
<img alt="Welcome to VHS" src="https://stuff.charm.sh/vhs/examples/neofetch_3.gif" width="600" />
The above example was generated with VHS ([view source](./examples/neofetch/neofetch.tape)).
## Tutorial
To get started, [install VHS](#installation) and create a new `.tape` file.
```sh
vhs new demo.tape
```
Open the `.tape` file with your favorite `$EDITOR`.
```sh
vim demo.tape
```
Tape files consist of a series of [commands](#vhs-command-reference). The commands are
instructions for VHS to perform on its virtual terminal. For a list of all
possible commands see [the command reference](#vhs-command-reference).
```elixir
# Where should we write the GIF?
Output demo.gif
# Set up a 1200x600 terminal with 46px font.
Set FontSize 46
Set Width 1200
Set Height 600
# Type a command in the terminal.
Type "echo 'Welcome to VHS!'"
# Pause for dramatic effect...
Sleep 500ms
# Run the command by pressing enter.
Enter
# Admire the output for a bit.
Sleep 5s
```
Once you've finished, save the file and feed it into VHS.
```sh
vhs demo.tape
```
All done! You should see a new file called `demo.gif` (or whatever you named
the `Output`) in the directory.
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/demo.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/demo.gif">
<img width="600" alt="A GIF produced by the VHS code above" src="https://stuff.charm.sh/vhs/examples/demo.gif">
</picture>
For more examples see the [`examples/`](https://github.com/charmbracelet/vhs/tree/main/examples) directory.
## Installation
> [!NOTE]
> VHS requires [`ttyd`](https://github.com/tsl0922/ttyd) and [`ffmpeg`](https://ffmpeg.org) to be installed and available on your `PATH`.
Use a package manager:
```sh
# macOS or Linux
brew install vhs
# Arch Linux (btw)
pacman -S vhs
# Nix
nix-env -iA nixpkgs.vhs
# Windows using scoop
scoop install vhs
```
Or, use Docker to run VHS directly, dependencies included:
```sh
docker run --rm -v $PWD:/vhs ghcr.io/charmbracelet/vhs <cassette>.tape
```
Or, download it:
- [Packages][releases] are available in Debian and RPM formats
- [Binaries][releases] are available for Linux, macOS, and Windows
Or, just install it with `go`:
```sh
go install github.com/charmbracelet/vhs@latest
```
<details>
<summary>Windows, Debian, Ubuntu, Fedora, RHEL, Void Instructions</summary>
- Debian / Ubuntu
```sh
# Debian/Ubuntu
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
# Install ttyd from https://github.com/tsl0922/ttyd/releases
sudo apt update && sudo apt install vhs ffmpeg
```
- Fedora / RHEL
```sh
echo '[charm]
name=Charm
baseurl=https://repo.charm.sh/yum/
enabled=1
gpgcheck=1
gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo
# Install ttyd from https://github.com/tsl0922/ttyd/releases
sudo yum install vhs ffmpeg
```
- Void
```sh
sudo xbps-install vhs
```
- Windows
```sh
winget install charmbracelet.vhs
# or scoop
scoop install vhs
```
</details>
[releases]: https://github.com/charmbracelet/vhs/releases
## Record Tapes
VHS has the ability to generate tape files from your terminal actions!
To record to a tape file, run:
```bash
vhs record > cassette.tape
```
Perform any actions you want and then `exit` the terminal session to stop
recording. You may want to manually edit the generated `.tape` file to add
settings or modify actions. Then, you can generate the GIF:
```bash
vhs cassette.tape
```
## Publish Tapes
VHS allows you to publish your GIFs to our servers for easy sharing with your
friends and colleagues. Specify which file you want to share, then use the
`publish` sub-command to host it on `vhs.charm.sh`. The output will provide you
with links to share your GIF via browser, HTML, and Markdown.
```bash
vhs publish demo.gif
```
## The VHS Server
VHS has an SSH server built in! When you self-host VHS you can access it as
though it were installed locally. VHS will have access to commands and
applications on the host, so you don't need to install them on your machine.
To start the server run:
```sh
vhs serve
```
<details>
<summary>Configuration Options</summary>
- `VHS_PORT`: The port to listen on (`1976`)
- `VHS_HOST`: The host to listen on (`localhost`)
- `VHS_GID`: The Group ID to run the server as (current user's GID)
- `VHS_UID`: The User ID to run the server as (current user's UID)
- `VHS_KEY_PATH`: The path to the SSH key to use (`.ssh/vhs_ed25519`)
- `VHS_AUTHORIZED_KEYS_PATH`: The path to the authorized keys file (empty, publicly accessible)
</details>
Then, simply access VHS from a different machine via `ssh`:
```sh
ssh vhs.example.com < demo.tape > demo.gif
```
## VHS Command Reference
> [!NOTE]
> You can view all VHS documentation on the command line with `vhs manual`.
There are a few basic types of VHS commands:
- [`Output <path>`](#output): specify file output
- [`Require <program>`](#require): specify required programs for tape file
- [`Set <Setting> Value`](#settings): set recording settings
- [`Type "<characters>"`](#type): emulate typing
- [`Left`](#arrow-keys) [`Right`](#arrow-keys) [`Up`](#arrow-keys) [`Down`](#arrow-keys): arrow keys
- [`Backspace`](#backspace) [`Enter`](#enter) [`Tab`](#tab) [`Space`](#space): special keys
- [`ScrollUp`](#scroll-up--down) [`ScrollDown`](#scroll-up--down): scroll terminal viewport
- [`Ctrl[+Alt][+Shift]+<char>`](#ctrl): press control + key and/or modifier
- [`Sleep <time>`](#sleep): wait for a certain amount of time
- [`Wait[+Screen][+Line] /regex/`](#wait): wait for specific conditions
- [`Hide`](#hide): hide commands from output
- [`Show`](#show): stop hiding commands from output
- [`Screenshot`](#screenshot): screenshot the current frame
- [`Copy/Paste`](#copy--paste): copy and paste text from clipboard.
- [`Source`](#source): source commands from another tape
- [`Env <Key> Value`](#env): set environment variables
### Output
The `Output` command allows you to specify the location and file format
of the render. You can specify more than one output in a tape file which
will render them to the respective locations.
```elixir
Output out.gif
Output out.mp4
Output out.webm
Output frames/ # a directory of frames as a PNG sequence
```
### Require
The `Require` command allows you to specify dependencies for your tape file.
These are useful to fail early if a required program is missing from the
`$PATH`, and it is certain that the VHS execution will not work as expected.
Require commands must be defined at the top of a tape file, before any non-
setting or non-output command.
```elixir
# A tape file that requires gum and glow to be in the $PATH
Require gum
Require glow
```
### Settings
The `Set` command allows you to change global aspects of the terminal, such as
the font settings, window dimensions, and GIF output location.
Setting must be administered at the top of the tape file. Any setting (except
`TypingSpeed`) applied after a non-setting or non-output command will be
ignored.
#### Set Shell
Set the shell with the `Set Shell <shell>` command
```elixir
Set Shell fish
```
#### Set Font Size
Set the font size with the `Set FontSize <number>` command.
```elixir
Set FontSize 10
Set FontSize 20
Set FontSize 40
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/font-size-10.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/font-size-10.gif">
<img width="600" alt="Example of setting the font size to 10 pixels" src="https://stuff.charm.sh/vhs/examples/font-size-10.gif">
</picture>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/font-size-20.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/font-size-20.gif">
<img width="600" alt="Example of setting the font size to 20 pixels" src="https://stuff.charm.sh/vhs/examples/font-size-20.gif">
</picture>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/font-size-40.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/font-size-40.gif">
<img width="600" alt="Example of setting the font size to 40 pixels" src="https://stuff.charm.sh/vhs/examples/font-size-40.gif">
</picture>
#### Set Font Family
Set the font family with the `Set FontFamily "<font>"` command
```elixir
Set FontFamily "Monoflow"
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/font-family.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/font-family.gif">
<img width="600" alt="Example of changing the font family to Monoflow" src="https://stuff.charm.sh/vhs/examples/font-family.gif">
</picture>
#### Set Width
Set the width of the terminal with the `Set Width` command.
```elixir
Set Width 300
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/width.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/width.gif">
<img width="300" alt="Example of changing the width of the terminal" src="https://stuff.charm.sh/vhs/examples/width.gif">
</picture>
#### Set Height
Set the height of the terminal with the `Set Height` command.
```elixir
Set Height 1000
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/height.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/height.gif">
<img width="300" alt="Example of changing the height of the terminal" src="https://stuff.charm.sh/vhs/examples/height.gif">
</picture>
#### Set Letter Spacing
Set the spacing between letters (tracking) with the `Set LetterSpacing`
Command.
```elixir
Set LetterSpacing 20
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/letter-spacing.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/letter-spacing.gif">
<img width="600" alt="Example of changing the letter spacing to 20 pixels between characters" src="https://stuff.charm.sh/vhs/examples/letter-spacing.gif">
</picture>
#### Set Line Height
Set the spacing between lines with the `Set LineHeight` Command.
```elixir
Set LineHeight 1.8
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/line-height.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/line-height.gif">
<img width="600" alt="Example of changing the line height to 1.8" src="https://stuff.charm.sh/vhs/examples/line-height.gif">
</picture>
#### Set Typing Speed
```elixir
Set TypingSpeed 500ms # 500ms
Set TypingSpeed 1s # 1s
```
Set the typing speed of seconds per key press. For example, a typing speed of
`0.1` would result in a `0.1s` (`100ms`) delay between each character being typed.
This setting can also be overwritten per command with the `@<time>` syntax.
```elixir
Set TypingSpeed 0.1
Type "100ms delay per character"
Type@500ms "500ms delay per character"
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/typing-speed.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/typing-speed.gif">
<img width="600" alt="Example of using the Type command in VHS" src="https://stuff.charm.sh/vhs/examples/typing-speed.gif">
</picture>
#### Set Theme
Set the theme of the terminal with the `Set Theme` command. The theme value
should be a JSON string with the base 16 colors and foreground + background.
```elixir
Set Theme { "name": "Whimsy", "black": "#535178", "red": "#ef6487", "green": "#5eca89", "yellow": "#fdd877", "blue": "#65aef7", "magenta": "#aa7ff0", "cyan": "#43c1be", "white": "#ffffff", "brightBlack": "#535178", "brightRed": "#ef6487", "brightGreen": "#5eca89", "brightYellow": "#fdd877", "brightBlue": "#65aef7", "brightMagenta": "#aa7ff0", "brightCyan": "#43c1be", "brightWhite": "#ffffff", "background": "#29283b", "foreground": "#b3b0d6", "selection": "#3d3c58", "cursor": "#b3b0d6" }
```
<img alt="Example of changing the theme to Whimsy" src="https://stuff.charm.sh/vhs/examples/theme.gif" width="600" />
You can also set themes by name:
```elixir
Set Theme "Catppuccin Frappe"
```
See the full list by running `vhs themes`, or in [THEMES.md](./THEMES.md).
#### Set Padding
Set the padding (in pixels) of the terminal frame with the `Set Padding`
command.
```elixir
Set Padding 0
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/padding.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/padding.gif">
<img width="600" alt="Example of setting the padding" src="https://stuff.charm.sh/vhs/examples/padding.gif">
</picture>
#### Set Margin
Set the margin (in pixels) of the video with the `Set Margin` command.
```elixir
Set Margin 60
Set MarginFill "#6B50FF"
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://vhs.charm.sh/vhs-1miKMtNHenh7O4sv76TMwG.gif">
<source media="(prefers-color-scheme: light)" srcset="https://vhs.charm.sh/vhs-1miKMtNHenh7O4sv76TMwG.gif">
<img width="600" alt="Example of setting the margin" src="https://vhs.charm.sh/vhs-1miKMtNHenh7O4sv76TMwG.gif">
</picture>
#### Set Window Bar
Set the type of window bar (Colorful, ColorfulRight, Rings, RingsRight) on the terminal window with the `Set WindowBar` command.
```elixir
Set WindowBar Colorful
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://vhs.charm.sh/vhs-4VgviCu38DbaGtbRzhtOUI.gif">
<source media="(prefers-color-scheme: light)" srcset="https://vhs.charm.sh/vhs-4VgviCu38DbaGtbRzhtOUI.gif">
<img width="600" alt="Example of setting the margin" src="https://vhs.charm.sh/vhs-4VgviCu38DbaGtbRzhtOUI.gif">
</picture>
#### Set Border Radius
Set the border radius (in pixels) of the terminal window with the `Set BorderRadius` command.
```elixir
# You'll likely want to add a Margin + MarginFill if you use BorderRadius.
Set Margin 20
Set MarginFill "#674EFF"
Set BorderRadius 10
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://vhs.charm.sh/vhs-4nYoy6IsUKmleJANG7N1BH.gif">
<source media="(prefers-color-scheme: light)" srcset="https://vhs.charm.sh/vhs-4nYoy6IsUKmleJANG7N1BH.gif">
<img width="400" alt="Example of setting the margin" src="https://vhs.charm.sh/vhs-4nYoy6IsUKmleJANG7N1BH.gif">
</picture>
#### Set Framerate
Set the rate at which VHS captures frames with the `Set Framerate` command.
```elixir
Set Framerate 60
```
#### Set Playback Speed
Set the playback speed of the final render.
```elixir
Set PlaybackSpeed 0.5 # Make output 2 times slower
Set PlaybackSpeed 1.0 # Keep output at normal speed (default)
Set PlaybackSpeed 2.0 # Make output 2 times faster
```
#### Set Loop Offset
Set the offset for when the GIF loop should begin. This allows you to make the
first frame of the GIF (generally used for previews) more interesting.
```elixir
Set LoopOffset 5 # Start the GIF at the 5th frame
Set LoopOffset 50% # Start the GIF halfway through
```
#### Set Cursor Blink
Set whether the cursor should blink. Enabled by default.
```elixir
Set CursorBlink false
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://vhs.charm.sh/vhs-3rMCb80VEkaDdTOJMCrxKy.gif">
<source media="(prefers-color-scheme: light)" srcset="https://vhs.charm.sh/vhs-3rMCb80VEkaDdTOJMCrxKy.gif">
<img width="600" alt="Example of setting the cursor blink." src="https://vhs.charm.sh/vhs-3rMCb80VEkaDdTOJMCrxKy.gif">
</picture>
### Type
Use `Type` to emulate key presses. That is, you can use `Type` to script typing
in a terminal. Type is handy for both entering commands and interacting with
prompts and TUIs in the terminal. The command takes a string argument of the
characters to type.
You can set the standard typing speed with [`Set TypingSpeed`](#set-typing-speed)
and override it in places with a `@time` argument.
```elixir
# Type something
Type "Whatever you want"
# Type something really slowly!
Type@500ms "Slow down there, partner."
```
Escape single and double quotes with backticks.
```elixir
Type `VAR="Escaped"`
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/type.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/type.gif">
<img width="600" alt="Example of using the Type command in VHS" src="https://stuff.charm.sh/vhs/examples/type.gif">
</picture>
### Keys
Key commands take an optional `@time` and optional repeat `count` for repeating
the key press every interval of `<time>`.
```
Key[@<time>] [count]
```
#### Backspace
Press the backspace key with the `Backspace` command.
```elixir
Backspace 18
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/backspace.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/backspace.gif">
<img width="600" alt="Example of pressing the Backspace key 18 times" src="https://stuff.charm.sh/vhs/examples/backspace.gif">
</picture>
#### Ctrl
You can access the control modifier and send control sequences with the `Ctrl`
command.
```elixir
Ctrl+R
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/ctrl.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/ctrl.gif">
<img width="600" alt="Example of pressing the Ctrl+R key to reverse search" src="https://stuff.charm.sh/vhs/examples/ctrl.gif">
</picture>
#### Enter
Press the enter key with the `Enter` command.
```elixir
Enter 2
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/enter.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/enter.gif">
<img width="600" alt="Example of pressing the Enter key twice" src="https://stuff.charm.sh/vhs/examples/enter.gif">
</picture>
#### Arrow Keys
Press any of the arrow keys with the `Up`, `Down`, `Left`, `Right` commands.
```elixir
Up 2
Down 2
Left
Right
Left
Right
Type "B"
Type "A"
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/arrow.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/arrow.gif">
<img width="600" alt="Example of pressing the arrow keys to navigate text" src="https://stuff.charm.sh/vhs/examples/arrow.gif">
</picture>
#### Tab
Enter a tab with the `Tab` command.
```elixir
Tab@500ms 2
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/tab.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/tab.gif">
<img width="600" alt="Example of pressing the tab key twice for autocomplete" src="https://stuff.charm.sh/vhs/examples/tab.gif">
</picture>
#### Space
Press the space bar with the `Space` command.
```elixir
Space 10
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/space.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/space.gif">
<img width="600" alt="Example of pressing the space key" src="https://stuff.charm.sh/vhs/examples/space.gif">
</picture>
#### Page Up / Down
Press the Page Up / Down keys with the `PageUp` or `PageDown` commands.
```elixir
PageUp 3
PageDown 5
```
#### Scroll Up / Down
Scroll the terminal viewport directly with `ScrollUp` and `ScrollDown`.
Both commands use the same optional `@time` and repeat count shape as other
repeatable key commands: `ScrollUp[@<time>] [count]`.
```elixir
ScrollUp 10
ScrollDown 4
ScrollDown@100ms 12
```
### Wait
The `Wait` command allows you to wait for something to appear on the screen.
This is useful when you need to wait on something to complete, even if you don't
know how long it'll take, while including it in the recording like a spinner or
loading state.
The command takes a regular expression as an argument, and optionally allows to
set the duration to wait and if you want to check the whole screen or just the
last line (the scope).
```elixir
Wait
Wait /World/
Wait+Screen /World/
Wait+Line /World/
Wait@10ms /World/
Wait+Line@10ms /World/
```
The default regular expression is `/>$/`, the wait timeout is `15s`, and the
default scope is `Line`.
### Sleep
The `Sleep` command allows you to continue capturing frames without interacting
with the terminal. This is useful when you need to wait on something to
complete while including it in the recording like a spinner or loading state.
The command takes a number argument in seconds.
```elixir
Sleep 0.5 # 500ms
Sleep 2 # 2s
Sleep 100ms # 100ms
Sleep 1s # 1s
```
### Hide
The `Hide` command instructs VHS to stop capturing frames. It's useful to pause
a recording to perform hidden commands.
```elixir
Hide
```
This command is helpful for performing any setup and cleanup required to record
a GIF, such as building the latest version of a binary and removing the binary
once the demo is recorded.
```elixir
Output example.gif
# Setup
Hide
Type "go build -o example . && clear"
Enter
Show
# Recording...
Type 'Running ./example'
...
Enter
# Cleanup
Hide
Type 'rm example'
Enter
```
### Show
The `Show` command instructs VHS to begin capturing frames, again. It's useful
after a `Hide` command to resume frame recording for the output.
```elixir
Hide
Type "You won't see this being typed."
Show
Type "You will see this being typed."
```
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/hide.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/hide.gif">
<img width="600" alt="Example of typing something while hidden" src="https://stuff.charm.sh/vhs/examples/hide.gif">
</picture>
### Screenshot
The `Screenshot` command captures the current frame (png format).
```elixir
# At any point...
Screenshot examples/screenshot.png
```
### Copy / Paste
The `Copy` and `Paste` copy and paste the string from clipboard.
```elixir
Copy "https://github.com/charmbracelet"
Type "open "
Sleep 500ms
Paste
```
### Env
`Env` command sets the environment variable via key-value pair.
```elixir
Env HELLO "WORLD"
Type "echo $HELLO"
Enter
Sleep 1s
```
### Source
The `source` command allows you to execute commands from another tape.
```elixir
Source config.tape
```
---
## Continuous Integration
You can hook up VHS to your CI pipeline to keep your GIFs up-to-date with
the official VHS GitHub Action:
> [⚙️ charmbracelet/vhs-action](https://github.com/charmbracelet/vhs-action)
VHS can also be used for integration testing. Use the `.txt` or `.ascii` output
to generate golden files. Store these files in a git repository to ensure there
are no diffs between runs of the tape file.
```elixir
Output golden.ascii
```
## Syntax Highlighting
There’s a tree-sitter grammar for `.tape` files available for editors that
support syntax highlighting with tree-sitter:
> [🌳 charmbracelet/tree-sitter-vhs](https://github.com/charmbracelet/tree-sitter-vhs)
It works great with Neovim, Emacs, and so on!
## Contributing
See [contributing][contribute].
[contribute]: https://github.com/charmbracelet/vhs/contribute
## Feedback
We’d love to hear your thoughts on this project. Feel free to drop us a note!
- [Twitter](https://twitter.com/charmcli)
- [The Fediverse](https://mastodon.social/@charmcli)
- [Discord](https://charm.sh/chat)
## License
[MIT](https://github.com/charmbracelet/vhs/raw/main/LICENSE)
---
Part of [Charm](https://charm.sh).
<a href="https://charm.sh/">
<img
alt="The Charm logo"
width="400"
src="https://stuff.charm.sh/charm-badge.jpg"
/>
</a>
Charm热爱开源 • Charm loves open source
================================================
FILE: THEMES.md
================================================
# Themes
* `3024 Day`
* `3024 Night`
* `Aardvark Blue`
* `Abernathy`
* `Adventure`
* `AdventureTime`
* `Afterglow`
* `Alabaster`
* `AlienBlood`
* `Andromeda`
* `Apple Classic`
* `arcoiris`
* `Argonaut`
* `Arthur`
* `AtelierSulphurpool`
* `Atom`
* `AtomOneLight`
* `Aurora`
* `ayu`
* `Ayu Mirage`
* `ayu_light`
* `Banana Blueberry`
* `Batman`
* `Belafonte Day`
* `Belafonte Night`
* `BirdsOfParadise`
* `Blazer`
* `Blue Matrix`
* `BlueBerryPie`
* `BlueDolphin`
* `BlulocoDark`
* `BlulocoLight`
* `Borland`
* `Breeze`
* `Bright Lights`
* `Broadcast`
* `Brogrammer`
* `Bubbles`
* `Builtin Dark`
* `Builtin Light`
* `Builtin Pastel Dark`
* `Builtin Solarized Dark`
* `Builtin Solarized Light`
* `Builtin Tango Dark`
* `Builtin Tango Light`
* `C64`
* `Calamity`
* `Catppuccin Frappe`
* `Catppuccin Latte`
* `Catppuccin Macchiato`
* `Catppuccin Mocha`
* `catppuccin-frappe`
* `catppuccin-latte`
* `catppuccin-macchiato`
* `catppuccin-mocha`
* `CGA`
* `Chalk`
* `Chalkboard`
* `ChallengerDeep`
* `Chester`
* `Ciapre`
* `CLRS`
* `Cobalt Neon`
* `Cobalt2`
* `coffee_theme`
* `Contrast Light`
* `coolnight`
* `CrayonPonyFish`
* `Crystal Violet`
* `Cyber Cube`
* `Cyberdyne`
* `cyberpunk`
* `CyberPunk2077`
* `Dark Pastel`
* `Dark+`
* `darkermatrix`
* `darkmatrix`
* `Darkside`
* `deep`
* `Desert`
* `DimmedMonokai`
* `Django`
* `DjangoRebornAgain`
* `DjangoSmooth`
* `Doom Peacock`
* `DoomOne`
* `DotGov`
* `Dracula`
* `Dracula+`
* `DraculaPlus`
* `duckbones`
* `Duotone Dark`
* `Earthsong`
* `Elemental`
* `Elementary`
* `ENCOM`
* `Espresso`
* `Espresso Libre`
* `Everblush`
* `Fahrenheit`
* `Fairyfloss`
* `Fideloper`
* `FirefoxDev`
* `Firewatch`
* `FishTank`
* `Flat`
* `Flatland`
* `Floraverse`
* `ForestBlue`
* `Framer`
* `FrontEndDelight`
* `FunForrest`
* `Galaxy`
* `Galizur`
* `Ganyu`
* `Github`
* `GitHub Dark`
* `Glacier`
* `Glorious`
* `Grape`
* `Grass`
* `Grey-green`
* `Gruvbox Light`
* `GruvboxDark`
* `GruvboxDarkHard`
* `Guezwhoz`
* `h4rithd`
* `h4rithd.com`
* `Hacktober`
* `Hardcore`
* `Harper`
* `HaX0R_BLUE`
* `HaX0R_GR33N`
* `HaX0R_R3D`
* `Highway`
* `Hipster Green`
* `Hivacruz`
* `Homebrew`
* `Hopscotch`
* `Hopscotch.256`
* `Horizon`
* `Hurtado`
* `Hybrid`
* `Hyper`
* `IC_Green_PPL`
* `IC_Orange_PPL`
* `iceberg-dark`
* `iceberg-light`
* `idea`
* `idleToes`
* `IR_Black`
* `iTerm2 Dark Background`
* `iTerm2 Default`
* `iTerm2 Light Background`
* `iTerm2 Pastel Dark Background`
* `iTerm2 Smoooooth`
* `iTerm2 Solarized Dark`
* `iTerm2 Solarized Light`
* `iTerm2 Tango Dark`
* `iTerm2 Tango Light`
* `Jackie Brown`
* `Japanesque`
* `Jellybeans`
* `JetBrains Darcula`
* `jubi`
* `Juicy Colors`
* `Kanagawa`
* `kanagawabones`
* `Kibble`
* `Kolorit`
* `Konsolas`
* `Lab Fox`
* `Laser`
* `Later This Evening`
* `Lavandula`
* `LiquidCarbon`
* `LiquidCarbonTransparent`
* `LiquidCarbonTransparentInverse`
* `lovelace`
* `Man Page`
* `Mariana`
* `Material`
* `MaterialDark`
* `MaterialDarker`
* `MaterialDesignColors`
* `MaterialOcean`
* `Mathias`
* `matrix`
* `Medallion`
* `midnight-in-mojave`
* `Mirage`
* `Misterioso`
* `Molokai`
* `MonaLisa`
* `Monokai Cmder`
* `Monokai Pro`
* `Monokai Pro (Filter Octagon)`
* `Monokai Pro (Filter Ristretto)`
* `Monokai Remastered`
* `Monokai Soda`
* `Monokai Vivid`
* `Moonlight II`
* `N0tch2k`
* `neobones_dark`
* `neobones_light`
* `Neon`
* `Neopolitan`
* `Neutron`
* `Night Owlish Light`
* `NightLion v1`
* `NightLion v2`
* `niji`
* `Nocturnal Winter`
* `nord`
* `nord-light`
* `Novel`
* `Obsidian`
* `Ocean`
* `Oceanic-Next`
* `OceanicMaterial`
* `Ollie`
* `OneDark`
* `OneHalfDark`
* `OneHalfLight`
* `OneStar`
* `Operator Mono Dark`
* `Overnight Slumber`
* `PaleNightHC`
* `Pandora`
* `Paraiso Dark`
* `PaulMillr`
* `PencilDark`
* `PencilLight`
* `Peppermint`
* `Piatto Light`
* `Pnevma`
* `Popping and Locking`
* `primary`
* `Primer`
* `Pro`
* `Pro Light`
* `Purple Rain`
* `purplepeter`
* `QB64 Super Dark Blue`
* `Rapture`
* `Raycast_Dark`
* `Raycast_Light`
* `rebecca`
* `Red Alert`
* `Red Planet`
* `Red Sands`
* `Relaxed`
* `Retro`
* `Retrowave`
* `Rippedcasts`
* `Rose Pine`
* `rose-pine`
* `rose-pine-dawn`
* `rose-pine-moon`
* `Rouge 2`
* `Royal`
* `Ryuuko`
* `Sakura`
* `Scarlet Protocol`
* `Seafoam Pastel`
* `SeaShells`
* `seoulbones_dark`
* `seoulbones_light`
* `Serendipity Midnight`
* `Serendipity Morning`
* `Serendipity Sunset`
* `Seti`
* `shades-of-purple`
* `Shaman`
* `Slate`
* `SleepyHollow`
* `Smyck`
* `Snazzy`
* `SoftServer`
* `Solarized Darcula`
* `Solarized Dark - Patched`
* `Solarized Dark Higher Contrast`
* `Sonoran Gothic`
* `Sonoran Sunrise`
* `Spacedust`
* `SpaceGray`
* `SpaceGray Eighties`
* `SpaceGray Eighties Dull`
* `Spiderman`
* `Spring`
* `Square`
* `Sublette`
* `Subliminal`
* `Sundried`
* `Symfonic`
* `synthwave`
* `synthwave-everything`
* `SynthwaveAlpha`
* `Tango Adapted`
* `Tango Half Adapted`
* `Teerb`
* `Terminal Basic`
* `Thayer Bright`
* `The Hulk`
* `Tinacious Design (Dark)`
* `Tinacious Design (Light)`
* `TokyoNight`
* `tokyonight`
* `tokyonight-day`
* `tokyonight-storm`
* `TokyoNightLight`
* `TokyoNightStorm`
* `Tomorrow`
* `Tomorrow Night`
* `Tomorrow Night Blue`
* `Tomorrow Night Bright`
* `Tomorrow Night Burns`
* `Tomorrow Night Eighties`
* `ToyChest`
* `Treehouse`
* `Twilight`
* `Ubuntu`
* `UltraDark`
* `UltraViolent`
* `UnderTheSea`
* `Unholy`
* `Unikitty`
* `Urple`
* `Vaughn`
* `VibrantInk`
* `vimbones`
* `Violet Dark`
* `Violet Light`
* `WarmNeon`
* `Wez`
* `Whimsy`
* `WildCherry`
* `wilmersdorf`
* `Wombat`
* `Wryan`
* `zenbones`
* `zenbones_dark`
* `zenbones_light`
* `Zenburn`
* `zenburned`
* `zenwritten_dark`
* `zenwritten_light`
* `Zeonica`
================================================
FILE: command.go
================================================
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
"github.com/atotto/clipboard"
"github.com/charmbracelet/vhs/parser"
"github.com/charmbracelet/vhs/token"
"github.com/go-rod/rod/lib/input"
)
// Execute executes a command on a running instance of vhs.
func Execute(c parser.Command, v *VHS) error {
err := CommandFuncs[c.Type](c, v)
if err != nil {
return fmt.Errorf("failed to execute command: %w", err)
}
if v.recording && v.Options.Test.Output != "" {
err := v.SaveOutput()
if err != nil {
return fmt.Errorf("failed to save output: %w", err)
}
}
return nil
}
// CommandFunc is a function that executes a command on a running
// instance of vhs.
type CommandFunc func(c parser.Command, v *VHS) error
// CommandFuncs maps command types to their executable functions.
var CommandFuncs = map[parser.CommandType]CommandFunc{
token.BACKSPACE: ExecuteKey(input.Backspace),
token.DELETE: ExecuteKey(input.Delete),
token.INSERT: ExecuteKey(input.Insert),
token.DOWN: ExecuteKey(input.ArrowDown),
token.ENTER: ExecuteKey(input.Enter),
token.LEFT: ExecuteKey(input.ArrowLeft),
token.RIGHT: ExecuteKey(input.ArrowRight),
token.SPACE: ExecuteKey(input.Space),
token.UP: ExecuteKey(input.ArrowUp),
token.TAB: ExecuteKey(input.Tab),
token.ESCAPE: ExecuteKey(input.Escape),
token.PAGE_UP: ExecuteKey(input.PageUp),
token.PAGE_DOWN: ExecuteKey(input.PageDown),
token.SCROLL_UP: ExecuteScroll(-1),
token.SCROLL_DOWN: ExecuteScroll(1),
token.HIDE: ExecuteHide,
token.REQUIRE: ExecuteRequire,
token.SHOW: ExecuteShow,
token.SET: ExecuteSet,
token.OUTPUT: ExecuteOutput,
token.SLEEP: ExecuteSleep,
token.TYPE: ExecuteType,
token.CTRL: ExecuteCtrl,
token.ALT: ExecuteAlt,
token.SHIFT: ExecuteShift,
token.ILLEGAL: ExecuteNoop,
token.SCREENSHOT: ExecuteScreenshot,
token.COPY: ExecuteCopy,
token.PASTE: ExecutePaste,
token.ENV: ExecuteEnv,
token.WAIT: ExecuteWait,
}
// ExecuteNoop is a no-op command that does nothing.
// Generally, this is used for Unknown commands when dealing with
// commands that are not recognized.
func ExecuteNoop(_ parser.Command, _ *VHS) error { return nil }
// ExecuteKey is a higher-order function that returns a CommandFunc to execute
// a key press for a given key. This is so that the logic for key pressing
// (since they are repeatable and delayable) can be re-used.
//
// i.e. ExecuteKey(input.ArrowDown) would return a CommandFunc that executes
// the ArrowDown key press.
func ExecuteKey(k input.Key) CommandFunc {
return func(c parser.Command, v *VHS) error {
typingSpeed, err := time.ParseDuration(c.Options)
if err != nil {
typingSpeed = v.Options.TypingSpeed
}
repeat, err := strconv.Atoi(c.Args)
if err != nil {
repeat = 1
}
for i := 0; i < repeat; i++ {
err = v.Page.Keyboard.Type(k)
if err != nil {
return fmt.Errorf("failed to type key %c: %w", k, err)
}
time.Sleep(typingSpeed)
}
return nil
}
}
// ExecuteScroll returns a command function that scrolls xterm's viewport.
//
// The direction argument is expected to be:
//
// -1 for ScrollUp
// +1 for ScrollDown
//
// Each command repeat applies one viewport row movement with the same timing
// semantics as other repeatable key-like commands (Options is the per-step
// delay, Args is repeat count).
//
// A caller that wires an unexpected direction (for example 10) will still call
// xterm's scroll API with that value and produce larger jumps per step.
func ExecuteScroll(direction int) CommandFunc {
return func(c parser.Command, v *VHS) error {
typingSpeed, err := time.ParseDuration(c.Options)
if err != nil {
typingSpeed = v.Options.TypingSpeed
}
repeat, err := strconv.Atoi(c.Args)
if err != nil {
repeat = 1
}
for i := 0; i < repeat; i++ {
// ScrollUp/ScrollDown are viewport operations implemented directly via
// xterm's scroll API.
_, err = v.Page.Eval(fmt.Sprintf("() => term.scrollLines(%d)", direction))
if err != nil {
return fmt.Errorf("failed to scroll viewport: %w", err)
}
time.Sleep(typingSpeed)
}
return nil
}
}
// WaitTick is the amount of time to wait between checking for a match.
const WaitTick = 10 * time.Millisecond
// ExecuteWait is a CommandFunc that waits for a regex match for the given amount of time.
func ExecuteWait(c parser.Command, v *VHS) error {
scope, rxStr, ok := strings.Cut(c.Args, " ")
rx := v.Options.WaitPattern
if ok {
// This is validated on parse so using MustCompile reduces noise.
rx = regexp.MustCompile(rxStr)
}
timeout := v.Options.WaitTimeout
if c.Options != "" {
t, err := time.ParseDuration(c.Options)
if err != nil {
// Shouldn't be possible due to parse validation.
return fmt.Errorf("failed to parse duration: %w", err)
}
timeout = t
}
checkT := time.NewTicker(WaitTick)
defer checkT.Stop()
timeoutT := time.NewTimer(timeout)
defer timeoutT.Stop()
for {
var last string
switch scope {
case "Line":
line, err := v.CurrentLine()
if err != nil {
return fmt.Errorf("failed to get current line: %w", err)
}
last = line
if rx.MatchString(line) {
return nil
}
case "Screen":
lines, err := v.Buffer()
if err != nil {
return fmt.Errorf("failed to get buffer: %w", err)
}
last = strings.Join(lines, "\n")
if rx.MatchString(last) {
return nil
}
default:
// Should be impossible due to parse validation, but we don't want to
// hang if it does happen due to a bug.
return fmt.Errorf("invalid scope %q", scope)
}
select {
case <-checkT.C:
continue
case <-timeoutT.C:
return fmt.Errorf("timeout waiting for %q to match %s; last value was: %s", c.Args, rx.String(), last)
}
}
}
// ExecuteCtrl is a CommandFunc that presses the argument keys and/or modifiers
// with the ctrl key held down on the running instance of vhs.
func ExecuteCtrl(c parser.Command, v *VHS) error {
// Create key combination by holding ControlLeft
action := v.Page.KeyActions().Press(input.ControlLeft)
keys := strings.Split(c.Args, " ")
for i, key := range keys {
var inputKey *input.Key
switch key {
case "Shift":
inputKey = &input.ShiftLeft
case "Alt":
inputKey = &input.AltLeft
case "Enter":
inputKey = &input.Enter
case "Space":
inputKey = &input.Space
case "Backspace":
inputKey = &input.Backspace
case "Left":
inputKey = &input.ArrowLeft
case "Right":
inputKey = &input.ArrowRight
case "Up":
inputKey = &input.ArrowUp
case "Down":
inputKey = &input.ArrowDown
default:
r := rune(key[0])
if k, ok := keymap[r]; ok {
inputKey = &k
}
}
// Press or hold key in case it's valid
if inputKey != nil {
if i != len(keys)-1 {
action.Press(*inputKey)
} else {
// Other keys will remain pressed until the combination reaches the end
action.Type(*inputKey)
}
}
}
err := action.Do()
if err != nil {
return fmt.Errorf("failed to type key %s: %w", c.Args, err)
}
return nil
}
// ExecuteAlt is a CommandFunc that presses the argument key with the alt key
// held down on the running instance of vhs.
func ExecuteAlt(c parser.Command, v *VHS) error {
err := v.Page.Keyboard.Press(input.AltLeft)
if err != nil {
return fmt.Errorf("failed to press Alt key: %w", err)
}
if k, ok := token.Keywords[c.Args]; ok { //nolint:nestif
switch k {
case token.ENTER:
err = v.Page.Keyboard.Type(input.Enter)
if err != nil {
return fmt.Errorf("failed to type Enter key: %w", err)
}
case token.TAB:
err := v.Page.Keyboard.Type(input.Tab)
if err != nil {
return fmt.Errorf("failed to type Tab key: %w", err)
}
}
} else {
for _, r := range c.Args {
if k, ok := keymap[r]; ok {
err = v.Page.Keyboard.Type(k)
if err != nil {
return fmt.Errorf("failed to type key %c: %w", r, err)
}
}
}
}
err = v.Page.Keyboard.Release(input.AltLeft)
if err != nil {
return fmt.Errorf("failed to release Alt key: %w", err)
}
return nil
}
// ExecuteShift is a CommandFunc that presses the argument key with the shift
// key held down on the running instance of vhs.
func ExecuteShift(c parser.Command, v *VHS) error {
err := v.Page.Keyboard.Press(input.ShiftLeft)
if err != nil {
return fmt.Errorf("failed to press Shift key: %w", err)
}
if k, ok := token.Keywords[c.Args]; ok { //nolint:nestif
switch k {
case token.ENTER:
err = v.Page.Keyboard.Type(input.Enter)
if err != nil {
return fmt.Errorf("failed to type Enter key: %w", err)
}
case token.TAB:
err = v.Page.Keyboard.Type(input.Tab)
if err != nil {
return fmt.Errorf("failed to type Tab key: %w", err)
}
}
} else {
for _, r := range c.Args {
if k, ok := keymap[r]; ok {
err = v.Page.Keyboard.Type(k)
if err != nil {
return fmt.Errorf("failed to type key %c: %w", r, err)
}
}
}
}
err = v.Page.Keyboard.Release(input.ShiftLeft)
if err != nil {
return fmt.Errorf("failed to release Shift key: %w", err)
}
return nil
}
// ExecuteHide is a CommandFunc that starts or stops the recording of the vhs.
func ExecuteHide(_ parser.Command, v *VHS) error {
v.PauseRecording()
return nil
}
// ExecuteRequire is a CommandFunc that checks if all the binaries mentioned in the
// Require command are present. If not, it exits with a non-zero error.
func ExecuteRequire(c parser.Command, _ *VHS) error {
_, err := exec.LookPath(c.Args)
return err //nolint:wrapcheck
}
// ExecuteShow is a CommandFunc that resumes the recording of the vhs.
func ExecuteShow(_ parser.Command, v *VHS) error {
v.ResumeRecording()
return nil
}
// ExecuteSleep sleeps for the desired time specified through the argument of
// the Sleep command.
func ExecuteSleep(c parser.Command, _ *VHS) error {
dur, err := time.ParseDuration(c.Args)
if err != nil {
return fmt.Errorf("failed to parse duration: %w", err)
}
time.Sleep(dur)
return nil
}
// ExecuteType types the argument string on the running instance of vhs.
func ExecuteType(c parser.Command, v *VHS) error {
typingSpeed := v.Options.TypingSpeed
if c.Options != "" {
var err error
typingSpeed, err = time.ParseDuration(c.Options)
if err != nil {
return fmt.Errorf("failed to parse typing speed: %w", err)
}
}
for _, r := range c.Args {
k, ok := keymap[r]
if ok {
err := v.Page.Keyboard.Type(k)
if err != nil {
return fmt.Errorf("failed to type key %c: %w", r, err)
}
} else {
err := v.Page.MustElement("textarea").Input(string(r))
if err != nil {
return fmt.Errorf("failed to input text: %w", err)
}
v.Page.MustWaitIdle()
}
time.Sleep(typingSpeed)
}
return nil
}
// ExecuteOutput applies the output on the vhs videos.
func ExecuteOutput(c parser.Command, v *VHS) error {
switch c.Options {
case ".mp4":
v.Options.Video.Output.MP4 = c.Args
case ".test", ".ascii", ".txt":
v.Options.Test.Output = c.Args
case ".png":
v.Options.Video.Output.Frames = c.Args
case ".webm":
v.Options.Video.Output.WebM = c.Args
default:
v.Options.Video.Output.GIF = c.Args
}
return nil
}
// ExecuteCopy copies text to the clipboard.
func ExecuteCopy(c parser.Command, _ *VHS) error {
return clipboard.WriteAll(c.Args) //nolint:wrapcheck
}
// ExecuteEnv sets env with given key-value pair.
func ExecuteEnv(c parser.Command, _ *VHS) error {
return os.Setenv(c.Options, c.Args) //nolint:wrapcheck
}
// ExecutePaste pastes text from the clipboard.
func ExecutePaste(_ parser.Command, v *VHS) error {
clip, err := clipboard.ReadAll()
if err != nil {
return fmt.Errorf("failed to read clipboard: %w", err)
}
for _, r := range clip {
k, ok := keymap[r]
if ok {
err = v.Page.Keyboard.Type(k)
if err != nil {
return fmt.Errorf("failed to type key %c: %w", r, err)
}
} else {
err = v.Page.MustElement("textarea").Input(string(r))
if err != nil {
return fmt.Errorf("failed to input text: %w", err)
}
v.Page.MustWaitIdle()
}
}
return nil
}
// Settings maps the Set commands to their respective functions.
var Settings = map[string]CommandFunc{
"FontFamily": ExecuteSetFontFamily,
"FontSize": ExecuteSetFontSize,
"Framerate": ExecuteSetFramerate,
"Height": ExecuteSetHeight,
"LetterSpacing": ExecuteSetLetterSpacing,
"LineHeight": ExecuteSetLineHeight,
"PlaybackSpeed": ExecuteSetPlaybackSpeed,
"Padding": ExecuteSetPadding,
"Theme": ExecuteSetTheme,
"TypingSpeed": ExecuteSetTypingSpeed,
"Width": ExecuteSetWidth,
"Shell": ExecuteSetShell,
"LoopOffset": ExecuteLoopOffset,
"MarginFill": ExecuteSetMarginFill,
"Margin": ExecuteSetMargin,
"WindowBar": ExecuteSetWindowBar,
"WindowBarSize": ExecuteSetWindowBarSize,
"BorderRadius": ExecuteSetBorderRadius,
"WaitPattern": ExecuteSetWaitPattern,
"WaitTimeout": ExecuteSetWaitTimeout,
"CursorBlink": ExecuteSetCursorBlink,
}
// ExecuteSet applies the settings on the running vhs specified by the
// option and argument pass to the command.
func ExecuteSet(c parser.Command, v *VHS) error {
return Settings[c.Options](c, v)
}
// ExecuteSetFontSize applies the font size on the vhs.
func ExecuteSetFontSize(c parser.Command, v *VHS) error {
fontSize, err := strconv.Atoi(c.Args)
if err != nil {
return fmt.Errorf("failed to parse font size: %w", err)
}
v.Options.FontSize = fontSize
_, err = v.Page.Eval(fmt.Sprintf("() => term.options.fontSize = %d", fontSize))
if err != nil {
return fmt.Errorf("failed to set font size: %w", err)
}
// When changing the font size only the canvas dimensions change which are
// scaled back during the render to fit the aspect ration and dimensions.
//
// We need to call term.fit to ensure that everything is resized properly.
_, err = v.Page.Eval("term.fit")
if err != nil {
return fmt.Errorf("failed to fit terminal: %w", err)
}
return nil
}
// ExecuteSetFontFamily applies the font family on the vhs.
func ExecuteSetFontFamily(c parser.Command, v *VHS) error {
v.Options.FontFamily = c.Args
_, err := v.Page.Eval(fmt.Sprintf("() => term.options.fontFamily = '%s'", withSymbolsFallback(c.Args)))
if err != nil {
return fmt.Errorf("failed to set font family: %w", err)
}
return nil
}
// ExecuteSetHeight applies the height on the vhs.
func ExecuteSetHeight(c parser.Command, v *VHS) error {
height, err := strconv.Atoi(c.Args)
if err != nil {
return fmt.Errorf("failed to parse height: %w", err)
}
v.Options.Video.Style.Height = height
return nil
}
// ExecuteSetWidth applies the width on the vhs.
func ExecuteSetWidth(c parser.Command, v *VHS) error {
width, err := strconv.Atoi(c.Args)
if err != nil {
return fmt.Errorf("failed to parse width: %w", err)
}
v.Options.Video.Style.Width = width
return nil
}
// ExecuteSetShell applies the shell on the vhs.
func ExecuteSetShell(c parser.Command, v *VHS) error {
s, ok := Shells[c.Args]
if !ok {
return fmt.Errorf("invalid shell %s", c.Args)
}
v.Options.Shell = s
return nil
}
const (
bitSize = 64
base = 10
)
// ExecuteSetLetterSpacing applies letter spacing (also known as tracking) on
// the vhs.
func ExecuteSetLetterSpacing(c parser.Command, v *VHS) error {
letterSpacing, err := strconv.ParseFloat(c.Args, bitSize)
if err != nil {
return fmt.Errorf("failed to parse letter spacing: %w", err)
}
v.Options.LetterSpacing = letterSpacing
_, err = v.Page.Eval(fmt.Sprintf("() => term.options.letterSpacing = %f", letterSpacing))
if err != nil {
return fmt.Errorf("failed to set letter spacing: %w", err)
}
return nil
}
// ExecuteSetLineHeight applies the line height on the vhs.
func ExecuteSetLineHeight(c parser.Command, v *VHS) error {
lineHeight, err := strconv.ParseFloat(c.Args, bitSize)
if err != nil {
return fmt.Errorf("failed to parse line height: %w", err)
}
v.Options.LineHeight = lineHeight
_, err = v.Page.Eval(fmt.Sprintf("() => term.options.lineHeight = %f", lineHeight))
if err != nil {
return fmt.Errorf("failed to set line height: %w", err)
}
return nil
}
// ExecuteSetTheme applies the theme on the vhs.
func ExecuteSetTheme(c parser.Command, v *VHS) error {
var err error
v.Options.Theme, err = getTheme(c.Args)
if err != nil {
return err
}
bts, err := json.Marshal(v.Options.Theme)
if err != nil {
return fmt.Errorf("failed to marshal theme: %w", err)
}
_, err = v.Page.Eval(fmt.Sprintf("() => term.options.theme = %s", string(bts)))
if err != nil {
return fmt.Errorf("failed to set theme: %w", err)
}
v.Options.Video.Style.BackgroundColor = v.Options.Theme.Background
v.Options.Video.Style.WindowBarColor = v.Options.Theme.Background
return nil
}
// ExecuteSetTypingSpeed applies the default typing speed on the vhs.
func ExecuteSetTypingSpeed(c parser.Command, v *VHS) error {
typingSpeed, err := time.ParseDuration(c.Args)
if err != nil {
return fmt.Errorf("failed to parse typing speed: %w", err)
}
v.Options.TypingSpeed = typingSpeed
return nil
}
// ExecuteSetWaitTimeout applies the default wait timeout on the vhs.
func ExecuteSetWaitTimeout(c parser.Command, v *VHS) error {
waitTimeout, err := time.ParseDuration(c.Args)
if err != nil {
return fmt.Errorf("failed to parse wait timeout: %w", err)
}
v.Options.WaitTimeout = waitTimeout
return nil
}
// ExecuteSetWaitPattern applies the default wait pattern on the vhs.
func ExecuteSetWaitPattern(c parser.Command, v *VHS) error {
rx, err := regexp.Compile(c.Args)
if err != nil {
return fmt.Errorf("failed to compile regexp: %w", err)
}
v.Options.WaitPattern = rx
return nil
}
// ExecuteSetPadding applies the padding on the vhs.
func ExecuteSetPadding(c parser.Command, v *VHS) error {
padding, err := strconv.Atoi(c.Args)
if err != nil {
return fmt.Errorf("failed to parse padding: %w", err)
}
v.Options.Video.Style.Padding = padding
return nil
}
// ExecuteSetFramerate applies the framerate on the vhs.
func ExecuteSetFramerate(c parser.Command, v *VHS) error {
framerate, err := strconv.ParseInt(c.Args, base, 0)
if err != nil {
return fmt.Errorf("failed to parse framerate: %w", err)
}
v.Options.Video.Framerate = int(framerate)
return nil
}
// ExecuteSetPlaybackSpeed applies the playback speed option on the vhs.
func ExecuteSetPlaybackSpeed(c parser.Command, v *VHS) error {
playbackSpeed, err := strconv.ParseFloat(c.Args, bitSize)
if err != nil {
return fmt.Errorf("failed to parse playback speed: %w", err)
}
v.Options.Video.PlaybackSpeed = playbackSpeed
return nil
}
// ExecuteLoopOffset applies the loop offset option on the vhs.
func ExecuteLoopOffset(c parser.Command, v *VHS) error {
loopOffset, err := strconv.ParseFloat(strings.TrimRight(c.Args, "%"), bitSize)
if err != nil {
return fmt.Errorf("failed to parse loop offset: %w", err)
}
v.Options.LoopOffset = loopOffset
return nil
}
// ExecuteSetMarginFill sets vhs margin fill.
func ExecuteSetMarginFill(c parser.Command, v *VHS) error {
v.Options.Video.Style.MarginFill = c.Args
return nil
}
// ExecuteSetMargin sets vhs margin size.
func ExecuteSetMargin(c parser.Command, v *VHS) error {
margin, err := strconv.Atoi(c.Args)
if err != nil {
return fmt.Errorf("failed to parse margin: %w", err)
}
v.Options.Video.Style.Margin = margin
return nil
}
// ExecuteSetWindowBar sets window bar type.
func ExecuteSetWindowBar(c parser.Command, v *VHS) error {
v.Options.Video.Style.WindowBar = c.Args
return nil
}
// ExecuteSetWindowBarSize sets window bar size.
func ExecuteSetWindowBarSize(c parser.Command, v *VHS) error {
windowBarSize, err := strconv.Atoi(c.Args)
if err != nil {
return fmt.Errorf("failed to parse window bar size: %w", err)
}
v.Options.Video.Style.WindowBarSize = windowBarSize
return nil
}
// ExecuteSetBorderRadius sets corner radius.
func ExecuteSetBorderRadius(c parser.Command, v *VHS) error {
borderRadius, err := strconv.Atoi(c.Args)
if err != nil {
return fmt.Errorf("failed to parse border radius: %w", err)
}
v.Options.Video.Style.BorderRadius = borderRadius
return nil
}
// ExecuteSetCursorBlink sets cursor blinking.
func ExecuteSetCursorBlink(c parser.Command, v *VHS) error {
var err error
v.Options.CursorBlink, err = strconv.ParseBool(c.Args)
if err != nil {
return fmt.Errorf("failed to parse cursor blink: %w", err)
}
return nil
}
// ExecuteScreenshot is a CommandFunc that indicates a new screenshot must be taken.
func ExecuteScreenshot(c parser.Command, v *VHS) error {
v.ScreenshotNextFrame(c.Args)
return nil
}
func getTheme(s string) (Theme, error) {
if strings.TrimSpace(s) == "" {
return DefaultTheme, nil
}
switch s[0] {
case '{':
return getJSONTheme(s)
default:
return findTheme(s)
}
}
func getJSONTheme(s string) (Theme, error) {
var t Theme
if err := json.Unmarshal([]byte(s), &t); err != nil {
return DefaultTheme, fmt.Errorf("invalid `Set Theme %q: %w`", s, err)
}
return t, nil
}
================================================
FILE: command_test.go
================================================
package main
import (
"reflect"
"testing"
"github.com/charmbracelet/vhs/parser"
)
func TestCommand(t *testing.T) {
const numberOfCommands = 31
if len(parser.CommandTypes) != numberOfCommands {
t.Errorf("Expected %d commands, got %d", numberOfCommands, len(parser.CommandTypes))
}
const numberOfCommandFuncs = 31
if len(CommandFuncs) != numberOfCommandFuncs {
t.Errorf("Expected %d commands, got %d", numberOfCommandFuncs, len(CommandFuncs))
}
}
func TestExecuteSetTheme(t *testing.T) {
t.Run("empty", func(t *testing.T) {
theme, err := getTheme(" ")
requireNoErr(t, err)
requireDefaultTheme(t, theme)
})
t.Run("named", func(t *testing.T) {
theme, err := getTheme("Andromeda")
requireNoErr(t, err)
requireNotDefaultTheme(t, theme)
})
t.Run("json", func(t *testing.T) {
theme, err := getTheme(`{"background": "#29283b"}`)
requireNoErr(t, err)
requireNotDefaultTheme(t, theme)
if "#29283b" != theme.Background {
t.Errorf("wrong background, expected %q, got %q", "#29283b", theme.Background)
}
})
t.Run("suggestion", func(t *testing.T) {
theme, err := getTheme("cattppuccin latt")
requireEqualErr(t, err, "invalid `Set Theme \"cattppuccin latt\"`: did you mean \"Catppuccin Latte\"")
requireDefaultTheme(t, theme)
})
t.Run("invalid json", func(t *testing.T) {
theme, err := getTheme(`{"background`)
requireErr(t, err)
requireDefaultTheme(t, theme)
})
t.Run("unknown theme", func(t *testing.T) {
theme, err := getTheme("foobar")
requireErr(t, err)
requireDefaultTheme(t, theme)
})
}
func requireErr(tb testing.TB, err error) {
tb.Helper()
if err == nil {
tb.Fatalf("expected an error, got nil")
}
}
func requireEqualErr(tb testing.TB, err1 error, err2 string) {
tb.Helper()
if err1 == nil {
tb.Fatalf("expected an error, got nil")
}
if err1.Error() != err2 {
tb.Fatalf("errors do not match: %q != %q", err1.Error(), err2)
}
}
func requireNoErr(tb testing.TB, err error) {
tb.Helper()
if err != nil {
tb.Fatalf("expected no error, got: %v", err)
}
}
func requireDefaultTheme(tb testing.TB, theme Theme) {
tb.Helper()
if !reflect.DeepEqual(DefaultTheme, theme) {
tb.Fatalf("expected theme to be the default theme, got something else: %+v", theme)
}
}
func requireNotDefaultTheme(tb testing.TB, theme Theme) {
tb.Helper()
if reflect.DeepEqual(DefaultTheme, theme) {
tb.Fatalf("expected theme to be different from the default theme, got the default instead")
}
}
================================================
FILE: draw.go
================================================
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"math"
"os"
)
type circle struct {
p image.Point
r int
}
const (
white = 0xFF
black = 0x17
)
func (c *circle) ColorModel() color.Model {
return color.AlphaModel
}
func (c *circle) Bounds() image.Rectangle {
return image.Rect(
c.p.X-c.r,
c.p.Y-c.r,
c.p.X+c.r,
c.p.Y+c.r,
)
}
const (
halfPixel = 0.5
doublingFactor = 2
)
func double(i int) int { return i * doublingFactor }
func half(i int) int { return i / doublingFactor }
func (c *circle) At(x, y int) color.Color {
// Prepare points for circle calculations.
// We subtract 1 from the radius to leave space for
// antialiased pixels.
xx := float64(x-c.p.X) + halfPixel
yy := float64(y-c.p.Y) + halfPixel
rr := float64(c.r) - 1
// The distance from this pixel to the closest point
// in the circle.
dist := math.Sqrt(xx*xx+yy*yy) - rr
if dist < 0 {
// This pixel is inside the circle
return color.Alpha{white}
} else if dist <= 1 {
// This pixel is partly inside the circle
// and needs antialiasing
return color.Alpha{
uint8((1 - dist) * white),
}
}
// This pixel is outside the circle
// and should be fully transparent
return color.Alpha{0x00}
}
type rect struct {
pa image.Point
pb image.Point
}
func (r *rect) ColorModel() color.Model {
return color.AlphaModel
}
func (r *rect) Bounds() image.Rectangle {
return image.Rect(r.pa.X, r.pa.Y, r.pb.X, r.pb.Y)
}
func (r *rect) At(x, y int) color.Color {
if (x >= r.pa.X) &&
(x < r.pb.X) &&
(y >= r.pa.Y) &&
(y < r.pb.Y) {
return color.Alpha{white}
}
return color.Alpha{0x00}
}
type roundedrect struct {
pa image.Point
pb image.Point
radius int
}
func (r *roundedrect) ColorModel() color.Model {
return color.AlphaModel
}
func (r *roundedrect) Bounds() image.Rectangle {
return image.Rect(r.pa.X, r.pa.Y, r.pb.X, r.pb.Y)
}
func (r *roundedrect) At(x, y int) color.Color {
// Top-left corner
if (x >= r.pa.X) &&
(x < r.pa.X+r.radius) &&
(y >= r.pa.Y) &&
(y < r.pa.Y+r.radius) {
c := circle{
image.Point{
r.radius,
r.radius,
},
// Add one to corner radius so that
// fully-opaque pixels match the rectangle.
// The outermost pixels of a circle are
// always antialiased and thus transparent.
r.radius + 1,
}
return c.At(x, y)
}
// Top-right corner
if (x >= r.pb.X-r.radius) &&
(x < r.pb.X) &&
(y >= r.pa.Y) &&
(y < r.pa.Y+r.radius) {
c := circle{
image.Point{
r.pb.X - r.radius,
r.radius,
},
r.radius + 1,
}
return c.At(x, y)
}
// Bottom-left corner
if (x >= r.pa.X) &&
(x < r.pa.X+r.radius) &&
(y >= r.pb.Y-r.radius) &&
(y < r.pb.Y) {
c := circle{
image.Point{
r.radius,
r.pb.Y - r.radius,
},
r.radius + 1,
}
return c.At(x, y)
}
// Bottom-right corner
if (x >= r.pb.X-r.radius) &&
(x < r.pb.X) &&
(y >= r.pb.Y-r.radius) &&
(y < r.pb.Y) {
c := circle{
image.Point{
r.pb.X - r.radius,
r.pb.Y - r.radius,
},
r.radius + 1,
}
return c.At(x, y)
}
return color.Alpha{white}
}
// MakeBorderRadiusMask a mask to round a terminal's corners.
func MakeBorderRadiusMask(width, height, radius int, targetpng string) {
img := image.NewGray(
image.Rectangle{
image.Point{0, 0},
image.Point{width, height},
},
)
// Fill image with black
draw.DrawMask(
img, img.Bounds(), &image.Uniform{color.Gray{0x00}}, image.Point{0, 0},
&rect{image.Point{0, 0}, image.Point{width, height}},
image.Point{0, 0}, draw.Src,
)
// Put mask in white on top
draw.DrawMask(
img, img.Bounds(), &image.Uniform{color.Gray{white}}, image.Point{0, 0},
&roundedrect{image.Point{0, 0}, image.Point{width, height}, radius},
image.Point{0, 0}, draw.Over,
)
f, err := os.Create(targetpng)
if err != nil {
fmt.Println(ErrorStyle.Render("Could not draw Border Mask: unable to save file."))
} else {
err = png.Encode(f, img)
}
if err != nil {
fmt.Println(ErrorStyle.Render("Could not draw Border Mask: encoding failed."))
}
}
// MakeWindowBar a window bar and save it to a file.
func MakeWindowBar(termWidth, termHeight int, opts StyleOptions, file string) {
var err error
switch opts.WindowBar {
case "Colorful":
err = makeColorfulBar(termWidth, termHeight, false, opts, file)
case "ColorfulRight":
err = makeColorfulBar(termWidth, termHeight, true, opts, file)
case "Rings":
err = makeRingBar(termWidth, termHeight, false, opts, file)
case "RingsRight":
err = makeRingBar(termWidth, termHeight, true, opts, file)
}
if err != nil {
fmt.Println(ErrorStyle.Render("Couldn't draw Bar: encoding failed"))
}
}
const (
barToDotRatio = 6
barToDotBorderRatio = 5
)
func makeColorfulBar(termWidth int, termHeight int, isRight bool, opts StyleOptions, targetpng string) error {
// Radius of dots
dotRad := opts.WindowBarSize / barToDotRatio
dotDia := double(dotRad)
// Space between dots and edge
dotGap := half(opts.WindowBarSize - dotDia)
// Space between dot centers
dotSpace := dotDia + opts.WindowBarSize/barToDotRatio
// Dimensions of bar image
width := termWidth
height := termHeight + opts.WindowBarSize
img := image.NewRGBA(
image.Rectangle{
image.Point{0, 0},
image.Point{width, height},
},
)
bg, _ := parseHexColor(opts.WindowBarColor)
dotA := color.RGBA{white, 0x4F, 0x4D, white}
dotB := color.RGBA{0xFE, 0xBB, 0x00, white}
dotC := color.RGBA{0x00, 0xCC, 0x1D, white}
var pta, ptb, ptc image.Point
if isRight {
pta = image.Point{termWidth - (dotGap + dotRad), dotRad + dotGap}
ptb = image.Point{termWidth - (dotGap + dotRad + dotSpace), dotRad + dotGap}
ptc = image.Point{termWidth - (dotGap + dotRad + 2*dotSpace), dotRad + dotGap}
} else {
pta = image.Point{dotGap + dotRad, dotRad + dotGap}
ptb = image.Point{dotGap + dotRad + dotSpace, dotRad + dotGap}
ptc = image.Point{dotGap + dotRad + 2*dotSpace, dotRad + dotGap}
}
draw.DrawMask(
img, img.Bounds(), &image.Uniform{bg}, image.Point{0, 0},
&rect{image.Point{0, 0}, image.Point{width, height}},
image.Point{0, 0}, draw.Src,
)
draw.DrawMask(
img,
img.Bounds(),
&image.Uniform{dotA},
image.Point{0, 0},
&circle{pta, dotRad},
image.Point{0, 0},
draw.Over,
)
draw.DrawMask(
img,
img.Bounds(),
&image.Uniform{dotB},
image.Point{0, 0},
&circle{ptb, dotRad},
image.Point{0, 0},
draw.Over,
)
draw.DrawMask(
img,
img.Bounds(),
&image.Uniform{dotC},
image.Point{0, 0},
&circle{ptc, dotRad},
image.Point{0, 0},
draw.Over,
)
f, err := os.Create(targetpng)
if err != nil {
fmt.Println(ErrorStyle.Render("Couldn't draw colorful bar: unable to save file."))
} else {
err = png.Encode(f, img)
}
return err //nolint:wrapcheck
}
func makeRingBar(termWidth int, termHeight int, isRight bool, opts StyleOptions, targetpng string) error {
// Radius of dots
outerRad := opts.WindowBarSize / barToDotBorderRatio
outerDia := double(outerRad)
innerRad := double(outerDia) / barToDotBorderRatio
// Space between dots and edge
ringGap := half(opts.WindowBarSize - outerDia)
// Space between dot centers
ringSpace := outerDia + opts.WindowBarSize/barToDotRatio
// Dimensions of bar image
width := termWidth
height := termHeight + opts.WindowBarSize
img := image.NewRGBA(
image.Rectangle{
image.Point{0, 0},
image.Point{width, height},
},
)
bg, _ := parseHexColor(opts.WindowBarColor)
ring := color.RGBA{0x33, 0x33, 0x33, white}
draw.DrawMask(
img, img.Bounds(), &image.Uniform{bg}, image.Point{0, 0},
&rect{image.Point{0, 0}, image.Point{width, height}},
image.Point{0, 0}, draw.Src,
)
for i := 0; i <= 2; i++ {
var pt image.Point
if isRight {
pt = image.Point{
termWidth - (ringGap + outerRad + i*ringSpace),
outerRad + ringGap,
}
} else {
pt = image.Point{
ringGap + outerRad + i*ringSpace,
outerRad + ringGap,
}
}
draw.DrawMask(
img,
img.Bounds(),
&image.Uniform{ring},
image.Point{0, 0},
&circle{pt, outerRad},
image.Point{0, 0},
draw.Over,
)
draw.DrawMask(
img,
img.Bounds(),
&image.Uniform{bg},
image.Point{0, 0},
&circle{pt, innerRad},
image.Point{0, 0},
draw.Over,
)
}
f, err := os.Create(targetpng)
if err != nil {
fmt.Println(ErrorStyle.Render("Couldn't draw ring bar: unable to save file."))
} else {
err = png.Encode(f, img)
}
return err //nolint:wrapcheck
}
//nolint:mnd
func parseHexColor(s string) (c color.RGBA, err error) {
c.R, c.G, c.B, c.A = black, black, black, white
switch len(s) {
case 7:
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
case 6:
_, err = fmt.Sscanf(s, "%02x%02x%02x", &c.R, &c.G, &c.B)
case 4:
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
// Double the hex digits:
c.R *= 17
c.G *= 17
c.B *= 17
case 3:
_, err = fmt.Sscanf(s, "%1x%1x%1x", &c.R, &c.G, &c.B)
// Double the hex digits:
c.R *= 17
c.G *= 17
c.B *= 17
default:
err = fmt.Errorf("%s color of invalid length", s)
}
return
}
================================================
FILE: embed.go
================================================
package main
import _ "embed"
//go:embed examples/demo.tape
var demoTape []byte
================================================
FILE: error.go
================================================
package main
import (
"fmt"
"io"
"strings"
"github.com/charmbracelet/vhs/parser"
)
// InvalidSyntaxError is returned when the parser encounters one or more errors.
type InvalidSyntaxError struct {
Errors []parser.Error
}
func (e InvalidSyntaxError) Error() string {
return fmt.Sprintf("parser: %d error(s)", len(e.Errors))
}
// ErrorColumnOffset is the number of columns that an error should be printed
// to the left to account for the line number.
const ErrorColumnOffset = 5
// Underline returns a string of ^ characters which helps underline the problematic token
// in a parser.Error.
func Underline(n int) string {
return ErrorStyle.Render(strings.Repeat("^", n))
}
// LineNumber returns a formatted version of the given line number.
func LineNumber(line int) string {
return LineNumberStyle.Render(fmt.Sprintf(" %2d │ ", line))
}
func printError(out io.Writer, tape string, err parser.Error) {
lines := strings.Split(tape, "\n")
_, _ = fmt.Fprint(out, LineNumber(err.Token.Line))
_, _ = fmt.Fprintln(out, lines[err.Token.Line-1])
_, _ = fmt.Fprint(out, strings.Repeat(" ", err.Token.Column+ErrorColumnOffset))
_, _ = fmt.Fprintln(out, Underline(len(err.Token.Literal)), err.Msg)
_, _ = fmt.Fprintln(out)
}
func printErrors(out io.Writer, tape string, errs []error) {
for _, err := range errs {
switch err := err.(type) {
case InvalidSyntaxError:
for _, v := range err.Errors {
printError(out, tape, v)
}
_, _ = fmt.Fprintln(out, ErrorStyle.Render(err.Error()))
default:
_, _ = fmt.Fprintln(out, ErrorStyle.Render(err.Error()))
}
}
}
================================================
FILE: evaluator.go
================================================
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"github.com/charmbracelet/vhs/lexer"
"github.com/charmbracelet/vhs/parser"
"github.com/charmbracelet/vhs/token"
"github.com/go-rod/rod"
)
// EvaluatorOption is a function that can be used to modify the VHS instance.
type EvaluatorOption func(*VHS)
// Evaluate takes as input a tape string, an output writer, and an output file
// and evaluates all the commands within the tape string and produces a GIF.
func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...EvaluatorOption) []error {
l := lexer.New(tape)
p := parser.New(l)
cmds := p.Parse()
errs := p.Errors()
if len(errs) != 0 || len(cmds) == 0 {
return []error{InvalidSyntaxError{errs}}
}
v := New()
for _, cmd := range cmds {
if cmd.Type == token.SET && cmd.Options == "Shell" || cmd.Type == token.ENV {
err := Execute(cmd, &v)
if err != nil {
return []error{err}
}
}
}
// Start things up
if err := v.Start(); err != nil {
return []error{err}
}
defer func() { _ = v.close() }()
// Let's wait until we can access the window.term variable.
//
// This is necessary because some SET commands modify the terminal.
err := v.Page.Wait(rod.Eval("() => window.term != undefined"))
if err != nil {
return []error{err}
}
var offset int
for i, cmd := range cmds {
if cmd.Type == token.SET || cmd.Type == token.OUTPUT || cmd.Type == token.REQUIRE {
_, _ = fmt.Fprintln(out, Highlight(cmd, false))
if cmd.Options != "Shell" {
err := Execute(cmd, &v)
if err != nil {
return []error{err}
}
}
} else {
offset = i
break
}
}
// Make sure image is big enough to fit padding, bar, and margins
video := v.Options.Video
minWidth := double(video.Style.Padding) + double(video.Style.Margin)
minHeight := double(video.Style.Padding) + double(video.Style.Margin)
if video.Style.WindowBar != "" {
minHeight += video.Style.WindowBarSize
}
if video.Style.Height < minHeight || video.Style.Width < minWidth {
//nolint:staticcheck
v.Errors = append(
v.Errors,
fmt.Errorf(
"Dimensions must be at least %d x %d",
minWidth, minHeight,
),
)
}
if len(v.Errors) > 0 {
return v.Errors
}
// Setup the terminal session so we can start executing commands.
v.Setup()
// If the first command (after Settings and Outputs) is a Hide command, we can
// begin executing the commands before we start recording to avoid capturing
// any unwanted frames.
if cmds[offset].Type == token.HIDE {
for i, cmd := range cmds[offset:] {
if cmd.Type == token.SHOW {
offset += i
break
}
_, _ = fmt.Fprintln(out, Highlight(cmd, true))
err := Execute(cmd, &v)
if err != nil {
return []error{err}
}
}
}
// Begin recording frames as we are now in a recording state.
ctx, cancel := context.WithCancel(ctx) //nolint:gosec
ch := v.Record(ctx)
// Clean up temporary files at the end.
defer func() {
if v.Options.Video.Output.Frames != "" {
// Move the frames to the output directory.
_ = os.Rename(v.Options.Video.Input, v.Options.Video.Output.Frames)
}
_ = v.Cleanup()
}()
teardown := func() {
// Stop recording frames.
cancel()
// Read from channel to ensure recorder is done.
<-ch
}
// Log errors from the recording process.
go func() {
for err := range ch {
log.Print(err.Error())
}
}()
for _, cmd := range cmds[offset:] {
if ctx.Err() != nil {
teardown()
return []error{ctx.Err()}
}
// When changing the FontFamily, FontSize, LineHeight, Padding
// The xterm.js canvas changes dimensions and causes FFMPEG to not work
// correctly (specifically) with palettegen.
// It will be possible to change settings on the fly in the future, but
// it is currently not as it does not result in a proper render of the
// GIF as the frame sequence will change dimensions. This is fixable.
//
// We should remove if isSetting statement.
isSetting := cmd.Type == token.SET && cmd.Options != "TypingSpeed"
if isSetting {
fmt.Println(ErrorStyle.Render(fmt.Sprintf("WARN: 'Set %s %s' has been ignored. Move the directive to the top of the file.\nLearn more: https://github.com/charmbracelet/vhs#settings", cmd.Options, cmd.Args)))
}
if isSetting || cmd.Type == token.REQUIRE {
_, _ = fmt.Fprintln(out, Highlight(cmd, true))
continue
}
_, _ = fmt.Fprintln(out, Highlight(cmd, !v.recording || cmd.Type == token.SHOW || cmd.Type == token.HIDE || isSetting))
err := Execute(cmd, &v)
if err != nil {
teardown()
return []error{err}
}
}
// If running as an SSH server, the output file is a temporary file
// to use for the output.
//
// We need to set the GIF file path before it is created but after all of
// the settings and commands are executed. This is done in `serve.go`.
//
// Since the GIF creation is deferred, setting the output file here will
// achieve what we want.
for _, opt := range opts {
opt(&v)
}
teardown()
if err := v.Render(); err != nil {
return []error{err}
}
return nil
}
================================================
FILE: examples/README.md
================================================
# Examples
### Gum
Example of recording a demo of [Gum](https://github.com/charmbracelet/gum)
with VHS.
#### Gum File
<img alt="gum file demo with VHS" src="./gum/file.gif" width="600" />
```
Output file.gif
Type "gum file ./src"
Sleep 1s
Enter
Sleep 2s
Down@500ms 4
Up@500ms 1
Sleep 1s
Enter
Sleep 1s
Down@500ms 4
Sleep 1s
Up@500ms 2
Sleep 2s
```
#### Gum Pager
<img alt="gum pager demo with VHS" src="./gum/pager.gif" width="600" />
```
Output pager.gif
Set Padding 20
Set FontSize 16
Set Height 600
Type "gum pager < ~/src/gum/README.md --border normal"
Sleep 1s
Enter
Sleep 2s
Down@25ms 40
Sleep 1s
Up@25ms 30
Sleep 1s
Down@25ms 20
Sleep 3s
```
#### Gum Table
<img alt="gum table demo with VHS" src="./gum/table.gif" width="600" />
```
Output table.gif
Type "gum table < superhero.csv -w 2,12,5,6,6,8,4,20 --height 10"
Enter
Sleep 1s
Down@200ms 10
Sleep 1s
Down@200ms 10
Sleep 1s
Up@200ms 10
Sleep 1s
Enter
Sleep 3s
```
### GitHub CLI
Examples recorded with VHS for the GitHub CLI (`gh`):
#### Issues
<img alt="Simple gh issue demo" src="./gh-cli/gh-issue.gif" width="600" />
```
Output gh-issue.gif
Type "gh issue list"
Sleep 500ms
Enter 1
Sleep 4s
Ctrl+L
Sleep 500ms
Type "gh issue view 19"
Sleep 500ms
Enter
Sleep 5s
```
#### Pull Requests
<img alt="Simple gh pr demo" src="./gh-cli/gh-pr.gif" width="600" />
```
Output gh-pr.gif
Type "gh pr list --state all"
Sleep 500ms
Enter
Sleep 5s
```
### Bubble Tea
Examples recorded with VHS for Bubble Tea.
* [GIFS Renders](https://github.com/charmbracelet/bubbletea/tree/master/examples)
* [Tape Files](./bubbletea)
### jqp
Example of recording a demo of [`jqp`](https://github.com/noahgorstein/jqp)
with VHS.
<img alt="Simple jqp demo with VHS" src="./jqp/jqp.gif" width="600" />
### Glow
Example of recording a demo of [Glow](https://github.com/charmbracelet/glow)
with VHS.
#### Glow Simple
<img alt="Simple glow demo with VHS" src="./glow/glow-simple.gif" width="600" />
```
Output glow-simple.ascii
Output glow-simple.gif
Set Width 1000
Set Height 1000
Type "glow"
Enter
Sleep 1s
Enter
Sleep 1s
Escape
Sleep 1s
Type "q"
Sleep 1s
```
#### Glow
<img alt="Glow demo with VHS" src="./glow/vhs-glow.gif" />
```
Output vhs-glow.gif
Output glow.ascii
Set Width 1600
Set Height 1040
Sleep 1s
Type "glow"
Sleep 100ms
Enter
Sleep 1s
Hide
Tab
Type "/artichoke"
Enter
Down 2
Show
Sleep 0.5s
Down 20
Hide
Escape
Type "l"
Down 5
Show
Sleep 1s
Up@400ms 5
Hide
Type "/ulysses"
Enter
Show
Sleep 0.5s
Down@200ms 20
Hide
Escape
Type "/"
Show
Sleep 0.5s
Type@500ms "todo"
Sleep 1
Hide
Escape
Type "/ulysses"
Enter
Show
Sleep 0.5s
Type@750ms "????"
Hide
Escape
Type "/artichoke"
Enter
Type "m"
Ctrl+A
Right 4
Show
Sleep 1s
Type@250ms "Tasty "
Sleep 1s
Hide
Escape
Down 5
Type "m"
Ctrl+U
Show
Sleep 1s
Type@150ms "Your new internet thing"
Sleep 3s
Hide
Ctrl+C
Show
```
================================================
FILE: examples/bubbletea/altscreen-toggle.tape
================================================
Output examples/bubbletea/altscreen-toggle.gif
Hide
Type "go build -o altscreen-toggle ."
Enter
Type "clear"
Enter
Show
Type "./altscreen-toggle"
Enter
Sleep 0.5
Space@0.5 4
Type "q"
Sleep 1
Hide
Type "rm ./altscreen-toggle"
Enter
================================================
FILE: examples/bubbletea/chat.tape
================================================
Output examples/bubbletea/chat.gif
Hide
Type "go build -o chat ."
Enter
Type "clear"
Enter
Show
Type "./chat"
Enter
Sleep 0.5
Type "Hello, Chat Room" Sleep .25
Enter
Sleep 0.5
Type "!!!" Sleep .25
Enter
Sleep 1
Hide
Ctrl+C
Type "rm chat"
Enter
================================================
FILE: examples/bubbletea/composable-views.tape
================================================
Output examples/bubbletea/composable-views.gif
Hide
Type "go build -o views ."
Enter
Type "clear"
Enter
Show
Type "./views"
Enter
Sleep 0.5
Tab@1 4
Sleep 0.5
Type "n"
Tab
Sleep 0.5
Type@250ms "nnnn"
Sleep 1
Hide
Ctrl+C
Type "rm views"
Enter
================================================
FILE: examples/bubbletea/credit-card-form.tape
================================================
Output examples/bubbletea/credit-card-form.gif
Hide
Type "go build -o credit-card ."
Enter
Type "clear"
Enter
Show
Type "./credit-card"
Enter
Sleep 0.5
Type "1234 5678 9012 3456"
Sleep .3 Tab Sleep .3
Type "12/34"
Sleep .3 Tab Sleep .3
Type "123"
Sleep 1
Hide
Ctrl+C
Type "rm credit-card"
Enter
================================================
FILE: examples/bubbletea/debounce.tape
================================================
Output examples/bubbletea/debounce.gif
Hide
Type "go build -o debounce ."
Enter
Type "clear"
Enter
Show
Type "./debounce"
Enter
Sleep 0.5
Space@250ms 10
Sleep 1
Hide
Ctrl+C
Type "rm debounce"
Enter
================================================
FILE: examples/bubbletea/exec.tape
================================================
Output examples/bubbletea/exec.gif
Hide
Type "go build -o exec ."
Enter
Type "clear"
Enter
Show
Type "EDITOR=nano ./exec"
Enter
Sleep 0.5
Type@0.5 "aaaa"
Sleep 1
Type@0.5 "e"
Type "Hello, EDITOR!"
Sleep 1
Ctrl+X
Sleep 0.5
Type "n"
Sleep 1
Hide
Type "q"
Type "rm exec"
Enter
================================================
FILE: examples/bubbletea/fullscreen.tape
================================================
Output examples/bubbletea/fullscreen.gif
Hide
Type "go build -o fullscreen ."
Enter
Type "clear"
Enter
Show
Type "./fullscreen"
Sleep 0.5
Enter
Sleep 4
Hide
Type "rm fullscreen"
Enter
================================================
FILE: examples/bubbletea/glamour.tape
================================================
Output examples/bubbletea/glamour.gif
Set Height 750
Set FontSize 16
Hide
Type "go build -o glamour ."
Enter
Type "clear"
Enter
Show
Type "./glamour"
Enter
Sleep 1
Down@10ms 25
Sleep 1
Hide
Type "q"
Type "rm glamour"
Enter
Show
================================================
FILE: examples/bubbletea/help.tape
================================================
Output examples/bubbletea/help.gif
Hide
Type "go build -o help ."
Enter
Type "clear"
Enter
Show
Type "./help"
Enter
Sleep 0.5
Type@1 "?"
Up@0.5
Down@0.5
Left@0.5
Right@0.5
Type@1 "?"
Type "q"
Sleep 1
Hide
Type "rm help"
Enter
================================================
FILE: examples/bubbletea/http.tape
================================================
Output examples/bubbletea/http.gif
Hide
Type "go build -o http ."
Enter
Type "clear"
Enter
Show
Type "./http"
Enter
Sleep 2
Hide
Ctrl+C
Type "rm http"
Enter
================================================
FILE: examples/bubbletea/list-default.tape
================================================
Output examples/bubbletea/list-default.gif
Set FontSize 16
Hide
Type "go build -o list-default ."
Enter
Type "clear"
Enter
Show
Type "./list-default"
Enter
Sleep 0.5
Down@250ms 3
Right@250ms 3
Type "/"
Sleep 0.5
Type "nutel"
Enter
Sleep 0.5
Type "?"
Sleep 0.5
Type "?"
Sleep 0.5
Escape@250ms 2
Hide
Ctrl+C
Type "rm list-default"
Enter
================================================
FILE: examples/bubbletea/list-fancy.tape
================================================
Output examples/bubbletea/list-fancy.gif
Set FontSize 14
Hide
Type "go build -o list-fancy ."
Enter
Type "clear"
Enter
Show
Type "./list-fancy"
Enter
Sleep 0.5
Down@250ms 3
Enter
Sleep 0.5
Type "x"
Sleep 0.5
Type "?"
Sleep 0.5
Type@150ms "aaaa"
Type@150ms "xxxx"
Hide
Ctrl+C
Type "rm list-fancy"
Enter
================================================
FILE: examples/bubbletea/list-simple.tape
================================================
Output examples/bubbletea/list-simple.gif
Set FontSize 18
Hide
Type "go build -o list-simple ."
Enter
Type "clear"
Enter
Show
Type "./list-simple"
Enter
Sleep 0.5
Down@250ms 3
Enter
Sleep 1
Hide
Ctrl+C
Type "rm list-simple"
Enter
================================================
FILE: examples/bubbletea/package-manager.tape
================================================
Output examples/bubbletea/package-manager.gif
Hide
Type "go build -o package-manager ."
Enter
Type "clear"
Enter
Show
Type "./package-manager"
Enter
Sleep 0.5
Sleep 10
Hide
Ctrl+C
Type "rm package-manager"
Enter
Show
================================================
FILE: examples/bubbletea/pager.tape
================================================
Output examples/bubbletea/pager.gif
Set FontSize 16
Hide
Type "go build -o pager ."
Enter
Type "clear"
Enter
Show
Type "./pager"
Enter
Sleep 0.5
Down@25ms 20
Hide
Type "q"
Type "rm pager"
Enter
================================================
FILE: examples/bubbletea/paginator.tape
================================================
Output examples/bubbletea/paginator.gif
Set FontSize 12
Set Padding 36
Hide
Type "go build -o paginator ."
Enter
Type "clear"
Enter
Show
Type "./paginator"
Enter
Sleep 1
Right@250ms 10
Sleep 1
Left@250ms 10
Sleep 1
Hide
Ctrl+C
Type "rm paginator"
Enter
================================================
FILE: examples/bubbletea/pipe.tape
================================================
Output examples/bubbletea/pipe.gif
Hide
Type "go build -o pipe . && clear"
Enter
Show
Type "echo 'Hello' | ./pipe"
Enter
Sleep 1
Type ", world!"
Sleep 0.5
Enter
Sleep 1
Hide
Ctrl+C
Type "rm pipe"
Enter
================================================
FILE: examples/bubbletea/progress-animated.tape
================================================
Output examples/bubbletea/progress-animated.gif
Hide
Type "go build -o progress-animated ."
Enter
Type "clear"
Enter
Show
Type "./progress-animated"
Enter
Sleep 1
Sleep 5
Hide
Ctrl+C
Type "rm progress-animated"
Enter
Show
================================================
FILE: examples/bubbletea/progress-static.tape
================================================
Output examples/bubbletea/progress-static.gif
Hide
Type "go build -o progress-static ."
Enter
Type "clear"
Enter
Show
Type "./progress-static"
Enter
Sleep 5
Hide
Ctrl+C
Type "rm progress-static"
Enter
================================================
FILE: examples/bubbletea/realtime.tape
================================================
Output examples/bubbletea/realtime.gif
Hide
Type "go build -o realtime ."
Enter
Type "clear"
Enter
Show
Type "./realtime"
Enter
Sleep 0.5
Sleep 2
Space # Exit
Sleep 1
Hide
Ctrl+C
Type "rm realtime"
Enter
================================================
FILE: examples/bubbletea/result.tape
================================================
Output examples/bubbletea/result.gif
Hide
Type "go build -o result ."
Enter
Type "clear"
Enter
Show
Type "./result"
Enter
Sleep 0.5
Down@250ms 2
Up@250ms 2
Enter
Sleep 1
Hide
Ctrl+C
Type "rm result"
Enter
================================================
FILE: examples/bubbletea/send-msg.tape
================================================
Output examples/bubbletea/send-msg.gif
Hide
Type "go build -o send-msg ."
Enter
Type "clear"
Enter
Show
Type "./send-msg"
Enter
Sleep 3
Space
Sleep 1
Hide
Ctrl+C
Type "rm send-msg"
Enter
================================================
FILE: examples/bubbletea/sequence.tape
================================================
Output examples/bubbletea/sequence.gif
Hide
Type "go build -o sequence ."
Enter
Type "clear"
Enter
Show
Type "./sequence"
Enter
Sleep 3
Hide
Ctrl+C
Type "rm sequence"
Enter
================================================
FILE: examples/bubbletea/simple.tape
================================================
Output examples/bubbletea/simple.gif
Hide
Type "go build -o simple ."
Enter
Type "clear"
Enter
Show
Type "./simple"
Enter
Sleep 0.5
Sleep 3
Hide
Ctrl+C
Type "rm simple"
Enter
================================================
FILE: examples/bubbletea/spinner.tape
================================================
Output examples/bubbletea/spinner.gif
Hide
Type "go build -o spinner ."
Enter
Type "clear"
Enter
Show
Type "./spinner"
Enter
Sleep 0.5
Sleep 3
Type "q"
Sleep 0.5
Hide
Ctrl+C
Type "rm spinner"
Enter
================================================
FILE: examples/bubbletea/spinners.tape
================================================
Output examples/bubbletea/spinners.gif
Hide
Type "go build -o spinners ."
Enter
Type "clear"
Enter
Show
Type "./spinners"
Enter
Sleep 3
Right@1 5
Sleep 1
Hide
Ctrl+C
Type "rm spinners"
Enter
================================================
FILE: examples/bubbletea/split-editors.tape
================================================
Output examples/bubbletea/split-editors.gif
Set FontSize 16
Hide
Type "go build -o split-editors ."
Enter
Type "clear"
Enter
Show
Type "./split-editors"
Enter
Sleep 1
Type "Hello, there!"
Sleep 0.5
Tab
Sleep 0.5
Type "Hi!"
Sleep 0.5
Enter
Type "How are you?"
Sleep 0.5
Tab
Sleep 0.5
Ctrl+U
Sleep 0.5
Type "I'm good! Thanks for asking :)"
Sleep .5
Enter
Sleep 0.5
Ctrl+N
Sleep 0.5
Tab@250ms 2
Type "Hello, world!"
Sleep 1
Hide
Escape
Type "rm split-editors"
Enter
================================================
FILE: examples/bubbletea/stopwatch.tape
================================================
Output examples/bubbletea/stopwatch.gif
Hide
Type "go build -o stopwatch ."
Enter
Type "clear"
Enter
Show
Type "./stopwatch"
Enter
Sleep 0.5
Sleep 1
Type "s"
Sleep 0.5
Type "r"
Sleep 0.5
Type "s"
Sleep 2
Type "q"
Sleep 0.5
Hide
Ctrl+C
Type "rm stopwatch"
Enter
Show
================================================
FILE: examples/bubbletea/table.tape
================================================
Output examples/bubbletea/table.gif
Hide
Type "go build -o table ."
Enter
Type "clear"
Enter
Show
Type "./table"
Enter
Sleep 0.5
Down@250ms 4
Sleep 1
Enter
Sleep 1
Hide
Ctrl+C
Type "rm table"
Enter
================================================
FILE: examples/bubbletea/tabs.tape
================================================
Output examples/bubbletea/tabs.gif
Hide
Type "go build -o tabs ."
Enter
Type "clear"
Enter
Show
Type "./tabs"
Enter
Sleep 0.5
Right@0.5 5
Left@0.5 5
Sleep 1
Hide
Ctrl+C
Type "rm tabs"
Enter
================================================
FILE: examples/bubbletea/textarea.tape
================================================
Output examples/bubbletea/textarea.gif
Hide
Type "go build -o textarea ."
Enter
Type "clear"
Enter
Show
Type "./textarea"
Enter
Sleep 0.5
Type "Makin' my way downtown"
Sleep 250ms
Enter
Type "Walking fast, faces pass"
Sleep 250ms
Enter
Type "And I'm homebound"
Sleep 1
Ctrl+C
Sleep 1
Hide
Type "rm textarea"
Enter
================================================
FILE: examples/bubbletea/textinput.tape
================================================
Output examples/bubbletea/textinput.gif
Hide
Type "go build -o textinput ."
Enter
Type "clear"
Enter
Show
Type "./textinput"
Enter
Sleep 0.5
Type "Ponyta"
Sleep 0.5
Left@100ms 2
Type "(lalala)"
Sleep 1
Hide
Ctrl+C
Type "rm textinput"
Enter
================================================
FILE: examples/bubbletea/textinputs.tape
================================================
Output examples/bubbletea/textinputs.gif
Hide
Type "go build -o textinputs ."
Enter
Type "clear"
Enter
Show
Type "./textinputs"
Enter
Sleep 0.5
Type "qt314"
Sleep 0.5
Tab
Sleep 0.5
Ctrl+R
Sleep 0.5
Type "pi@cute.com"
Sleep 0.5
Ctrl+R
Tab
Sleep 0.5
Type "password"
Hide
Ctrl+C
Type "rm textinputs"
Enter
================================================
FILE: examples/bubbletea/timer.tape
================================================
Output examples/bubbletea/timer.gif
Hide
Type "go build -o timer ."
Enter
Type "clear"
Enter
Show
Type "./timer"
Sleep 0.5
Enter
Sleep 0.5
Type "s"
Sleep 0.5
Type "s"
Sleep 2
Hide
Ctrl+C
Type "rm timer"
Enter
================================================
FILE: examples/bubbletea/tui-daemon-combo.tape
================================================
Output examples/bubbletea/tui-daemon-combo.gif
Hide
Type "go build -o tui-daemon-combo ."
Enter
Type "clear"
Enter
Show
Type "./tui-daemon-combo"
Enter
Sleep 0.5
Sleep 3
Space
Sleep 1
Hide
Ctrl+C
Type "rm tui-daemon-combo"
Enter
================================================
FILE: examples/bubbletea/views.tape
================================================
Output examples/bubbletea/views.gif
Hide
Type "go build -o views ."
Enter
Type "clear"
Enter
Show
Type "./views"
Enter
Sleep 1
Down@0.5 2
Sleep 0.5
Enter
Sleep 3
Hide
Ctrl+C
Type "rm views"
Enter
================================================
FILE: examples/cli-ui/Gemfile
================================================
source "https://rubygems.org"
gem 'cli-ui'
================================================
FILE: examples/cli-ui/README.md
================================================
# CLI UI
### Format
<img width="600" src="./format.gif" />
```
Output examples/cli-ui/format.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type 'puts CLI::UI.fmt "{{red:Red}} {{green:Green}}"'
Sleep .5
Enter
Sleep 5
```
### Nested Frames
<img width="600" src="./nested-frames.gif" />
```
Output examples/cli-ui/nested-frames.gif
Set FontSize 32
Set Width 2000
Set Height 750
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI::Frame.open('Frame 1') do"
Enter
Type " CLI::UI::Frame.open('Frame 2') { puts 'inside frame 2' }"
Enter
Type " puts 'inside frame 1'"
Enter
Type "end"
Sleep 1
Enter
Sleep 3
```
### Progress
<img width="600" src="./progress.gif" />
```
Output examples/cli-ui/progress.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI::Progress.progress { |bar| 100.times { sleep 0.02; bar.tick } }"
Sleep .5
Enter
Sleep 5
```
### Spinner
<img width="600" src="./spinner.gif" />
```
Output examples/cli-ui/spinner.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI::Spinner.spin('Spinning...') { sleep 3 }"
Sleep .5
Enter
Sleep 5
```
### Status Widget
<img width="600" src="./status-widget.gif" />
```
Output examples/cli-ui/status-widget.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type 'CLI::UI::Spinner.spin("Building: {{@widget/status:1:2:3:4}}") { |spinner| sleep 3 }'
Sleep .5
Enter
Sleep 5
```
### Symbols
<img width="600" src="./symbols.gif" />
```
Output examples/cli-ui/symbols.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type 'puts CLI::UI.fmt "{{*}} {{v}} {{?}} {{x}}"'
Sleep .5
Enter
Sleep 5
```
### Text Prompt
<img width="600" src="./text-prompt.gif" />
```
Output examples/cli-ui/text-prompt.ascii
Output examples/cli-ui/text-prompt.gif
Set FontSize 32
Set Width 2200
Set Height 500
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')"
Sleep .5
Enter
Sleep 1
Type "I love it!"
Sleep 1
Enter
Sleep 3
```
================================================
FILE: examples/cli-ui/format.tape
================================================
Output examples/cli-ui/format.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type 'puts CLI::UI.fmt "{{red:Red}} {{green:Green}}"'
Sleep .5
Enter
Sleep 5
================================================
FILE: examples/cli-ui/interactive-prompt.tape
================================================
Output examples/cli-ui/interactive-prompt.gif
Set FontSize 32
Set Width 2200
Set Height 500
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI.ask('What do you use?', options: %w(rails go ruby python))"
Sleep .5
Enter
Sleep 1
Down@.5 3
Sleep 1
Up@.5 2
Sleep 1
Enter
Sleep 3
================================================
FILE: examples/cli-ui/nested-frames.tape
================================================
Output examples/cli-ui/nested-frames.gif
Set FontSize 32
Set Width 2000
Set Height 750
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI::Frame.open('Frame 1') do"
Enter
Type " CLI::UI::Frame.open('Frame 2') { puts 'inside frame 2' }"
Enter
Type " puts 'inside frame 1'"
Enter
Type "end"
Sleep 1
Enter
Sleep 3
================================================
FILE: examples/cli-ui/progress.tape
================================================
Output examples/cli-ui/progress.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI::Progress.progress { |bar| 100.times { sleep 0.02; bar.tick } }"
Sleep .5
Enter
Sleep 5
================================================
FILE: examples/cli-ui/spinner.tape
================================================
Output examples/cli-ui/spinner.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI::Spinner.spin('Spinning...') { sleep 3 }"
Sleep .5
Enter
Sleep 5
================================================
FILE: examples/cli-ui/status-widget.tape
================================================
Output examples/cli-ui/status-widget.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type 'CLI::UI::Spinner.spin("Building: {{@widget/status:1:2:3:4}}") { |spinner| sleep 3 }'
Sleep .5
Enter
Sleep 5
================================================
FILE: examples/cli-ui/symbols.tape
================================================
Output examples/cli-ui/symbols.gif
Set FontSize 32
Set Width 2200
Set Height 400
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type 'puts CLI::UI.fmt "{{*}} {{v}} {{?}} {{x}}"'
Sleep .5
Enter
Sleep 5
================================================
FILE: examples/cli-ui/text-prompt.ascii
================================================
irb(main):003:0>
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
? Is CLI UI Awesome? (empty = It is great!)
>
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
? Is CLI UI Awesome? (empty = It is great!)
> I love it!
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
? Is CLI UI Awesome? (empty = It is great!)
> I love it!
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
? Is CLI UI Awesome? (empty = It is great!)
> I love it!
=> "I love it!"
────────────────────────────────────────────────────────────────────────────────
irb(main):003:0> CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
? Is CLI UI Awesome? (empty = It is great!)
> I love it!
=> "I love it!"
irb(main):004:0>
────────────────────────────────────────────────────────────────────────────────
================================================
FILE: examples/cli-ui/text-prompt.tape
================================================
Output examples/cli-ui/text-prompt.ascii
Output examples/cli-ui/text-prompt.gif
Set FontSize 32
Set Width 2200
Set Height 500
Hide
Type "irb --noautocomplete"
Enter
Type "require 'cli/ui'"
Enter
Type "CLI::UI::StdoutRouter.enable"
Enter
Ctrl+L
Show
Type "CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')"
Sleep .5
Enter
Sleep 1
Type "I love it!"
Sleep 1
Enter
Sleep 3
================================================
FILE: examples/commands/README.md
================================================
# Commands
### Arrow
<img width="600" src="./arrow.gif" />
```
Output examples/commands/arrow.gif
Set FontSize 42
Set Height 225
Type "Navigate around"
Sleep .25
Left 10
Sleep 1
Right@50ms 10
Sleep 1
```
### Backspace
<img width="600" src="./backspace.gif" />
```
Output examples/commands/backspace.gif
Set FontSize 42
Set Height 225
Type@50ms "Delete anything..."
Backspace 18
Sleep 1
```
### Comment
<img width="600" src="./comment.gif" />
```
Output examples/commands/comment.gif
Set Height 500
Set Width 1000
# This is a comment.
# These are ignored by the parser so you can write whatever you want!
# Quickly comment out a command you don't need.
# Type "Hello, world!"
Sleep 1
```
### Ctrl
<img width="600" src="./ctrl.gif" />
```
Output examples/commands/ctrl.gif
Set FontSize 42
Set Height 225
Sleep 1
Ctrl+R
Sleep 1
```
### Enter
<img width="600" src="./enter.gif" />
```
Output examples/commands/enter.gif
Set FontSize 42
Set Height 350
Sleep 1
Enter@.5 2
Sleep 1
```
### Hide
<img width="600" src="./hide.gif" />
```
Output examples/commands/hide.gif
Set FontSize 42
Set Height 300
Hide
Type "You won't see this being typed." Ctrl+C
Show
Type "You will see this being typed."
Sleep 2
```
### Show
<img width="600" src="./show.gif" />
```
Output examples/commands/show.gif
Hide
Type "export HIDDEN=wow"
Enter
Ctrl+L
Show
Type "echo $HIDDEN"
Enter
Sleep 1
```
### Space
<img width="600" src="./space.gif" />
```
Output examples/commands/space.gif
Set FontSize 42
Set Height 225
Sleep .25
Space 10
Sleep 1
```
### Tab
<img width="600" src="./tab.gif" />
```
Output examples/commands/tab.gif
Set FontSize 42
Set Height 300
Type "cd ."
Sleep 0.5s
Tab@0.5s 2
Sleep 1s
```
### Type
<img width="600" src="./type.gif" />
```
Output examples/commands/type.gif
Set FontSize 42
Set Height 225
Sleep 1
# Type something
Type "Whatever you want"
Sleep 1 Ctrl+U Sleep 1
# Type something really slowly!
Type@500ms "Slow down there, partner."
Sleep 1
```
================================================
FILE: examples/commands/alt.tape
================================================
Output examples/commands/alt.gif
Set FontSize 42
Set Height 225
Sleep 1
Alt+.
Sleep 1
================================================
FILE: examples/commands/arrow.tape
================================================
Output examples/commands/arrow.gif
Set FontSize 42
Set Height 225
Type "Navigate around"
Sleep .25
Left 10
Sleep 1
Right@50ms 10
Sleep 1
================================================
FILE: examples/commands/backspace.tape
================================================
Output examples/commands/backspace.gif
Set FontSize 42
Set Height 225
Type@50ms "Delete anything..."
Backspace 18
Sleep 1
================================================
FILE: examples/commands/clipboard.tape
================================================
Output examples/commands/clipboard.gif
Set FontSize 42
Set Height 225
Copy "https://github.com/charmbracelet"
Type "open "
Sleep 500ms
Paste
Sleep 3s
================================================
FILE: examples/commands/comment.tape
================================================
Output examples/commands/comment.gif
Set Height 500
Set Width 1000
# This is a comment.
# These are ignored by the parser so you can write whatever you want!
# Quickly comment out a command you don't need.
# Type "Hello, world!"
Sleep 1
================================================
FILE: examples/commands/ctrl.tape
================================================
Output examples/commands/ctrl.gif
Set FontSize 42
Set Height 225
Sleep 1
Ctrl+R
Sleep 1
================================================
FILE: examples/commands/enter.tape
================================================
Output examples/commands/enter.gif
Set FontSize 42
Set Height 350
Sleep 1
Enter@.5 2
Sleep 1
================================================
FILE: examples/commands/hide.tape
================================================
Output examples/commands/hide.gif
Set FontSize 42
Set Height 300
Hide
Type "You won't see this being typed." Ctrl+C
Show
Type "You will see this being typed."
Sleep 2
================================================
FILE: examples/commands/show.tape
================================================
Output examples/commands/show.gif
Hide
Type "export HIDDEN=wow"
Enter
Ctrl+L
Show
Type "echo $HIDDEN"
Enter
Sleep 1
================================================
FILE: examples/commands/space.tape
================================================
Output examples/commands/space.gif
Set FontSize 42
Set Height 225
Sleep .25
Space 10
Sleep 1
================================================
FILE: examples/commands/tab.tape
================================================
Output examples/commands/tab.gif
Set FontSize 42
Set Height 300
Type "cd ."
Sleep 0.5s
Tab@0.5s 2
Shift+Tab
Sleep 1s
================================================
FILE: examples/commands/type.tape
================================================
Output examples/commands/type.gif
Set FontSize 42
Set Height 225
Sleep 1
# Type something
Type "Whatever you want"
Sleep 1 Ctrl+U Sleep 1
# Type something really slowly!
Type@500ms "Slow down there, partner."
Sleep 1
================================================
FILE: examples/decorations/decorations.tape
================================================
Output examples/decorations/decorations.gif
Set FontSize 28
Set Width 1200
Set Height 800
Set Padding 30
Set Margin 80
Set MarginFill "#674EFF"
Set WindowBar Colorful
Set WindowBarSize 40
Set BorderRadius 8
Type "I can't believe it's not butter."
Sleep 2s
================================================
FILE: examples/demo.tape
================================================
# VHS documentation
#
# Output:
# Output <path>.gif Create a GIF output at the given <path>
# Output <path>.mp4 Create an MP4 output at the given <path>
# Output <path>.webm Create a WebM output at the given <path>
#
# Require:
# Require <string> Ensure a program is on the $PATH to proceed
#
# Settings:
# Set FontSize <number> Set the font size of the terminal
# Set FontFamily <string> Set the font family of the terminal
# Set Height <number> Set the height of the terminal
# Set Width <number> Set the width of the terminal
# Set LetterSpacing <float> Set the font letter spacing (tracking)
# Set LineHeight <float> Set the font line height
# Set LoopOffset <float>% Set the starting frame offset for the GIF loop
# Set Theme <json|string> Set the theme of the terminal
# Set Padding <number> Set the padding of the terminal
# Set Framerate <number> Set the framerate of the recording
# Set PlaybackSpeed <float> Set the playback speed of the recording
# Set MarginFill <file|#000000> Set the file or color the margin will be filled with.
# Set Margin <number> Set the size of the margin. Has no effect if MarginFill isn't set.
# Set BorderRadius <number> Set terminal border radius, in pixels.
# Set WindowBar <string> Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight)
# Set WindowBarSize <number> Set window bar size, in pixels. Default is 40.
# Set TypingSpeed <time> Set the typing speed of the terminal. Default is 50ms.
#
# Sleep:
# Sleep <time> Sleep for a set amount of <time> in seconds
#
# Type:
# Type[@<time>] "<characters>" Type <characters> into the terminal with a
# <time> delay between each character
#
# Keys:
# Escape[@<time>] [number] Press the Escape key
# Backspace[@<time>] [number] Press the Backspace key
# Delete[@<time>] [number] Press the Delete key
# Insert[@<time>] [number] Press the Insert key
# Down[@<time>] [number] Press the Down key
# Enter[@<time>] [number] Press the Enter key
# Space[@<time>] [number] Press the Space key
# Tab[@<time>] [number] Press the Tab key
# Left[@<time>] [number] Press the Left Arrow key
# Right[@<time>] [number] Press the Right Arrow key
# Up[@<time>] [number] Press the Up Arrow key
# Down[@<time>] [number] Press the Down Arrow key
# PageUp[@<time>] [number] Press the Page Up key
# PageDown[@<time>] [number] Press the Page Down key
# Ctrl+<key> Press the Control key + <key> (e.g. Ctrl+C)
#
# Display:
# Hide Hide the subsequent commands from the output
# Show Show the subsequent commands in the output
# ScrollUp[@<time>] [number] Scroll terminal viewport up by rows
# ScrollDown[@<time>] [number] Scroll terminal viewport down by rows
Output examples/demo.gif
Require echo
Set Shell "bash"
Set FontSize 32
Set Width 1200
Set Height 600
Type "echo 'Welcome to VHS!'" Sleep 500ms Enter
Sleep 5s
================================================
FILE: examples/demo.webm
================================================
version https://git-lfs.github.com/spec/v1
oid sha256:0444491291389ed9cdbcdda3f777c6d572d7323bd75d29ed9129c9b6e0f4d7d7
size 13086
================================================
FILE: examples/env/env.tape
================================================
Output examples/env/env.gif
Set Width 500
Set Height 250
Env HELLO "WORLD"
Type "echo $HELLO"
Sleep 500ms
Enter
Sleep 1s
================================================
FILE: examples/errors/dimensions.tape
================================================
Set Height 100
Set Width 100
Set Padding 100
Output demo.gif
Type "This will fail because the padding is too large for the width and height"
================================================
FILE: examples/errors/parser.tape
================================================
Set Foo Bar
Foo
Type Enter
Bar Backspace@
foo
================================================
FILE: examples/errors/require.tape
================================================
Output out.gif
Require foo
Require bar
Type "foo" Enter
================================================
FILE: examples/fixtures/all.tape
================================================
# All Commands
# Output:
Output examples/fixtures/all.gif
Output examples/fixtures/all.mp4
Output examples/fixtures/all.webm
# Settings:
Set Shell "fish"
Set FontSize 22
Set FontFamily "DejaVu Sans Mono"
Set Height 600
Set Width 1200
Set LetterSpacing 1
Set LineHeight 1.2
Set Theme { "name": "Whimsy", "black": "#535178", "red": "#ef6487", "green": "#5eca89", "yellow": "#fdd877", "blue": "#65aef7", "purple": "#aa7ff0", "cyan": "#43c1be", "white": "#ffffff", "brightBlack": "#535178", "brightRed": "#ef6487", "brightGreen": "#5eca89", "brightYellow": "#fdd877", "brightBlue": "#65aef7", "brightPurple": "#aa7ff0", "brightCyan": "#43c1be", "brightWhite": "#ffffff", "background": "#29283b", "foreground": "#b3b0d6", "selectionBackground": "#3d3c58", "cursorColor": "#b3b0d6" }
Set Theme "Catppuccin Mocha"
Set Padding 50
Set Framerate 60
Set PlaybackSpeed 2
Set TypingSpeed .1
Set LoopOffset 60.4
Set LoopOffset 20.99%
Set CursorBlink false
# Sleep:
Sleep 1
Sleep 500ms
Sleep .5
Sleep 0.5
# Type:
Type@.5 "All"
Type@500ms "All"
Type "Double Quote"
Type '"Single" Quote'
Type `"Backtick" 'Quote'`
# Keys:
Backspace
Backspace 2
Backspace@1 3
Delete
Delete 2
Delete@1 3
Insert
Insert 2
Insert@1 3
Down
Down 2
Down@1 3
PageDown
PageDown 2
PageDown@1 3
ScrollDown
ScrollDown 2
ScrollDown@1 3
Enter
Enter 2
Enter@1 3
Space
Space 2
Space@1 3
Tab
Tab 2
Tab@1 3
Left
Left 2
Left@1 3
Right
Right 2
Right@1 3
Up
Up 2
Up@1 3
PageUp
PageUp 2
PageUp@1 3
ScrollUp
ScrollUp 2
ScrollUp@1 3
Down
Down 2
Down@1 3
# Control:
Ctrl+C
Ctrl+L
Ctrl+R
# Alt:
Alt+.
Alt+L
Alt+i
# Display:
Hide
Show
================================================
FILE: examples/gh-cli/README.md
================================================
# GitHub CLI
### GitHub PR List
<img width="600" src="./gh-pr.gif" />
```
Output examples/gh-cli/gh-pr.gif
Type "gh pr list --state all"
Sleep 500ms
Enter
Sleep 5s
```
### GitHub Issue List
<img width="600" src="./gh-issue.gif" />
```
Output examples/gh-cli/gh-issue.gif
Type "gh issue list"
Sleep 500ms
Enter 1
Sleep 4s
Ctrl+L
Sleep 500ms
Type "gh issue view 19"
Sleep 500ms
Enter
Sleep 5s
```
================================================
FILE: examples/gh-cli/gh-issue.tape
================================================
Output examples/gh-cli/gh-issue.gif
Type "gh issue list"
Sleep 500ms
Enter 1
Sleep 4s
Ctrl+L
Sleep 500ms
Type "gh issue view 19"
Sleep 500ms
Enter
Sleep 5s
================================================
FILE: examples/gh-cli/gh-pr.tape
================================================
Output examples/gh-cli/gh-pr.gif
Type "gh pr list --state all"
Sleep 500ms
Enter
Sleep 5s
================================================
FILE: examples/glow/CarrotCake.md
================================================
# Carrot Cake
Carrot cake is delicious. And, it takes only 20 minutes to make!
Here is the recipe:
* Carrots
* Cake
Tada!
================================================
FILE: examples/glow/NiHao.md
================================================
# 你好
================================================
FILE: examples/glow/README.md
================================================
# Glow
### Glow Simple
<img width="600" src="./glow-simple.gif" />
```
Output examples/glow/glow-simple.ascii
Output examples/glow/glow-simple.gif
Set Width 1000
Set Height 1050
Type "glow"
Enter
Sleep 1s
Enter
Sleep 1s
Escape
Sleep 1s
Type "q"
Sleep 1s
```
### Glow Full
<img width="600" src="./vhs-glow.gif" />
```
Output examples/glow/vhs-glow.gif
Output examples/glow/glow.ascii
Set Width 1600
Set Height 1040
Sleep 1s
Type "glow"
Sleep 100ms
Enter
Sleep 1s
Hide
Tab
Type "/artichoke"
Enter
Down 2
Show
Sleep 0.5s
Down 20
Hide
Escape
Type "l"
Down 5
Show
Sleep 1s
Up@400ms 5
Hide
Type "/ulysses"
Enter
Show
Sleep 0.5s
Down@200ms 20
Hide
Escape
Type "/"
Show
Sleep 0.5s
Type@500ms "todo"
Sleep 1
Hide
Escape
Type "/ulysses"
Enter
Show
Sleep 0.5s
Type@750ms "????"
Hide
Escape
Type "/artichoke"
Enter
Type "m"
Ctrl+A
Right 4
Show
Sleep 1s
Type@250ms "Tasty "
Sleep 1s
Hide
Escape
Down 5
Type "m"
Ctrl+U
Show
Sleep 1s
Type@150ms "Your new internet thing"
Sleep 3s
Hide
Ctrl+C
Show
```
================================================
FILE: examples/glow/StewedPeaches.md
================================================
# Stewed Peaches
================================================
FILE: examples/glow/glow-edit.tape
================================================
Output glow-edit.gif
Set FontSize 22
Sleep .5s
Type "glow"
Enter
Sleep 1.5s
Enter
Sleep 1s
Type "e"
Sleep 1s
Type@100ms "jjjjjjjo"
Sleep 0.5s
Type " * Love"
Sleep 1s
Escape
Sleep 1s
Type@300ms ":wq"
Sleep 1s
Enter
Sleep 2s
Type "c"
Sleep 4s
================================================
FILE: examples/glow/glow-simple.ascii
================================================
> glow
────────────────────────────────────────────────────────────────────────────────
> glow
────────────────────────────────────────────────────────────────────────────────
Glow
9 local │ 14 stashed │ 3 news
│ CarrotCake.md
│ 2 days ago
StewedPeaches.md
2 days ago
notes/Currywurst.md
2 days ago
notes/Käsewurst.md
2 days ago
notes/Spätzle.md
2 days ago
notes/Weißwurst.md
2 days ago
to-do/Okonomiyaki.md
2 days ago
••
tab section • h/l ←/→ page • / find • s stash • …
────────────────────────────────────────────────────────────────────────────────
Carrot Cake
Carrot cake is delicious. And, it takes only 20 minutes to
make!
Here is the recipe:
• Carrots
• Cake
Tada!
────────────────────────────────────────────────────────────────────────────────
Carrot Cake
Carrot cake is delicious. And, it takes only 20 minutes to
make!
Here is the recipe:
• Carrots
• Cake
Tada!
Glow CarrotCake.md 100% ? Help
────────────────────────────────────────────────────────────────────────────────
Carrot Cake
Carrot cake is delicious. And, it takes only 20 minutes to
make!
Here is the recipe:
• Carrots
• Cake
Tada!
Glow CarrotCake.md 100% ? Help
────────────────────────────────────────────────────────────────────────────────
Glow
9 local │ 14 stashed │ 3 news
│ CarrotCake.md
│ 2 days ago
StewedPeaches.md
2 days ago
notes/Currywurst.md
2 days ago
notes/Käsewurst.md
2 days ago
notes/Spätzle.md
2 days ago
notes/Weißwurst.md
2 days ago
to-do/Okonomiyaki.md
2 days ago
••
tab section • h/l ←/→ page • / find • s stash • …
────────────────────────────────────────────────────────────────────────────────
> glow
────────────────────────────────────────────────────────────────────────────────
> glow
Thanks for using Glow!
>
────────────────────────────────────────────────────────────────────────────────
================================================
FILE: examples/glow/glow-simple.tape
================================================
Output examples/glow/glow-simple.ascii
Output examples/glow/glow-simple.gif
Set Width 1000
Set Height 1050
Type "glow"
Enter
Sleep 1s
Enter
Sleep 1s
Escape
Sleep 1s
Type "q"
Sleep 1s
================================================
FILE: examples/glow/glow.ascii
================================================
>
────────────────────────────────────────────────────────────────────────────────
> glow
────────────────────────────────────────────────────────────────────────────────
> glow
────────────────────────────────────────────────────────────────────────────────
> glow
────────────────────────────────────────────────────────────────────────────────
Glow
9 local │ 14 stashed │ 3 news
│ CarrotCake.md
│ 2 days ago
StewedPeaches.md
2 days ago
notes/Currywurst.md
2 days ago
notes/Käsewurst.md
2 days ago
notes/Spätzle.md
2 days ago
notes/Weißwurst.md
2 days ago
to-do/Okonomiyaki.md
2 days ago
••
tab section • h/l ←/→ page • / find • s stash • q quit • ? more
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
1. Carrots
2. Celery
3. Tacos
• Soft
• Hard
4. Cucumber
## Things to eat today
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
1. Carrots
2. Celery
3. Tacos
• Soft
• Hard
4. Cucumber
## Things to eat today
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
1. Carrots
2. Celery
3. Tacos
• Soft
• Hard
4. Cucumber
## Things to eat today
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Glow
9 local │ 14 stashed │ 3 news
• 牛油果 🥑
04 Sep 2022 17:04 UTC
• Hello from Glow
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Glow
9 local │ 14 stashed │ 3 news
• 牛油果 🥑
04 Sep 2022 17:04 UTC
• Hello from Glow
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Glow
9 local │ 14 stashed │ 3 news
│ • 牛油果 🥑
│ 04 Sep 2022 17:04 UTC
• Hello from Glow
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Find: ulysses
1 stashed
│ • Ulysses by James Joyce
│ 04 Sep 2022 17:11 UTC
────────────────────────────────────────────────────────────────────────────────
Glamour
A casual introduction. 你好世界!
## Let’s talk about artichokes
The artichoke is mentioned as a garden plant in the 8th century BC by Homer and Hesiod. The naturally
occurring variant of the artichoke, the cardoon, which is native to the Mediterranean area, also has
records of use as a food among the ancient Greeks and Romans. Pliny the Elder mentioned growing of carduus
in Carthage and Cordoba.
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
and hued like pale oak.
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
│ He holds him with a skinny hand,
│ ‘There was a ship,’ quoth he.
│ ‘Hold off! unhand me, grey-beard loon!’
│ An artichoke, dropt he.
--Samuel Taylor Coleridge, The Rime of the Ancient Mariner https://poetryfoundation.org/poems/43997/
## Other foods worth mentioning
Ulysses
by James Joyce
Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a
razor lay crossed. A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning
air. He held the bowl aloft and intoned:
—Introibo ad altare Dei.
Halted, he peered down the dark winding stairs and called out coarsely:
"Come up, Kinch! Come up, you fearful jesuit!"
Solemnly he came forward and mounted the round gunrest. He faced about and blessed gravely thrice the
tower, the surrounding land and the awaking mountains. Then, catching sight of Stephen Dedalus, he bent
towards him and made rapid crosses in the air, gurgling in his throat and shaking his head. Stephen
Dedalus, displeased and sleepy, leaned his arms on the top of the staircase and looked coldly at the
shaking gurgling face that blessed him, equine in its length, and at the light untonsured hair, grained
────────────────────────────────────────────────────────────────────────────────
> glow
────────────────────────────────────────────────────────────────────────────────
================================================
FILE: examples/glow/glow.tape
================================================
Output vhs-glow.gif
Output glow.ascii
Set Framerate 40
Set Width 1600
Set Height 1040
Sleep 1s
Type "glow"
Sleep 100ms
Enter
Sleep 1s
Hide
Sleep 5s
Show
Hide
Type "/artichoke"
Enter
Sleep 2s
Down 2
Show
Sleep 0.5s
Down 20
Hide
Escape
Type "l"
Down 5
Show
Sleep 1s
Up@400ms 5
Hide
Type "/ulysses"
Enter
Sleep 2s
Show
Sleep 0.5s
Down@200ms 20
Hide
Escape
Type "/"
Show
Sleep 0.5s
Type@500ms "Readme"
Sleep 1
Enter
Sleep 1
Type "s"
Sleep 1
Enter
Sleep 2
Hide
Escape
Type "/ulysses"
Enter
Sleep 2s
Show
Sleep 0.5s
Type@750ms "????"
Hide
Sleep 1s
Escape
Sleep 1s
Type "/artichoke"
Enter
Sleep 2s
Type "m"
Ctrl+A
Right 4
Show
Sleep 1s
Type@250ms "Tasty "
Sleep 1s
Hide
Escape
Type "a"
Sleep 1
Show
Type "j"
Sleep 1
Type "k"
Sleep 1
Enter
Sleep 2
Escape
Sleep 1
Tab
Sleep 1
Type@200ms "jjjjjj"
Sleep 1
Enter
Sleep 2
Hide
Escape
Escape
Type "G"
Type "m"
Ctrl+U
Show
Sleep 1s
Type@150ms "Your new internet thing"
Sleep 3s
Hide
Ctrl+C
Show
================================================
FILE: examples/glow/notes/Currywurst.md
================================================
# Currywurst
================================================
FILE: examples/glow/notes/Kasewurst.md
================================================
# Käsewurst
================================================
FILE: examples/glow/notes/Spatzle.md
================================================
# Spätzle
================================================
FILE: examples/glow/notes/Weibwurst.md
================================================
# Weißwurst
================================================
FILE: examples/glow/to-do/Okonomiyaki.md
================================================
# Okonomiyaki
================================================
FILE: examples/glow/to-do/Takoyaki.md
================================================
# Takoyaki
================================================
FILE: examples/gum/README.md
================================================
# Gum
### File
<img width="600" src="file.gif" />
```
Output examples/gum/file.gif
Type "gum file ./src"
Sleep 0.5s
Enter
Sleep 0.5s
Down@250ms 4
Up@250ms 1
Sleep 0.5s
Enter
Sleep 0.5s
Down@250ms 4
Sleep 0.5s
Up@250ms 2
Sleep 1s
```
### Pager
<img width="600" src="pager.gif" />
```
Output examples/gum/pager.gif
Set Padding 32
Set FontSize 16
Set Height 600
Type "gum pager < ~/src/gum/README.md --border normal"
Sleep 0.5s
Enter
Sleep 1s
Down@15ms 40
Sleep 0.5s
Up@15ms 30
Sleep 0.5s
Down@15ms 20
Sleep 2s
```
### Table
<img width="600" src="pager.gif" />
```
Output examples/gum/table.gif
Type "gum table < superhero.csv -w 2,12,5,6,6,8,4,20 --height 10"
Enter
Sleep 0.5s
Down 10
Sleep 0.5s
Down 10
Sleep 0.5s
Up 10
Sleep 0.5s
Enter
Sleep 2s
```
================================================
FILE: examples/gum/file.tape
================================================
Output examples/gum/file.gif
Type "gum file ./src"
Sleep 0.5s
Enter
Sleep 0.5s
Down@250ms 4
Up@250ms 1
Sleep 0.5s
Enter
Sleep 0.5s
Down@250ms 4
Sleep 0.5s
Up@250ms 2
Sleep 1s
================================================
FILE: examples/gum/pager.tape
================================================
Output examples/gum/pager.gif
Set Padding 32
Set FontSize 16
Set Height 600
Type "gum pager < ~/src/gum/README.md --border normal"
Sleep 0.5s
Enter
Sleep 1s
Down@15ms 40
Sleep 0.5s
Up@15ms 30
Sleep 0.5s
Down@15ms 20
Sleep 2s
================================================
FILE: examples/gum/src/id_rsa
================================================
-----BEGIN OPENSSH PRIVATE KEY-----
Lorem ipsum dolor sit amet, officia
excepteur ex fugiat reprehenderit enim
labore culpa sint ad nisi Lorem pariatur
mollit ex esse exercitation amet. Nisi anim
cupidatat excepteur officia. Reprehenderit
nostrud nostrud ipsum Lorem est aliquip
amet voluptate voluptate dolor minim nulla
est proident. Nostrud officia pariatur ut
officia. Sit irure elit esse ea nulla sunt
ex occaecat reprehenderit commodo officia
dolor Lorem duis laboris cupidatat officia
voluptate. Culpa proident adipisicing id
nulla nisi laboris ex in Lorem sunt duis
officia eiusmod. Aliqua reprehenderit
commodo ex non excepteur duis sunt velit
enim. Voluptate laboris sint cupidatat
ullamco ut ea consectetur et est culpa et
Lorem ipsum dolor sit amet, officia
excepteur ex fugiat reprehenderit enim
labore culpa sint ad nisi Lorem pariatur
mollit ex esse exercitation amet. Nisi anim
cupidatat excepteur officia. Reprehenderit
nostrud nostrud ipsum Lorem est aliquip
amet voluptate voluptate dolor minim nulla
est proident. Nostrud officia pariatur ut
officia. Sit irure elit esse ea nulla sunt
ex occaecat reprehenderit commodo officia
dolor Lorem duis laboris cupidatat officia
voluptate. Culpa proident adipisicing id
nulla nisi laboris ex in Lorem sunt duis
officia eiusmod. Aliqua reprehenderit
commodo ex non excepteur duis sunt velit
enim. Voluptate laboris sint cupidatat
ullamco ut ea consectetur et est culpa et
-----END OPENSSH PRIVATE KEY-----
================================================
FILE: examples/gum/src/id_rsa.pub
================================================
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILiaVloJZCKCsZcYnslysVnInqwgIlEgBp1LvOktoGjP maas@lalani.dev
================================================
FILE: examples/gum/src/lipgloss/README.md
================================================
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
================================================
FILE: examples/gum/src/lipgloss/align.go
================================================
// Package lipgloss is an example package.
package lipgloss
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
================================================
FILE: examples/gum/src/lipgloss/borders.go
================================================
package lipgloss
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
================================================
FILE: examples/gum/src/lipgloss/colors.go
================================================
package lipgloss
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim
// labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi
// anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem
// est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud
// officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat
// reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia
// voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt
// duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt
// velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est
// culpa et culpa duis.
================================================
FILE: examples/gum/src/lipgloss/join.go
================================================
package lipgloss
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim
// labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet.
// Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum
// Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident.
// Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex
// occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat
// officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem
// sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis
// sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur
// et est culpa et culpa duis. Lorem ipsum dolor sit amet, officia excepteur ex
// fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex
// esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit
// nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim
// nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea
// nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris
// cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris
// ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non
// excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea
// consectetur et est culpa et culpa duis.
================================================
FILE: examples/gum/src/lipgloss/style.go
================================================
package lipgloss
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
================================================
FILE: examples/gum/src/super_secret_message.txt
================================================
Hello!
================================================
FILE: examples/gum/superhero.csv
================================================
ID,Name,Gender,Eye color,Race,Hair color,Height,Publisher,Skin color,Alignment,Weight
0,A-Bomb,Male,yellow,Human,No Hair,203,Marvel Comics,-,good,441
1,Abe Sapien,Male,blue,Icthyo Sapien,No Hair,191,Dark Horse Comics,blue,good,65
2,Abin Sur,Male,blue,Ungaran,No Hair,185,DC Comics,red,good,90
3,Abomination,Male,green,Human / Radiation,No Hair,203,Marvel Comics,-,bad,441
4,Abraxas,Male,blue,Cosmic Entity,Black,-99,Marvel Comics,-,bad,-99
5,Absorbing Man,Male,blue,Human,No Hair,193,Marvel Comics,-,bad,122
6,Adam Monroe,Male,blue,-,Blond,-99,NBC - Heroes,-,good,-99
7,Adam Strange,Male,blue,Human,Blond,185,DC Comics,-,good,88
8,Agent 13,Female,blue,-,Blond,173,Marvel Comics,-,good,61
9,Agent Bob,Male,brown,Human,Brown,178,Marvel Comics,-,good,81
10,Agent Zero,Male,-,-,-,191,Marvel Comics,-,good,104
11,Air-Walker,Male,blue,-,White,188,Marvel Comics,-,bad,108
12,Ajax,Male,brown,Cyborg,Black,193,Marvel Comics,-,bad,90
13,Alan Scott,Male,blue,-,Blond,180,DC Comics,-,good,90
14,Alex Mercer,Male,-,Human,-,-99,Wildstorm,-,bad,-99
15,Alex Woolsly,Male,-,-,-,-99,NBC - Heroes,-,good,-99
16,Alfred Pennyworth,Male,blue,Human,Black,178,DC Comics,-,good,72
17,Alien,Male,-,Xenomorph XX121,No Hair,244,Dark Horse Comics,black,bad,169
18,Allan Quatermain,Male,-,-,-,-99,Wildstorm,-,good,-99
19,Amazo,Male,red,Android,-,257,DC Comics,-,bad,173
20,Ammo,Male,brown,Human,Black,188,Marvel Comics,-,bad,101
21,Ando Masahashi,Male,-,-,-,-99,NBC - Heroes,-,good,-99
22,Angel,Male,blue,-,Blond,183,Marvel Comics,-,good,68
23,Angel,Male,-,Vampire,-,-99,Dark Horse Comics,-,good,-99
24,Angel Dust,Female,yellow,Mutant,Black,165,Marvel Comics,-,good,57
25,Angel Salvadore,Female,brown,-,Black,163,Marvel Comics,-,good,54
26,Angela,Female,-,-,-,-99,Image Comics,-,bad,-99
27,Animal Man,Male,blue,Human,Blond,183,DC Comics,-,good,83
28,Annihilus,Male,green,-,No Hair,180,Marvel Comics,-,bad,90
29,Ant-Man,Male,blue,Human,Blond,211,Marvel Comics,-,good,122
30,Ant-Man II,Male,blue,Human,Blond,183,Marvel Comics,-,good,86
31,Anti-Monitor,Male,yellow,God / Eternal,No Hair,61,DC Comics,-,bad,-99
32,Anti-Spawn,Male,-,-,-,-99,Image Comics,-,bad,-99
33,Anti-Venom,Male,blue,Symbiote,Blond,229,Marvel Comics,-,-,358
34,Apocalypse,Male,red,Mutant,Black,213,Marvel Comics,grey,bad,135
35,Aquababy,Male,blue,-,Blond,-99,DC Comics,-,good,-99
36,Aqualad,Male,blue,Atlantean,Black,178,DC Comics,-,good,106
37,Aquaman,Male,blue,Atlantean,Blond,185,DC Comics,-,good,146
38,Arachne,Female,blue,Human,Blond,175,Marvel Comics,-,good,63
39,Archangel,Male,blue,Mutant,Blond,183,Marvel Comics,blue,good,68
40,Arclight,Female,violet,-,Purple,173,Marvel Comics,-,bad,57
41,Ardina,Female,white,Alien,Orange,193,Marvel Comics,gold,good,98
42,Ares,Male,brown,-,Brown,185,Marvel Comics,-,good,270
43,Ariel,Female,purple,-,Pink,165,Marvel Comics,-,good,59
44,Armor,Female,black,-,Black,163,Marvel Comics,-,good,50
45,Arsenal,Male,-,Human,-,-99,DC Comics,-,good,-99
46,Astro Boy,Male,brown,-,Black,-99,,-,good,-99
47,Atlas,Male,brown,Mutant,Red,183,Marvel Comics,-,good,101
48,Atlas,Male,blue,God / Eternal,Brown,198,DC Comics,-,bad,126
49,Atom,Male,blue,-,Red,178,DC Comics,-,good,68
50,Atom,Male,-,-,-,-99,DC Comics,-,good,-99
51,Atom Girl,Female,black,-,Black,168,DC Comics,-,good,54
52,Atom II,Male,brown,Human,Auburn,183,DC Comics,-,good,81
53,Atom III,Male,-,-,Red,-99,DC Comics,-,good,-99
54,Atom IV,Male,brown,-,Black,-99,DC Comics,-,good,72
55,Aurora,Female,blue,Mutant,Black,180,Marvel Comics,-,good,63
56,Azazel,Male,yellow,Neyaphem,Black,183,Marvel Comics,red,bad,67
57,Azrael,Male,brown,Human,Black,-99,DC Comics,-,good,-99
58,Aztar,Male,-,-,-,-99,DC Comics,-,good,-99
59,Bane,Male,-,Human,-,203,DC Comics,-,bad,180
60,Banshee,Male,green,Human,Strawberry Blond,183,Marvel Comics,-,good,77
61,Bantam,Male,brown,-,Black,165,Marvel Comics,-,good,54
62,Batgirl,Female,-,-,-,-99,DC Comics,-,good,-99
63,Batgirl,Female,green,Human,Red,170,DC Comics,-,good,57
64,Batgirl III,Female,-,-,-,-99,DC Comics,-,good,-99
65,Batgirl IV,Female,green,Human,Black,165,DC Comics,-,good,52
66,Batgirl V,Female,-,-,-,-99,DC Comics,-,good,-99
67,Batgirl VI,Female,blue,-,Blond,168,DC Comics,-,good,61
68,Batman,Male,blue,Human,black,188,DC Comics,-,good,95
69,Batman,Male,blue,Human,Black,178,DC Comics,-,good,77
70,Batman II,Male,blue,Human,Black,178,DC Comics,-,good,79
71,Battlestar,Male,brown,-,Black,198,Marvel Comics,-,good,133
72,Batwoman V,Female,green,Human,Red,178,DC Comics,-,good,-99
73,Beak,Male,black,-,White,175,Marvel Comics,-,good,63
74,Beast,Male,blue,Mutant,Blue,180,Marvel Comics,blue,good,181
75,Beast Boy,Male,green,Human,Green,173,DC Comics,green,good,68
76,Beetle,Male,-,-,-,-99,Marvel Comics,-,bad,-99
77,Ben 10,Male,-,-,-,-99,DC Comics,-,good,-99
78,Beta Ray Bill,Male,-,-,No Hair,201,Marvel Comics,-,good,216
79,Beyonder,Male,-,God / Eternal,-,-99,Marvel Comics,-,good,-99
80,Big Barda,Female,blue,New God,Black,188,DC Comics,-,bad,135
81,Big Daddy,Male,-,-,-,-99,Icon Comics,-,good,-99
82,Big Man,Male,blue,-,Brown,165,Marvel Comics,-,bad,71
83,Bill Harken,Male,-,Alpha,-,-99,SyFy,-,good,-99
84,Billy Kincaid,Male,-,-,-,-99,Image Comics,-,bad,-99
85,Binary,Female,blue,-,Blond,180,Marvel Comics,-,good,54
86,Bionic Woman,Female,blue,Cyborg,Black,-99,,-,good,-99
87,Bird-Brain,-,-,-,-,-99,Marvel Comics,-,good,-99
88,Bird-Man,Male,-,Human,-,-99,Marvel Comics,-,bad,-99
89,Bird-Man II,Male,-,Human,-,-99,Marvel Comics,-,bad,-99
90,Birdman,Male,-,God / Eternal,-,-99,Hanna-Barbera,-,good,-99
91,Bishop,Male,brown,Mutant,No Hair,198,Marvel Comics,-,good,124
92,Bizarro,Male,black,Bizarro,Black,191,DC Comics,white,neutral,155
93,Black Abbott,Male,red,-,Black,-99,Marvel Comics,-,bad,-99
94,Black Adam,Male,brown,-,Black,191,DC Comics,-,bad,113
95,Black Bolt,Male,blue,Inhuman,Black,188,Marvel Comics,-,good,95
96,Black Canary,Female,blue,Human,Blond,165,DC Comics,-,good,58
97,Black Canary,Female,blue,Metahuman,Blond,170,DC Comics,-,good,59
98,Black Cat,Female,green,Human,Blond,178,Marvel Comics,-,good,54
99,Black Flash,Male,-,God / Eternal,-,-99,DC Comics,-,neutral,-99
100,Black Goliath,Male,-,-,-,-99,Marvel Comics,-,good,-99
101,Black Knight III,Male,brown,Human,Brown,183,Marvel Comics,-,good,86
102,Black Lightning,Male,brown,-,No Hair,185,DC Comics,-,good,90
103,Black Mamba,Female,green,-,Black,170,Marvel Comics,-,bad,52
104,Black Manta,Male,black,Human,No Hair,188,DC Comics,-,bad,92
105,Black Panther,Male,brown,Human,Black,183,Marvel Comics,-,good,90
106,Black Widow,Female,green,Human,Auburn,170,Marvel Comics,-,good,59
107,Black Widow II,Female,blue,-,Blond,170,Marvel Comics,-,good,61
108,Blackout,Male,red,Demon,White,191,Marvel Comics,white,bad,104
109,Blackwing,Male,blue,-,Black,185,Marvel Comics,-,bad,86
110,Blackwulf,Male,red,Alien,White,188,Marvel Comics,-,-,88
111,Blade,Male,brown,Vampire,Black,188,Marvel Comics,-,good,97
112,Blaquesmith,-,black,-,No Hair,-99,Marvel Comics,-,good,-99
113,Bling!,Female,-,-,-,168,Marvel Comics,-,good,68
114,Blink,Female,green,Mutant,Magenta,165,Marvel Comics,pink,good,56
115,Blizzard,Male,-,-,-,-99,Marvel Comics,-,bad,-9
================================================
FILE: examples/gum/table.tape
================================================
Output examples/gum/table.gif
Type "gum table < superhero.csv -w 2,12,5,6,6,8,4,20 --height 10"
Enter
Sleep 0.5s
Down 10
Sleep 0.5s
Down 10
Sleep 0.5s
Up 10
Sleep 0.5s
Enter
Sleep 2s
================================================
FILE: examples/jqp/README.md
================================================
# JQP
<img width="600" src="./jqp.gif" />
```
Output examples/jqp/jqp.gif
Set Width 2400
Set Height 1600
Hide
Type "curl https://dummyjson.com/products | jqp"
Enter
Sleep 1
Show
Sleep 1
Type@.2'[ .products[] | select(.category=="smartphones") ]'
Sleep 1
Enter
Sleep 1
Tab
Sleep 1
Down@25ms 20
Sleep .5
Down@25ms 20
Tab
Sleep .5
Down@25ms 30
Sleep 1
Ctrl+S
Sleep 3
Escape
Sleep 3
```
================================================
FILE: examples/jqp/jqp.tape
================================================
Output examples/jqp/jqp.gif
Set Width 2400
Set Height 1600
Hide
Type "curl https://dummyjson.com/products | jqp"
Enter
Sleep 1
Show
Sleep 1
Type@.2'[ .products[] | select(.category=="smartphones") ]'
Sleep 1
Enter
Sleep 1
Tab
Sleep 1
Down@25ms 20
Sleep .5
Down@25ms 20
Tab
Sleep .5
Down@25ms 30
Sleep 1
Ctrl+S
Sleep 3
Escape
Sleep 3
================================================
FILE: examples/meta.tape
================================================
Output examples/meta.webm
Output examples/meta.mp4
Set Width 1920
Set Height 1080
Set FontSize 32
Set Framerate 30
Set Theme { "brightGreen": "#00D787", "brightYellow": "#FE5F86", "yellow": "#FFFFFF", "brightBlue": "#875FFF" }
Hide
Type "cp examples/welcome.tape cassette.tape && clear" Enter
Show
Type "vhs < cassette.tape" Sleep 0.5 Enter
Sleep 30s
Hide
Type "rm cassette.tape" Enter
================================================
FILE: examples/neofetch/README
gitextract_5eziu92j/ ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ ├── dependabot.yml │ └── workflows/ │ ├── build.yml │ ├── dependabot-sync.yml │ ├── goreleaser.yml │ ├── lint-sync.yml │ ├── lint.yml │ └── nightly.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── THEMES.md ├── command.go ├── command_test.go ├── draw.go ├── embed.go ├── error.go ├── evaluator.go ├── examples/ │ ├── README.md │ ├── bubbletea/ │ │ ├── altscreen-toggle.tape │ │ ├── chat.tape │ │ ├── composable-views.tape │ │ ├── credit-card-form.tape │ │ ├── debounce.tape │ │ ├── exec.tape │ │ ├── fullscreen.tape │ │ ├── glamour.tape │ │ ├── help.tape │ │ ├── http.tape │ │ ├── list-default.tape │ │ ├── list-fancy.tape │ │ ├── list-simple.tape │ │ ├── package-manager.tape │ │ ├── pager.tape │ │ ├── paginator.tape │ │ ├── pipe.tape │ │ ├── progress-animated.tape │ │ ├── progress-static.tape │ │ ├── realtime.tape │ │ ├── result.tape │ │ ├── send-msg.tape │ │ ├── sequence.tape │ │ ├── simple.tape │ │ ├── spinner.tape │ │ ├── spinners.tape │ │ ├── split-editors.tape │ │ ├── stopwatch.tape │ │ ├── table.tape │ │ ├── tabs.tape │ │ ├── textarea.tape │ │ ├── textinput.tape │ │ ├── textinputs.tape │ │ ├── timer.tape │ │ ├── tui-daemon-combo.tape │ │ └── views.tape │ ├── cli-ui/ │ │ ├── Gemfile │ │ ├── README.md │ │ ├── format.tape │ │ ├── interactive-prompt.tape │ │ ├── nested-frames.tape │ │ ├── progress.tape │ │ ├── spinner.tape │ │ ├── status-widget.tape │ │ ├── symbols.tape │ │ ├── text-prompt.ascii │ │ └── text-prompt.tape │ ├── commands/ │ │ ├── README.md │ │ ├── alt.tape │ │ ├── arrow.tape │ │ ├── backspace.tape │ │ ├── clipboard.tape │ │ ├── comment.tape │ │ ├── ctrl.tape │ │ ├── enter.tape │ │ ├── hide.tape │ │ ├── show.tape │ │ ├── space.tape │ │ ├── tab.tape │ │ └── type.tape │ ├── decorations/ │ │ └── decorations.tape │ ├── demo.tape │ ├── demo.webm │ ├── env/ │ │ └── env.tape │ ├── errors/ │ │ ├── dimensions.tape │ │ ├── parser.tape │ │ └── require.tape │ ├── fixtures/ │ │ └── all.tape │ ├── gh-cli/ │ │ ├── README.md │ │ ├── gh-issue.tape │ │ └── gh-pr.tape │ ├── glow/ │ │ ├── CarrotCake.md │ │ ├── NiHao.md │ │ ├── README.md │ │ ├── StewedPeaches.md │ │ ├── glow-edit.tape │ │ ├── glow-simple.ascii │ │ ├── glow-simple.tape │ │ ├── glow.ascii │ │ ├── glow.tape │ │ ├── notes/ │ │ │ ├── Currywurst.md │ │ │ ├── Kasewurst.md │ │ │ ├── Spatzle.md │ │ │ └── Weibwurst.md │ │ └── to-do/ │ │ ├── Okonomiyaki.md │ │ └── Takoyaki.md │ ├── gum/ │ │ ├── README.md │ │ ├── file.tape │ │ ├── pager.tape │ │ ├── src/ │ │ │ ├── id_rsa │ │ │ ├── id_rsa.pub │ │ │ ├── lipgloss/ │ │ │ │ ├── README.md │ │ │ │ ├── align.go │ │ │ │ ├── borders.go │ │ │ │ ├── colors.go │ │ │ │ ├── join.go │ │ │ │ └── style.go │ │ │ └── super_secret_message.txt │ │ ├── superhero.csv │ │ └── table.tape │ ├── jqp/ │ │ ├── README.md │ │ └── jqp.tape │ ├── meta.tape │ ├── neofetch/ │ │ ├── README.md │ │ ├── colorize-ascii.go │ │ ├── neofetch.tape │ │ ├── neofetch.webm │ │ ├── vhs-color.ascii │ │ ├── vhs.ascii │ │ └── vhs.conf │ ├── publish/ │ │ ├── cassette.tape │ │ └── publish.tape │ ├── screenshot.tape │ ├── settings/ │ │ ├── README.md │ │ ├── height.tape │ │ ├── set-border-radius.tape │ │ ├── set-cursor-blink.tape │ │ ├── set-font-family.tape │ │ ├── set-font-size-10.tape │ │ ├── set-font-size-20.tape │ │ ├── set-font-size-40.tape │ │ ├── set-letter-spacing.tape │ │ ├── set-line-height.tape │ │ ├── set-loop-offset.tape │ │ ├── set-margin.tape │ │ ├── set-padding.tape │ │ ├── set-shell-bash.tape │ │ ├── set-shell-cmd.tape │ │ ├── set-shell-custom.tape │ │ ├── set-shell-fish.tape │ │ ├── set-shell-nu.tape │ │ ├── set-shell-osh.tape │ │ ├── set-shell-pwsh.tape │ │ ├── set-shell-xonsh.tape │ │ ├── set-shell-zsh.tape │ │ ├── set-theme-name.tape │ │ ├── set-theme.tape │ │ ├── set-typing-speed.tape │ │ ├── set-window-bar.tape │ │ └── width.tape │ ├── slides/ │ │ ├── README.md │ │ └── slides.tape │ ├── split/ │ │ └── split.tape │ ├── vhs-promo.webm │ └── welcome.tape ├── ffmpeg.go ├── go.mod ├── go.sum ├── keys.go ├── lexer/ │ ├── lexer.go │ └── lexer_test.go ├── main.go ├── man.go ├── parser/ │ ├── parser.go │ └── parser_test.go ├── publish.go ├── record.go ├── record_test.go ├── screenshot.go ├── screenshot_test.go ├── scripts/ │ └── download_theme.sh ├── serve.go ├── serve_unix.go ├── serve_windows.go ├── shell.go ├── style.go ├── syntax.go ├── testing.go ├── themes.go ├── themes.json ├── themes_test.go ├── token/ │ ├── token.go │ └── token_test.go ├── tty.go ├── tty_unix.go ├── tty_windows.go ├── vhs.go └── video.go
SYMBOL INDEX (407 symbols across 34 files)
FILE: command.go
function Execute (line 20) | func Execute(c parser.Command, v *VHS) error {
type CommandFunc (line 38) | type CommandFunc
function ExecuteNoop (line 78) | func ExecuteNoop(_ parser.Command, _ *VHS) error { return nil }
function ExecuteKey (line 86) | func ExecuteKey(k input.Key) CommandFunc {
function ExecuteScroll (line 121) | func ExecuteScroll(direction int) CommandFunc {
constant WaitTick (line 147) | WaitTick = 10 * time.Millisecond
function ExecuteWait (line 150) | func ExecuteWait(c parser.Command, v *VHS) error {
function ExecuteCtrl (line 213) | func ExecuteCtrl(c parser.Command, v *VHS) error {
function ExecuteAlt (line 268) | func ExecuteAlt(c parser.Command, v *VHS) error {
function ExecuteShift (line 307) | func ExecuteShift(c parser.Command, v *VHS) error {
function ExecuteHide (line 346) | func ExecuteHide(_ parser.Command, v *VHS) error {
function ExecuteRequire (line 353) | func ExecuteRequire(c parser.Command, _ *VHS) error {
function ExecuteShow (line 359) | func ExecuteShow(_ parser.Command, v *VHS) error {
function ExecuteSleep (line 366) | func ExecuteSleep(c parser.Command, _ *VHS) error {
function ExecuteType (line 376) | func ExecuteType(c parser.Command, v *VHS) error {
function ExecuteOutput (line 407) | func ExecuteOutput(c parser.Command, v *VHS) error {
function ExecuteCopy (line 425) | func ExecuteCopy(c parser.Command, _ *VHS) error {
function ExecuteEnv (line 430) | func ExecuteEnv(c parser.Command, _ *VHS) error {
function ExecutePaste (line 435) | func ExecutePaste(_ parser.Command, v *VHS) error {
function ExecuteSet (line 486) | func ExecuteSet(c parser.Command, v *VHS) error {
function ExecuteSetFontSize (line 491) | func ExecuteSetFontSize(c parser.Command, v *VHS) error {
function ExecuteSetFontFamily (line 515) | func ExecuteSetFontFamily(c parser.Command, v *VHS) error {
function ExecuteSetHeight (line 526) | func ExecuteSetHeight(c parser.Command, v *VHS) error {
function ExecuteSetWidth (line 537) | func ExecuteSetWidth(c parser.Command, v *VHS) error {
function ExecuteSetShell (line 548) | func ExecuteSetShell(c parser.Command, v *VHS) error {
constant bitSize (line 559) | bitSize = 64
constant base (line 560) | base = 10
function ExecuteSetLetterSpacing (line 565) | func ExecuteSetLetterSpacing(c parser.Command, v *VHS) error {
function ExecuteSetLineHeight (line 581) | func ExecuteSetLineHeight(c parser.Command, v *VHS) error {
function ExecuteSetTheme (line 597) | func ExecuteSetTheme(c parser.Command, v *VHS) error {
function ExecuteSetTypingSpeed (line 621) | func ExecuteSetTypingSpeed(c parser.Command, v *VHS) error {
function ExecuteSetWaitTimeout (line 632) | func ExecuteSetWaitTimeout(c parser.Command, v *VHS) error {
function ExecuteSetWaitPattern (line 642) | func ExecuteSetWaitPattern(c parser.Command, v *VHS) error {
function ExecuteSetPadding (line 652) | func ExecuteSetPadding(c parser.Command, v *VHS) error {
function ExecuteSetFramerate (line 663) | func ExecuteSetFramerate(c parser.Command, v *VHS) error {
function ExecuteSetPlaybackSpeed (line 674) | func ExecuteSetPlaybackSpeed(c parser.Command, v *VHS) error {
function ExecuteLoopOffset (line 685) | func ExecuteLoopOffset(c parser.Command, v *VHS) error {
function ExecuteSetMarginFill (line 696) | func ExecuteSetMarginFill(c parser.Command, v *VHS) error {
function ExecuteSetMargin (line 702) | func ExecuteSetMargin(c parser.Command, v *VHS) error {
function ExecuteSetWindowBar (line 713) | func ExecuteSetWindowBar(c parser.Command, v *VHS) error {
function ExecuteSetWindowBarSize (line 719) | func ExecuteSetWindowBarSize(c parser.Command, v *VHS) error {
function ExecuteSetBorderRadius (line 730) | func ExecuteSetBorderRadius(c parser.Command, v *VHS) error {
function ExecuteSetCursorBlink (line 741) | func ExecuteSetCursorBlink(c parser.Command, v *VHS) error {
function ExecuteScreenshot (line 752) | func ExecuteScreenshot(c parser.Command, v *VHS) error {
function getTheme (line 757) | func getTheme(s string) (Theme, error) {
function getJSONTheme (line 769) | func getJSONTheme(s string) (Theme, error) {
FILE: command_test.go
function TestCommand (line 10) | func TestCommand(t *testing.T) {
function TestExecuteSetTheme (line 22) | func TestExecuteSetTheme(t *testing.T) {
function requireErr (line 58) | func requireErr(tb testing.TB, err error) {
function requireEqualErr (line 65) | func requireEqualErr(tb testing.TB, err1 error, err2 string) {
function requireNoErr (line 75) | func requireNoErr(tb testing.TB, err error) {
function requireDefaultTheme (line 82) | func requireDefaultTheme(tb testing.TB, theme Theme) {
function requireNotDefaultTheme (line 89) | func requireNotDefaultTheme(tb testing.TB, theme Theme) {
FILE: draw.go
type circle (line 13) | type circle struct
method ColorModel (line 23) | func (c *circle) ColorModel() color.Model {
method Bounds (line 27) | func (c *circle) Bounds() image.Rectangle {
method At (line 44) | func (c *circle) At(x, y int) color.Color {
constant white (line 19) | white = 0xFF
constant black (line 20) | black = 0x17
constant halfPixel (line 37) | halfPixel = 0.5
constant doublingFactor (line 38) | doublingFactor = 2
function double (line 41) | func double(i int) int { return i * doublingFactor }
function half (line 42) | func half(i int) int { return i / doublingFactor }
type rect (line 72) | type rect struct
method ColorModel (line 77) | func (r *rect) ColorModel() color.Model {
method Bounds (line 81) | func (r *rect) Bounds() image.Rectangle {
method At (line 85) | func (r *rect) At(x, y int) color.Color {
type roundedrect (line 95) | type roundedrect struct
method ColorModel (line 101) | func (r *roundedrect) ColorModel() color.Model {
method Bounds (line 105) | func (r *roundedrect) Bounds() image.Rectangle {
method At (line 109) | func (r *roundedrect) At(x, y int) color.Color {
function MakeBorderRadiusMask (line 178) | func MakeBorderRadiusMask(width, height, radius int, targetpng string) {
function MakeWindowBar (line 213) | func MakeWindowBar(termWidth, termHeight int, opts StyleOptions, file st...
constant barToDotRatio (line 232) | barToDotRatio = 6
constant barToDotBorderRatio (line 233) | barToDotBorderRatio = 5
function makeColorfulBar (line 236) | func makeColorfulBar(termWidth int, termHeight int, isRight bool, opts S...
function makeRingBar (line 317) | func makeRingBar(termWidth int, termHeight int, isRight bool, opts Style...
function parseHexColor (line 392) | func parseHexColor(s string) (c color.RGBA, err error) {
FILE: error.go
type InvalidSyntaxError (line 12) | type InvalidSyntaxError struct
method Error (line 16) | func (e InvalidSyntaxError) Error() string {
constant ErrorColumnOffset (line 22) | ErrorColumnOffset = 5
function Underline (line 26) | func Underline(n int) string {
function LineNumber (line 31) | func LineNumber(line int) string {
function printError (line 35) | func printError(out io.Writer, tape string, err parser.Error) {
function printErrors (line 45) | func printErrors(out io.Writer, tape string, errs []error) {
FILE: evaluator.go
type EvaluatorOption (line 17) | type EvaluatorOption
function Evaluate (line 21) | func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...E...
FILE: examples/neofetch/colorize-ascii.go
function main (line 18) | func main() {
function colorize (line 37) | func colorize(c, s string) string {
function clamp (line 41) | func clamp(v, low, high int) int {
FILE: ffmpeg.go
type FilterComplexBuilder (line 11) | type FilterComplexBuilder struct
method WithWindowBar (line 108) | func (fb *FilterComplexBuilder) WithWindowBar(barStream int) *FilterCo...
method WithBorderRadius (line 129) | func (fb *FilterComplexBuilder) WithBorderRadius(cornerMarkStream int)...
method WithMarginFill (line 148) | func (fb *FilterComplexBuilder) WithMarginFill(marginStream int) *Filt...
method WithGIF (line 172) | func (fb *FilterComplexBuilder) WithGIF() *FilterComplexBuilder {
method Build (line 188) | func (fb *FilterComplexBuilder) Build() []string {
function NewVideoFilterBuilder (line 20) | func NewVideoFilterBuilder(videoOpts *VideoOptions) *FilterComplexBuilder {
function NewScreenshotFilterComplexBuilder (line 58) | func NewScreenshotFilterComplexBuilder(style *StyleOptions) *FilterCompl...
function calcTermDimensions (line 93) | func calcTermDimensions(style StyleOptions) (int, int) {
type StreamBuilder (line 196) | type StreamBuilder struct
method WithMargin (line 223) | func (sb *StreamBuilder) WithMargin() *StreamBuilder {
method WithBar (line 259) | func (sb *StreamBuilder) WithBar() *StreamBuilder {
method WithCorner (line 277) | func (sb *StreamBuilder) WithCorner() *StreamBuilder {
method WithMP4 (line 299) | func (sb *StreamBuilder) WithMP4() *StreamBuilder {
method WithWebm (line 311) | func (sb *StreamBuilder) WithWebm() *StreamBuilder {
method Build (line 322) | func (sb *StreamBuilder) Build() []string {
function NewStreamBuilder (line 209) | func NewStreamBuilder(streamCounter int, input string, style *StyleOptio...
FILE: keys.go
function shift (line 19) | func shift(k input.Key) input.Key {
FILE: lexer/lexer.go
type Lexer (line 7) | type Lexer struct
method readChar (line 24) | func (l *Lexer) readChar() {
method NextToken (line 32) | func (l *Lexer) NextToken() token.Token {
method newToken (line 106) | func (l *Lexer) newToken(tokenType token.Type, ch byte) token.Token {
method readComment (line 118) | func (l *Lexer) readComment() string {
method readString (line 133) | func (l *Lexer) readString(endChar byte) string {
method readRegex (line 152) | func (l *Lexer) readRegex(endChar byte) string {
method readJSON (line 189) | func (l *Lexer) readJSON() string {
method readNumber (line 202) | func (l *Lexer) readNumber() string {
method readIdentifier (line 212) | func (l *Lexer) readIdentifier() string {
method skipWhitespace (line 223) | func (l *Lexer) skipWhitespace() {
method peekChar (line 284) | func (l *Lexer) peekChar() byte {
function New (line 17) | func New(input string) *Lexer {
function isDot (line 236) | func isDot(ch byte) bool {
function isDash (line 241) | func isDash(ch byte) bool {
function isUnderscore (line 246) | func isUnderscore(ch byte) bool {
function isPercent (line 251) | func isPercent(ch byte) bool {
function isSlash (line 256) | func isSlash(ch byte) bool {
function isLetter (line 261) | func isLetter(ch byte) bool {
function isDigit (line 266) | func isDigit(ch byte) bool {
function isWhitespace (line 271) | func isWhitespace(ch byte) bool {
function isNewLine (line 279) | func isNewLine(ch byte) bool {
FILE: lexer/lexer_test.go
function TestNextToken (line 10) | func TestNextToken(t *testing.T) {
function TestLexTapeFile (line 148) | func TestLexTapeFile(t *testing.T) {
FILE: main.go
constant extension (line 27) | extension = ".tape"
function main (line 243) | func main() {
function init (line 256) | func init() {
function getVersion (line 296) | func getVersion(program string) *version.Version {
function ensureDependencies (line 308) | func ensureDependencies() error {
FILE: man.go
constant specialChar (line 18) | specialChar = "%"
function markdownManual (line 128) | func markdownManual() string {
function sanitizeMarkdown (line 138) | func sanitizeMarkdown(s string) string {
function sanitizeSpecial (line 143) | func sanitizeSpecial(s string) string {
FILE: parser/parser.go
function NewError (line 18) | func NewError(token token.Token, msg string) Error {
type CommandType (line 26) | type CommandType
method String (line 64) | func (c CommandType) String() string { return token.ToCamel(string(c)) }
type Command (line 67) | type Command struct
method String (line 76) | func (c Command) String() string {
type Error (line 85) | type Error struct
method String (line 92) | func (e Error) String() string {
method Error (line 97) | func (e Error) Error() string {
type Parser (line 102) | type Parser struct
method Parse (line 122) | func (p *Parser) Parse() []Command {
method parseCommand (line 138) | func (p *Parser) parseCommand() []Command {
method parseWait (line 194) | func (p *Parser) parseWait() Command {
method parseSpeed (line 239) | func (p *Parser) parseSpeed() string {
method parseRepeat (line 253) | func (p *Parser) parseRepeat() string {
method parseTime (line 266) | func (p *Parser) parseTime() string {
method parseCtrl (line 295) | func (p *Parser) parseCtrl() Command {
method parseAlt (line 358) | func (p *Parser) parseAlt() Command {
method parseShift (line 384) | func (p *Parser) parseShift() Command {
method parseKeypress (line 406) | func (p *Parser) parseKeypress(ct token.Type) Command {
method parseOutput (line 417) | func (p *Parser) parseOutput() Command {
method parseSet (line 444) | func (p *Parser) parseSet() Command {
method parseSleep (line 539) | func (p *Parser) parseSleep() Command {
method parseHide (line 548) | func (p *Parser) parseHide() Command {
method parseRequire (line 556) | func (p *Parser) parseRequire() Command {
method parseShow (line 572) | func (p *Parser) parseShow() Command {
method parseType (line 581) | func (p *Parser) parseType() Command {
method parseCopy (line 614) | func (p *Parser) parseCopy() Command {
method parsePaste (line 643) | func (p *Parser) parsePaste() Command {
method parseEnv (line 652) | func (p *Parser) parseEnv() Command {
method parseSource (line 672) | func (p *Parser) parseSource() []Command {
method parseScreenshot (line 756) | func (p *Parser) parseScreenshot() Command {
method Errors (line 782) | func (p *Parser) Errors() []Error {
method nextToken (line 788) | func (p *Parser) nextToken() {
function New (line 110) | func New(l *lexer.Lexer) *Parser {
function isValidWindowBar (line 794) | func isValidWindowBar(w string) bool {
FILE: parser/parser_test.go
function TestParser (line 12) | func TestParser(t *testing.T) {
function TestParserErrors (line 90) | func TestParserErrors(t *testing.T) {
function TestParseTapeFile (line 120) | func TestParseTapeFile(t *testing.T) {
function TestParseCtrl (line 232) | func TestParseCtrl(t *testing.T) {
type parseSourceTest (line 348) | type parseSourceTest struct
method run (line 355) | func (st *parseSourceTest) run(t *testing.T) {
function TestParseSource (line 384) | func TestParseSource(t *testing.T) {
type parseScreenshotTest (line 440) | type parseScreenshotTest struct
method run (line 445) | func (st *parseScreenshotTest) run(t *testing.T) {
function TestParseScreeenshot (line 465) | func TestParseScreeenshot(t *testing.T) {
FILE: publish.go
constant ghostHost (line 23) | ghostHost = "ghost.charm.sh"
constant ghostPort (line 24) | ghostPort = 22
function dataPath (line 59) | func dataPath() (string, error) {
function hostKeyCallback (line 72) | func hostKeyCallback(path string) ssh.HostKeyCallback {
function sshSession (line 102) | func sshSession() (*ssh.Session, error) {
function publishShareInstructions (line 138) | func publishShareInstructions(url string) {
function Publish (line 152) | func Publish(ctx context.Context, path string) (string, error) {
FILE: record.go
constant sleepThreshold (line 22) | sleepThreshold = 500 * time.Millisecond
function Record (line 72) | func Record(_ *cobra.Command, _ []string) error {
function inputToTape (line 131) | func inputToTape(input string) string {
function quote (line 204) | func quote(s string) string {
FILE: record_test.go
function TestInputToTape (line 8) | func TestInputToTape(t *testing.T) {
function TestInputToTapeLongSleep (line 93) | func TestInputToTapeLongSleep(t *testing.T) {
function TestInputToTape_RepeatedSleepAfterExit (line 102) | func TestInputToTape_RepeatedSleepAfterExit(t *testing.T) {
FILE: screenshot.go
type ScreenshotOptions (line 10) | type ScreenshotOptions struct
method makeScreenshot (line 39) | func (opts *ScreenshotOptions) makeScreenshot(frame int) {
method enableFrameCapture (line 47) | func (opts *ScreenshotOptions) enableFrameCapture(path string) {
method buildFFopts (line 72) | func (opts *ScreenshotOptions) buildFFopts(targetFile, textStream, cur...
function NewScreenshotOptions (line 27) | func NewScreenshotOptions(input string, style *StyleOptions) ScreenshotO...
function MakeScreenshots (line 53) | func MakeScreenshots(opts ScreenshotOptions) []*exec.Cmd {
FILE: screenshot_test.go
function TestScreenshot (line 5) | func TestScreenshot(t *testing.T) {
FILE: serve.go
constant maxNumber (line 24) | maxNumber = 1000000000
constant timeout (line 25) | timeout = 30 * time.Second
type config (line 28) | type config struct
FILE: serve_unix.go
function dropUserPrivileges (line 11) | func dropUserPrivileges(gid int, uid int) error {
FILE: serve_windows.go
function dropUserPrivileges (line 7) | func dropUserPrivileges(int, int) error {
FILE: shell.go
constant bash (line 5) | bash = "bash"
constant cmdexe (line 6) | cmdexe = "cmd"
constant fish (line 7) | fish = "fish"
constant nushell (line 8) | nushell = "nu"
constant osh (line 9) | osh = "osh"
constant powershell (line 10) | powershell = "powershell"
constant pwsh (line 11) | pwsh = "pwsh"
constant xonsh (line 12) | xonsh = "xonsh"
constant zsh (line 13) | zsh = "zsh"
type Shell (line 17) | type Shell struct
FILE: style.go
constant Background (line 9) | Background = "#171717"
constant Foreground (line 10) | Foreground = "#dddddd"
constant Black (line 11) | Black = "#282a2e"
constant BrightBlack (line 12) | BrightBlack = "#4d4d4d"
constant Red (line 13) | Red = "#D74E6F"
constant BrightRed (line 14) | BrightRed = "#FE5F86"
constant Green (line 15) | Green = "#31BB71"
constant BrightGreen (line 16) | BrightGreen = "#00D787"
constant Yellow (line 17) | Yellow = "#D3E561"
constant BrightYellow (line 18) | BrightYellow = "#EBFF71"
constant Blue (line 19) | Blue = "#8056FF"
constant BrightBlue (line 20) | BrightBlue = "#9B79FF"
constant Magenta (line 21) | Magenta = "#ED61D7"
constant BrightMagenta (line 22) | BrightMagenta = "#FF7AEA"
constant Cyan (line 23) | Cyan = "#04D7D7"
constant BrightCyan (line 24) | BrightCyan = "#00FEFE"
constant White (line 25) | White = "#bfbfbf"
constant BrightWhite (line 26) | BrightWhite = "#e6e6e6"
constant Indigo (line 27) | Indigo = "#5B56E0"
constant defaultColumns (line 31) | defaultColumns = 80
constant defaultHeight (line 32) | defaultHeight = 600
constant defaultMaxColors (line 33) | defaultMaxColors = 256
constant defaultPadding (line 34) | defaultPadding = 60
constant defaultWindowBarSize (line 35) | defaultWindowBarSize = 30
constant defaultPlaybackSpeed (line 36) | defaultPlaybackSpeed = 1.0
constant defaultWidth (line 37) | defaultWidth = 1200
type StyleOptions (line 62) | type StyleOptions struct
function DefaultStyleOptions (line 76) | func DefaultStyleOptions() *StyleOptions {
FILE: syntax.go
constant sourceDisplayMaxLength (line 13) | sourceDisplayMaxLength = 10
function Highlight (line 18) | func Highlight(c parser.Command, faint bool) string {
function isNumber (line 84) | func isNumber(s string) bool {
function isTime (line 90) | func isTime(s string) bool {
FILE: testing.go
type TestOptions (line 11) | type TestOptions struct
function DefaultTestOptions (line 17) | func DefaultTestOptions() TestOptions {
constant separator (line 24) | separator = "───────────────────────────────────────────────────────────...
method SaveOutput (line 32) | func (v *VHS) SaveOutput() error {
method Buffer (line 68) | func (v *VHS) Buffer() ([]string, error) {
method CurrentLine (line 85) | func (v *VHS) CurrentLine() (string, error) {
FILE: themes.go
type Theme (line 30) | type Theme struct
method String (line 55) | func (t Theme) String() string {
constant margin (line 91) | margin = 2
function boolPtr (line 117) | func boolPtr(b bool) *bool { return &b }
function stringPtr (line 118) | func stringPtr(s string) *string { return &s }
function uintPtr (line 119) | func uintPtr(u uint) *uint { return &u }
type ThemeNotFoundError (line 125) | type ThemeNotFoundError struct
method Error (line 130) | func (e ThemeNotFoundError) Error() string {
function sortedThemeNames (line 142) | func sortedThemeNames() ([]string, error) {
constant distance (line 160) | distance = 2
function findTheme (line 163) | func findTheme(name string) (Theme, error) {
function parseThemes (line 197) | func parseThemes(bts []byte) ([]Theme, error) {
FILE: themes_test.go
function TestFindAllThemes (line 10) | func TestFindAllThemes(t *testing.T) {
function TestFindTheme (line 21) | func TestFindTheme(t *testing.T) {
FILE: token/token.go
type Type (line 10) | type Type
method String (line 206) | func (t Type) String() string {
type Token (line 13) | type Token struct
constant AT (line 22) | AT = "@"
constant EQUAL (line 23) | EQUAL = "="
constant PLUS (line 24) | PLUS = "+"
constant PERCENT (line 25) | PERCENT = "%"
constant SLASH (line 26) | SLASH = "/"
constant BACKSLASH (line 27) | BACKSLASH = "\\"
constant DOT (line 28) | DOT = "."
constant DASH (line 29) | DASH = "-"
constant MINUS (line 31) | MINUS = "-"
constant RIGHT_BRACKET (line 32) | RIGHT_BRACKET = "]"
constant LEFT_BRACKET (line 33) | LEFT_BRACKET = "["
constant CARET (line 34) | CARET = "^"
constant EM (line 36) | EM = "EM"
constant MILLISECONDS (line 37) | MILLISECONDS = "MILLISECONDS"
constant MINUTES (line 38) | MINUTES = "MINUTES"
constant PX (line 39) | PX = "PX"
constant SECONDS (line 40) | SECONDS = "SECONDS"
constant EOF (line 42) | EOF = "EOF"
constant ILLEGAL (line 43) | ILLEGAL = "ILLEGAL"
constant ALT (line 45) | ALT = "ALT"
constant BACKSPACE (line 46) | BACKSPACE = "BACKSPACE"
constant CTRL (line 47) | CTRL = "CTRL"
constant DELETE (line 48) | DELETE = "DELETE"
constant END (line 49) | END = "END"
constant ENTER (line 50) | ENTER = "ENTER"
constant ESCAPE (line 51) | ESCAPE = "ESCAPE"
constant HOME (line 52) | HOME = "HOME"
constant INSERT (line 53) | INSERT = "INSERT"
constant PAGE_DOWN (line 54) | PAGE_DOWN = "PAGE_DOWN"
constant PAGE_UP (line 55) | PAGE_UP = "PAGE_UP"
constant SCROLL_DOWN (line 56) | SCROLL_DOWN = "SCROLL_DOWN"
constant SCROLL_UP (line 57) | SCROLL_UP = "SCROLL_UP"
constant SLEEP (line 58) | SLEEP = "SLEEP"
constant SPACE (line 59) | SPACE = "SPACE"
constant TAB (line 60) | TAB = "TAB"
constant SHIFT (line 61) | SHIFT = "SHIFT"
constant COMMENT (line 63) | COMMENT = "COMMENT"
constant NUMBER (line 64) | NUMBER = "NUMBER"
constant STRING (line 65) | STRING = "STRING"
constant JSON (line 66) | JSON = "JSON"
constant REGEX (line 67) | REGEX = "REGEX"
constant BOOLEAN (line 68) | BOOLEAN = "BOOLEAN"
constant DOWN (line 70) | DOWN = "DOWN"
constant LEFT (line 71) | LEFT = "LEFT"
constant RIGHT (line 72) | RIGHT = "RIGHT"
constant UP (line 73) | UP = "UP"
constant HIDE (line 75) | HIDE = "HIDE"
constant OUTPUT (line 76) | OUTPUT = "OUTPUT"
constant REQUIRE (line 77) | REQUIRE = "REQUIRE"
constant SET (line 78) | SET = "SET"
constant SHOW (line 79) | SHOW = "SHOW"
constant SOURCE (line 80) | SOURCE = "SOURCE"
constant TYPE (line 81) | TYPE = "TYPE"
constant SCREENSHOT (line 82) | SCREENSHOT = "SCREENSHOT"
constant COPY (line 83) | COPY = "COPY"
constant PASTE (line 84) | PASTE = "PASTE"
constant SHELL (line 85) | SHELL = "SHELL"
constant ENV (line 86) | ENV = "ENV"
constant FONT_FAMILY (line 87) | FONT_FAMILY = "FONT_FAMILY"
constant FONT_SIZE (line 88) | FONT_SIZE = "FONT_SIZE"
constant FRAMERATE (line 89) | FRAMERATE = "FRAMERATE"
constant PLAYBACK_SPEED (line 90) | PLAYBACK_SPEED = "PLAYBACK_SPEED"
constant HEIGHT (line 91) | HEIGHT = "HEIGHT"
constant WIDTH (line 92) | WIDTH = "WIDTH"
constant LETTER_SPACING (line 93) | LETTER_SPACING = "LETTER_SPACING"
constant LINE_HEIGHT (line 94) | LINE_HEIGHT = "LINE_HEIGHT"
constant TYPING_SPEED (line 95) | TYPING_SPEED = "TYPING_SPEED"
constant PADDING (line 96) | PADDING = "PADDING"
constant THEME (line 97) | THEME = "THEME"
constant LOOP_OFFSET (line 98) | LOOP_OFFSET = "LOOP_OFFSET"
constant MARGIN_FILL (line 99) | MARGIN_FILL = "MARGIN_FILL"
constant MARGIN (line 100) | MARGIN = "MARGIN"
constant WINDOW_BAR (line 101) | WINDOW_BAR = "WINDOW_BAR"
constant WINDOW_BAR_SIZE (line 102) | WINDOW_BAR_SIZE = "WINDOW_BAR_SIZE"
constant BORDER_RADIUS (line 103) | BORDER_RADIUS = "CORNER_RADIUS"
constant WAIT (line 104) | WAIT = "WAIT"
constant WAIT_TIMEOUT (line 105) | WAIT_TIMEOUT = "WAIT_TIMEOUT"
constant WAIT_PATTERN (line 106) | WAIT_PATTERN = "WAIT_PATTERN"
constant CURSOR_BLINK (line 107) | CURSOR_BLINK = "CURSOR_BLINK"
function IsSetting (line 175) | func IsSetting(t Type) bool {
function IsCommand (line 188) | func IsCommand(t Type) bool {
function IsModifier (line 201) | func IsModifier(t Type) bool {
function ToCamel (line 214) | func ToCamel(s string) string {
function LookupIdentifier (line 227) | func LookupIdentifier(ident string) Type {
FILE: token/token_test.go
function TestToCamel (line 5) | func TestToCamel(t *testing.T) {
FILE: tty.go
function randomPort (line 20) | func randomPort() int {
function buildTtyCmd (line 27) | func buildTtyCmd(port int, shell Shell) *exec.Cmd {
FILE: tty_unix.go
constant defaultShell (line 6) | defaultShell = bash
FILE: vhs.go
type VHS (line 23) | type VHS struct
method Start (line 125) | func (vhs *VHS) Start() error {
method Setup (line 160) | func (vhs *VHS) Setup() {
method terminate (line 199) | func (vhs *VHS) terminate() error {
method Cleanup (line 214) | func (vhs *VHS) Cleanup() error {
method Render (line 223) | func (vhs *VHS) Render() error {
method ApplyLoopOffset (line 250) | func (vhs *VHS) ApplyLoopOffset() error {
method Record (line 323) | func (vhs *VHS) Record(ctx context.Context) <-chan error {
method ResumeRecording (line 391) | func (vhs *VHS) ResumeRecording() {
method PauseRecording (line 399) | func (vhs *VHS) PauseRecording() {
method ScreenshotNextFrame (line 407) | func (vhs *VHS) ScreenshotNextFrame(path string) {
type Options (line 39) | type Options struct
constant defaultFontSize (line 58) | defaultFontSize = 22
constant defaultTypingSpeed (line 59) | defaultTypingSpeed = 50 * time.Millisecond
constant defaultLineHeight (line 60) | defaultLineHeight = 1.0
constant defaultLetterSpacing (line 61) | defaultLetterSpacing = 1.0
constant fontsSeparator (line 62) | fontsSeparator = ","
constant defaultCursorBlink (line 63) | defaultCursorBlink = true
constant defaultWaitTimeout (line 64) | defaultWaitTimeout = 15 * time.Second
function withSymbolsFallback (line 86) | func withSymbolsFallback(font string) string {
function DefaultVHSOptions (line 91) | func DefaultVHSOptions() Options {
function New (line 114) | func New() VHS {
constant cleanupWaitTime (line 193) | cleanupWaitTime = 100 * time.Millisecond
constant quality (line 320) | quality = 1.0
FILE: video.go
constant textFrameFormat (line 21) | textFrameFormat = "frame-text-%05d.png"
constant cursorFrameFormat (line 22) | cursorFrameFormat = "frame-cursor-%05d.png"
constant mp4 (line 26) | mp4 = ".mp4"
constant webm (line 27) | webm = ".webm"
constant gif (line 28) | gif = ".gif"
function randomDir (line 33) | func randomDir() string {
type VideoOutputs (line 43) | type VideoOutputs struct
type VideoOptions (line 51) | type VideoOptions struct
constant defaultFramerate (line 62) | defaultFramerate = 50
constant defaultStartingFrame (line 63) | defaultStartingFrame = 1
function DefaultVideoOptions (line 68) | func DefaultVideoOptions() VideoOptions {
function marginFillIsColor (line 79) | func marginFillIsColor(marginFill string) bool {
function makeMedia (line 84) | func makeMedia(opts VideoOptions, targetFile string) *exec.Cmd {
function ensureDir (line 101) | func ensureDir(output string) {
function buildFFopts (line 109) | func buildFFopts(opts VideoOptions, targetFile string) []string {
function MakeGIF (line 156) | func MakeGIF(opts VideoOptions) *exec.Cmd {
function MakeWebM (line 161) | func MakeWebM(opts VideoOptions) *exec.Cmd {
function MakeMP4 (line 166) | func MakeMP4(opts VideoOptions) *exec.Cmd {
Condensed preview — 205 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (685K chars).
[
{
"path": ".gitattributes",
"chars": 274,
"preview": "**/*.gif filter=lfs diff=lfs merge=lfs -text\n**/*.mp4 filter=lfs diff=lfs merge=lfs -text\n**/*.webm filter=lfs diff=lfs "
},
{
"path": ".github/CODEOWNERS",
"chars": 26,
"preview": "* @charmbracelet/everyone\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 828,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 120,
"preview": "blank_issues_enabled: true\ncontact_links:\n- name: Discord\n url: https://charm.sh/discord\n about: Chat on our Discord.\n"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 604,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is"
},
{
"path": ".github/dependabot.yml",
"chars": 1114,
"preview": "version: 2\n\nupdates:\n - package-ecosystem: \"gomod\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n day:"
},
{
"path": ".github/workflows/build.yml",
"chars": 259,
"preview": "name: build\n\non: [push, pull_request]\n\njobs:\n build:\n uses: charmbracelet/meta/.github/workflows/build.yml@main\n\n s"
},
{
"path": ".github/workflows/dependabot-sync.yml",
"chars": 419,
"preview": "name: dependabot-sync\non:\n schedule:\n - cron: \"0 0 * * 0\" # every Sunday at midnight\n workflow_dispatch: # allows m"
},
{
"path": ".github/workflows/goreleaser.yml",
"chars": 995,
"preview": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: goreleaser\n\non:\n push:\n tag"
},
{
"path": ".github/workflows/lint-sync.yml",
"chars": 271,
"preview": "name: lint-sync\non:\n schedule:\n # every Sunday at midnight\n - cron: \"0 0 * * 0\"\n workflow_dispatch: # allows man"
},
{
"path": ".github/workflows/lint.yml",
"chars": 160,
"preview": "name: lint\non:\n push:\n pull_request:\n\njobs:\n lint:\n uses: charmbracelet/meta/.github/workflows/lint.yml@main\n w"
},
{
"path": ".github/workflows/nightly.yml",
"chars": 609,
"preview": "name: nightly\n\non:\n push:\n branches:\n - main\n\njobs:\n nightly:\n uses: charmbracelet/meta/.github/workflows/n"
},
{
"path": ".gitignore",
"chars": 85,
"preview": ".DS_Store\n.ssh/\ncaptures/\ntmp/\nframes/\nmanpages\ncompletions\ndist\nvhs\n.idea/\n.vscode/\n"
},
{
"path": ".golangci.yml",
"chars": 1025,
"preview": "version: \"2\"\nrun:\n tests: false\nlinters:\n enable:\n - bodyclose\n - exhaustive\n - goconst\n - godot\n - gom"
},
{
"path": ".goreleaser.yml",
"chars": 349,
"preview": "version: 2\n\nincludes:\n - from_url:\n url: charmbracelet/meta/main/goreleaser-vhs.yaml\n\nvariables:\n main: \".\"\n des"
},
{
"path": "Dockerfile",
"chars": 1489,
"preview": "FROM tsl0922/ttyd:alpine as ttyd\nFROM alpine:latest as fontcollector\n\n# Install Fonts\nRUN apk add --no-cache \\\n --rep"
},
{
"path": "LICENSE",
"chars": 1080,
"preview": "MIT License\n\nCopyright (c) 2022-2023 Charmbracelet, Inc\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "Makefile",
"chars": 432,
"preview": "themes.json:\n\t# this is the url used by https://windowsterminalthemes.dev/\n\t# See https://github.com/atomcorp/themes/blo"
},
{
"path": "README.md",
"chars": 25406,
"preview": "# VHS\n\n<p>\n <img src=\"https://user-images.githubusercontent.com/42545625/198402537-12ca2f6c-0779-4eb8-a67c-8db9cb3df13c"
},
{
"path": "THEMES.md",
"chars": 5577,
"preview": "# Themes\n\n* `3024 Day`\n* `3024 Night`\n* `Aardvark Blue`\n* `Abernathy`\n* `Adventure`\n* `AdventureTime`\n* `Afterglow`\n* `A"
},
{
"path": "command.go",
"chars": 21055,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/at"
},
{
"path": "command_test.go",
"chars": 2462,
"preview": "package main\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/charmbracelet/vhs/parser\"\n)\n\nfunc TestCommand(t *testing.T) {"
},
{
"path": "draw.go",
"chars": 9017,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\t\"image/color\"\n\t\"image/draw\"\n\t\"image/png\"\n\t\"math\"\n\t\"os\"\n)\n\ntype circle struct {\n\t"
},
{
"path": "embed.go",
"chars": 82,
"preview": "package main\n\nimport _ \"embed\"\n\n//go:embed examples/demo.tape\nvar demoTape []byte\n"
},
{
"path": "error.go",
"chars": 1591,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/vhs/parser\"\n)\n\n// InvalidSyntaxError is retur"
},
{
"path": "evaluator.go",
"chars": 5044,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/charmbracelet/vhs/lexer\"\n\t\"github.com/charmbra"
},
{
"path": "examples/README.md",
"chars": 2873,
"preview": "# Examples\n\n### Gum\n\nExample of recording a demo of [Gum](https://github.com/charmbracelet/gum)\nwith VHS.\n\n#### Gum File"
},
{
"path": "examples/bubbletea/altscreen-toggle.tape",
"chars": 235,
"preview": "Output examples/bubbletea/altscreen-toggle.gif\n\nHide\nType \"go build -o altscreen-toggle .\"\nEnter\nType \"clear\"\nEnter\nShow"
},
{
"path": "examples/bubbletea/chat.tape",
"chars": 247,
"preview": "Output examples/bubbletea/chat.gif\n\nHide\nType \"go build -o chat .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./chat\"\nEnter\nSl"
},
{
"path": "examples/bubbletea/composable-views.tape",
"chars": 244,
"preview": "Output examples/bubbletea/composable-views.gif\n\nHide\nType \"go build -o views .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./v"
},
{
"path": "examples/bubbletea/credit-card-form.tape",
"chars": 299,
"preview": "Output examples/bubbletea/credit-card-form.gif\n\nHide\nType \"go build -o credit-card .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nTyp"
},
{
"path": "examples/bubbletea/debounce.tape",
"chars": 202,
"preview": "Output examples/bubbletea/debounce.gif\n\nHide\nType \"go build -o debounce .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./deboun"
},
{
"path": "examples/bubbletea/exec.tape",
"chars": 279,
"preview": "Output examples/bubbletea/exec.gif\n\nHide\nType \"go build -o exec .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"EDITOR=nano ./ex"
},
{
"path": "examples/bubbletea/fullscreen.tape",
"chars": 188,
"preview": "Output examples/bubbletea/fullscreen.gif\n\nHide\nType \"go build -o fullscreen .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./fu"
},
{
"path": "examples/bubbletea/glamour.tape",
"chars": 233,
"preview": "Output examples/bubbletea/glamour.gif\nSet Height 750\nSet FontSize 16\n\nHide\nType \"go build -o glamour .\"\nEnter\nType \"clea"
},
{
"path": "examples/bubbletea/help.tape",
"chars": 234,
"preview": "Output examples/bubbletea/help.gif\n\nHide\nType \"go build -o help .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./help\"\nEnter\nSl"
},
{
"path": "examples/bubbletea/http.tape",
"chars": 160,
"preview": "Output examples/bubbletea/http.gif\n\nHide\nType \"go build -o http .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./http\"\nEnter\nSl"
},
{
"path": "examples/bubbletea/list-default.tape",
"chars": 341,
"preview": "Output examples/bubbletea/list-default.gif\nSet FontSize 16\n\nHide\nType \"go build -o list-default .\"\nEnter\nType \"clear\"\nEn"
},
{
"path": "examples/bubbletea/list-fancy.tape",
"chars": 306,
"preview": "Output examples/bubbletea/list-fancy.gif\nSet FontSize 14\n\nHide\nType \"go build -o list-fancy .\"\nEnter\nType \"clear\"\nEnter\n"
},
{
"path": "examples/bubbletea/list-simple.tape",
"chars": 234,
"preview": "Output examples/bubbletea/list-simple.gif\nSet FontSize 18\n\nHide\nType \"go build -o list-simple .\"\nEnter\nType \"clear\"\nEnte"
},
{
"path": "examples/bubbletea/package-manager.tape",
"chars": 221,
"preview": "Output examples/bubbletea/package-manager.gif\n\nHide\nType \"go build -o package-manager .\"\nEnter\nType \"clear\"\nEnter\nShow\n\n"
},
{
"path": "examples/bubbletea/pager.tape",
"chars": 198,
"preview": "Output examples/bubbletea/pager.gif\nSet FontSize 16\n\nHide\nType \"go build -o pager .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType"
},
{
"path": "examples/bubbletea/paginator.tape",
"chars": 257,
"preview": "Output examples/bubbletea/paginator.gif\nSet FontSize 12\nSet Padding 36\n\nHide\nType \"go build -o paginator .\"\nEnter\nType \""
},
{
"path": "examples/bubbletea/pipe.tape",
"chars": 206,
"preview": "Output examples/bubbletea/pipe.gif\n\nHide\nType \"go build -o pipe . && clear\"\nEnter\nShow\n\nType \"echo 'Hello' | ./pipe\"\nEnt"
},
{
"path": "examples/bubbletea/progress-animated.tape",
"chars": 226,
"preview": "Output examples/bubbletea/progress-animated.gif\n\nHide\nType \"go build -o progress-animated .\"\nEnter\nType \"clear\"\nEnter\nSh"
},
{
"path": "examples/bubbletea/progress-static.tape",
"chars": 205,
"preview": "Output examples/bubbletea/progress-static.gif\n\nHide\nType \"go build -o progress-static .\"\nEnter\nType \"clear\"\nEnter\nShow\n\n"
},
{
"path": "examples/bubbletea/realtime.tape",
"chars": 208,
"preview": "Output examples/bubbletea/realtime.gif\n\nHide\nType \"go build -o realtime .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./realti"
},
{
"path": "examples/bubbletea/result.tape",
"chars": 209,
"preview": "Output examples/bubbletea/result.gif\n\nHide\nType \"go build -o result .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./result\"\nEn"
},
{
"path": "examples/bubbletea/send-msg.tape",
"chars": 190,
"preview": "Output examples/bubbletea/send-msg.gif\n\nHide\nType \"go build -o send-msg .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./send-m"
},
{
"path": "examples/bubbletea/sequence.tape",
"chars": 177,
"preview": "Output examples/bubbletea/sequence.gif\n\nHide\nType \"go build -o sequence .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./sequen"
},
{
"path": "examples/bubbletea/simple.tape",
"chars": 179,
"preview": "Output examples/bubbletea/simple.gif\n\nHide\nType \"go build -o simple .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./simple\"\nEn"
},
{
"path": "examples/bubbletea/spinner.tape",
"chars": 202,
"preview": "Output examples/bubbletea/spinner.gif\n\nHide\nType \"go build -o spinner .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./spinner\""
},
{
"path": "examples/bubbletea/spinners.tape",
"chars": 194,
"preview": "Output examples/bubbletea/spinners.gif\n\nHide\nType \"go build -o spinners .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./spinne"
},
{
"path": "examples/bubbletea/split-editors.tape",
"chars": 467,
"preview": "Output examples/bubbletea/split-editors.gif\nSet FontSize 16\n\nHide\nType \"go build -o split-editors .\"\nEnter\nType \"clear\"\n"
},
{
"path": "examples/bubbletea/stopwatch.tape",
"chars": 270,
"preview": "Output examples/bubbletea/stopwatch.gif\n\nHide\nType \"go build -o stopwatch .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./stop"
},
{
"path": "examples/bubbletea/table.tape",
"chars": 202,
"preview": "Output examples/bubbletea/table.gif\n\nHide\nType \"go build -o table .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./table\"\nEnter"
},
{
"path": "examples/bubbletea/tabs.tape",
"chars": 194,
"preview": "Output examples/bubbletea/tabs.gif\n\nHide\nType \"go build -o tabs .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./tabs\"\nEnter\nSl"
},
{
"path": "examples/bubbletea/textarea.tape",
"chars": 318,
"preview": "Output examples/bubbletea/textarea.gif\n\nHide\nType \"go build -o textarea .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./textar"
},
{
"path": "examples/bubbletea/textinput.tape",
"chars": 244,
"preview": "Output examples/bubbletea/textinput.gif\n\nHide\nType \"go build -o textinput .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./text"
},
{
"path": "examples/bubbletea/textinputs.tape",
"chars": 307,
"preview": "Output examples/bubbletea/textinputs.gif\n\nHide\nType \"go build -o textinputs .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./te"
},
{
"path": "examples/bubbletea/timer.tape",
"chars": 212,
"preview": "Output examples/bubbletea/timer.gif\n\nHide\nType \"go build -o timer .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./timer\"\nSleep"
},
{
"path": "examples/bubbletea/tui-daemon-combo.tape",
"chars": 233,
"preview": "Output examples/bubbletea/tui-daemon-combo.gif\n\nHide\nType \"go build -o tui-daemon-combo .\"\nEnter\nType \"clear\"\nEnter\nShow"
},
{
"path": "examples/bubbletea/views.tape",
"chars": 200,
"preview": "Output examples/bubbletea/views.gif\n\nHide\nType \"go build -o views .\"\nEnter\nType \"clear\"\nEnter\nShow\n\nType \"./views\"\nEnter"
},
{
"path": "examples/cli-ui/Gemfile",
"chars": 43,
"preview": "source \"https://rubygems.org\"\ngem 'cli-ui'\n"
},
{
"path": "examples/cli-ui/README.md",
"chars": 2756,
"preview": "# CLI UI\n\n### Format\n\n<img width=\"600\" src=\"./format.gif\" />\n\n```\nOutput examples/cli-ui/format.gif\n\nSet FontSize 32\n\nSe"
},
{
"path": "examples/cli-ui/format.tape",
"chars": 284,
"preview": "Output examples/cli-ui/format.gif\n\nSet FontSize 32\n\nSet Width 2200\nSet Height 400\n\nHide\nType \"irb --noautocomplete\"\nEnte"
},
{
"path": "examples/cli-ui/interactive-prompt.tape",
"chars": 367,
"preview": "Output examples/cli-ui/interactive-prompt.gif\n\nSet FontSize 32\n\nSet Width 2200\nSet Height 500\n\nHide\nType \"irb --noautoco"
},
{
"path": "examples/cli-ui/nested-frames.tape",
"chars": 406,
"preview": "Output examples/cli-ui/nested-frames.gif\n\nSet FontSize 32\n\nSet Width 2000\nSet Height 750\n\nHide\nType \"irb --noautocomplet"
},
{
"path": "examples/cli-ui/progress.tape",
"chars": 311,
"preview": "Output examples/cli-ui/progress.gif\n\nSet FontSize 32\n\nSet Width 2200\nSet Height 400\n\nHide\nType \"irb --noautocomplete\"\nEn"
},
{
"path": "examples/cli-ui/spinner.tape",
"chars": 287,
"preview": "Output examples/cli-ui/spinner.gif\n\nSet FontSize 32\n\nSet Width 2200\nSet Height 400\n\nHide\nType \"irb --noautocomplete\"\nEnt"
},
{
"path": "examples/cli-ui/status-widget.tape",
"chars": 328,
"preview": "Output examples/cli-ui/status-widget.gif\n\nSet FontSize 32\n\nSet Width 2200\nSet Height 400\n\nHide\nType \"irb --noautocomplet"
},
{
"path": "examples/cli-ui/symbols.tape",
"chars": 281,
"preview": "Output examples/cli-ui/symbols.gif\n\nSet FontSize 32\n\nSet Width 2200\nSet Height 400\n\nHide\nType \"irb --noautocomplete\"\nEnt"
},
{
"path": "examples/cli-ui/text-prompt.ascii",
"chars": 1727,
"preview": "irb(main):003:0>\n\n\n\n\n\n\n\n────────────────────────────────────────────────────────────────────────────────\nirb(main):003:0"
},
{
"path": "examples/cli-ui/text-prompt.tape",
"chars": 382,
"preview": "Output examples/cli-ui/text-prompt.ascii\nOutput examples/cli-ui/text-prompt.gif\n\nSet FontSize 32\n\nSet Width 2200\nSet Hei"
},
{
"path": "examples/commands/README.md",
"chars": 1999,
"preview": "# Commands\n\n### Arrow\n\n<img width=\"600\" src=\"./arrow.gif\" />\n\n```\nOutput examples/commands/arrow.gif\nSet FontSize 42\nSet"
},
{
"path": "examples/commands/alt.tape",
"chars": 89,
"preview": "Output examples/commands/alt.gif\nSet FontSize 42\nSet Height 225\n\nSleep 1\n\nAlt+.\n\nSleep 1\n"
},
{
"path": "examples/commands/arrow.tape",
"chars": 138,
"preview": "Output examples/commands/arrow.gif\nSet FontSize 42\nSet Height 225\n\nType \"Navigate around\"\nSleep .25\nLeft 10\nSleep 1\nRigh"
},
{
"path": "examples/commands/backspace.tape",
"chars": 123,
"preview": "Output examples/commands/backspace.gif\nSet FontSize 42\nSet Height 225\n\nType@50ms \"Delete anything...\"\nBackspace 18\nSleep"
},
{
"path": "examples/commands/clipboard.tape",
"chars": 152,
"preview": "Output examples/commands/clipboard.gif\n\nSet FontSize 42\nSet Height 225\n\nCopy \"https://github.com/charmbracelet\"\nType \"op"
},
{
"path": "examples/commands/comment.tape",
"chars": 240,
"preview": "Output examples/commands/comment.gif\nSet Height 500\nSet Width 1000\n\n# This is a comment.\n# These are ignored by the pars"
},
{
"path": "examples/commands/ctrl.tape",
"chars": 91,
"preview": "Output examples/commands/ctrl.gif\nSet FontSize 42\nSet Height 225\n\nSleep 1\n\nCtrl+R\n\nSleep 1\n"
},
{
"path": "examples/commands/enter.tape",
"chars": 94,
"preview": "Output examples/commands/enter.gif\nSet FontSize 42\nSet Height 350\n\nSleep 1\nEnter@.5 2\nSleep 1\n"
},
{
"path": "examples/commands/hide.tape",
"chars": 170,
"preview": "Output examples/commands/hide.gif\n\nSet FontSize 42\nSet Height 300\n\nHide\nType \"You won't see this being typed.\" Ctrl+C\nSh"
},
{
"path": "examples/commands/show.tape",
"chars": 118,
"preview": "Output examples/commands/show.gif\n\nHide\nType \"export HIDDEN=wow\"\nEnter\nCtrl+L\nShow\n\nType \"echo $HIDDEN\"\nEnter\nSleep 1\n"
},
{
"path": "examples/commands/space.tape",
"chars": 94,
"preview": "Output examples/commands/space.gif\nSet FontSize 42\nSet Height 225\n\nSleep .25\nSpace 10\nSleep 1\n"
},
{
"path": "examples/commands/tab.tape",
"chars": 118,
"preview": "Output examples/commands/tab.gif\nSet FontSize 42\nSet Height 300\n\nType \"cd .\"\nSleep 0.5s\nTab@0.5s 2\nShift+Tab\nSleep 1s\n"
},
{
"path": "examples/commands/type.tape",
"chars": 223,
"preview": "Output examples/commands/type.gif\nSet FontSize 42\nSet Height 225\n\nSleep 1\n\n# Type something\nType \"Whatever you want\"\n\nSl"
},
{
"path": "examples/decorations/decorations.tape",
"chars": 258,
"preview": "Output examples/decorations/decorations.gif\n\nSet FontSize 28\nSet Width 1200\nSet Height 800\nSet Padding 30\n\nSet Margin 80"
},
{
"path": "examples/demo.tape",
"chars": 3334,
"preview": "# VHS documentation\n#\n# Output:\n# Output <path>.gif Create a GIF output at the given <path>\n# Output <"
},
{
"path": "examples/demo.webm",
"chars": 130,
"preview": "version https://git-lfs.github.com/spec/v1\noid sha256:0444491291389ed9cdbcdda3f777c6d572d7323bd75d29ed9129c9b6e0f4d7d7\ns"
},
{
"path": "examples/env/env.tape",
"chars": 124,
"preview": "Output examples/env/env.gif\n\nSet Width 500\nSet Height 250\n\nEnv HELLO \"WORLD\"\n\nType \"echo $HELLO\"\nSleep 500ms\nEnter\nSleep"
},
{
"path": "examples/errors/dimensions.tape",
"chars": 143,
"preview": "Set Height 100\nSet Width 100\nSet Padding 100\n\nOutput demo.gif\n\nType \"This will fail because the padding is too large for"
},
{
"path": "examples/errors/parser.tape",
"chars": 50,
"preview": "Set Foo Bar\n\nFoo\n\nType Enter\n\nBar Backspace@\n\nfoo\n"
},
{
"path": "examples/errors/require.tape",
"chars": 58,
"preview": "Output out.gif\n\nRequire foo\nRequire bar\n\nType \"foo\" Enter\n"
},
{
"path": "examples/fixtures/all.tape",
"chars": 1595,
"preview": "# All Commands\n\n# Output:\nOutput examples/fixtures/all.gif\nOutput examples/fixtures/all.mp4\nOutput examples/fixtures/all"
},
{
"path": "examples/gh-cli/README.md",
"chars": 407,
"preview": "# GitHub CLI\n\n### GitHub PR List\n\n<img width=\"600\" src=\"./gh-pr.gif\" />\n\n```\nOutput examples/gh-cli/gh-pr.gif\n\nType \"gh "
},
{
"path": "examples/gh-cli/gh-issue.tape",
"chars": 160,
"preview": "Output examples/gh-cli/gh-issue.gif\n\nType \"gh issue list\"\nSleep 500ms\nEnter 1\nSleep 4s\n\nCtrl+L\nSleep 500ms\n\nType \"gh iss"
},
{
"path": "examples/gh-cli/gh-pr.tape",
"chars": 92,
"preview": "Output examples/gh-cli/gh-pr.gif\n\nType \"gh pr list --state all\"\nSleep 500ms\nEnter\n\nSleep 5s\n"
},
{
"path": "examples/glow/CarrotCake.md",
"chars": 129,
"preview": "# Carrot Cake\n\nCarrot cake is delicious. And, it takes only 20 minutes to make!\n\nHere is the recipe:\n\n * Carrots\n * Cake"
},
{
"path": "examples/glow/NiHao.md",
"chars": 5,
"preview": "# 你好\n"
},
{
"path": "examples/glow/README.md",
"chars": 1021,
"preview": "# Glow\n\n### Glow Simple\n\n<img width=\"600\" src=\"./glow-simple.gif\" />\n\n```\nOutput examples/glow/glow-simple.ascii\nOutput "
},
{
"path": "examples/glow/StewedPeaches.md",
"chars": 17,
"preview": "# Stewed Peaches\n"
},
{
"path": "examples/glow/glow-edit.tape",
"chars": 244,
"preview": "Output glow-edit.gif\nSet FontSize 22\n\nSleep .5s\nType \"glow\"\nEnter\nSleep 1.5s\nEnter\nSleep 1s\nType \"e\"\nSleep 1s\nType@100ms"
},
{
"path": "examples/glow/glow-simple.ascii",
"chars": 2214,
"preview": "> glow\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n────────────────────────────────────────────────────────────────────────────────\n> g"
},
{
"path": "examples/glow/glow-simple.tape",
"chars": 185,
"preview": "Output examples/glow/glow-simple.ascii\nOutput examples/glow/glow-simple.gif\n\nSet Width 1000\nSet Height 1050\n\nType \"glow\""
},
{
"path": "examples/glow/glow.ascii",
"chars": 29535,
"preview": ">\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n────────────────────────────────────────────────────────────────────────────────\n> glow\n\n"
},
{
"path": "examples/glow/glow.tape",
"chars": 957,
"preview": "Output vhs-glow.gif\nOutput glow.ascii\n\nSet Framerate 40\nSet Width 1600\nSet Height 1040\n\nSleep 1s\n\nType \"glow\"\nSleep 100m"
},
{
"path": "examples/glow/notes/Currywurst.md",
"chars": 13,
"preview": "# Currywurst\n"
},
{
"path": "examples/glow/notes/Kasewurst.md",
"chars": 12,
"preview": "# Käsewurst\n"
},
{
"path": "examples/glow/notes/Spatzle.md",
"chars": 10,
"preview": "# Spätzle\n"
},
{
"path": "examples/glow/notes/Weibwurst.md",
"chars": 12,
"preview": "# Weißwurst\n"
},
{
"path": "examples/glow/to-do/Okonomiyaki.md",
"chars": 14,
"preview": "# Okonomiyaki\n"
},
{
"path": "examples/glow/to-do/Takoyaki.md",
"chars": 11,
"preview": "# Takoyaki\n"
},
{
"path": "examples/gum/README.md",
"chars": 762,
"preview": "# Gum\n\n### File\n\n<img width=\"600\" src=\"file.gif\" />\n\n```\nOutput examples/gum/file.gif\n\nType \"gum file ./src\"\nSleep 0.5s\n"
},
{
"path": "examples/gum/file.tape",
"chars": 176,
"preview": "Output examples/gum/file.gif\n\nType \"gum file ./src\"\nSleep 0.5s\nEnter\nSleep 0.5s\nDown@250ms 4\nUp@250ms 1\nSleep 0.5s\nEnter"
},
{
"path": "examples/gum/pager.tape",
"chars": 227,
"preview": "Output examples/gum/pager.gif\n\nSet Padding 32\nSet FontSize 16\nSet Height 600\n\nType \"gum pager < ~/src/gum/README.md --bo"
},
{
"path": "examples/gum/src/id_rsa",
"chars": 1466,
"preview": "-----BEGIN OPENSSH PRIVATE KEY-----\nLorem ipsum dolor sit amet, officia\nexcepteur ex fugiat reprehenderit enim\nlabore cu"
},
{
"path": "examples/gum/src/id_rsa.pub",
"chars": 97,
"preview": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILiaVloJZCKCsZcYnslysVnInqwgIlEgBp1LvOktoGjP maas@lalani.dev\n"
},
{
"path": "examples/gum/src/lipgloss/README.md",
"chars": 2840,
"preview": "Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur moll"
},
{
"path": "examples/gum/src/lipgloss/align.go",
"chars": 5765,
"preview": "// Package lipgloss is an example package.\npackage lipgloss\n\n// Lorem ipsum dolor sit amet, officia excepteur ex fugiat "
},
{
"path": "examples/gum/src/lipgloss/borders.go",
"chars": 2870,
"preview": "package lipgloss\n\n// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nis"
},
{
"path": "examples/gum/src/lipgloss/colors.go",
"chars": 758,
"preview": "package lipgloss\n\n// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim\n// labore culpa sint ad "
},
{
"path": "examples/gum/src/lipgloss/join.go",
"chars": 1495,
"preview": "package lipgloss\n\n// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim\n// labore culpa sint ad "
},
{
"path": "examples/gum/src/lipgloss/style.go",
"chars": 14278,
"preview": "package lipgloss\n\n// Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nis"
},
{
"path": "examples/gum/src/super_secret_message.txt",
"chars": 7,
"preview": "Hello!\n"
},
{
"path": "examples/gum/superhero.csv",
"chars": 7056,
"preview": "ID,Name,Gender,Eye color,Race,Hair color,Height,Publisher,Skin color,Alignment,Weight\n0,A-Bomb,Male,yellow,Human,No Hair"
},
{
"path": "examples/gum/table.tape",
"chars": 184,
"preview": "Output examples/gum/table.gif\n\nType \"gum table < superhero.csv -w 2,12,5,6,6,8,4,20 --height 10\"\nEnter\nSleep 0.5s\nDown 1"
},
{
"path": "examples/jqp/README.md",
"chars": 397,
"preview": "# JQP\n\n<img width=\"600\" src=\"./jqp.gif\" />\n\n```\nOutput examples/jqp/jqp.gif\nSet Width 2400\nSet Height 1600\n\nHide\nType \"c"
},
{
"path": "examples/jqp/jqp.tape",
"chars": 345,
"preview": "Output examples/jqp/jqp.gif\nSet Width 2400\nSet Height 1600\n\nHide\nType \"curl https://dummyjson.com/products | jqp\"\nEnter\n"
},
{
"path": "examples/meta.tape",
"chars": 391,
"preview": "Output examples/meta.webm\nOutput examples/meta.mp4\n\nSet Width 1920\nSet Height 1080\nSet FontSize 32\nSet Framerate 30\nSet "
},
{
"path": "examples/neofetch/README.md",
"chars": 513,
"preview": "# Neofetch\n\n<img width=\"600\" src=\"neofetch.gif\" />\n\n```\n# Source code for the VHS neofetch example.\n#\n# To run:\n#\n# "
},
{
"path": "examples/neofetch/colorize-ascii.go",
"chars": 832,
"preview": "// Package neofetch is an example package.\n//\n//nolint:unused\npackage neofetch\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"strings\"\n\n\t"
},
{
"path": "examples/neofetch/neofetch.tape",
"chars": 453,
"preview": "# Source code for the VHS neofetch example.\n#\n# To run:\n#\n# vhs < neofetch.tape\n\nOutput examples/neofetch/neofetch.g"
},
{
"path": "examples/neofetch/neofetch.webm",
"chars": 131,
"preview": "version https://git-lfs.github.com/spec/v1\noid sha256:c21bf41b43a78165a5f9f2ba8344ad26b89c1ef02bafff6a53ace70fcf848972\ns"
},
{
"path": "examples/neofetch/vhs-color.ascii",
"chars": 881,
"preview": "\u001b[38;2;173;146;255m .--. .--. .-. ,.-. ,.-s-.\u001b[0m\n\u001b[38;2;173;146;255m ,p#G*\"\"TG#Gf\"\"*GG#\"\"\"@###\"\"\"$#p#GW\"\""
},
{
"path": "examples/neofetch/vhs.ascii",
"chars": 585,
"preview": " .--. .--. .-. ,.-. ,.-s-.\n ,p#G*\"\"TG#Gf\"\"*GG#\"\"\"@###\"\"\"$#p#GW\"\"^\"\"%G#,\n{GGG Gp G GG GG^ "
},
{
"path": "examples/neofetch/vhs.conf",
"chars": 526,
"preview": "print_info () {\n prin\n prin \"$(color 5)charmbracelet/vhs\"\n prin\n prin \"$(color 2)OS\" \"Video Home System\"\n "
},
{
"path": "examples/publish/cassette.tape",
"chars": 69,
"preview": "Output output.gif\n\nType \"Hello from https://vhs.charm.sh!\"\n\nSleep 1s\n"
},
{
"path": "examples/publish/publish.tape",
"chars": 98,
"preview": "Output publish.gif\nSet FontSize 20\n\nType 'vhs cassette.tape --publish'\nSleep 0.5s\nEnter\nSleep 10s\n"
},
{
"path": "examples/screenshot.tape",
"chars": 208,
"preview": "Output examples/screenshot.gif\n\nSet FontSize 32\n\nType \"echo 'Hello world'\"\nEnter\nSleep 500ms\n\nScreenshot examples/hello_"
},
{
"path": "examples/settings/README.md",
"chars": 3331,
"preview": "# Settings\n\n### Width\n\n<img width=\"600\" src=\"./width.gif\" />\n\n\n```\nOutput examples/settings/width.gif\n\nSet Height 200\nSe"
},
{
"path": "examples/settings/height.tape",
"chars": 119,
"preview": "Output examples/settings/height.gif\n\nSet Height 1000\nSet Width 1000\nSet FontSize 50\n\nType \"I'm a big teapot\"\n\nSleep 1s\n"
},
{
"path": "examples/settings/set-border-radius.tape",
"chars": 203,
"preview": "Output examples/settings/set-border-radius.gif\nSet FontSize 32\nSet Width 800\nSet Height 400\nSet Padding 30\n\nSet MarginFi"
},
{
"path": "examples/settings/set-cursor-blink.tape",
"chars": 131,
"preview": "Output examples/settings/set-cursor-blink.gif\nSet FontSize 42\nSet Height 225\nSet CursorBlink false\n\nType \"Never blink..."
},
{
"path": "examples/settings/set-font-family.tape",
"chars": 144,
"preview": "Output examples/settings/set-font-family.gif\n\nSet FontSize 42\nSet Height 225\nSet FontFamily \"Monoflow\"\n\nType \"One moment"
},
{
"path": "examples/settings/set-font-size-10.tape",
"chars": 114,
"preview": "Output examples/settings/set-font-size-10.gif\n\nSet Height 225\n\nSet FontSize 10\n\nType \"Super Mushroom!!!\"\n\nSleep 1\n"
},
{
"path": "examples/settings/set-font-size-20.tape",
"chars": 114,
"preview": "Output examples/settings/set-font-size-20.gif\n\nSet Height 225\n\nSet FontSize 20\n\nType \"Super Mushroom!!!\"\n\nSleep 1\n"
},
{
"path": "examples/settings/set-font-size-40.tape",
"chars": 114,
"preview": "Output examples/settings/set-font-size-40.gif\n\nSet Height 225\n\nSet FontSize 40\n\nType \"Super Mushroom!!!\"\n\nSleep 1\n"
},
{
"path": "examples/settings/set-letter-spacing.tape",
"chars": 134,
"preview": "Output examples/settings/set-letter-spacing.gif\nSet FontSize 42\nSet Height 225\nSet LetterSpacing 20\n\nType \"Smooooooooooo"
},
{
"path": "examples/settings/set-line-height.tape",
"chars": 141,
"preview": "Output examples/settings/set-line-height.gif\nSet Height 400\nSet FontSize 32\nSet LineHeight 1.8\n\nType \"Stay far away!\"\nSl"
},
{
"path": "examples/settings/set-loop-offset.tape",
"chars": 313,
"preview": "Output examples/settings/set-loop-offset.gif\n\nSet FontSize 40\nSet LoopOffset 50% # % is optional\n\nType \"Round and 'round"
},
{
"path": "examples/settings/set-margin.tape",
"chars": 210,
"preview": "Output examples/settings/set-margin.gif\n\nSet FontSize 24\nSet Width 800\nSet Height 400\nSet Padding 40\n\nSet Margin 60\nSet "
},
{
"path": "examples/settings/set-padding.tape",
"chars": 115,
"preview": "Output examples/settings/set-padding.gif\nSet FontSize 32\nSet Height 150\nSet Padding 0\n\nType \"I'm so edgy!\"\nSleep 1\n"
},
{
"path": "examples/settings/set-shell-bash.tape",
"chars": 135,
"preview": "Output examples/settings/set-shell-bash.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell bash\n\nSleep 1s\nType \"I am using B"
},
{
"path": "examples/settings/set-shell-cmd.tape",
"chars": 132,
"preview": "Output examples/settings/set-shell-cmd.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell cmd\n\nSleep 1s\nType \"I am using CMD"
},
{
"path": "examples/settings/set-shell-custom.tape",
"chars": 135,
"preview": "Output examples/settings/set-shell-custom.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell ksh\n\nSleep 1s\nType \"I am using "
},
{
"path": "examples/settings/set-shell-fish.tape",
"chars": 160,
"preview": "Output examples/settings/set-shell-fish.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell fish\n\nSleep 1s\nType \"I am using t"
},
{
"path": "examples/settings/set-shell-nu.tape",
"chars": 129,
"preview": "Output examples/settings/set-shell-nu.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell nu\n\nSleep 1s\nType \"I am using nu.\"\n"
},
{
"path": "examples/settings/set-shell-osh.tape",
"chars": 132,
"preview": "Output examples/settings/set-shell-osh.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell osh\n\nSleep 1s\nType \"I am using OSH"
},
{
"path": "examples/settings/set-shell-pwsh.tape",
"chars": 135,
"preview": "Output examples/settings/set-shell-pwsh.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell pwsh\n\nSleep 1s\nType \"I am using P"
},
{
"path": "examples/settings/set-shell-xonsh.tape",
"chars": 138,
"preview": "Output examples/settings/set-shell-xonsh.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell xonsh\n\nSleep 1s\nType \"I am using"
},
{
"path": "examples/settings/set-shell-zsh.tape",
"chars": 132,
"preview": "Output examples/settings/set-shell-zsh.gif\n\nSet FontSize 38\nSet Height 225\n\nSet Shell zsh\n\nSleep 1s\nType \"I am using ZSH"
},
{
"path": "examples/settings/set-theme-name.tape",
"chars": 145,
"preview": "Output examples/settings/set-theme-name.gif\nSet FontSize 42\nSet Height 225\nSet Theme \"Catppuccin Latte\"\n\nType \"I can't m"
},
{
"path": "examples/settings/set-theme.tape",
"chars": 638,
"preview": "Output examples/settings/set-theme.gif\nSet FontSize 42\nSet Height 225\nSet Theme { \"name\": \"Whimsy\", \"black\": \"#535178\", "
},
{
"path": "examples/settings/set-typing-speed.tape",
"chars": 238,
"preview": "Output examples/settings/set-typing-speed.gif\nSet Height 400\nSet FontSize 32\n\nSet TypingSpeed 25ms\nType \"Bunny\"\nCtrl+C\nS"
},
{
"path": "examples/settings/set-window-bar.tape",
"chars": 201,
"preview": "Output examples/settings/set-window-bar.gif\n\nSet FontSize 25\nSet Width 800\nSet Height 400\nSet Padding 20\n\nSet WindowBar "
},
{
"path": "examples/settings/width.tape",
"chars": 103,
"preview": "Output examples/settings/width.gif\n\nSet Height 200\nSet Width 475\n\nType \"I'm a little teapot\"\n\nSleep 1s\n"
},
{
"path": "examples/slides/README.md",
"chars": 276,
"preview": "# Slides\n\n<img width=\"600\" src=\"./slides.gif\" />\n\n```\nOutput examples/slides/slides.gif\nSet FontSize 32\nSet Padding 0\nSe"
},
{
"path": "examples/slides/slides.tape",
"chars": 218,
"preview": "Output examples/slides/slides.gif\nSet FontSize 32\nSet Padding 0\nSet Framerate 1\nSet Width 1600\nSet Height 1200\n\nHide\nTyp"
},
{
"path": "examples/split/split.tape",
"chars": 442,
"preview": "Output split.gif\n\nRequire tmux\n\nHide\n Type \"tmux -f /dev/null -L test new-session -- bash\" Enter\n Type \"tmux split-win"
},
{
"path": "examples/vhs-promo.webm",
"chars": 131,
"preview": "version https://git-lfs.github.com/spec/v1\noid sha256:2efe4b93955fcc08abcd4c472c3652f3defcdfbd6c42997e48a7204605d3ebd8\ns"
},
{
"path": "examples/welcome.tape",
"chars": 658,
"preview": "# Render the output GIF to demo.gif\nOutput examples/welcome.gif\nOutput examples/frames/\n\n# Set up a 1200x600 frame with "
},
{
"path": "ffmpeg.go",
"chars": 7649,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// FilterComplexBuilder generates -filter_complex opt"
},
{
"path": "go.mod",
"chars": 3299,
"preview": "module github.com/charmbracelet/vhs\n\ngo 1.25.8\n\nrequire (\n\tgithub.com/agnivade/levenshtein v1.2.1\n\tgithub.com/atotto/cli"
},
{
"path": "go.sum",
"chars": 14943,
"preview": "github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=\ngithub.com/agnivade/levenshtein v"
},
{
"path": "keys.go",
"chars": 3415,
"preview": "// Package vhs keys.go defines the key map for the Type command.\n// The `keymap` map is used to convert runes from a str"
},
{
"path": "lexer/lexer.go",
"chars": 6605,
"preview": "// Package lexer provides a lexer for the VHS Tape language.\npackage lexer\n\nimport \"github.com/charmbracelet/vhs/token\"\n"
},
{
"path": "lexer/lexer_test.go",
"chars": 9852,
"preview": "package lexer\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/charmbracelet/vhs/token\"\n)\n\nfunc TestNextToken(t *testing.T) {\n\ti"
},
{
"path": "main.go",
"chars": 8228,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t_ \"embed\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path"
},
{
"path": "man.go",
"chars": 3691,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/glamour\"\n\t\"github.com/mattn/go-isatty\"\n\tmcobr"
},
{
"path": "parser/parser.go",
"chars": 18951,
"preview": "// Package parser provides a parser for the VHS Tape language.\npackage parser\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\""
},
{
"path": "parser/parser_test.go",
"chars": 14064,
"preview": "package parser\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/charmbracelet/vhs/lexer\"\n\t\"github.com/charmbracelet/v"
},
{
"path": "publish.go",
"chars": 5179,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/c"
},
{
"path": "record.go",
"chars": 5857,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/charmbra"
},
{
"path": "record_test.go",
"chars": 1402,
"preview": "package main\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestInputToTape(t *testing.T) {\n\ttests := []struct {\n\t\tname string"
},
{
"path": "screenshot.go",
"chars": 2824,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"path/filepath\"\n)\n\n// ScreenshotOptions holds options related with screenshots"
},
{
"path": "screenshot_test.go",
"chars": 1083,
"preview": "package main\n\nimport \"testing\"\n\nfunc TestScreenshot(t *testing.T) {\n\tt.Run(\"makeScreenshot should add screenshot to map "
},
{
"path": "scripts/download_theme.sh",
"chars": 197,
"preview": "#!/bin/sh\n\ncurl $1 |\n\tsed \\\n\t\t-e 's/\"purple\":/\"magenta\":/g' \\\n\t\t-e 's/\"brightPurple\":/\"brightMagenta\":/g' \\\n\t\t-e 's/sel"
},
{
"path": "serve.go",
"chars": 4050,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"t"
},
{
"path": "serve_unix.go",
"chars": 442,
"preview": "//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris\n// +build darwin dragonfly freebsd li"
},
{
"path": "serve_windows.go",
"chars": 173,
"preview": "//go:build windows\n// +build windows\n\npackage main\n\n// Windows doesn't support UID and GID, so we need to skip this.\nfun"
},
{
"path": "shell.go",
"chars": 1995,
"preview": "package main\n\n// Supported shells of VH.\nconst (\n\tbash = \"bash\"\n\tcmdexe = \"cmd\"\n\tfish = \"fish\"\n\tnushell "
},
{
"path": "style.go",
"chars": 2752,
"preview": "package main\n\nimport (\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\n// Theme colors.\nconst (\n\tBackground = \"#171717\"\n\tFore"
},
{
"path": "syntax.go",
"chars": 2133,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/vhs/parser\"\n\t\"github.com/charmbracelet/vh"
},
{
"path": "testing.go",
"chars": 2255,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n)\n\n// TestOptions is the set of options for the testing fun"
},
{
"path": "themes.go",
"chars": 5475,
"preview": "// Package main theme.go contains the information about a terminal theme.\n// It stores the 16 base colors as well as the"
},
{
"path": "themes.json",
"chars": 253248,
"preview": "[\n {\n \"name\": \"3024 Day\",\n \"black\": \"#090300\",\n \"red\": \"#db2d20\",\n \"green\": \"#01a252\",\n \"yellow\": \"#fded"
},
{
"path": "themes_test.go",
"chars": 1793,
"preview": "package main\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n)\n\nfunc TestFindAllThemes(t *testing.T) {\n\tthemes, err :="
},
{
"path": "token/token.go",
"chars": 5622,
"preview": "// Package token provides the token types and structures for the VHS Tape\n// language.\npackage token\n\nimport (\n\t\"strings"
},
{
"path": "token/token_test.go",
"chars": 313,
"preview": "package token\n\nimport \"testing\"\n\nfunc TestToCamel(t *testing.T) {\n\t// simple case\n\tres := ToCamel(\"SIMPLE\")\n\tif res != \""
}
]
// ... and 5 more files (download for full content)
About this extraction
This page contains the full source code of the charmbracelet/vhs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 205 files (586.4 KB), approximately 199.8k tokens, and a symbol index with 407 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.