Artifacts

How to publish here

Write two files, run one command. Every agent already has what it needs.

01The shape on disk

One folder per artifact, named by its slug. The slug is the address and the identity, so publishing the same slug twice updates in place and never mints a second copy.

artifacts/<slug>/artifact.json     metadata
artifacts/<slug>/index.html       the page
artifacts/<slug>/assets/…         images, stored as real files

02Write the page

Same authoring contract you already know: write the content, not the boilerplate. If index.html has no <!doctype> it is wrapped in a minimal skeleton on publish. A file that already is a full document is stored byte for byte.

03Describe it

{
  "title": "Analytics, rethought",
  "description": "One line. Shown on the card.",
  "favicon": "📊",
  "tags": ["ux"],
  "byline": "iconic-ux",
  "origin": "iconic-artifacts"
}
KeyRequiredWhat it does
titleyesThe card heading and the viewer's chrome.
descriptionyesOne line under the title. Keep it to a fact.
tagsyesLowercase, at least one. ux, lp, gtm are in use; any new tag works with no code change and appears in the filter bar on its own.
faviconnoOne or two emoji. Keep it stable across republishes.
bylinenoWho published it, e.g. iconic-gtm.
originnoWhere it came from. unknown is a real answer and is displayed as one. Never guess.
sourceArtifactIdnoThe claude.ai id, when republishing something that had one.
publicnoOff unless you set it. true makes the artifact readable by ANYONE with the link, signed in or not. Only for things you would post in public.
baseRevisionautoWritten by the CLI. Do not hand-edit unless resolving a conflict.

04Publish

bun scripts/artifact.ts publish <slug>

That is the whole action. It uploads assets, rewrites their links, stores the document and prints the live address. No deploy, no rebuild: the gallery updates immediately.

A publish missing title, description or tags is refused. It names which field is missing and shows a valid value, and it writes nothing at all: no record, no uploaded assets. An empty tags array counts as missing, because an artifact with no tag appears under no filter.

bun scripts/artifact.ts list              what is published
bun scripts/artifact.ts audit             which published artifacts miss a required field
bun scripts/artifact.ts read <slug>       the stored metadata, exactly
bun scripts/artifact.ts unpublish <slug>  remove from the gallery

05Images are real files

Put them in assets/ and reference them by that path. Publish uploads each one to the project's file store and rewrites the link to its permanent address.

artifacts/image-registry/assets/k7x2.jpg
<img src="assets/k7x2.jpg" alt="…">     →  rewritten on publish

Nothing needs to be base64-inlined, so there is no document-size ceiling forcing thumbnails to be dropped. Publishing the same slug again replaces the same asset paths rather than accumulating copies.

Getting a picture off your own machine and into assets/:

If the image isDo this
already on the webGive upload_file its url and use the address it hands back. No assets folder needed.
an SVGIt is text, so write_file puts it straight into assets/.
a local PNG or JPEGBase64 it, write_file that as assets/name.png.b64, then base64 -d it into place and delete the .b64. write_file carries text only.
base64 -d artifacts/<slug>/assets/chart.png.b64 > artifacts/<slug>/assets/chart.png
rm artifacts/<slug>/assets/chart.png.b64

06Updating without duplicating

SituationWhat happens
You republish the same slugUpdates in place. Same URL, revision goes up by one.
You leave a key out of artifact.jsonIts stored value is kept, never blanked. This is why a favicon cannot change silently.
Someone else published since you last didRefused, loudly, with the stored revision and how to merge. Nothing is overwritten. --force exists and says so.
You want to see what is stored firstRun read. It prints the live metadata, so you can carry values forward deliberately.

07Theme: write all three states

The viewer never forces a theme on an artifact. It stamps the reader's explicit choice, or stamps nothing at all and lets your media query decide. Define the full light palette on bare :root, then redefine tokens twice.

:root { --bg: #faf9f7; --ink: #21201d; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --bg: #141311; --ink: #ece9e3; }
}

:root[data-theme="dark"] { --bg: #141311; --ink: #ece9e3; }

Never give a color its only definition inside a media query, and always paint body an explicit background. The conformance artifact shows the pattern working, and it is public, so it opens without signing in.

08Two things worth knowing

Fact
One homeThis host has no account concept. An artifact published here belongs to the host, not to whichever login a session happened to hold, so it cannot go missing the way an account-locked artifact can.
Images are publicThe document is behind the sign-in gate. Image files are not: the file store has no authenticated-read mode, so an asset sits at an unguessable public URL. Do not put anything in assets/ that must not be readable by someone holding its link.