VHS 官方示例全解:用 .tape 脚本为 Gum、gh CLI、Bubble Tea、jqp 与 Glow 录制 CLI/TUI 演示 GIF

原创2026-09-30 20:16:441,456 阅读
文章标签:开发工具

VHS 官方示例全解:用 .tape 脚本为 Gum、gh CLI、Bubble Tea、jqp 与 Glow 录制 CLI/TUI 演示 GIF

本文围绕 VHS 仓库 examples/ 目录下的官方示例文档展开,逐条拆解 VHS 如何以"代码化"方式为 Gum、GitHub CLI、Bubble Tea、jqp、Glow 等典型 CLI/TUI 工具录制演示 GIF。读完本文,你将掌握 .tape 脚本的完整语法(Output/Set/Type/按键/时序控制)、Hide/Show 的录制遮罩技巧,以及如何照搬这些官方模板为任意命令行程序产出可复现的演示视频。

运行示例的前提:安装 VHS 并补齐依赖

VHS 运行 .tape 脚本需要 ttyd)。安装方式按平台可选:

# macOS / Linux
brew install vhs
# Arch Linux
pacman -S vhs
# Nix
nix-env -iA nixpkgs.vhs
# Windows(scoop)
scoop install vhs

也可以使用官方 Docker 镜像(依赖已内置):

docker run --rm -v $PWD:/vhs ghcr.io/charmbracelet/vhs <cassette>.tape

或直接通过 Go 安装:

go install github.com/charmbracelet/vhs@latest

安装完成后,在仓库根目录执行:

vhs examples/gum/file.tape

即可按脚本生成对应的 GIF/MP4/WebM/PNG 序列帧输出。examples/ 下的每个子目录都配有一份 .tape 脚本和对应的成品 GIF,是学习 VHS 语法最好的"可运行文档"。

示例目录全景

examples/ 按演示对象组织子目录,官方示例文档 examples/README.md 重点讲解五类工具,其余子目录则覆盖 VHS 自身的设置与命令演示:

子目录 主题
examples/gum 录制 Gum 的 file / pager / table 组件
examples/gh-cli 录制 GitHub CLI 的 issue / pr 命令
examples/bubbletea 35 个 Bubble Tea 官方示例的 tape 脚本集合
examples/jqp 录制 jqp(JSON 查询 TUI)
examples/glow 录制 Glow(Markdown 阅读器)的简单与完整交互
examples/settings 逐项演示 Set 设置(宽高、字体、主题、边距等)
examples/commands 逐项演示 Type、按键、Hide/Show 等命令
examples/decorations 综合展示边距、窗口栏、圆角等装饰效果
examples/errors 展示非法脚本、依赖缺失、尺寸冲突等报错场景
examples/neofetch 单脚本同时输出 GIF / MP4 / WebM / PNG 帧目录
examples/publish 演示 --publish 上传发布
examples/split 借助 tmux 录制多窗格场景
examples/slides 低帧率模拟幻灯片翻页

录制 Gum 演示:file / pager / table

Gum 是 Charm 的 shell 脚本组件库,非常适合作为 VHS 的"演员"。官方文档给出了三个完整脚本,覆盖了 Type、Sleep、Enter 以及带 @time 的按键组合。

Gum File:文件选择器

VHS 录制的 Gum file 演示 GIF

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

逐行解读:

  • Output file.gif:声明渲染产物路径。只写文件名时,VHS 会把它写在当前工作目录(注意 examples/gum/file.tape 中写作 Output examples/gum/file.gif,即相对仓库根目录的路径,两种写法都能工作)。
  • Type "gum file ./src":把字符串逐字符"敲"进终端(速度受 TypingSpeed 控制,默认 50ms/字符)。
  • Sleep 1s:等待 1 秒后再继续,给程序启动留出时间。
  • Enter:按回车执行命令。
  • Down@500ms 4:每 500ms 按一次方向键 Down,共按 4 次——这是 VHS 最常用的"带节奏的按键"语法,形状为 Key[@<time>] [count],用于在列表/菜单中缓慢向下移动光标,模拟真实用户的浏览速度。
  • Up@500ms 1 / Up@500ms 2:向上回退选中项,制造"先选中再修改"的演示层次。

从源码看,这类按键统一由 ExecuteKey 实现(command.go):@time 被 time.ParseDuration 解析为每次按键的间隔,解析失败时回退到全局 TypingSpeed;后面的数字作为重复次数,缺省为 1。也就是说 Down@500ms 4 等价于在循环里"按键 + 睡 500ms"执行 4 次,这正是它能在 GIF 里呈现均匀滚动节奏的原因。

Gum Pager:分页阅读

VHS 录制的 Gum pager 演示 GIF

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

这个示例展示了 Set 命令的三种用法:

  • Set Padding 20:终端画面四周留 20px 内边距,让画面更透气;
  • Set FontSize 16:字号越小,同样高度下能容纳的行越多,适合展示长篇文本;
  • Set Height 600:指定虚拟终端高度(像素)。

交互部分使用 Down@25ms 40——以 25ms 间隔快速连按 40 次 Down,模拟 pager 中"快速向下翻页"的手感,随后 Up@25ms 30、Down@25ms 20 模拟来回翻阅。gum pager < ~/src/gum/README.md 演示了通过 shell 重定向把文件喂给 TUI 的常见组合。

Gum Table:表格浏览

VHS 录制的 Gum table 演示 GIF

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

gum table < superhero.csv 从 superhero.csv(仓库 examples/gum/superhero.csv)读取 CSV 并渲染为表格;-w 2,12,5,6,6,8,4,20 指定各列宽度,--height 10 限制表格可视高度。脚本用 Down@200ms 10 重复向下滚动两轮、再 Up@200ms 10 回退,最后 Enter 确认选择并停留 3 秒收尾。

录制 GitHub CLI 演示:issue 与 pr

GitHub CLI(gh)的输出天生适合录制成演示:issue 列表、详情、PR 列表都是结构化的表格/富文本。官方文档给出了两个脚本。

Issue 列表与详情

VHS 录制的 gh issue 演示 GIF

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

要点:

  • Enter 1:注意这里的 1 是重复次数,不是"按 1 键"——它等价于按一次回车(Enter 与 Enter 1 效果相同,写法展示的是带计数的形式)。
  • Sleep 4s:给 gh issue list 的网络请求与表格渲染留出充足时间,避免 GIF 里出现"未渲染完"的画面。
  • Ctrl+L:发送控制序列清屏,让下一段演示(issue 详情)从干净画面开始。Ctrl 命令支持 Ctrl[+Alt][+Shift]+<char> 组合,此处是 Ctrl + L。
  • 随后 gh issue view 19 拉取单个 issue 的完整详情。

运行此脚本前,需要机器上已安装并登录 gh(gh auth login),且当前仓库存在对应的 issue/pr 数据——这正是 Require 之外的真实运行约束。

Pull Request 列表

VHS 录制的 gh pr 演示 GIF

Output gh-pr.gif

Type "gh pr list --state all"
Sleep 500ms
Enter

Sleep 5s

更简洁:一条命令 + 回车 + 5 秒展示。--state all 让列表同时包含 open / merged / closed 状态的 PR,信息密度更高。

Bubble Tea 示例:TUI 录制的标准工作流

官方文档将 examples/bubbletea 目录中的 35 个 .tape 文件作为 Bubble Tea 的配套演示脚本(对应 Bubble Tea 官方示例程序),涵盖 altscreen-toggle、chat、composable-views、credit-card-form、debounce、exec、fullscreen、glamour、help、http、list-default/fancy/simple、package-manager、pager、paginator、pipe、progress-animated/static、realtime、result、send-msg、sequence、simple、spinner(s)、split-editors、stopwatch、table、tabs、textarea、textinput(s)、timer、tui-daemon-combo、views 等场景。这些脚本有一个共同骨架,以 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

这是 TUI 录制的最重要模式,值得拆解:

  1. Hide 遮罩构建阶段:go build、clear 这些"准备工作"虽然会被真实执行,但不会出现在最终 GIF 里(VHS 在 Hide 期间停止抓帧)。
  2. Show 恢复抓帧:从 Type "./simple" 开始,GIF 只记录"程序运行"的精彩部分。
  3. Hide + Ctrl+C 清理:演示结束后,在隐藏状态下发送 Ctrl+C 终止 TUI、rm simple 删除编译产物,让仓库保持干净。

再看 examples/bubbletea/textinput.tape 中与输入框交互的部分:

Type "./textinput"
Enter
Sleep 0.5

Type "Ponyta"
Sleep 0.5
Left@100ms 2
Type "(lalala)"
Sleep 1

Left@100ms 2 以 100ms 间隔把光标向左移动两格,随后输入 (lalala),演示"在已有文本中间插入内容"——这种精细的编辑过程只有通过 @time 控速才能自然呈现。

examples/bubbletea/list-fancy.tape 则展示了 TUI 中"过滤"的录制手法:

Down@250ms 3
Enter
Sleep 0.5
Type "x"
Sleep 0.5
Type "?"
Sleep 0.5
Type@150ms "aaaa"
Type@150ms "xxxx"

先缓慢移动选择、回车展开,再用 Type "x"、Type "?" 逐个输入过滤关键字,Type@150ms 控制过滤词的输入节奏。

录制 jqp 演示:管道数据 + 隐藏加载

jqp 是一个 JSON 查询 TUI(jq 的交互式包装器)。官方文档 examples/README.md 给出了一张成品图,而完整的录制脚本在 examples/jqp/README.md 与 examples/jqp/jqp.tape 中:

VHS 录制的 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

值得学习的技巧:

  • Set Width 2400 / Set Height 1600:jqp 的 JSON 视图信息量大,用超大画布保证每一行都清晰可读。
  • Hide + 拉数据 + Show:curl ... | jqp 的下载过程是隐藏的,Show 之后画面直接进入已加载完成的 jqp 界面——这是"隐藏网络等待"的标准做法。
  • Type@.2'...':注意 @ 后紧跟 .2(0.2 秒),这是 Type[@<time>] 的时间参数写法;jq 过滤表达式里同时含单双引号,需要用反引号包裹整个字符串(Type \VAR="Escaped"`` 是官方文档给出的转义规则),因此这里的写法是 Type@.2'...'。
  • Down@25ms 20:快速滚动结果树;Tab 在 jqp 的查询/结果视图间切换;Ctrl+S 保存查询;最后 Escape 退出。

录制 Glow 演示:Markdown 阅读与编辑的完整交互

Glow 是 Charm 的 Markdown 终端阅读器,官方文档用两个脚本分别展示"极简"与"复杂交互"两种录制风格。

Glow Simple:双输出 + 简单浏览

VHS 录制的 Glow 简单演示 GIF

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
  • 第一条值得注意的语法是双 Output:同一段录制同时渲染出 glow-simple.ascii 和 glow-simple.gif。ASCII 输出正是主 README 中"集成测试 + golden 文件"玩法的基础——把 .ascii 提交进 Git 仓库,任何运行结果不一致都会在 diff 中暴露。
  • 交互层面:Enter 进入目录、Escape 退出浏览、Type "q" 退出 Glow,完整交代了"进→看→退"的过程。

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

这是 examples/ 中最复杂的脚本之一,几乎用遍了 VHS 的"隐藏+显示+控速"手法:

  • 搜索演示:Hide 中按 Tab 进入命令模式、输入 /artichoke 回车,Show 后再 Down 2 选中——整个过程"搜索是隐藏的、结果是可见的",避免 GIF 里出现输入框闪动干扰。
  • 浏览节奏:Down 20 普通速度滚动,Up@400ms 5 慢速回滚,Down@200ms 20 快速下落,三种速度对比形成明确的演示叙事。
  • 编辑演示:Type "m" 进入 Markdown 编辑后,Ctrl+A 跳到行首、Right 4 移动光标、Ctrl+U 清空整行,随后以 Type@250ms "Tasty " 等不同速度补写文本——Ctrl+U/Ctrl+A 这类 Emacs 风格快捷键在 VHS 中均有对应 Ctrl+<key> 命令。

从实现层面看,Type 之所以能逐字符"真实敲击",是因为 VHS 维护了一张字符→按键的映射表(keys.go):小写字母映射到普通按键,大写字母与 !/"/# 等符号通过 shift() 加上 Shift 修饰符,从而在虚拟终端里产生与真人键盘一致的输入事件。

示例中反复出现的命令速查

把上面所有脚本里用到的指令汇总,就是一份从实战中提炼的语法速查(完整参考见根目录 README.md 的 "VHS Command Reference",也可用 vhs manual 在终端查看):

指令 说明 示例
Output <path> 声明渲染输出,可声明多个 Output out.gif / Output frames/(PNG 帧目录)
Require <program> 要求程序在 $PATH 中,缺失则提前失败;必须放在脚本顶部 Require tmux(见 examples/split/split.tape)
Set <Setting> <Value> 设置终端参数;除 TypingSpeed 外须在脚本顶部 Set FontSize 16 / Set Theme "Catppuccin Frappe"
Type[@<time>] "<chars>" 模拟逐字符输入,@time 覆盖全局打字速度 Type@500ms "Slow down"
Enter/Backspace/Tab/Space/Up/Down/Left/Right/Escape/PageUp/PageDown [@time] [count] 按键,可带间隔与次数 Down@200ms 10 / Enter 1
Ctrl[+Alt][+Shift]+<char> 组合键 Ctrl+L(清屏)、Ctrl+C(中断)、Ctrl+A(行首)
Sleep <time> 停留指定时长,单位支持 s/ms/纯数字秒 Sleep 500ms / Sleep 1
Wait[+Screen][+Line] /regex/ 等待屏幕出现匹配内容(默认 />$/、15s 超时、Line 作用域) Wait+Screen /World/
Hide / Show 停止 / 恢复抓帧,用于隐藏构建与清理 见 Bubble Tea、Glow 脚本
Screenshot <path> 抓取当前帧为 PNG(见 examples/screenshot.tape) Screenshot examples/hello_world.png
Copy / Paste 复制/粘贴剪贴板文本 Copy "url" + Paste
Env <Key> <Value> 设置环境变量 Env HELLO "WORLD"
Source <file> 从另一个 tape 导入命令 Source config.tape

所有可重复按键/滚动指令(含 ScrollUp/ScrollDown)都遵循 Key[@<time>] [count] 的通用形态,源码中由 ExecuteKey(command.go)统一处理时间间隔与重复次数;而 Hide/Show/Screenshot/Require 等指令分别挂载在 command.go 的 CommandFuncs 映射表中。

Set 家族还有更多示例目录可参考:FontFamily、LetterSpacing、LineHeight、TypingSpeed、Padding、Margin/MarginFill、WindowBar/WindowBarSize、BorderRadius、Theme、Framerate、PlaybackSpeed、LoopOffset、CursorBlink 均有独立 GIF 演示(见 examples/settings),综合装饰效果见 examples/decorations/decorations.tape。

把官方示例改造成自己的演示脚本

以模板起步

最简单的路径是复制一份现有脚本改头换面:把 Output 换成自己的文件名,把 Type 里的命令换成目标 CLI,再按需调整 Set 尺寸与 Sleep 时长。例如仿照 gh-pr 脚本:

Output my-demo.gif
Set FontSize 32
Set Width 1200
Set Height 600

Type "mycli --version"
Enter
Sleep 2s

Type "mycli run --dry-run"
Enter
Sleep 4s

用 vhs new 与 vhs record 辅助生成

不擅长手写脚本时,可以先用 vhs new demo.tape 生成骨架,再手动填写命令;或者直接用 vhs record > cassette.tape 录制一段真实操作,VHS 会把按键序列自动写成 tape 文件,之后手动补上 Set 设置与 Sleep 节奏即可(详见根目录 README.md 的 "Record Tapes" 一节)。录制类的示例可参考 examples/publish/cassette.tape 与 examples/publish/publish.tape。

用示例学习错误处理

examples/errors 目录提供了三种"反面教材":parser.tape 是非法语法(Set Foo Bar、Type Enter 等无法解析的写法);require.tape 在非顶部位置声明 Require;dimensions.tape 把 Padding 100 设得比 Height/Width 100 还大,触发尺寸冲突。运行这些脚本能直观看到 VHS 的报错信息,帮助你在自己的脚本里规避同类问题。

接入 CI:golden 文件

最后,把 Output demo.ascii(或 .txt)声明进脚本,渲染出的纯文本结果可以作为 golden 文件提交到 Git 仓库;CI 中每次运行后只要 git diff 无变化,就说明 CLI 输出保持稳定——这是 VHS 从"演示工具"延伸到"集成测试工具"的关键路径(根目录 README.md 的 "Continuous Integration" 一节对此有说明)。

总而言之,examples/ 不是孤立的展示品,而是一套覆盖"简单命令→复杂 TUI→隐藏构建→多格式输出"全谱系的模板库:Gum 示例教会你基础按键节奏,Bubble Tea 示例教会你构建/运行/清理三段式,Glow 示例教会你隐藏式搜索与编辑交互。照抄其中任何一份脚本,替换成你自己的命令,五分钟内就能产出一条专业水准的 CLI 演示 GIF。

登录后查看全文
vhs