DiagramScribe — User Guide
What Is DiagramScribe?
DiagramScribe is a Python library and CLI tool that turns plain English descriptions into Excalidraw diagrams. You describe what you want — a flowchart, architecture diagram, sequence diagram, anything — and DiagramScribe generates it.
It works in two steps:
- An AI model (called an LLM) reads your description and figures out what nodes and connections to draw.
- DiagramScribe turns that into an Excalidraw file and opens it in your browser.
You need access to an AI model to use it. DiagramScribe supports three options — Claude, OpenRouter (free), and Ollama (local, no internet) — explained in the Setup section below.
Installation
pip install diagram-scribe
OpenRouter and Ollama work out of the box. To use Claude (Anthropic), install the extra:
pip install "diagram-scribe[claude]"
Setup: Choosing an LLM
What is an LLM?
An LLM (Large Language Model) is a program that understands and generates text. DiagramScribe uses one to read your diagram description and figure out what nodes and connections to draw. You don't need to know how it works — you just need access to one.
There are three ways to get access:
| Option | Cost | Privacy | Setup effort |
|---|---|---|---|
| OpenRouter ✅ default, start here | Free tier available, no credit card | Your descriptions are sent to a cloud server | 5 minutes — sign up and copy a key |
| Ollama (privacy / offline) | Free forever | Everything stays on your machine | 10–15 minutes — needs 8 GB RAM and ~4 GB disk for the model download |
| Claude (highest quality) | Paid, ~$0.01–0.05 per diagram | Your descriptions are sent to Anthropic | 5 minutes — sign up and copy a key |
Start with OpenRouter. It's the default, takes 5 minutes, has free models, and works on any machine. Switch to Ollama later if you need everything to stay local, or Claude if you want the highest quality results.
Option A: OpenRouter — free, no credit card required
OpenRouter is a website that gives you access to many AI models through a single account. Several models are completely free.
Step 1 — Create an account
Go to openrouter.ai and sign up. No credit card needed for free models.
Step 2 — Create an API key
- Click your avatar (top right) → Keys
- Click Create key, give it any name, click Create
- Copy the key — it starts with
sk-or-v1-...
Step 3 — Set your key
Create a file called .env in the folder where you run diagram-scribe:
OPENROUTER_API_KEY=sk-or-v1-...
That's it. DiagramScribe picks this up automatically when you run it.
Recommended free models
All of these are free on OpenRouter (:free suffix = no cost). Copy the model name exactly into your .env:
| Model | Set OPENROUTER_MODEL= |
Notes |
|---|---|---|
| Llama 3.1 8B by Meta | meta-llama/llama-3.1-8b-instruct:free |
Default — good balance of quality and speed |
| Qwen 2.5 7B by Alibaba | qwen/qwen-2.5-7b-instruct:free |
Excellent at following structured instructions |
| Gemma 2 9B by Google | google/gemma-2-9b-it:free |
Higher quality, slightly slower |
| Mistral 7B | mistralai/mistral-7b-instruct:free |
Fast and lightweight |
To use a specific model, add a second line to your .env:
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=qwen/qwen-2.5-7b-instruct:free
If you don't set OPENROUTER_MODEL, DiagramScribe uses Llama 3.1 8B by default.
You can browse all free models at openrouter.ai/models — filter by "Free" in the top right.
Option B: Ollama — fully local, nothing leaves your machine
Ollama runs AI models on your own computer. After a one-time model download, it works completely offline. Good choice if you don't want your descriptions sent to any server.
Requirements: At least 8 GB of RAM. More RAM lets you run larger (better) models.
Step 1 — Install Ollama
Download and run the installer from ollama.com. It works on Mac, Windows, and Linux.
After installing, Ollama runs in the background automatically. You'll see its icon in your system tray or menu bar.
Step 2 — Download a model
Open a terminal and run:
ollama pull qwen2.5
This downloads the model (about 4 GB). You only do this once.
Step 3 — Tell DiagramScribe which model to use
Create a .env file in the folder where you run diagram-scribe:
OLLAMA_MODEL=qwen2.5
No API key needed.
Recommended local models
| Model | Pull command | RAM needed | Notes |
|---|---|---|---|
| Qwen 2.5 | ollama pull qwen2.5 |
5 GB | Best choice — reliable structured output |
| Llama 3.2 | ollama pull llama3.2 |
5 GB | Good general quality |
| Phi 3.5 Mini | ollama pull phi3.5 |
3 GB | Smallest option — great if RAM is tight |
| Gemma 2 | ollama pull gemma2 |
6 GB | Slightly higher quality |
To see what you've already downloaded: ollama list
Option C: Claude — best quality
Requires a paid Anthropic account. Typical cost is a few cents per diagram.
Step 1 — Get an API key
- Sign up at console.anthropic.com
- Go to API Keys → Create Key
- Copy the key — it starts with
sk-ant-...
Step 2 — Set your key
Create a .env file in the folder where you run diagram-scribe:
ANTHROPIC_API_KEY=sk-ant-...
.env file reference
The .env file goes in whichever folder you run diagram-scribe from. Create it with any text editor. Only set the lines for the option you chose:
# Option A — OpenRouter
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=meta-llama/llama-3.1-8b-instruct:free # optional, this is the default
# Option B — Ollama (no key needed)
OLLAMA_MODEL=qwen2.5
# Option C — Claude
ANTHROPIC_API_KEY=sk-ant-...
If you set multiple keys, DiagramScribe uses them in this priority order: OpenRouter → Ollama → Claude.
CLI Usage
Run the interactive session:
diagram-scribe
Example session:
DiagramScribe — describe your diagram in plain English.
Press Enter on an empty line to quit.
> Describe your diagram: CI/CD pipeline — push code, run tests, if tests pass
deploy to staging, run smoke tests, if smoke tests pass
deploy to production, otherwise notify the team.
Generating diagram...
[diagram opened]
> Refine (or press Enter to finish): add a manual approval step before production deploy
Updating diagram...
[diagram updated]
> Refine (or press Enter to finish): add a rollback path if production deploy fails
Updating diagram...
[diagram updated]
> Refine (or press Enter to finish):
Done.
The diagram opens as an .excalidraw file in your browser on the first call. Subsequent refinements update the same file — refresh the browser tab to see changes.
Library Usage
Basic: draw a diagram
from diagram_scribe import DiagramScribe
ds = DiagramScribe() # uses OpenRouter by default (OPENROUTER_API_KEY must be set)
ds.draw(
"User authentication flow — user submits credentials, validate token, "
"if valid return dashboard, if invalid increment retry count, "
"if retries exceeded lock account."
)
Iterative refinement
from diagram_scribe import DiagramScribe
ds = DiagramScribe()
ds.draw(
"CI/CD pipeline — push code, run tests, if tests pass deploy to staging, "
"run smoke tests, if smoke tests pass deploy to production."
)
# user sees the diagram, wants changes
ds.refine("add a manual approval step before production deploy")
ds.refine("add a rollback path if production deploy fails")
Each refine() call sends the original description, the current diagram state, and your feedback to the LLM. The result replaces the current diagram.
Using OpenRouter (free model)
from diagram_scribe import DiagramScribe
from diagram_scribe.adapters.llm.openrouter import OpenRouterAdapter
ds = DiagramScribe(
llm=OpenRouterAdapter(
api_key="sk-or-...",
model="meta-llama/llama-3.1-8b-instruct:free",
)
)
ds.draw("Microservices architecture — API gateway routes to auth service, "
"user service, and order service. Each service has its own database.")
Using Ollama (local model)
from diagram_scribe import DiagramScribe
from diagram_scribe.adapters.llm.ollama import OllamaAdapter
ds = DiagramScribe(llm=OllamaAdapter(model="qwen2.5"))
ds.draw("Git branching strategy — main branch, feature branches, "
"pull request review, merge to main, tag release.")
Saving to a custom file path
By default the diagram is saved to ~/Documents/diagram-scribe.excalidraw. To save elsewhere:
from diagram_scribe import DiagramScribe
from diagram_scribe.adapters.backend.excalidraw import ExcalidrawAdapter
ds = DiagramScribe(backend=ExcalidrawAdapter(output_path="/tmp/my-diagram.excalidraw"))
ds.draw("Two microservices talking over a message queue.")
Custom backend adapter
Implement the BackendAdapter protocol to render to any target:
from diagram_scribe.models import DiagramIR
class MyAdapter:
def render(self, ir: DiagramIR) -> None:
for node in ir.nodes:
print(f"Node {node.id}: {node.label} ({node.shape})")
for edge in ir.edges:
label = f" [{edge.label}]" if edge.label else ""
print(f"Edge {edge.from_id} → {edge.to_id}{label}")
from diagram_scribe import DiagramScribe
ds = DiagramScribe(backend=MyAdapter())
ds.draw("Login flow — user submits form, validate credentials, return token.")
Architecture
description (str)
↓
LLMAdapter.generate() or .refine()
↓
DiagramIR { nodes: list[Node], edges: list[Edge] }
↓
BackendAdapter.render()
↓
Diagram output (e.g. Excalidraw file)
DiagramIR
The intermediate representation that decouples the LLM from the backend:
@dataclass
class Node:
id: str
label: str
shape: str # "box", "diamond", "circle", "cylinder"
@dataclass
class Edge:
from_id: str
to_id: str
label: str | None = None
@dataclass
class DiagramIR:
nodes: list[Node]
edges: list[Edge]
Adapter protocols
class LLMAdapter(Protocol):
def generate(self, description: str) -> DiagramIR: ...
def refine(self, feedback: str, current: DiagramIR) -> DiagramIR: ...
class BackendAdapter(Protocol):
def render(self, ir: DiagramIR) -> None: ...
Any object that implements these methods works — no base class required.
LLM adapter selection (CLI)
The CLI picks the LLM based on environment variables, in priority order:
OPENROUTER_API_KEYset →OpenRouterAdapterOLLAMA_MODELset →OllamaAdapterANTHROPIC_API_KEYset →ClaudeAdapter- None set → error with instructions
Running Tests
Unit tests (no API keys needed):
pytest -v
Integration tests (require real API keys):
ANTHROPIC_API_KEY=sk-ant-... pytest -m integration -v
OPENROUTER_API_KEY=sk-or-... pytest -m integration -v
OLLAMA_MODEL=qwen2.5 pytest -m integration -v
Contributing
- Fork the repo and create a branch:
git checkout -b feat/my-feature - Write a failing test first (TDD)
- Implement the minimal code to make it pass
- Run the full suite:
pytest -v - Commit with a descriptive message and open a PR
New LLM adapters go in src/diagram_scribe/adapters/llm/. New backend adapters go in src/diagram_scribe/adapters/backend/. Follow the existing adapter pattern and add tests in tests/.