
Drawio
- 218 installs
- 2 repo stars
- Updated July 24, 2026
- ilteoood/harness
Render.drawio architecture diagrams to PNG for READMEs, specs, and agent context using CLI or headless viewer fallbacks.
About
drawio is a harness-embedded agent skill oriented around turning diagrams.net sources into PNGs that solo builders can drop into repos, PRs, or spec folders. The documented script prioritizes fidelity: use the native draw.io CLI when installed for speed and accuracy, otherwise load the official viewer in a headless browser so orthogonal connectors and container layouts match what authors see in the editor. Batch mode walks directories so a single command refreshes every architecture sheet before a release note or onboarding doc update. For agentic workflows, that means Mermaid-like convenience but with full draw.io expressiveness—provided you accept Node tooling and optional network fetches for the viewer assets. It complements markdown prose rather than replacing diagram authoring; you still maintain .drawio files as the source of truth.
- drawio-to-png.mjs converts single files or whole directories of .drawio sources
- Three renderer modes: draw.io CLI, official viewer in headless browser, and auto selection
- Official draw.io viewer JS path for pixel-perfect edges and shape routing when CLI is missing
- Supports compressed diagram payloads via zlib inflate before render
- Puppeteer-core drives headless rendering when network-backed viewer is required
Drawio by the numbers
- 218 all-time installs (skills.sh)
- +30 installs in the week ending Jul 25, 2026 (Skillselion tracking)
- Ranked #505 of 1,901 Documentation skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 25, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ilteoood/harness --skill drawioAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 218 |
|---|---|
| repo stars | ★ 2 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 24, 2026 |
| Repository | ilteoood/harness ↗ |
What it does
Render.drawio architecture diagrams to PNG for READMEs, specs, and agent context using CLI or headless viewer fallbacks.
Files
Draw.io Diagram Skill
Generate draw.io diagrams as native .drawio files and export them to PNG images that can be embedded in Word documents.
How to Create a Diagram
1. Generate draw.io XML in mxGraphModel format for the requested diagram 2. Write the XML to a .drawio file using the create/edit file tool 3. Export to PNG using the bundled export script
Bundled Export Script
This skill includes drawio-to-png.mjs, a Node.js export script with two rendering backends:
1. draw.io CLI (pixel-perfect, fastest) — used automatically if draw.io desktop is installed 2. Official draw.io viewer in headless browser (pixel-perfect, needs Chromium/Edge) — fallback when CLI is unavailable
Usage
# Install dependencies (one-time, from the scripts folder)
cd skills/drawio/scripts && npm install
# Export a single diagram
node skills/drawio/scripts/drawio-to-png.mjs <input.drawio> [output.png]
# Export all .drawio files in a directory
node skills/drawio/scripts/drawio-to-png.mjs --dir <directory>
# Force a specific renderer
node skills/drawio/scripts/drawio-to-png.mjs --renderer=cli|viewer|auto <input.drawio>Skill Folder Contents
| File | Purpose |
|---|---|
SKILL.md | This instruction file |
scripts/drawio-to-png.mjs | Node.js export script (CLI + browser fallback) |
scripts/package.json | Dependencies (puppeteer-core) |
Supported Export Formats
| Format | Embed XML | Notes |
|---|---|---|
png | Yes | Viewable everywhere, editable in draw.io |
svg | Yes | Scalable, editable in draw.io |
pdf | Yes | Printable, editable in draw.io |
Draw.io XML Style Conventions
Use these styles for consistent, professional diagrams:
<!-- Primary service (highlighted) -->
<mxCell style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;strokeWidth=2;arcSize=12;shadow=1;" />
<!-- External system -->
<mxCell style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" />
<!-- Success/processing stage -->
<mxCell style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" />
<!-- Warning/quality gate -->
<mxCell style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" />
<!-- Error/failure path -->
<mxCell style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" />
<!-- Data store (cylinder) -->
<mxCell style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" />
<!-- Arrow -->
<mxCell style="edgeStyle=orthogonalEdgeStyle;rounded=1;strokeColor=#6c8ebf;strokeWidth=2;" />Locating the draw.io CLI
Try drawio first (works if on PATH), then fall back:
- Windows:
"C:\Program Files\draw.io\draw.io.exe" - macOS:
/Applications/draw.io.app/Contents/MacOS/draw.io - Linux:
drawio(via snap/apt/flatpak)
CLI Export Command
drawio -x -f png -e -b 10 -o <output.png> <input.drawio>Flags: -x (export), -f (format), -e (embed diagram XML), -b (border), -o (output path).
/**
* drawio-to-png.mjs - Convert .drawio files to PNG with accurate rendering.
*
* Rendering priority:
* 1. draw.io CLI (if installed) — pixel-perfect, fastest
* 2. Official draw.io viewer JS in headless browser — pixel-perfect, needs network
*
* Usage: node drawio-to-png.mjs <input.drawio> [output.png]
* node drawio-to-png.mjs --dir <directory> (converts all .drawio files in directory)
* node drawio-to-png.mjs --renderer=cli|viewer|auto <input.drawio> [output.png]
*/
import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
import { join, basename, dirname, resolve } from "path";
import { spawnSync } from "child_process";
import { inflateRawSync } from "zlib";
import puppeteer from "puppeteer-core";
// --- Build HTML that uses the official draw.io viewer for rendering ---
function buildViewerHtml(rawFileContent) {
// Escape for embedding in a JS template literal
const escaped = rawFileContent
.replace(/\\/g, "\\\\")
.replace(/`/g, "\\`")
.replace(/\$/g, "\\$");
// The official draw.io viewer (viewer-static.min.js) contains the full mxGraph
// rendering engine — it handles orthogonal edge routing, all shape types,
// container layouts, and compressed/uncompressed diagram formats.
return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; }
body { background: white; }
</style>
</head>
<body>
<div id="diagram-host"></div>
<script>
// Prepare diagram XML and set up the viewer target div
(function() {
var raw = \`${escaped}\`;
// Wrap raw mxGraphModel in mxfile if needed (viewer expects mxfile format)
var xmlStr = raw.trim();
if (xmlStr.startsWith('<mxGraphModel')) {
xmlStr = '<mxfile><diagram name="Page-1">' + xmlStr + '</diagram></mxfile>';
} else if (!xmlStr.startsWith('<mxfile')) {
// Assume it's already an mxfile or a diagram element
if (xmlStr.startsWith('<diagram')) {
xmlStr = '<mxfile>' + xmlStr + '</mxfile>';
}
}
var config = {
xml: xmlStr,
highlight: "none",
nav: false,
resize: true,
toolbar: null,
"toolbar-nohide": true,
edit: null,
lightbox: false,
"auto-fit": true,
"check-visible-state": false
};
var div = document.createElement('div');
div.className = 'mxgraph';
div.setAttribute('data-mxgraph', JSON.stringify(config));
document.getElementById('diagram-host').appendChild(div);
})();
// Poll until the viewer renders the diagram (viewer script loaded separately)
window.__pollStarted = false;
window.__startPoll = function() {
if (window.__pollStarted) return;
window.__pollStarted = true;
// Explicitly trigger viewer processing
if (typeof GraphViewer !== 'undefined' && GraphViewer.processElements) {
GraphViewer.processElements();
}
(function poll() {
// Viewer places SVG directly inside .mxgraph div
var mxDiv = document.querySelector('.mxgraph');
if (mxDiv) {
var svg = mxDiv.querySelector('svg');
if (svg) {
var rect = mxDiv.getBoundingClientRect();
if (rect.width > 10 && rect.height > 10) {
window.__renderComplete = true;
window.__renderWidth = rect.width;
window.__renderHeight = rect.height;
return;
}
}
}
setTimeout(poll, 150);
})();
};
</script>
</body>
</html>`;
}
// --- Extract mxGraph XML from .drawio input (supports mxGraphModel and mxfile) ---
function extractMxGraphModelXml(inputXml) {
const trimmed = inputXml.trim();
if (trimmed.startsWith("<mxGraphModel")) {
return trimmed;
}
const diagramMatch = trimmed.match(/<diagram\b[^>]*>([\s\S]*?)<\/diagram>/i);
if (!diagramMatch) {
throw new Error("Unsupported .drawio format: missing <mxGraphModel> or <diagram> content");
}
const diagramContent = diagramMatch[1].trim();
if (diagramContent.startsWith("<mxGraphModel")) {
return diagramContent;
}
// draw.io compressed diagrams are base64(deflateRaw(encodeURIComponent(xml)))
try {
const inflated = inflateRawSync(Buffer.from(diagramContent, "base64")).toString("utf-8");
const decoded = decodeURIComponent(inflated);
if (!decoded.trim().startsWith("<mxGraphModel")) {
throw new Error("decoded content is not mxGraphModel XML");
}
return decoded;
} catch (err) {
throw new Error(`Failed to decode compressed <diagram> content: ${err.message}`);
}
}
function resolveRenderer(rawArgs) {
let renderer = "auto";
const args = [];
for (const arg of rawArgs) {
if (arg.startsWith("--renderer=")) {
renderer = arg.substring("--renderer=".length).trim().toLowerCase();
continue;
}
args.push(arg);
}
if (!["auto", "cli", "viewer"].includes(renderer)) {
throw new Error(`Invalid renderer '${renderer}'. Use auto, cli, or viewer.`);
}
return { renderer, args };
}
function findDrawioCliPath() {
const envPath = process.env.DRAWIO_PATH;
if (envPath) {
try {
if (statSync(envPath).isFile()) return envPath;
} catch { /* ignore */ }
}
const candidates = [
"C:\\Program Files\\draw.io\\draw.io.exe",
"C:\\Program Files (x86)\\draw.io\\draw.io.exe",
"/Applications/draw.io.app/Contents/MacOS/draw.io",
"/usr/bin/drawio",
"/usr/local/bin/drawio",
];
for (const p of candidates) {
try {
if (statSync(p).isFile()) return p;
} catch { /* ignore */ }
}
const locator = process.platform === "win32" ? "where" : "which";
const names = process.platform === "win32" ? ["drawio", "draw.io"] : ["drawio"];
for (const name of names) {
const probe = spawnSync(locator, [name], { encoding: "utf-8" });
if (probe.status === 0 && probe.stdout) {
const first = probe.stdout.split(/\r?\n/).map(line => line.trim()).find(Boolean);
if (first) return first;
}
}
return null;
}
function exportWithDrawioCli(drawioPath, input, output) {
const args = ["-x", "-f", "png", "-e", "-b", "10", "-o", output, input];
const result = spawnSync(drawioPath, args, { encoding: "utf-8" });
if (result.status !== 0) {
const stderr = (result.stderr || "").trim();
const stdout = (result.stdout || "").trim();
throw new Error(stderr || stdout || `draw.io CLI failed with exit code ${result.status}`);
}
}
// --- Main ---
async function main() {
const parsed = resolveRenderer(process.argv.slice(2));
const renderer = parsed.renderer;
const args = parsed.args;
let files = [];
if (args[0] === "--dir") {
const dir = resolve(args[1] || ".");
files = readdirSync(dir)
.filter(f => f.endsWith(".drawio"))
.map(f => ({
input: join(dir, f),
output: join(dir, f.replace(/\.drawio$/, ".drawio.png"))
}));
} else if (args[0]) {
const input = resolve(args[0]);
const output = args[1] || input.replace(/\.drawio$/, ".drawio.png");
files = [{ input, output }];
} else {
console.error("Usage: node drawio-to-png.mjs <input.drawio> [output.png]");
console.error(" node drawio-to-png.mjs --dir <directory>");
console.error(" node drawio-to-png.mjs --renderer=cli|auto|custom <input.drawio> [output.png]");
process.exit(1);
}
if (files.length === 0) {
console.log("No .drawio files found.");
return;
}
const drawioCliPath = findDrawioCliPath();
// --- Path 1: draw.io CLI (best fidelity, no network needed) ---
if (renderer === "cli" || (renderer === "auto" && drawioCliPath)) {
if (!drawioCliPath) {
console.error("draw.io CLI not found. Install draw.io desktop or set DRAWIO_PATH.");
process.exit(1);
}
console.log(`Using renderer: draw.io CLI (${basename(drawioCliPath)})`);
for (const { input, output } of files) {
console.log(`Rendering: ${basename(input)}`);
try {
exportWithDrawioCli(drawioCliPath, input, output);
let kb = "?";
try {
kb = (statSync(output).size / 1024).toFixed(0);
} catch { /* ignore size read errors */ }
console.log(` -> ${basename(output)} (${kb} KB)`);
} catch (err) {
console.error(` Error rendering ${basename(input)}: ${err.message}`);
}
}
console.log("Done.");
return;
}
// --- Path 2: Official draw.io viewer in headless browser ---
// Find browser
const browserPaths = [
process.env.CHROME_PATH,
process.env.EDGE_PATH,
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"/usr/bin/google-chrome",
"/usr/bin/chromium-browser",
"/usr/bin/microsoft-edge",
].filter(Boolean);
let execPath;
for (const p of browserPaths) {
try {
if (statSync(p).isFile()) { execPath = p; break; }
} catch { /* not found */ }
}
if (!execPath) {
console.error("No browser found. Set CHROME_PATH or EDGE_PATH environment variable.");
process.exit(1);
}
console.log(`Using renderer: draw.io viewer (${basename(execPath)})`);
const browser = await puppeteer.launch({
executablePath: execPath,
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
});
for (const { input, output } of files) {
console.log(`Rendering: ${basename(input)}`);
try {
const rawContent = readFileSync(input, "utf-8");
const html = buildViewerHtml(rawContent);
const page = await browser.newPage();
await page.setViewport({ width: 2400, height: 1600, deviceScaleFactor: 2 });
// Set HTML content first (sets up the .mxgraph div with diagram XML)
await page.setContent(html, { waitUntil: "domcontentloaded" });
// Load the official draw.io viewer JS via addScriptTag (more reliable than inline src)
const VIEWER_URL = "https://viewer.diagrams.net/js/viewer-static.min.js";
try {
await page.addScriptTag({ url: VIEWER_URL });
} catch (scriptErr) {
throw new Error(`Failed to load draw.io viewer JS: ${scriptErr.message}`);
}
// Start polling for the rendered diagram
await page.evaluate(() => window.__startPoll());
// Wait for the viewer to finish rendering
await page.waitForFunction(() => window.__renderComplete === true, { timeout: 30000 });
// Check rendering succeeded
const viewerOk = await page.evaluate(() => window.__renderWidth > 0);
if (!viewerOk) {
throw new Error("draw.io viewer failed to load or render (check network access)");
}
// Take element screenshot of just the diagram div for exact bounds
const containerHandle = await page.$('.mxgraph');
let pngBuffer;
if (containerHandle) {
pngBuffer = await containerHandle.screenshot({ type: "png" });
} else {
// Fallback: full-page screenshot
const dims = await page.evaluate(() => ({
w: Math.ceil(window.__renderWidth),
h: Math.ceil(window.__renderHeight)
}));
pngBuffer = await page.screenshot({
type: "png",
clip: { x: 0, y: 0, width: dims.w + 20, height: dims.h + 20 },
});
}
writeFileSync(output, pngBuffer);
console.log(` -> ${basename(output)} (${(pngBuffer.length / 1024).toFixed(0)} KB)`);
await page.close();
} catch (err) {
console.error(` Error rendering ${basename(input)}: ${err.message}`);
}
}
await browser.close();
console.log("Done.");
}
main().catch(err => { console.error(err); process.exit(1); });
{
"private": true,
"type": "module",
"description": "Dependencies for the draw.io diagram export skill",
"dependencies": {
"puppeteer-core": "^24.39.1"
}
}
Related skills
Forks & variants (1)
Drawio has 1 known copy in the catalog totaling 1 installs. They canonicalize to this original listing.
- github - 1 installs
FAQ
Is Drawio safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.