# Open Artifacts ![](https://img.shields.io/badge/Cloudflare-Workers-F38020?logo=cloudflare&logoColor=white) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Node](https://img.shields.io/badge/node-%3E%3D22-green)](https://nodejs.org) **English** | [简体中文](README.zh-CN.md) Open-source, self-hosted [Claude Code Artifacts](https://code.claude.com/docs/en/artifacts): let any coding agent publish self-contained HTML/Markdown pages to shareable URLs, protect them with passwords (zero-knowledge, client-side encryption), and keep them updated as the project they describe evolves. Runs entirely on Cloudflare (Workers + D1 + R2), fits in the free tier, no accounts anywhere. > **Hosted or self-hosted.** [coda0.com](https://coda0.com) is the official > managed instance, run by the project — point your agent at it for zero-setup > publishing. Or self-host the engine on your own Cloudflare account (see > below); it's the same MIT-licensed code either way. ```mermaid flowchart LR you["you"] -- "share the app's interaction flows as a page" --> agent["your agent"] agent -- "POST /api/artifacts" --> worker["your Worker"] worker -- "https:///a/3fKx9mQp2Wvb" --> url["shareable URL"] later["later: the flows change"] --> status["agent runs
artifact.mjs status"] status -- "stale" --> regen["regenerates page"] regen -- "PUT (same id)" --> worker2["your Worker"] worker2 -- "same URL, v2" --> url ``` ## Give your agent the skill ```sh npx skills add coda0HQ/open-artifacts -s using-open-artifacts # project scope (.claude/skills/) npx skills add coda0HQ/open-artifacts -s using-open-artifacts -g # or user scope ``` Works with Claude Code and any agent supporting the [Agent Skills](https://agentskills.io) standard. Then point it at an instance — the hosted one, or your own: ```sh export OPEN_ARTIFACTS_URL=https://coda0.com # hosted; or your self-hosted URL ``` No instance yet? `references/deployment.md` (bundled with the skill) lists three ways to get one: use the public shared instance with zero setup, self-host on your own Cloudflare account, or share a team instance, with a trust-model table for picking based on content sensitivity. The bundled `SKILL.md` and `references/design.md` teach the agent the design philosophy: an expert-designer workflow (understand, explore, plan, build, verify), an explicit anti-AI-slop list, modern CSS power moves, and a 5-direction library (Editorial / Modern minimal / Human / Tech utility / Brutalist) with ready-to-paste OKLch palettes and font stacks for when no brand is specified. `references/tokens.css` is the shared token contract the Recipe builder injects into every HTML artifact before its theme fragment. The skill also provides a quality profile for responsive HTML builds and an optional Reference DNA workflow that records approved design facts as a static Recipe input without bringing source assets or network requests into the Artifact. The quality profile rejects recurring HTML defects such as fake browser chrome, unsafe sticky stacks, overflowing display text, and unconstrained media grids. Adapted from [open-design](https://github.com/nexu-io/open-design), Claude's `artifact-design` skill, [impeccable](https://github.com/pbakaus/impeccable) by Paul Bakaus (Apache-2.0, interaction-state and anti-pattern rules), Emil Kowalski (easing, frequency, and duration rules), and Apple WWDC 2018 *Designing Fluid Interfaces* (canvas gesture physics). Retargeted to this project's strict no-external-requests CSP. Ask your agent to "publish this as an artifact" — it runs the bundled CLI: ```sh node skills/using-open-artifacts/scripts/artifact.mjs validate \ .artifacts/recipes/app-interactions.recipe.json node skills/using-open-artifacts/scripts/artifact.mjs smoke \ .artifacts/recipes/app-interactions.recipe.json node skills/using-open-artifacts/scripts/artifact.mjs create \ .artifacts/recipes/app-interactions.recipe.json ``` Every artifact is generated from a versioned JSON Recipe plus ordered fragments. The Recipe owns title, favicon, format, scope, watch globs, channel, level, Canvas mode, locality, and encryption policy. `create` and `update` compose and validate in memory, then send exactly one final publish request. For a responsive scrolling HTML artifact, `smoke` renders the composed output at 320, 375, 414, and 768px with `agent-browser` before publishing; it detects horizontal scrolling and heading overflow without writing project state. ## Deploy your own instance ```sh git clone https://github.com/coda0HQ/open-artifacts && cd open-artifacts pnpm install npx wrangler d1 create open-artifacts # put database_id into wrangler.jsonc npx wrangler r2 bucket create open-artifacts-content pnpm run deploy ``` The schema applies itself on first request — no migration step. To restrict who can create artifacts on your instance (updates are always restricted by per-artifact write tokens): ```sh npx wrangler secret put CREATE_TOKEN # then set OPEN_ARTIFACTS_TOKEN client-side ``` Local development: `pnpm dev` (state persists in `.wrangler/state`). ## How it works | Concern | Design | | --- | --- | | Identity | No accounts. Artifact ids are 12-char crypto-random (unguessable, unlisted). Creation returns a one-time `writeToken`; only its SHA-256 is stored. | | Deterministic sources | A strict Recipe plus ordered fragments generates every Artifact. The builder injects tokens and, for Canvas, the vendored runtime and controls. Manifest v2 records Recipe/input/output hashes; direct HTML/Markdown CLI publishing is rejected. | | Quality checks | HTML builds reject selected structural defects: non-token visual values, fake device/browser chrome, unsafe sticky stacks, display-text overflow risks, wrapping primary actions, and unconstrained media grids. `smoke` renders scrolling HTML at 320, 375, 414, and 768px to catch horizontal scroll and heading overflow. Canvas, Markdown, and React keep their dedicated rendering contracts. | | Reference DNA | An approved `document.referenceDna` sidecar stores inert design facts and provenance after explicit user attestation. It contributes to Recipe input hashes and stale detection but is never injected into the published page. Shared sidecars live in `.artifacts/reference-dna/`; local or encrypted sidecars live in `.artifacts/reference-dna.local/`. | | Channels | `artifact.channel` binds an artifact to a stable URL. The CLI keeps a per-channel token (`ch_`) in `.artifacts/credentials.json`; presenting it on a later `create` updates the bound artifact (new version, same link) instead of minting a new one. Only the channel hash is stored server-side. | | Local mode | `artifact.local: true` places private sources under gitignored `.artifacts/recipes.local/` and `.artifacts/fragments.local/`, with state in `manifest.local.json`. Shared Recipes/fragments live under `.artifacts/recipes/` and `.artifacts/fragments/` and may be committed. Encrypted Recipes are always private. | | Storage | D1 for metadata/tokens/version index, R2 for content bodies (`content//`). Both strongly consistent — updates are visible immediately. | | Versions | Every publish is an immutable version with an optional label and its own title, description, favicon, format, and encryption state, so history reflects what each version actually looked like. `?v=N` views history; `PUT` accepts `baseVersion` and returns 409 on conflicts (override with `force`). | | Serving | The Worker wraps stored content in a skeleton (CSS reset, emoji favicon, viewport, light/dark theme with a `data-theme` toggle) and serves it with `Content-Security-Policy: sandbox allow-scripts ...; default-src 'none'` — artifact scripts run in an opaque origin and cannot make any external request. | | Link previews | Every page emits OpenGraph + Twitter tags (title, description, image). `GET /og/:id` returns a 1200x630 PNG card rasterized on the edge with `@resvg/resvg-wasm` from an embedded Inter subset — a real raster crawlers render (they ignore SVG), self-contained with no external requests. | | Passwords | The CLI encrypts locally: PBKDF2-HMAC-SHA256 (600k iterations) + AES-256-GCM. The server stores only `{salt, iv, ciphertext}`. The viewer serves an unlock shell that decrypts in the browser and renders the result inside a sandboxed iframe. The password never leaves the client. | | Auto-update | The Recipe records `scope`, `watch`, and `autoUpdate`; Manifest v2 keeps the publication snapshot. `artifact.mjs status` reports stale watched artifacts, while the optional Stop-hook path only surfaces opted-in entries. Agents update Recipe fragments or `ack` reviewed drift. | | Markdown | Rendered client-side (vendored `marked`, inlined — no CDN), so encrypted Markdown works without the server ever seeing plaintext. | ## API ``` POST /api/artifacts { content, favicon, title?, description?, format?, label?, encrypted?, channel? } → 201 { id, url, writeToken, version, channel? } PUT /api/artifacts/:id same fields + baseVersion?/force? (Bearer writeToken or channel token) GET /api/artifacts/:id metadata + version history GET /api/artifacts/:id/raw stored content (?v=N) DELETE /api/artifacts/:id (Bearer writeToken) GET /a/:id rendered page (?v=N) ``` `encrypted` is `{ salt, iv, iterations }` (all base64/int) with base64 ciphertext as `content`. `channel` is a channel token (`ch_...`) that targets the artifact already bound to that channel, or binds a new one on first use. Max content size 4 MiB. ## Security model - Serving untrusted HTML on your own origin is the classic stored-XSS trap; every user-content response here carries the CSP `sandbox` directive (opaque origin — no cookies, no storage, no same-origin API calls) plus `default-src 'none'`, `connect-src 'none'`, `X-Content-Type-Options: nosniff` and `Referrer-Policy: no-referrer`. - `*.workers.dev` is on the Public Suffix List, isolating your instance from other sites. - Anyone with the URL of an unprotected artifact can read it (like an unlisted gist). Use `--password` for anything sensitive; title/favicon metadata stays plaintext. - An open instance (no `CREATE_TOKEN`) lets anyone with the URL create pages. Set the secret for anything public-facing. ## Development ```sh pnpm test # Worker integration tests (vitest + workerd) pnpm test:cli # skill CLI tests pnpm typecheck pnpm check # biome lint + format ``` Install [`agent-browser`](https://github.com/vercel-labs/agent-browser) to run `artifact.mjs smoke`; unit tests use a stub and do not require its browser binary. BDD scenarios live in `tests/features/`; the architecture decision record in `docs/architecture.md`. MIT licensed.