tcut — 端末セッションを動画に は何をしますか?
tcut は実際のシェル (PTY + ヘッドレス Ghostty 端末) を録画し、決定論的な動画をレンダリングします。スクリプトはプレーンな TypeScript で記述します — DSL は不要です。同じ録画から、常に同じピクセルの結果が得られます。
セットアップ
bun add -g termcut # Bun >= 1.4、`tcut` コマンドをインストール
MP4/GIF/WebM には ffmpeg が PATH 上にある必要があります。SVG および HTML プレイヤーは追加で何も必要としません。すべての機能は --json に対応しており、機械可読な結果を出力します。プロンプトを表示することはありません。
2 つの録画方法
スクリプトモード (エージェント向け) — 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 はシェルを開き、exit するまで録画し、正確な録画 (demo.cast) と、入力された内容の編集可能なスクリプト (demo.video.ts) の両方を書き出します。tcut rec -o demo.mp4 -- npm create vite は 1 つのコマンドだけを録画します。
t API (defineVideo 内で使用)
t.run(cmd, { wait? })— 入力 + Enter + シェルプロンプトを待機 (タイマーではなく画面ベース)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 () => { ... })— カメラに映らない場所でセットアップを実行 (状態は保持されます; ここでバックグラウンドジョブを kill しないでください)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 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 (同じレイアウトで 2 倍のピクセル — Retina/HiDPI やプレイヤーで拡大表示される場合に使用), typingSpeed/typingJitter, keys: true (キー入力オーバーレイ、一度に 1 チップ; { 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 (ソフトドロップシャドウ; margin はデフォルト 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 # 同じ cols×rows が必要; 各接合部で画面リセット
再実行せずに再レンダリング
録画 (.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]— 2 つの録画の画面テキストを比較、差異があれば exit 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) はシェルなしで任意のキャストを再レンダリング。他にも recordLive, cutRecording, concatRecordings, selectChapters, buildSvg, buildHtml, replayFrames, diffCasts, diagnoseCast, generateScript, runScriptTests, publishFiles をエクスポート。CI では明示的に theme/font を指定し ("auto" は実行中の端末の設定を読み取る)、動画は一度に 1 つずつレンダリングしてください。