Reproducible terminal demos and workflow diagrams for READMEs.
Build animated terminal demos from captured output or static workflow diagrams from JSON. Generated SVGs embed directly in GitHub with no browser runtime.
Install · Use it · Used by Brigade
pipx install plating-cli
npm install -g svg-term-cli # SVG renderer plating shells out to
plating render examples/plating-demo.json| Job | What you get | |
|---|---|---|
| Spec | JSON steps + outputs | Commands stay honest; animation is synthesized |
| Scan | Refuse identity leaks | Home paths, username, hostname, private IPs |
| Render | Animated SVG embed | GitHub README and sites as a plain img |
| Diagram | Lay out a workflow from JSON | Matching static SVGs across repositories |
| Verify | Drift detection | Re-run specs when CLI output changes |
That recording was made by plating itself.
Write a spec, quickstart.json:
{
"title": "quickstart",
"width": 84,
"steps": [
{ "command": "mytool --version", "output": "mytool 1.2.3\n" },
{ "command": "mytool build", "output_file": "build-output.txt" }
]
}Render it:
plating render quickstart.json
# wrote quickstart.svg (and quickstart.cast, the reproducible source)Then embed quickstart.svg in your README or drop it into a site.
plating workflow renders a constrained JSON document into the shared Escoffier Labs workflow style. Authors name columns, nodes, and connections; connections are forward-only, running from an earlier column to a later column. Plating owns the canvas, spacing, typography, colors, arrows, and accessible SVG metadata.
{
"title": "Source to release",
"eyebrow": "BUILD WORKFLOW",
"description": "Tracked source passes through verification before release.",
"columns": [
{"title": "INPUT", "nodes": [{"id": "source", "label": "source"}]},
{"title": "CHECK", "nodes": [{"id": "test", "label": "tests", "kind": "focus"}]},
{"title": "OUTPUT", "nodes": [{"id": "release", "label": "release", "kind": "success"}]}
],
"edges": [
{"from": "source", "to": "test"},
{"from": "test", "to": "release", "label": "pass"}
]
}plating workflow examples/workflow.json
# plating: wrote examples/workflow.svgCommit the JSON source and generated SVG together. Re-running the command with the same source produces the same bytes.
plating workflow runs a leak scan before writing. It scans the parsed input strings and the rendered SVG, prints each finding once, and exits 2 without writing the SVG or creating its parent directory. The scan_patterns key only adds rules to the default set.
In priority order:
| In the step | Output is |
|---|---|
"output": "..." |
the literal string |
"output_file": "path" |
a captured-output file (relative to the spec, confined under the spec directory) |
"run": true (or plating render --run) |
the live result of running command |
Live capture (--run) is convenient; committing captured output is what makes it reproducible in CI. Use normalize to rewrite a throwaway path into something clean:
{ "normalize": [["/tmp/tmp.AbC123/demo", "~/my-repo"]] }normalize rules are applied to the command and output shown in the recording. Live execution always runs the original, unnormalized command string or argv array, so a normalized display path never breaks the actual run.
output_file is resolved under the spec's directory. Absolute paths, .. traversal, and symlink escapes are rejected with a plating: error before any artifact is written. A normal nested path inside the spec directory (e.g. "captures/out.txt") works as expected.
On POSIX systems with O_DIRECTORY and O_NOFOLLOW, paths are opened component-by-component through directory descriptors so symlink swaps after validation are rejected. Windows and other platforms without that capability cannot use output_file. Use a literal "output" value instead. Literal backslashes in POSIX filenames are preserved and are not treated as path separators.
Before rendering, plating scans the recording for /home/... and /Users/... paths, the machine's current username and hostname, private IPs, and a few narrow secret shapes (SOMETHING_TOKEN=... / SOMETHING_API_KEY=... / SOMETHING_SECRET=... / SOMETHING_PASSWORD=... assignments and PEM -----BEGIN ... PRIVATE KEY----- headers). Secret findings are redacted so the value is never echoed back. If the scan finds one it refuses to render and tells you how to fix it with a normalize rule or an explicit --allow-leaks override.
This scan is best-effort and dependency-free. It catches common shapes that leak into a recording. It is not a secrets scanner, so use a dedicated scanner for sensitive material.
plating scan some-recording.castWhen a step runs live, plating parses the command into an argv list and runs it with shell=False. Consequences:
- Use argv arrays for non-trivial commands. A
commandstring is split withshlex(POSIX rules). On Windows, strings containing backslashes are rejected. POSIX also rejects ambiguous drive and UNC forms such asC:\...and\\.... Use an explicit JSON argv array for quoted arguments, empty strings, and backslash paths. - No shell operators. Pipes (
|), redirects (>),&&,;, backticks, and$()substitutions are not supported in live capture. Use a real script for anything that needs a shell. - Empty or malformed commands are rejected (e.g. an unterminated quote).
cwdis confined on POSIX. A spec-declaredcwdis resolved relative to and confined within the spec directory using stable directory descriptors. Windows and other platforms withoutO_DIRECTORY+O_NOFOLLOWreject spec-declaredcwdbefore subprocess start. The CLI--cwdargument may point anywhere on disk (not confined to the spec directory), but must exist and be a directory.- Environment is an explicit allowlist. Live runs inherit only
PATH,LANG,LC_ALL,LC_CTYPE,TERM,COLORTERM,NO_COLOR, and (when present) the Windows launch variablesSYSTEMROOT,WINDIR,COMSPEC,PATHEXT.HOME, cloud-provider, CI, SSH, and token variables are not inherited. - Timeout is enforced. Each live step gets a positive finite timeout, default 30 seconds. Set it with the spec's
run_timeoutkey or the CLI--timeoutflag (CLI wins). A timeout is reported as aplating:error, not a traceback.
Spec keys: title, width, height, padding, window (macOS chrome, on by default), prompt, prompt_color, the timing knobs (type_speed, line_delay, command_pause, ... see src/plating/cast.py), normalize, scan_patterns, cwd, run_timeout.
CLI:
plating render <spec> [--run] [--cwd DIR] [--timeout SECONDS] [--out-dir DIR] [--png MS] [--allow-leaks]
plating scan <file>
plating workflow <spec> [--out FILE]
--png MS writes a static PNG of the frame at MS milliseconds (via headless Chrome), handy for a quick eyeball before you commit the SVG. --timeout SECONDS overrides the spec's run_timeout for live runs.
examples/brigade-quickstart.json rebuilds the quickstart recording used in the Brigade README from its real, captured output:
plating render examples/brigade-quickstart.jsonMIT. See LICENSE.
