termcast is a small Go package that streams the screen of a chromedp
page to a terminal that has graphics. It uses the screencast of the Chrome
DevTools Protocol to get the frames, and rasterm to draw them. It works
in Kitty, iTerm2 and Sixel terminals. You start it with one call.
The module needs Go 1.25 or later. It requires chromedp v0.20.0 and
cdproto v0.157.8.
go get github.com/chromedp/termcastLoad the page first, so that the stream has a page to show. Start also starts
the browser when the context has none. Then call Flags.Start or Start with
the context. The stream stops when you call
Stop, and when the context ends.
package main
import (
"context"
"flag"
"log"
"time"
"github.com/chromedp/chromedp"
"github.com/chromedp/termcast"
)
func main() {
var tc termcast.Flags
tc.Register(flag.CommandLine)
verbose := flag.Bool("v", false, "print the protocol messages")
flag.Parse()
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
// Load the page. This call starts the browser.
if err := chromedp.Do(ctx, chromedp.Navigate("https://example.com")); err != nil {
log.Fatal(err)
}
// s is nil when the flag -visible-on-terminal is off. A nil stream is
// safe to use.
s, err := tc.Start(ctx, *verbose)
if err != nil {
log.Fatal(err)
}
log.SetOutput(s.LogWriter())
defer s.Stop()
log.Println("the page is on the screen")
if err := chromedp.Do(ctx, chromedp.Sleep(10*time.Second)); err != nil {
s.Fatal(err)
}
}Run it with the flag:
go run . -visible-on-terminalTo start the stream with no flag, call termcast.Start(ctx, opts...).
The browser sends a frame only when the page changes. The stream keeps the
latest frame. At each tick it clears the terminal and draws the latest frame,
but only when the frame is new. A page that stops changing stays on the screen.
A program can end before the browser sends the first frame. In that case Stop
takes one screenshot, so that the final frame is never empty.
The stream clears the screen with \x1b[H\x1b[2J at each redraw. It uses no
alternate screen and does not hide the cursor. A program that ends with
log.Fatal skips its deferred calls, and the terminal must not stay in a
changed state. See
the decision.
| Option | Default | Meaning |
|---|---|---|
WithFPS(fps) |
4 | The most frames per second that the stream draws |
WithQuality(q) |
80 | The JPEG quality of the frames, from 0 to 100 |
WithMaxSize(w, h) |
1024 by 768 | The largest size of a frame in pixels |
WithOutput(w) |
os.Stdout |
The writer that receives the frames |
WithLogOutput(w) |
os.Stderr |
The writer that receives the held log lines |
WithElement(sel) |
the whole page | Draws only the element that the selector finds |
WithEncoder(e) |
Kitty, iTerm2 or Sixel | The rasterm.Encoder that draws a frame |
Flags registers two flags: -visible-on-terminal, which turns the stream on,
and -terminal-fps, which sets the frame rate and defaults to 4.
The chromedp/examples repository has 34 programs that use termcast. Most of them take the flags -visible-on-terminal and -terminal-fps with Flags. The program termcast calls Start directly. It plays an animated SVG and streams it to the terminal. These programs use the package: click, console, cookie, dialogs, download_file, download_image, dragdrop, emulate, eval, eventsiter, exposefunc, extension, fast, forecast, frames, geoip, headers, intercept, keys, latlon, logic, pdf, pdfoptions, proxy, remote, screenshot, selectors, structeval, submit, subtree, termcast, text, upload and visible.
WithElement takes a chromedp selector, such as chromedp.CSS("#chart"), and
draws only the first element that it matches.
s, err := termcast.Start(ctx, termcast.WithElement(chromedp.CSS("#chart")))The stream looks up the element again at each frame, so an element that moves
or changes its size stays in view. It crops the frame to the border box of the
element. The browser sends the viewport, so the stream shows the part of the
element that is in the viewport. Use chromedp.ScrollIntoView first when the
element is lower on the page. While the page has no such element, the stream
keeps what is on the screen.
The terminal must support one of these graphics protocols:
- Kitty and Ghostty
- iTerm2, WezTerm and mintty
- Sixel
rasterm detects the protocol from the environment (TERM, TERM_PROGRAM,
LC_TERMINAL and the variables of Kitty) and, for Sixel, with a query to the
terminal. The variable TERM_GRAPHICS forces the protocol to kitty, iterm
or sixel, and the value none turns graphics off. Start returns
ErrNoGraphics when no protocol is available. Available reports the same
thing before you start.
A log line that the program prints draws over the frames, and the next redraw
erases it. Stream.LogWriter returns a writer that holds the lines while the
stream runs. When the stream stops, it draws a final frame and then prints the
lines to the log output. Use log.SetOutput(s.LogWriter()). After Stop, the
writer passes each line on at once.
Stream.Fatal stops the stream, prints the held lines, and then calls
log.Fatal. Use it where a program calls log.Fatal, because log.Fatal ends
the program before a deferred Stop runs.
Every method of *Stream is safe on a nil pointer. On a nil stream, Stop does
nothing, Fatal calls log.Fatal and LogWriter returns os.Stderr.
The flag -v of a program prints the protocol messages. They draw over the
frames, so the two do not work together. Flags.Start returns ErrVerbose
when both are on.
The tests start Chrome and need no terminal. They use a fake encoder and a local
page that animates. See AGENTS.md for the commands.
GOWORK=off go test -race ./...See CONTRIBUTING.md. Ask questions in the
GitHub Discussions of chromedp, and report bugs in the
issues of chromedp. The documents of this repository are AGENTS.md,
CLAUDE.md, CONTRIBUTING.md and docs/decisions/README.md.