Skip to content

Repository files navigation

Open Artifacts

GitHub Release GitHub Stars npm version npm downloads skills.sh Website License: MIT Node.js Docker MCP Server Constructor Mode: 23 Blocks S3-compatible storage

English | Русский | Landing Page | Changelog | История изменений

Self-hosted artifact hosting for AI agents — an open alternative to Claude Artifacts. Any AI agent (Claude, Cursor, Codex, Windsurf, Antigravity, OpenCode, or custom LLM pipelines) authenticates with a token or OAuth device flow, publishes HTML, Markdown, Mermaid diagrams, or SVG content, and gets back a shareable link that humans can open and interact with in their browser.

Runs as a lightweight, single-stack Docker Compose deployment.

🌐 Landing Page: https://emaxe.github.io/open-artifacts


Topics & Tags

ai-agentsartifactsclaude-artifactsclaudemcpmcp-servermodel-context-protocolself-hosteddockerhonoreacttypescriptclideveloper-toolsopen-artifactsllms3constructoryamlblock-builder


Key Features

  • 🛡️ Zero-Trust Security Sandbox: Untrusted agent-generated artifacts render inside strictly isolated <iframe> containers with sandboxed origins (allow-scripts, no credential or cookie leaks, strict Content Security Policy).
  • 🧩 Constructor Mode (Declarative Artifact Builder): Fast, token-efficient assembly of HTML deliverables. Agents describe pages as clean YAML/JSON specifications or reference existing Markdown reports using 23 modular blocks. Compiles in milliseconds, cuts token generation costs by ~80%, and eliminates CSS/HTML hallucinations.
  • 📦 Multi-Format Support: Interactive HTML applications, GitHub-flavored Markdown, responsive Mermaid diagrams, and raw vector SVG graphics.
  • Built-in MCP Server: Ready-to-use Model Context Protocol endpoint at /mcp (Streamable HTTP) for immediate integration with Cursor, Claude Desktop, and Claude Code.
  • 🤖 Standard AI Agent Skill: First-class support for skills.sh (npx skills add emaxe/open-artifacts) with automatic CLI detection and device-flow authorization.
  • 🎨 Built-in Design Systems: Four ready-made design templates (dashboards, documents, promo/decks, diagrams) ship with the skill so an agent picks the right one before publishing, keeping artifacts visually consistent.
  • 🔑 Flexible Authentication: Interactive OAuth Device Flow (oa login) and non-interactive organization API tokens (OA_TOKEN).
  • 🖥️ Bilingual Sign-In Screen: / greets a signed-out visitor with a two-panel sign-in/sign-up screen (English/Russian, toggle in the header) — no marketing copy to scroll past on your own instance. Signed-in visitors are redirected straight to their own workspace. The product's marketing site lives separately at emaxe.github.io/open-artifacts (source: apps/landing/), published to GitHub Pages and carrying no auth of its own.
  • 👥 Multi-Tenancy & Teams: Organizations, user management, and fine-grained roles (superadmin, admin, member) with an intuitive Team Switcher.
  • 🔗 Secure Sharing: Team-only links, password-protected links, public links (optionally labeled for your own reference), and automatic link expiration (1 hour, 1 day, 7 days, 30 days) — teams and instance admins control the default link mode and can disable public links entirely. Every share page shows a branded viewer panel: one compact row with the title, type, version and date. Anyone who can manage the artifact also gets a version picker, a collapsible details block (size, views, expiry, description) and a visibility control — pick Team only, Password protected or Public and it applies instantly, in place, on the same link: no page reload, no revoking it and handing out a new URL.
  • 🖼️ File Attachments: Images and other files can be uploaded to an S3-compatible bucket (MinIO ships in docker-compose.yml by default) and attached to an artifact — proxied through the app, never a public bucket. An agent can check its remaining quota (oa quota / the get_storage_quota MCP tool) before deciding whether to upload. Instance-wide and per-team storage quotas (team-wide and per-artifact, unlimited by default) keep usage in check; files are deleted automatically with their artifact.
  • 🚀 One-Command Deployment: Instant production setup with Docker Compose or the interactive ./run.sh runner.

Quick Start

Option 1: Interactive Runner (Recommended)

./run.sh

An interactive menu to orchestrate Docker production containers, local development, builds, tests, and database migrations without memorizing individual commands.

Option 2: Docker Compose

cp .env.example .env        # Configure SESSION_SECRET, SUPERADMIN_EMAIL/PASSWORD
docker compose up -d

Open http://localhost:3000, log in using the superadmin credentials configured in .env, and you're ready! Database migrations run automatically on container startup.


Project Layout

apps/api/               Hono REST API + MCP server + Postgres (Drizzle) + Static web server
apps/web/               React SPA (Vite) — sign-in screen + Admin & User interface
apps/landing/           Static marketing site (Vite, no backend) — published to GitHub Pages
packages/shared/       Zod schemas + shared validation rules (access control, CSP, TTL parsing)
packages/cli/          `oa` CLI for AI agents (published on npm as @emaxe/oa)
skills/open-artifacts/ Agent skill (SKILL.md) + design templates (references/design/) compatible with skills.sh
docker/                Multi-stage Dockerfile for containerized deployment
run.sh                 Interactive terminal management script

Local Development

Requirements: Node.js 20+, pnpm, and Docker (for local PostgreSQL).

# 1. Install dependencies
pnpm install

# 2. Start PostgreSQL dev database on localhost:5433
docker compose -f docker-compose.dev.yml up -d

# 3. Synchronize database schema
cd apps/api && DATABASE_URL=postgres://postgres:postgres@localhost:5433/open_artifacts_dev \
  pnpm exec drizzle-kit push

# 4. Start development servers
pnpm dev:api      # API & MCP server at http://localhost:3000
pnpm dev:web      # React UI at http://localhost:5173 (proxies /api to :3000)
pnpm dev:landing  # Marketing site at http://localhost:5174 (apps/landing, no backend needed)

Develop and test the CLI locally:

cd packages/cli && pnpm dev -- login --server http://localhost:3000

Tests

pnpm -r run test                                             # Run all unit tests
cd apps/api && pnpm run test:integration                     # Integration tests (requires dev DB)
cd apps/api && pnpm exec playwright install chromium          # Install Playwright browser once
cd apps/api && pnpm run test:e2e                              # End-to-end security sandbox tests

Security Verification: The e2e test suite (apps/api/e2e/sandbox-security.spec.ts) verifies sandbox isolation by injecting malicious payloads and confirming that artifacts cannot access host cookies, localStorage, or backend APIs.


AI Agent Skill (for Claude, Cursor, Windsurf, etc.)

Open Artifacts ships with an agent skill compliant with the skills.sh standard. It instructs AI assistants how to automatically install @emaxe/oa, authenticate, publish generated artifacts, and present clean preview URLs in chat. It also ships four ready-made design systems — dashboards, reports, promo/decks, and diagrams — plus a shared foundation, which the agent picks between before writing an html artifact, so published artifacts look consistent regardless of which agent or session produced them.

1. Install via skills.sh

# In the current project workspace (recommended):
npx skills add emaxe/open-artifacts

# Or install globally:
npx skills add emaxe/open-artifacts -g

2. Agent Authentication

Agents can authenticate using either of two methods:

Method A: Personal access (recommended)

  1. Run in terminal or agent session:
    oa login --server http://localhost:3000
  2. The CLI outputs a verification code (e.g. ABCD-1234) and activation URL.
  3. Open http://localhost:3000/activate?code=ABCD-1234 in the browser and approve — no organization to pick here.
  4. This issues a personal key, valid across every team the approver belongs to (with their real role in each), saved to ~/.config/open-artifacts/credentials.json (600 permissions). Pick a team for a given project with:
    oa orgs           # list your teams
    oa use <team>     # set the default for this project (writes .oa.json, no secrets, safe to commit)

Method B: Agent key locked to one team (for CI)

Run oa login --agent (organization selection required at approval time), or create an API key in the web UI under Team > Agents (/t/:orgId/agents) and pass it to the agent's environment:

export OA_SERVER="http://localhost:3000"
export OA_TOKEN="oa_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Verify connection:

oa whoami

3. CLI Command Cheat Sheet

Task Command
Check token, key type, and selected team oa whoami
List teams your key can act in oa orgs
Set the default team for this project oa use <team-slug>
Publish artifact and generate a link in the team's default mode oa push report.html --title "Q3 Summary" --share
Update an existing artifact (create new version) oa push report.html --id <artifact-id> --message "Updated metrics"
Create a team-only link (only logged-in team members can open it) oa share <artifact-id> --team
Create expiring link with password protection oa share <artifact-id> --password "secret123" --expires 7d
Create a fully public link (may be disabled by team/instance policy) oa share <artifact-id> --public
List all artifacts in the active team oa list
Download artifact source oa get <artifact-id> -o output.html
Remove an artifact oa rm <artifact-id>
Revoke a shared link oa unshare <share-id>

Supported file formats: html, markdown, mermaid, svg. Append --json to any command for structured JSON output.


🧩 Constructor Mode: Declarative Artifact Builder

Writing raw HTML/CSS from an AI agent has significant drawbacks:

  • Massive token consumption: 2,000–5,000+ output tokens per artifact, consuming context windows and driving up costs.
  • High latency: Streaming thousands of repetitive HTML tags takes 30–60+ seconds.
  • Fragile styling & layout hallucinations: LLMs frequently introduce syntax errors, broken CSS, missing responsive styles, broken dark mode, or blocked CDN imports that fail the strict sandbox CSP.

Constructor Mode solves this completely. Rather than generating raw HTML, the agent emits a concise, structured YAML or JSON specification. A lightweight builder (skills/open-artifacts/constructor/build.mjs) then compiles it in milliseconds into a standalone, accessible, dark-mode-ready HTML artifact.

Key Benefits

Benefit Raw HTML Constructor Mode
Token Usage 2,000 – 5,000+ tokens ~150 – 400 tokens (~80% reduction)
Generation Speed 30 – 60+ seconds 1 – 3 seconds (instant)
Styling & Theming Hand-crafted, often inconsistent Engineered design tokens with 5 instant themes
Dark / Light Mode Often missing or broken Automatic via CSS custom properties & Chart.js theme binding
Sandbox CSP Often blocked (external fonts, unapproved CDNs) 100% compliant with Open Artifacts iframe CSP
Interactive Components Requires custom JS boilerplate Pre-tested: sortable tables, tabs, zoomable images, Chart.js, Mermaid

How It Works

┌───────────────────────────┐      ┌───────────────────────────────┐      ┌─────────────────────────┐
│  AI Agent                 │      │  Constructor (build.mjs)      │      │  Open Artifacts Server  │
│                           │      │                               │      │                         │
│  Emits YAML/JSON spec     │ ───► │  • Validates blocks           │ ───► │  Self-contained HTML    │
│  or points to .md report  │      │  • Inlines CSS themes         │      │  Sandboxed iframe       │
│  (~80% fewer tokens)      │      │  • Dedupes Chart/Mermaid CDNs │      │  Shareable URL link     │
└───────────────────────────┘      └───────────────────────────────┘      └─────────────────────────┘

1. Compile & Publish in One Command

# Build and publish with an auto-generated share link:
node skills/open-artifacts/constructor/build.mjs report.yaml --share

2. Declarative Specification Example (report.yaml)

title: "Quarterly Analytics"
theme: data # default | data | document | promo | diagram
lang: en

blocks:
  - type: hero
    title: "Q3 Performance Dashboard"
    subtitle: "Enterprise telemetry & ARR"
    badge: "Verified"

  - type: kpi-row
    items:
      - label: "Total Revenue"
        value: "$4.25M"
        delta: "+18.4%"
        trend: up
        icon: "💰"
      - label: "Active Clients"
        value: "1,240"
        delta: "+5.1%"
        trend: up
        icon: "👥"
      - label: "Monthly Churn"
        value: "1.18%"
        delta: "-0.2%"
        trend: down
        icon: "📉"

  - type: two-columns
    ratio: "1:1"
    left:
      - type: chart-bar
        title: "Monthly ARR ($k)"
        labels: ["Jul", "Aug", "Sep"]
        datasets:
          - label: "Actual"
            data: [740, 780, 840]
            color: c1
    right:
      - type: chart-pie
        title: "Revenue by Tier"
        donut: true
        labels: ["Enterprise", "Mid-Market", "SMB"]
        data: [55, 30, 15]

  - type: tabs
    items:
      - label: "Accounts"
        blocks:
          - type: table
            caption: "Top Accounts by ARR"
            sortable: true
            columns: ["Client", "Tier", "ARR", "Status"]
            rows:
              - ["Acme Global", "Enterprise", "$420k", "Active"]
              - ["Globex Tech", "Enterprise", "$380k", "Active"]
      - label: "Architecture"
        blocks:
          - type: mermaid-diagram
            caption: "Processing Pipeline"
            definition: |
              graph LR
                A[Agent] --> B[Constructor]
                B --> C[Open Artifacts]

  - type: image
    src: "./assets/diagram.png" # local images auto-inline to base64
    alt: "System Architecture"
    caption: "Click to zoom"
    width: "400px"
    zoomable: true # Fullscreen lightbox on click

  - type: raw # Escape hatch: arbitrary HTML/CSS/JS with access to theme tokens
    html: |
      <div style="background: var(--surface-2); padding: 12px; border-radius: var(--radius);">
        Custom component styled with active theme tokens
      </div>

3 Ways to Publish Markdown Reports

Already have a finished Markdown document (research note, RFC, post-mortem, or audit)? You don't need to rebuild it as blocks:

  1. Option A — Passthrough Mode (Lowest token cost): Point directly to the markdown file. The builder parses it, applies the document theme, and outputs a clean standalone artifact. Frontmatter (---...---) is stripped automatically:

    title: "Q3 Research Note"
    theme: document
    source: /path/to/report.md
  2. Option B — markdown-file block (Framed report): Combine an existing .md file with top-level KPI metrics or a hero banner:

    title: "Executive Summary"
    theme: document
    blocks:
      - type: hero
        title: "Q3 Executive Summary"
      - type: kpi-row
        items: [...]
      - type: divider
        label: "Full Report"
      - type: markdown-file
        path: /path/to/report.md
  3. Option C — markdown block (Inline): Embed Markdown prose directly inside the YAML specification:

    blocks:
      - type: markdown
        content: |
          ## Executive Summary
          Revenue grew **18% QoQ** driven by expansion in the enterprise segment.

23 Built-in Modular Blocks

Block Description
hero Header banner with title, subtitle, pill badge, and metadata
kpi-row Responsive grid of KPI metric tiles with deltas, trends (up/down/neutral), and icons
stats-grid Large-number highlight cards with custom theme accent colors
table Responsive, striped data table with instant client-side column sorting
chart-bar Bar chart (Chart.js) supporting vertical, horizontal, stacked configurations
chart-line Smooth line chart (Chart.js) with optional area fill and tension control
chart-pie Pie or donut chart (Chart.js) with legend and auto-palette binding
mermaid-diagram Flowcharts, sequence diagrams, and ERDs rendered with Mermaid.js
tabs Accessible tabbed panels with auto-redrawing of hidden charts and diagrams
two-columns Multi-column layout with configurable ratio (1:1, 2:1, 3:2) and nested blocks
timeline Vertical chronological milestones with visual status indicators (done, active, pending)
progress-bars Multi-item progress bars with automatic percentage calculation
list-cards Responsive grid of cards for features, links, and content blocks
image Responsive image with auto-base64 inlining, captions, and fullscreen lightbox (zoomable: true)
code-block Syntax-highlighted code snippets with dark-mode compatibility
alert Semantic callout boxes (info, warning, error, success) with default icons
text-section Structured prose section with heading and Markdown body
markdown Inline GitHub-flavored Markdown text
markdown-file Load and embed external .md files
badge-row Color-coded tag row with customizable alignment
divider Horizontal separator with optional text label
spacer Configurable vertical whitespace
raw Universal escape hatch: write arbitrary HTML, CSS, and JS with access to active theme variables

Independent CSS Themes

Themes decouple aesthetics from content. Switching a theme takes one line in YAML:

  • default: Clean neutral theme suitable for general documents and mixed deliverables.
  • data: Optimized for dashboards, dense metrics, sortable tables, and hover-highlighting.
  • document: Optimized for long-form reading (720px readable width, 1.8 line-height, elegant typography).
  • promo: High-impact landing pages and announcements (gradient hero, elevated cards).
  • diagram: Centered, responsive layout for architecture and flow diagrams.

Model Context Protocol (MCP) Server

Every Open Artifacts instance serves a built-in MCP server at <APP_ORIGIN>/mcp via Streamable HTTP. Connect any MCP client by supplying an API key (personal or agent) in the Authorization header. A personal key spans every team it belongs to — ?orgId= in the URL sets the default team for this connection (omit it if the key only has one team):

{
  "mcpServers": {
    "open-artifacts": {
      "url": "http://localhost:3000/mcp?orgId=<team-id>",
      "headers": {
        "Authorization": "Bearer oa_live_xxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

Available MCP Tools

  • whoami: Check token identity, scopes, and team context.
  • list_orgs: List the teams this identity can act in.
  • list_artifacts: Search and retrieve artifacts in a team.
  • get_artifact: Fetch artifact metadata and raw content.
  • create_artifact: Create a new artifact (html, markdown, mermaid, svg).
  • update_artifact: Publish a new version for an existing artifact.
  • delete_artifact: Soft-delete an artifact.
  • create_share: Create a team-only, password-protected, or public expiring share link. Omitting mode uses the team's configured default.
  • list_shares: View all active shares for an artifact.
  • revoke_share: Instantly deactivate a share link.

list_artifacts and create_artifact take an optional orgId argument, overriding the connection's default. With neither set and more than one team to choose from, they return a structured org_required error listing the candidates instead of guessing.


Agent Instructions Page

An interactive cheatsheet with copyable commands, live instance URLs, and direct links to /activate is integrated into the web UI at /help/agents, available to every signed-in user — connecting an agent is something any team member does for their own team, not a superadmin-only task.


Teams, invites, and admin

Every user gets an auto-provisioned personal main workspace on registration (including when registering through an invite link — they land in both their own workspace and the inviting team). Teams are created explicitly from Команды (/teams).

Inviting someone to a team (/t/:orgId/settings) works for both new and existing accounts:

  • New email — you get a one-time link (/invite/:token) to send them yourself; there is no email delivery built in.
  • Existing account — they see the invite under Приглашения (/invites) in their own session and accept or decline it explicitly; nothing is added silently.

Superadmins get a dedicated admin area at /admin (Overview, Users, Teams, Audit, Instance settings), separate from the agent-connection instructions above.


Configuration

Configure the application through environment variables (see .env.example):

Variable Description Default
PORT HTTP server port 3000
DATABASE_URL PostgreSQL connection string postgres://postgres:postgres@db:5432/open_artifacts
APP_ORIGIN Public base URL of the instance (used for share links, invite links, device-flow verification, CORS/CSP) http://localhost:3000
ARTIFACT_ORIGIN Optional separate origin to serve artifact content (/embed/*) from, for stronger isolation unset (serves from APP_ORIGIN)
SESSION_SECRET Random string used to sign session cookies (generate with openssl rand -hex 32) Required
IP_HASH_SALT Salt used when hashing viewer IPs for analytics Required
SUPERADMIN_EMAIL Initial superadmin email, created on first run if set admin@example.com
SUPERADMIN_PASSWORD Initial superadmin password Required in production
DEFAULT_KEY_TTL_DAYS Default lifetime for agent API tokens (0 = never expires) 90
DEFAULT_REGISTRATION_MODE Who may self-register: open, invite_only, or closed invite_only
ARTIFACT_PURGE_INTERVAL_MINUTES How often the background sweeper hard-deletes expired artifacts (0 disables it; lifetimes still apply lazily on read either way) 5
S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_REGION, S3_FORCE_PATH_STYLE S3-compatible object storage for uploaded artifact files. Leave S3_BUCKET unset to disable the feature entirely (upload endpoints answer 501); docker-compose.yml sets these to its own bundled MinIO by default unset (disabled)
STORAGE_MAX_FILE_BYTES Hard per-file upload cap, independent of the team/artifact byte quotas set in /admin/settings 26214400 (25 MiB)
STORAGE_GC_INTERVAL_MINUTES How often the background sweeper deletes objects queued for removal (0 disables it) 5

Most operational settings — registration mode, default key TTL, invite link lifetime, the CDN allowlist, the maximum artifact size, the maximum/default artifact lifetime, and storage quotas — can also be modified at runtime by superadmins in Настройки инстанса (/admin/settings); they don't need an environment variable or a restart.

File storage and quotas

Uploaded files (images, attachments) live in an S3-compatible bucket, proxied through the app at /af/<token> — the bucket itself stays private, no presigned URLs are ever handed out. A file always belongs to exactly one artifact and is deleted with it (soft-delete, expiry, or an org being deleted all queue its files for removal); there is no way for an orphaned file to exist.

Storage quotas are unlimited by default, at both the team and per-artifact level. A superadmin sets instance-wide ceilings in /admin/settings; a team can set its own stricter override (never looser) — the per-artifact one in its own team settings, the team-wide one only by a superadmin. Before uploading, an agent can check GET /api/v1/quota (or oa quota / the get_storage_quota MCP tool) to see remaining room and decide whether the upload is worth attempting.

Artifact lifetime (TTL)

By default artifacts live forever. A superadmin can set an instance-wide maximum artifact lifetime (in minutes, in /admin/settings) — it doubles as the default for newly created artifacts. Team owners/admins may set their own stricter limit for their team, but never looser than the instance maximum. Callers (CLI --lifetime, the web UI, or the MCP/API lifetime field) may pick anything up to the effective limit at creation time, and change it later.

Once an artifact's lifetime expires its content is hard-deleted — the version history is gone for good, only a tombstone row remains for audit/analytics. Lowering the maximum re-clamps every existing artifact's expiry from its own creation date (min(current deadline, created_at + new max)); raising it never extends anything already created.

Link modes and share policy

A share link is team (only logged-in members of the artifact's team can open it), password (anyone with the URL and the password), or public (anyone with the URL). Creating one without naming a mode — the plain --share flag, an omitted mode field over the API, or MCP's create_share with no mode argument — uses the team's configured default, not always public.

Team owners/admins set two things per team in Настройки команды (/t/:orgId/settings, including for their personal main workspace): the default link mode (team or public) and whether public links are allowed at all. A superadmin sets the same two knobs instance-wide in /admin/settings; a team can only be equal-or-stricter than the instance, never looser — it can never re-enable public links the instance disabled, nor default to public while public links are forbidden. Disabling public links only blocks new ones; existing public shares keep working until revoked, one by one from the artifact page or in bulk from the team settings page.

An existing link's own access mode can also be changed in place — without revoking it and handing out a new URL — right from its own viewer page (/s/:token), by anyone who can manage the artifact (PATCH /api/v1/shares/:id). Picking a mode in the panel's visibility menu saves it immediately and updates the panel without reloading the page; Password protected first asks for the new password.


License

MIT © emaxe

Releases

Packages

Contributors

Languages