bin/setup # Installs all tools and dependencies
bin/media pull # Pull all assets locally - must be done before using `sync`, else chaosbin/dev # http://localhost:4321, reloads as you write
bin/preview # http://localhost:8787, the production build, with searchUse bin/generate.
It makes the entry's folder, named <slug>-<id>, with a fresh ID, plus a directory for
its media under media/originals/, and prints the file to edit and the URL it'll have.
Important
The folder name is the URL
There are no drafts: it stays unpublished for as long as it isn't committed. To publish:
- Set
publishedAtto now. bin/link-previewsfor the hover cards of its links, andbin/media variants, then commit, includingsrc/data/link-previews.jsonandsrc/data/media.json, and push.bin/deploy. It fetches any missing hover cards and makes the variants first, then checks everything is committed and pushed, so a forgotten step 2 stops it there. Then it syncs the media to R2 (asking before it deletes anything there), builds and deploys.
bin/generate article "Clean air & AI"Write it in src/content/articles/<slug>-<id>/index.mdx, in Markdown:
---
title: "Clean air & AI"
publishedAt: '2026-10-01T07:00:00Z'
updatedAt: '2026-10-01T07:00:00Z'
tags: [ruby, rails]
---Photos go in media/originals/articles/<slug>-<id>/, and the body refers to them by file
name, with no imports:
<Figure media="view.jpeg" caption="Zagreb from Sljeme" />
<Gallery>
<Figure media="one.jpeg" inGallery />
<Figure media="two.jpeg" inGallery />
</Gallery>
<Video media="demo.mp4" caption="The demo" />- When it's shared, the article shows a generated card: your avatar, the title and the
excerpt. To show one of its images instead, name it in the frontmatter:
ogImage: view.jpeg. Talks work the same way; snaps show their photo. - A video's poster is the image with the same name beside it (
demo.jpg). If there isn't one,bin/media variantstakes a frame from the video. Put your own there to override it. - A GIF goes in as an MP4 with
clipon its<Video>: it loops without controls. bin/media variantsmakes the resized copies and strips location data from the originals. Run it after adding media.- Bump
updatedAtwhen you edit a published article.
bin/generate talk "Deconstructing Action Cable"In src/content/talks/<slug>-<id>/index.mdx, replace the placeholder event and heldAt,
and set kind to conference or meetup. The body is the abstract.
event: RubyZG meetup
kind: meetup
eventUrl: https://www.meetup.com/rubyzg/events/313063040/
heldAt: 2026-02-12T18:00:00Z
video: talk.mp4
videoMirrorUrl: https://www.youtube.com/watch?v=...For a recording, put talk.mp4 in media/originals/talks/<slug>-<id>/ and run
bin/media variants. It makes the poster, talk.jpg, from a frame a fifth of the way in.
A YouTube copy goes in videoMirrorUrl and shows up as "View via mirror". With only a
YouTube link and no file, the page embeds YouTube instead. Either one gets the talk a VIDEO
label.
bin/generate snap "Soca Valley" ~/Pictures/soca.jpgThat copies the photo into the snap's media folder and sets image for you. A snap has no
body, just frontmatter in src/content/snaps/<slug>-<id>/index.md, with an optional
caption:
image: soca.jpg
caption: Morning fog over the valleyFor a video snap, use video instead of image. The poster and the length shown on the
grid come from the video. Then run bin/media variants, which also strips the GPS
location, and look at it in bin/dev.
bin/rename article 8s17uw9GPHgD "New title"
bin/rename 8s17uw9GPHgD "New title" # the type is optionalThe ID is the end of the folder name. This sets the title, moves the entry and its media to
<new-slug>-<id>, and updates the media keys. The old URL redirects to the new one, and feed
readers don't see it as a new post. If the entry has media, run bin/media sync afterwards.
AGENTS.mdanddoc/cover how everything works..agents/skills/has the step-by-step versions of the above.