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 " 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


Latest Release Go Docs Build Status

Write terminal GIFs as code for integration testing and demoing your CLI tools. Welcome to VHS 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. A GIF produced by the VHS code above 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 .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 ```
Windows, Debian, Ubuntu, Fedora, RHEL, Void Instructions - 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 ```
[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 ```
Configuration Options - `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)
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 `](#output): specify file output - [`Require `](#require): specify required programs for tape file - [`Set Value`](#settings): set recording settings - [`Type ""`](#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]+`](#ctrl): press control + key and/or modifier - [`Sleep