ScreenFlow 1.0.0 ================ Turn a recorded terminal session into a shareable animated GIF. ScreenFlow reads a cast file written by SessionForge, replays it into an in-memory screen buffer, draws that buffer with a built-in bitmap font, and encodes the frames as a real GIF89a animation. The result is a file you can drop straight into a chat message, a ticket or a README, where a terminal replay would be useless. WHAT THIS IS NOT ---------------- READ THIS FIRST, BECAUSE THE NAME CAN MISLEAD. ScreenFlow does NOT capture your screen. It never looks at your display, your window manager, your camera or your desktop. There is no screen recorder in here at all. It takes a session that SessionForge recorded EARLIER - a text file of timestamped output chunks - and paints those characters into images. Everything in the GIF was reconstructed from recorded bytes, not photographed. It also does not produce video. No MP4, no WebM, no H.264. GIF and PNG only. It does not record anything itself. Use SessionForge for that: sessionforge record build.jsonl -- make deploy screenflow render build.jsonl --out build.gif --apply INSTALL ------- Prebuilt binaries are in dist/: dist/screenflow-linux-amd64 dist/screenflow-darwin-amd64 dist/screenflow-darwin-arm64 dist/screenflow-windows-amd64.exe Or build it yourself. Go 1.24, standard library only, no dependencies: go build -o screenflow . GOPROXY=off works; nothing is downloaded. COMMANDS -------- screenflow render --out [--cols 80] [--rows 24] [--speed 1.0] [--max-idle 2.0] [--font-scale 1] [--theme dark|light] [--apply] screenflow info [--json] screenflow frames --out [--every N] [--cols 80] [--rows 24] [--font-scale 1] [--theme dark|light] [--apply] screenflow help | version render Replays the cast and writes the animated GIF. --out PATH the GIF to write. Required. --cols N terminal width in characters. Default 80. --rows N terminal height in lines. Default 24. --speed N divide every delay by N. 2 is twice as fast. Default 1.0. --max-idle S cap any single gap at S seconds, so a five minute pause becomes a two second one. Default 2.0. 0 means no cap. --font-scale N integer pixel scale, 1 to 8. Default 1. Scale 2 is a good choice for a README on a high density display. --theme NAME dark or light. Default dark. --apply actually write the file. WITHOUT --apply, render is a dry run: it does all the work, reports the frame count, the pixel dimensions and the encoded size, and writes nothing. Verified: running render and frames without --apply in an empty directory leaves that directory empty. info What would be rendered, using render's defaults: event count, recorded duration, longest idle gap, frame count, output dimensions and the length of the resulting animation after idle-capping. --json emits the same data as one JSON object for scripts. frames Writes individual PNG frames into a directory instead of a GIF, for anyone who wants to assemble their own animation with other tools. --every N keeps only every Nth frame; the final frame is always kept. Also a dry run unless --apply is given. Files are named frame-00000.png, numbered by frame index, so --every N leaves gaps in the numbering on purpose - the numbers still tell you where in the session each frame came from. FRAMES AND TIMING - EXACTLY WHAT HAPPENS ---------------------------------------- There is one frame per recorded event, plus one opening frame showing the still-empty screen. A cast with 18 events becomes a 19 frame GIF. GIF cannot express a delay finer than 1/100 of a second. ScreenFlow rounds each gap to the nearest hundredth and CARRIES THE ROUNDING ERROR into the next frame, rather than throwing it away. Two consequences, stated plainly: 1. Output bursts closer together than about 5ms get a delay of 0. Most viewers show a zero delay frame as fast as they can draw it; some clamp small delays up to 10ms. Either way, those frames are the ones a human could never have seen separately anyway. 2. Because the error is carried, the TOTAL running time of the animation still matches the recorded session to within half a hundredth of a second. A 0.125s gap repeated becomes 13, 12, 13, 12 hundredths, not 13, 13, 13, 13. No artificial hold is added to the last frame: its delay is only the leftover rounding residue, usually 0. The GIF loops forever (loop count 0), so the final screen flicks straight back to the empty one. If you want the end held, that is a roadmap item. --max-idle applies BEFORE --speed. A 6 second pause with --max-idle 2 --speed 4 becomes 2/4 = 0.5s of animation. There is a limit of 1000 kept frames per run, because every kept frame is held in memory at once. A longer cast is refused with a clear message pointing at frames --every N, which samples the cast and gets under the limit. WHAT IT DRAWS, AND WHAT IT IGNORES ---------------------------------- IMPLEMENTED - Printable ASCII, 0x20 to 0x7E, in a built-in 5x7 bitmap font. The font is a 95 x 5 byte column table compiled into the binary, 475 bytes total; no font file is loaded and no system font is used. - Newline, carriage return, backspace and tab. \r returns to column 0 and subsequent text OVERWRITES the line, which is what makes recorded progress bars collapse into a single animated line instead of hundreds of lines. - Line wrapping at --cols and scrolling once output passes --rows lines. - stdout and stderr are drawn in different colours: the theme foreground for stdout and a warning colour for stderr. That is the only colour decision ScreenFlow makes. - ANSI escape sequences are PARSED so that they can be discarded cleanly: CSI sequences, OSC strings and single character escapes are recognised and dropped, including when a sequence is split across two recorded chunks. - Both themes, and any integer font scale from 1 to 8. NOT IMPLEMENTED - be clear about this before you file a bug - ANSI COLOUR IS NOT RENDERED. Colour escapes are stripped, not honoured. A red error line in the original session is drawn in the plain foreground colour unless it happened to arrive on stderr. - No other ANSI semantics either: cursor positioning, screen clears, scroll regions, alternate screen, bold, underline and reverse video are all discarded along with the escape that requested them. A full screen TUI (vim, htop, less) will therefore render as garbage. ScreenFlow is built for line oriented output: builds, test runs, deploys, scripts. - Non-ASCII is not rendered. Every non-ASCII rune becomes a single '?' cell. No UTF-8 box drawing, no emoji, no accented letters. - No video output. No MP4, no WebM. - No screen capture, no window capture, no recording of any kind. - No cursor is drawn. - No text selection, no scrollback beyond the visible --rows lines. CAST FORMAT ----------- The input is SessionForge's JSON-lines cast. Line 1 is a header object, then one array per event, then an optional footer object: {"version":1,"command":["sh","-c","..."],"width":80,"height":24, ...} [0.001460, "o", "building\n"] [1.002542, "o", "done\n"] {"exit_code":0,"duration":1.0026} The timestamp is seconds since the recording started. "o" is stdout, "e" is stderr. A fourth field of "b64" means the payload is base64, used when the recorded bytes were not valid UTF-8; ScreenFlow decodes those too. Only cast version 1 is understood. Note that the cast records PIPE output, where a line ends with a bare newline. ScreenFlow therefore treats a newline as carriage return plus line feed, which is what you saw in the terminal at the time. EXIT CODES AND ERRORS --------------------- 0 success, and for help and version 1 anything wrong: bad flags, missing file, malformed cast, no events, out of range dimensions, unwritable output Bad invocations print the message and the usage text to stderr. Errors never leave a half written GIF behind: a failed encode removes the output file. EXAMPLE ------- $ sessionforge record deploy.jsonl -- make deploy $ screenflow info deploy.jsonl events: 18 duration: 3.358s frames: 19 dimensions: 492x204 pixels animation: 3.36s (336 hundredths) at --speed 1 --max-idle 2 $ screenflow render deploy.jsonl --out deploy.gif --font-scale 2 --apply wrote deploy.gif (47.5 KiB) ROADMAP ------- - ANSI colour, bold and reverse video, rendered rather than stripped. - Cursor block, and enough cursor addressing to render simple TUIs. - True screen capture, so a live window can be recorded directly instead of only a piped session. - MP4 and WebM export for long sessions, where GIF gets far too large. - A hold on the final frame, and a title card. - UTF-8 box drawing characters in the built-in font. Part of the Techlosoft "Remote Ops Workspace" product line, alongside SessionForge, which is the tool that records the sessions ScreenFlow renders.