base on Your CLI home video recorder 📼 # 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 - [`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 ``` ### 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 ", Assign "at most 3 tags" to the expected json: {"id":"9386","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"