Fix convert.mjs to resolve canvas from .export-runtime via createRequire instead of bare import (which resolves relative to script location, not CWD). Add File Location Convention section to SKILL.md — diagrams save .excalidraw source alongside PNGs in the project's image directory for easy re-export. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
6.9 KiB
Markdown
156 lines
6.9 KiB
Markdown
---
|
|
name: excalidraw-png-export
|
|
description: "This skill should be used when creating diagrams, architecture visuals, or flowcharts and exporting them as PNG files. It uses the Excalidraw MCP to render hand-drawn style diagrams locally and Playwright to export them to PNG without sending data to any remote server. Triggers on requests like 'create a diagram', 'make an architecture diagram', 'draw a flowchart and export as PNG', or any request that needs a visual diagram delivered as an image file."
|
|
---
|
|
|
|
# Excalidraw PNG Export
|
|
|
|
Create hand-drawn style diagrams with the Excalidraw MCP and export them locally to PNG files. All rendering happens on the local machine. Diagram data never leaves the user's computer.
|
|
|
|
## Prerequisites
|
|
|
|
### First-Time Setup
|
|
|
|
Run the setup script once per machine to install Playwright and Chromium headless:
|
|
|
|
```bash
|
|
bash <skill-path>/scripts/setup.sh
|
|
```
|
|
|
|
This creates a `.export-runtime` directory inside `scripts/` with the Node.js dependencies. The setup is idempotent and skips installation if already present.
|
|
|
|
### Required MCP
|
|
|
|
The Excalidraw MCP server must be configured. Verify availability by checking for `mcp__excalidraw__create_view` and `mcp__excalidraw__read_checkpoint` tools.
|
|
|
|
## File Location Convention
|
|
|
|
Save diagram source files alongside their PNG exports in the project's image directory. This enables re-exporting diagrams when content or styling changes.
|
|
|
|
**Standard pattern:**
|
|
```
|
|
docs/images/my-diagram.excalidraw # source (commit this)
|
|
docs/images/my-diagram.png # rendered output (commit this)
|
|
```
|
|
|
|
**When updating an existing diagram**, look for a `.excalidraw` file next to the PNG. If one exists, edit it and re-export rather than rebuilding from scratch.
|
|
|
|
**Temporary files** (raw checkpoint JSON) go in `/tmp/excalidraw-export/` and are discarded after conversion.
|
|
|
|
## Workflow
|
|
|
|
### Step 1: Design the Diagram Elements
|
|
|
|
Translate the user's request into Excalidraw element JSON. Load [excalidraw-element-format.md](./references/excalidraw-element-format.md) for the full element specification, color palette, and sizing guidelines.
|
|
|
|
Key design decisions:
|
|
- Choose appropriate colors from the palette to distinguish different components
|
|
- Use `label` on shapes instead of separate text elements
|
|
- Use `roundness: { type: 3 }` for rounded corners on rectangles
|
|
- Include `cameraUpdate` as the first element to frame the view (MCP rendering only)
|
|
- Use arrow bindings (`startBinding`/`endBinding`) to connect shapes
|
|
|
|
### Step 2: Render with Excalidraw MCP
|
|
|
|
Call `mcp__excalidraw__create_view` with the element JSON array. This renders an interactive preview in the Claude Code UI.
|
|
|
|
```
|
|
mcp__excalidraw__create_view({ elements: "<JSON array string>" })
|
|
```
|
|
|
|
The response includes a `checkpointId` for retrieving the rendered state.
|
|
|
|
### Step 3: Extract the Checkpoint Data
|
|
|
|
Call `mcp__excalidraw__read_checkpoint` with the checkpoint ID to get the full element JSON back.
|
|
|
|
```
|
|
mcp__excalidraw__read_checkpoint({ id: "<checkpointId>" })
|
|
```
|
|
|
|
### Step 4: Convert Checkpoint to .excalidraw File
|
|
|
|
Use the `convert.mjs` script to transform raw MCP checkpoint JSON into a valid `.excalidraw` file. This handles all the tedious parts automatically:
|
|
|
|
- Filters out pseudo-elements (`cameraUpdate`, `delete`, `restoreCheckpoint`)
|
|
- Adds required Excalidraw defaults (`seed`, `version`, `fontFamily`, etc.)
|
|
- Expands `label` properties on shapes/arrows into proper bound text elements
|
|
|
|
```bash
|
|
# Save checkpoint JSON to a temp file, then convert to the project's image directory:
|
|
node <skill-path>/scripts/convert.mjs /tmp/excalidraw-export/raw.json docs/images/my-diagram.excalidraw
|
|
```
|
|
|
|
The input JSON should be the raw checkpoint data from `mcp__excalidraw__read_checkpoint` (the `{"elements": [...]}` object). The output `.excalidraw` file goes in the project's image directory (see File Location Convention above).
|
|
|
|
**For batch exports**: Write each checkpoint to a separate raw JSON file, then convert each one:
|
|
```bash
|
|
node <skill-path>/scripts/convert.mjs raw1.json diagram1.excalidraw
|
|
node <skill-path>/scripts/convert.mjs raw2.json diagram2.excalidraw
|
|
```
|
|
|
|
**Manual alternative**: If you need to write the `.excalidraw` file by hand (e.g., without the convert script), each element needs these defaults:
|
|
|
|
```
|
|
angle: 0, roughness: 1, opacity: 100, groupIds: [], seed: <unique int>,
|
|
version: 1, versionNonce: <unique int>, isDeleted: false,
|
|
boundElements: null, link: null, locked: false
|
|
```
|
|
|
|
Text elements also need: `fontFamily: 1, textAlign: "left", verticalAlign: "top", baseline: 14, containerId: null, originalText: "<same as text>"`
|
|
|
|
Bound text (labels on shapes/arrows) needs: `containerId: "<parent-id>"`, `textAlign: "center"`, `verticalAlign: "middle"`, and the parent needs `boundElements: [{"id": "<text-id>", "type": "text"}]`.
|
|
|
|
### Step 5: Export to PNG
|
|
|
|
Run the export script. Determine the runtime path relative to this skill's scripts directory:
|
|
|
|
```bash
|
|
cd <skill-path>/scripts/.export-runtime && node <skill-path>/scripts/export_png.mjs docs/images/my-diagram.excalidraw docs/images/my-diagram.png
|
|
```
|
|
|
|
The script:
|
|
1. Starts a local HTTP server serving the `.excalidraw` file and an HTML page
|
|
2. Launches headless Chromium via Playwright
|
|
3. The HTML page loads the Excalidraw library from esm.sh (library code only, not user data)
|
|
4. Calls `exportToBlob` on the local diagram data
|
|
5. Extracts the base64 PNG and writes it to disk
|
|
6. Cleans up temp files and exits
|
|
|
|
The script prints the output path on success. Verify the result with `file <output.png>`.
|
|
|
|
### Step 5.5: Validate and Iterate
|
|
|
|
Run the validation script on the `.excalidraw` file to catch spatial issues:
|
|
|
|
```bash
|
|
node <skill-path>/scripts/validate.mjs docs/images/my-diagram.excalidraw
|
|
```
|
|
|
|
Then read the exported PNG back using the Read tool to visually inspect:
|
|
|
|
1. All label text fits within its container (no overflow/clipping)
|
|
2. No arrows cross over text labels
|
|
3. Spacing between elements is consistent
|
|
4. Legend and titles are properly positioned
|
|
|
|
If the validation script or visual inspection reveals issues:
|
|
1. Identify the specific elements that need adjustment
|
|
2. Edit the `.excalidraw` file (adjust coordinates, box sizes, or arrow waypoints)
|
|
3. Re-run the export script (Step 5)
|
|
4. Re-validate
|
|
|
|
### Step 6: Deliver the Result
|
|
|
|
Read the PNG file to display it to the user. Provide the file path so the user can access it directly.
|
|
|
|
## Troubleshooting
|
|
|
|
**Setup fails**: Verify Node.js v18+ is installed (`node --version`). Ensure npm has network access for the initial Playwright/Chromium download.
|
|
|
|
**Export times out**: The HTML page has a 30-second timeout. If it fails, check browser console output in the script's error messages. Common cause: esm.sh CDN is temporarily slow on first load.
|
|
|
|
**Blank PNG**: Ensure elements include all required properties (see Step 4 defaults). Missing `seed`, `version`, or `fontFamily` on text elements can cause silent render failures.
|
|
|
|
**"READY" never fires**: The `exportToBlob` call requires valid elements. Filter out `cameraUpdate` and other pseudo-elements before writing the `.excalidraw` file.
|