tcut — 終端機工作階段轉換為影片
tcut 會錄製一個真實的 Shell(PTY + 無頭模式的 Ghostty 終端機),並渲染出確定性的影片。腳本使用純 TypeScript 撰寫,無需領域特定語言。每次錄影結果相同,像素一致。
設定
bun add -g termcut # 需要 Bun >= 1.4,安裝後可使用 `tcut` 指令
MP4/GIF/WebM 格式需要 ffmpeg 在 PATH 環境變數中。SVG 和 HTML 播放器無需其他依賴。所有模式皆支援 --json 輸出機器可讀的結果,且不會要求使用者互動。
兩種錄影方式
腳本模式(代理推薦) — 撰寫 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"); // 模擬輸入並按下 Enter,等待提示字元返回
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? })— 模擬輸入 + 按下 Enter + 等待 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("安裝")— 真實的 MP4 章節元資料,以及剪輯點:可在渲染時使用--chapters 安裝/--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 中實現真正的透明度;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]— 比較兩次錄影的螢幕文字,若發生偏差則回傳 exit 1
注意事項
- 在 PTY 中啟動背景開發伺服器:
bun run dev </dev/null >/tmp/dev.log 2>&1 &(標準輸入讀取程式會收到 SIGTTIN;零散日誌會覆蓋 TUI 畫面) - 等待螢幕內容,而非等待時間:優先使用
t.run/t.wait(/Local:/, { scope: "screen" })而非長時間的sleep 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" 會讀取執行中的終端機設定),並一次只渲染一個影片。