Install
$ agentstack add skill-aeonbook-aeon-excalidraw-diagram-excalidraw-diagram ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
> ${var} — REQUIRED. A drawfile slug. Must match an existing drawfiles/.drawfile at the repo root. If no drawfile exists, the skill aborts with EXCALIDRAW_NO_DRAWFILE and prints the scaffold command.
Today is ${today}. Your task: read a drawfile, generate a .excalidraw v2 scene file via a script-driven path that uses only the pattern helpers, then pass the result through a machine validator. If the validator fails after one fix pass, abort cleanly. The output is portable JSON renderable anywhere (excalidraw.com, the @excalidraw/excalidraw React component, server-side SVG/PNG exporters).
Mental model
You are NOT drawing on a live canvas. You are writing a JavaScript generate script that imports references/render-helpers.mjs and emits a v2 .excalidraw file. The script becomes a reproducible artifact — re-running it must produce the same scene. Authoring raw JSON is off the table; the helpers handle defaults, the design system, and grid snapping for you. The validator catches anything the helpers don't.
drawfile → Step 1 parse → Step 2 plan + budget check → Step 3 generate-script.mjs →
│
▼
.excalidraw JSON file
│
▼
Step 4 machine validator (PASS) ─── Step 5 article + log + notify
│
▼ FAIL ─→ fix → re-validate → FAIL again → EXCALIDRAW_VALIDATION_FAILED
HARDCODED design system (read every run)
Before doing anything else, read references/design-system.md. Rules there override every other consideration:
- Strict two-color rule: black
#1e1e1efor ALL strokes + text; ONE accent from six palettes; neutrals (#ffffff,#f8fafc,#64748b,transparent) for everything else. No other hex codes anywhere. - 60-30-10: 60% whitespace + neutrals, 30% accent, 10% black.
- Universal defaults:
roughness: 1,roundness: { type: 3 },strokeWidth: 2-3. - Grid: shape
x,y,width,heightsnap to multiples of 20 (prefer 40 / 60). - Typography: title 32-40, headers 24-28, body 18-20, notes 14-16. fontFamily 1 (Virgil) default.
- Hierarchy via size + position FIRST, color SECOND.
If you feel tempted to reach for a second accent, shrink margins, or fudge a coordinate — STOP. The helpers and validator are the authority, not your judgment.
Reference files (read in this order)
| File | When | |---|---| | references/design-system.md | Every run, first. | | references/drawfile-spec.md | Step 1, to parse the drawfile. | | references/catalog.md | Step 2, to confirm diagram type + see element-cost range. | | references/patterns.md | Step 2 (budget math) + Step 3 (composition). | | references/schema.md | Step 3 only if you need to look up a field. | | references/render-helpers.mjs | Step 3 — imports for the generate script. | | examples/aeon-self-heal.generate.mjs | Step 3 — canonical reference for what a good generate script looks like. Read this before writing your own. | | examples/aeon-self-heal.excalidraw | The known-good output paired with the script above. |
Steps
1. Parse the drawfile
DRAWFILE="drawfiles/${var}.drawfile"
If ${var} is empty:
./notify "excalidraw-diagram aborted: var empty — pass a drawfile slug"
Exit with EXCALIDRAW_NO_VAR.
If the file does not exist at drawfiles/${var}.drawfile:
./notify "excalidraw-diagram aborted: drawfiles/${var}.drawfile not found. Bootstrap one with: ./skills/excalidraw-diagram/scaffold-drawfile ${var}"
Exit with EXCALIDRAW_NO_DRAWFILE. Do not attempt to interpret ${var} as a catalog slug or free-form spec — drawfile is the only input mode.
Read the drawfile per references/drawfile-spec.md. Extract:
- Frontmatter:
slug,diagram_type,palette,canvas,audience,font,style_ref,constraints(structured),sources. - Body sections: Subject, What to show, Context, Emphasis, Notes.
- For each path in
sources, read the file (fail withEXCALIDRAW_DRAWFILE_SOURCE_MISSINGif any is absent — never silently proceed without context). - Validate
paletteis one of the six valid names. Validateconstraintskeys are recognized. Invalid →EXCALIDRAW_DRAWFILE_INVALID. - If
style_refis set and the referenced article exists, read it.
If diagram_type is absent, pick one via the picker table at the bottom of catalog.md based on Subject + What to show.
If palette is absent, pick one via design-system.md §2 keyword rules (default modernTeal).
2. Plan + budget check (do BEFORE generating)
Read context: memory/MEMORY.md, any sources listed in the drawfile, soul/STYLE.md if present (for article voice; the diagram itself stays design-system-locked).
Compose your patterns on paper. Sketch tier-by-tier (y bands) and column-by-column (x bands) inside a scratch comment block. All coordinates multiples of 40 (or 60 on larger canvases). Whitespace margins ≥ min_whitespace_margin on every side.
Then do the element-count math using the pattern costs from patterns.md. Example:
A1 architecture composition:
1 P11 title 1
3 P1 lanes (2 each) 6
6 P2 sub-cards w/ bound label 6
2 P4 action buttons 4
5 P10 numbered steps + 4 arrows 14
1 P11 section header 1
─────
32
If your total > constraints.max_elements, you have a choice:
- Drop a section (lose the file row, the function lists, etc.)
- Raise
max_elementsin the drawfile (if the operator's budget was unrealistic — but flag this; usually the drawfile is right)
Do not start generation until the math works. If you cannot get the composition under budget without dropping the diagram's intent:
./notify "excalidraw-diagram aborted: composition for ${var} requires N elements but max_elements=M. Drop sections or raise max_elements in drawfile."
Exit with EXCALIDRAW_BUDGET_EXCEEDED.
3. Write the generate script (NOT raw JSON)
Create scripts/generate-.mjs at the repo root (NOT in skills/). This is the canonical artifact — committed alongside the .excalidraw file, re-runnable, diffable.
Read examples/aeon-self-heal.generate.mjs before writing your own to see the exact shape. Hard requirements:
- Imports from
skills/excalidraw-diagram/references/render-helpers.mjs— primitives, pattern helpers,PALETTES,buildSceneFile. No direct hex codes; always referencePAL.accent,PAL.lightTint,neutrals.*. - Resets ids at start (
ex.resetIds()). - Picks the palette via
PALETTES[]matching the drawfile. - Uses pattern helpers (
lane,subCard,flowStep,pill,actionButton,fileCard,sectionHeader,boundArrow,curveArrow,msgArrows,fanOutArrow) — NOT raw primitives, unless a primitive is the natural choice (e.g. a single decorativeline). - All coordinate inputs are multiples of 40 (preferred) or 60 (multiples of 20 minimum). The helpers
_assertGrid()throws if you pass anything else. - For step-and-arrow flows: override the auto-generated rect id with a stable one (e.g.
stepRect.id = 'step_3') so subsequentboundArrowcalls can reference it. Also setstepText.containerId = stepRect.idso the validator can associate labels with shapes. - Builds the scene with
buildSceneFile(els, { backgroundColor: '#ffffff' }). - Emits the JSON on stdout:
console.log(JSON.stringify(scene, null, 2));
Then run it:
node scripts/generate-.mjs > diagrams/-${today}.excalidraw
If the script throws (likely from _assertGrid catching a non-snapped coord), fix the offending input in the script and re-run. Never silence the assertion — the assertion is the design system enforcing itself.
4. Machine quality gate (MANDATORY — run, don't think)
Run the validator. Do NOT skip this and claim the diagram looks fine. Do NOT mental-walkthrough the rules. The validator is the source of truth.
node skills/excalidraw-diagram/scripts/validate.mjs \
diagrams/-${today}.excalidraw \
drawfiles/${var}.drawfile
The validator prints structured JSON to stdout and exits non-zero on any violation. Read the JSON, look at violations[] — each violation has element_id, rule, expected, actual, fix_hint. Fix each one element-by-element in the generate script (not by hand-editing the .excalidraw file), then re-run the generate script + validator.
The retry loop is exactly one pass. After the first re-run:
- If validator exits 0 → continue to Step 5.
- If validator exits 1 again → abort.
Abort sequence on second failure:
- Move the failed scene and the generate script to
memory/excalidraw-failures/${today}-/for post-mortem. - Append the violation list to the failure dir as
violations.json. - Delete
diagrams/-${today}.excalidraw. ./notify "excalidraw-diagram FAILED for ${var}: . Failure artifacts at memory/excalidraw-failures/${today}-/"- Exit
EXCALIDRAW_VALIDATION_FAILED.
Do not ship a non-compliant diagram. Aborting cleanly is always better than committing broken output.
5. Write the companion article
Only after step 4 passes. Write articles/-${today}.md:
#
**
## What this shows
## Reading guide
- ****:
- :
- :
- :
## Built with
- Diagram type: ()
- Palette: (accent ``)
- Canvas: ×
- Audience:
- Drawfile: `drawfiles/.drawfile`
- Generate script: `scripts/generate-.mjs`
- Element count: (budget: )
- Validator: PASS
If soul/ is populated, match its voice in the prose sections. The diagram itself stays design-system-locked regardless of voice.
6. Log
Append to memory/logs/${today}.md:
### excalidraw-diagram
- Slug:
- Drawfile: drawfiles/.drawfile
- Diagram type:
- Palette: (accent )
- Canvas: ×
- Element count: /
- Validator: PASS (1st try) | PASS (after 1 fix) | FAILED (twice)
- Generate script: scripts/generate-.mjs
- Scene file: diagrams/-${today}.excalidraw
- Article: articles/-${today}.md
- Status: EXCALIDRAW_OK
7. Notify
*excalidraw-diagram — *
Type: · Palette: · elements (validated)
Scene: /blob/main/diagrams/-${today}.excalidraw
Article: /blob/main/articles/-${today}.md
Script: /blob/main/scripts/generate-.mjs
Under 4000 chars. Don't paste the JSON.
Exit taxonomy
| Code | When | Action | |---|---|---| | EXCALIDRAW_OK | Scene + article written, validator PASS | Notify with links | | EXCALIDRAW_NO_VAR | ${var} empty | Notify abort reason; stop | | EXCALIDRAW_NO_DRAWFILE | drawfiles/${var}.drawfile doesn't exist | Notify with scaffold command; stop | | EXCALIDRAW_DRAWFILE_INVALID | Frontmatter unparseable, invalid palette, malformed constraints | Notify with parse error; stop | | EXCALIDRAW_DRAWFILE_SOURCE_MISSING | A sources: file doesn't exist | Notify with missing path; stop | | EXCALIDRAW_BUDGET_EXCEEDED | Composition estimate exceeds max_elements, no clean drop | Notify with N vs M; stop | | EXCALIDRAW_VALIDATION_FAILED | Validator fails twice (one fix pass attempted) | Persist failure artifacts; delete partial scene; notify; stop |
Inline invocation contract (for other skills calling this one)
When another skill needs to embed a diagram, it can inline-invoke this skill rather than dispatching a separate workflow.
Caller writes:
.outputs/excalidraw-brief.md— a drawfile-format file. Same schema asdrawfiles/.drawfile.
Caller invokes:
- Reads
skills/excalidraw-diagram/SKILL.md - Executes steps 2-7 with
.outputs/excalidraw-brief.mdas input (skip step 1's var parsing — the brief replaces it).
Caller reads back:
.outputs/excalidraw-result.json—{ diagramPath, articlePath, scriptPath, elementCount, palette, exitCode }
The caller embeds diagramPath in its own output (e.g. the article it's writing).
Sandbox note
This skill is fully offline. No curl, no WebFetch, no MCP, no canvas server. The only I/O:
- Read
references/*.md,references/render-helpers.mjs,examples/*(skill files) - Read
drawfiles/${var}.drawfileand anysources:files - Read
memory/MEMORY.md, optionallymemory/topics/*.md,soul/*.md - Execute
node scripts/generate-.mjs— runs JS in the sandbox; allowed and required. - Execute
node skills/excalidraw-diagram/scripts/validate.mjs— same. - Write
diagrams/-${today}.excalidraw,articles/-${today}.md,scripts/generate-.mjs - Append to
memory/logs/${today}.md - On failure: write
memory/excalidraw-failures/${today}-/{generate.mjs, scene.failed.excalidraw, violations.json} - Call
./notify
The two node invocations replace what used to be mental walkthroughs. They are mandatory, not optional.
Constraints (don't break)
- One diagram per run. If the spec implies multiple, pick the most important and mention the others as candidates in the article footer.
- Never bypass the validator. Claiming "the diagram looks fine" without
validate.mjsexit 0 is a process violation. The skill ships only what the machine says is correct. - Never author raw JSON. Always go through
scripts/generate-.mjsusing the helpers. Raw JSON authoring is how mental-math grid errors get past the helpers' guards. - Never call a canvas server. This skill never talks to
localhost:3000orlocalhost:8080. The output is a file, not a side effect. - Never invent color hexes. The seven valid hexes per diagram are:
#1e1e1e,#ffffff,#f8fafc,#64748b,transparent, plus the chosen palette'saccentandlightTint. Validator enforces. - Drawfile is contract. Constraints in the drawfile (maxelements, maxtitlechars, requiredfocal, …) are hard fails, not suggestions. If the operator's budget is impossible,
EXCALIDRAW_BUDGET_EXCEEDEDand let them fix the drawfile.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: aeonbook
- Source: aeonbook/aeon-excalidraw-diagram
- License: MIT
- Homepage: https://github.com/aeonbook/aeon-excalidraw-diagram
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.