# Publish a skill

Publishing copies one folder from your Library into the team repository as its next immutable version. It is the only way a skill reaches the team, and it runs only when you type it.

## One verb writes skills to the team

```sh
npx -y terum-skills@latest publish <skill-or-path>
```

The ref is a skill name or a path, and either must resolve inside your Library (Global plus every project you registered), so publish can never be pointed at an arbitrary folder on disk.

Nothing else in the tool creates a version, and the push guard in your clone refuses a hand `git push` of skill bytes.

## Before you publish

- **The folder is in your Library.** Under Global, or in a registered project. See [Install and manage](https://www.terum.ai/docs/install-and-manage#where-the-copy-goes).
- **This machine has joined a team.** Run `setup <org>/<repo>` first; a machine that never completed a join is refused.
- **The folder passes hygiene.** `validate` reports every finding, offline. `skill fix` repairs the mechanical ones, such as YAML quoting, and lists what still needs you.

```sh
npx -y terum-skills@latest validate <path>
npx -y terum-skills@latest skill fix <path>
```

On a folder that has never been published, `validate` also reports the four managed fields as missing. Publish writes those itself, so that finding does not block it. See [Hygiene checks](https://www.terum.ai/docs/hygiene).

## What publish does

::: steps
### Fetch
The team clone is fetched and hard reset to `origin/main` under the clone's writer lock. If another terum-skills command holds the lock, publish waits a short while for it.
### Read the folder
Every file is read, eval assets included. A folder that is a symlink, contains one, or has no `SKILL.md` is refused.
### Resolve the category
The category your `SKILL.md` declares wins, then `--category <name>`, then one `claude --model haiku` suggestion matched against the team's list, then `misc`. The model cannot add a category, and publish never blocks on it.
### Inject the managed fields
An in-memory copy of `SKILL.md` gets `license` (the team's policy), `metadata.id` (the uuid the repository already holds for this name; a new name keeps a declared id no other skill uses, or gets a fresh one), `metadata.author` (`Name <email>` from your identity) and the category, when the file declares none.
### Hygiene
The checks run on the injected copy and every other file. An error refuses the publish; a warning is printed and gates nothing.
### The regression gate
If your newest local eval receipt of these exact bytes is a `FAIL`, publish asks whether to publish anyway. Having no receipt never blocks, and this step reads only your machine.
### Write to the team
The injected `SKILL.md` is written back to your folder after the last question, so a canceled publish leaves it untouched. Then one commit goes straight to `main`: a new `skills/<name>/v<N>/` unless an existing version holds these exact bytes, the skill's eval assets, and every local receipt of these bytes. `--project <name>` also adds the skill to that team project's list.
### Your profile entry
The skill is added to your profile without a question, because typing `publish` is the endorsement. `profile --remove <skill>` takes it off and unpublishes nothing.
:::

::: figure publish-line
A skill folder moves down the line through category, managed fields, hygiene and the regression gate, and lands as the next immutable version.
:::

A run ends one of two ways, and both are successes. Either a version was minted:

```text
Published deploy-check as Version 4 to the acme marketplace. Attached 2 eval run(s).
```

Or the bytes match an existing version, so nothing is minted, though eval assets and matching receipts still travel. When nothing moved at all, the line starts `Nothing to publish:`.

## What publish never does

- **It opens no pull request.** Every write is a direct commit to `main`, whatever the verb's help text says.
- **It checks no ownership.** Anyone in the team can publish any name, and a folder whose name the team already has becomes that skill's next version. Before appending, publish checks that the name still carries the uuid it resolved, and refuses if it changed hands in the meantime.
- **It does not install.** Nobody's machine gains a copy because you published.
- **It sends nothing to Terum.** The category suggestion goes to Anthropic through your `claude`; the bytes go to your git host.

## Republishing

**After an edit.** Change any file and the content digest changes, so the next publish mints the next version. Older versions stay exactly as they were: nothing can change what Version 3 is. See [Versions and identity](https://www.terum.ai/docs/versions).

**After an eval-only change.** Eval cases live in `evals/`, which the content digest leaves out. The next publish mints no version, writes the eval assets and attaches matching receipts, which is how an eval reaches the team without moving a skill byte. When `eval` generates assets it says the next publish mints a new version; it does not.

**Changing a category.** `publish --category` fills a category in and never replaces a declared one. To change it, edit the folder:

```sh
npx -y terum-skills@latest skill category ~/.claude/skills/deploy-check --to ops
```

That rewrites one field locally and touches nothing else. The team shows the old category until you publish again, which mints a new version because the category is part of the content.

## Unpublishing

```sh
npx -y terum-skills@latest unpublish <skill>
```

The argument is the skill's name in the Marketplace. Anyone in the team may unpublish any skill, with no approval, so whoever notices a wrong publish can retract it. The brake is typing the skill's name to confirm; `--yes` skips it.

::: warning
Unpublish removes every version of the skill for everyone, in one commit: the version folders, their eval assets, every attached receipt, and the skill's entry in every team project and every member's profile.
:::

What stays: installed copies keep working until each machine runs `sync`, which then reports the skill as removed from the team. The git history is not rewritten, so the bytes remain reachable in old commits. `installed[]` records are left alone, because they describe machines, not the repository.

Unpublishing does not reserve the name. Publishing the same folder later starts again at `v1`, and it keeps the `metadata.id` your `SKILL.md` carries unless another published skill holds it.
