| name | excalidraw | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| description | Create hand-drawn style diagrams — flowcharts, architecture, sequence, concept maps. Default workflow for flowcharts is CLI layout + freeform agent JSON editing + Obsidian native output. Raw JSON for non-flowchart layouts. | ||||||||||||
| version | 3.0.0 | ||||||||||||
| author | Hermes Agent | ||||||||||||
| license | MIT | ||||||||||||
| dependencies | |||||||||||||
| metadata |
|
Three steps:
- CLI generates layout —
excalidraw-cliplaces nodes with proper spacing, no overlaps - Agent edits JSON freeform — read the output, add colors, restyle arrows, add background zones, tweak anything. No constraints — full access to the Excalidraw JSON spec.
- Convert to Obsidian native — wrap in
.excalidraw.mdfor inline rendering
# DSL → plain .excalidraw JSON
python3 ~/.hermes/skills/creative/excalidraw/scripts/hybrid.py layout \
--dsl "(Start) -> [Process] -> {OK?} -> (End)" \
-o /tmp/diagram.excalidraw
# Or from a .dsl file
python3 ~/.hermes/skills/creative/excalidraw/scripts/hybrid.py layout \
--dsl-file /tmp/flow.dsl -o /tmp/diagram.excalidrawDSL reference:
[Label]→ rectangle |{Label?}→ diamond |(Label)→ ellipse |[[Label]]→ database->arrow |-> "text" ->labeled arrow |-->dashed arrow@direction TB|BT|LR|RL|@spacing N
Read the generated .excalidraw file, then modify elements freely via execute_code or patch. Common operations:
Color nodes by meaning:
# Color map: label substring → background color
colors = {"Deploy": "#b2f2bb", "Error": "#ffc9c9", "Test": "#fff3bf"}
for el in data["elements"]:
if el["type"] in ("text", "arrow"): continue
label = labels.get(el["id"], "") # lookup from bound text elements
for pattern, color in colors.items():
if pattern.lower() in label.lower():
el["backgroundColor"] = color
el["fillStyle"] = "solid"
breakRestyle specific arrows (dashed, colored):
for el in data["elements"]:
if el["type"] == "arrow" and el.get("endBinding", {}).get("elementId") == "error_node":
el["strokeColor"] = "#e03131"
el["strokeStyle"] = "dashed"Add background zones (group related nodes):
zone = {"type": "rectangle", "id": "zone_deploy", "x": 400, "y": 500,
"width": 350, "height": 250, "backgroundColor": "#b2f2bb",
"fillStyle": "solid", "opacity": 20, "strokeStyle": "dashed", ...}
data["elements"].insert(0, zone) # insert at back (z-order)Add rounded corners:
for el in data["elements"]:
if el["type"] == "rectangle":
el["roundness"] = {"type": 3}Add title:
title = {"type": "text", "id": "title", "x": 250, "y": 10,
"text": "My Pipeline", "fontSize": 28, "fontFamily": 1, ...}
data["elements"].insert(0, title)The point: you're not limited to flags. Do whatever the Excalidraw JSON format supports.
Pitfalls with CLI output:
- The CLI does NOT use
containerIdbinding. Text elements float spatially on top of shapes without linking. To find a shape's label, use bounding-box overlap matching (check if text x/y falls within shape x/y/w/h ± 10px tolerance), NOTcontainerIdlookups. - Element IDs are random — you can't predict them. Always match by label content or position.
- Arrow labels may also lack
containerId. Use the same spatial matching approach.
python3 ~/.hermes/skills/creative/excalidraw/scripts/hybrid.py convert \
--input /tmp/diagram.excalidraw \
-o "~/Documents/My Vault/path/to/diagram.excalidraw.md"Or write the wrapper yourself — it's just frontmatter + text element index + JSON in a code block (see Obsidian format section below).
For simple diagrams where auto-color-by-shape-type is fine:
python3 ~/.hermes/skills/creative/excalidraw/scripts/hybrid.py quick \
--dsl "(Start) -> [Process] -> {OK?} -> (End)" \
--title "My Flow" \
-o diagram.excalidraw.mdAuto-colors: rectangle=#a5d8ff blue, diamond=#fff3bf yellow, ellipse=#d0bfff purple.
For architecture diagrams, sequence diagrams, concept maps, spatial layouts — anything the DSL can't express. Hand-place elements with x/y coordinates.
WARNING:
"label": { "text": "..." }is NOT valid. Use container binding.
{ "type": "rectangle", "id": "r1", "x": 100, "y": 100, "width": 200, "height": 80,
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
"boundElements": [{ "id": "t_r1", "type": "text" }] },
{ "type": "text", "id": "t_r1", "x": 105, "y": 110, "width": 190, "height": 25,
"text": "Hello", "fontSize": 20, "fontFamily": 1, "strokeColor": "#1e1e1e",
"textAlign": "center", "verticalAlign": "middle",
"containerId": "r1", "originalText": "Hello", "autoResize": true }{ "type": "arrow", "id": "a1", "x": 300, "y": 150, "width": 150, "height": 0,
"points": [[0,0],[150,0]], "endArrowhead": "arrow",
"startBinding": { "elementId": "r1", "fixedPoint": [1, 0.5] },
"endBinding": { "elementId": "r2", "fixedPoint": [0, 0.5] } }fixedPoint: top=[0.5,0] bottom=[0.5,1] left=[0,0.5] right=[1,0.5]
Arrowheads: null | "arrow" | "bar" | "dot" | "triangle"
Stroke: "solid" | "dashed" | "dotted"
Array order = z-order. Emit: shape → its bound text → its arrows → next shape.
- Font: min 16 body, min 20 titles, never below 14
- Shapes: min 120x60 for labeled elements
- Spacing: min 20-30px gaps
| Use | Hex |
|---|---|
| Primary / Input | #a5d8ff |
| Success / Output | #b2f2bb |
| Warning / External | #ffd8a8 |
| Processing / Special | #d0bfff |
| Error / Critical | #ffc9c9 |
| Decisions | #fff3bf |
| Storage / Data | #c3fae8 |
Text contrast minimum on white: #757575. No emoji in text (won't render).
Always use for vault files. Extension: .excalidraw.md
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
# Excalidraw Data
## Text Elements
Label Text ^elementId
Another Label ^anotherId
%%
## Drawing
```json
{ ...excalidraw JSON... }%%
Renders inline via `![[file]]` in Reading View (Cmd+E).
---
## Uploading / PNG Export
**Shareable link:**
```bash
python3 ~/.hermes/skills/creative/excalidraw/scripts/upload.py diagram.excalidraw
PNG for chat (no headless export exists):
- Upload → get URL
browser_navigate(url)→Ctrl+A→Ctrl+-(repeat) →Alt+Z→Escapebrowser_vision→MEDIA:<path>
- See
references/colors.md,references/dark-mode.md,references/examples.md @excalidraw/utilsnpm DOES NOT WORK headless (needs browser DOM)- Export dialog (
Ctrl+Shift+E) tends to timeout on clicks