mcpField
S
stack-diagram

stack-diagram

github.com/caru-iniDesign & UI1.1k371 installsUpdated 1d ago
Repository

About stack-diagram

A skill for producing engineering-style architecture diagrams from a list of technologies and services. It builds a self-contained HTML source with official icons, grouped components, and labeled connections, then derives requested formats such as PNG or TSX.

Key features

  1. 01Build diagrams from nodes and flows
  2. 02Fetch official logos and icons
  3. 03Group related components visually
  4. 04Generate self-contained HTML
  5. 05Render PNG and derive TSX

Use cases

  1. 01Map a service architecture
  2. 02Show deployment and runtime groupings
  3. 03Document primary and secondary data flows
  4. 04Export a diagram as PNG or TSX

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/caru-ini/stack-diagram --skill stack-diagram

After installing, type/stack-diagramin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

LICENSE
assets/
template.html
assets/fonts/
OFL-Geist.txt
OFL-JetBrainsMono.txt
README.md
assets/generic/
browser.svg
device.svg
internet.svg
server.svg
user.svg
references/
geometry.md
tsx.md
scripts/
build.py
fetch_icons.py
render_png.py

Details

From the project README

stack-diagram

Produces engineering-drawing-style architecture diagrams: real logos on white tiles, labeled group boxes (like cloud-vendor VPC diagrams), orthogonal arrows with monospace labels, on a cool graph-paper background. One self-contained HTML file is the source of truth; PNG / TSX / artifact are derived from it.

Requirements

  • Python 3.9+ (standard library only for fetch_icons.py and build.py)
  • Network access to the icon sources (svgl.app, raw.githubusercontent.com, cdn.jsdelivr.net, plus the brand's own site or repo for name=URL) when fetching icons
  • Playwright + Chromium for render_png.py: pip install playwright && python3 -m playwright install --with-deps chromium

Workflow

  1. Settle the content (nodes, groups, edges)
  2. Fetch icons — scripts/fetch_icons.py
  3. Plan coordinates — read references/geometry.md
  4. Build the HTML from assets/template.html — scripts/build.py
  5. Render a PNG and look at it before delivering
  6. Deliver in the requested format(s)

Step 1 — Content

From the user's list of technologies/services, decide:

  • Nodes: each tech = one node (name + a short lowercase role like http server, sqlite db)
  • Groups: draw a box around things that share a runtime, vendor, or repo (e.g. a cloud provider box containing a runtime box; a monorepo box for tooling). Nesting one level deep is fine; deeper gets noisy. Write group labels in the brand's own casing (GitHub, npm); the template shows them as written.
  • Edges, three kinds:
    • accent-colored solid = the primary request/data flow (one continuous path reads best)
    • neutral solid = secondary internal connections
    • neutral dashed = dev-time / deploy-time relationships (CI, git hooks, IaC deploy)

If the user gave only a flat list of tools, infer sensible groups and flows from what the tools are — only ask when a tool is ambiguous enough that the diagram would be wrong either way.

Step 2 — Icons

python3 scripts/fetch_icons.py <icons-dir> tanstack hono drizzle "betterauth:better-auth" "orpc=https://orpc.unnoq.com/logo.webp"

Spec forms: name (name used as slug), name:slug (different slug), name=URL (explicit URL). The script tries, in order: svgl → devicon → gilbarbara/logos, writes files plus manifest.json, and flags icons whose fill is white. svgl (svgl.app) is first because it carries current official marks for modern dev brands (TanStack, Better Auth, Hono...). When a logo has light/dark versions, the light one becomes <name> (tiles are white in both themes) and the dark one is saved as <name>_dark for group labels. svgl is matched by exact title only, so a miss just falls through. Its API also exposes wordmark variants — occasionally useful for a wide group-label, never for tiles.

Rules that matter more than the mechanics:

  • Never recolor a logo. Users notice. If a logo is white or too light for a white tile, put it on a dark tile (tile dark class) instead of touching its fill. The manifest's white_fill flag is a heuristic — a white fill can be an inner detail on a colored mark (tRPC, Ubuntu), so glance at the SVG before reaching for the dark tile.
  • Official marks only. simple-icons is deliberately not a source: its glyphs are monochrome redraws, and users call them wrong on sight. When every source misses (FAIL), find the official file and pass it as name=URL — look in the project's repo (root logo*.svg, docs/, assets/, public/) and on its site (/logo.svg, /favicon.svg, /images/logo.svg). Some marks live only in the repo (Lefthook's is logo_sign.svg at the root of evilmartians/lefthook).
  • Abstract concepts get pictograms, not text. A node like "dev environment", "cron", "queue", "cache", or "webhook receiver" has no brand — reach for a Lucide line icon via name:lucide/<icon> (e.g. devenv:lucide/terminal, cron:lucide/clock; browse lucide.dev/icons). No text-in-a-tile, no emoji.
  • Vendor sub-products (Cloudflare D1/R2/KV, AWS's endless services...) usually have no distinct logo: just reuse the vendor logo on the tile and let the node name carry the distinction ("Cloudflare D1" / "sqlite db").
  • Non-brand nodes (user, browser, internet, devices, bare-metal server): copy a glyph from assets/generic/ into the icons dir under the node's name, or use name:lucide/<icon> — don't use emoji. These aren't brand logos, so tinting them to the diagram's ink color is fine (the fetch script does it for Lucide automatically).
  • Group-label icons render at 14px — they need a square glyph mark. Wordmarks and wide lockups turn to mud at that size; if the main icon is a wordmark, fetch the brand's square icon separately for the label (svgl usually has it; otherwise the project's own icon or favicon file).
  • Group labels sit on the group's tint, not on a white tile, so in the dark theme a mark with black parts vanishes there. If fetch_icons.py saved <name>_dark, use the template's lt/dk pair in the label; if there is no dark variant and the mark has black parts, add class plate to the label's <img> (white backing, dark theme only).
  • Raster logos (png/webp) are fine; the build script embeds everything as data URIs, so nothing is loaded at view time.

Step 3 — Layout

Read references/geometry.md for the coordinate system, spacing constants, and — most importantly — the edge anchoring rules that prevent arrows from running through label text. The one-line version: a node is its tile plus the text under it; vertical arrows connect node-box boundaries, never tile-to-tile through text.

Step 4 — Build

Copy assets/template.html, replace the sample GROUPS / EDGES / NODES / header sections with yours (the template's markup doubles as format documentation), then inline the assets. Set lang on <html> to the language of the diagram's labels (en by default; use ja for Japanese so browsers pick Japanese glyph variants). Two options the template already carries: a dark variant (set data-theme="dark" on <html>; tiles stay white so logos survive, group-label icons follow the Step 2 rule, and arrowheads and legend swatches follow the tokens automatically), and a "deploy to platform X" edge where X is a group, not a node — point the arrow at the most representative node inside the group and label the edge (deploy), which reads correctly.

python3 scripts/build.py my-diagram.html <icons-dir> out.html --fonts <skill>/assets/fonts

Icons are referenced in markup as %%ICON_<name>%%; fonts as %%FONT_DISPLAY%% (Geist) / %%FONT_JBM%% (JetBrains Mono). The build fails loudly on unresolved tokens. Keep every color and font behind the CSS custom properties already in the template — tweak the tokens, not scattered values.

Step 5 — Verify by looking

Render and actually read the image:

python3 scripts/render_png.py out.html check.png --selector .canvas --scale 2

If the diagram contains Japanese text, the render host needs a Japanese font, or CJK characters silently come out in Chinese glyph variants (users notice immediately). The script warns and prints a fix; the template's font stack already names the Noto JP faces.

Checklist while looking at it:

  • No arrow crosses label text; no arrowhead lands inside text
  • Edge labels readable (they have a paper-colored halo — confirm it's working)
  • Every icon visible (white-on-white is the classic failure → dark tile), including group-label icons in the dark render
  • Group labels sit in their group's top-left corner, clear of nodes and edges; each group's left and right padding match
  • Nothing sticks out of its group box; nothing clipped at canvas edge

Fix and re-render until clean. This loop is cheap; shipping a diagram with overlapping text is not.

Step 6 — Deliver

  • HTML (default): the built file itself. Self-contained, works offline.
  • PNG: always produced by screenshotting the built HTML with headless Chromium (render_png.py, --scale 2 for crisp text) — never by drawing a separate image with matplotlib/PIL/standalone SVG rasterizers; the HTML is the single source of truth and the browser's text/SVG rendering is what makes it look right. By default the script emits BOTH themes (out.png light + out-dark.png) with square corners (radius is stripped for the screenshot only) — deliver both unless the user asked for one; --single captures the document as-authored. Target .canvas for just the diagram, --full-page for the whole page.
  • TSX: convert per references/tsx.md (self-contained React component, no deps).
  • Artifact (where the environment offers an artifact or page-publishing tool): publish the built HTML with the environment's artifact tool. Everything is already inlined as data URIs, so the artifact's blocked-network sandbox is a non-issue. Prefer this when the user will iterate on the diagram in conversation; pair it with a PNG when they need something to paste elsewhere.

If several formats are requested, build the HTML once and derive the rest from it.

In the footer, credit the icon sources actually used — the source field of each entry in the icons dir's manifest.json (e.g. icons: svgl / devicon / Lucide). Brand logos remain the trademarks of their owners. Keep the template's "made with stack-diagram" link after the credits unless the user asks to remove it; a default PNG captures only the canvas, so the link stays out of it.

Comments

Markdown supported