dist.town

docs/Start here

Start here


Sharing a release should be the easy part. This page takes you from nothing to a published release with notes: a login, a committed file, and one publish command.

What you need

  • An ATProto account — the same kind of account you’d use on Bluesky or Tangled. New to the ecosystem? Get an account. Your releases are published as records in your repo, signed by your identity; dist.town never holds them.
  • The disttown CLI — see Install.

1. Log in

sh
disttown login your-handle.example

Your browser opens for OAuth against your own PDS, and the session is cached on this machine. Running over SSH? --app-password logs in with an app password instead — it prompts for the password, so it still wants a terminal.

CI has its own path, with no login step and nothing on disk: set DISTTOWN_IDENTIFIER and DISTTOWN_APP_PASSWORD (plus DISTTOWN_PDS if you’d rather not have your handle resolved), and every command creates a fresh session for that run.

2. Describe your repo — once

sh
disttown init

init writes disttown.jsonc: the project this repo publishes, the files each release contains, where the bytes go, and which account is expected to do the publishing. Commit it like any other file.

jsonc
{
  "$schema": "https://dist.town/disttown.schema.json",
  "project": "mytool",
  // your DID — another login stops and asks, and is refused outright in CI
  "publisher": "did:plc:…",
  "artifacts": ["dist/*"],
  // "blobs" stores artifact bytes on your own PDS — zero setup, right for small artifacts
  "storage": "blobs",
}

The CLI looks for disttown.jsonc in the current directory and walks upward until it finds one — nearest wins, so a monorepo sub-project gets its own context for free.

An artifacts entry is a glob. A plain string is the whole entry; an object carries a per-entry destination, so { "glob": "dist/*.iso", "storage": "big" } sends those files to a named storage profile while everything else follows the manifest’s storage. Globs resolve against the manifest’s own directory and stay inside it: absolute paths, .., and symlinks pointing outward are all refused, because a manifest you cloned is a stranger’s file deciding what gets published under your name.

$schema names the schema this site serves; editors fetch it for completion and validation.

From now on the CLI infers everything this file knows, and tells you when it does. The precedence is the same everywhere: a flag beats an environment variable, which beats the manifest, which beats this machine’s config, which beats asking you.

Check the setup before you publish anything:

sh
disttown doctor

It reports the session and its scopes, whether your PDS answers, whether the storage this repo would actually use is ready — "storage": "blobs" needs no bucket at all; a named profile this machine doesn’t define is a failure you’d otherwise meet mid-publish — and whether the public index has seen your repo yet.

3. Publish

sh
git tag -a v1.0.0
disttown publish --notes notes.md

The tag proposes the version, the manifest supplies the files, and the CLI hashes each one and shows you the whole plan — project, version and where each came from, every file with its digest and destination, the pointer moves. On a terminal it asks before anything uploads. In a script (no terminal, no --yes) it proceeds without asking, on the assumption the plan was reviewed when the script was written. Then one atomic commit to your repo: the release, its notes, and a latest pointer. There is no separate “create project” step — the first publish creates the project in the same commit.

The tag does a second job: it becomes the release’s source claim, pinning the commit and the annotated tag object into the record. That claim is also what makes forge discovery possible — with a Tangled repo, publish looks for artifacts uploaded against the same tag and asks whether to attach them, hashing every one locally first. That question is separate from the publish confirmation, and --yes does not answer it: pass --attach-tangled to say yes in CI.

  • --notes notes.md publishes your markdown as release notes, readable on your release page and in any standard.site reader.
  • --dry-run renders the same plan and stops. Nothing is uploaded and nothing is written — the manifest included.
  • No manifest? The fully explicit form always works: disttown publish --project mytool --blobs 1.0.0 dist/mytool-linux-amd64.tar.gz. Flags come before positional arguments.

Publishing is safe to retry. Re-publishing a version that already exists exits 0 and writes nothing. If an upload fails partway, the command names every key it already wrote and deletes none of them — the keys are content-addressed, so a retry reclaims them.

Big files? Bring your own storage instead of blobs: define a profile with storage setup --profile and name it in the manifest — each repo picks its destination, your machine keeps the credentials.

4. That’s it — see it live

Your project now has a page and a steady address:

  • https://dist.town/your-handle.example/mytool — the project page: releases, notes, downloads.
  • https://dist.town/your-handle.example/mytool@latest — always the newest release.
  • https://dist.town/your-handle.example/mytool@1.0.0 — this release, forever.

Give the index a moment for the downloads. dist.town fetches each artifact and hashes it against the digest in your record before it will redirect anyone to it; until that finishes the download URLs answer 425 and the pages say verification is pending. The checksum endpoints answer straight away — they come from your record, not from the bytes.

Scripts can fetch artifacts and checksums from the same addresses — see the front door.

You don’t need a browser to check any of this. disttown release list reports what’s published, disttown release show latest shows one release in full, and disttown whoami tells you whose session this machine holds. (Standing in the repo is enough — the manifest supplies the project, and each command says so; name a project explicitly to look anywhere else.) Those two release reads consult dist.town’s index by default (the same public surface the pages use); pass --pds to read straight from your own repo instead, or --compare to see where the two disagree — useful right after a publish, before the index catches up. disttown project list and pointer list always read your repo directly, because those records are keyed by name and your repo answers instantly.

Where to go next

  • Ship the next version the same way; latest moves automatically (or manage channels with disttown pointer set).
  • Attach or revise notes on any already-published version with disttown changelog set.
  • Organize multi-component projects with nesting and publications — see How dist.town thinks.
  • Something shipped broken? disttown release yank marks a release do-not-newly-adopt without breaking anyone’s pins — and release deprecate is the softer word for “still works, stop building on it.”