---
name: publish-ability
description: Publish a skill, scene, MCP server, plugin, or bundle to the Vetta ability marketplace. Use when the user asks to upload/publish/submit an ability, put a plugin on the marketplace, share a skill with other users, or migrate an existing package into Vetta.
metadata:
  version: 2.1.0
  author: Vetta
  category: 开发
---

# Publish an Ability to the Vetta Marketplace

Submitting is a **local** action: it reads an archive from the user's disk, so it runs through
the bundled script rather than a tool call.

```bash
node "$SKILL_DIR/scripts/publish.mjs" --input /abs/path/to/payload.json
```

Checking review status afterwards is a **remote** action — use the `list_my_abilities` tool from
the built-in `vetta` MCP server. It is always available and needs no setup.

## Procedure

1. **Determine the type.** Ask the user if it is not obvious from the artifact.

   | Type | What it is | Physical artifact |
   | --- | --- | --- |
   | `skill` | A `SKILL.md` workflow | `.zip` / `.tar.gz` containing `SKILL.md` |
   | `scene` | A skill installed into the scene directory | same as `skill` |
   | `plugin` | A Vetta plugin | `.zip` containing `plugin.json` |
   | `mcp` | One entry in the user's `mcp.json` | none — config only |
   | `bundle` | A named group of already-published abilities | none — references only |

2. **Source `detail` from the package. Do not invent it.** `name`, `description`, `author`, and
   `content` are all required and the submission is rejected without them. Read `plugin.json`
   (`name`/`description`/`author`), `SKILL.md` frontmatter, `README.md`, `LICENSE`. Ask the user
   for anything you cannot source.

   For plugins, also list `locales/*.json`: those filenames are the exact locale keys your
   `detail.i18n` must use, and whatever they already translate is text you should not restate.
   See [Multi-language](references/payload.md#multi-language).

   `content` is the detail-page body in markdown — it is what a user reads to decide whether to
   install. Write what the ability does, when to use it, and any setup it needs. A one-line
   `content` is not acceptable.

3. **Read the available categories before you set one.** `category` is matched by **name** —
   a name that matches nothing is not an error, it silently lands in "uncategorised". So never
   invent one:

   ```bash
   node "$SKILL_DIR/scripts/categories.mjs"          # human-readable
   node "$SKILL_DIR/scripts/categories.mjs" --json   # machine-readable
   ```

   Put the `name` field verbatim into the payload's `category`. The `i18n` values are display
   translations only — matching accepts them, but the canonical name is what you should write.
   Omit `category` entirely if nothing fits.

4. **Write the payload to a JSON file**, then pass it with `--input`. Never try to pass fields as
   command-line arguments: `content` is multi-line markdown containing backticks, quotes and `$`,
   and the shell will mangle it.

   Full field reference: [references/payload.md](references/payload.md) — read it when you need
   anything beyond the four required `detail` fields. It is also where the **valid icon names**
   and the meaning of each `showcase.canvas` value are listed; both are closed sets, and a wrong
   value either fails validation or renders as a fallback.

5. **Dry-run first** when the payload is non-trivial. It runs the full validation without
   submitting, so you spend no network round-trip on a payload that was never going to pass:

   ```bash
   node "$SKILL_DIR/scripts/publish.mjs" --input payload.json --dry-run
   ```

   For `skill`/`scene`/`plugin` this also **opens the archive** and cross-checks the payload
   against `plugin.json`, `locales/*.json`, and `SKILL.md` frontmatter. That is the only place
   the "same data, two sources" mistakes surface: a translation key that does not match the
   package's own locale files, a `slug` or `version` that the server will ignore, a `vetta.json`
   that your `detail` silently supersedes. None of these fail the upload — they just make part
   of what you wrote unreachable. Do not skip the dry-run on packaged types.

6. **Submit.** On failure the script returns *all* problems at once — fix the whole list before
   retrying rather than resubmitting after each single fix.

## Script contract

- **stdout is a single JSON object**; exit code 0 = success, 1 = failure.
- Failure shape: `{"ok": false, "message": "...", "errors": [...]}` — `errors` is present for
  validation failures.
- Success shape: `{"ok": true, "message": "...", "slug": "...", "version": "...",
  "review_status": "...", "has_pending": bool, "warnings": [...]}`.
- `warnings` are things that did not block the submission but changed what gets published —
  a hand-written translation overriding the package's own, a `slug` that was ignored, a
  `vetta.json` that was skipped. Relay them to the user; do not treat the run as clean.
- Input may also arrive on stdin (`cat payload.json | node .../publish.mjs`) if that is more
  convenient than a temp file.
- The script reads the login token from `~/.vetta/auth.json` itself. If it reports "未登录", tell
  the user to log in through the Vetta client — do not attempt to pass credentials yourself.

## Review

Submissions from non-administrators enter a review queue and are **not** visible in the
marketplace until an administrator approves them. Tell the user this rather than implying the
ability is live.

Re-submitting an ability that is already published does not disturb it: the new version waits in
a pending slot while the marketplace keeps serving the current version, so installed users are
unaffected until the update is approved. `has_pending: true` in the result means exactly this.

**A re-submission replaces `detail.i18n` wholesale.** Sending only the locale you changed drops
every other translation on that entry, including any an administrator added later. Fetch the
current state first (`list_my_abilities`) or resend the complete set of locales.

You may only submit updates to abilities you own. Submitting to a slug owned by someone else is
rejected.

## Verify

After a successful run, state the returned `slug`, `version`, and whether the ability is live or
pending review. If the run failed, fix every listed error and retry — do not report success.

Use `list_my_abilities` to report progress later: `review_status` is `pending`, `approved`, or
`rejected` (reason in `review_note`); a non-empty `pending_version` means an update is queued
behind the live version.
