diff --git a/plugins/compound-engineering/skills/excalidraw-png-export/SKILL.md b/plugins/compound-engineering/skills/excalidraw-png-export/SKILL.md index 4cc27ee..00142bd 100644 --- a/plugins/compound-engineering/skills/excalidraw-png-export/SKILL.md +++ b/plugins/compound-engineering/skills/excalidraw-png-export/SKILL.md @@ -23,6 +23,20 @@ This creates a `.export-runtime` directory inside `scripts/` with the Node.js de 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 @@ -63,11 +77,11 @@ Use the `convert.mjs` script to transform raw MCP checkpoint JSON into a valid ` - Expands `label` properties on shapes/arrows into proper bound text elements ```bash -# Save checkpoint JSON to a file first, then convert: -node /scripts/convert.mjs +# Save checkpoint JSON to a temp file, then convert to the project's image directory: +node /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 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 @@ -92,7 +106,7 @@ Bound text (labels on shapes/arrows) needs: `containerId: ""`, `textA Run the export script. Determine the runtime path relative to this skill's scripts directory: ```bash -cd /scripts/.export-runtime && node /scripts/export_png.mjs /tmp/excalidraw-export/diagram.excalidraw /tmp/excalidraw-export/output.png +cd /scripts/.export-runtime && node /scripts/export_png.mjs docs/images/my-diagram.excalidraw docs/images/my-diagram.png ``` The script: @@ -110,7 +124,7 @@ The script prints the output path on success. Verify the result with `file /scripts/validate.mjs /tmp/excalidraw-export/diagram.excalidraw +node /scripts/validate.mjs docs/images/my-diagram.excalidraw ``` Then read the exported PNG back using the Read tool to visually inspect: diff --git a/plugins/compound-engineering/skills/excalidraw-png-export/scripts/convert.mjs b/plugins/compound-engineering/skills/excalidraw-png-export/scripts/convert.mjs index 801b1d4..c6eeed0 100755 --- a/plugins/compound-engineering/skills/excalidraw-png-export/scripts/convert.mjs +++ b/plugins/compound-engineering/skills/excalidraw-png-export/scripts/convert.mjs @@ -4,13 +4,20 @@ * Filters pseudo-elements, adds required defaults, expands labels into bound text. */ import { readFileSync, writeFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const runtimeRequire = createRequire(join(__dirname, '.export-runtime', 'package.json')); // Canvas-based text measurement with graceful fallback to heuristic. // Excalidraw renders with Virgil (hand-drawn font); system sans-serif // is a reasonable proxy. The 1.1x multiplier accounts for Virgil being wider. let measureText; try { - const { createCanvas } = await import('canvas'); + const canvas = runtimeRequire('canvas'); + const { createCanvas } = canvas; const cvs = createCanvas(1, 1); const ctx = cvs.getContext('2d'); measureText = (text, fontSize) => {