# Indy **Drop-in, universal, feature-rich artifacts for agents.** Agents publish pages to Indy and hand you a link. You read, tick, edit, or comment, then send it back. Works with any MCP agent, self-hosted in one container.

Features · Indy vs Claude artifacts · Benchmarks · Docs

Indy in use: an agent publishes a page, then charts, editing in place, a shared checklist, a comment sent to the agent, share links, a visitor commenting, a form, and the phone view

## Quick install ```bash curl -fsSLO https://raw.githubusercontent.com/liamdmcgarrigle/indy-artifacts/main/compose.yaml docker compose up -d # open http://localhost:1936, create your account, and the Connect page walks your agent through the rest ``` ## Features **Pages that are cheap for an agent to write** - Markdown with number tiles, cards, callouts, tabs, columns, timelines, and verdict badges - Charts, sortable tables, and Mermaid diagrams from short fenced blocks - React in TSX (with Tailwind and shadcn/ui), Svelte, or plain HTML, compiled and sandboxed - Your project's real components, drawn from its Storybook build - A saved theme per project, dark mode, and pages that work on a phone **Pages people use** - Checklists that show who ticked each item and when - Forms and multiple-choice questions the agent reads back - Comments pinned to the exact words or element they are about - The agent reads your comments as you write them, and Mark ready tells it you are done - Editing in place for text, number tiles, chart data, and form questions, with other people's carets shown live - The agent can type into the page while you watch **History and sharing** - Every version is kept, and you or the agent can diff any two - Pages are private until you share them, by open link or by email code - Visitors give their name before they comment or tick, and agents treat what they write as untrusted until you say otherwise - Agents can make share links too, if you turn that on **Yours to run** - One container, one port, one volume, and one SQLite file - OAuth sign-in for agents, with a token per agent that you can revoke on its own - A library grouped by project and branch, with search and a command palette - Free software under the AGPL ## Indy vs Claude artifacts Claude Code and claude.ai publish artifacts of their own. Indy is built for handing a page to a person and getting their answer back, from any agent. Claude's artifacts do more as standalone apps inside Anthropic's platform, where a page can call Claude and your connectors. **3× faster, 54% cheaper, 64% fewer tokens, and 100% less lock-in.** ([benchmarks](#benchmarks)) | | Indy | Claude artifacts | |---|---|---| | **Agents** | | | | Works with Claude Code | ✅ | ✅ | | Works with Codex and other MCP clients | ✅ | ❌ | | Pages written as markdown blocks | ✅ | ❌ HTML only | | Agent reads comments back | ✅ | ✅ | | Agent waits for the next comment, tick, or answer | ✅ `artifact_wait` | ✅ watch, while the session is open | | Agent sees what changed between versions | ✅ `artifact_diff` | ❌ | | Agent types into a page while you watch | ✅ | ❌ | | **Page content** | | | | Charts, tables, callouts, and number tiles without writing code | ✅ | ❌ hand-written in HTML | | Mermaid diagrams | ✅ | ✅ | | Native React components in TSX, compiled from a file map | ✅ | ❌ one HTML file, React from a CDN | | Native Svelte components | ✅ | ❌ | | Storybook integration: your project's real components on the page | ✅ | ❌ | | Saved themes per project | ✅ | ⚠️ design systems, for decks and designs | | Slides, docs, and design canvases as ready-made types | ❌ | ✅ | | **People on the page** | | | | Checklists that save ticks | ✅ built in, with names | ⚠️ the agent has to code it (`artifact` or `db` capability) | | Forms the agent reads back | ✅ built in | ⚠️ the agent has to code it (`db` capability) | | Comments pinned to text or elements | ✅ | ✅ | | Edit the page in place | ✅ | ❌ | | Live co-editing with others' carets | ✅ | ⚠️ via the `room` capability, if the agent builds it | | Page calls Claude or your connectors | ❌ | ✅ `sample` and `mcp` capabilities | | Version history and compare | ✅ | ⚠️ history, no compare | | **Sharing** | | | | Private by default | ✅ | ✅ | | Share link open to anyone | ✅ | ✅ | | Share link gated by an email code | ✅ | ❌ | | Visitors comment without an account | ✅ with a name | ❌ | | Agent makes share links, with limits you set | ✅ opt-in | ❌ | | **Running it** | | | | Where it runs | your server | Anthropic | | Works across agents and accounts | ✅ | ❌ one Claude account | | Your data stays on your machine | ✅ | ❌ | | Source available | ✅ AGPL | ❌ | ## Benchmarks Claude Code made the same pages with Indy and with Claude's own Artifact tool, three times each (medians, Claude Opus 5.5, Indy first): | page | output tokens | cost | time | |---|---:|---:|---:| | Report with number tiles, a chart, a table, and a callout | 2,175 vs 7,595 | $0.30 vs $0.51 | 20 s vs 66 s | | A revision to that report | 3,921 vs 9,285 | $0.41 vs $0.60 | 15 s vs 18 s | | Checklist and form, saved and read back | 1,483 vs 6,243 | $0.11 vs $0.62 | 16 s vs 59 s | | Large page: nine charts, tables, a timeline, and a diagram | 13,361 vs 36,528 | $0.72 vs $1.42 | 94 s vs 292 s | | The same large page with a form at the end | 13,847 vs 38,324 | $0.74 vs $1.78 | 97 s vs 322 s | The prompts, the runner, and every run's numbers are in [`docs/benchmarks.md`](docs/benchmarks.md). ### Why it's lighter With Claude's Artifact tool, the agent writes every page from scratch: the HTML, the CSS for light and dark, and the chart code. With Indy it writes short markdown blocks and Indy draws the components. This is the whole source of a page, and the page Indy draws from it:

Thirty lines of markdown (a frontmatter title, a kpis block with four numbers, a stacked bar chart fence with a week of data, and a warning callout) beside the page Indy draws from it: four number tiles, a stacked bar chart, and a warning callout

For the report in the benchmark, the agent wrote 2,165 characters of markdown with Indy and 11,660 characters of HTML with the Claude Artifact tool. ## Docs ### Install You need Docker (or Podman) with Compose, and a machine that stays on. ```bash mkdir indy && cd indy curl -fsSLO https://raw.githubusercontent.com/liamdmcgarrigle/indy-artifacts/main/compose.yaml curl -fsSL https://raw.githubusercontent.com/liamdmcgarrigle/indy-artifacts/main/.env.example -o .env ``` Set `INDY_URL` in `.env` to the address you will open Indy at, such as `http://192.168.1.20:1936` on a home network or `https://indy.example.com` on a server. Then run `docker compose up -d`. Open `INDY_URL` and create your account. Until you do, anyone who can reach the address can create it, so do it right after the first start. If someone beats you to it, start over: ```bash docker compose down docker volume rm indy_indy-data docker compose up -d ``` This keeps Caddy's certificates, so a reinstall doesn't count against Let's Encrypt's limits. ### Connect an agent Setup ends on the Connect page (`/connect`), which walks you through this with your address filled in and shows when the agent reaches Indy. Agents connect over MCP at `/mcp` and sign in with OAuth in the browser, so nothing needs copying. For Claude Code: ```bash claude mcp add --transport http --scope user indy https://indy.example.com/mcp # then in Claude Code: /mcp, pick indy, Authenticate ``` For Codex, which starts the sign-in by itself: ```bash codex mcp add indy --url https://indy.example.com/mcp ``` Claude Code only signs in this way over HTTPS. On plain HTTP, or on a machine with no browser, make a token on the Connect page and pass it as a header. Settings › Agents lists every connected agent and disconnects any of them. The optional plugin adds four skills (`publish` for making pages, `charts` for picking the right chart, `storybook` for showing a project's components, and `feedback` for picking up your comments) and tells the agent Indy is there at the start of each session: ```bash claude plugin marketplace add liamdmcgarrigle/indy-artifacts && claude plugin install indy@indy codex plugin marketplace add liamdmcgarrigle/indy-artifacts && codex plugin add indy@indy ``` Any other MCP client can use `/mcp` over streamable HTTP, with OAuth or an `Authorization: Bearer` token. claude.ai and Claude Desktop can add it as a custom connector when Indy is on a public HTTPS address. The server serves its block reference as `indy://reference`, so an agent can learn the format without the plugin. ### HTTPS Anything reachable from the internet should be on HTTPS, since sign-in cookies are only marked Secure when `INDY_URL` starts with `https://`. On a tailnet or a home network, plain HTTP is fine. To use the bundled Caddy, point a domain at the server, open ports 80 and 443, add this to `.env`, and start with `docker compose --profile https up -d`. Caddy gets and renews the certificate. ```bash INDY_URL=https://indy.example.com INDY_DOMAIN=indy.example.com INDY_BIND=127.0.0.1 INDY_PROXY_HOPS=1 ``` With your own proxy, forward everything to port 1936 and set `INDY_PROXY_HOPS=1`. The proxy has to pass websockets on `/collab` and must not buffer `/api/live`. Traefik does both by default. For nginx: ```nginx location / { proxy_pass http://127.0.0.1:1936; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_buffering off; proxy_read_timeout 1h; } ``` ### Settings Everything is an environment variable in `.env`. Only `INDY_URL` is required. | Variable | Default | What it does | |---|---|---| | `INDY_URL` | `http://localhost:1936` | The address people open Indy at. Share links, agent snippets, and cookies are built from it. | | `INDY_PORT` | `1936` | The host port. | | `INDY_BIND` | `0.0.0.0` | The host address the port is published on. Use `127.0.0.1` behind a proxy. | | `INDY_DOMAIN` | | The domain Caddy gets a certificate for, with `--profile https`. | | `RESEND_API_KEY` | | Turns on email through [Resend](https://resend.com). See below. | | `INDY_EMAIL_FROM` | `Indy ` | The sender for emails. | | `INDY_AUTH` | `password` | `local` skips sign-in and treats every request as you. Only for an install nobody else can reach. | | `INDY_ASSET_ROOTS` | | Colon-separated folders a page may load images from by absolute path. Each must also be mounted into the container. | | `INDY_PROXY_HOPS` | `0` | How many reverse proxies sit in front of Indy. Set `1` behind one (the bundled Caddy included), so sign-in limits count each visitor separately. | | `INDY_IMAGE` | `ghcr.io/liamdmcgarrigle/indy-artifacts:latest` | The image to run. Pin a release such as `:1.2` to update on your own schedule. | Any of these can be read from a file, the way Docker and Swarm mount secrets: set `RESEND_API_KEY_FILE=/run/secrets/resend_api_key` instead of `RESEND_API_KEY`. The plain variable wins when both are set. Older `ARTIFACTS_*` names still work when their `INDY_*` name is unset. For anything compose doesn't expose, such as mounting a folder for `INDY_ASSET_ROOTS`, use a `compose.override.yaml` next to `compose.yaml`. `compose.override.example.yaml` shows the shape. ### Email Email is optional. It turns on two things: a sign-in code after your password, and share links that ask visitors to confirm their email. Set `RESEND_API_KEY` for both. Resend's shared test sender only delivers to your own Resend address, which covers sign-in codes. To email visitors, verify a domain with Resend and set `INDY_EMAIL_FROM` to an address on it. ### Storage and images Indy caps its disk use at 5 GB by default. At the cap, agents can't publish or update pages and are told why, while reading, comments, and sharing keep working. Settings › Data raises or removes the cap and shows what is using the space. Attached images are scaled to fit 2560 px, re-encoded at quality 80 in their own format, and stripped of EXIF data. GIFs and SVGs are stored as they are. Settings › Data changes this or turns it off, for images attached afterwards. ### Updating and backups ```bash docker compose pull docker compose up -d ``` Updates leave the `indy-data` volume alone and apply database changes on start. That volume holds everything: one SQLite database plus uploaded assets. To back it up, stop Indy for a moment so the database is consistent: ```bash docker compose stop indy docker run --rm -v indy_indy-data:/data:ro -v "$PWD":/backup alpine \ tar czf /backup/indy-$(date +%F).tgz -C /data . docker compose start indy ``` To restore into a fresh install, before its first start: ```bash docker volume create indy_indy-data docker run --rm -v indy_indy-data:/data -v "$PWD":/backup alpine \ sh -c "tar xzf /backup/indy-2026-09-26.tgz -C /data && chown -R 1000:1000 /data" docker compose up -d ``` ### Building the image yourself ```bash git clone https://github.com/liamdmcgarrigle/indy-artifacts cd indy-artifacts docker build -t indy:local . ``` Then set `INDY_IMAGE=indy:local` in `.env`. Published images are built for `linux/amd64` and `linux/arm64`. `:latest` follows `main`, and each version also gets its own tags. ### Pages A page has a stable address and a list of versions that never change once written. There are four kinds: | kind | the agent sends | shown as | |---|---|---| | `markdown` | one markdown document with frontmatter | the page itself, with HTML and Mermaid blocks in sandboxed frames | | `react` | a map of files with `App.tsx` | compiled with esbuild, run in a sandboxed frame | | `svelte` | a map of files with `App.svelte` | the same, through the Svelte 5 compiler | | `html` | one complete HTML document | a sandboxed frame | Markdown is the cheap path; [Why it's lighter](#why-its-lighter) shows a whole page. The full set is `:::card`, `:::callout`, `:::kpis`, `:::columns` with `:::col`, `:::tabs` with `:::tab`, `:::details`, the form blocks `::field` and `:::choice` with `:::option`, and the fences `chart`, `table`, `mermaid`, `html`, and `story`. The reference is `plugin/skills/publish/reference.md`, the same text MCP serves as `indy://reference`. ### Themes A theme is twenty settings: ten colors, four fonts, text size, line height, corner radius, shadow, spacing, and reading width. Indy derives the rest, including hover colors, chart colors, and the dark scheme, and you can override any dark color by hand. Pages use their project's theme, and projects without one use the default. Settings › Themes edits themes with a light and dark preview, and has three built-ins (Paper, Graphite, and Sand) to use or copy. Agents can create themes and assign them to projects with `artifact_theme_set`, which the publish skill uses to match a project's look. Only you can delete a theme or pick the default. Indy serves its own fonts, so pages make no requests to a font CDN. ### Storybooks If a project has a Storybook, a page can show its stories drawn by the real, compiled components: screens at device size, a component with different props, or a form asking which of two designs to ship. The agent builds the Storybook and uploads it over HTTP: `artifact_storybook_upload` gives it a single-use address to send the build to with `tar` and `curl`. A page then names stories with a `story` fence: ````markdown ```story id: screens-friends--requests width: 402 height: 874 ``` ```` Each page version keeps the build it was published with, so a design review keeps showing what was reviewed. Files are stored once by content, so a new build costs only what changed. Indy keeps the three newest builds of each Storybook plus any build a page still shows. Settings › Storybooks lists and deletes them. A Storybook can map its globals to Indy's light and dark schemes, and list the hosts its stories may load images, fonts, and styles from. For CI, `POST /api/storybooks//builds` with an agent token and the gzipped tar does the same as the upload tool. ### Security There is one account. Each agent gets its own token, revocable on its own. Anything an agent wrote that can run goes in an `