Communitygithub.com

AmanVarshney01/tcut

从 TypeScript 脚本或实时会话录制可复现的终端视频(MP4/GIF/SVG/HTML)。适合用户需要制作终端演示、CLI 截屏、TUI 录制或用于 README、网站、社交媒体的动画终端 GIF 的场景。

tcut 是什么?

tcut 是一款用于录制可复现终端视频的工具,输出格式包括 MP4、GIF、SVG 和 HTML。它通过真实的 PTY(伪终端)和 Ghostty 终端仿真渲染视频,确保相同的录制脚本每次生成完全一致的像素画面。用户可以使用纯 TypeScript 编写录制脚本,无需学习专有的领域特定语言。脚本模式下,tcut 能够自动模拟键盘输入、等待屏幕输出、进行断言检查,并支持录制过程中的缩放、标题幻灯片、章节切分等高级功能。除了脚本录制,tcut 还提供实时录制模式,可直接操作为可编辑的脚本。渲染和录制过程分离,用户可以在不重新执行命令的情况下切换主题、分辨率或帧率重新渲染。tcut 还支持测试模式 (tcut test)、差异对比 (tcut diff) 以及 CI 集成,非常适合自动化文档制作和维护。该工具支持在 Bun(≥1.4)环境中运行,可独立使用或作为 Node.js/Bun 库集成到工作流程中。

兼容平台✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/AmanVarshney01/tcut/tree/HEAD/skills/tcut

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

tcut — 终端会话转视频

tcut 会录制真实的 shell(PTY + 无界面 Ghostty 终端)并渲染出确定性的视频。脚本使用纯 TypeScript —— 无需学习领域特定语言。相同的录制,每次产生相同的像素。

安装

bun add -g termcut   # 需要 Bun >= 1.4,安装 `tcut` 命令

MP4/GIF/WebM 格式需要系统中存在 ffmpeg。SVG 和 HTML 播放器无需额外依赖。所有输出都支持 --json 以获取机器可读的结果;tcut 永远不会主动弹出提示。

两种录制方式

脚本录制(推荐用于 Agent) — 编写 demo.video.ts,运行 tcut demo.video.ts:

import { defineVideo } from "tcut";

export default defineVideo(
  { output: ["demo.mp4", "demo.gif"], preset: "x", theme: "Catppuccin Mocha", keys: true, maxPause: "1.5s" },
  async (t) => {
    await t.run("bun --version");        // 自动输入命令、按回车,等待提示符返回
    await t.expect(/1\.\d+/);            // 断言屏幕上显示出的内容(如果不匹配则视频录制失败)
    await t.sleep("1.5s");
  },
);

实时录制 — tcut rec -o demo.gif 会打开一个 shell,录制直到输入 exit,同时生成精确的录制文件(demo.cast)和一个可编辑的脚本(demo.video.ts),其中记录了所有键入的内容。tcut rec -o demo.mp4 -- npm create vite 直接录制单条命令。

t API(在 defineVideo 内部使用)

  • t.run(cmd, { wait? }) — 输入命令 + 回车 + 等待 shell 提示符(基于屏幕内容,非计时器)
  • t.type(text) / t.paste(text) / t.enter() / t.escape() / t.tab() — 逐个按键驱动 TUI
  • t.up/down/left/right/pageUp/pageDown/home/end()、t.ctrl("c")、t.alt("b")、t.shift("tab")、t.key("f5")、t.scrollUp/scrollDown()
  • t.wait(regex?, { scope: "line" | "screen" }) — 等待屏幕上出现指定内容;t.expect(re) — 断言
  • t.sleep("800ms") — 持续时间支持 "500ms"、"1.5s" 或数字(毫秒)
  • t.hide(async () => { ... }) — 在摄像头外执行准备操作(状态会保持;不要在此处杀后台进程)
  • t.print(markdown) / t.title(text) — 将 Markdown 字幕渲染到视频中,而不实际输入文本
  • t.zoom({ rows: [0, 5], cols: [0, 60], duration: "500ms" }) — 放大某个区域;t.zoom(null) 恢复全屏
  • t.slide("部署到云端", { eyebrow: "3", subtitle: "...", during }) — 在功能演示之间插入全屏过渡卡片:居中大字标题使用真实排版,淡入淡出,并记录同名章节;during: async () => … 可在卡片背后运行 cd/clear/服务器启动
  • t.chapter("Install") — 实际的 MP4 章节元数据,同时标记切分点:可在渲染时使用 --chapters Install / --split-chapters 分割
  • t.expect(/…/, { scope: "scrollback" }) 用于检测已经滚出屏幕的输出;t.scrollback() 返回完整记录文本;-o demo.log 将记录写入文件
  • 当程序启用了应用程序光标模式时,方向键会以 SS3 序列发送,t.paste() 在程序请求时使用带括号粘贴——编辑器表现与真实终端一致;print("[text](url)") 可在 SVG/HTML 中创建可点击的链接
  • title: "auto" 跟随 OSC 标题;tcut doctor demo.cast 解释录制使用了什么功能以及 tcut 无法显示的内容(如内嵌图片)
  • t.timelapse(async () => { await t.run("bun install") }, { speed: 8 }) — 内部的任何操作都以 8 倍速播放(maxPause 仅去除静默时间;此压缩输出)
  • t.snapshot("shot.png") / t.snapshot("hero.svg") — 在每次渲染时抓取当前时刻的静态截图(screenshot 是别称),t.clear()、t.resize(cols, rows)、t.screen() / t.line() 可用于读取屏幕内容
  • 浏览器面板:在配置中设置 browser: { position: "right" | "overlay", width },然后使用 t.browser.goto(url)、t.browser.waitFor(/text/)、t.browser.click(sel)、t.focus("browser" | "terminal") — 在终端旁边/上方录制一个真实的 WebView(用于开发服务器演示)

配置要点

output(数组 = 多种格式)、preset: "readme" | "x" | "youtube" | "square"、theme(约 600 种 Ghostty 主题,tcut themes 查看)、cols/rows 或 width/height(像素)、fps、scale: 2(保持相同布局,像素翻倍——用于 Retina/HiDPI 或任何播放器会缩放的情况)、typingSpeed/typingJitter、keys: true(按键叠加提示,每次显示一个键帽;可设置 { limit, font, color, background, radius, position })、maxPause: "1.5s"(空闲压缩)、requires: ["bun", "eza"](录制前快速检查工具是否存在——也可在 tcut test 中使用)、windowBar: "none" + margin: 0 + borderRadius: 0 实现裸终端效果、title、marginFill("transparent" 在 png/webp/gif/webm/svg/html 中实现真正 alpha 通道;mp4 回退)、shadow: true(柔和阴影;边距默认为 40)、watermark: "© you" 或 { image: "logo.png", position: "top-left", opacity, size }。

剪辑与拼接(无需重新录制)

所有操作基于录制文件的可视时间线,因此每种格式都支持,结果仍是 .cast 文件:

tcut render demo.cast --from 2s --to 10s -o clip.gif      # 时间窗口
tcut render demo.cast --chapters Zoom,Intro -o clip.mp4    # 按章节顺序导出
tcut render demo.cast --split-chapters -o demo.mp4         # 生成 demo-01-intro.mp4, demo-02-zoom.mp4 ... (适合作为 Remotion 的剪辑库)
tcut cut demo.cast --from 2s --to 10s                      # 写入 demo-cut.cast
tcut concat intro.cast demo.cast --gap 500ms -o launch.mp4 # 要求相同的列×行数;在每个拼接处重置屏幕

无需重新运行即可重新渲染

录制(.cast)和渲染是分离的:

tcut render demo.cast --theme "Gruvbox Dark" -o demo.svg
tcut render demo.cast --width 1280 --height 720 --speed 1.5 -o demo.mp4

CI / 测试

  • tcut test demo.video.ts — 将脚本作为测试运行(快速,不进行视频编码)
  • tcut diff a.cast b.cast [--images dir] — 比较两个录制文件的屏幕文本,若发现差异则退出代码为 1

注意事项

  • PTY 中的后台开发服务器:bun run dev </dev/null >/tmp/dev.log 2>&1 &(标准输入读取的进程会收到 SIGTTIN;杂散日志会覆盖 TUI 界面)
  • 等待屏幕内容而非时间:优先使用 t.run / t.wait(/Local:/, { scope: "screen" }) 而非长时间休眠
  • t.hide 会保持状态——在末尾隐藏的 kill 会泄露到最后一帧
  • 对于 TUI 程序(nvim、lazygit、claude),使用 t.run(cmd, { wait: /something-on-screen/ }) 启动,然后用按键驱动

完整参考:https://tcut.amanv.dev/llms.txt

将 tcut 作为库使用

import { defineVideo, renderCast } from "termcut"(仅限 Bun)。defineVideo(config, script) 返回一个 Video:await video.run({ force, log }) 录制并渲染,返回 { outputs, screenshots, durationSeconds, recording };video.record() 和 video.render(recording, { overrides, clip }) 可将步骤拆分;renderCast(file, overrides) 可重新渲染任意录制文件而无需 shell。此外还导出了:recordLive、cutRecording、concatRecordings、selectChapters、buildSvg、buildHtml、replayFrames、diffCasts、diagnoseCast、generateScript、runScriptTests、publishFiles。在 CI 中请显式传递 theme/font("auto" 会读取当前运行中的终端),并且一次只渲染一个视频。

Individual skills in this repo

This repo contains 7 individual skills — each has its own dedicated page.

相关技能