This directory converts a session's event log into recording exports. generateAsciicast builds an asciicast v2 recording as a string and leaves the file write to the caller. generateWebmExport replays the events into a video-capable renderer backend (by default GhosttyWebBackend, which runs headless Chromium) and saves the recorded WebM video at the requested path. renderGridFramesToSvg turns styled grid frames into SVG text, still or animated, and leaves the file write to the caller.
AsciicastHeader:version: 2,width,height,timestamp,title, optionalsessionIdandtoolVersion, andenv.TERM.widthandheightare the creation size of the session (manifest.creationColsandmanifest.creationRows, ormanifest.colsandmanifest.rowswhen they are absent), the size that replay starts from. A laterresizeevent becomes anrline, so a resized session keeps its first size in the header [asciicast.ts:AsciicastHeader].AsciicastExport:contents,header,capturedAtSeq,durationMs,outputEventCount,resizeEventCountandmarkerCount[asciicast.ts:AsciicastExport].generateAsciicast(sessionId, manifest, events, toolVersion?)returns anAsciicastExport. It runs synchronously [asciicast.ts:generateAsciicast].WebmExportOptions:sessionId,sessionDir,manifest,events,outputPath, and optionaltargetSeq,profileName,timingModeandrendererName[webm.ts:WebmExportOptions].WebmExportDeps: optionalbackendFactoryandstallWindowMs, for tests and callers that supply their own backend.stallWindowMsreplaces the 5-minute stall window (see Invariants) [webm.ts:WebmExportDeps].WebmExportResult:capturedAtSeq,durationMs,outputEventCount,resizeEventCount,cols,rows,profileName,timingModeandrendererBackend, plus the optionalmissingGlyphsandmissingGlyphsTruncatedofRecordedGlyphFields[webm.ts:WebmExportResult].generateWebmExport(options, deps?)returns aPromise<WebmExportResult>[webm.ts:generateWebmExport].WebmReplayStallErroris the error thatgenerateWebmExportrejects with when the renderer makes no progress for the stall window. It carriesstallWindowMs,lastSeq(the last event that the replay reached, or -1 for none) andtimingMode[webm.ts:WebmReplayStallError].renderGridFramesToSvg({ profile, frames, animate, heapSizeLimit? })returns SVG text from styled grid frames, whichcaptureGridFramesinsrc/replay/gridFrames.tsbuilds. It runs synchronously and leaves the file write to the caller [svg.ts:renderGridFramesToSvg].
generateAsciicastasserts a non-emptysessionIdequal tomanifest.sessionId, a positive initial width and height (manifest.creationCols ?? manifest.colsandmanifest.creationRows ?? manifest.rows, as inbuildReplayInput), and a non-emptytoolVersionwhen given [asciicast.ts:generateAsciicast].manifest.createdAtmust parse as a valid timestamp even when there are events. The header uses it wheneventsis empty [asciicast.ts:parseTimestamp].- Event timestamps must parse to finite numbers [asciicast.ts:parseTimestamp]. They must not decrease from one event to the next [asciicast.ts:generateAsciicast].
- Event mapping:
outputbecomeso,resizebecomesrwithCOLSxROWS, andmarkerbecomesm. Input, run, signal and exit events are skipped. An unknown type reachesunreachable()[asciicast.ts:generateAsciicast]. generateWebmExportasserts a non-emptysessionId, absolutesessionDirandoutputPath, and positive initial dimensions. An empty event list gives a blank video withcapturedAtSeq-1 anddurationMs0 [webm.ts:generateWebmExport].generateAsciicastreportscapturedAtSeq-1 for an empty event list [asciicast.ts:generateAsciicast].- Defaults: timing mode
recordedand profilereference-dark[webm.ts:generateWebmExport]. - A requested
libghostty-vtrenderer is replaced withghostty-web. A code comment sayslibghostty-vtis semantic-only [webm.ts:generateWebmExport]. - The default backend factory accepts only
ghostty-web[webm.ts:createDefaultBackend]. - Timing presets:
acceleratedcaps gaps at 400 ms and holds frames at least 100 ms.max-speeduses a frame hold of 16 ms, but holds it only after the last flush of the replay and after a resize, and holds one video frame (40 ms) before a resize (see theghostty-webREADME). Each mode holds the final frame (1000 ms, or 500 ms formax-speed) [webm.ts:buildReplayTimingOptions]. - The timed replay has no total time limit. It fails with
WebmReplayStallErroronly when the renderer makes no progress forREPLAY_STALL_WINDOW_MS(5 minutes). The backend reports progress through theonProgressargument ofreplayWithTiming: each report restarts the window. A report that comes before a wait on the timing schedule carries the length of that wait, and the window starts after it, so a long recorded gap is not a stall. A stretch without a report is one page call or a fixed hold, never the whole replay: a call carries at most 1000 chunks, and a report follows it. The stretches are the start to the first report (the replay sizes the terminal and holds one frame), one page call (the call that ends a flush also writes the chunks that the earlier calls of the flush held, so the event log limit of 50 MiB bounds it), and the last report to the end (the final frame hold of 1000 ms, or 500 ms atmax-speed, with the glyph probes inside it, and then a read of the screen). Examples measured at 600x150 on a shared host under load (host CPU pressure 34 to 57 %): the start took 1.2 to 1.9 s and the end 1.1 to 1.4 s. One flush of 520 full-screen repaints of 93 KB (48.6 MB) had a longest stretch of 15.9 to 20.7 s in three runs, 5.3 to 6.9 % of the window. One flush of 11,000 events of 4 KB (45 MB) had a longest stretch of 3.5 s and 5.8 s in two runs. These are examples and not bounds: a stretch depends on the output in one flush, the terminal size and the load of the host. No timer runs longer than 2^31-1 ms (the longest delay that a timer runs): a longer span is a chain of timers. The watchdog starts no timer once the replay has ended, failed or stalled. Boot and finalize run outside the watchdog [webm.ts:replayWithStallWatchdog, webm.ts:REPLAY_STALL_WINDOW_MS]. renderGridFramesToSvgneeds the profile font sizeSVG_FONT_SIZE(14) and draws fixed 8.4x18 px cells, so equal input gives byte-identical output [svg.ts:renderGridFramesToSvg]. It always embeds the JetBrains Mono subset, and embeds each other bundled face only when a glyph needs it [svg.ts:embeddedFontAssets]. Output pastdefaultMaxSvgOutputLengthof the V8 heap limit (the heap limit divided by 8 bytes, at mostMAX_SVG_OUTPUT_LENGTH) fails withEXPORT_ERROR[svg.ts:defaultMaxSvgOutputLength].
generateAsciicast: none found in code. It returns the NDJSON text incontents.renderGridFramesToSvg: none found in code. It returns the SVG text.generateWebmExportcreates a temporary video directory withmkdtempunderos.tmpdir()(prefixagent-tty-webm-) and sets its mode to0o700[webm.ts:generateWebmExport].- It builds the replay input with
buildReplayInputfromsrc/host/replay.tsand the profile withresolveProfilefromsrc/renderer/profiles.ts[webm.ts:generateWebmExport]. - The default backend is a
GhosttyWebBackendthat records video into the temporary directory [webm.ts:createDefaultBackend]. - The
.webmfile is written only bybackend.finalizeVideo(outputPath).backend.recordedMissingGlyphs()runs before it, becausefinalizeVideocloses the page that the glyph check probes [webm.ts:generateWebmExport]. - In
finallyit awaitsbackend.dispose()without a catch, so dispose errors propagate. Then it removes the temporary directory withrm({ recursive: true, force: true })and ignoresrmerrors. A throwingdispose()skips therm[webm.ts:generateWebmExport].
- Run:
npx vitest run test/unit/export test/unit/commands/record-export.test.ts - Run (integration):
npx vitest run --maxWorkers=1 test/integration/record-export.test.ts - Run (e2e, needs Playwright Chromium from
mise run bootstrap):npx vitest run --maxWorkers=1 test/e2e/export-fixtures.test.ts - Run:
npm run typecheck