Push a local md/html file, get a public HTML page on the internet for agent-first collaboration
Overview
OpenArtifacts publishes rendered HTML at a stable link and preserves every update as an immutable version. Publishers use an authenticated HTTP API; readers open a public document without an account, and a publisher can instead share a document only with people they invite, who sign in to open it. The Worker stores document bytes in Cloudflare R2 and ownership, version, deletion, and sharing metadata in D1. It is a small API-first publishing service with inline comments on a quoted passage. - Send a complete HTML document and get a link. The CLI starts a new page private (invited people only) when the plan allows it, and public otherwise or when you pass --public. - Every push creates a new version. The shared link shows the latest version, while pinned /v2-style links keep showing the version they name. - Publishing needs a bearer key. Reading a public document needs nothing. - Set a document to "Only those invited" and add people by email.
README
OpenArtifacts publishes rendered HTML at a stable link and preserves every update as an immutable version. Publishers use an authenticated HTTP API; readers open a public document without an account, and a publisher can instead share a document only with people they invite, who sign in to open it. The Worker stores document bytes in Cloudflare R2 and ownership, version, deletion, and sharing metadata in D1.
It is a small API-first publishing service with inline comments on a quoted passage.
What works today
- Publish rendered HTML. Send a complete HTML document and get a link. The
CLI starts a new page private (invited people only) when the plan allows it,
and public otherwise or when you pass
--public. - Keep one stable link. Every push creates a new version. The shared link
shows the latest version, while pinned
/v2-style links keep showing the version they name. - Read without signing in. Publishing needs a bearer key. Reading a public document needs nothing.
- Share privately. Set a document to “Only those invited” and add people by email. They sign in once with Google, GitHub, or an emailed code and open the same link; anyone else can ask the owner for access, and the owner approves or declines from Share in the top bar of every served page (a popover, or the share page where scripts do not run). Private sharing is a paid-plan feature on the hosted service, and a self-hosted server needs a sign-in method configured to offer it.
- Comment on a passage. The owner gives named people Can comment (a paid-plan
feature); they and the owner’s agent select text, comment and reply, and the owner resolves the
thread against the version that fixed it. Threads follow their quote across
versions and are marked orphaned if the text goes.
openartifacts commentsdoes the same from a terminal. - Keep pages out of search results. Serving responses carry
noindexdirectives. - Run interactive documents. Scripts, charts, and simulations work. Pages cannot submit forms or be embedded by another site.
- List and unshare documents. Deleting destroys the stored files and leaves
a
410 Gonetombstone. Copies already in a reader’s browser cache cannot be recalled. - Bound abuse. Hosted free accounts get three live documents, six publishes/updates per UTC day, and 1 MiB HTML per document. The paid plan and paid Copilot licenses allow 500 documents, 100 pushes per day, and 10 MiB. All credentials share their account’s allowance. Unsharing frees a document slot, not a daily push. Standalone checkout is not available yet.
Use from an agent
Install the CLI and shared skill into detected Claude Code, Codex, OpenCode, and pi installations:
npx openartifacts install
Hermes Agent needs only its native skill install:
hermes skills install https://cdn.jsdelivr.net/npm/openartifacts@latest/skill/openartifacts/SKILL.md
Hermes security-scans the skill and can refresh it later with
hermes skills update. The skill uses an installed openartifacts command when
available and falls back to npx otherwise. Node.js 20+ and npm are required.
The first publish opens a browser approval and stores the resulting token
without printing it. Later publishes of the same local file update its existing
document. Set OPENARTIFACTS_API_HOST for a self-hosted deployment, or provide
an opaque credential through OPENARTIFACTS_TOKEN in a non-interactive session.
Vision
OpenArtifacts is an agent-first medium for publishing and reading webpages and other artifacts. It is designed for groups of humans and agents to iterate on the same page and converge on a shared result.
| Goal | Status |
|---|---|
| Agent-first publishing and reading | Available today |
| Immutable versions and pinned version links | Available today |
| Public sharing | Available today |
| Self-hosting | Available today |
| Private sharing (invite people by email; paid plans) | Available today |
| Inline comments and replies (quote-anchored, humans and agents; paid plans) | Available today |
| Version-history browser, visual diffs, and rollback UI | Roadmap |
Run locally
Requires Node.js 22 or newer. Local development uses the real Workers runtime with local R2 and D1 data, but needs no Cloudflare account.
npm install
npm run typecheck
npm test
# Create the local schema and one local publisher.
npx wrangler d1 migrations apply DB --local
KEY=local-development-key
HASH=$(printf '%s' "$KEY" | shasum -a 256 | cut -d' ' -f1)
npx wrangler d1 execute DB --local --command \
"INSERT OR REPLACE INTO publishers (key_hash, owner, plan, validated_at)
VALUES ('$HASH', 'local-owner', 'plus', $(($(date +%s) * 1000)))"
npm run dev
In another terminal, exercise the whole publish, read, list, and delete path:
OPENARTIFACTS_LICENSE_KEY=local-development-key \
scripts/smoke.sh http://127.0.0.1:8787
local-development-key is deliberately easy to copy and is only for local
development. Use a generated high-entropy key for any internet-facing
deployment.
Self-host on Cloudflare
How it fits together
One Cloudflare Worker runs the backend. A Worker is a serverless application: Cloudflare runs its code when requests arrive and manages the servers and scaling. The API and public document hostnames reach the same Worker, but expose different operations.
flowchart TD
Publisher["CLI, agent, or other API client"] -->|"Publish, update, list, unshare"| API["API hostnameapi.example.com"]
Reader["Reader's browser"] -->|"Open a public link"| Serving["Document hostnameexampleusercontent.net"]
subgraph Account["Your Cloudflare account"]
API --> Worker["One OpenArtifacts WorkerAuthenticate publishers, enforce limits,manage and serve documents"]
Serving --> Worker
Worker --> DB[("D1 databaseOwnership and version records,accounts, tokens, and quotas")]
Worker --> Files[("R2 storagePublished HTML versions")]
end
D1 and R2 keep the persistent data; they are storage services, not additional Workers. Use a separate registrable domain for document HTML, not just another subdomain of your API’s domain. Readers of public documents need no account, and the document host does not expose the publishing API.
This deployment uses your own Cloudflare resources. It does not require the OpenArtifacts hosted website, a Copilot subscription, or a billing service. The setup below uses a self-managed publisher key; browser sign-in is optional and requires separate OAuth configuration.
Set up your deployment
You need a Cloudflare account with Workers, R2, and D1, plus two domains added to that account:
- an API hostname on your brand domain; and
- a separate registrable domain for untrusted document HTML.
The separation is important. A document that is reported as phishing should not take down your API or brand domain. Serving domain explains the boundary.
Choose your own account limits with PLAN_LIMITS and DEFAULT_PLAN in Worker
vars; the checked-in values are the hosted free/paid policy. Without an override,
the built-in map has one free plan: 3 documents, 6 pushes/day, and 1 MiB HTML.
Billing is optional: plan configuration and the admin API
let your own service change plans. Never expose the admin secret to clients.
Sign in to Cloudflare and create the storage resources. The names below are
examples; any names work when these commands and wrangler.jsonc agree.
npx wrangler login
SELF_HOST_DB=openartifacts
SELF_HOST_BUCKET=openartifacts-docs
npx wrangler d1 create "$SELF_HOST_DB"
npx wrangler r2 bucket create "$SELF_HOST_BUCKET"
The D1 command prints a database_id. Update wrangler.jsonc for your account:
- Set the Worker
name. - Set
bucket_nameto your R2 bucket name. - Set
database_nameanddatabase_idto your D1 values. - Replace
routeswith exactly twocustom_domainentries: your document host and your API host. (Private sharing, below, adds one optional wildcard route.) - Set
SERVING_HOSTandAPI_HOSTundervarsto those hosts and remove the two legacy host vars. Keep your chosenPLAN_LIMITS/DEFAULT_PLAN. - Keep
workers_devset tofalse.
Apply the schema, create a publisher key, deploy, and run the smoke test:
npx wrangler d1 migrations apply "$SELF_HOST_DB" --remote
SELF_HOST_KEY=$(openssl rand -hex 32)
SELF_HOST_HASH=$(printf '%s' "$SELF_HOST_KEY" | shasum -a 256 | cut -d' ' -f1)
SELF_HOST_NOW_MS=$(($(date +%s) * 1000))
npx wrangler d1 execute "$SELF_HOST_DB" --remote --command \
"INSERT INTO publishers (key_hash, owner, plan, validated_at)
VALUES ('$SELF_HOST_HASH', 'self-hosted-owner', 'plus', $SELF_HOST_NOW_MS)"
npx wrangler deploy
OPENARTIFACTS_LICENSE_KEY="$SELF_HOST_KEY" \
scripts/smoke.sh https://api.example.com
Replace api.example.com with your API host. Save SELF_HOST_KEY in a password
manager before closing the terminal. The raw key can publish, list, update, and
delete every document owned by self-hosted-owner; only its SHA-256 hash is
stored in D1.
Leave LICENSE_API_URL and LICENSE_API_KEY unset for this pre-seeded-key
setup. Unknown keys then fail closed. Because workers_dev is disabled, the
Worker becomes reachable only after Cloudflare attaches both custom domains.
Optional: private sharing
Documents are all public until you configure reader sign-in, and a server without
it answers the private-sharing routes 404, like a server that predates the
feature. A plain openartifacts publish publishes nothing there, because the CLI
never falls back to a public page after the private route answers 404; pass
--public to publish for anyone with the link. To turn it on you need, besides a Google or GitHub OAuth app (or Email
Service for emailed codes):
- a
READER_SESSION_SECRETWorker secret of at least 32 characters; - a proxied wildcard DNS record
*.{your document host}AAAAto100::and the matching*.{your document host}/*route inwrangler.jsonc, with the document host as a zone apex so the free wildcard certificate covers it; - if you use email,
EMAIL_FROMand thesend_emailbinding set for a domain you have onboarded to Email Service, which needs Workers Paid. On a free plan, delete thesend_emailblock andEMAIL_FROMthat ship pointing at the hosted service’s address.
Deploying has the whole list and the plan configuration.
Documentation
| Document | Contents |
|---|---|
| HTTP API | Publish, read, list, version, and delete endpoints. |
| Hosting | How Workers, R2, and D1 fit together. |
| Serving domain | Why uploaded HTML uses a separate registrable domain. |
| Identity | Why documents belong to an owner rather than a credential, and how an account is created. |
| Private sharing | Invite-only documents: reader sign-in, per-publisher origins, request access, and how access is checked. |
| Product analytics | What the five document events carry, and what they deliberately never carry. |
| Comments | Inline comments: quote anchors across versions, the per-document Durable Object, access, plans and limits. |
| Data model | Every D1 table, R2 key and Durable Object table, and which of them is the system of record. |
Maintainer npm release
Brevilabs maintainers publish the official npm package by merging a vX.Y.Z
release PR whose title matches its version bump; the protected workflow is
described in the release runbook. This process does not
apply to self-hosted deployments.
License
OpenArtifacts is available under the MIT License.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add brevilabs/openartifacts