# gridgram — full documentation
> Concatenated English docs plus the generated `gg llm` reference. Auto-generated; do not edit by hand.
---
# AI Guide
Gridgram is built to be **author-able by an LLM agent**. You describe
what you want in natural language; an agent produces a valid `.gg`
file, renders it, reads back diagnostics, and iterates — no manual
reference-pasting required.
This guide is **task-oriented**. Pick one of the three usage paths
below and follow it end-to-end. Each tutorial starts from a clean
machine and ends with a rendered diagram plus the command to update
later.
## Three ways to use gridgram from an agent
| Tutorial | Best for |
| ----------------------------------------- | ------------------------------------------------------------------------------ |
| [Claude Code plugin](./claude-plugin) | You use Claude Code. Slash commands (`/gg-install`, `/gg-icons`, `/gg-author`, `/gg-render`) drive the whole workflow. |
| [`gh skill`](./gh-skill) | You want the same skill bundle installed into Copilot, Cursor, Gemini CLI, or Codex alongside (or instead of) Claude Code. |
| [context7 (MCP)](./context7) | You want an agent to retrieve gridgram's docs via MCP without installing anything plugin-side. Read-only. |
You can mix and match. The Claude plugin includes a `/gg-install`
skill that fetches the CLI binary; `gh skill` gives you the same
skills in other hosts; context7 is a parallel retrieval channel that
works even without any plugin.
## What you'll need
None of this is installed by gridgram itself. Install whatever the
tutorial of your choice calls for before starting.
| Software | For which tutorial | Why |
| ---------------------------------------------------------------- | ------------------------------------- | -------------------------------------- |
| [git](https://git-scm.com/) | All | Plugin marketplaces clone over git. |
| [Claude Code](https://code.claude.com/) | Claude plugin, context7 | Host that runs the skills / MCP. |
| [GitHub CLI `gh`](https://cli.github.com/) (v2.90+) | `gh skill` | Provides the `gh skill` subcommand. |
| [Cursor](https://cursor.com/) / [Gemini CLI](https://github.com/google-gemini/gemini-cli) / [Codex](https://openai.com/index/introducing-codex/) | `gh skill` (optional target) | Alternative skill hosts. |
| `curl`, `tar`, `unzip` (windows) | `/gg-install` runtime | Downloading the `gg` binary. |
| `bun` ([install](https://bun.sh/)) | Only if building `gg` from source | You can use the release binary instead.|
You do **not** need to install the `gg` CLI manually — the Claude
plugin ships a `/gg-install` skill that handles it, and `gh skill`
users can run the same skill after installing it. If you'd rather
install `gg` outside your agent, follow the standard
[Quick start](/en/guide/) or [Install](/en/guide/install) page; any
install that puts `gg` on `$PATH` is picked up by the skills
automatically.
## At a glance: what gridgram exposes
If you want the full tour before picking a tutorial:
- **[`gg llm`](./cli#gg-llm)** — one-shot Markdown / JSON reference
teaching an agent the `.gg` grammar, CLI, icons, JSON envelope, and
canonical examples.
- **[`gg icons`](./cli#gg-icons)** — scored semantic search over
5,039 outline + 1,053 filled Tabler icons.
- **[`/llms.txt`](https://gridgram.ideamans.com/llms.txt) + [`/llms-full.txt`](https://gridgram.ideamans.com/llms-full.txt)** — public discovery files on the docs site.
- **[`context7.json`](https://github.com/ideamans/gridgram/blob/main/context7.json)** — registers gridgram for [context7](./context7) MCP retrieval.
- **[`plugins/gridgram`](https://github.com/ideamans/gridgram/tree/main/plugins/gridgram)** — four skills (`gg-install`, `gg-render`, `gg-icons`, `gg-author`) distributed via Claude Code and `gh skill`.
The [CLI reference page](./cli) documents `gg llm` and `gg icons` in
full — useful whether an agent is driving them through a skill or
you're running them manually in a terminal.
## Tutorials
Start here:
- **[Claude Code plugin](./claude-plugin)** — 10-minute path if you
already use Claude Code.
- **[`gh skill`](./gh-skill)** — if you want the skills in Copilot
/ Cursor / Gemini / Codex.
- **[context7](./context7)** — if you want an agent to *retrieve*
gridgram docs over MCP without installing anything gridgram-specific.
---
# Claude Code plugin tutorial
End-to-end walkthrough from a clean machine to rendering a diagram
with Claude Code's `/gg-*` slash commands.
**Time**: ~10 minutes. **Result**: a rendered SVG you can open in a
browser, plus the commands you'll use later to stay current.
## Prerequisites
Install these yourself if you haven't already:
- [**Claude Code**](https://code.claude.com/) (any recent version)
- [**git**](https://git-scm.com/)
- **A shell** (bash/zsh on macOS/Linux, PowerShell on Windows)
- **Network access** to `github.com`
You do **not** need the `gg` CLI installed yet — the plugin will
install it for you.
## 1. Add the gridgram marketplace
Start Claude Code and add the `ideamans/claude-public-plugins`
marketplace:
```text
/plugin marketplace add ideamans/claude-public-plugins
```
You should see:
```
✔ Successfully added marketplace: ideamans-plugins
```
Verify:
```text
/plugin marketplace list
```
## 2. Install the gridgram plugin
```text
/plugin install gridgram@ideamans-plugins
```
Output:
```
✔ Successfully installed plugin: gridgram@ideamans-plugins
```
Verify:
```text
/plugin list
```
You should see `gridgram@ideamans-plugins` with status `enabled`.
Now, **reload the session** so the skill commands become available:
```text
/reload-plugins
```
Type `/gg-` — Claude Code's autocomplete should now offer
`/gg-install`, `/gg-render`, `/gg-icons`, and `/gg-author`.
## 3. Install the `gg` CLI
The skills shell out to the `gg` binary. You have two options; either
works — pick whichever you prefer.
### Option A: Use `/gg-install` inside Claude (recommended for first-timers)
Ask Claude:
> Install the gridgram CLI for my platform.
Claude picks up `/gg-install`, which:
1. Detects your OS + CPU architecture (`uname`).
2. Fetches the latest release from
.
3. Finds a writable directory on `$PATH` (tries `~/.local/bin`,
`~/bin`, `/usr/local/bin`, `/opt/homebrew/bin` in that order).
4. Asks you to confirm the target directory.
5. Downloads + extracts + moves the binary into place.
If no candidate is writable without sudo, the skill stages `gg` at
`/tmp/gg` and prints the exact `sudo mv …` command to run:
```
gg staged at /tmp/gg — run 'sudo mv /tmp/gg /usr/local/bin/gg' to finish
```
### Option B: Install `gg` the regular way
If you already have a preferred install method, or you want to set
up `gg` outside of Claude Code, follow the standard install guide
at **[Quick start](/en/guide/)** (one-line curl / PowerShell scripts)
or **[Install](/en/guide/install)** for manual alternatives.
Any install that puts `gg` on `$PATH` will be picked up by the
skills — they don't care how it got there.
### Verify
```sh
gg --help
```
You should see `gg v` in the banner.
## 4. Render your first diagram
Ask Claude in plain English:
> Draw a gridgram diagram of a web app: browser client → API → Postgres
> database with a Redis cache and a background job queue. Save it to
> `~/first-diagram.svg`.
Behind the scenes Claude will:
- Invoke `/gg-icons` to find Tabler icons for browser/api/database/cache/queue.
- Invoke `/gg-author` to compose a `.gg` source file and validate it.
- Invoke `/gg-render` to produce the SVG and read back diagnostics.
Open the SVG in a browser. You'll see a small architecture diagram.
Claude will also have shown you the `.gg` source — save it somewhere
if you want to edit it later.
### If the first render has warnings
The `--diagnostics` channel reports non-fatal issues (unresolved
icons show as red rings, labels that can't be placed cleanly, …).
Just tell Claude:
> That diagram has a red ring around the cache node. Fix it.
Claude re-runs `/gg-icons` for the offending concept, patches the
`.gg` source, and re-renders. The whole loop stays inside the chat.
## 5. Keep everything up to date
Two moving parts: the plugin (skills + SKILL.md bodies) and the
`gg` binary itself.
### Update the plugin
```text
/plugin marketplace update ideamans-plugins
/reload-plugins
```
The marketplace entry uses a `git-subdir` source pinned to gridgram's
default branch, so this pulls the latest skills automatically.
### Update the `gg` binary
Just ask:
> Update my gridgram CLI to the latest version.
`/gg-install` detects the existing `gg` on `PATH`, downloads the
newest release, and replaces the binary in the same directory. No
sudo prompt unless the existing location needs it (rare for
`~/.local/bin`).
Or directly:
```text
/gg-install
```
The skill always checks for a newer version; if you're already on
latest it says so and exits.
## 6. Uninstall (optional)
```text
/plugin uninstall gridgram@ideamans-plugins
/plugin marketplace remove ideamans-plugins
```
To remove the `gg` binary:
```sh
rm "$(which gg)"
rm -rf ~/.cache/gridgram # runtime-fetched sharp cache
```
## Troubleshooting
### `/plugin marketplace add` fails with `Premature close`
The non-interactive git clone inside Claude Code can hit this on
hosts where the `owner/repo` shorthand SSH path isn't set up. Retry
with the full HTTPS URL:
```text
/plugin marketplace add https://github.com/ideamans/claude-public-plugins.git
```
### `/gg-install` stages the binary at `/tmp/gg` but no `$PATH` dir is writable
Run the `sudo mv` command the skill prints, then verify with
`gg --help`. Alternatively, add a user-writable directory to your
`PATH` in `~/.zshrc` / `~/.bashrc`:
```sh
export PATH="$HOME/.local/bin:$PATH"
```
Reopen your shell and rerun `/gg-install`.
### PNG rendering fails on first use
The PNG pipeline (sharp + libvips) is fetched lazily on first PNG
render and cached in `~/.cache/gridgram/`. If that fetch fails (flaky
network, older `gg` version), retry a second time. Versions older
than 0.4.0 have a known Linux-arm64 bug with detect-libc resolution;
upgrade with `/gg-install`.
## Where next
- [CLI reference](./cli) — what `gg llm` and `gg icons` accept in
detail, useful when you want to drive them by hand or understand
what the skills do internally.
- [`gh skill` tutorial](./gh-skill) — install the same skill bundle
into Copilot / Cursor / Gemini / Codex.
- [context7 tutorial](./context7) — give agents MCP-driven retrieval
of gridgram docs with no plugin install.
---
# CLI reference for AI workflows
This is a **reference page**, not a tutorial. Start with one of the
tutorials ([Claude plugin](./claude-plugin) / [`gh skill`](./gh-skill)
/ [context7](./context7)) if you haven't installed anything yet.
Two `gg` subcommands exist specifically for LLM consumption. Neither
needs network access; both are bundled in the `gg` single-file
binary. The skills in `plugins/gridgram` drive them on the agent's
behalf; you can also invoke them directly from a shell.
## `gg llm`
Emits a self-contained reference that teaches an agent everything it
needs to author `.gg` files. Pipe it into your agent's context on
session start.
### Markdown (default)
```sh
gg llm
```
The output covers:
- CLI usage for every subcommand
- `.gg` grammar (BNF) and statement semantics
- Document-level settings (`doc { … }` keys)
- Icon resolution order (inline map → `--icons` → built-in Tabler → error)
- Exit codes (0 success, 1 parse error, 2 integrity error, 3 I/O)
- JSON envelope shape returned by `gg render --format json`
- Canonical examples from the `examples/` directory
- Best-practices checklist for agent-authored diagrams
The Markdown is regenerated from source (the BNF comment in
`src/gg/dsl.ts`, the citty arg schema, the real example files) on
every build, so it can never drift from the implementation.
### JSON
```sh
gg llm --format json
```
Returns a structured view that agents can parse without worrying about
Markdown heuristics:
```json
{
"version": "0.4.0",
"iconCounts": { "outline": 5039, "filled": 1053, "total": 6092 },
"grammar": "…BNF extracted from dsl.ts…",
"reference": "…full Markdown body as a string…"
}
```
Useful when the agent just needs the version or the grammar block and
doesn't want to eat the rest of the Markdown.
### Agent prompt template
A good opening turn for a gridgram-authoring session:
```text
You are going to author diagrams in the gridgram `.gg` format.
The complete reference follows. It covers the grammar, CLI, icon
naming, and the JSON output shape.
---
---
When you produce a `.gg` file:
1. Validate with `gg --format json --diagnostics --stdout`
2. If the exit code is non-zero or diagnostics is non-empty, fix and retry.
3. Report the final `.gg` plus the diagnostics array.
```
## `gg icons`
Semantic search over the 6,092 built-in Tabler icons (5,039 outline +
1,053 filled). The index is generated at build time from Tabler's own
metadata, unioned with gridgram-authored synonym tags in
`src/data/icon-tags.json`.
### Commands
```sh
# Exact-concept search.
gg icons --search database --limit 5
# Browse available tags (useful when the keyword isn't obvious).
gg icons --tags --limit 30
# Filter by tag.
gg icons --tag queue --limit 10
# Restrict to a specific style.
gg icons --set tabler-filled --search star
# JSON output for agent consumption.
gg icons --search loadbalancer --format json --limit 3
```
### Output formats
**Plain text** (default) — tab-separated columns: `ref / label / category / tags`:
```
tabler/database Database Database storage,data,memory,…
tabler/filled/database Database Database storage,data,memory,…
tabler/database-cog Database cog Database cog,configuration,…
```
**JSON** — each hit includes its score:
```json
[
{ "name": "database", "set": "tabler-outline", "ref": "tabler/database",
"label": "Database", "category": "Database",
"tags": ["storage", "data", "memory", "database", …],
"score": 10 },
…
]
```
### Scoring
| Score | Match type |
| ----- | ------------------- |
| 10 | exact name |
| 7 | name prefix |
| 5 | exact tag |
| 4 | name substring |
| 3 | label substring |
| 2 | category substring |
| 1 | tag substring |
Results are sorted by score descending, then alphabetically for ties.
Anything above score 5 is almost always the right pick.
### Recommended agent flow
1. **Try direct search first.** `gg icons --search --format json --limit 5`.
2. **If top score ≤ 2, pivot on tags.** `gg icons --tags --limit 30` shows
the frequency ranking; pick a related tag, then
`gg icons --tag --limit 15`.
3. **Choose a reference string to embed.** Output the picked icon as
`tabler/` (outline) or `tabler/filled/` (filled).
### Why the scoring matters
Tabler tags are rich (often 5–15 tags per icon), but they were written
for human browsing, not for architecture-diagram vocabulary. The
gridgram-authored overrides in `src/data/icon-tags.json` patch the
common misses:
- `cache` → `tabler/bolt`, `tabler/clock-play`
- `microservice` → `tabler/box`, `tabler/puzzle`
- `kubernetes` → `tabler/box-multiple`
- `websocket` → `tabler/plug`, `tabler/route`
- `loadbalancer` → `tabler/arrows-split`, `tabler/route`
- `frontend` → `tabler/browser`, `tabler/world`, `tabler/layout`
- `backend` → `tabler/server`, `tabler/database`
- `payment` → `tabler/credit-card`, `tabler/cash`
If you hit a concept that still returns poor results, file an issue —
the tag overrides are a user-facing knob intended to be extended.
## Verifying a rendered diagram
Every agent workflow should end in a validation step. `gg render`
combines the SVG output with a diagnostics stream:
```sh
gg render diagram.gg --format json --diagnostics --stdout > /tmp/envelope.json 2>/tmp/diag.json
```
- Exit code `1` → parse error. The error message is on stderr.
- Exit code `2` → integrity error (unknown node reference, region not
4-connected, etc.).
- Exit code `0` with `/tmp/diag.json === "[]"` → clean render.
- Exit code `0` with non-empty diagnostics → SVG produced, but with
warnings (missing icon, failed routing, etc.) that an agent should
surface or retry on.
See the [Diagnostics page](/en/developer/diagnostics) for the full
diagnostic shape.
---
# context7 tutorial
[context7](https://context7.com/) indexes open-source documentation
for retrieval over [MCP](https://modelcontextprotocol.io/). gridgram
registers itself with context7 via a `context7.json` manifest, so any
MCP-connected agent can query gridgram's docs without installing
anything gridgram-specific.
**Time**: ~5 minutes (most of it is configuring your MCP client).
**Result**: your agent can pull gridgram docs on demand.
Unlike [the Claude plugin](./claude-plugin) or
[`gh skill`](./gh-skill), this path is **retrieval-only** — it gives
the agent a searchable view of gridgram's documentation but no
slash commands and no automatic CLI driving. For a full authoring
workflow, pair it with one of the other two.
## Prerequisites
- **An MCP-capable agent host**: Claude Code, Cursor, or anything
else that can run [MCP servers](https://modelcontextprotocol.io/).
- **[context7 MCP server](https://github.com/upstash/context7)** —
usually added as an MCP server in the agent's config.
- **Network access** to `context7.com`.
No git, no `gh`, no binaries on PATH. Everything is pulled over MCP
at query time.
## 1. Add the context7 MCP server
### Claude Code
Claude Code's MCP configuration lives in `~/.claude/settings.json`
(user scope) or `.claude/settings.json` (project scope). Add:
```json
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"]
}
}
}
```
Restart Claude Code. The `context7` tools should now appear in
`/mcp`.
### Cursor and others
Follow each host's MCP configuration docs; the `command` + `args`
above is portable. Cursor writes MCP settings to
`~/.cursor/mcp.json`.
## 2. Verify context7 sees gridgram
Ask your agent:
> Look up the `gridgram` library on context7.
The agent calls `resolve-library-id("gridgram")`, which should return
something like `/ideamans/gridgram`. If it returns "not found", the
library hasn't been indexed yet — see
[Registration](#registration-if-not-yet-indexed) below.
## 3. Query gridgram docs
Try:
> Using context7, fetch gridgram's `.gg` grammar reference and
> explain how to declare an `icon` node.
The agent calls `query-docs` against the resolved library id and
gets back the relevant doc sections. Because context7 indexes
`docs/en`, `examples/`, and `src/generated/` (per the
`context7.json` manifest), the agent sees:
- The English user + developer + AI guide pages (this whole site).
- Canonical `.gg` examples.
- The generated `src/generated/llm-reference.md` bundle.
It does **not** see `docs/ja/`, tests, or internal source files.
## 4. Pair context7 with authoring
context7 alone can't run `gg`. Combine it with one of:
- [Claude Code plugin](./claude-plugin) — the most ergonomic
pairing; slash commands render while context7 provides retrieval
for anything the skill bodies don't cover.
- [`gh skill`](./gh-skill) — same skills in other hosts.
The agent will naturally use context7 when it needs background
information and the plugin skills when it needs to invoke `gg`.
## 5. Staying up to date
context7 **re-indexes automatically** from the GitHub repository
whenever gridgram's default branch changes. There's nothing to
update on your side; a new release of gridgram becomes available in
context7 within a few minutes of the merge.
You don't "install" a version of context7-backed gridgram docs — the
agent always gets the current default branch view.
## Registration (if not yet indexed)
If `resolve-library-id("gridgram")` returns "not found", context7
hasn't seen the repo yet. gridgram's `context7.json` is already in
place at the repo root, but the initial submission happens once per
library:
- Visit .
- Paste `https://github.com/ideamans/gridgram`.
- context7 crawls the repo, validates the manifest, and indexes.
Once listed on , every MCP client can discover
it. This step is only needed once per library; future re-indexes
happen automatically.
## The `context7.json` manifest
For reference, gridgram's manifest at the repo root tells context7:
- Index `docs/en`, `examples`, and `src/generated`.
- Skip `docs/.vitepress`, `docs/ja`, `node_modules`, `tests`,
`dist`, and `src/data`.
- Apply a small set of hand-picked **rules** — the non-obvious
constraints agents are most likely to get wrong (Preact not React,
`doc { … }` replaced the legacy `%%{…}%%` directive, diagnostics
are warnings not exceptions, etc.).
See the [manifest on GitHub](https://github.com/ideamans/gridgram/blob/main/context7.json)
for the current content.
## Troubleshooting
### Agent says "no MCP tools available"
Restart the host. Most MCP clients only scan the config on startup.
### `resolve-library-id` returns a close-but-wrong match
Specify the org: `resolve-library-id("ideamans/gridgram")`. Otherwise
you may hit a different library called `gridgram` indexed earlier.
### Docs feel stale
The agent might be using its own training-time knowledge instead of
calling context7. Ask explicitly:
> Query context7 for the latest gridgram CLI flags.
## Where next
- [Claude Code plugin](./claude-plugin) — pair context7 with
slash-command authoring.
- [`gh skill`](./gh-skill) — same skill bundle in other hosts.
- [llms.txt](https://gridgram.ideamans.com/llms.txt) /
[llms-full.txt](https://gridgram.ideamans.com/llms-full.txt) — if
you'd rather hand the docs to an agent as a single payload
without MCP.
---
# `gh skill` tutorial
The GitHub CLI ships a `gh skill` subcommand (v2.90+) that installs
Agent Skills from any GitHub repository — not just Anthropic ones.
gridgram's four skills (`gg-install`, `gg-render`, `gg-icons`,
`gg-author`) use standards-only frontmatter, so the same bundle you
see in the Claude marketplace also works in Copilot, Cursor, Gemini
CLI, Codex, and Antigravity.
**Time**: ~5 minutes. **Result**: the gridgram skills installed into
your agent of choice, plus the update command.
## Prerequisites
Install these yourself:
- [**GitHub CLI (`gh`)**](https://cli.github.com/), **v2.90 or later**
(earlier versions don't have the `skill` subcommand).
- **A target agent host**: Claude Code, GitHub Copilot, Cursor,
Gemini CLI, Codex, or Antigravity. The `--agent` flag picks which.
- **git** (gh uses it under the hood).
- **Network access** to `github.com`.
Authenticate gh once if you haven't already:
```sh
gh auth status # check
gh auth login # if "not logged in"
```
Authentication isn't strictly required for a public repo like
gridgram, but it avoids rate-limiting on repeated installs.
Check your `gh skill` availability:
```sh
gh skill --help
```
If you see "unknown command: skill", upgrade gh:
```sh
# Homebrew
brew upgrade gh
# Debian/Ubuntu
sudo apt install --only-upgrade gh
```
## 1. Pick your target agent
`gh skill install --agent ` supports six host targets. Each
tutorial snippet from here on has a tab per target — pick yours and
the commands copy-paste directly.
| Target | Where skills end up |
| --------------- | ------------------------------------------------------------------ |
| `claude-code` | `~/.claude/skills//SKILL.md` |
| `copilot` | GitHub Copilot's skills directory (default if `--agent` is omitted)|
| `cursor` | Cursor's skill directory |
| `codex` | OpenAI Codex |
| `gemini-cli` | Google Gemini CLI |
| `antigravity` | Antigravity |
## 2. Preview before installing
`gh skill preview` downloads a `SKILL.md` and shows the frontmatter
+ body so you can read what it'll do before letting it run:
```sh
gh skill preview ideamans/gridgram plugins/gridgram/skills/gg-install
```
Do the same for the other three (`gg-render`, `gg-icons`, `gg-author`)
if you want to inspect each.
## 3. Install the skills
Install all four with one copy-paste. Switch tabs to match your
target agent:
::: code-group
```sh [Claude Code]
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-install --agent claude-code
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-render --agent claude-code
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-icons --agent claude-code
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-author --agent claude-code
```
```sh [Copilot]
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-install --agent copilot
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-render --agent copilot
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-icons --agent copilot
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-author --agent copilot
```
```sh [Cursor]
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-install --agent cursor
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-render --agent cursor
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-icons --agent cursor
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-author --agent cursor
```
```sh [Codex]
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-install --agent codex
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-render --agent codex
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-icons --agent codex
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-author --agent codex
```
```sh [Gemini CLI]
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-install --agent gemini-cli
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-render --agent gemini-cli
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-icons --agent gemini-cli
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-author --agent gemini-cli
```
```sh [Antigravity]
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-install --agent antigravity
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-render --agent antigravity
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-icons --agent antigravity
gh skill install ideamans/gridgram plugins/gridgram/skills/gg-author --agent antigravity
```
:::
Verify:
```sh
gh skill list
```
The list shows each skill plus the source repository + ref + tree SHA.
That provenance metadata gets written directly into each
`SKILL.md`'s frontmatter on install, so `gh skill update` can detect
content changes later even without a version bump.
## 4. Install the `gg` CLI
The skills shell out to the `gg` binary. Either path works:
### Option A: Use `/gg-install` inside your agent
Restart your agent host so the new skills load, then ask:
> Install the gridgram CLI for my platform.
The agent picks up `/gg-install` and performs:
1. OS + arch detection.
2. Download of the latest release from
.
3. Install into the first writable `$PATH` directory (or stage at
`/tmp/gg` with a sudo hint).
### Option B: Install `gg` the regular way
If you'd rather skip the skill, follow the standard install guide at
**[Quick start](/en/guide/)** (one-line curl / PowerShell scripts) or
**[Install](/en/guide/install)** for manual alternatives. Any install
that puts `gg` on `$PATH` is picked up by the skills automatically.
### Verify
```sh
gg --help
```
You should see `gg v`.
## 5. Try a first render
> Draw a gridgram diagram showing a browser calling an API backed by
> a Postgres database. Save it as `~/first-diagram.svg`.
The agent uses `/gg-icons` to pick icons, `/gg-author` to compose
the `.gg` source, and `/gg-render` to produce the SVG with
diagnostics. Open the result in a browser.
## 6. Keep everything up to date
### Update skills
```sh
gh skill update
```
With no arguments it updates every installed skill. The provenance
metadata in each `SKILL.md` tells gh where to fetch from — the tree
SHA comparison means you'll get genuine content changes, not spurious
no-op updates.
Update a single skill:
```sh
gh skill update ideamans/gridgram plugins/gridgram/skills/gg-author
```
### Update the `gg` binary
Just ask the agent:
> Update my gridgram CLI to the latest version.
`/gg-install` replaces the existing binary in place.
## 7. Uninstall (optional)
```sh
gh skill remove ideamans/gridgram plugins/gridgram/skills/gg-author
# …and the other three
```
To remove `gg`:
```sh
rm "$(which gg)"
rm -rf ~/.cache/gridgram
```
## Troubleshooting
### `unknown command: skill`
Upgrade gh to **v2.90+**. Check with `gh --version`.
### "Rate limit exceeded" from `gh skill install`
Run `gh auth login` and retry — authenticated requests get a higher
rate limit.
### Skill installed but agent doesn't see it
Restart the agent host. Most hosts only scan the skills directory at
startup.
### Skill with extra frontmatter fields logs a warning
Expected: Claude Code's frontmatter extensions (`disable-model-invocation`,
`paths`, …) aren't in gridgram's SKILL files, so this shouldn't
happen. If it does, file an issue.
## Where next
- [Claude Code plugin tutorial](./claude-plugin) — slash-command-driven
flow for Claude Code users, with marketplace-level auto-update.
- [CLI reference](./cli) — what the skills are doing under the hood.
- [context7](./context7) — complementary MCP retrieval; works
alongside any skill host.
---
# `llms.txt` — public discovery files
gridgram's docs site serves two files at the root of the deployed
domain. They follow the [llmstxt.org](https://llmstxt.org/) convention
and the Mintlify / Anthropic "full bundle" convention respectively.
| File | Size | Contents |
| ------------------------------------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------- |
| [`/llms.txt`](https://gridgram.ideamans.com/llms.txt) | ~5 KB | Markdown index: project name, one-line summary, grouped links to every English docs page plus the LLM bundle. |
| [`/llms-full.txt`](https://gridgram.ideamans.com/llms-full.txt) | ~230 KB | Every docs page's markdown body concatenated end-to-end, followed by the full [`gg llm`](./cli#gg-llm) reference. |
Both are regenerated from `docs/en/**` at docs build time
(`bun run build-llms-txt`), so the links and content always match
the live site.
## When to use which
- **`/llms.txt`** — when an agent is crawling and needs an index to
decide what to fetch next. Every link points at the raw `.md`
source of a page (not the rendered HTML), so the agent can
consume it directly.
- **`/llms-full.txt`** — when you want to **hand an agent the whole
docs tree plus the grammar reference in one payload**. Around
230 KB fits comfortably in modern context windows; saves the
agent from N round-trips.
## Quick examples
### Feed the bundle to a CLI agent
```sh
curl -s https://gridgram.ideamans.com/llms-full.txt | your-agent --context -
```
### Check the index from a shell
```sh
curl -s https://gridgram.ideamans.com/llms.txt | head -30
```
### Extract the LLM reference block
```sh
curl -s https://gridgram.ideamans.com/llms-full.txt \
| sed -n '//,$p'
```
## Relationship to the other surfaces
These files are a **discovery** channel, not a replacement for the
other paths in this guide:
- If you control an agent and can install plugins, the
[Claude plugin](./claude-plugin) or [`gh skill`](./gh-skill)
gives you slash-command authoring on top of the CLI.
- If you want retrieval over MCP (the `context7` protocol),
[context7](./context7) indexes the same docs but exposes them
through an MCP server.
- If you only have read-only HTTP, `llms.txt` / `llms-full.txt` is
the zero-setup path — no auth, no config, just `curl`.
An agent that walks `/llms.txt` can find everything else (including
`/llms-full.txt` itself, the GitHub releases, and the plugin
install commands), so it's a good single entry point if you're not
sure what's available.
## Languages
Only the English docs (`docs/en/**`) feed these files. Japanese
pages (`docs/ja/**`) are not indexed — gridgram is registered as an
English-primary library on context7, and keeping a single agent-facing
locale avoids the risk of agents mixing translations mid-answer.
## Source
The generator is `scripts/build-llms-txt.ts` in the gridgram repo.
It's wired into `bun run ai:regen` and into `docs:build`, so a fresh
deploy always ships current files. The source of truth is simply
`docs/en/**/*.md` — there's no hand-maintained link list.
---
# Developer Guide
You're here because you want to drive Gridgram from code — a Preact or
plain-ESM browser app, a Node server that renders on demand, an agent
that edits `.gg` and re-renders in a loop, a build step in CI, or an
MCP tool exposing diagramming to an LLM.
The [User Guide](/en/guide/) covers authoring: writing `.gg` files and
running the `gg` CLI. This side covers the TypeScript API — what the
npm package exports, what runs where, and the structured feedback
surface agents consume.
## Two entry points
```ts
// Browser-safe. Pure — no fs, no path, no network.
import { renderDiagram, parseGg, Diagram /* … */ } from 'gridgram'
// Node-only. Reads the filesystem (and optionally fetches over HTTP).
import { buildIconContext, loadProjectConfig } from 'gridgram/node'
```
The main entry point works in every ESM host — modern browsers (via
Vite / Rspack / esbuild / any bundler), Node ≥ 22, Bun, Deno. The
`gridgram/node` subpath is only safe inside a Node runtime; browser
bundles that accidentally import it will fail to build.
## Rendering primitives
Four entry points cover every integration pattern:
| Import | Shape | When to reach for it |
|--------------------------------|---------------------------------------------------------|------------------------------------------------------------------|
| `renderDiagram(def, opts)` | `(DiagramDef, opts?) => { svg, diagnostics }` | Default choice. SVG string plus layout/icon feedback. |
| `renderDiagramSvg(def, opts)` | `(DiagramDef, opts?) => string` | SVG only — the caller already knows the input is clean. |
| `Diagram` | Preact FC — `` | Embedding inline in a Preact app. |
| `buildDiagramTree(def, opts)` | `(DiagramDef, opts?) => VNode` | A raw Preact VNode tree for custom renderers / SSR pipelines. |
All four ultimately take a `DiagramDef`. You can construct one
programmatically or produce one from `.gg` source via `parseGg`.
## Reading order
1. **[Quickstart (TS API)](./quickstart)** — install, render your
first diagram from code.
2. **[`renderDiagram` & friends](./render)** — the four entry points
in depth, including ``, `RenderResult`, and
`computeRenderDimensions`.
3. **[Types](./types)** — `DiagramDef` and its members. Normalized vs
raw-input shapes.
4. **[Configuration](./config)** — the layered settings system
(system → project → document → render override) and how every
surface composes it.
5. **[Parser](./parser)** — `.gg` source to `DiagramDef`. `parseGg`,
icon resolution, `GgError`, `checkIntegrity`.
6. **[Diagnostics](./diagnostics)** — the `PlacementDiagnostic`
stream. Essential for agent workflows; useful for human authors too.
7. **[Integrations](./integrations)** — HTTP endpoint, MCP tool,
headless rendering in CI, Preact embedding, PNG.
8. **[Specification](./spec)** — the reference: token grammar,
resolution pipeline, determinism guarantees.
## Determinism is a contract
Every Gridgram surface (CLI, TS API, HTTP, MCP) drives the same
pipeline. Given the same `DiagramDef` — same nodes, same settings,
same icon sources — the pipeline produces **byte-identical SVG**
output across surfaces and runs. Agents rely on this:
- Commit the `.gg` source to git. Every AI edit reads as a diff.
- Pin an SVG baseline in CI. A rendering regression shows up as a
visible change.
- Skip the icon loader by pre-resolving `src` to inline SVG, and the
renderer is fully pure — no I/O at all.
The [Specification](./spec) page covers the invariants formally.
---
# Configuration
Gridgram resolves settings through four layers, in order of
increasing precedence:
```
system defaults
↓
project config (gridgram.config.{ts,js,json,json5})
↓
document (doc { … } block inside a .gg file)
↓
render override (CLI flag / DiagramOptions on a render call)
```
Every surface — CLI, TS API, HTTP endpoint, MCP tool — builds an
array of `DiagramSettings` layers in this order and calls
`resolveSettings()`. No surface branches past the resolver.
## The layer types
```ts
interface DiagramSettings {
cellSize?: number
padding?: number
columns?: number
rows?: number
theme?: Partial
renderWidth?: number
suppressErrors?: boolean
assetAliases?: Record
iconsDir?: string
}
interface ResolvedSettings extends Required> {
padding?: number
columns?: number
rows?: number
theme: DiagramTheme
renderWidth?: number
iconsDir?: string
}
```
Every `DiagramSettings` field is optional. `ResolvedSettings`
guarantees the fields that always have a sane default
(`cellSize`, `theme`, `suppressErrors`, `assetAliases`).
## `resolveSettings(layers)`
```ts
import { resolveSettings, SYSTEM_DEFAULTS } from 'gridgram'
const resolved = resolveSettings([
projectLayer, // from gridgram.config.ts
documentLayer, // from doc { } in the .gg
renderOverride, // CLI flag or DiagramOptions
])
```
Merge rules:
- Scalars (`cellSize`, `renderWidth`, …) — last layer wins.
- `theme` — deep-merged: each layer overlays its keys onto the
previous theme.
- `assetAliases` — shallow-merged: later entries replace earlier
ones by alias name.
- `SYSTEM_DEFAULTS` is always the starting point; you never need to
include it explicitly.
## Project config files
A project-wide baseline lives in the nearest `gridgram.config.*`
walking up from `cwd`. The CLI and the library-level
`loadProjectConfig` helper both use this discovery.
```ts
// gridgram.config.ts
import { defineConfig } from 'gridgram'
export default defineConfig({
cellSize: 128,
theme: {
primary: '#1e3a5f',
accent: '#e8792f',
},
assetAliases: {
brand: './assets/logos', // → '@brand/aws.svg' resolves under here
},
})
```
Supported extensions: `.ts`, `.mjs`, `.js`, `.cjs`, `.json5`,
`.json`. TS/JS files can be fully typed via `defineConfig`, which is
an identity helper:
```ts
import { defineConfig } from 'gridgram'
export default defineConfig({ … })
```
### Loading a config from code
`loadProjectConfig` reads the filesystem, so it lives on the
`'gridgram/node'` subpath:
```ts
import { loadProjectConfig } from 'gridgram/node'
import { resolveSettings } from 'gridgram'
const found = await loadProjectConfig({ cwd: process.cwd() })
const projectLayer = found?.settings ?? {}
const resolved = resolveSettings([projectLayer, renderOverride])
```
Pass `explicitPath` to skip walk-up discovery. Returns `null` if no
config is found (no error — projects without one are fine).
In a browser bundle this import will fail. If you want per-project
defaults in a browser app, serialize them into a plain `DiagramSettings`
object at build time and import it as regular data.
## The `doc { }` layer
A `.gg` file's `doc { }` block provides the document-layer settings:
```gg
doc {
cellSize: 256,
theme: { primary: '#1e3a5f' },
}
icon @A1 tabler/user "U"
```
When rendered through the CLI, this sits between the project config
and any `--cell-size` / `--width` flags. Via the TS API it ends up
on `rawDef.cellSize` / `.theme` after `parseGg`; `foldLayers` (inside
`renderDiagram`) merges those into the document layer automatically.
## `SYSTEM_DEFAULTS`
```ts
import { SYSTEM_DEFAULTS } from 'gridgram'
SYSTEM_DEFAULTS = {
cellSize: 256,
suppressErrors: false,
assetAliases: {},
theme: {
primary: '#1e3a5f',
secondary: '#3b5a80',
accent: '#e8792f',
text: '#2d3748',
bg: '#ffffff',
},
}
```
Read-only. Exported so callers can introspect (for example, a
settings UI that shows "default" alongside an override).
## Surface-specific composition
### CLI
```
[projectLayer, documentLayer, cliFlagOverrides]
```
Walks up from `--config ` or cwd; parses the .gg source for
its `doc { }`; collects CLI flags into the last layer.
### TS API (single call)
```
[documentLayer, opts]
```
`documentLayer` is extracted from the raw `DiagramDef`'s own
`cellSize` / `theme` / etc. on the fly. `opts` is what you pass to
`renderDiagram(def, opts)`.
### TS API (with a project config)
Call `loadProjectConfig` yourself and prepend:
```ts
const { settings: projectLayer } = (await loadProjectConfig({})) ?? {}
const resolved = resolveSettings([projectLayer ?? {}, documentLayer, opts])
```
### HTTP / MCP
Same shape as the TS API. The server decides whether a project
config applies (usually yes — a single config per deployment) and
prepends it.
## See also
- [`renderDiagram` & friends](./render) — the settings flow into
`foldLayers` automatically.
- [Parser](./parser) — `parseGg` extracts the document layer from
`doc { }`.
- [User Guide: Document](../guide/document/merging) — same rules
from the author's perspective.
---
# Diagnostics
Gridgram's pipeline doesn't just produce an SVG — it produces
structured feedback about anything that didn't fit cleanly. Labels
that couldn't find a collision-free slot, connectors that couldn't
route around obstacles, icon sources that failed to resolve — each
becomes a `PlacementDiagnostic` record the caller can inspect,
print, or hand to an AI agent for remediation.
This is the primary surface agents use to iterate on a diagram.
## The `PlacementDiagnostic` shape
```ts
interface PlacementDiagnostic {
kind:
| 'label-collision' // a label was forced into an overlapping fallback
| 'route-failed' // a connector couldn't route around obstacles
| 'icon-unresolved' // node.src / badge.icon didn't resolve
| 'region-disjoint' // (reserved; currently surfaces as an integrity error)
severity: 'warning' | 'error'
element: ElementRef
message: string // human-readable one-liner
suggestion?: string // optional remediation hint
finalRect?: PixelRect // where the element actually drew
attempts?: PlacementAttempt[] // full placement search history
iconSrc?: string // icon-unresolved: the original DSL id
iconReason?: 'not-found' | 'load-failed' | 'malformed'
}
```
Every diagnostic carries enough context to:
1. **Identify the element** that had trouble (`element` + optional
`finalRect` to pinpoint the drawn position).
2. **See what went wrong** (`message`, the `attempts` search history
with `obstacles` at each tried slot).
3. **Choose a fix** — either automatically (agent) or manually
(human reading CLI output).
## `ElementRef`
```ts
type ElementRef =
| { kind: 'node'; id: string; pos?: GridCellRef; line?: number }
| { kind: 'note'; id: string; pos?: GridCellRef; line?: number }
| { kind: 'region'; id?: string; span: GridSpanRef; line?: number }
| { kind: 'connector'; id?: string; from: string; to: string; line?: number }
interface GridCellRef { col: number; row: number; address: string } // 1-based
interface GridSpanRef { from: GridCellRef; to: GridCellRef }
```
All coordinates here are **1-based with A1 addresses** — the same
form an agent / human wrote in the source. No 0-based internal
coordinates reach this boundary.
## `PlacementAttempt`
```ts
interface PlacementAttempt {
slot: string // human-readable ("top-right", "seg 2 / t=0.5 above")
rect: PixelRect // where the label would have drawn
obstacles: Obstacle[] // everything that blocked this slot
accepted: boolean // the winning slot (last entry) vs rejected tries
}
```
The last attempt in the array is always marked `accepted: true` —
either a clean slot, or the fallback that got picked anyway even
with obstacles.
## `Obstacle`
```ts
type Obstacle =
| { kind: 'label'; owner: ElementRef; rect: PixelRect }
| { kind: 'icon'; owner: ElementRef; circle: PixelCircle }
| { kind: 'line'; owner: ElementRef; line: PixelLine }
| { kind: 'leader'; owner: ElementRef; line: PixelLine }
| { kind: 'canvas-bounds'; bounds: { width: number; height: number } }
```
`owner` points back to the element whose label/icon/line blocked
the placement. Pixel geometry lets an agent reason quantitatively
about how deep the overlap went.
## Example: a label-collision diagnostic
```json
{
"kind": "label-collision",
"severity": "warning",
"element": {
"kind": "node",
"id": "api",
"pos": { "col": 2, "row": 1, "address": "B1" }
},
"message": "Label for node \"api\" could not find a clear slot across 7 candidates; final fallback still blocked by icon of node \"db\", label of node \"web\".",
"finalRect": { "x": 512, "y": 200, "w": 82, "h": 28 },
"attempts": [
{
"slot": "top-right",
"rect": { "x": 520, "y": 148, "w": 82, "h": 28 },
"obstacles": [
{
"kind": "icon",
"owner": { "kind": "node", "id": "db", "pos": { "col": 2, "row": 2, "address": "B2" } },
"circle": { "cx": 512, "cy": 400, "r": 57.6 }
}
],
"accepted": false
},
{
"slot": "bottom-right",
"rect": { "x": 520, "y": 252, "w": 82, "h": 28 },
"obstacles": [
{
"kind": "line",
"owner": { "kind": "connector", "from": "api", "to": "db" },
"line": { "x1": 512, "y1": 200, "x2": 512, "y2": 400 }
}
],
"accepted": false
},
/* … five more attempts … */
{
"slot": "top-left",
"rect": { "x": 430, "y": 148, "w": 82, "h": 28 },
"obstacles": [/* fallback still collides */],
"accepted": true
}
]
}
```
An agent reading this can:
- Move `api` to an outer column — `C1` would clear `db`'s icon.
- Shorten the `api` label so it fits in the narrower slots.
- Remove the direct `api → db` connector or waypoint it.
## Example: a route-failed diagnostic
```json
{
"kind": "route-failed",
"severity": "warning",
"element": { "kind": "connector", "from": "a", "to": "b" },
"message": "Connector a→b crosses node(s) \"mid\" and no routed alternative was found; the line is drawn straight through.",
"suggestion": "Move a / b to an outer cell, add waypoints to steer the connector, or relocate \"mid\" so a clear path exists.",
"attempts": [{
"slot": "direct line",
"rect": { "x": 0, "y": 0, "w": 0, "h": 0 },
"obstacles": [{
"kind": "icon",
"owner": { "kind": "node", "id": "mid", "pos": { "col": 2, "row": 1, "address": "B1" } },
"circle": { "cx": 240, "cy": 120, "r": 58 }
}],
"accepted": true
}]
}
```
## Example: an icon-unresolved diagnostic
```json
{
"kind": "icon-unresolved",
"severity": "warning",
"element": { "kind": "node", "id": "api" },
"message": "Node \"api\" src=\"tabler/userr\" could not be resolved (no matching Tabler icon or registered entry).",
"iconSrc": "tabler/userr",
"iconReason": "not-found"
}
```
`iconReason` separates two distinct remediations:
- `'not-found'` — no such tabler name, no registered bare name, no
resolved path. **Typical fix: rename / register.**
- `'load-failed'` — the loader attempted the source and got an I/O
or network error. **Typical fix: check connectivity / path.**
For `'load-failed'`, the loader's error message is included in the
diagnostic's `message`.
## Getting diagnostics
### From the TS API
```ts
import { renderDiagram, resolveDiagramIcons } from 'gridgram'
const { def, diagnostics: iconDiagnostics } = resolveDiagramIcons(rawDef, ctx)
const { svg, diagnostics: layoutDiagnostics } = renderDiagram(def)
const all = [...iconDiagnostics, ...layoutDiagnostics]
```
### From the CLI
```sh
gg diagram.gg -o out.svg --diagnostics
# ^ writes JSON array of diagnostics to stderr
```
Or bundled with `--format json`:
```sh
gg diagram.gg --format json --stdout
# { "def": { … resolved DiagramDef … },
# "diagnostics": [ … all diagnostics … ] }
```
A single stdout read gives an agent the resolved def plus its
feedback.
## Writing an agent loop
```ts
import { parseGg, resolveDiagramIcons, renderDiagram } from 'gridgram'
import { buildIconContext } from 'gridgram/node'
async function autoFixDiagram(source: string, maxAttempts = 5): Promise {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const { def, errors, icons } = parseGg(source)
if (errors.length) throw new Error(errors.map((e) => e.message).join('\n'))
const ctx = await buildIconContext({ jsonIconsMap: icons, def, docDir: '.' })
const iconResolve = resolveDiagramIcons(def, ctx)
const rendered = renderDiagram(iconResolve.def)
const diagnostics = [...iconResolve.diagnostics, ...rendered.diagnostics]
if (diagnostics.length === 0) return rendered.svg
// Hand the diagnostics to an LLM that edits the .gg source.
source = await llm.rewriteDiagram(source, diagnostics)
}
throw new Error('diagram still has diagnostics after retry budget')
}
```
The shape is chosen so the LLM can receive the diagnostics as JSON
directly — no custom parsing layer.
## Severity
- `'warning'` — the diagram still renders. Labels show in
fallback positions; routes draw straight through obstacles. Red
markers in the SVG (unless `suppressErrors: true`) make the issue
visible.
- `'error'` — the render is structurally broken. Currently unused
at runtime (the reserved `region-disjoint` kind surfaces through
`parseGg`'s integrity errors instead); kept in the type for future
extension.
## See also
- [`renderDiagram` & friends](./render) — returns `diagnostics`.
- [Parser](./parser) — `resolveDiagramIcons` returns icon-unresolved
diagnostics.
- [Integrations](./integrations) — MCP and HTTP surfaces.
---
# Integrations
Patterns for embedding Gridgram in a host system. None of these are
separate packages — they're conventions that compose the primitive
API into whatever shape your host needs.
> **Import boundary reminder:** the pure pipeline (`parseGg`,
> `resolveDiagramIcons`, `renderDiagram`, …) comes from `'gridgram'`
> and runs anywhere. Filesystem / HTTP helpers (`buildIconContext`,
> `loadProjectConfig`) come from `'gridgram/node'` and only work in a
> Node runtime.
## HTTP endpoint
A minimal service that accepts `.gg` source and returns SVG +
diagnostics:
```ts
import { serve } from 'bun'
import { parseGg, resolveDiagramIcons, renderDiagram } from 'gridgram'
import { buildIconContext } from 'gridgram/node'
serve({
port: 3000,
async fetch(req) {
if (req.method !== 'POST') return new Response('POST /render', { status: 405 })
const source = await req.text()
const { def: rawDef, errors, icons } = parseGg(source)
if (errors.length > 0) {
return Response.json({ errors }, { status: 400 })
}
const ctx = await buildIconContext({
jsonIconsMap: icons,
def: rawDef,
docDir: process.cwd(),
})
const { def, diagnostics: iconDiags } = resolveDiagramIcons(rawDef, ctx)
const { svg, diagnostics: layoutDiags } = renderDiagram(def)
return Response.json({
svg,
diagnostics: [...iconDiags, ...layoutDiags],
})
},
})
```
Notes:
- No per-request project config — deploy-wide settings go in a
`gridgram.config.ts` loaded once at startup and prepended via
`resolveSettings` (see [Configuration](./config)).
- Node.js works the same; swap `Bun.serve` for `http.createServer` or
Fastify / Hono.
## MCP tool
A Model Context Protocol server exposing Gridgram as an agent tool.
Skeleton:
```ts
import { Server } from '@modelcontextprotocol/sdk/server'
import { parseGg, resolveDiagramIcons, renderDiagram } from 'gridgram'
import { buildIconContext } from 'gridgram/node'
const server = new Server({ name: 'gridgram', version: '0.1.0' }, {
capabilities: { tools: {} },
})
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name !== 'render_diagram') throw new Error('unknown tool')
const source = req.params.arguments?.source as string
const { def: rawDef, errors, icons } = parseGg(source)
if (errors.length) {
return { content: [{ type: 'text', text: JSON.stringify({ errors }) }] }
}
const ctx = await buildIconContext({
jsonIconsMap: icons, def: rawDef, docDir: process.cwd(),
})
const { def, diagnostics: iconDiags } = resolveDiagramIcons(rawDef, ctx)
const { svg, diagnostics: layoutDiags } = renderDiagram(def)
const diagnostics = [...iconDiags, ...layoutDiags]
return {
content: [
{ type: 'text', text: JSON.stringify({ diagnostics }, null, 2) },
{ type: 'image', data: Buffer.from(svg).toString('base64'), mimeType: 'image/svg+xml' },
],
}
})
```
The critical bit for agent usability: return the `diagnostics` array
in the response so the LLM sees *why* its diagram didn't render
cleanly, not just the red-tinted SVG. See [Diagnostics](./diagnostics)
for an example agent loop.
## Headless build step
In a static-site generator or CI job, process every `.gg` file in a
directory:
```ts
import { readdir, readFile, writeFile } from 'node:fs/promises'
import { join, dirname } from 'node:path'
import { parseGg, resolveDiagramIcons, renderDiagram } from 'gridgram'
import { buildIconContext } from 'gridgram/node'
async function buildAll(dir: string): Promise {
for (const entry of await readdir(dir, { recursive: true })) {
if (!entry.endsWith('.gg')) continue
const path = join(dir, entry)
const source = await readFile(path, 'utf-8')
const { def: rawDef, errors, icons } = parseGg(source)
if (errors.length) throw new Error(`${path}: ${errors[0].message}`)
const ctx = await buildIconContext({
jsonIconsMap: icons, def: rawDef, docDir: dirname(path),
})
const { def } = resolveDiagramIcons(rawDef, ctx)
const { svg, diagnostics } = renderDiagram(def)
if (diagnostics.length > 0) {
console.warn(`${path}:`, diagnostics.map((d) => d.message).join('\n '))
}
await writeFile(path.replace(/\.gg$/, '.svg'),
`\n${svg}`)
}
}
```
The docs site in this repository uses exactly this pattern — see
`scripts/build-docs-examples.ts` for the full version (adds PNG
rasterization via `sharp` and a `.gg` / `.ts` parity check).
## PNG rasterization
Gridgram doesn't ship `sharp` as a dependency — the TS API renders
SVG only, and `sharp` is a heavy native module that belongs to the
host. Install it when you need PNG:
```sh
npm install sharp
```
```ts
import sharp from 'sharp'
import { renderDiagram, computeRenderDimensions } from 'gridgram'
const { svg } = renderDiagram(def, { renderWidth: 2048 })
const { width, height } = computeRenderDimensions(def, { renderWidth: 2048 })
await sharp(Buffer.from(svg))
.resize(width, height)
.png()
.toFile('out.png')
```
`computeRenderDimensions` and `renderDiagram` take the same
`DiagramOptions`, so the aspect ratio lines up.
Alternative — the `gg` CLI bundles sharp (lazy-loaded into
`~/.cache/gridgram/` on first PNG render). If your pipeline already
has access to the binary, shelling out to `gg -o out.png` is often
simpler than wiring sharp into your app.
## Preact / browser embedding
If your host is a Preact application (or any ES-module browser app
bundled by Vite / Rspack / esbuild), use the `` component:
```tsx
import { Diagram } from 'gridgram'
import type { DiagramDef } from 'gridgram'
export function DiagramView({ def }: { def: DiagramDef }) {
return
}
```
Or drop to the raw VNode tree with `buildDiagramTree` if you want to
wrap it yourself. Only the pure entry point (`'gridgram'`) is used —
safe to ship to browsers.
`parseGg` and `resolveDiagramIcons` are pure too, so a browser-side
live editor only needs those plus the rendering functions. See the
[docs/.vitepress/theme/Editor.vue](https://github.com/ideamans/gridgram/blob/main/docs/.vitepress/theme/Editor.vue)
source for a complete example.
## Workers / worker threads
`renderDiagram` is pure and thread-safe — no module-level mutable
state, no globals. Each worker can import `gridgram` and call it
independently. The Tabler icon JSON is read-only and shared safely.
## Caching
For a memoized render layer:
```ts
const cache = new Map()
function renderCached(def: DiagramDef): string {
const key = JSON.stringify(def) // good enough for equality
const cached = cache.get(key)
if (cached !== undefined) return cached
const { svg } = renderDiagram(def)
cache.set(key, svg)
return svg
}
```
Because `renderDiagram` is deterministic, a cache keyed on the full
input def is sound. If you've resolved icons upstream, the def's
`src` fields are inline SVG strings — the cache key will be long but
stable.
## See also
- [CLI](../guide/cli) — built-in one-shot rendering.
- [Configuration](./config) — composing project / deploy-wide
defaults.
- [Diagnostics](./diagnostics) — the feedback format every
integration surfaces.
---
# Parser
`.gg` source → `DiagramDef` in three stages, each exposed as a
separate function you can call directly:
```
.gg source
│
▼
parseGg(source) → { def, errors, icons }
│ (includes integrity checks)
▼
buildIconContext({ … }) → IconContext (gridgram/node)
│ (filesystem / network reads)
▼
resolveDiagramIcons(def, ctx) → { def, diagnostics }
│
▼
renderDiagram(def) → { svg, diagnostics }
```
**Browser vs Node**: `parseGg`, `resolveDiagramIcons`, `checkIntegrity`,
`formatError`, and the icon classification helpers (`isPathRef`,
`collectPathRefs`, `stripSvgWrapper`) are all pure and exported from
`'gridgram'` — they work in every ESM host. The filesystem / HTTP icon
loader (`buildIconContext` and friends) lives on the `'gridgram/node'`
subpath and is Node-only.
## `parseGg(source)`
```ts
import { parseGg } from 'gridgram'
interface ParseResult {
def: DiagramDef
errors: GgError[]
icons?: Record // from `doc { icons: … }`
}
function parseGg(source: string): ParseResult
```
Tokenizes the source, folds `doc`/`icon`/`region`/`note`/connector
statements into a `DiagramDef`, and runs integrity checks (connector
refs, note targets, region spans). Three categories of error end up
in `errors`:
| `source` | Meaning |
|----------|---------|
| `'dsl'` | Tokenizer / statement-parser issue — unknown statement, malformed attr. |
| `'json'` | The JSON5 parser rejected a `doc { }` or `icons:` body. |
| `'check'`| Integrity: unknown connector target, disjoint region, etc. |
Parse errors are surfaced before the def is assembled; an errored
def may still be partially usable for quick-look previews, but don't
render one in production without handling `errors`.
### `GgError` shape
```ts
interface GgError {
message: string
line: number // 1-based
source: 'dsl' | 'json' | 'check' | 'icon'
snippet?: string // the offending source line
related?: { line: number; source: GgErrorSource; snippet?: string }
}
```
Use `formatError` for a readable string:
```ts
import { formatError } from 'gridgram'
for (const e of errors) console.error(formatError(e, filename))
// Error: Unknown statement "icn"
// at diagram.gg:3 (DSL: icn :a @A1 "hello")
```
### The `icons` field
A `.gg` file can register inline icon sources directly:
```gg
doc {
icons: {
logo: 'https://example.com/logo.svg',
brand: './assets/brand.svg',
},
}
icon @A1 logo "Us"
icon @B1 brand "You"
```
`parseGg` returns those as `icons: Record` alongside
the def. The resolver consumes it via `IconContext.inline` (for raw
SVG strings) or via `buildIconContext`'s `jsonIconsMap` for URL /
file / dataURL values that need loading. Keeps the source-time icon
references off the `DiagramDef` type (which models the rendered
diagram, not its provenance).
## `resolveDiagramIcons(def, ctx)`
```ts
import { resolveDiagramIcons } from 'gridgram'
function resolveDiagramIcons(
def: DiagramDef,
ctx: IconContext,
): { def: DiagramDef; diagnostics: PlacementDiagnostic[] }
```
Replaces every string-valued `node.src` with its resolved SVG
fragment. Nodes whose src couldn't be resolved get `iconError: true`
and their `src` deleted. Each of those failures emits a
[PlacementDiagnostic](./diagnostics) with:
- `kind: 'icon-unresolved'`
- `iconSrc`: the original DSL identifier
- `iconReason`: `'not-found'` (tabler miss / unregistered) or
`'load-failed'` (the loader tried and errored)
Callers usually concat these with the render-time diagnostics to
hand an agent one stream:
```ts
const iconResolve = resolveDiagramIcons(rawDef, ctx)
const rendered = renderDiagram(iconResolve.def)
const allDiagnostics = [...iconResolve.diagnostics, ...rendered.diagnostics]
```
Pure, synchronous, browser-safe. `ctx` is just a plain object — you
can construct it by hand (typical for in-browser use where only
Tabler built-ins and pre-loaded inline SVG are in play) or let
`buildIconContext` populate it for you in Node.
## `IconContext`
```ts
interface IconContext {
inline?: Record // from doc.icons / --icons map
dir?: Record // from --icons basename
aliases?: Record // from --alias / project config
paths?: Record // pre-loaded external refs
errors?: GgError[] // non-fatal loader issues
failedSources?: Map // identifier → reason
}
```
Every field is optional. The simplest valid context — `{}` — resolves
Tabler built-ins only and flags every other `src` as `iconError`.
## `buildIconContext(opts)` — Node only
```ts
import { buildIconContext } from 'gridgram/node'
function buildIconContext(opts: {
iconsDir?: string
jsonIconsMap?: Record
aliases?: Record
def?: DiagramDef
docDir: string // for cwd-relative paths AND for the icons map
aliasDir?: string // defaults to docDir
}): Promise
```
Walks the def for every external-path icon reference and pre-reads
it (filesystem or HTTP). Returns an `IconContext` the resolver
consumes. Non-fatal per-icon failures end up on `ctx.errors` (as
`GgError`) and `ctx.failedSources` (map keyed by the DSL identifier).
```ts
import { buildIconContext } from 'gridgram/node'
const ctx = await buildIconContext({
iconsDir: settings.iconsDir, // --icons
jsonIconsMap: parseResult.icons, // from parseGg
aliases: settings.assetAliases, // from project config
def: rawDef,
docDir: dirname(sourcePath),
aliasDir: process.cwd(),
})
// Surface per-icon loader errors early
for (const err of ctx.errors ?? []) console.error(formatError(err, sourcePath))
```
This import fails in browser bundles. In the browser, either skip
external icon references entirely (Tabler only) or pre-load them in
your host code and hand the result to `resolveDiagramIcons` via
`ctx.inline` / `ctx.paths`.
### Path reference resolution
| DSL form | Where it resolves |
|-----------------------|-------------------|
| `'@brand/aws.svg'` | `aliases.brand + '/aws.svg'` (absolute if `aliases.brand` is absolute; else joined with `aliasDir`) |
| `'./foo.svg'` | `docDir + '/foo.svg'` |
| `'/abs/path.svg'` | as-is |
| `'foo.svg'` | `docDir + '/foo.svg'` |
## `checkIntegrity(def)`
```ts
import { checkIntegrity } from 'gridgram'
function checkIntegrity(def: DiagramDef): GgError[]
```
Post-parse checks that `parseGg` runs for you automatically:
- Connector `from` / `to` reference known node ids
- Note `targets` reference known node / connector ids
- Region spans fit within `columns × rows`
- Region spans form a single 4-connected shape
- Coordinate malformations (bad A1, col < 1, …)
Exposed separately so programmatic callers who build a `DiagramDef`
from scratch (skipping `parseGg`) can still validate it. Error
messages use 1-based A1 coordinates — "`A1-J10 exceeds A1-B2 grid`"
— so agents don't need to decode the internal 0-based form.
## Icon classification helpers
Also exported from `'gridgram'` and safe in the browser:
```ts
import { isPathRef, collectPathRefs, stripSvgWrapper } from 'gridgram'
isPathRef('@brand/aws.svg') // true — needs external loading
isPathRef('tabler/user') // false — Tabler built-in
isPathRef('logo') // false — bare name → icon map
collectPathRefs(def) // string[] of every node.src / badge.icon
// that looks like an external path
stripSvgWrapper('') // '…' — keep the inner fragment
```
`collectPathRefs` is what `buildIconContext` uses to discover every
external file it needs to pre-read. Reach for it if you're implementing
your own async loader (e.g. resolving paths through a storage adapter
instead of `fs`).
## See also
- [Types](./types) — `DiagramDef` and its members.
- [Diagnostics](./diagnostics) — consumer-side view of the
icon-unresolved stream.
- [User Guide: CLI](../guide/cli) — same parsing from the CLI side.
---
# Quickstart (TS API)
The TypeScript API is published to npm as ESM. Install it, import what
you need, hand `renderDiagram` a `DiagramDef`, and get SVG back.
## Install
```sh
npm install gridgram
# or
bun add gridgram
pnpm add gridgram
yarn add gridgram
```
Runtime requirements:
- **ESM only.** `"type": "module"` is required in your `package.json`
(or equivalent bundler setup).
- **Node ≥ 22** for native ESM + JSON import attributes. Any version of
Bun works. Modern browsers work through any ES-module-aware bundler
(Vite, Rspack, esbuild, Rollup, webpack 5+).
- **Preact** and **preact-render-to-string** are regular dependencies,
resolved through your bundler. If your host already has a different
Preact version, you'll get two copies — the output is identical either
way (we pin nothing globally).
- **No `sharp` in the TS API.** The library renders SVG only; PNG
rasterization is done by the `gg` CLI at the edge, or by the host if
it chooses to (see [Integrations](./integrations)).
## Your first render
```ts
import { renderDiagram, tablerOutline } from 'gridgram'
import type { DiagramDef } from 'gridgram'
const def: DiagramDef = {
nodes: [
{ id: 'user', pos: 'A1', src: tablerOutline('user'), label: 'User' },
{ id: 'api', pos: 'B1', src: tablerOutline('server'), label: 'API' },
{ id: 'db', pos: 'C1', src: tablerOutline('database'),label: 'DB' },
],
connectors: [
{ from: 'user', to: 'api', label: 'HTTPS' },
{ from: 'api', to: 'db', label: 'SQL' },
],
}
const { svg, diagnostics } = renderDiagram(def)
console.log(svg) //
console.log(diagnostics) // [] for a clean layout
```
No coordinates? No icon sources? All fine — `renderDiagram` fills in
sensible defaults (auto-position across row 0, placeholder rings) and
reports anything noteworthy via `diagnostics`.
## Using the Preact component
If your host is a Preact app, skip the string round-trip and embed the
VNode tree directly:
```tsx
import { Diagram } from 'gridgram'
import type { DiagramDef } from 'gridgram'
export function Architecture({ def }: { def: DiagramDef }) {
return
}
```
`` accepts every `DiagramOptions` field as a prop (e.g.
`renderWidth`, `suppressErrors`, `theme`, `cellSize`). Internally it's
a thin wrapper around `buildDiagramTree`.
## Building a `DiagramDef`
`DiagramDef` is a plain object. See [Types](./types) for the full
field reference. The important parts:
- `nodes: NodeDef[]` — required. Each has an `id`, optional `pos`,
optional `label`, and an `src` that names its icon.
- `connectors?: ConnectorDef[]` — optional. `from` / `to` reference
node ids.
- `regions?: RegionDef[]` — background cell spans with a fill.
- `notes?: NoteDef[]` — callout boxes with optional leader lines.
- `theme?`, `cellSize?`, `columns?`, `rows?`, `padding?` — document
settings. Any of them can also come from a
[project config file](./config).
Coordinates accept three forms — A1 strings, 1-based tuples, 1-based
objects:
```ts
pos: 'A1' // column 1, row 1 (top-left)
pos: [1, 1] // same thing
pos: { col: 1, row: 1 }
```
The pipeline normalizes them all to the canonical 0-based
`{ col, row }` object before rendering. See [Types](./types) for
`GridPosInput` vs `GridPos`.
## Adding an icon
The default node has no icon — it renders as a ring with a label.
Usual forms for `src`:
```ts
import { tablerOutline, tablerFilled } from 'gridgram'
{ id: 'user', pos: 'A1', src: tablerOutline('user'), label: 'User' }
{ id: 'star', pos: 'B1', src: tablerFilled('star'), label: 'Hot' }
{ id: 'raw', pos: 'C1', src: '…', label: 'Raw' }
```
`tablerOutline(name)` / `tablerFilled(name)` return the inline SVG
fragment for any of the 5,500+ Tabler icons. Safer than a string
identifier — the TypeScript compiler won't catch a typo'd name, but
a missing icon at runtime is obvious (the node gets a red ring).
For URL / file-path icons, either pre-resolve them yourself (set `src`
to the final SVG string) or run the `.gg` icon loader (see
[Integrations](./integrations) and the `gridgram/node` subpath).
## Writing to a file
```ts
import { renderDiagram } from 'gridgram'
import { writeFileSync } from 'node:fs'
const { svg } = renderDiagram(def)
writeFileSync('out.svg', `\n${svg}`)
```
For PNG, see the [PNG rasterization](./integrations#png-rasterization)
pattern — you bring `sharp`, we give you `computeRenderDimensions` for
the canvas size.
## From a `.gg` source
When the diagram is authored as text, parse it first. `parseGg` and
`resolveDiagramIcons` are pure (no filesystem), so they work in any
ESM host:
```ts
import {
parseGg,
resolveDiagramIcons,
renderDiagram,
formatError,
} from 'gridgram'
const source = `
icon :user @A1 tabler/user "User"
icon :api @B1 tabler/server "API"
user --> api "HTTPS"
`
const { def: rawDef, errors, icons } = parseGg(source)
if (errors.length > 0) {
throw new Error(errors.map((e) => formatError(e, 'inline.gg')).join('\n'))
}
// With `inline`, the resolver handles Tabler built-ins + any inline
// SVG strings from `doc { icons: { … } }`. For URL / file-path refs,
// pre-load them via `buildIconContext` (Node only) — see below.
const { def, diagnostics: iconDiagnostics } = resolveDiagramIcons(rawDef, {
inline: icons,
})
const { svg, diagnostics: layoutDiagnostics } = renderDiagram(def)
const allDiagnostics = [...iconDiagnostics, ...layoutDiagnostics]
```
For filesystem / HTTP icon references (`./foo.svg`, `@brand/aws.svg`,
`https://…`), import the async loader from the Node subpath:
```ts
import { buildIconContext } from 'gridgram/node'
const ctx = await buildIconContext({
jsonIconsMap: icons,
def: rawDef,
docDir: '/path/to/project',
})
const { def } = resolveDiagramIcons(rawDef, ctx)
```
See [Parser](./parser) for the full pipeline and
[Diagnostics](./diagnostics) for what the `diagnostics` arrays carry.
## Next
- [`renderDiagram` & friends](./render) — options, return shapes,
`Diagram` and `buildDiagramTree` for Preact embedding.
- [Types](./types) — every field on every diagram-def type.
- [Parser](./parser) — `.gg` → `DiagramDef` in depth.
- [Diagnostics](./diagnostics) — agent-facing feedback stream.
---
# `renderDiagram` and friends
The render module is the top of the public API — three functions and
one Preact component, all taking the same `DiagramDef` shape and
sharing one internal pipeline. The difference is only what you want
back: an SVG string, a VNode tree, or an inlined JSX element.
## `renderDiagram(def, opts?)`
```ts
function renderDiagram(def: DiagramDef, opts?: DiagramOptions): RenderResult
interface RenderResult {
svg: string
diagnostics: PlacementDiagnostic[]
}
```
The default choice. Returns the rendered SVG alongside any
placement / route / icon diagnostics the pipeline produced.
Diagnostics are empty on a clean layout.
```ts
const { svg, diagnostics } = renderDiagram(def, { renderWidth: 1024 })
if (diagnostics.length > 0) {
// One record per element that couldn't be placed or routed cleanly.
// See the Diagnostics reference for the shape.
console.warn(diagnostics.map((d) => d.message).join('\n'))
}
```
## `renderDiagramSvg(def, opts?)`
```ts
function renderDiagramSvg(def: DiagramDef, opts?: DiagramOptions): string
```
Back-compat variant. Returns only the SVG string; discards any
diagnostics. Use it when the caller doesn't need feedback — for
instance inside a build step that already knows the input is clean.
Internally it's just `renderDiagram(def, opts).svg`; there's no
performance difference.
## `` — Preact component
```tsx
import { Diagram } from 'gridgram'
import type { DiagramProps } from 'gridgram'
```
A Preact functional component wrapping `buildDiagramTree`. Every
`DiagramOptions` field is accepted as a prop (e.g. `renderWidth`,
`theme`, `suppressErrors`, `cellSize`). Use this when your host
already renders Preact — no intermediate string, no double parse.
`DiagramProps` is `DiagramOptions & { def: DiagramDef }`.
## `buildDiagramTree(def, opts?)`
```ts
function buildDiagramTree(def: DiagramDef, opts?: DiagramOptions): any
```
Returns the raw Preact VNode tree for the diagram's root `