mermaid-to-images
Non-negotiable rules. Read these first; they override any instinct to be clever.
- The bundled script is the ONLY renderer. Every image is produced by
running
scripts/mermaid_to_images.py. You MUST NOT render diagrams any
other way. Specifically forbidden: a headless browser or Playwright
screenshot, a local HTTP server (http.server), converting SVG to PNG
yourself, @1x/@2x intermediates, or drawing/editing an image by hand.
- If the script cannot render, STOP and report to the user. A failure
(no
mmdc, no network to mermaid.ink) is a hard stop, not a problem to route
around. Relay the script's error and its remediation; do not invent a
fallback renderer. Producing the image by other means is a failure, not a
save.
- Do not choose output names, formats, or folders. The output is exactly
diagram-N.png inside <md-stem>-diagrams/. Never rename to something
"semantic" (e.g. assets/big-picture.png), never emit .svg, never save
.mmd source, never write a mermaid-config.json.
- Your only edits to the doc are: (a) turning an ASCII diagram fence into a
```mermaid fence (with a %% alt: comment), and (b) running the script,
which does the fence-to-image rewrite. Nothing else.
If any rule pushes you toward more work, you are misreading it: the goal is a
boring, identical result every run. When blocked, stopping is the correct
outcome.
Turn the diagrams in a Markdown file into image files and replace each block, in
place, with a Markdown image reference such as
. Images
land in a sibling folder named <md-stem>-diagrams/ next to the document. Two
kinds of source are handled:
```mermaid fenced blocks — rendered directly by the bundled script.
- ASCII diagrams in plain fences (hand-drawn sequence, flow, or box art) —
you translate each into an equivalent mermaid diagram first, then the script
renders it. This step is model work: ASCII art is not machine-parseable, and
telling a real diagram from ordinary text is a judgment call.
Do the rendering with the script (do not improvise)
All rendering and rewriting is done by the bundled, deterministic script
(scripts/mermaid_to_images.py, standard library only). Always run it. Your
job is to author mermaid (for ASCII diagrams) and to run the script; it is not to
render diagrams by hand.
This keeps every run identical and predictable:
- Never call
mmdc or mermaid.ink yourself, hand-encode a URL, or write the
image reference by hand. The script owns extraction, rendering, and the rewrite.
- The only outputs are
diagram-N.png files. Do not produce .svg, save
.mmd source, or write a mermaid-config.json or any other side artifact.
- Output is PNG only (the script default). Do not switch to SVG.
- Filenames are
diagram-1.png, diagram-2.png, ... in document order, not
semantic names. Re-running overwrites them in place rather than accumulating.
- Alt text is descriptive, sourced from a
%% alt: comment you put in the block
(see "Translating ASCII diagrams"); the script applies it deterministically.
The script also has a --snippet OUT preview mode (reads one mermaid diagram from
stdin, renders it to OUT) for checking a translation before writing it into the
doc. Previews go to a scratch path, never into the document's diagram folder.
Argument
One argument: the path to the Markdown file to process (for example
$ARGUMENTS). If it is missing, ask the user for it. Resolve it to an absolute
path before running.
Renderer
Two backends, selected by the script's --renderer flag (default auto):
mmdc (mermaid-cli): renders fully offline, best fidelity, respects
themes cleanly. Requires the mmdc binary on PATH
(npm install -g @mermaid-js/mermaid-cli).
ink (mermaid.ink): the hosted renderer the user referenced; needs
network access. auto falls back to this when mmdc is absent.
Prefer mmdc when it is installed. If neither mmdc nor network access is
available, the script will fail: that is a hard stop. Report the script's error
and remediation (install mmdc, or enable network for the command) and wait for
the user. Do not reach for a browser, a local server, or any other renderer to
get around it. A missing renderer is the user's to resolve, not yours to
engineer around.
Steps
Resolve the file. Confirm the argument points to an existing Markdown
file. Absolutize the path.
Survey the diagrams. Two passes:
- Count
```mermaid blocks (for example grep -c '```mermaid' <file>).
- Scan the plain
``` fences for ASCII diagrams (see "Translating ASCII
diagrams" below). If there are neither, say so and stop.
Pick the renderer. Detect mmdc (command -v mmdc). Use it if present;
otherwise use ink and note that it needs network access. The ink backend
reaches https://mermaid.ink, so if the environment sandboxes network the
Bash call may need network access enabled.
Translate ASCII diagrams to mermaid (skip if there are none). For each
ASCII diagram you and the user agree to convert, replace the plain fence's
contents with an equivalent ```mermaid block, editing the source file
directly. Preview first (see below) so a bad translation never lands.
Run the script, defaulting to --backup (a transient safety net: the
.bak is written before the overwrite and removed on success, so nothing is
left behind unless the write fails):
python3 skills/mermaid-to-images/scripts/mermaid_to_images.py \
"<absolute-md-path>" --backup
This converts every ```mermaid block (native plus the ones you just
authored) to PNG images and rewrites each fence as an image reference. Leave
the defaults alone: they produce diagram-N.png deterministically. Add
--renderer mmdc|ink, --theme <name>, --background <color>, or
--out <dir> only when the user explicitly asks; do not reach for --format svg. Copy the script alongside the skill when it has been installed into a
project's .claude/skills/.
Report. State how many blocks were converted (mermaid vs ASCII-derived),
where the images were written, and that the Markdown now references them. If
the user is inside a git repo, suggest reviewing the diff before committing.
Idempotency and determinism
The conversion is designed to be repeatable and side-effect-free:
- Deterministic names. Blocks become
diagram-1.png, diagram-2.png, ... in
document order. A re-run overwrites the same files in place; it never creates a
diagram-3.png variant or a semantically named copy.
- Safe re-runs. Once a fence is replaced by an image reference there is no
```mermaid block left, so running the script again is a no-op ("No mermaid
blocks found"). It will not double-convert or duplicate images.
- No stray artifacts. The only thing written under
<md-stem>-diagrams/ is
the diagram-N.png set. If you see .svg, .mmd, or *-config.json files
there, they did not come from this script and should not be created.
- One writer. Only the script edits the Markdown and the image folder. If you
need to change a rendered diagram, edit its mermaid source (re-inserting the
```mermaid block if it was already converted) and run the script again,
rather than editing the PNG or the reference by hand.
Translating ASCII diagrams
ASCII art is not machine-parseable, so this is model work, not something the
script can do. Be conservative and get the user's sign-off before rewriting.
Decide what is actually a diagram. Convert only blocks that draw a
structure: sequence diagrams (lifelines with arrows between actors), flow or
box-and-arrow diagrams, state machines. Do not convert code, JSON, config,
or console/log output just because it contains -> or |. For example
GET /hello (same jti twice) -> 200, then 401 is log output, not a diagram,
even though a naive scan flags the arrow.
Author the mermaid. Reproduce the actors/nodes, the messages/edges, their
order and direction, and any inline notes. Pick the fitting diagram type
(sequenceDiagram for actor-to-actor message flows, flowchart for
box-and-arrow). Keep labels faithful to the original wording.
Add descriptive alt text. Put a %% alt: comment inside the block, one
sentence describing what the diagram shows. Mermaid ignores %% comments, so
it does not affect the render; the script reads it and uses it as the image's
alt text (falling back to Diagram N if you omit it). For example:
sequenceDiagram
%% alt: A single protected call: the browser sends a session cookie to the Next.js server tier, which signs a DPoP proof and calls Spring Boot; the backend verifies the proof against cnf.jkt and returns the greeting.
...
Give the boxes breathing room. Mermaid's defaults pack text tight against
box borders, and long actor labels make some boxes far wider than others. Add
an init directive that increases the internal margins and wraps long labels
so boxes are padded and evenly sized. For a sequenceDiagram, a good starting
point (tune per diagram, and preview) is:
%%{init: {"sequence": {"noteMargin": 18, "boxTextMargin": 10, "boxMargin": 12, "messageMargin": 40, "wrap": true, "width": 200}}}%%
wrap plus a fixed width is the only way to add horizontal padding to actor
boxes (mermaid otherwise sizes them tight to the label); raise width if a
message label wraps when you did not want it to.
Preview before writing with snippet mode, then look at the result:
python3 skills/mermaid-to-images/scripts/mermaid_to_images.py \
--snippet /tmp/preview.png <<'MMD'
sequenceDiagram
...your translation...
MMD
Read the produced image and compare it against the ASCII source. Fix the
mermaid (and the padding) and re-preview until it matches and reads cleanly.
Confirm with the user, especially when several blocks are borderline.
Show which blocks you plan to convert and which you are leaving as text, then
rewrite the agreed fences in the source (step 4 of the main steps) and run the
script.
Guardrails
- The script edits the Markdown in place. Always run with
--backup (or confirm
the file is tracked in git) so the original is recoverable. The .bak is
transient: it is removed automatically once the rewrite succeeds, and kept only
if the write itself fails.
- Do not hand-edit
```mermaid fences into image links yourself; let the
script do that extraction and rewrite so indentation and offsets stay correct.
Your only manual edit is turning an ASCII fence into a ```mermaid fence.
- Be conservative with ASCII: when a block is ambiguous, leave it as text and ask
rather than guessing. A wrong diagram is worse than an untouched one.
- Keep images readable: the default background is opaque
white on purpose.
Mermaid draws message/edge labels in a light gray meant for a white backdrop,
so a transparent PNG can make those labels vanish on dark surfaces. Only pass
--background transparent when the doc is known to render on a light
background.
- Do not commit or push the generated images or the edited file unless the user
explicitly asks.
1---2name: mermaid-to-images3description: Turn a Markdown file's diagrams into image files and rewrite each block as a Markdown image reference. Handles two sources: ```mermaid fenced blocks (rendered directly) and hand-drawn ASCII diagrams in plain fences (sequence/flow/box art), which are first translated to equivalent mermaid and then rendered. Uses mermaid-cli (mmdc) offline when available, or the hosted mermaid.ink service otherwise. Use when a Markdown doc must show diagrams in a viewer that does not render mermaid (many editors, PDF/print, some blog engines), or to promote ASCII art to real diagrams. Takes one argument: the path to the Markdown file. Edits the file in place, so it is user-invoked only.4---56# mermaid-to-images78> **Non-negotiable rules. Read these first; they override any instinct to be clever.**9>10> 1. **The bundled script is the ONLY renderer.** Every image is produced by11> running `scripts/mermaid_to_images.py`. You MUST NOT render diagrams any12> other way. Specifically forbidden: a headless browser or Playwright13> screenshot, a local HTTP server (`http.server`), converting SVG to PNG14> yourself, `@1x`/`@2x` intermediates, or drawing/editing an image by hand.15> 2. **If the script cannot render, STOP and report to the user.** A failure16> (no `mmdc`, no network to mermaid.ink) is a hard stop, not a problem to route17> around. Relay the script's error and its remediation; do not invent a18> fallback renderer. Producing the image by other means is a failure, not a19> save.20> 3. **Do not choose output names, formats, or folders.** The output is exactly21> `diagram-N.png` inside `<md-stem>-diagrams/`. Never rename to something22> "semantic" (e.g. `assets/big-picture.png`), never emit `.svg`, never save23> `.mmd` source, never write a `mermaid-config.json`.24> 4. **Your only edits to the doc** are: (a) turning an ASCII diagram fence into a25> ` ```mermaid ` fence (with a `%% alt:` comment), and (b) running the script,26> which does the fence-to-image rewrite. Nothing else.27>28> If any rule pushes you toward more work, you are misreading it: the goal is a29> boring, identical result every run. When blocked, stopping is the correct30> outcome.3132Turn the diagrams in a Markdown file into image files and replace each block, in33place, with a Markdown image reference such as34``. Images35land in a sibling folder named `<md-stem>-diagrams/` next to the document. Two36kinds of source are handled:3738- **` ```mermaid ` fenced blocks** — rendered directly by the bundled script.39- **ASCII diagrams in plain fences** (hand-drawn sequence, flow, or box art) —40 *you* translate each into an equivalent mermaid diagram first, then the script41 renders it. This step is model work: ASCII art is not machine-parseable, and42 telling a real diagram from ordinary text is a judgment call.4344## Do the rendering with the script (do not improvise)4546All rendering and rewriting is done by the bundled, deterministic script47(`scripts/mermaid_to_images.py`, standard library only). **Always run it.** Your48job is to author mermaid (for ASCII diagrams) and to run the script; it is not to49render diagrams by hand.5051This keeps every run identical and predictable:5253- **Never** call `mmdc` or `mermaid.ink` yourself, hand-encode a URL, or write the54 image reference by hand. The script owns extraction, rendering, and the rewrite.55- The only outputs are `diagram-N.png` files. **Do not** produce `.svg`, save56 `.mmd` source, or write a `mermaid-config.json` or any other side artifact.57- Output is **PNG only** (the script default). Do not switch to SVG.58- Filenames are `diagram-1.png`, `diagram-2.png`, ... in document order, not59 semantic names. Re-running overwrites them in place rather than accumulating.60- Alt text is descriptive, sourced from a `%% alt:` comment you put in the block61 (see "Translating ASCII diagrams"); the script applies it deterministically.6263The script also has a `--snippet OUT` preview mode (reads one mermaid diagram from64stdin, renders it to `OUT`) for checking a translation before writing it into the65doc. Previews go to a scratch path, never into the document's diagram folder.6667## Argument6869One argument: the path to the Markdown file to process (for example70`$ARGUMENTS`). If it is missing, ask the user for it. Resolve it to an absolute71path before running.7273## Renderer7475Two backends, selected by the script's `--renderer` flag (default `auto`):7677- **`mmdc`** (mermaid-cli): renders fully offline, best fidelity, respects78 themes cleanly. Requires the `mmdc` binary on `PATH`79 (`npm install -g @mermaid-js/mermaid-cli`).80- **`ink`** (mermaid.ink): the hosted renderer the user referenced; needs81 network access. `auto` falls back to this when `mmdc` is absent.8283Prefer `mmdc` when it is installed. **If neither `mmdc` nor network access is84available, the script will fail: that is a hard stop.** Report the script's error85and remediation (install `mmdc`, or enable network for the command) and wait for86the user. Do not reach for a browser, a local server, or any other renderer to87get around it. A missing renderer is the user's to resolve, not yours to88engineer around.8990## Steps91921. **Resolve the file.** Confirm the argument points to an existing Markdown93 file. Absolutize the path.942. **Survey the diagrams.** Two passes:95 - Count ` ```mermaid ` blocks (for example `grep -c '```mermaid' <file>`).96 - Scan the plain ` ``` ` fences for **ASCII diagrams** (see "Translating ASCII97 diagrams" below). If there are neither, say so and stop.983. **Pick the renderer.** Detect `mmdc` (`command -v mmdc`). Use it if present;99 otherwise use `ink` and note that it needs network access. The `ink` backend100 reaches `https://mermaid.ink`, so if the environment sandboxes network the101 Bash call may need network access enabled.1024. **Translate ASCII diagrams to mermaid** (skip if there are none). For each103 ASCII diagram you and the user agree to convert, replace the plain fence's104 contents with an equivalent ` ```mermaid ` block, editing the source file105 directly. Preview first (see below) so a bad translation never lands.1065. **Run the script**, defaulting to `--backup` (a transient safety net: the107 `.bak` is written before the overwrite and removed on success, so nothing is108 left behind unless the write fails):109110 ```bash111 python3 skills/mermaid-to-images/scripts/mermaid_to_images.py \112 "<absolute-md-path>" --backup113 ```114115 This converts every ` ```mermaid ` block (native plus the ones you just116 authored) to PNG images and rewrites each fence as an image reference. Leave117 the defaults alone: they produce `diagram-N.png` deterministically. Add118 `--renderer mmdc|ink`, `--theme <name>`, `--background <color>`, or119 `--out <dir>` only when the user explicitly asks; do not reach for `--format120 svg`. Copy the script alongside the skill when it has been installed into a121 project's `.claude/skills/`.1226. **Report.** State how many blocks were converted (mermaid vs ASCII-derived),123 where the images were written, and that the Markdown now references them. If124 the user is inside a git repo, suggest reviewing the diff before committing.125126## Idempotency and determinism127128The conversion is designed to be repeatable and side-effect-free:129130- **Deterministic names.** Blocks become `diagram-1.png`, `diagram-2.png`, ... in131 document order. A re-run overwrites the same files in place; it never creates a132 `diagram-3.png` variant or a semantically named copy.133- **Safe re-runs.** Once a fence is replaced by an image reference there is no134 ` ```mermaid ` block left, so running the script again is a no-op ("No mermaid135 blocks found"). It will not double-convert or duplicate images.136- **No stray artifacts.** The only thing written under `<md-stem>-diagrams/` is137 the `diagram-N.png` set. If you see `.svg`, `.mmd`, or `*-config.json` files138 there, they did not come from this script and should not be created.139- **One writer.** Only the script edits the Markdown and the image folder. If you140 need to change a rendered diagram, edit its mermaid source (re-inserting the141 ` ```mermaid ` block if it was already converted) and run the script again,142 rather than editing the PNG or the reference by hand.143144## Translating ASCII diagrams145146ASCII art is not machine-parseable, so this is model work, not something the147script can do. Be conservative and get the user's sign-off before rewriting.1481491. **Decide what is actually a diagram.** Convert only blocks that *draw* a150 structure: sequence diagrams (lifelines with arrows between actors), flow or151 box-and-arrow diagrams, state machines. **Do not** convert code, JSON, config,152 or **console/log output** just because it contains `->` or `|`. For example153 `GET /hello (same jti twice) -> 200, then 401` is log output, not a diagram,154 even though a naive scan flags the arrow.1552. **Author the mermaid.** Reproduce the actors/nodes, the messages/edges, their156 order and direction, and any inline notes. Pick the fitting diagram type157 (`sequenceDiagram` for actor-to-actor message flows, `flowchart` for158 box-and-arrow). Keep labels faithful to the original wording.1593. **Add descriptive alt text.** Put a `%% alt:` comment inside the block, one160 sentence describing what the diagram shows. Mermaid ignores `%%` comments, so161 it does not affect the render; the script reads it and uses it as the image's162 alt text (falling back to `Diagram N` if you omit it). For example:163164 ```165 sequenceDiagram166 %% alt: A single protected call: the browser sends a session cookie to the Next.js server tier, which signs a DPoP proof and calls Spring Boot; the backend verifies the proof against cnf.jkt and returns the greeting.167 ...168 ```1694. **Give the boxes breathing room.** Mermaid's defaults pack text tight against170 box borders, and long actor labels make some boxes far wider than others. Add171 an `init` directive that increases the internal margins and wraps long labels172 so boxes are padded and evenly sized. For a `sequenceDiagram`, a good starting173 point (tune per diagram, and preview) is:174175 ```176 %%{init: {"sequence": {"noteMargin": 18, "boxTextMargin": 10, "boxMargin": 12, "messageMargin": 40, "wrap": true, "width": 200}}}%%177 ```178179 `wrap` plus a fixed `width` is the only way to add horizontal padding to actor180 boxes (mermaid otherwise sizes them tight to the label); raise `width` if a181 message label wraps when you did not want it to.1825. **Preview before writing** with snippet mode, then look at the result:183184 ```bash185 python3 skills/mermaid-to-images/scripts/mermaid_to_images.py \186 --snippet /tmp/preview.png <<'MMD'187 sequenceDiagram188 ...your translation...189 MMD190 ```191192 Read the produced image and compare it against the ASCII source. Fix the193 mermaid (and the padding) and re-preview until it matches and reads cleanly.1946. **Confirm with the user**, especially when several blocks are borderline.195 Show which blocks you plan to convert and which you are leaving as text, then196 rewrite the agreed fences in the source (step 4 of the main steps) and run the197 script.198199## Guardrails200201- The script edits the Markdown in place. Always run with `--backup` (or confirm202 the file is tracked in git) so the original is recoverable. The `.bak` is203 transient: it is removed automatically once the rewrite succeeds, and kept only204 if the write itself fails.205- Do not hand-edit ` ```mermaid ` fences into image links yourself; let the206 script do that extraction and rewrite so indentation and offsets stay correct.207 Your only manual edit is turning an ASCII fence into a ` ```mermaid ` fence.208- Be conservative with ASCII: when a block is ambiguous, leave it as text and ask209 rather than guessing. A wrong diagram is worse than an untouched one.210- Keep images readable: the default background is opaque `white` on purpose.211 Mermaid draws message/edge labels in a light gray meant for a white backdrop,212 so a transparent PNG can make those labels vanish on dark surfaces. Only pass213 `--background transparent` when the doc is known to render on a light214 background.215- Do not commit or push the generated images or the edited file unless the user216 explicitly asks.