CLI screenshots

click-extra produces colored terminal output, and inside this Sphinx documentation the click:run directive executes each CLI and renders its real output at build time, so these pages need no screenshots. A README on GitHub or PyPI, a slide, a social post or a page of your own cannot run code, and those surfaces need a capture instead. click-extra ships the command that produces one, as an image or as HTML:

A help screen captioned, numbered and left see-through on a gradient backdrop

Every part of that window answers to an option: the terminal it is drawn as, the chrome under its colors, the backdrop, the caption, the line numbers, the transparency, the border, the shadow, the corner radius and the room around it. The block that produced it sits further down this page, and rewrites the image on every build.

The screenshot command

click-extra screenshot runs a CLI, captures its colored output and writes it out. Point it at any command, with -- separating your CLI’s own options from the ones above:

$ click-extra screenshot --output cli-help.svg -- my-cli --help

Two formats, two surfaces

The extension of --output picks what gets written, and the two are not interchangeable:

Format

Text is

Goes where

.svg

a picture

a surface that strips inline HTML: a README on GitHub or PyPI

.html

selectable, searchable, copyable

a page you own: your site, a blog post, a slide deck

So the choice is really made for you. GitHub and PyPI render an image and drop inline styling, which leaves a README no option but SVG. Everywhere you control the markup, HTML is the better artifact: a reader can select a flag out of the help screen and paste it into their terminal, and search finds it.

Neither format needs an optional dependency. Both read the same click_extra.styling.split_ansi() stream: SVG is laid out on a character grid by click_extra.screenshot.render_svg(), HTML is inline-styled markup from click_extra.styling.ansi_to_html().

$ click-extra screenshot --output cli-help.html -- my-cli --help

That writes a standalone document. Add --fragment to get the bare <pre> instead, styled inline so it needs no stylesheet from the page you paste it into.

$ click-extra screenshot --help
Usage: click-extra screenshot [OPTIONS] COMMAND_LINE...

  Capture a command's colored output and write it as an image or HTML.

  Runs COMMAND_LINE with colors forced on and its terminal width pinned, then
  writes the captured output where --output points. Its extension picks the
  format:

    .svg  a picture of a terminal window, for a surface that strips inline
    HTML. A README on GitHub or PyPI has no other option.

    .html selectable, searchable, copy-pasteable text, for a page you own.

  Put -- before the command line so its own options are not mistaken for this
  command's:

    click-extra screenshot --output shot.svg -- my-cli --help

  COMMAND_LINE is anything the shell can run, Click CLI or not. A Click CLI not
  built on Click Extra prints its help uncolored, so --wrap routes it through
  the wrap subcommand first and captures the colored rendering.

  An SVG starts each run of text on its own column, so it renders correctly
  outside a web browser, where a file manager, a git client or a thumbnailer
  would otherwise slide the columns out of place.

  Neither format needs an optional dependency.

Options:
  --output FILE                 Path of the file to write. Its extension picks
                                the format: .svg for an image, .html for
                                selectable text.  [required]
  --columns INTEGER|auto        Terminal width, in characters, the command wraps
                                its output to and the image is laid out at. Pass
                                auto to pin neither: the command finds its own
                                width, and the image is laid out at the longest
                                line it printed, so nothing folds inside the
                                picture.  [default: 80]
  --background [dark|light]     Terminal chrome the capture is drawn on, and the
                                palette its colors resolve against. Match it to
                                the theme the captured CLI renders with: a
                                light-background theme washes out on the dark
                                default.  [default: dark]
  --preset [linux|macos|plain|windows]
                                Terminal to draw the capture as: its window
                                decorations, palette, font and prompt sigil.
                                Anything stated alongside wins over it. Left
                                out, the capture keeps the renderer's own
                                neutral window.
  --border TEXT                 Color of the frame drawn around the terminal
                                window, as CSS names it. Pass none to draw no
                                frame. Defaults to the one the chrome can show.
  --border-width INTEGER RANGE  Thickness of that frame, in pixels.  [default:
                                1; x>=0]
  --radius INTEGER RANGE        How round the window's corners are, in pixels.
                                Zero squares them. Defaults to 8, or to the
                                rounding --preset terminal draws.  [x>=0]
  --backdrop TEXT               Color filling the image behind the window,
                                margin included, as CSS names it. Left
                                transparent by default, so the page shows
                                through.  [default: none]
  --shadow TEXT                 Color of the drop shadow lifting the window off
                                the page, as CSS names it. Pass none to draw no
                                shadow. Defaults to the one the chrome calls
                                for.
  --margin INTEGER RANGE        Transparent pixels left around the window, on
                                all four sides. The room the drop shadow falls
                                into, so a capture drawing one wants some.
                                [default: 48; x>=0]
  --padding INTEGER RANGE       Pixels added inside the window, around the
                                captured text, on top of the few the renderer
                                adds on its own.  [default: 8; x>=0]
  --opacity FLOAT RANGE         How solid the window's body is. Under 1 it turns
                                see-through, the way a terminal set to
                                transparency does: whatever the capture sits on
                                shows through it, while its text, frame and
                                title bar keep their own paint.  [default: 1.0;
                                0<=x<=1]
  --watermark TEXT              Credit line drawn in the image's bottom-right
                                corner, in the margin around the window. Pass an
                                empty string to draw none, or your own text to
                                credit your project instead.  [default:
                                generated with click-extra 9.0.0]
  --watermark-color TEXT        Color that credit line is drawn in, as CSS names
                                it, alpha included. Defaults to a neutral gray:
                                the line sits in the transparent margin, so it
                                answers to the page embedding the image rather
                                than to the chrome.
  --prompt TEXT                 Command line to display above the output, when
                                it differs from the one that is run. Pass an
                                empty string to draw no prompt at all. Defaults
                                to the command line itself.
  --head INTEGER RANGE          Keep only the first N lines of output.  [x>=1]
  --tail INTEGER RANGE          Keep only the last N lines of output.  [x>=1]
  --truncation TEXT             Line standing in for what --head or --tail cut
                                away.  [default: [...]]
  --merge-stderr                Fold the command's stderr into the capture, for
                                a CLI printing its help there. Off by default,
                                which is what keeps a wrapper's build chatter
                                out of the image.
  --line-numbers                Number the captured lines in a gutter, the way
                                Pygments does inline. The prompt counts as line
                                1, being the invocation everything under it came
                                from.
  --title TEXT                  Caption drawn in an SVG's window chrome, or an
                                HTML document's title.  [default: ""]
  --fragment                    For HTML, emit the bare block instead of a
                                standalone document, to paste into a page that
                                has its own.
  --wrap                        Route COMMAND_LINE through the wrap subcommand,
                                so a Click CLI that is not built on Click Extra
                                is captured with its colors. Only works on a
                                target wrap can resolve.
  --timeout FLOAT RANGE         Seconds before the command is killed. Waits
                                forever by default.  [x>0]
  --time / --no-time            Measure and print elapsed execution time.
                                [default: no-time]
  --config CONFIG_PATH          Location of the configuration file. Supports
                                local path with glob patterns or remote URL.
                                [default: ~/.config/click-extra/{*.toml,*.yaml,*
                                .yml,*.json,*.json5,*.jsonc,*.hjson,*.ini,*.xml,
                                *.plist,*.sqlite,*.sqlite3,*.conf,pyproject.toml
                                }]
  --no-config                   Ignore all configuration files and only use
                                command line parameters and environment
                                variables.
  --validate-config FILE        Validate the configuration file and exit.
  --export-config FORMAT        Export the configuration in the selected format
                                to <stdout>, then exit.
  --accessible                  Accessibility mode: disable colors and render
                                tables in a borderless, screen-reader-friendly
                                format.
  --color [auto|always|never]   Colorize the output. A bare --color is the same
                                as --color=always.  [default: auto]
  --no-color                    Disable colorization (alias of --color=never).
  --progress / --no-progress    Show progress indicators during long operations.
                                Disabled for non-interactive output (pipes, dumb
                                terminals, CI) and by --accessible.  [default:
                                progress]
  --theme [auto|dark|dracula|light|manpage|monokai|nord|solarized_dark]
                                Color theme used for help screens.  [default:
                                dark]
  --params                      Show all CLI parameters, their provenance,
                                defaults and value, then exit.
  --table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
                                Rendering style of tables.  [default: rounded-
                                outline]
  --verbosity LEVEL             Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
                                [default: WARNING]
  -v, --verbose                 Increase the default WARNING verbosity by one
                                level for each additional repetition of the
                                option.  [default: 0]
  -q, --quiet                   Decrease the default WARNING verbosity by one
                                level for each additional repetition of the
                                option.  [default: 0]
  --tree                        Show the tree of nested subcommands and exit.
  --man                         Read the command's manual page and exit.
  --help-format [carapace|json|json-full|man|markdown|markdown-full]
                                Render the command in the given format and exit.
  --version                     Show the version and exit.
  -h, --help                    Show this message and exit.

Three things it settles that a general-purpose capture tool leaves to you:

  • Colors, which a command strips on its own the moment its output is a pipe rather than a terminal. The capture runs under click_extra.color.forced_color(), setting the FORCE_COLOR lever every mainstream color system obeys and clearing any NO_COLOR the environment carries.

  • Width, where --columns pins what the command wraps to and what the image is drawn at. Let those two disagree and the rendered lines overrun the image.

  • stderr, which stays out of the capture unless --merge-stderr asks for it. That is what keeps a wrapper’s build chatter out of the picture with no shell redirection to remember.

Every SVG it writes also gives each column its own offset, rather than padding a line with spaces and leaning on textLength to hold the rest of it in place. Written the other way, a column only lands where it belongs if the reader’s renderer both honors textLength and resolves the font the file names. A web browser does both; librsvg (and through it rsvg-convert and ImageMagick) ignores textLength outright, and a file manager, a git client or a thumbnailer commonly falls back to a proportional font. Either way a gutter paid for in glyphs collapses and the columns slide onto each other. click_extra.screenshot.column_segments() documents where the cut falls.

Three smaller things travel with that, each one a way a capture used to depend on the reader being a browser:

  • Nothing is fetched. The text is set in the first family of click_extra.screenshot.CAPTURE_FONT_STACK the reader already has, so a capture renders the same offline and on a page that forbids third-party requests.

  • The encoding is declared outright. A standalone SVG carries no HTTP header to state it, and a reader that assumes the platform’s own turns every multi-byte character into mojibake, a full block becoming â.

  • Nothing important is left to a stylesheet or a filter. The terminal text names its face, size and color as attributes as well as in the stylesheet, because a renderer that ignores a <style> block would otherwise fall back to a proportional face in default black. And the window’s drop shadow is cast by a rectangle of its own rather than by a filter on the window: an element whose filter a renderer cannot resolve is an element in error, which the spec answers by not drawing it at all, so a filter hung on the window would take the background and the frame down with it. macOS Finder’s thumbnailer and ImageMagick both do exactly that.

Capturing a CLI that is not yours

screenshot runs whatever the shell runs, Click CLI or not, so git --help and docker ps capture as readily as your own tool. What it captures is what the command prints: a Click CLI that is not built on Click Extra prints its help uncolored, and that is what lands in the file.

To picture it with colors, run it through wrap first, which patches Click’s help rendering without touching the target’s code. --wrap does that for you:

$ click-extra screenshot --output flask-help.svg --wrap -- flask --help

which is the shorthand for composing the two commands by hand:

$ click-extra screenshot --output flask-help.svg --prompt "click-extra wrap -- flask --help" -- click-extra wrap -- flask --help

The prompt drawn above the output is the wrap invocation, not the bare command, because that is what reproduces the colored screen: running flask --help on its own gives back the plain one.

Note

The two commands stay separate because they answer different questions. wrap decides how a CLI renders, and only reaches Click commands it can import: a module:function, a project directory, a .py file, an entry point. screenshot decides where the output goes, and reaches anything executable. Composing them covers the overlap; merging them would cost every CLI that wrap cannot import, which is most of what a README wants to show.

That separation is also why --wrap insists on the installed click-extra command rather than falling back to python -m click_extra: the two resolve a target differently, so the fallback would quietly capture a different CLI.

Note

HTML carries two limitations SVG does not. An OSC 8 hyperlink loses its URL and keeps its visible text, and the eight base ANSI colors render as their CSS names, so the browser’s palette decides their exact shade rather than the terminal’s. Neither shows up on a help screen, which is why the format is worth having anyway.

Width

--columns pins the width twice over: the command wraps its output to it, and the image is laid out at the same one. They have to agree, or the rendered lines overrun the picture.

--columns auto pins neither. The command finds its own width (the terminal it runs in, or Click’s own 80 through a pipe), and the image is laid out at the longest line that came back:

$ click-extra screenshot --output params.svg --columns auto -- my-cli --params

Reach for it when the output holds a line the command does not wrap on its own, which a pinned width folds mid-word: a long invocation drawn as the prompt, a wide table, a machine-readable dump. The trade-off is that the picture stops being a fixed-width terminal, so captures meant to sit side by side at the same width should name that width instead.

Wide glyphs and writing systems

A terminal cell is not a character. A Chinese ideograph, a Hangul syllable, a fullwidth Latin letter and most emoji are each drawn two cells wide, while a combining accent is drawn in none at all. A capture measures its runs in cells, through click_extra.screenshot.cell_width(), which is the same wcwidth measurement click-extra already uses to align tables and the command tree.

The CLI below pads with that measurement, so every name lands on column 12 whatever script precedes it:

from click_extra import command, echo
from click_extra.screenshot import cell_width

FRUITS = (
    ("苹果", "apple"),
    ("バナナ", "banana"),
    ("체리", "cherry"),
    ("KIWI", "kiwi"),
    ("🍎🍌🍒", "basket"),
    ("┌──┬──┐", "crate"),
    ("مشمش", "apricot"),
)

@command
def market():
    """List each fruit beside its name, aligned on a fixed column."""
    for glyphs, name in FRUITS:
        echo(f"{glyphs}{' ' * (12 - cell_width(glyphs))}{name}")
$ market
苹果        apple
バナナ      banana
체리        cherry
KIWI    kiwi
🍎🍌🍒      basket
┌──┬──┐     crate
مشمش        apricot

Size a run by its character count instead and the picture drifts: the seven lines above would land on four different columns, the wide scripts drawn at half their width and stacking on each other. That is rich#2742, open upstream since 2023 and one of the reasons this renderer is click-extra’s own. The upstream page collects the rest.

Note

Right-to-left scripts are the case cell arithmetic cannot settle. Arabic and Hebrew are reordered by whoever draws them, and the cursive ones are shaped: a letter’s form depends on the letters it joins. A run carrying any of them is therefore left to size itself, rather than pinned to an exact width that would pay for the difference in letter spacing and pull the word apart at its joins. The run still starts on its own column, so the grid around it holds; only its own width floats.

Box-drawing and block characters are not letters but tiles: a table’s rule, a tree’s elbow and a gradient’s bar are drawn by butting them edge to edge. They are emitted in short groups, each landing on a stated offset, so a font drawing them a fraction of a pixel off the grid cannot accumulate that error across a rule and leave the table’s corners missing their own border. What remains is whether two adjacent tiles join cleanly, which is the font’s business and no renderer’s: that one is rich#2536, where the answer upstream is that it would take replacing the characters with drawn shapes. click-extra does not do that either.

Light and dark chrome

A capture freezes the colors of the run it pictures, so the window it is drawn in has to answer to the theme that run rendered for. --background light swaps the dark chrome for white, along with the ANSI palette the capture’s own colors resolve against:

$ click-extra screenshot --output light-help.svg --background light -- my-cli --theme light --help

Both halves are needed, and they are not the same half: --theme light is what the CLI renders with, --background light is what the image is drawn on. Pass one without the other and you get the washed-out screen the theme gallery warns about, in one direction or the other.

The prompt line follows the chrome on its own. It is the one line a capture draws itself rather than collects, so on white it would otherwise land in the dark theme’s near-white invoked_command style and vanish.

So does a CLI that asks. A capture states its chrome to the command the way a terminal would, through the CLITHEME and COLORFGBG variables background detection reads, on top of the width it pins and the colors it forces. A CLI passing --theme auto then renders for the window it lands in, and needs telling only once:

$ click-extra screenshot --output light-help.svg --background light -- my-cli --theme auto --help

Here is that at work, on one of click-extra’s own help screens. The two images below were shot from the same command line, --background apart, and the CLI picked its palette from the terminal each capture claimed to be:

A help screen under --theme auto, drawn on dark chrome

$ click-extra screenshot --output docs/assets/auto-theme-dark-screen.svg --prompt "click-extra --theme auto themes --help" -- uv run --frozen -- click-extra --theme auto themes --help

The same command under --theme auto, drawn on light chrome

$ click-extra screenshot --output docs/assets/auto-theme-light-screen.svg --background light --prompt "click-extra --theme auto themes --help" -- uv run --frozen -- click-extra --theme auto themes --help

auto is not the default, and deliberately: a CLI that never asks for it keeps rendering exactly as it does everywhere else, which is why --theme dark and --theme light are spelled out below rather than left to detection.

Here is one help screen taken both ways, with the CLI’s theme and the image’s chrome moving together:

The pantry help screen under the dark theme, drawn on dark chrome

The same screen under the light theme, drawn on light chrome

Both blocks hide their results, which is the exception to what :screenshot: is usually for: a results block on this page is colored by the site’s stylesheet and follows the reader’s own theme, so the one thing being compared here is the one thing it cannot show.

The window itself

A capture is drawn as a terminal window: a rounded rectangle in the chrome’s background color, framed with a one-pixel border and lifted off the page by a drop shadow. Every part of that is an option:

$ click-extra screenshot --output shot.svg --title "my-cli --help" --backdrop "#1f6feb" --radius 0 --border-width 2 --margin 28 -- my-cli --help

Option

Takes

Default

Draws

--border

CSS color

the chrome’s

The frame around the window. none leaves it bare.

--border-width

pixels

1

How thick that frame is.

--radius

pixels

8, or the preset’s

How round the window’s corners are. 0 squares them.

--shadow

CSS color

the chrome’s

The drop shadow under the window. none leaves it flat.

--backdrop

CSS color

none

A page behind the window, margin included.

--margin

pixels

48

Transparent space around the window.

--opacity

0 to 1

1

How solid the window’s body is. Under 1 it lets what is behind it through.

--watermark

text

the click-extra credit

A credit line in the image’s bottom-right corner. Empty draws none.

--watermark-color

CSS color

a neutral gray

The ink that line is drawn in.

--padding

pixels

8

Space inside it, on top of the renderer’s own.

--line-numbers

flag

off

A dim gutter numbering the captured lines.

--title

text

none

A caption centered in the window’s title bar.

Two of them carry a default that is not a fixed value but the chrome’s own. That is the whole reason they are not constants: a renderer frames its window in a translucent white, and a light capture wearing it is a white window on a white page, its edge left for the reader to infer.

The rest answer to what the capture is for. A shadow needs --margin to fall into, since a filter paints outside the shape it is applied to and the image’s own box cuts whatever lands past it. --backdrop fills that same space instead of leaving the page through, which is what turns a capture into a self-contained picture for a slide or a social card. --radius 0 drops the desktop-window look, for a capture meant to read as a plain block of output.

Both formats take the same set of options (click_extra.screenshot.render_svg() draws them, click_extra.screenshot.render_html() translates them), so an HTML capture lands them on the block’s border, border-radius, box-shadow, margin, padding and the page’s background.

Tip

A shadow is an SVG filter. A renderer that skips filters, and a few outside the browser do, still draws the border, so the window keeps an edge either way.

Gradients

--backdrop also takes a CSS gradient, which is what turns a capture into a picture that carries its own page:

$ click-extra screenshot --output card.svg --backdrop "linear-gradient(135deg, #667eea, #764ba2)" -- my-cli --help

An SVG fill has no syntax for that, so the CSS is read and re-emitted as the paint server SVG does understand (click_extra.screenshot.gradient_svg()), placed in user space rather than approximated: the gradient line runs through the image’s center at the angle asked for, as long as the image measures along it, and a radial one reaches the farthest corner. Understood are linear-gradient, opening with an angle (135deg) or a side keyword (to bottom right), and radial-gradient, both followed by two or more color stops, each pinnable at a percentage (#667eea 30%). Anything else is taken for the plain color it presumably is, and HTML captures pass the value through to CSS untouched either way.

Transparency

--opacity thins the window’s body out, the way a terminal set to transparency does. Below 1, whatever the capture sits on comes through it, while its text, frame and title bar keep their own paint:

$ click-extra screenshot --output glass.svg --opacity 0.7 --backdrop "linear-gradient(135deg, #667eea, #764ba2)" -- my-cli --help

Over a backdrop it reads as frosted glass, the gradient tinting the terminal instead of stopping at its edge. With no backdrop it is the page that comes through, which is what a capture dropped on a surface you do not control wants: an image holding no opinion on the color behind it. What limits the value is legibility, and that is the reader’s screen deciding, not the capture: a body much under half solid hands the text whatever contrast the backdrop happens to have.

An HTML capture thins the block’s background color with CSS color-mix() rather than a rectangle’s fill, so the page it is pasted into shows through the same way.

Line numbers

--line-numbers draws each line’s number in a dim gutter, the way Pygments does inline. Line 1 is the prompt, the invocation everything under it came from:

$ click-extra screenshot --output numbered.svg --line-numbers -- my-cli --help

The numbers land in the terminal text rather than in a column of their own, which is the same trade Pygments makes: every renderer places them for free, and a reader copying an HTML capture copies them too.

It also means the gutter spends columns the command already used: a screen wrapped at 80 comes back a few characters too wide, and folds. Pair the flag with --columns auto, or with a width that leaves room for the gutter, so the image grows instead of the lines breaking.

The credit line

Every capture the command writes carries a credit in its bottom-right corner, in the margin around the window. Here is one, reading generated with pantry 1.4.2 because that is what shot it:

A help screen credited in the margin under its bottom-right corner

Left alone, the line names click-extra and the release that drew the image, which is what a capture needs once it has travelled: on a slide, in a README or on a social card it is a long way from the page that explains where it came from. --watermark replaces the text, and an empty string draws none at all:

$ click-extra screenshot --output shot.svg --watermark "pantry 1.4.2 · example.com" -- my-cli --help
$ click-extra screenshot --output shot.svg --watermark "" -- my-cli --help

Crediting your own project rather than the tool that drew it is the expected case, not an exception. Set it once in your configuration file and every capture carries it.

The mark is the one thing in a capture drawn outside the window, so it is also the one paint that cannot answer to the chrome: the margin is transparent, and what sits behind it is a page this command never sees. Hence a neutral gray, which reads on a white README and a dark one alike, and --watermark-color for a capture whose backdrop it has to sit on.

A capture written by a click:run block carries none of this by default. That image is regenerated and committed on every documentation build, so a release number in it would rewrite every asset the day the release changes, and the page around it already says what drew it. :screenshot-watermark:, or the click_extra_screenshot_watermark conf.py value, turns it on for a project that wants it anyway.

All of it at once

Here is the pantry screen again, on a gradient, captioned, numbered, rounded, given room to breathe, and left see-through enough for the gradient to tint it. The second tab is the block that wrote it, options and assertions included:

The pantry help screen, captioned and numbered on a gradient backdrop

```{click:run}
:screenshot: styled-window-screen
:screenshot-columns: auto
:screenshot-title: 🍎 pantry --help
:screenshot-backdrop: 'linear-gradient(135deg, #667eea, #764ba2)'
:screenshot-line-numbers:
:screenshot-opacity: 0.75
:screenshot-radius: 12
:screenshot-padding: 24
:hide-results:
result = invoke(pantry, args=["--help"])
assert result.exit_code == 0
assert "--crates" in result.stdout
```

A caption takes any text a terminal can draw, emoji included. :hide-results: is this page’s own choice, the window being the thing on show here: leave it out and the block renders its live text below the fence as well.

Terminal presets

A capture is a picture of a terminal, and terminals do not look alike. --preset draws one as a named desktop’s:

$ click-extra screenshot --output shot.svg --preset windows -- my-cli --help

Each preset carries the four things that make a terminal recognizable: its window decorations, the palette its colors resolve against, the font it ships with, and the sigil its usual shell prompts with. Anything stated alongside wins, so --preset windows --radius 8 rounds the corners Windows squares.

A preset also paints the strip its buttons and caption sit in, a shade off the terminal’s own background, because that strip belongs to the desktop rather than to the terminal. Which is what makes the fourth preset the odd one out: plain mimics no desktop, wears no buttons, and drops the strip entirely unless a --title gives it something to hold.

The pantry screen drawn as an Apple Terminal window

Round buttons on the left, Apple’s Pro and Basic palettes, SF Mono, and a $ prompt.

The pantry screen drawn as a Windows Terminal window

Minimize, maximize and close on the right, square corners, the Campbell and One Half Light schemes, Cascadia Code, and a PS C:\> prompt.

The pantry screen drawn as a GNOME Terminal window

A single close button, the Tango palette GNOME Terminal ships, Ubuntu Mono, and a $ prompt.

The pantry screen drawn as a plain block of output

No buttons, no rounded corners, no title bar: for a capture that has to read as a block of output rather than as a window, on a slide or in a paper.

Caution

A preset’s palette is the scheme its terminal ships with, not the one your reader has configured theirs to. It also decides how the captured CLI’s own colors land: a screen rendered for a dark theme on a light preset washes out exactly as the chrome section describes, since the two halves have to agree either way.

Stating a default once

screenshot is itself a Click Extra CLI, so every option above is also a configuration key. A project drawing all of its captures the same way says so once, in the pyproject.toml click-extra finds by walking up from the working directory:

[tool.click-extra.screenshot]
preset = "macos"
margin = 64
opacity = 0.85
watermark = "pantry 1.4.2 · example.com"

Any dedicated configuration file does the same under a [click-extra.screenshot] table. A flag on the command line still wins over both, following the usual precedence, so a single capture can break the house style without editing anything.

Inside a Sphinx documentation the captures are written by click:run blocks rather than by the command, so the same wish is a conf.py value:

click_extra_screenshot_preset = "macos"

It covers every block whose :screenshot: names no preset of its own, which leaves :screenshot-preset: for the pages that mean to depart from it. A project keeping its settings in one place can read the value back out of its pyproject.toml, conf.py being Python:

import tomllib
from pathlib import Path

pyproject = tomllib.loads(Path("../pyproject.toml").read_text(encoding="utf-8"))
click_extra_screenshot_preset = pyproject["tool"]["click-extra"]["screenshot"]["preset"]

The captures on this page

Here is the command turned on click-extra itself, whose bundled CLI doubles as a live demo of the rendering features. Each image below was produced by the line printed under it, run from a checkout.

gradient puts 24-bit ramps next to their 256-color quantized equivalents, so the stepping the smaller palette introduces is there to see:

Color gradients rendered in 24-bit and quantized to 256 colors

$ click-extra screenshot --output docs/assets/color-gradient-screen.svg --prompt "click-extra gradient" -- uv run --frozen -- click-extra gradient

styles crosses every color with every text style. Its table wants far more than 80 columns, so the capture is taken at the width it actually needs:

Every color rendered under each text style

$ click-extra screenshot --output docs/assets/text-styles-screen.svg --columns 160 --head 14 --prompt "click-extra styles" -- uv run --frozen -- click-extra styles

themes renders a sample help screen under each built-in palette, and this capture keeps the first two of them, which is what --theme does to a screen:

The same CLI help screen captured under the dark and dracula themes

$ click-extra screenshot --output docs/assets/theme-gallery-screen.svg --head 34 --prompt "click-extra themes" -- uv run --frozen -- click-extra themes

--prompt is what makes each image show the bare click-extra a reader would type, while uv run --frozen -- is what actually ran: the plumbing that reaches a checkout’s copy of the CLI is not worth picturing. --head bounds the two long ones, and the [...] marker admits that the rest was cut.

The before/after pair opening the readme takes the other route. Those two screens already exist as live click:run blocks in the tutorial, so rather than shoot them again, the blocks maintain them: a :screenshot: option writes each image on every documentation build, which is what keeps the readme’s front page in step with the code nobody thought to re-check.

Reach for the command when the CLI you want to picture has no live block, and for the directive when it does.

Whichever route, a full click-extra help screen carries --table-format, whose choice list is a single 463-character line. Click never wraps an option’s own term, so the renderer folds it across six rows, exactly as a terminal would.

Keeping a capture honest

Once committed, an image goes stale the first time the CLI’s help changes, and nothing about it complains. tests/test_screenshots.py reads the SVG back, rebuilds the terminal text from the glyph coordinates, and compares it to what the command prints today, failing on the first line that diverged. Re-running the command above is then all it takes to refresh the picture.

GitHub integration

A README can track the reader’s theme. Capture the same screen twice, once per chrome, with the CLI’s own theme moving along:

$ click-extra screenshot --output docs/assets/help-dark.svg -- my-cli --theme dark --help
$ click-extra screenshot --output docs/assets/help-light.svg --background light -- my-cli --theme light --help

Then hand both to a <picture> element, which GitHub renders in a README and which picks between them on prefers-color-scheme:

<picture>
 <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/me/my-cli/main/docs/assets/help-dark.svg"/>
 <img alt="my-cli help screen" src="https://raw.githubusercontent.com/me/my-cli/main/docs/assets/help-light.svg"/>
</picture>

The <img> is not a spare tyre: it is what every surface without the switch shows, PyPI and most editors’ previews included, so it holds the capture that reads on the light background those default to. The <source> is the one a reader in dark mode gets instead.

Both URLs are absolute on purpose. A README is read on PyPI and in a hundred forks of the page, none of which resolve a repository-relative path, which is why every capture in this project’s own README is addressed through raw.githubusercontent.com.

Note

The switch keys on the browser’s color scheme, not on the theme toggle of a documentation site, so it belongs to a README rather than to these pages: Furo’s own switch would leave it unmoved.

click_extra.screenshot API

Width a capture is taken and rendered at, see AUTO_COLUMNS.

click_extra.screenshot.TColumns: TypeAlias = int | typing.Literal['auto']

Width a capture is taken and rendered at, see AUTO_COLUMNS.

class click_extra.screenshot.CaptureFormat(*values)[source]

Bases: Enum

Document formats a capture can be rendered to.

The value doubles as the file extension format_from_path() matches on.

HTML = 'html'

Selectable, searchable text in a self-contained <pre>.

Built on ansi_to_html(), so it needs no extra.

SVG = 'svg'

A picture of a terminal window, for a surface that strips inline HTML.

Laid out on a character grid by render_svg().

class click_extra.screenshot.CaptureBackground(*values)[source]

Bases: Enum

Terminal chrome a capture is drawn on.

A capture freezes the colors of the run it pictures, so the chrome has to answer to the palette that run was colored for. Neither direction survives the other: a screen colored for a dark terminal is unreadable on white, and click-extra’s own light and manpage themes wash out on the dark chrome a renderer defaults to.

The value doubles as the --background choice the CLI offers.

DARK = 'dark'

What a terminal, and this package’s default theme, usually look like.

LIGHT = 'light'

For a CLI rendered with a light-background theme.

click_extra.screenshot.DEFAULT_PRESET = ('No terminal at all', ((), ''), 0, '$', "'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace", ('#292929', '#c5c8c6', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#292929'), ('#ffffff', '#000000', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#ffffff'))

Terminal a capture with no --preset is drawn as.

plain mimics no desktop, which is what a capture wearing no decoration should resolve its colors against. Naming it here is what keeps the two formats looking like the same terminal, and keeps one catalog answering for every palette a capture can use: without it the default colors would be a second set of literals free to drift from the one the presets publish.

click_extra.screenshot.CAPTURE_PALETTES: dict[CaptureBackground, TerminalPalette] = {CaptureBackground.DARK: ('#292929', '#c5c8c6', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#292929'), CaptureBackground.LIGHT: ('#ffffff', '#000000', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#ffffff')}

Colors each chrome resolves a capture’s ANSI codes against.

A palette carries the 16 ANSI colors alongside the background and foreground, which is the other half of the job: a CLI naming blue leaves the shade to whoever draws it, and the one that reads on white is not the one that reads on #292929.

click_extra.screenshot.CAPTURE_BACKGROUND = '#292929'

Background a dark capture is drawn on.

Stating it is not optional: a help screen colored for a dark terminal is unreadable on a page that defaults to white.

click_extra.screenshot.CAPTURE_FOREGROUND = '#c5c8c6'

Color of the text a dark capture leaves unstyled. See CAPTURE_BACKGROUND.

click_extra.screenshot.LIGHT_CAPTURE_BACKGROUND = '#ffffff'

Background a light capture is drawn on.

See CAPTURE_BACKGROUND: an SVG and an HTML capture of the same run have to look like the same terminal.

click_extra.screenshot.LIGHT_CAPTURE_FOREGROUND = '#000000'

Color of the text a light capture leaves unstyled.

See LIGHT_CAPTURE_BACKGROUND.

click_extra.screenshot.PROMPT_THEMES: dict[CaptureBackground, HelpTheme | None] = {CaptureBackground.DARK: None, CaptureBackground.LIGHT: HelpTheme(invoked_command=Style(fg='black', bold), command_help=<function identity>, heading=Style(fg='magenta', underline), constraint=Style(fg='magenta'), section_help=<function identity>, col1=<function identity>, col2=<function identity>, alias=Style(fg='blue', bold), alias_secondary=Style(fg='blue', bold, dim), epilog=<function identity>, critical=Style(fg='red', bold), error=Style(fg='red'), warning=Style(fg='magenta'), info=<function identity>, debug=Style(fg='blue', dim), option=Style(fg='blue', bold), subcommand=Style(fg='blue', bold), choice=Style(fg='magenta', bold), metavar=Style(fg='blue', dim, italic), bracket=Style(dim), envvar=Style(fg='magenta', dim), default=Style(fg='green', dim, italic), range_label=Style(fg='blue', dim), required=Style(fg='red', dim), argument=Style(fg='blue', italic), deprecated=Style(fg='red'), search=Style(fg='green'), success=Style(fg='green'), cross_ref_highlight=True, subheading=Style(fg='blue', dim))}

Theme the prompt line is drawn with, per chrome.

The captured output arrives already colored by the CLI that produced it, under whatever theme that run was told to use. The prompt is the one line this process draws itself, so it is the one that would otherwise land on white chrome in the dark default’s near-white invoked_command style, invisible.

None keeps whatever theme the invocation already runs under. So does a missing entry: the mapping is read through dict.get(), and BUILTIN_THEMES is empty when a trimmed install drops themes.toml.

click_extra.screenshot.NO_PAINT = 'none'

Border or shadow value asking for none to be drawn.

SVG’s own keyword for an absent paint, so it reaches the stroke attribute unchanged, and CSS’s for an absent shadow.

click_extra.screenshot.OPAQUE = 1.0

Opacity of a window showing nothing of what sits behind it.

Anything under it is what a terminal calls transparency: the backdrop, or the page embedding the capture, comes through the window’s body while its text, frame and title bar stay as they are. 0.0 leaves the text alone on the page.

click_extra.screenshot.CAPTURE_BORDERS: dict[CaptureBackground, str] = {CaptureBackground.DARK: 'rgba(255,255,255,0.35)', CaptureBackground.LIGHT: 'rgba(0,0,0,0.25)'}

Color the window frame is drawn in, per chrome.

The dark entry is a translucent white that reads against #292929 and against nothing else: a light capture framed with it is a white window on a white page, the shape of the terminal only guessable from its text. Each chrome names a frame its own background can show.

click_extra.screenshot.CAPTURE_SHADOWS: dict[CaptureBackground, str] = {CaptureBackground.DARK: 'rgba(0,0,0,0.5)', CaptureBackground.LIGHT: 'rgba(0,0,0,0.25)'}

Color the window’s drop shadow floods with, per chrome.

Where the frame states the window’s edge, the shadow lifts it off whatever page embeds the capture, which is the other half of not dissolving into it. A reader whose renderer drops the filter still gets the frame.

click_extra.screenshot.WATERMARK_INK = 'rgba(128,128,128,0.85)'

Color the credit line is drawn in.

The one paint in a capture that answers to neither chrome, because it is the one thing drawn outside the window: the margin is transparent, so the mark sits on whatever page embeds the image, which the capture never gets to see. A white mark suits the dark chrome it was picked for and disappears on a README; a neutral gray reads on both, and dims into a backdrop when one is painted.

click_extra.screenshot.DEFAULT_BORDER_WIDTH = 1

Thickness, in pixels, of the frame drawn around the window.

click_extra.screenshot.TITLEBAR_HEIGHT = 40

Height, in pixels, of the strip a title and its buttons sit in.

The padding a renderer leaves above the text, which is what a window’s chrome occupies. Restated here because a capture wearing neither decoration nor caption drops the strip, and one drawn as a real terminal paints it.

click_extra.screenshot.DEFAULT_RADIUS = 8

How round the window’s corners are, in pixels.

The radius a renderer draws on its own, which is what a terminal on a desktop looks like. Zero squares them, for a capture meant to read as a plain block.

click_extra.screenshot.SHADOW_BLUR = 6

Standard deviation, in pixels, of the drop shadow’s blur.

click_extra.screenshot.SHADOW_OFFSET = 3

Downward offset, in pixels, of the drop shadow.

click_extra.screenshot.CSS_SIDE_ANGLES = {'to bottom': 180.0, 'to bottom left': 225.0, 'to bottom right': 135.0, 'to left': 270.0, 'to left bottom': 225.0, 'to left top': 315.0, 'to right': 90.0, 'to right bottom': 135.0, 'to right top': 45.0, 'to top': 0.0, 'to top left': 315.0, 'to top right': 45.0}

Angle each CSS side keyword names, in degrees clockwise from to top.

to bottom is what a gradient opening with no direction at all means, which is why it doubles as the default. See gradient_svg().

click_extra.screenshot.DEFAULT_MARGIN = 48

Transparent pixels left around the window, on all four sides.

Room for the shadow to fall into, first of all: a filter draws outside the shape it is applied to, and anything past the image’s own box is cut. It is also what a backdrop has to show through, and what keeps the window from touching the text of the page embedding it.

click_extra.screenshot.DEFAULT_PADDING = 8

Pixels added inside the window, around the captured text.

On top of the few a renderer adds on its own (8, and 40 above for the title bar), which leaves a help screen’s first column tight against the frame.

click_extra.screenshot.TITLE_SIZE = 18

Height, in pixels, of the caption drawn in a window’s title bar.

click_extra.screenshot.WATERMARK_SIZE = 13

Height, in pixels, of the credit line’s glyphs.

Below the terminal’s own text, since a mark competing with the screen it credits is a mark in the way.

click_extra.screenshot.WATERMARK_INSET = 12

Pixels between the credit line and the image’s bottom-right corner.

It is drawn in the margin, the one band of a capture that carries nothing else. A capture shot with margin=0 has no such band, and the line lands on the window’s own corner instead of beside it.

click_extra.screenshot.DEFAULT_WATERMARK = 'generated with click-extra 9.0.0'

Credit line every capture carries unless another one, or none, is asked for.

A capture travels: it lands on a slide, in a README, on a social card, far from the page that explains where it came from. The mark is what still says so, and names the release that drew it, so a reader can tell an image shot two years ago from one shot today.

Note

This is a default, not a fixture. --watermark "" draws none, and any other text replaces it: a project crediting itself rather than its tooling is the expected case, not an exception.

click_extra.screenshot.CAPTURE_TERMINAL_HINTS: dict[CaptureBackground, dict[str, str]] = {CaptureBackground.DARK: {'CLITHEME': 'dark', 'COLORFGBG': '15;0'}, CaptureBackground.LIGHT: {'CLITHEME': 'light', 'COLORFGBG': '0;15'}}

Environment a terminal of each chrome would carry, handed to the command.

A capture is a terminal simulated for a command that cannot see one: its width is pinned and its colors forced, because a pipe would have it wrap to a guess and print none. Its background is the third thing a terminal states and a pipe does not, through the two variables resolve_background() reads: the cli-theme CLITHEME, and COLORFGBG carrying foreground;background palette indices.

So a CLI asking for –theme auto renders for the chrome its picture is drawn on, instead of falling back to dark inside a light window. A CLI that never asks is unaffected: the variables only answer a question it does not put.

click_extra.screenshot.CAPTURE_FONT_STACK = "'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace"

Monospaced fonts a capture asks for, best first.

Nothing is embedded and nothing is fetched, so both formats set the text in the first family the reader already has, and a capture renders the same offline, on a page forbidding third-party requests, and in a viewer that speaks no CSS @font-face.

Family names are single-quoted on purpose: this lands in a double-quoted style attribute, which a double quote here would terminate early.

click_extra.screenshot.CELL_HEIGHT = 20.0

Height of one glyph cell, in pixels, which is also the text’s font size.

click_extra.screenshot.FONT_ASPECT_RATIO = 0.61

Width-to-height ratio of the font a capture is laid out for.

Fira Code’s, the first family CAPTURE_FONT_STACK asks for. Every monospaced fallback behind it is close enough that the grid holds, and textLength pins each run to its columns for the ones that are not.

click_extra.screenshot.CELL_WIDTH = 12.2

Width of one glyph cell, in pixels. One character of a monospaced terminal.

click_extra.screenshot.LINE_HEIGHT = 24.4

Vertical distance between two consecutive text baselines, in pixels.

click_extra.screenshot.CELL_BLEED = 0.25

Pixels a cell’s background is grown by, past the line it belongs to.

Two rectangles meeting on an exact boundary leave a hairline of page showing through when a renderer rounds their edges to different pixels. Overlapping them slightly is what closes that seam, and is invisible because the color painted twice is the same color.

click_extra.screenshot.CELL_TOP_INSET = 1.5

Pixels between a line’s top edge and the cell backgrounds drawn on it.

A glyph does not fill its line box: the leading sits above the tallest letter. Starting the paint just under that keeps a highlighted run reading as one block of color rather than as a band taller than the text it marks.

click_extra.screenshot.TILE_RUN = 8

Cells of tiling characters drawn before their offset is restated.

Small enough that a font whose tiles are a fraction of a pixel off the grid cannot drift a visible amount before the next offset resets it, and large enough that a table’s rule stays a handful of elements rather than one per cell. See tile_runs().

click_extra.screenshot.DIM_RATIO = 0.4

How far a dim run’s ink is mixed toward the background, see blend().

click_extra.screenshot.RTL_BIDI_CLASSES = frozenset({'AL', 'AN', 'R'})

Unicode bidirectional classes written right to left.

Right-to-left letters, Arabic letters and Arabic-Indic numbers, as unicodedata.bidirectional() names them. See is_bidirectional().

click_extra.screenshot.WINDOW_PADDING = 8

Pixels every window keeps between its frame and its text, on three sides.

The fourth is the top, where TITLEBAR_HEIGHT answers instead. This is the window’s own breathing room, before the padding a capture may ask for on top.

click_extra.screenshot.WINDOW_INSET = 1

Pixels between the image’s edge and the window’s frame.

A stroke straddles the shape it outlines, so a frame drawn flush with the viewBox loses its outer half to the crop. Inset by more than that half and the whole line shows.

click_extra.screenshot.AUTO_COLUMNS: Literal['auto'] = 'auto'

Width asking for the one the captured text itself decides.

Neither end of the pipeline is pinned: the command wraps to whatever terminal it finds (Click’s own 80 when that is a pipe, or a documentation build), and the image is laid out at the longest line that came back, see fit_columns(). Nothing the command printed folds inside the picture then, which is what a line the command does not wrap on its own needs: a prompt, a wide table, a machine-readable dump.

The cost is that the picture stops being a fixed-width terminal, so a capture meant to sit beside others at the same width should name that width instead.

click_extra.screenshot.DEFAULT_COLUMNS = 80

Terminal width a capture is taken at, in characters.

Both ends of the pipeline have to agree on it: the command wraps its output to this width, and the renderer lays the image out at the same one. Let them disagree and the rendered lines overrun the image. 80 is the width Click itself falls back to off a terminal, which makes it the value a capture lands on by accident anyway.

click_extra.screenshot.MIN_COLUMNS = 20

Narrowest width a capture is rendered at.

A floor on AUTO_COLUMNS as much as on an explicit width: a command printing nothing but blank lines would otherwise ask for an image no glyph fits in.

click_extra.screenshot.LINE_NUMBER_SEPARATOR = ' '

Rule drawn between a line’s number and the line itself.

A vertical bar rather than a bare space, so the gutter reads as a column of its own even where the output is itself indented.

click_extra.screenshot.DEFAULT_TRUNCATION = '[...]'

Marker standing in for the lines trim_lines() cut away.

click_extra.screenshot.PADDING = ' \xa0'

Characters separating one column of a capture from the next.

render_svg() emits every space as a non-breaking one, so the padding survives an XML round-trip and no renderer collapses a run of them.

click_extra.screenshot.number_lines(text, start=1)[source]

Prefix each line of text with its number, in a dim gutter.

The numbers are drawn into the terminal text rather than into a column of the image, which is the same trade Pygments makes with its inline line numbers: every renderer places them for free, and every reader copying the capture copies them too.

Right-aligned on the widest number, so the gutter is one column whatever the output’s length, and separated by LINE_NUMBER_SEPARATOR.

Parameters:
  • text (str) – captured output, ANSI escape sequences included.

  • start (int) – number given to the first line.

Return type:

str

Returns:

the numbered text.

click_extra.screenshot.preset_palette(preset, background)[source]

The colors a preset shows on the given chrome.

Return type:

TerminalPalette

click_extra.screenshot.resolve_palette(preset, background)[source]

The colors a capture resolves its ANSI codes against.

The preset’s palette on the given chrome, or the default terminal’s (CAPTURE_PALETTES) when no preset dresses the capture. The one resolution rule shared by render() and render_html(), so the two formats cannot disagree on what a chrome looks like.

Return type:

TerminalPalette

click_extra.screenshot.is_bidirectional(text)[source]

Whether text carries a character written right to left.

Arabic, Hebrew and their neighbours are reordered by whoever draws them, and the cursive ones are shaped: a letter’s form depends on what it joins. A terminal grid describes neither, which is why render_svg() stops pinning such a run to an exact width.

Parameters:

text (str) – the text to inspect.

Return type:

bool

Returns:

True when at least one character is right-to-left.

click_extra.screenshot.cell_width(text)[source]

Columns text occupies on a terminal’s character grid.

Not its length: a CJK ideograph is drawn two cells wide, a combining mark none at all. wcwidth.wcswidth() answers for both, and returns -1 for a string carrying a control character, where the count of characters is the closest thing to an answer left.

Parameters:

text (str) – the text to measure.

Return type:

int

Returns:

the number of cells it occupies.

click_extra.screenshot.fit_columns(text)[source]

Width, in characters, of the longest line in text.

ANSI escapes are stripped first: they style the glyphs around them and occupy no cell of their own. Measured in terminal cells, so a line of CJK asks for the two columns per glyph it is drawn with. Floored at MIN_COLUMNS.

Parameters:

text (str) – captured output, ANSI escape sequences included.

Return type:

int

Returns:

the width laying every line out without folding any.

click_extra.screenshot.capture_output(args, *, columns=80, background=CaptureBackground.DARK, merge_stderr=False, timeout=None)[source]

Run a command and capture its output, ANSI escape sequences and all.

A command whose output is a pipe rather than a terminal strips its own colors, and wraps to whatever width it can guess. Both are pinned here: forced_color() sets the FORCE_COLOR lever every mainstream color system obeys and clears any opt-out the environment carries, while COLUMNS fixes the width the command wraps to.

Only stdout is captured by default. That is what keeps a capture free of the progress lines and build chatter a wrapper like uv writes to stderr, with no shell redirection to remember.

Parameters:
Return type:

CompletedProcess[str]

Returns:

the completed process, whose stdout holds the captured text.

click_extra.screenshot.trim_lines(text, *, head=None, tail=None, truncation='[...]')[source]

Keep only the first head and last tail lines of text.

Whatever is dropped is replaced by a single truncation line, so the image admits that it was cut rather than pretending to be the whole output. Text short enough to survive both bounds comes back untouched, with no marker.

Parameters:
  • text (str) – the captured output.

  • head (int | None) – number of leading lines to keep, or None for no head bound.

  • tail (int | None) – number of trailing lines to keep, or None for no tail bound.

  • truncation (str) – line standing in for what was cut.

Return type:

str

Returns:

the trimmed text.

click_extra.screenshot.palette_color(color, palette)[source]

Resolve any color a Style carries to a hex string.

The 16 named and indexed ANSI slots are not colors, they are names: a terminal decides what its red looks like, and a capture has no terminal, so they answer to palette. Every other form a style can carry (a 24-bit triplet, a 256-cube index, a hex string) already states its own color and passes through.

Parameters:
  • color (object) – the value to resolve, as Style holds it.

  • palette (TerminalPalette) – the terminal colors to resolve names against.

Return type:

str

Returns:

the color, as #rrggbb.

Raises:

ValueError – when the value names no color.

click_extra.screenshot.blend(color, into, ratio)[source]

Mix color toward into, the way a terminal fades dim text.

SVG has no dim, and thinning the glyphs with opacity would let whatever sits behind the capture show through them. Mixing the two colors up front keeps the text opaque and lands the same shade.

Parameters:
  • color (str) – the color to fade, as #rrggbb.

  • into (str) – the color to fade it toward, usually the background.

  • ratio (float) – how far to go, from 0.0 (unchanged) to 1.0 (into).

Return type:

str

Returns:

the blended color, as #rrggbb.

click_extra.screenshot.grid(text, columns)[source]

Lay ANSI text out on a terminal’s character grid.

The one place a capture stops being a stream and becomes a picture. Each styled run of split_ansi() is split at newlines into rows, then placed on the column it starts at, measured in cells rather than characters so a wide glyph takes the two it is drawn with.

A line reaching past columns soft-wraps onto the next row, the way it would on a terminal that narrow, rather than being cropped: a command is free to print a line it never wraps itself (a long URL, a wide table, a machine-readable dump), and a picture that silently swallowed the overflow would be lying about what ran. A glyph straddling the edge moves down whole.

Returning the column with each run is what lets render_svg() place a run without measuring anything back out of its own output.

Parameters:
  • text (str) – captured output, ANSI escape sequences included.

  • columns (int) – width of the grid, in cells.

Return type:

list[list[tuple[Style, str, int]]]

Returns:

one list of (style, text, column) runs per row.

click_extra.screenshot.gradient_svg(value, unique_id, width, height)[source]

Translate a CSS gradient into the paint server SVG draws it with.

An SVG fill takes a paint: a color, or a reference to a gradient declared as an element of its own. The syntax a page’s CSS carries, linear-gradient(135deg, #ff9a9e, #fad0c4), means nothing to it, and a capture handed one would come out unpainted. So the CSS is read here and re-emitted as the element SVG does understand, which is what lets the same --backdrop value serve both formats.

Understood: linear-gradient opening with an optional angle (135deg) or side keyword (to bottom right, see CSS_SIDE_ANGLES), and radial-gradient, both followed by two or more color stops, each pinnable at a percentage. Anything else returns None and is left alone, being a plain color as far as this is concerned.

The gradient is placed in user space, which is what makes it exact rather than approximated: the CSS line runs through the image’s center at the given angle, and is as long as the box measures along it (|W·sinθ| + |H·cosθ|), while a radial one reaches the farthest corner.

Parameters:
  • value (str) – the --backdrop value, gradient or not.

  • unique_id (str) – identifier the paint server is declared under.

  • width (float) – width of the image the gradient fills, in pixels.

  • height (float) – its height.

Return type:

tuple[str, str] | None

Returns:

the <defs> markup and the fill value referencing it, or None when the value is not a gradient this understands.

click_extra.screenshot.titlebar_strip(left, top, width, *, paint, radius)[source]

Paint the strip a terminal seats its title and buttons in.

A capture leaves that strip the color of the terminal itself, where a real window carries a chrome of its own: the strip is what a reader’s eye reads as the top of a window rather than as the first line of output.

Drawn as a path rather than a rectangle because only its top corners follow the window’s own rounding; the bottom two meet the text and stay square.

Parameters:
  • left (float) – where the window starts, in pixels.

  • top (float) – where the window starts vertically, in pixels.

  • width (float) – how wide the window is, in pixels.

  • paint (str) – color to fill the strip with.

  • radius (int) – the window’s corner radius, in pixels.

Return type:

str

Returns:

the SVG markup.

click_extra.screenshot.watermark_svg(text, *, width, height, paint, font_stack="'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace")[source]

Draw the credit line in the image’s bottom-right corner.

Placed in the margin rather than over the terminal, which is what keeps it from covering a line of output: a capture is a picture of text, and a mark crossing that text costs the reader the thing being shown.

Carries a watermark class, so a reader taking a capture apart can tell the one run the renderer never captured from the ones it did.

Parameters:
  • text (str) – the credit to draw. Empty draws nothing.

  • width (float) – width of the whole image, in pixels.

  • height (float) – its height, in pixels.

  • paint (str) – color to draw the text in, alpha included.

  • font_stack (str) – fonts it is set in, the capture’s own.

Return type:

str

Returns:

the SVG markup, empty when there is nothing to draw.

click_extra.screenshot.window_buttons(buttons, *, width, color, font_stack="'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace")[source]

Draw a title bar’s decorations, as the terminal being mimicked draws them.

Two conventions, and a window carries one or the other: macOS fills round buttons on the left, Windows and GNOME set glyphs against the right edge. Both are placed in the window’s own coordinates, so they follow it wherever the frame moves it.

Parameters:
  • buttons (WindowButtons) – which decorations to draw.

  • width (float) – width of the window they are drawn in, in pixels.

  • color (str) – paint for the glyphs. Circles carry their own.

  • font_stack (str) – fonts the glyphs are set in, the window’s own.

Return type:

str

Returns:

the SVG markup, empty when the window wears none.

click_extra.screenshot.column_segments(text, column)[source]

Cut a run of text into the columns it actually occupies.

A run carries its own padding: a help screen’s –count INTEGER Number of greetings. is one styled run holding two columns and the gutter between them. Drawn as a single element, the second column only lands where it belongs if the renderer honors textLength and resolves the font, because the gutter’s width is being paid for in glyphs. librsvg does neither, and the columns collapse onto each other.

Cutting the run at its gutters and giving each piece its own offset asks nothing of the renderer but to draw glyphs at coordinates.

Parameters:
  • text (str) – the run’s text, padding included.

  • column (int) – the terminal column the run starts on.

Return type:

Iterator[tuple[str, int]]

Returns:

each column’s text, with the column it starts on.

click_extra.screenshot.tile_runs(text, column)[source]

Break a column’s text into the pieces drawn as one element each.

Ordinary text is one piece: the renderer lays it out and textLength holds the result to the width it occupies.

Text carrying a tile (_TILING_RE) is cut into groups of at most TILE_RUN cells, each landing on a stated offset. A <text> element is the smallest thing some renderers position at all: librsvg (and through it rsvg-convert and ImageMagick) honors the first x of an element and then lays every following glyph out at the font’s own advance, ignoring both textLength and any further x. A rule of 75 tiles drawn a tenth of a pixel narrow therefore ends a whole cell short of the below it, and the table’s corners miss. Restating the offset every few cells bounds that error to well under a pixel, whatever the font, and costs a tile nothing since none of them ligate.

Parameters:
  • text (str) – the column’s text.

  • column (int) – the terminal column it starts on.

Return type:

Iterator[tuple[str, int]]

Returns:

each piece, with the column it starts on.

click_extra.screenshot.glyph_offsets(text, column)[source]

Place a piece of text on the grid, as the attributes SVG reads.

A right-to-left piece is pinned by its offset alone: it is reordered and shaped by whoever draws it, and holding it to a width fights that.

Parameters:
  • text (str) – the piece’s glyphs.

  • column (int) – the terminal column it starts on.

Return type:

str

Returns:

the x attribute, and a textLength where one applies.

click_extra.screenshot.style_rules(style, palette)[source]

Compile a style to the CSS an SVG text run is drawn with.

Parameters:
Return type:

str

Returns:

the CSS declarations, semicolon-separated.

click_extra.screenshot.run_paint(style, palette)[source]

The color painted behind a run, or None where it shows the terminal’s own.

Parameters:
  • style (Style) – the run’s style.

  • palette (TerminalPalette) – the terminal colors to resolve names against.

Return type:

str | None

Returns:

the background color, as #rrggbb, or None to paint nothing.

click_extra.screenshot.render_svg(text, *, columns, title='', unique_id=None, palette=('#292929', '#c5c8c6', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#292929'), font_stack="'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace", border='none', border_width=1, radius=8, backdrop='none', shadow='none', margin=0, padding=0, buttons=(('#ff5f57', '#febc2e', '#28c840'), ''), buttons_color=None, titlebar='none', collapse_titlebar=False, opacity=1.0, watermark='', watermark_color='rgba(128,128,128,0.85)')[source]

Draw captured terminal text as a picture of a terminal window.

A terminal is a fixed grid of identically-sized cells, which is what makes this arithmetic rather than typesetting: grid() says which cell each run of same-styled characters starts on, and every coordinate below is that column times CELL_WIDTH.

Two primitives draw everything. A <rect> fills the cells behind a run that carries a background, and a <text> draws its glyphs, pinned to its columns with textLength so the layout survives a reader who does not have the font.

Note

A run’s padding is left out of its <text> and paid for in the x offset instead. Written the other way, a column only lands where it belongs if the glyphs are exactly the width assumed here, which asks the renderer to both honor textLength and resolve the font. A web browser does both. librsvg (and through it rsvg-convert and ImageMagick) ignores textLength, and a file manager, a git client or a thumbnailer commonly falls back to a proportional font. Starting each run on its own column asks neither.

Parameters:
  • text (str) – captured output, ANSI escape sequences included.

  • columns (int) – width of the terminal, in characters.

  • title (str) – caption drawn in the window’s title bar. Empty draws none.

  • unique_id (str | None) – prefix namespacing this document’s CSS classes and element IDs, see render(). Derived from the content when not given.

  • palette (TerminalPalette) – terminal colors the capture’s ANSI codes resolve against.

  • font_stack (str) – fonts the text is set in, best first.

  • border (str) – paint for the window’s frame. NO_PAINT draws none.

  • border_width (int) – thickness of that frame, in pixels.

  • radius (int) – how round the window’s corners are, in pixels.

  • backdrop (str) – paint filling the whole image, margin included, or a CSS gradient, see gradient_svg(). NO_PAINT leaves it transparent.

  • shadow (str) – color the window’s drop shadow floods with.

  • margin (int) – transparent pixels left around the window, on all four sides.

  • padding (int) – pixels added inside the window, around the text.

  • buttons (WindowButtons) – decorations drawn in the title bar.

  • buttons_color (str | None) – paint for the glyph decorations. Circles carry their own colors. None takes the palette’s foreground.

  • titlebar (str) – paint for the strip the title and buttons sit in. NO_PAINT leaves it the terminal’s own color.

  • collapse_titlebar (bool) – drop that strip, closing the window over the first line of text. For a capture wearing neither decoration nor caption.

  • opacity (float) – how solid the window’s body is, from OPAQUE down to 0.0. Only the body thins out: the frame, the title bar and the text keep their own paint.

  • watermark (str) – credit line drawn in the image’s bottom-right corner.

  • watermark_color (str) – color that line is drawn in, alpha included.

Return type:

str

Returns:

the SVG source.

click_extra.screenshot.render_html(text, *, title='', full=True, background=CaptureBackground.DARK, preset=None, border='none', border_width=1, radius=8, backdrop='none', shadow='none', margin=0, padding=0, buttons=None, buttons_color='#c5c8c6', font_stack="'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace", titlebar='none', collapse_titlebar=False, opacity=1.0, watermark='', watermark_color='rgba(128,128,128,0.85)')[source]

Render captured terminal text to HTML.

The <pre> carries its own inline styling, so a fragment pasted into an existing page needs no stylesheet and cannot be restyled out of legibility by the host. Nothing else is needed either: a <pre> preserves the capture’s own spacing, which is what spares HTML the column arithmetic render_svg() performs for a picture.

Caution

The text is escaped before its ANSI is translated, the order click_extra.table uses for its html format. Skip it and any < a CLI prints opens a tag: click-extra’s own --export-config help says it writes to <stdout>.

Note

An OSC 8 hyperlink loses its URL and keeps its visible text: the escape is dropped rather than turned into an <a>.

Parameters:
  • text (str) – captured output, ANSI escape sequences included.

  • title (str) – <title> of the document. Ignored for a fragment.

  • full (bool) – wrap the <pre> in a standalone document. False returns the <pre> alone, to paste into a page that has its own.

  • background (CaptureBackground) – chrome to draw on, see CaptureBackground.

  • border (str) – color of the block’s frame, see render_svg().

  • border_width (int) – thickness of that frame, in pixels.

  • radius (int) – how round the block’s corners are, in pixels.

  • backdrop (str) – paint filling the page behind the block.

  • shadow (str) – color of the block’s drop shadow, see render_svg().

  • margin (int) – pixels left around the block, on all four sides.

  • padding (int) – pixels added inside the block, on top of its own.

  • buttons (WindowButtons | None) – ignored. HTML reflows with the page embedding it, so it carries the text and its colors, not a window drawn around them.

  • buttons_color (str) – ignored, see buttons.

  • titlebar (str) – ignored, see buttons.

  • collapse_titlebar (bool) – ignored, see buttons.

  • opacity (float) – how solid the block’s background is, from OPAQUE down to 0.0, where the page shows straight through the text.

  • watermark (str) – credit line drawn under the block, against its right edge, where an SVG draws it in the margin. Empty draws none.

  • watermark_color (str) – color that line is drawn in, alpha included.

Return type:

str

Returns:

the rendered markup.

click_extra.screenshot.render(text, *, format=CaptureFormat.SVG, columns=80, title='', unique_id=None, full=True, background=CaptureBackground.DARK, preset=None, border=None, border_width=1, radius=None, backdrop='none', shadow=None, margin=48, padding=8, opacity=1.0, watermark='generated with click-extra 9.0.0', watermark_color=None)[source]

Render captured terminal text to the document format names.

Parameters:
  • text (str) – captured output, ANSI escape sequences included.

  • format (CaptureFormat) – which document to produce.

  • columns (int | Literal['auto']) – terminal width, in characters, an SVG is laid out at, or AUTO_COLUMNS for the width its own longest line asks for. HTML reflows, so it ignores this.

  • title (str) – caption drawn in an SVG’s window chrome, or an HTML document’s <title>.

  • unique_id (str | None) – SVG only. Prefix namespacing the source’s CSS classes and element IDs. Pinning it to something stable (the output file’s name, say) keeps a regenerated capture diffing line by line, instead of renaming every class as soon as a single character of output changes. Characters a CSS class name cannot carry are folded to a dash.

  • full (bool) – HTML only. See render_html().

  • background (CaptureBackground) – chrome to draw on, see CaptureBackground.

  • border (str | None) – color of the window’s frame. None takes the one the chrome can show, see CAPTURE_BORDERS; NO_PAINT draws none.

  • border_width (int) – thickness of that frame, in pixels.

  • radius (int | None) – how round the window’s corners are, in pixels. Zero squares them.

  • backdrop (str) – paint filling the image behind the window, margin included. NO_PAINT leaves it transparent.

  • shadow (str | None) – color of the window’s drop shadow. None takes the chrome’s own, see CAPTURE_SHADOWS; NO_PAINT draws none.

  • margin (int) – transparent pixels left around the window, on all four sides.

  • padding (int) – pixels added inside the window, around the text.

  • opacity (float) – how solid the window’s body is, from OPAQUE down to 0.0. Below it, whatever the capture is laid over shows through.

  • watermark (str) – credit line drawn in the image’s bottom-right corner, see DEFAULT_WATERMARK. An empty string draws none.

  • watermark_color (str | None) – color that line is drawn in. None takes WATERMARK_INK, which reads on a page of either color.

Return type:

str

Returns:

the rendered document.

Raises:

ImportError – rendering SVG without the screenshot extra installed.

click_extra.screenshot.capture(args, *, format=CaptureFormat.SVG, columns=80, prompt=None, head=None, tail=None, truncation='[...]', merge_stderr=False, timeout=None, line_numbers=False, title='', unique_id=None, full=True, background=CaptureBackground.DARK, preset=None, border=None, border_width=1, radius=None, backdrop='none', shadow=None, margin=48, padding=8, opacity=1.0, watermark='generated with click-extra 9.0.0', watermark_color=None)[source]

Run a command and render its output as a document.

Chains capture_output(), trim_lines() and render(). The invocation is drawn above the output as a shell prompt, styled by the active theme through format_cli_prompt(), so the capture shows what to type to reproduce it.

Parameters:
Return type:

tuple[str, int]

Returns:

the rendered document, and the command’s exit code.

click_extra.screenshot.format_from_path(path)[source]

Pick the capture format a file name asks for.

Parameters:

path (Path) – where the capture is to be written.

Return type:

CaptureFormat

Returns:

the CaptureFormat its extension names.

Raises:

ValueError – when the extension names no format.

click_extra.screenshot_presets API

The bundled catalog of terminal presets a capture can be drawn as.

A capture is a picture of a terminal, and terminals do not look alike. A preset carries the four things that make one recognizable, so a reader placing the image knows which desktop it came from:

  • the window decorations, three round buttons on the left for macOS, three glyphs on the right for Windows, a single one for GNOME;

  • the palette its colors resolve against, which is what turns a bright blue into Campbell’s #3B78FF or Tango’s #729FCF;

  • the font the terminal ships with;

  • the prompt its shell draws, $ against PS C:\>.

None of it is applied unless asked for: a capture with no preset keeps the renderer’s own neutral window, which is what every image in this project’s documentation is drawn as.

Caution

A palette here is a published default, transcribed from the scheme each terminal ships (Campbell and One Half Light for Windows Terminal, Basic and Pro for Apple’s Terminal, Tango for GNOME), and cross-checked against iTerm2-Color-Schemes. It is what the terminal looks like out of the box, not what any given reader has configured theirs to.

class click_extra.screenshot_presets.TerminalPalette(background: str, foreground: str, ansi: tuple[str, ...], titlebar: str)[source]

Bases: NamedTuple

The colors a terminal resolves a capture’s ANSI codes against.

The 16 ansi entries are the standard palette in the canonical order: black, red, green, yellow, blue, magenta, cyan, white, then the same eight in their bright variants.

Create new instance of TerminalPalette(background, foreground, ansi, titlebar)

background: str

Color behind the text, as CSS spells it.

foreground: str

Color of the text a CLI leaves unstyled.

ansi: tuple[str, ...]

The 16 palette entries, normal eight first.

titlebar: str

Color of the strip the window’s title and buttons sit in.

A window’s chrome, which the desktop paints rather than the terminal: a shade off the background, so the top of the window reads as a window rather than as the first line of output.

class click_extra.screenshot_presets.WindowButtons(circles: tuple[str, ...] = (), glyphs: str = '')[source]

Bases: NamedTuple

The decorations a terminal draws in its title bar.

Two shapes cover the desktops: macOS draws filled circles on the left, Windows and GNOME draw glyphs on the right. None in either field leaves that half undrawn, which is what a bare window asks for.

Create new instance of WindowButtons(circles, glyphs)

circles: tuple[str, ...]

Colors of the round buttons drawn from the left, in order.

glyphs: str

Characters drawn from the right, closing button last.

click_extra.screenshot_presets.MACOS_BUTTONS: Final = (('#ff5f57', '#febc2e', '#28c840'), '')

Close, minimize and zoom, the three round buttons of an Aqua title bar.

click_extra.screenshot_presets.WINDOWS_BUTTONS: Final = ((), '-□✕')

Minimize, maximize and close, the three glyphs of a Windows title bar.

click_extra.screenshot_presets.GNOME_BUTTONS: Final = ((), '✕')

The single close button a GNOME window carries by default.

click_extra.screenshot_presets.APPLE_ANSI: Final = ('#000000', '#c23621', '#25bc24', '#adad27', '#492ee1', '#d338d3', '#33bbc8', '#cbcccd', '#818383', '#fc391f', '#31e722', '#eaec23', '#5833ff', '#f935f8', '#14f0f0', '#e9ebeb')

Palette shared by Apple Terminal’s Basic and Pro schemes.

click_extra.screenshot_presets.CAMPBELL_ANSI: Final = ('#0c0c0c', '#c50f1f', '#13a10e', '#c19c00', '#0037da', '#881798', '#3a96dd', '#cccccc', '#767676', '#e74856', '#16c60c', '#f9f1a5', '#3b78ff', '#b4009e', '#61d6d6', '#f2f2f2')

Palette of Campbell, the scheme Windows Terminal opens with.

click_extra.screenshot_presets.ONE_HALF_LIGHT_ANSI: Final = ('#383a42', '#e45649', '#50a14f', '#c18301', '#0184bc', '#a626a4', '#0997b3', '#fafafa', '#4f525d', '#df6c75', '#98c379', '#e4c07a', '#61afef', '#c577dd', '#56b5c1', '#ffffff')

Palette of One Half Light, the light scheme Windows Terminal ships.

click_extra.screenshot_presets.TANGO_ANSI: Final = ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec')

Palette of Tango, which GNOME Terminal ships in a dark and a light dress.

class click_extra.screenshot_presets.TerminalPreset(label: str, buttons: WindowButtons, radius: int, prompt: str, font_stack: str, dark: TerminalPalette, light: TerminalPalette)[source]

Bases: NamedTuple

A terminal a capture can be drawn as.

Pass one to click-extra screenshot --preset, or to a click:run block as :screenshot-preset:. Anything stated alongside it wins: a preset picks the defaults, it does not lock them.

Create new instance of TerminalPreset(label, buttons, radius, prompt, font_stack, dark, light)

label: str

Human name of the terminal, for the documentation and the help screen.

buttons: WindowButtons

Decorations drawn in the title bar, see WindowButtons.

radius: int

How round the window’s corners are, in pixels.

prompt: str

Sigil the terminal’s usual shell draws before a command.

font_stack: str

Fonts the capture asks for, the terminal’s own first.

Nothing is embedded, so a reader without the family falls back down the list. Which is why each ends with the same generic monospace a browser always resolves.

dark: TerminalPalette

Colors the terminal shows on its dark scheme.

light: TerminalPalette

Colors it shows on its light one.

click_extra.screenshot_presets.PRESETS: Final[dict[str, TerminalPreset]] = {'linux': ('GNOME Terminal', ((), '✕'), 6, '$', "'Ubuntu Mono', 'DejaVu Sans Mono', monospace", ('#2e3436', '#d3d7cf', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#303030'), ('#ffffff', '#2e3436', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#ebebeb')), 'macos': ('Apple Terminal', (('#ff5f57', '#febc2e', '#28c840'), ''), 10, '$', "'SF Mono', Menlo, Monaco, monospace", ('#000000', '#f2f2f2', ('#000000', '#c23621', '#25bc24', '#adad27', '#492ee1', '#d338d3', '#33bbc8', '#cbcccd', '#818383', '#fc391f', '#31e722', '#eaec23', '#5833ff', '#f935f8', '#14f0f0', '#e9ebeb'), '#3a3a3a'), ('#ffffff', '#000000', ('#000000', '#c23621', '#25bc24', '#adad27', '#492ee1', '#d338d3', '#33bbc8', '#cbcccd', '#818383', '#fc391f', '#31e722', '#eaec23', '#5833ff', '#f935f8', '#14f0f0', '#e9ebeb'), '#e9e9e9')), 'plain': ('No terminal at all', ((), ''), 0, '$', "'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace", ('#292929', '#c5c8c6', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#292929'), ('#ffffff', '#000000', ('#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'), '#ffffff')), 'windows': ('Windows Terminal', ((), '-□✕'), 0, 'PS C:\\>', "'Cascadia Code', 'Cascadia Mono', Consolas, monospace", ('#0c0c0c', '#cccccc', ('#0c0c0c', '#c50f1f', '#13a10e', '#c19c00', '#0037da', '#881798', '#3a96dd', '#cccccc', '#767676', '#e74856', '#16c60c', '#f9f1a5', '#3b78ff', '#b4009e', '#61d6d6', '#f2f2f2'), '#202020'), ('#fafafa', '#383a42', ('#383a42', '#e45649', '#50a14f', '#c18301', '#0184bc', '#a626a4', '#0997b3', '#fafafa', '#4f525d', '#df6c75', '#98c379', '#e4c07a', '#61afef', '#c577dd', '#56b5c1', '#ffffff'), '#f3f3f3'))}

Every terminal a capture can be drawn as, alphabetically.

plain is the odd one out: it mimics no desktop, dropping the buttons and the rounded corners for a capture that has to read as a block of output rather than as a window, on a slide or in a paper.