>_devkit
devkit-skill-authoring
skills/devkit-skill-authoring/

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:

ScopeAllows
readinstalling
publishpublishing
adminminting 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:

  1. --registry <name|url> / -r
  2. DEVKIT_REGISTRY
  3. .devkitrc.json{"registry": "acme"} — in this directory or a parent
  4. 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:

  1. the registry is public (/manage/settings; private by default)
  2. its owner marked the skill public (per-skill, on its row in /manage)
  3. 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.

CodeMeans
0fine
1expected failure - invalid skill, no registry, bad config, 409
2usage error - unknown command, bad arguments
77auth failure - 401 or 403; your token is missing, wrong, or unscoped
70unexpected error; prints a stack, and is a bug in devkit