docs · reference
Publishing.
Publishing a Rune means turning your directory into a content-addressed archive, uploading any blobs the registry hasn't seen, and registering a new immutable version. Operators install by name and version; the registry hands them back exactly the bytes you uploaded.
The flow
- Pack. The CLI reads
rune.toml, applies yourpublish.includeandpublish.excludeglobs, runs the publish preset (esbuild for TypeScript), computes sha256 per file, and assembles the canonical manifest JSON. Identical content always produces the same manifest hash. - Upload. The CLI sends the manifest to
POST /api/v1/runes/<name>/versions. The registry verifies the schema, claims the name on first publish, and returns pre-signed R2 PUT URLs for any blobs it doesn't already have. The CLI uploads those blobs directly to R2. - Finalize. The CLI calls
POST /api/v1/runes/<name>/versions/<v>/finalize. The registry verifies every blob is present in R2, atomically activates the version, updateslatestVersionif applicable, and returns the install string.
Content addressing
Every file you ship is stored under blobs/<sha256> in R2. The manifest is stored under manifests/<sha256>. Two Runes that ship the same README.md share one blob; if you publish the same source twice, the second publish writes zero new bytes.
The manifest hash is part of your version's identity. It's shown on the detail page, exposed via the API, and verifiable by re-packing locally: rune pack emits the same hash given the same source.
Naming
Names match ^(?:@[a-z0-9-]+/)?[a-z0-9-]+$.
- Unscoped names (
ward) are first-come-first-served across the registry. - Scoped names (
@yourname/ward) are reserved to whoever owns the@yournamescope, set up by claiming a username during onboarding.
Versioning
Versions are semver. The registry rejects any version that isn't strictly greater than the highest existing version for that name (per semver-precedence, so 1.0.0-beta.1 < 1.0.0). latestVersion tracks the highest stable version; pre-releases (anything with a hyphen) are visible but not surfaced by default.
Capability changes
Capabilities are part of the manifest hash, which means they're part of the version's identity. Adding, removing, or reordering capabilities requires a new version — operators get to review the delta before they upgrade. See capabilities for the format.
What ends up in the archive
The CLI honors your publish.include and publish.exclude globs. Defaults are intentionally broad — every file in the project root that isn't a dotfile — and you narrow from there.
[publish]
include = [
"**/*.ts",
"rune.jsonc",
"README*",
"LICENSE*",
"web/dist/**", # ship the built dashboard, not the sources
]
exclude = [
"**/*.test.ts",
"**/.env*",
]Order matters for clarity: include first, then exclude. Anything outside the project root, anything starting with ., and the contents of node_modules/ are never packed regardless of globs.
Yanking
If you ship a broken version, soft-delete it. Yanked versions stay installable for operators who pinned them but disappear from the default resolver, and the Runebook detail page shows a banner with your reason. The right move is to yank the bad version and ship a fix as a new patch version — never republish over a name + version combo.
rune yank @yourname/ward@0.1.0 --reason "broken on 1.21.4"Installing a published Rune
Operators install with one command from a Paper server root:
rune add ward # latest stable
rune add ward@0.1.0 # pin a specific version
rune add @scope/foo # scopedThe CLI fetches the manifest, downloads every blob from R2, materializes the directory under plugins/Rune/scripts/<name>/, and exits. A /rune reload from the server console picks it up.