# claudemaster.com — full page summary > Start agent work on Claude Opus 5 (`claude-opus-5`); move to Claude Fable 5.1 for long-horizon runs. Sonnet 5 is the balance point, Haiku 4.5 the cheap tier. Independent reading room, not a news site. Not affiliated with Anthropic. Canonical site: https://claudemaster.com Last reviewed: 2026-09-20 Desk figures as of 2026-09-19, sourced to Anthropic published list prices. Independent publication. Not affiliated with, endorsed by, or sponsored by Anthropic. ## Signature: The Harness Desk The homepage (https://claudemaster.com/) is the desk. Routing calls are this site's editorial judgement applied to Anthropic's published guidance. Vendor benchmark numbers are labelled as such and are not independent measurements. | Model | API ID | In/out per MTok | Cache read per MTok | Context | Max out | |---|---|---|---|---|---| | Claude Fable 5.1 | `claude-fable-5-1` | $10 / $50 | $0.25 (2.5%) | 1M | 128K | | Claude Opus 5 | `claude-opus-5` | $5 / $25 | $0.50 (10%) | 1M | 128K | | Claude Sonnet 5 | `claude-sonnet-5` | $2 / $10 | $0.20 (10%) | 1M | 128K | | Claude Haiku 4.5 | `claude-haiku-4-5` | $1 / $5 | $0.10 (10%) | 200K | 64K | - Starting a new harness → `claude-opus-5`. Start on Opus 5. Anthropic’s own routing guidance is to make it the default for most workloads, and at $5/$25 per MTok it is half Fable 5.1’s base rate for the same 1M context window and 128K output ceiling. - Long-horizon agentic runs → `claude-fable-5-1`. Fable 5.1 is the documented reach for demanding reasoning and long-horizon agentic work. On Anthropic’s published numbers its margin over Opus 5 is widest exactly here — Terminal-Bench-Science 0.1 at 52.6% against 29.0%, AutomationBench at 31.4% against 26.9% — while CursorBench 3.2.0 has them within a few points. - Opus 5 is missing your evals → `claude-fable-5-1`. This is the escalation Anthropic names explicitly: when evals on Opus 5 at higher effort still fall short, move to Fable 5.1. - Large cached prefix, many turns → `claude-fable-5-1`. Fable 5.1 bills cache reads at 2.5% of base input — $0.25 per MTok — where Opus 5 bills 10%, or $0.50. It is twice the base input price and half the cache-read price, and an agent loop is mostly cache reads. - Latency over the last few points → `claude-sonnet-5`. Sonnet 5 is the documented balance point between speed and intelligence, with the same 1M context and 128K output as the tiers above it at $2/$10 per MTok. - High-volume mechanical turns → `claude-haiku-4-5`. Haiku 4.5 at $1/$5 is the cheap tier, and the new per-message effort beta means the mechanical turns no longer have to inherit the session’s setting even when you keep them on a larger model. - Planner and executor split → `claude-fable-5-1`. Fable 5.1 plans well enough to justify the split, with Sonnet 5 or Haiku 4.5 executing. The handoff is where the migration cost lives. Fable 5.1 is twice Opus 5 base input and half its cache-read price, so an agent loop dominated by cache reads can be cheaper on the nominally more expensive model. Sonnet 5 introductory pricing of $2/$10 did not rise to $3/$15 on 2026-09-01; it is now the standard rate. ## Articles ### Picking a Claude Model for Agent Work: Fable 5.1, Opus 5, Sonnet 5, Haiku 4.5 URL: https://claudemaster.com/claude-model-selection-2026-09/ Published: 2026-09-19 · updated 2026-09-19 A September 2026 engineering reference — routing defaults, cache-read economics, and the Fable 5.1 changes that will break an existing agent harness. This is a reference, not a build log. Every number below comes from Anthropic's published documentation, pricing pages, and announcements, linked inline. Where I do arithmetic, it is arithmetic on their list prices and I show the work. Where I quote an eval score, it is Anthropic's published score, not something I measured. If you wrote an agent harness against Claude in early 2026 and have not touched the model configuration since, three things have changed enough to matter: Opus 5 shipped in July, Fable 5.1 shipped on September 1 and inverted the cache-read cost ordering, and Fable 5.1 introduced behavioral changes that will surface as runtime errors rather than as quietly worse output. ## The lineup as of September 2026 | Model | API ID | In / out per MTok | Context | Max out | Default effort | Knowledge cutoff | |---|---|---|---|---|---|---| | Claude Fable 5.1 | `claude-fable-5-1` | $10 / $50 | 1M | 128K | high | Jun 2026 | | Claude Opus 5 | `claude-opus-5` | $5 / $25 | 1M | 128K | high | May 2026 | | Claude Sonnet 5 | `claude-sonnet-5` | $2 / $10 | 1M | 128K | high | Jan 2026 | | Claude Haiku 4.5 | `claude-haiku-4-5` | $1 / $5 | 200K | 64K | n/a | Feb 2025 | Source: the [models overview](https://docs.anthropic.com/en/docs/about-claude/models) and [pricing](https://platform.claude.com/docs/en/about-claude/pricing). Fable 5.1, Opus 5, and Sonnet 5 all use adaptive thinking; on Fable 5.1 it is always on. Haiku 4.5 uses extended thinking and is the only model in the list still on a 200K context window and a Feb 2025 cutoff — which is the real reason to be careful about using it for anything that needs current library APIs. Retirement is not imminent for any of them. Anthropic's published floors are 2027-09-01 for Fable 5.1, 2027-07-24 for Opus 5, 2027-06-30 for Sonnet 5, and 2026-10-15 for Haiku 4.5. Haiku 4.5 is the one worth putting a calendar reminder on. Anthropic's own [routing guidance](https://platform.claude.com/docs/en/about-claude/models/choosing-a-model) is to start with Opus 5 for most workloads, and reach for Fable 5.1 for demanding reasoning and long-horizon agentic work — or when your evals on Opus 5 at higher effort still fall short. Sonnet 5 is the speed/intelligence balance point and Haiku 4.5 is the cheap, fast tier. Two corrections to claims that are still circulating: - **Opus 4.8 is not the current Opus.** [Opus 5 launched July 24, 2026](https://www.anthropic.com/news/claude-opus-5) at the same $5/$25 as Opus 4.8. - **Sonnet 5 pricing did not go up on September 1.** The $2/$10 rate was announced as introductory through 2026-08-31 and was widely expected to become $3/$15. It did not. $2/$10 is now the standard rate; the pricing page is the authority. ## Cache reads are where the cost model stopped being intuitive The interesting consequence of the [Fable 5.1 launch](https://www.anthropic.com/claude-fable-and-mythos-5-1) is not the headline price — it is the cache-read rate. Most Claude models bill a cache read at 10% of base input. Fable 5.1 and Mythos 5.1 bill cache reads at **2.5%** of base input. Multiply that out against the list prices above: | Model | Base input / MTok | Cache read rate | Cache read / MTok | |---|---|---|---| | Claude Fable 5.1 | $10 | 2.5% | **$0.25** | | Claude Opus 5 | $5 | 10% | **$0.50** | | Claude Sonnet 5 | $2 | 10% | $0.20 | | Claude Haiku 4.5 | $1 | 10% | $0.10 | Fable 5.1 is twice the base input price of Opus 5 and **half** the cache-read price. For an agent loop, that ordering is the one that matters, because an agent loop is mostly cache reads. Take a coding agent with a 200K-token cached prefix — repo map, system prompt, tool schemas, accumulated transcript — that runs 40 turns. Cache reads alone are 40 × 200K = 8M tokens: - Fable 5.1: 8 × $0.25 = **$2.00** - Opus 5: 8 × $0.50 = **$4.00** That is only the cache-read line. Fresh input and output still bill at full rate, where Fable 5.1 is the more expensive model ($10/$50 vs $5/$25), so the total comparison depends on your ratio of cached prefix to new tokens per turn. The point is that you cannot rank these two models by cost from the headline numbers. You have to know your cache hit profile. Anthropic's own framing of the change is roughly 25% savings on typical workloads and up to about 45% on highly agentic ones. Treat those as vendor figures and measure your own; the per-token rates above are the part you can verify directly. Batch API is still 50% off, and stacks. ## Effort defaults differ by surface Fable 5.1's default effort is **High in Claude Code** and **Medium on Claude.ai and Cowork**. If you are comparing output quality between your terminal and the web app and concluding the model is inconsistent, check this first — you are probably comparing two different effort settings, not two different days. There is also a new per-message effort beta, which means effort no longer has to be a property of the whole session. For a harness, the useful pattern is low effort for mechanical turns (file reads, test reruns, formatting) and high effort for the turns that actually require planning. ## Breaking changes in Fable 5.1 that hit harnesses, not chats These are called out in the [platform overview](https://platform.claude.com/docs/en/models/fable-5-1/overview) as breaking relative to Fable 5. They are easy to miss because they mostly do not affect interactive chat use — they affect programmatic orchestration. **Forced tool use now errors.** If your harness pins `tool_choice` to a specific tool to guarantee structured output, that pattern needs revisiting. This is the most likely source of a hard failure on migration. **Earlier models cannot read Fable 5.1's thinking blocks.** If you run a multi-model pipeline — Fable 5.1 plans, Sonnet 5 or Haiku 4.5 executes — you cannot pass the thinking blocks downstream. Whatever the planner needs to communicate has to be in ordinary output content. **Editing earlier turns invalidates thinking.** Any harness that rewrites conversation history — transcript compaction, retry-with-correction, replaying a run with one turn patched — loses the thinking state when it does. If you built a replay or rewind feature, test it explicitly against Fable 5.1 rather than assuming it carries over. On the additive side, the same release adds turn-scoped system messages, a `display: "updates"` progress mode, the cheaper cache reads described above, and content provenance. Turn-scoped system messages are the one I would reach for first: they let you inject per-step instructions without permanently mutating the cached system prompt, which is exactly the thing that used to force a cache invalidation. ## Published benchmarks From the Fable 5.1 announcement. These are Anthropic's numbers, cited as their claims — not independent measurements, and not something this site reproduced. | Eval | Fable 5.1 | Fable 5 | Opus 5 | |---|---|---|---| | Terminal-Bench-Science 0.1 | 52.6% | 24.7% | 29.0% | | Terminal-Bench 4.0 | 55.8% | 42.0% | 52.3% | | GDPval-AA v2 | 1853 | 1723 | 1824 | | OSWorld 2.0 (partial / strict) | 77.9% / 41.7% | 72.9% / 36.1% | 75.4% / 39.6% | | HLE (no tools / with tools) | 60.9% / 65.0% | 57.8% / 63.8% | 56.6% / 63.6% | | AutomationBench | 31.4% | 17.1% | 26.9% | | CursorBench 3.2.0 | 73.4% | 70.5% | 70.0% | The gap that stands out for agent builders is AutomationBench and Terminal-Bench-Science, where the reported margin over Opus 5 is much wider than on the general coding evals. CursorBench, by contrast, shows Fable 5.1 and Opus 5 within a few points. If your workload looks like ordinary editor-scoped coding, the published numbers do not obviously justify the higher base rate; if it looks like long-horizon terminal automation, they do. ## Platform behavior your harness should expect **Automatic safety fallbacks.** A beta introduced with Opus 5 adds automatic fallbacks when safety classifiers flag Opus 5 or Fable 5. Biology-related Fable blocks now route to Opus 5 rather than Opus 4.8. In practice this means the model that answers may not be the model you requested, so log the responding model rather than assuming your configured one. **Cyber classifier scope.** Anthropic's published policy allows finding vulnerabilities in source code, and blocks binary-based scanning, penetration testing, and exploit generation. If you are building security tooling, that line determines what you can automate. I am describing the policy boundary, not how to work around it. **Mid-conversation tool changes.** Also introduced with Opus 5: changing tool definitions mid-conversation without invalidating the prompt cache. For an agent that swaps tool sets between phases, this removes a real cost penalty that previously argued for a single bloated tool list. ## Text watermarking Since [August 14, 2026](https://www.anthropic.com/news/claude-text-watermark), future Claude models watermark text output, for EU AI Act and Code of Practice compliance. It is global at launch, with a transition period for models released before 2026-08-02. The engineering-relevant properties, per the announcement: no practical quality impact, no hidden characters, no extra tokens, and not traceable to a person, organization, or conversation. A detection API is coming. Three practical implications. Your token budgets and cost models are unaffected — there is no token overhead to account for. Your text-processing pipeline does not need to sanitize anything, because there are no hidden characters to strip. And the watermark is not an audit trail: it does not let you attribute a given output back to a specific user or session, so it is not a substitute for your own logging. ## Surface changes worth knowing [Claude in Chrome is generally available](https://claude.com/blog/claude-in-chrome-generally-available) on every paid plan, with a safety classifier on autonomous browser actions and admin domain allowlists for enterprise. [Memory now works the same across chat and Cowork](https://claude.com/blog/claudes-memory-works-everywhere-and-you-decide-whats-in-it), with topic-level edit and delete. Sensitive topics are off by default. Memory is on by default for Free, Pro, and Max; Team and Enterprise are admin-gated and off until a user enables it. If you are writing internal guidance for a team account, that last clause is the one people get wrong. ## Sources - Models overview — - Pricing — - Choosing a model — - Claude Fable 5.1 and Mythos 5.1 — - Fable 5.1 platform overview — - Claude Opus 5 — - Opus 5 platform overview — - Claude text watermarking — - Claude in Chrome GA — - Memory everywhere — - Claude Code docs — Model IDs, prices, and dates change. Check the pricing and models pages before you rely on any figure here. ### From Skill to CLI to npm: Building an Excalidraw Toolchain with Claude Code URL: https://claudemaster.com/excalidraw-toolchain/ Published: 2026-02-07 How a single Claude Code skill turned into two published npm packages in one session — and what it reveals about building with AI agents. It started with a simple realization: Claude is surprisingly good at spatial reasoning. I had been using Claude Code for a while — the CLI-based coding agent from Anthropic. It operates entirely in your terminal, reading and writing files, running commands, searching codebases. No browser. No GUI. Just you, your shell, and a very capable model. I decided to see if it could generate [Excalidraw](https://excalidraw.com/) diagrams — the hand-drawn-style whiteboarding tool that developers love. Excalidraw stores its diagrams as plain JSON: an array of elements with coordinates, dimensions, colors, and bindings between shapes. If Claude could output valid JSON in this format, we'd have instant diagrams from natural language. So I wrote a Claude Code **skill** — a reusable prompt template that lives in `~/.claude/commands/excalidraw.md`. When invoked with `/excalidraw `, it instructs Claude to: 1. Parse the description and identify the diagram type (architecture, flowchart, sequence, ERD, mind map) 2. Plan the layout using precise coordinate math 3. Generate valid Excalidraw JSON with proper bidirectional bindings 4. Write the `.excalidraw` file The skill prompt is ~800 lines of carefully structured instructions: layout formulas, color semantics, complete JSON templates, a quality checklist. It's essentially a visual DSL encoded as a system prompt. And it works. Remarkably well. One-shot architecture diagrams, decision flowcharts with labeled branches, multi-tier service layouts with container groupings. Claude nails the spatial layout because the skill gives it concrete formulas: ``` For box[i] in a horizontal row (0-indexed): x = START_X + i * (BOX_W + GAP) y = START_Y Arrow from box[i] to box[i+1]: x = box[i].x + BOX_W y = box[i].y + BOX_H/2 points = [[0, 0], [GAP, 0]] ``` No ambiguity. No "place it roughly to the right." Exact pixel coordinates. The model follows the math, and the diagrams come out aligned and properly spaced. But there was a problem. ## The Manual Step That Shouldn't Exist After Claude generated the `.excalidraw` file, you had to: 1. Open [excalidraw.com](https://excalidraw.com) 2. Click the menu → "Open" → select the file 3. Export as PNG or SVG 4. Save it Four manual steps. Every time. For a tool built for an AI agent workflow, this friction killed the whole point. Claude should generate the diagram and show it to you. End of story. My first instinct was **Puppeteer** — spin up a headless browser, load Excalidraw, inject the JSON, trigger an export. It's the brute-force answer to "I need what a browser does, but without a browser." But it's the wrong answer, and the reason why is worth understanding. ## Why Puppeteer Is the Wrong Tool ### How Excalidraw Renders (The Browser Path) When you open an Excalidraw diagram in the browser, here's what actually happens: 1. **roughjs** generates hand-drawn-style geometry. For each shape (rectangle, ellipse, diamond), it computes a series of mathematical operations — cubic bezier curves that simulate imprecise hand-drawn strokes. The "roughness" parameter controls how much jitter is added to the control points. 2. These operations are rendered onto an **HTML5 Canvas 2D context** — the browser's pixel-level drawing API. Calls like `ctx.moveTo()`, `ctx.bezierCurveTo()`, `ctx.stroke()` paint each shape. 3. Text rendering uses the Canvas 2D `measureText()` API for layout calculations and `fillText()` for rendering — including the custom **Virgil** font that gives Excalidraw its hand-written look. 4. For export, the canvas is converted to a bitmap via `canvas.toBlob()` (PNG) or serialized as SVG with embedded font data. The critical browser APIs involved: | API | Purpose | |-----|---------| | `CanvasRenderingContext2D` | All shape rendering | | `ctx.measureText()` | Text width calculation for centering | | `ctx.fillText()` | Text rendering with custom fonts | | `FontFace` API | Loading Virgil and other custom fonts | | `canvas.toBlob()` | PNG export | | `canvas.toDataURL()` | Inline image generation | | `Path2D` | Complex path construction | Puppeteer would give us all of these. But it means: - **~400MB Chromium download** as a dependency - **2-5 second startup** per export (cold browser launch) - **Memory overhead** — a full browser process for what should be a pure computation - **System dependency** — needs a display server or `--no-sandbox` flags in CI - **Fragile** — tied to Excalidraw's web app DOM structure, which changes between versions All of this to do what is fundamentally **math** — computing bezier curves and assembling SVG. ### The Insight: roughjs Doesn't Need a Browser Here's the key realization: roughjs has a **generator mode** that never touches the DOM. ```js import rough from "roughjs"; // This creates a generator that outputs pure math — no canvas, no DOM const gen = rough.generator(); // Returns an object with .sets[] containing operation arrays const drawable = gen.rectangle(0, 0, 200, 80, { roughness: 1, seed: 12345, // deterministic output }); // drawable.sets[0].ops = [ // { op: "move", data: [2.34, 1.12] }, // { op: "bcurveTo", data: [45.2, -1.3, 98.7, 2.1, 200.4, 0.8] }, // { op: "lineTo", data: [200.1, 79.3] }, // ... // ] ``` Those operation arrays are just numbers — cubic bezier curve control points that describe the hand-drawn stroke paths. They can be converted directly to SVG `` elements: ``` M2.34 1.12 C45.20 -1.30,98.70 2.10,200.40 0.80 L200.10 79.30 ... ``` No Canvas API needed. No browser needed. Pure computation → SVG string. ## Building the CLI Export Tool With this insight, the architecture became clear: ``` .excalidraw JSON → Parse elements → roughjs generator → SVG paths → SVG document → resvg → PNG ``` ### The Rendering Pipeline **Step 1: Parse the Excalidraw JSON.** Filter deleted elements, sort by layer order, extract the `files` map (embedded images). **Step 2: For each element, generate SVG.** This is where the shape-specific logic lives: - **Rectangles, ellipses, diamonds** → `rough.generator()` computes the hand-drawn strokes. Each shape produces a `drawable` with multiple "sets": a `path` set (the outline strokes) and optionally a `fillPath` or `fillSketch` set (the interior). Each set contains an array of operations (move, lineTo, bcurveTo). - **Text** → SVG `` elements with Excalidraw's font families. The Virgil font (fontFamily 1) is embedded as a base64-encoded `@font-face` declaration in the SVG's ``. Text alignment and multi-line wrapping are computed from the element's width and text content. - **Arrows and lines** → Multi-point paths. Straight arrows use `rough.linearPath()`. Curved arrows (those with `roundness` set) use `rough.curve()` to generate smooth bezier curves through the control points. Arrowheads are computed as small triangular polygons at the endpoint, rotated to match the arrow's final direction. - **Freedraw** → Raw point arrays are converted directly to SVG `` data by connecting each recorded point with line-to commands. The `stroke-linecap: round` and `stroke-linejoin: round` attributes smooth out the visual result without needing any external library. - **Images** → Direct `` SVG elements referencing the base64 data URLs from the Excalidraw file's `files` map. - **Frames** → Dashed rectangles with optional text labels, rendered behind their contained elements. **Step 3: Handle rotation.** Excalidraw stores rotation as `angle` in radians, centered on the element's midpoint. Each rotated element gets wrapped in an SVG `` with a `transform="rotate(deg, cx, cy)"`. **Step 4: Compute the viewport.** The bounding box calculation iterates all elements, rotating corner points for elements with non-zero angles, and finds the min/max coordinates. A padding of 40px is added on all sides. **Step 5: Assemble the SVG document.** The elements become children of an `` root element with the computed viewBox. Font definitions go in ``. Background color (if not transparent) becomes a full-viewport ``. **Step 6: PNG conversion.** For PNG output, the SVG string is passed to **resvg-js** — a Rust-based SVG renderer compiled to a native Node addon via napi-rs. It rasterizes the SVG at the requested scale factor (default 2x for retina quality). ```js import { Resvg } from "@resvg/resvg-js"; const resvg = new Resvg(svgString, { fitTo: { mode: "zoom", value: scale }, }); const pngBuffer = resvg.render().asPng(); ``` The entire pipeline — JSON parse, roughjs generation, SVG assembly, PNG rasterization — runs in **under 500ms** for typical diagrams. ### The Operation-to-Path Conversion The core utility function that makes this work converts roughjs operations to SVG path data: ```js export function opsToPath(ops) { return ops.map(op => { switch (op.op) { case "move": return `M${op.data[0].toFixed(2)} ${op.data[1].toFixed(2)}`; case "lineTo": return `L${op.data[0].toFixed(2)} ${op.data[1].toFixed(2)}`; case "bcurveTo": // Cubic bezier: 6 values = 3 control points (cp1x, cp1y, cp2x, cp2y, x, y) return `C${op.data[0].toFixed(2)} ${op.data[1].toFixed(2)},` + `${op.data[2].toFixed(2)} ${op.data[3].toFixed(2)},` + `${op.data[4].toFixed(2)} ${op.data[5].toFixed(2)}`; } }).join(" "); } ``` Each roughjs operation maps 1:1 to an SVG path command. `move` → `M`, `lineTo` → `L`, `bcurveTo` → `C`. The hand-drawn appearance comes entirely from the jittered control point positions that roughjs computes — the SVG rendering is perfectly standard. ### The Dependency Tree ``` @moona3k/excalidraw-export ├── roughjs — Hand-drawn geometry generation (generator mode, no DOM) └── @resvg/resvg-js — Rust-based SVG → PNG rasterization (native addon) ``` Two dependencies. No browser. No Puppeteer. No Canvas polyfill. ### Publishing to npm This was my first npm publish. A few things I learned along the way: **npm 2FA with security keys doesn't work for CLI publishing.** npm's publish command requires a TOTP (time-based one-time password) for 2FA verification, but security keys (WebAuthn) only work in the browser UI. The workaround: create a **granular access token** on npmjs.com with "Bypass 2FA on publish" enabled, then use that token for CLI publishing. **Scoped package names avoid collisions.** My first choice — `excalidraw-export` — was rejected because npm considers it too similar to an existing package `excalidraw_export` (underscore vs hyphen). Scoping to `@moona3k/excalidraw-export` solves this, but requires `--access=public` when publishing (scoped packages are private by default). **Registry propagation is not instant.** After `npm publish` succeeds, the package returns 404 on the registry API for 2-5 minutes. The npmjs.com web UI shows it immediately, but `npm view` and `npx` need the CDN to catch up. Published as [`@moona3k/excalidraw-export`](https://www.npmjs.com/package/@moona3k/excalidraw-export). The skill prompt was updated to call `npx @moona3k/excalidraw-export` after generating the JSON, closing the loop: `/excalidraw` now generates the diagram, exports it to PNG, and shows you the rendered image — all without leaving your terminal. ## The Mermaid Conversion: Filling an Open Gap While working on the export tool, I noticed [excalidraw/mermaid-to-excalidraw#66](https://github.com/excalidraw/mermaid-to-excalidraw/issues/66) — a long-standing GitHub issue requesting the **reverse** direction: Excalidraw → Mermaid. The existing `mermaid-to-excalidraw` library converts Mermaid text to Excalidraw JSON. But many users wanted to go the other way — take a visual diagram they'd drawn in Excalidraw and get Mermaid syntax they could paste into GitHub READMEs, Notion pages, or documentation systems that render Mermaid. I'd already built a parser that understands Excalidraw's element structure. The conversion logic was straightforward: ### How Excalidraw-to-Mermaid Works **Step 1: Parse the document into a graph.** Excalidraw stores diagrams as a flat array of elements. The parser reconstructs the graph structure: - **Nodes**: rectangles, ellipses, diamonds — anything in `NODE_TYPES` - **Edges**: arrows with `startBinding` and `endBinding` pointing to node IDs - **Labels**: text elements with `containerId` pointing to their parent shape or arrow - **Groups**: frames (spatial containment) or shared `groupIds` The binding system is key. When you draw an arrow between two shapes in Excalidraw, it creates a bidirectional binding: ```json // Arrow element { "type": "arrow", "startBinding": { "elementId": "box_001" }, "endBinding": { "elementId": "box_002" } } // Source shape { "id": "box_001", "boundElements": [{ "id": "arrow_001", "type": "arrow" }] } ``` This is what makes the conversion reliable — we're not guessing connectivity from proximity. The graph topology is explicit in the data. **Step 2: Map shapes to Mermaid syntax.** | Excalidraw Element | Mermaid Syntax | Visual | |---|---|---| | Rectangle (no roundness) | `A[Label]` | Square box | | Rectangle (with roundness) | `A(Label)` | Rounded box | | Diamond | `A{Label}` | Rhombus | | Ellipse | `A((Label))` | Circle | | Dashed rectangle | `A[[Label]]` | Subroutine box | **Step 3: Map arrow styles.** | Excalidraw Arrow | Mermaid | Syntax | |---|---|---| | Solid + arrowhead | Forward arrow | `-->` | | Solid + no head | Plain line | `---` | | Dashed + arrowhead | Dotted arrow | `-.->` | | Thick (strokeWidth >= 4) | Bold arrow | `==>` | Arrow labels (text elements bound to arrows via `containerId`) become Mermaid edge labels: `-->|label|`. **Step 4: Detect flow direction.** Rather than forcing the user to specify `graph TD` vs `graph LR`, the converter analyzes the spatial layout. For each edge, it computes the horizontal vs vertical displacement between source and target node centers: ```js const dx = Math.abs(target.cx - source.cx); const dy = Math.abs(target.cy - source.cy); if (dx > dy) horizontalScore++; else verticalScore++; ``` Majority wins. If most edges flow left-to-right, it's `graph LR`. If most flow top-to-bottom, it's `graph TD`. **Step 5: Handle groups as subgraphs.** Excalidraw frames (a `type: "frame"` element with spatial bounds) become Mermaid `subgraph` blocks. The converter checks which nodes fall within the frame's bounding box and nests them: ``` graph TD subgraph backend[Backend Services] A[API Server] B[Database] end C[Client] A --> B C --> A ``` **Step 6: Assign short IDs and generate output.** Excalidraw element IDs are long strings like `"xK2jF9qL..."`. The converter assigns clean alphabetic IDs (A, B, C, ... Z, AA, AB, ...) and outputs clean Mermaid syntax. Labels with special characters (colons, brackets, pipes) are automatically quoted per Mermaid's escaping rules. Published as [`excalidraw-to-mermaid`](https://www.npmjs.com/package/excalidraw-to-mermaid). Zero dependencies. ## The Bigger Picture: What This Session Reveals In a single Claude Code session, we went from "I wish this manual step didn't exist" to two published npm packages with 159 combined tests, full CLI interfaces, and programmatic APIs. The entire toolchain: ``` Natural language → /excalidraw skill → .excalidraw JSON ↓ excalidraw-export CLI → PNG/SVG ↓ excalidraw-to-mermaid CLI → Mermaid syntax ``` Each piece solves a real problem. The skill generates diagrams from descriptions. The export tool removes the browser dependency. The Mermaid converter fills a gap that developers had been requesting for years. What made this possible: 1. **A capable model** that can reason about coordinate math, SVG paths, and bezier curves 2. **An agent interface** that can read files, run commands, write code, and iterate on test failures 3. **A human** who knows what to build and can steer The model didn't design this on its own. The human identified the opportunity (automate the export), rejected the wrong approach (Puppeteer), and pointed toward the right one (roughjs generator mode). The model handled the implementation — parsing Excalidraw's JSON format, computing arrowhead angles, debugging npm 2FA token flows, writing 159 tests. This is what building with AI looks like in 2026. Not "AI writes my app." More like: I have an idea, I have taste, and I have an incredibly fast collaborator who can turn direction into working code. ### And Yes, This Website Too After the two packages were published and tested, I said: "we should document all of this." Claude wrote this article. Then I said: "let's deploy it." Claude scaffolded the site, configured Cloudflare Pages, and deployed it. The website you're reading — claudemaster.com — was built, written, and deployed in the same session as the tools it describes. The same Claude Code instance that computed bezier curve control points also wrote the HTML templates and ran `wrangler pages deploy`. The entire chain — from "I wish this manual step didn't exist" to a live website documenting the solution — happened in one sitting. The only thing the human needed to provide was direction, taste, and an npm token. --- ## Technical Reference ### excalidraw-export ```bash npx @moona3k/excalidraw-export diagram.excalidraw ``` **GitHub**: [moona3k/excalidraw-export](https://github.com/moona3k/excalidraw-export) | **npm**: [@moona3k/excalidraw-export](https://www.npmjs.com/package/@moona3k/excalidraw-export) Rectangles, ellipses, diamonds, arrows, lines, text (all 4 font families), freedraw, images, frames, rotation, fill styles, stroke styles, opacity, arrowheads, curved multi-point arrows. 71 tests. ### excalidraw-to-mermaid ```bash npx excalidraw-to-mermaid diagram.excalidraw ``` **GitHub**: [moona3k/excalidraw-to-mermaid](https://github.com/moona3k/excalidraw-to-mermaid) | **npm**: [excalidraw-to-mermaid](https://www.npmjs.com/package/excalidraw-to-mermaid) 5 node shapes, 6 edge styles, arrow labels, frames as subgraphs, groupId-based subgraphs, auto direction detection, special character quoting. 88 tests. Zero dependencies. ### /excalidraw Skill Available as a [GitHub Gist](https://gist.github.com/moona3k/f2d95af650e9c931cc6b13677c9fee97) — install into `~/.claude/commands/excalidraw.md` for Claude Code. Supports: architecture diagrams, flowcharts, sequence diagrams, data flow, ERDs, mind maps. Clean, hand-drawn, and sketchy style presets. Disclaimer: claudemaster.com is an independent reading room and is not affiliated with Anthropic.