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.
Resources
3Install
npx skillscat add hwy-game/metoai/publish-ability Install via the SkillsCat registry.
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.
node "$SKILL_DIR/scripts/publish.mjs" --input /abs/path/to/payload.jsonChecking 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
Determine the type. Ask the user if it is not obvious from the artifact.
Type What it is Physical artifact skillA SKILL.mdworkflow.zip/.tar.gzcontainingSKILL.mdsceneA skill installed into the scene directory same as skillpluginA Vetta plugin .zipcontainingplugin.jsonmcpOne entry in the user's mcp.jsonnone — config only bundleA named group of already-published abilities none — references only Source
detailfrom the package. Do not invent it.name,description,author, andcontentare all required and the submission is rejected without them. Readplugin.json
(name/description/author),SKILL.mdfrontmatter,README.md,LICENSE. Ask the user
for anything you cannot source.For plugins, also list
locales/*.json: those filenames are the exact locale keys yourdetail.i18nmust use, and whatever they already translate is text you should not restate.
See Multi-language.contentis 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-linecontentis not acceptable.Read the available categories before you set one.
categoryis matched by name —
a name that matches nothing is not an error, it silently lands in "uncategorised". So never
invent one:node "$SKILL_DIR/scripts/categories.mjs" # human-readable node "$SKILL_DIR/scripts/categories.mjs" --json # machine-readablePut the
namefield verbatim into the payload'scategory. Thei18nvalues are display
translations only — matching accepts them, but the canonical name is what you should write.
Omitcategoryentirely if nothing fits.Write the payload to a JSON file, then pass it with
--input. Never try to pass fields as
command-line arguments:contentis multi-line markdown containing backticks, quotes and$,
and the shell will mangle it.Full field reference: references/payload.md — read it when you need
anything beyond the four requireddetailfields. It is also where the valid icon names
and the meaning of eachshowcase.canvasvalue are listed; both are closed sets, and a wrong
value either fails validation or renders as a fallback.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:node "$SKILL_DIR/scripts/publish.mjs" --input payload.json --dry-runFor
skill/scene/pluginthis also opens the archive and cross-checks the payload
againstplugin.json,locales/*.json, andSKILL.mdfrontmatter. 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, aslugorversionthat the server will ignore, avetta.json
that yourdetailsilently 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.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": [...]}—errorsis present for
validation failures. - Success shape:
{"ok": true, "message": "...", "slug": "...", "version": "...", "review_status": "...", "has_pending": bool, "warnings": [...]}. warningsare things that did not block the submission but changed what gets published —
a hand-written translation overriding the package's own, aslugthat was ignored, avetta.jsonthat 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.jsonitself. 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, orrejected (reason in review_note); a non-empty pending_version means an update is queued
behind the live version.