skills/excalidraw/SKILL.md
Create Excalidraw diagrams programmatically. Use when the user wants architecture diagrams, system designs, flowcharts, or any visual diagram with a hand-drawn aesthetic. Outputs editable .excalidraw JSON files. Best for architecture overviews and presentations where the hand-drawn style and drag-and-drop editing add value over Mermaid's text-only approach.
npx skillsauth add tslateman/duet excalidrawInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate Excalidraw diagrams programmatically using Python. Produces professional-looking, editable .excalidraw files instead of ASCII art.
Output format: .excalidraw JSON files that can be:
Token cost note: Excalidraw JSON is verbose (4,000-22,000+ tokens per file). Delegate diagram generation to subagents when context is tight.
Write a Python script using the generator library and run it:
#!/usr/bin/env python3
import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import Diagram, Flowchart, ArchitectureDiagram
# Create your diagram
d = Diagram()
box1 = d.box(100, 100, "Step 1", color="blue")
box2 = d.box(300, 100, "Step 2", color="green")
d.arrow_between(box1, box2, "next")
d.save("my_diagram.excalidraw")
Run with:
python3 /path/to/your_script.py
python3 -c "
import sys, os
sys.path.insert(0, os.path.expanduser('~/.claude/plugins/duet/skills/excalidraw/scripts'))
from excalidraw_generator import Diagram
d = Diagram()
a = d.box(100, 100, 'Hello', color='blue')
b = d.box(300, 100, 'World', color='green')
d.arrow_between(a, b)
d.save('hello.excalidraw')
print('Created: hello.excalidraw')
"
The base class for all diagrams.
from excalidraw_generator import Diagram
d = Diagram(background="#ffffff") # white background
box(x, y, label, width=150, height=60, color="blue", shape="rectangle", font_size=18)
Create a labeled shape. Returns an Element for connecting.
x, y: Position (top-left corner)label: Text to displaycolor: "blue", "green", "red", "yellow", "orange", "violet", "cyan", "teal", "gray", "black"shape: "rectangle", "ellipse", "diamond"box1 = d.box(100, 100, "Process A", color="blue")
box2 = d.box(100, 200, "Decision?", color="yellow", shape="diamond")
text_box(x, y, content, font_size=20, color="black")
Create standalone text.
d.text_box(100, 50, "System Architecture", font_size=28, color="black")
arrow_between(source, target, label=None, color="black", from_side="auto", to_side="auto")
Draw an arrow between two elements.
from_side, to_side: "auto", "left", "right", "top", "bottom"d.arrow_between(box1, box2, "sends data")
d.arrow_between(box1, box3, from_side="bottom", to_side="top")
line_between(source, target, color="black")
Draw a line (no arrowhead) between elements.
save(path)
Save the diagram. Extension .excalidraw added if not present.
d.save("output/my_diagram") # Creates output/my_diagram.excalidraw
Specialized for flowcharts with automatic positioning.
from excalidraw_generator import Flowchart
fc = Flowchart(direction="vertical", spacing=80)
fc.start("Begin")
fc.process("p1", "Process Data")
fc.decision("d1", "Valid?")
fc.process("p2", "Save")
fc.end("Done")
fc.connect("__start__", "p1")
fc.connect("p1", "d1")
fc.connect("d1", "p2", label="Yes")
fc.connect("d1", "__end__", label="No")
fc.save("flowchart.excalidraw")
start(label="Start") - Green ellipseend(label="End") - Red ellipseprocess(node_id, label, color="blue") - Blue rectangledecision(node_id, label, color="yellow") - Yellow diamondnode(node_id, label, shape, color, width, height) - Generic nodeconnect(from_id, to_id, label=None) - Arrow between nodesposition_at(x, y) - Set position for next nodeFor complex flowcharts with automatic hierarchical layout. Requires grandalf package (pip install grandalf).
from excalidraw_generator import AutoLayoutFlowchart, DiagramStyle, FlowchartStyle, LayoutConfig
fc = AutoLayoutFlowchart(
diagram_style=DiagramStyle(roughness=0),
flowchart_style=FlowchartStyle(decision_color="orange"),
layout_config=LayoutConfig(vertical_spacing=100, horizontal_spacing=80)
)
fc.add_node("start", "Start", shape="ellipse", color="green", node_type="terminal")
fc.add_node("process1", "Validate Input", node_type="process")
fc.add_node("check", "Is Valid?", shape="diamond", color="yellow", node_type="decision")
fc.add_node("success", "Process Data", node_type="process")
fc.add_node("error", "Show Error", color="red", node_type="process")
fc.add_node("end", "End", shape="ellipse", color="red", node_type="terminal")
fc.add_edge("start", "process1")
fc.add_edge("process1", "check")
fc.add_edge("check", "success", label="Yes")
fc.add_edge("check", "error", label="No")
fc.add_edge("success", "end")
fc.add_edge("error", "process1", label="Retry")
result = fc.compute_layout(two_column=True, target_aspect_ratio=0.8)
fc.save("auto_flowchart.excalidraw")
For system architecture diagrams.
from excalidraw_generator import ArchitectureDiagram
arch = ArchitectureDiagram()
arch.user("user", "User", x=100, y=200)
arch.component("frontend", "React App", x=250, y=200, color="blue")
arch.service("api", "API Gateway", x=450, y=200, color="violet")
arch.database("db", "PostgreSQL", x=650, y=200, color="green")
arch.connect("user", "frontend", "HTTPS")
arch.connect("frontend", "api", "REST")
arch.connect("api", "db", "SQL")
arch.save("architecture.excalidraw")
component(id, label, x, y, width=150, height=80, color="blue")database(id, label, x, y, color="green") - Ellipse shapeservice(id, label, x, y, color="violet")user(id, label="User", x=100, y=100) - Gray ellipseconnect(from_id, to_id, label=None, bidirectional=False)from excalidraw_generator import Diagram, DiagramStyle
d = Diagram(diagram_style=DiagramStyle(
roughness=0, # 0=clean, 1=hand-drawn, 2=rough sketch
stroke_style="solid", # "solid", "dashed", "dotted"
stroke_width=2, # Line thickness (1-4)
color_scheme="corporate" # Named color scheme
))
Roughness levels:
0 - Architect: Clean, precise lines1 - Artist: Normal hand-drawn look (default)2 - Cartoonist: Rough, sketchy appearanced = Diagram(diagram_style=DiagramStyle(color_scheme="vibrant"))
primary = d.scheme_color("primary")
secondary = d.scheme_color("secondary")
accent = d.scheme_color("accent")
warning = d.scheme_color("warning")
danger = d.scheme_color("danger")
neutral = d.scheme_color("neutral")
| Scheme | Primary | Secondary | Accent | Warning | Danger | | ---------- | ------- | --------- | ------ | ------- | ------ | | default | blue | green | violet | yellow | red | | monochrome | black | gray | gray | gray | black | | corporate | blue | teal | violet | orange | red | | vibrant | violet | cyan | orange | yellow | red | | earth | teal | green | orange | yellow | red |
| Color | Stroke Hex | Use For |
| -------- | ---------- | ------------------- |
| blue | #1971c2 | Primary components |
| green | #2f9e44 | Success, databases |
| red | #e03131 | Errors, end states |
| yellow | #f08c00 | Warnings, decisions |
| orange | #e8590c | Highlights |
| violet | #6741d9 | Services |
| cyan | #0c8599 | Network |
| teal | #099268 | Secondary |
| gray | #868e96 | Users, actors |
| black | #1e1e1e | Text, arrows |
import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import Diagram
d = Diagram()
d.text_box(200, 30, "Data Processing Pipeline", font_size=24)
input_box = d.box(100, 100, "Input", color="gray")
process = d.box(300, 100, "Process", color="blue")
output = d.box(500, 100, "Output", color="green")
d.arrow_between(input_box, process, "raw data")
d.arrow_between(process, output, "results")
d.save("pipeline.excalidraw")
import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import Flowchart
fc = Flowchart(direction="vertical", spacing=100)
fc.start("User Request")
fc.process("auth", "Authenticate")
fc.decision("valid", "Valid Token?")
fc.position_at(300, 340)
fc.process("proc", "Process Request")
fc.process("resp", "Return Response")
fc.position_at(100, 340)
fc.process("err", "Return 401")
fc.connect("__start__", "auth")
fc.connect("auth", "valid")
fc.connect("valid", "proc", "Yes")
fc.connect("valid", "err", "No")
fc.connect("proc", "resp")
fc.save("auth_flow.excalidraw")
import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import ArchitectureDiagram
arch = ArchitectureDiagram()
arch.user("client", "Client", x=400, y=50)
arch.service("gateway", "API Gateway", x=350, y=180, color="violet")
arch.service("auth", "Auth Service", x=100, y=350, color="blue")
arch.service("users", "User Service", x=300, y=350, color="blue")
arch.service("orders", "Order Service", x=500, y=350, color="blue")
arch.service("notify", "Notification", x=700, y=350, color="cyan")
arch.database("authdb", "Auth DB", x=100, y=500, color="green")
arch.database("userdb", "User DB", x=300, y=500, color="green")
arch.database("orderdb", "Order DB", x=500, y=500, color="green")
arch.component("queue", "Message Queue", x=600, y=450, color="orange")
arch.connect("client", "gateway", "HTTPS")
arch.connect("gateway", "auth", "gRPC")
arch.connect("gateway", "users", "gRPC")
arch.connect("gateway", "orders", "gRPC")
arch.connect("auth", "authdb", "SQL")
arch.connect("users", "userdb", "SQL")
arch.connect("orders", "orderdb", "SQL")
arch.connect("orders", "queue", "publish")
arch.connect("queue", "notify", "subscribe")
arch.save("microservices.excalidraw")
After generating a .excalidraw file:
open filename.excalidraw on macOS# First time setup
cd ~/.claude/plugins/duet/skills/excalidraw/scripts
npm install
npx playwright install chromium
# Export
node ~/.claude/plugins/duet/skills/excalidraw/scripts/export_playwright.js diagram.excalidraw output.png
/mermaid - For inline documentation diagrams that render natively on GitHub/design - Design thinking applies to diagram composition and layout/prose - Diagram labels benefit from the same concision rules as prosedevelopment
Judgment linter for vibe-coded output — reads the energy of the code, not just correctness. Use when the user says "vibe check", "check this vibe code", "does this hold up", "sanity check this AI code", or after a fast generation session before committing.
tools
Survey the project and choose what to play next
development
Design test strategy using Beck's Test Desiderata — which properties matter, which tradeoffs to make. Use when the user asks "how should I test this", "what tests do I need", "review my test strategy", "is this well-tested", or when planning tests for a new feature or refactor.
testing
Post-op check for artifacts, damage, and stale references after agent work