@mcuste/pi-diagram
Terminal-native diagram rendering as one safe, model-callable tool for the Pi and Oh My Pi coding agents
Package details
Install @mcuste/pi-diagram from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@mcuste/pi-diagram- Package
@mcuste/pi-diagram- Version
0.3.0- Published
- Aug 22, 2026
- Downloads
- 198/mo · 198/wk
- Author
- mcuste
- License
- MIT
- Types
- extension
- Size
- 466.1 KB
- Dependencies
- 2 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-diagram
An extension for the Pi and
Oh My Pi terminal coding agents. It gives the model one
diagram tool that turns declarative D2 source into a rendered diagram in the
transcript, or into documentation artifacts checked into the repository.
If any of those names are new to you:
- D2 is a declarative diagram language. Text such as
client -> gateway -> apicompiles to a laid-out diagram, and the same language covers containers, sequence diagrams, and SQL tables. - Pi and Oh My Pi are terminal coding agents. An extension is an npm package they load at startup to add tools the model can call.
Status: images, text, and files work. Unicode box drawing is the default. Users can opt in to inline images where the terminal supports them. Files land outside the repository unless a destination is named.
Why
Coding agents explain spatial things in prose: "A calls B, B queues C, C writes D, D emits an event back to A." The user rebuilds a graph in their head that the agent could have drawn. A fenced Mermaid block moves the problem rather than solving it, because a terminal shows the source instead of the picture.
This extension gives the model one tool for that. The model writes the meaning: nodes, edges, groups, labels, emphasis. The tool owns the appearance: layout engine, theme, spacing, fonts, and which representation the current terminal can actually display. Diagrams then look consistent across calls, because the model is not styling them one at a time.
Requirements
- Node.js 22 or newer
- The D2 CLI, version 0.8.0 or newer, on
PATHor named byD2_BIN
brew install d2 # ships 0.8.1
go install github.com/d2lang/d2@v0.8.1 # or a pinned build from source
The prebuilt binaries on D2's GitHub releases page stop at 0.7.1, which draws SQL tables as empty boxes, so they are below the supported floor.
D2 is an external dependency on purpose. The extension never downloads a renderer during a tool call. Without it, the extension still loads and a call explains how to install it.
Install
Pi:
pi install npm:@mcuste/pi-diagram
Oh My Pi:
omp plugin install @mcuste/pi-diagram
Or through the Oh My Pi marketplace:
/marketplace add mcuste/pi-diagram
/marketplace install pi-diagram@pi-diagram
From a local checkout:
pnpm install
pi install /absolute/path/to/pi-diagram
omp plugin link /absolute/path/to/pi-diagram
What the tool does
One tool named diagram. Give it D2 source and it draws the diagram in the terminal:
edge: Edge { gateway }
core: Core { api; worker }
edge.gateway -> core.api
core.api -> core.worker: enqueue
┌───────────────────┐
│ Edge │
│ │
│ ┌──────────┐ │
│ │ gateway │ │
│ └──────────┘ │
│ │ │
└──────────│────────┘
│
┌─────────│─────────┐
│ Core │
│ ▼ │
│ ┌──────┐ │
│ │ api │ │
│ └──────┘ │
│ │ │
│ enqueue │
│ ▼ │
│ ┌─────────┐ │
│ │ worker │ │
│ └─────────┘ │
└───────────────────┘
Containers, sequence diagrams, SQL tables, class shapes, and state flows all work from the same language.
| Field | Purpose |
|---|---|
source |
The diagram, in D2 |
title |
Label shown above the diagram |
render |
auto uses the user preference; image and unicode override it; source echoes the D2 |
profile |
What the diagram is for. It sets the layout engine, theme, and spacing |
formats |
Files to produce: source, svg, png, txt. Written outside the repository |
save |
Also copy them into the repository. dir is required |
When the model draws
A described tool gets called when the user asks for a diagram, not when a diagram is the clearer answer. So the extension adds a short block to the host system prompt: draw when structure, flow, or relationships are easier to see than to read, call the tool before explaining, keep the prose to what the picture does not show, and never hand-write ASCII art or Mermaid. The block tells the model to check C4, sequence, class, data, dependency, or tree before drawing a generic flow. The tool description contains the detailed profile and shape rules.
The result is fewer walls of text: an answer about how parts connect arrives as a picture with a
few lines around it. The block is appended to whatever the host built, is added once, and is left
out when the diagram tool is not active.
What a profile changes
The model says what a diagram is for, and the tool decides how it looks:
| Profile | For | Drawn as |
|---|---|---|
explain |
States, decisions, transformations, or process flow inside an answer | Hand drawn, neutral theme, small margins |
architecture |
Runtime services and infrastructure without C4 levels | Neutral theme, more room between rows so edges stay separable |
data |
Stored records and relations, or types and public APIs | Neutral theme, tight spacing: tables and classes are tall already |
docs |
A saved diagram when no more specific profile fits | Grey theme that prints in greyscale, page-sized margins |
tree |
Folders, call trees, or other parent-child structures | Dagre, which fans children out under their parent |
c4 |
System scope, users, external systems, runnable units, or module responsibilities | Architecture spacing under the C4 palette |
dependency |
Imports, packages, build order, coupling, impact, or cycles | The tightest spacing, cutting the room edges take past nodes |
explain is the default and is drawn by hand: an answer in a conversation is a rough model, and a
crisp diagram claims more precision than it has. Every other profile is crisp.
Every profile also sets a dark theme, so a saved SVG adapts to dark mode.
Only the picture changes. D2 draws text in character cells, so every profile gives the same box drawing.
Images in the terminal
Unicode is the default. To persist image rendering for one repository, create
.pi/pi-diagram.json for Pi or .omp/pi-diagram.json for OMP:
{
"render": "image"
}
For every repository, use ~/.pi/agent/pi-diagram.json or
~/.omp/agent/pi-diagram.json. PI_CODING_AGENT_DIR replaces those agent directories when set.
For one run, use the host extension flag or environment variable:
pi --diagram-render image
omp --diagram-render image
PI_DIAGRAM_RENDER=image pi
Precedence is command-line flag, PI_DIAGRAM_RENDER, project configuration, global
configuration, then the Unicode default.
Configuration files are read before each diagram, so changes apply without restarting the host.
On Kitty, Ghostty, WezTerm, iTerm2, and anything else that speaks a terminal image protocol,
auto then shows the drawn diagram. At host startup the extension uses the host's TUI library to
detect whether the terminal and its harness support an image protocol. If image rendering is
disabled or unavailable, the call shows Unicode and reports Image support is unavailable; generated as Unicode. Explicit render: "unicode" never attempts an image.
A terminal with no image protocol is never sent one. Note that a multiplexer between the terminal
and the agent has to forward the protocol: tmux needs allow-passthrough, and herdr needs
experimental.kitty_graphics.
The image never enters the model's context. It is written to a private temporary directory and read back when the row is displayed.
The collapsed row is a 60 by 18 cell preview. Press Ctrl+O, the default tool-expansion
shortcut, to zoom the image to the available terminal width and up to 60 rows. Press it again to
return to the preview.
Where the terminal supports OSC 8 hyperlinks, the title above the diagram links to the full image file. A diagram with no title shows the linked file name under the image instead. Open that link to pan or zoom beyond the terminal view. Ghostty, Kitty, WezTerm, and iTerm2 support these links, some of them on a modified click.
D2 exports PNG by driving a headless browser it downloads on first use, which this tool will not do during a call. Instead the SVG it already produces is rasterized locally by resvg, which needs no browser and no network. Labels are drawn with the fonts the SVG carries, so the picture matches the boxes D2 measured. Characters those fonts do not cover, such as CJK, fall back to the fonts on the machine and say so.
What the model gets back
In a terminal this extension draws the row, so the model reads one line: Drew "Request path" as an image. It is on the user's screen, so it is not repeated here. Saved paths and notes come with
it, so a conversation full of diagrams costs about what a conversation of text costs. Print, RPC,
and JSON modes have no row to draw, so there the diagram is the result text.
Expanding an image row zooms it and adds the render mode, the profile, the D2 version, the file paths, diagnostics, and the source.
Where files go
Most diagrams explain something in passing and should leave nothing behind. So nothing is written unless it is asked for, and the repository is never the default:
| Call | Files | Approval |
|---|---|---|
{ source } |
none, the diagram is only in the transcript | read |
{ source, formats: ["svg"] } |
an SVG in a private temp directory, path returned | read |
{ source, title, save: { dir: "docs/diagrams" } } |
copied into the repository | write, prompts with the exact files |
save.dir has no default. There is no directory convention that holds across repositories, so
the destination has to be named. Only pass save when the user asked to keep the diagram. A
saved .d2 is put through d2 fmt first, because people edit it later.
A saved pair is editable source plus a viewable rendering:
docs/diagrams/request-lifecycle.d2
docs/diagrams/request-lifecycle.svg
Markdown then needs no D2 or Mermaid runtime:

For documentation prefer svg: it scales, and Markdown renders it anywhere. png exists for
places where SVG support is weak.
Repository paths stay inside the workspace. Absolute paths, .., and symlinks pointing outside
are all refused.
What D2 drew is also kept in a temporary store, so the same diagram drawn again is about ten times faster, including in a later session. A new D2 version, or any change to a profile, is a different entry. The store holds 64 MB for a week.
Layout engine, theme, padding, and font are deliberately not in the schema. They are policy here, and a model given those knobs spends tokens on styling and produces a different look every call.
What is deliberately missing
- Model-drawn images. The model never emits SVG coordinates or ASCII art directly. It writes source, and a renderer lays it out.
- Imports and external assets. Diagram source cannot pull in files, local images, or remote icons. Those turn a rendering request into file and network access.
- Interactive editing. The tool renders; it is not a diagram editor in the terminal.
- Every D2 feature. A safe subset is supported, and anything outside it is reported as an error the model can correct rather than being silently dropped.