Install
openclaw skills install @readysoon/multi-channel-skill-publisherPublish ONE skill to ALL agent-skill channels from a single source of truth: the ClawHub skill registry, a ClawHub bundle-plugin package, and a Claude Code plugin marketplace (plain GitHub repo). Use when the user wants to publish, release, rename, or version-bump a skill/plugin across registries, or asks how to package a skill as a Claude Code plugin or OpenClaw bundle plugin. Covers the SSOT/sync model against redundancy, exact manifests (plugin.json, marketplace.json, openclaw.plugin.json), known CLI bugs and their web-UI workarounds (rename, --name, display-name fallback), versioning pitfalls (permanent versions, highest-version-wins display name, web-UI version drift), and publish gotchas (version must exceed live on BOTH namespaces, push before source-linked package publish, name-driven search).
openclaw skills install @readysoon/multi-channel-skill-publisherRelease one skill everywhere — without maintaining three diverging copies.
| Channel | What it is | Publish via |
|---|---|---|
| ClawHub Skill | searchable skill registry entry | web UI (most reliable) or clawhub publish <dir> --slug <slug> |
| ClawHub Package (bundle-plugin) | plugin package, source-linked to a GitHub commit | clawhub package publish <abs-path> --family bundle-plugin … |
| Claude Code plugin | installable via /plugin from a plain GitHub repo | just git push — the repo IS the marketplace |
Never hand-maintain copies. Canonical files live once, everything else is generated:
repo/
SKILL.md <- SSOT (canonical skill text; YAML frontmatter: name + description)
skill.json <- SSOT (registry metadata; THE version number lives here)
sync.py <- copies SSOT into every target + stamps the version everywhere
clawhub-skill/ <- generated copy for clawhub publish
plugin/ <- the ONE plugin folder, served to BOTH plugin channels
.claude-plugin/plugin.json <- Claude Code manifest (name, displayName, version, …)
openclaw.plugin.json <- OpenClaw manifest, REQUIRED for bundle-plugin publish:
{"id": "...", "name": "...", "version": "...", "configSchema": {}}
skills/<name>/SKILL.md <- generated from SSOT
commands/<name>.md <- optional slash command ($ARGUMENTS)
.claude-plugin/marketplace.json <- makes the repo its own Claude Code marketplace:
{"name": "...", "owner": {...}, "plugins": [{"name": "...", "source": "./plugin"}]}
sync.py (concept): read version from skill.json, copy SKILL.md into clawhub-skill/ and plugin/skills/<name>/, stamp the version into plugin.json, marketplace.json, openclaw.plugin.json. Run it after EVERY edit of the SSOT files.
Make sync.py also run a version preflight: query the live version on both namespaces (clawhub inspect <slug> + clawhub package inspect <name>, parse Latest: x.y.z) and refuse to sync unless skill.json is strictly higher. That turns two whole gotcha classes ("version already exists", web-UI drift) into a hard error before anything reaches the registry. Known blind spot: a just-published version still in the moderation scan is invisible to inspect on BOTH namespaces — the preflight can't see it, and republishing the same number fails at the registry instead.
clawhub inspect <skill-slug> # skill: Latest: x.y.z
clawhub package explore <plugin-name> # package: [Bundle Plugin] version
python3 sync.pygit add -A && git commit -m "Version -> <v>" && git push
git rev-parse HEAD
gh repo create <owner>/<repo> --public --source . --pushclawhub publish "$(pwd)/clawhub-skill" --slug <skill-slug> --name "<Display Name>" --version <v>
clawhub package publish "$(pwd)/plugin" \
--family bundle-plugin \
--name <plugin-name> --display-name <DisplayName> \
--version <v> \
--host-targets claude-code \
--source-repo <owner>/<repo> \
--source-commit <HEAD-SHA> \
--source-ref refs/heads/main \
--source-path plugin
/plugin marketplace add <owner>/<repo> → /plugin install <plugin-name>@<marketplace-name>.clawhub inspect <skill-slug> # Latest == <v>
clawhub package explore <plugin-name> # Bundle Plugin == <v>
Right after publishing, clawhub inspect returns "Skill is hidden by moderation (pending.publication)" until the security scan clears — transient at first, NOT a failed publish. Poll every ~30 s instead of republishing. Similarly, clawhub package inspect <name> may say "Package not found" while package explore already lists it (indexing/moderation lag) — trust explore for existence.
If it STAYS hidden: your content likely tripped the moderation scanner. clawhub unhide <slug> --yes does NOT work — moderation hides are Forbidden for the owner ("contact a moderator"). Self-serve fix: clean the flagged content, bump, and publish a higher version; escalate to a moderator only if that doesn't surface it. Meanwhile the profile page shows the skill missing under "Skills" while the plugin twin is visible under "Plugins" — that asymmetry is the tell that moderation, not the publish, is the problem.
clawhub rename is broken: it errors newSlug required even when the new slug IS provided. Rename via the web UI instead. And after a rename, CLI publish may still only match the original slug — another reason the web UI is the safer skill-publish path.--name on clawhub publish (skill flow) can throw a server error — observed on EXISTING/renamed skills; on a fresh first publish (CLI v0.9.0) it worked fine. If it errors: omitting it makes the display name fall back to the folder name (you get a skill literally called "Clawhub Skill"); fix by typing the display name exactly into the web UI name field.clawhub publish (skill) and clawhub package publish error with Path must be a folder on a relative path. Always pass "$(pwd)/<dir>".inspect, package inspect) carry the hint too — the (reset in Ns) suffix tells you how long to wait.known-error-fixes-database), not only the description.clawhub inspect <name> may hit the skill entry — use clawhub package inspect / package explore for packages.--family code-plugin requires a package.json (it means a Node/code plugin, e.g. an MCP server). A skill bundle is --family bundle-plugin.bundle-plugin requires openclaw.plugin.json at the plugin root — minimal valid manifest: {"id", "name", "version", "configSchema": {}}. hostTargets/format do NOT go in the manifest; they are CLI flags (--host-targets, format auto-detects claude from .claude-plugin/plugin.json).git push is all a "release" takes on that channel.clawhub inspect <skill-slug> + clawhub package explore <plugin-name>). The sync.py preflight automates exactly this check.skill.json; the next repo-driven release then fails as "too low" or silently ships an older number. After ANY web-UI publish, write the live version back into the SSOT skill.json and run sync.py — its preflight flags the drift (live > local) as a hard error.skill.json, plugin.json, marketplace.json, and openclaw.plugin.json — hand-edited manifests drift apart within a release or two.