skill-release-plus
Version: 1.1.0
Author: Evan Song <github.com/Songhonglei>
Repo: https://github.com/Songhonglei/build-better-skills/tree/main/skills/skill-release-plus
License: MIT (this repo) — clawhub mirror auto-uses MIT-0 (platform-enforced)
Part of: build-better-skills suite — see Stages for the lifecycle map.
Sign → Pack → Publish to multiple skill hubs in one command. Supports
clawhub.com, skillhub.cn (Tencent Cloud), GitHub Releases, and any
custom hub via a user-hook script.
What's new in 1.1.0
- Sign mode auto-detect: signing is now optional and automatic — publish
unsigned out-of-the-box (zero setup); drop a private key via
--init-sign-keyand every publish signs automatically. Explicit overrides:--sign(force) /--no-sign(force off). - One file-set baseline for signing & packaging: signing runs against a
clean staged copy of exactly what gets shipped, so
sign.key'scontent_hashnever drifts from the package content (the classic "Tampering detected" false alarm is gone). Unsigned mode drops stalesign.keyfiles from source instead of shipping outdated signatures. --package-only: build a deterministic, credential-free ZIP for governance / controlled-release flows — strict version check against SKILL.md frontmatter, symlink rejection, fixed timestamps and permission bits, per-file sha256 manifest, package-level sha256. No network, no tokens, no source mutation. Requires--expected-version.
Quick start
# Default: publish to clawhub only
python3 scripts/release.py --slug my-skill -m "first release" --version 1.0.0
# Publish to all three real hubs at once
python3 scripts/release.py --slug my-skill -m "ship it" --version 1.0.0 --target all
# Custom hub via user hook
python3 scripts/release.py --slug my-skill -m "v1" --version 1.0.0 \
--target user-hook:/path/to/my-publish.sh
# Check token readiness before publishing
python3 scripts/release.py --check --target all
# Dry-run (sign + pack, skip publish)
python3 scripts/release.py --slug my-skill -m "test" --version 1.0.0 --dry-run
# Inspect current exclude rules
python3 scripts/release.py --show-exclude
# Generate the signing private key (then every publish signs automatically)
python3 scripts/release.py --init-sign-key
# Publish without signing (force unsigned)
python3 scripts/release.py --slug my-skill -m "v1" --version 1.0.0 --no-sign
# Deterministic governance ZIP: no network, no tokens, no source mutation
python3 scripts/release.py --package-only --slug my-skill \
--expected-version 1.0.0 --skill-dir ./my-skill
Supported targets
| Target | Status | Auth | Notes |
|---|---|---|---|
clawhub |
✅ | SRP_CLAWHUB_TOKEN (env) or clawhub login |
Wraps the clawhub CLI |
skillhub-cn |
✅ | SRP_SKILLHUB_CN_TOKEN (env) |
Tencent Cloud — POST /api/v1/community/skills/publish (multipart). Rate-limit 3 publish/min/token. Auto-retries on 429. |
github-release |
✅ | SRP_GITHUB_TOKEN (env) |
Pure git + GitHub REST API (no gh CLI required). Supports owner/repo[/subdir] for monorepo layouts. |
user-hook:<path> |
✅ | Hook-defined | Your script receives metadata via env vars + JSON stdin, returns {"ok": true, "url": "..."} on stdout |
Not supported:
cn.clawhub-mirror.com(ByteDance Volcano) is a read-only public mirror of clawhub.com — publishing to clawhub automatically syncs there. There is no separate publish endpoint.
Installation
# Install Python 3.8+ (standard library only, no pip install needed)
git clone https://github.com/Songhonglei/build-better-skills.git
cd build-better-skills/skills/skill-release-plus
# Or install via clawhub:
clawhub install skill-release-plus
Dependencies:
- Python 3.8+ (standard library only —
urllib,subprocess,tarfile,json,argparse,dataclasses,pathlib) git2.x (forgithub-releasetarget only)clawhubCLI (forclawhubtarget only — auto-detected from$PATH)skill-sign(optional — v1.1.0 auto-detects: unsigned when absent, signed when a private key exists)PyYAML(optional — improves SKILL.md frontmatter parsing for edge cases; stdlib fallback handlesdescription: "..."single-line anddescription: >folded scalar without it)
Configuration
Resolution priority (highest → lowest):
- CLI args (
--slug,--target,--version, etc.) - Process env vars (
SRP_*private namespace) - Project-local
./.env - Global
~/.config/skill-release-plus/.env - Missing required key → onboarding prompt (interactive)
Environment variables (all SRP_* prefixed)
| Variable | Required for | Description |
|---|---|---|
SRP_CLAWHUB_TOKEN |
clawhub target | clawhub API token (or run clawhub login) |
SRP_SKILLHUB_CN_TOKEN |
skillhub-cn target | Tencent skillhub.cn personal token (starts with skh_) |
SRP_GITHUB_TOKEN |
github-release target | GitHub PAT with repo scope |
SRP_GITHUB_OWNER |
github-release (optional) | Default repo owner (defaults to authenticated user) |
SRP_GITHUB_REPO |
github-release (optional) | Default repo name (defaults to slug) |
SRP_GITHUB_BRANCH |
github-release (optional) | Default branch (default: main) |
SRP_GITHUB_BASIC_AUTH_HEADER |
github-release (optional) | If true, use HTTP Basic Auth header instead of URL-embedded token |
SRP_DEFAULT_TARGETS |
optional | Comma-separated default target list (default: clawhub) |
SRP_OUTPUT_DIR |
optional | Package output directory (default: <cwd>/output/skill-release) |
SRP_SIGN_OPTIONAL |
optional | If true, skip signing when skill-sign not installed (default: true) |
SRP_SIGN_SCRIPT |
optional | Explicit path to sign.py (overrides auto-detection of skill-sign) |
Architecture
release.py (dispatcher, 352 LOC)
├─ _lib_config.py (4-layer config + parse_targets, 289 LOC)
├─ _lib_package.py (sign / pack / exclude rules / multipart, 305 LOC)
├─ _lib_adapters_base.py (BaseAdapter ABC, 98 LOC)
└─ _adapter_*.py:
├─ clawhub (CLI wrap, 185 LOC)
├─ github-release (git + GitHub REST API, 392 LOC)
├─ skillhub-cn (Tencent Cloud HTTP/multipart, 308 LOC)
└─ user-hook (subprocess + JSON envelope, 140 LOC)
Design principles:
- Single source of truth: One
config/exclude.json, one signing pass, one tar.gz, fan out to N targets - Adapter independence: Each adapter is a separate Python module subclassing
BaseAdapter; no cross-adapter coupling - Fail-open by default: One target failing does not block others (dispatcher reports per-target status)
- Protocol-agnostic base: BaseAdapter doesn't assume HTTP / multipart / git — adapters own their protocol
Writing a custom adapter (user-hook)
Create an executable script:
#!/bin/bash
# my-publisher.sh
set -e
# Hook receives:
# $SRP_HOOK_SLUG, $SRP_HOOK_VERSION, $SRP_HOOK_CHANGELOG,
# $SRP_HOOK_TAR_PATH, $SRP_HOOK_SKILL_DIR, $SRP_HOOK_DISPLAY_NAME
curl -sf -F "package=@$SRP_HOOK_TAR_PATH" \
-F "version=$SRP_HOOK_VERSION" \
"https://my-private-hub.example.com/publish"
# Print JSON envelope on stdout for the dispatcher to parse
echo '{"ok": true, "url": "https://my-private-hub.example.com/skills/'"$SRP_HOOK_SLUG"'"}'
Then invoke:
chmod +x my-publisher.sh
python3 scripts/release.py --slug my-skill -m "v1" --version 1.0.0 \
--target user-hook:./my-publisher.sh
Exclude rules
Default config/exclude.json strips:
- VCS:
.git,.svn,.hg - IDE:
.idea,.vscode,.DS_Store,Thumbs.db - Build artifacts:
__pycache__,*.pyc,node_modules,dist,build - Secrets:
.env,.secrets,*.pem,*.key,.token - Test/dev:
tests/__pycache__,.pytest_cache,.tox - See full list:
python3 scripts/release.py --show-exclude
Command reference
usage: release.py [-h] [--slug SLUG] [--changelog CHANGELOG]
[--version VERSION] [--display-name DISPLAY_NAME]
[--skill-dir SKILL_DIR] [--target TARGET] [--dry-run]
[--show-exclude] [--check] [--sign] [--no-sign]
[--init-sign-key] [--package-only]
[--expected-version EXPECTED_VERSION] [--output-dir OUTPUT_DIR]
options:
-h, --help show this help message and exit
--slug SLUG skill slug (lowercase, digits, hyphens)
--changelog, -m release changelog
--version VERSION explicit version (semver); default: auto-bump patch
--display-name human-readable display name (default: slug)
--skill-dir SKILL_DIR path to skill source directory (default: ./<slug>)
--target TARGET comma-separated targets, or "all". Default: clawhub.
Special: user-hook:<path> for custom hooks.
--dry-run sign + pack but skip publish
--show-exclude print current exclude rules and exit
--check check token readiness for selected targets
--sign force signing (requires an existing private key;
run --init-sign-key first)
--no-sign force unsigned publish (skip signing; package has
no sign.key)
--init-sign-key generate the signing private key
(~/.openclaw/workspace/.sign-key) and exit
--package-only build a deterministic unsigned ZIP only; no
credentials, no network, no source mutation
--expected-version EXPECTED_VERSION
required by --package-only; must exactly match the
SKILL.md frontmatter version
--output-dir OUTPUT_DIR
package output directory (default: ./output/skill-release)
Signing behavior (v1.1.0)
| Situation | What happens |
|---|---|
| No private key (default for new users) | Publishes unsigned; zero setup needed |
Private key present (--init-sign-key created one) |
Every publish signs automatically |
--sign |
Forces signing; errors out if no usable key |
--no-sign |
Forces unsigned, even with a key present |
| Key file corrupt | Hard error (never silently downgrade); fix or delete it, or pass --no-sign |
Signature integrity: signing runs against a clean staged copy that matches
the shipped package exactly, so content_hash in sign.key always matches
the files installers verify. In unsigned mode, any stale sign.key left in
the source directory is dropped from the package (old signatures cover old
content and only trigger false tampering alarms).
Exit codes:
0— all targets succeeded1— at least one target failed (others may have succeeded)2— invalid arguments or config error
Triggers
This skill should be used when the user says:
- "publish skill X to clawhub / skillhub.cn / GitHub"
- "ship my skill to multiple hubs"
- "release X.Y.Z of my skill"
- "发布 skill 到 clawhub"
- "把 skill 推到 skillhub.cn"
- "skill 同时发到三个 hub"
- "用 user-hook 发到我自己的 hub"
Limitations
skillhub-cnrate limit: 3 publish/minute/token. Dispatcher waits and retries on 429.github-releaserequires explicit--version: No auto-tag-bump (would require parsing existing tags reliably across owner/repo combos).skillhub-cnrequiresdescriptionin SKILL.md frontmatter (≤1024 bytes). YAML folded (>) scalar is supported.- No rollback: If 2/3 targets succeed and 1 fails, you must manually clean up the 2 successes or accept partial state.
License
MIT (this repo). See LICENSE.
Note: when this skill is published to clawhub.com, the platform enforces MIT-0 license metadata on hub mirror — original repo MIT license is unchanged.
