tcut — sesiones de terminal a vídeo
tcut graba un shell real (PTY + terminal Ghostty sin cabeza) y renderiza vídeos deterministas. Los scripts son TypeScript plano, sin DSL. La misma grabación, los mismos píxeles, siempre.
Configuración
bun add -g termcut # Bun >= 1.4, instala el comando `tcut`
MP4/GIF/WebM necesitan ffmpeg en PATH. Los reproductores SVG y HTML no necesitan nada más. Todo soporta --json para resultados legibles por máquina; nunca pregunta.
Dos formas de grabar
Scripteada (preferida para agentes) — escribe demo.video.ts, ejecuta 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"); // escribe, presiona Enter, espera a que vuelva el prompt
await t.expect(/1\.\d+/); // afirma en la pantalla renderizada (falla el vídeo si no está)
await t.sleep("1.5s");
},
);
En vivo — tcut rec -o demo.gif abre un shell, graba hasta exit, y escribe tanto la grabación exacta (demo.cast) como un script editable (demo.video.ts) de lo que se escribió. tcut rec -o demo.mp4 -- npm create vite graba un solo comando.
La API t (dentro de defineVideo)
t.run(cmd, { wait? })— escribe + Enter + espera el prompt del shell (basado en pantalla, no en temporizador)t.type(texto)/t.paste(texto)/t.enter()/t.escape()/t.tab()— maneja TUIs tecla por teclat.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" })— espera hasta que la pantalla lo muestre;t.expect(re)— afirmat.sleep("800ms")— duraciones aceptan"500ms","1.5s", números (ms)t.hide(async () => { ... })— ejecuta configuración fuera de cámara (el estado persiste; no mates trabajos en segundo plano aquí)t.print(markdown)/t.title(texto)— renderiza leyendas Markdown en el vídeo sin escribirt.zoom({ rows: [0, 5], cols: [0, 60], duration: "500ms" })— magnifica una región;t.zoom(null)reiniciat.slide("Desplegar en la nube", { eyebrow: "3", subtitle: "…", during })— tarjeta de transición a pantalla completa entre demos de funcionalidades: encabezado grande centrado con tipografía real, se desvanece, graba un capítulo con el mismo nombre;during: async () => …ejecutacd/clear/configuración de servidor invisiblemente detrást.chapter("Instalar")— metadatos reales de capítulo MP4, y un punto de corte:--chapters Instalar/--split-chaptersal renderizart.expect(/…/, { scope: "scrollback" })ve la salida que se desplazó fuera;t.scrollback()devuelve la transcripción completa;-o demo.logla escribe- Las flechas se envían como SS3 cuando el programa activó el modo cursor de aplicación,
t.paste()es bracketed cuando el programa lo solicitó — los editores se comportan como con un terminal real;print("[texto](url)")hace un enlace clickeable en SVG/HTML title: "auto"sigue títulos OSC;tcut doctor demo.castexplica qué usó una grabación y qué tcut no puede mostrar (imágenes inline)t.timelapse(async () => { await t.run("bun install") }, { speed: 8 })— todo lo interno se reproduce 8× más rápido (maxPausesolo elimina silencios; esto comprime la salida)t.snapshot("foto.png")/t.snapshot("hero.svg")— instantánea de ese momento exacto en cada renderizado (screenshotes alias),t.clear(),t.resize(cols, rows),t.screen()/t.line()para leer la pantalla- Panel del navegador:
browser: { position: "right" | "overlay", width }en la configuración, luegot.browser.goto(url),t.browser.waitFor(/text/),t.browser.click(sel),t.focus("browser" | "terminal")— graba una WebView real junto/sobre el terminal (demos de servidores de desarrollo)
Aspectos esenciales de configuración
output (array = múltiples formatos), preset: "readme" | "x" | "youtube" | "square", theme (~600 temas de Ghostty, tcut themes), cols/rows o width/height (px), fps, scale: 2 (mismo diseño, 2× los píxeles — úsalo para Retina/HiDPI o cualquier cosa que el reproductor vaya a escalar), typingSpeed/typingJitter, keys: true (superposición de pulsaciones de teclas, un chip a la vez; { limit, font, color, background, radius, position }), maxPause: "1.5s" (compresión de inactividad), requires: ["bun", "eza"] (falla rápido antes de grabar si falta una herramienta — también en tcut test), windowBar: "none" + margin: 0 + borderRadius: 0 para un terminal desnudo, title, marginFill ("transparent" = alfa real en png/webp/gif/webm/svg/html; mp4 usa fallback), shadow: true (sombra suave; margin por defecto 40), watermark: "© tú" o { image: "logo.png", position: "top-left", opacity, size }.
Cortar y unir (sin regrabar)
Todo en la línea de tiempo visible del cast, por lo que cada formato funciona y el resultado sigue siendo un .cast:
tcut render demo.cast --from 2s --to 10s -o clip.gif # ventana de tiempo
tcut render demo.cast --chapters Zoom,Intro -o clip.mp4 # capítulos, en ese orden
tcut render demo.cast --split-chapters -o demo.mp4 # demo-01-intro.mp4, demo-02-zoom.mp4 … (biblioteca de clips ideal para Remotion)
tcut cut demo.cast --from 2s --to 10s # escribe demo-cut.cast
tcut concat intro.cast demo.cast --gap 500ms -o launch.mp4 # mismas cols×rows requeridas; la pantalla se reinicia en cada unión
Renderizar de nuevo sin volver a ejecutar
La grabación (.cast) y el renderizado están separados:
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 / testing
tcut test demo.video.ts— ejecuta el script como prueba (rápido, sin codificación de vídeo)tcut diff a.cast b.cast [--images dir]— compara texto de pantalla de dos grabaciones, sale con 1 si hay deriva
Advertencias
- Servidores de desarrollo en segundo plano en el PTY:
bun run dev </dev/null >/tmp/dev.log 2>&1 &(los lectores de stdin reciben SIGTTIN; los logs perdidos repintan sobre TUIs) - Espera en la pantalla, no en el tiempo: prefiere
t.run/t.wait(/Local:/, { scope: "screen" })sobre sleeps largos t.hidemantiene el estado — unkilloculto al final se filtra en el último fotograma- Para TUIs (nvim, lazygit, claude), inicia con
t.run(cmd, { wait: /algo-en-pantalla/ }), luego maneja con teclas
Referencia completa: https://tcut.amanv.dev/llms.txt
Usar tcut como biblioteca
import { defineVideo, renderCast } from "termcut" (solo Bun). defineVideo(config, script) devuelve un Video: await video.run({ force, log }) graba y renderiza y devuelve { outputs, screenshots, durationSeconds, recording }; video.record() y video.render(recording, { overrides, clip }) separan los pasos; renderCast(file, overrides) re-renderiza cualquier cast sin un shell. También se exportan: recordLive, cutRecording, concatRecordings, selectChapters, buildSvg, buildHtml, replayFrames, diffCasts, diagnoseCast, generateScript, runScriptTests, publishFiles. En CI pasa theme/font explícitos ("auto" lee el terminal en ejecución) y renderiza un vídeo a la vez.