references/publishing.md
Publishing, registries, and the life of a version
This file is the general behaviour of any devkit registry. The house specifics - https://devkit.chempa.dev, sources in ~/skills/<slug>/, and the rule that a skill only ever reaches the registry through devkit publish and never through a git push to devkit-site - are in the skill body, under "How we publish here".
Getting the CLI
The CLI is served by the registry and needs no token - it is a client and holds no skills.
curl -sSL https://your-registry.example.com/install/cli | sh
Then mint a token at /manage/settings and log in:
devkit login https://your-registry.example.com --as work # prompts for the token
--token - reads the token from stdin, which is what to use in CI so it never appears in a process list or a shell history.
Tokens and scopes
Three scopes, granted per token:
| Scope | Allows |
|---|---|
read | installing |
publish | publishing |
admin | minting tokens, administering the registry |
Tokens are stored as sha256 and shown once at creation. A lost token is revoked and replaced, not recovered. They are per-person and revocable without touching anyone else, which is how somebody who only needs to publish gets access without an account.
The install commands the site displays carry a signed token that expires in a day, so a curl line pasted into a chat stops working on its own.
Which registry a command uses
Most specific wins:
--registry <name|url>/-rDEVKIT_REGISTRY.devkitrc.json—{"registry": "acme"}— in this directory or a parent- the default set by
devkit use
devkit whoami prints the active registry, the token, and why that one was chosen. Use it before publishing anything you would not want in the wrong place.
Option 3 is the one worth setting up. A repo that pins its registry means a contributor publishes to the right place without being told, and cannot silently publish an internal skill to their own default.
devkit registries # list them, marking the active one
devkit use acme # change the default
devkit list -r other # override for one command
Publishing
devkit validate ./my-skill # optional; publish does it anyway
devkit publish ./my-skill
publish validates locally first so the common mistakes cost nothing, then uploads the archive. The server re-validates regardless - the local check is a convenience, not the authority. Both run the same function from @devkit/core, so they cannot disagree.
If you publish an older line - 1.4.2 when 2.0.0 exists - it succeeds and the CLI warns that latest is unchanged.
Versions are immutable
Publishing a version that already exists is a 409. There is no --force.
An install pins a content digest. The server sends it as X-Devkit-Digest, and the CLI refuses to install anything whose archive does not hash to the claimed value - and refuses outright if the header is missing, rather than installing unverified content.
That is what makes rollback boring: install the previous version.
latest is the highest release. Publishing 2.0.0-rc.1 does not become what everyone gets by default; it is installable only by exact version.
Withdrawing a release
devkit yank my-skill@1.2.0 --reason "leaks the token into the log"
devkit unyank my-skill@1.2.0
Yanking moves latest off the bad version and marks it everywhere somebody could be handed it: the skill page, the generated install script, and the CLI on both install and update.
A yanked version stays installable by exact version, and warns after the install rather than instead of it. Somebody who pinned it may well have meant to, and refusing would make pinning useless - but handing it over silently is the failure yanking exists to prevent.
Yank state belongs to the version, not the skill: a yanked 1.2.0 sits beside a live 1.1.0.
Namespaces and forking
Every account gets a namespace, so a skill is named by owner and slug together: @you/deploy-pipeline. Two people can each have a deploy-pipeline. A bare name resolves while it points at one thing, and returns the candidates when it does not - nothing guesses which one you meant.
devkit fork @someone/their-skill # copy into your namespace
devkit fork @someone/their-skill --as mine # and rename it
Installing and updating
devkit install @owner/my-skill # latest
devkit install my-skill@1.2.0 # exact version
devkit list --installed
devkit update # everything
devkit update my-skill # one
Setting up a machine from your whole catalogue:
devkit install --all # everything this login can see
devkit install --all --mine # only what you own, private included
devkit install --all --tag ci # narrowed, the same way list is
Use these rather than curl …/install/all | sh for your own skills. That URL is fetched anonymously, so the registry answers with the public catalogue and nothing else — correct for a stranger, and no way to install a private skill of your own. The CLI already holds your token, so it asks as you.
One skill failing does not stop the rest, and the exit code still reports the failure.
Skills install into ~/.claude/skills/<slug>/. CLAUDE_HOME redirects that, which is how to inspect an install without touching your real config:
export CLAUDE_HOME=$(mktemp -d)
devkit install my-skill
find "$CLAUDE_HOME"
The install removes the directory first rather than merging over it, so a file deleted in a later version actually disappears instead of being left behind and loaded forever.
Each install writes a .devkit.json marker recording slug, owner, version, digest, registry, url, and time. That is how devkit update on a machine with skills from three registries asks each one for its own. The marker is never published - it is stripped from any folder you publish.
Public or private
A skill is world-readable only when all of these hold:
- the registry is public (
/manage/settings; private by default) - its owner marked the skill public (per-skill, on its row in
/manage) - and, if that owner is not an admin, somebody approved it
Publishing a skill and publishing it to the world are separate decisions. Flipping the registry to public exposes nothing until skills are marked public one at a time; flipping it back seals everything immediately. Publishing and administration always require authentication, in both modes.
The review gate exists because a skill is text that Claude reads as instructions on whoever installs it, nothing detects that reliably, and so a person looks.
Exit codes
Worth knowing when scripting this.
| Code | Means |
|---|---|
0 | fine |
1 | expected failure - invalid skill, no registry, bad config, 409 |
2 | usage error - unknown command, bad arguments |
77 | auth failure - 401 or 403; your token is missing, wrong, or unscoped |
70 | unexpected error; prints a stack, and is a bug in devkit |