给 AI Skill 做 CI/CD:GitHub + ClawHub + Xiaping 同步发布实战
把同一个 skill 手动发到 3 个平台 3 次之后我疯了,于是写了一套自动发布 + 三平台健康巡检脚本。这篇把代码、API 端点、踩过的 7 个坑都放出来。
背景
我做了一个叫 zhuge-skill 的 Claude/AI Agent 技能(六十四卦推演 + 足球预测),要同时发布到三个地方:
| 平台 | 定位 | 入口 |
|---|---|---|
| GitHub | 源码 + release tag,是另外两个平台同步的"真源" | git push + gh release |
| ClawHub (clawhub.ai) | OpenClaw 生态的 Skill 市场,从 GitHub tag 同步 | clawhub publish CLI |
| Xiaping (xiaping.coze.site) | Coze 子站的 Skill 市场,接受 ZIP 上传 | 私有 REST API + API key |
每个平台的发布方式都不一样,流程、验证、版本控制、甚至文件编码风格都各有讲究。一个 skill 改一行文案,要手动跑三遍、填三遍表单、搞错一步就要回滚——非常劝退。
方案总览
写了两个脚本:
publish_xiaping.py— Xiaping 发布脚本(打包 + multipart 上传)skill_health.py— 三平台健康巡检(版本一致性、扫描状态、评测数)
再加上 clawhub CLI 和 gh CLI,一条链路走完:
代码改动 → git commit → git push
→ gh release create v1.0.x
→ clawhub publish ... --version 1.0.x
→ python publish_xiaping.py --update <skill_id> --version 1.0.x
→ python skill_health.py # 验证三平台同步
之前我给每个动作起草单独脚本,后来发现合并成一条 Makefile target 最清爽。
Xiaping 发布脚本(含关键 API 端点)
Xiaping 没有公开 CLI,得自己走 REST。以下是我摸索出的三个核心端点(官方文档没收录,都是从浏览器开发者工具 + 盲探拿到的):
| 端点 | 方法 | 说明 |
|---|---|---|
POST /api/skills | multipart | 首次发布,表单字段含 name/description/trigger/category/tags/version + file |
POST /api/upload | multipart | 更新版本,只需 skill_id + changelog + version + file |
GET /api/skills/<id> | — | 拿 skill meta(含 current_version/security_status/avg_stars/review_task) |
认证:Authorization: Bearer <AGENT_WORLD_API_KEY>(前缀 agent-world-xxx)。
关键代码
打包函数最容易踩坑(Windows 路径分隔符问题,见下文踩坑 #1):
def pack_skill_zip(skill_dir: Path) -> bytes:
"""Pack skill folder with Linux-compatible paths."""
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
for root, dirs, files in os.walk(skill_dir):
if "__pycache__" in root or ".git" in root:
continue
for fn in files:
if fn.endswith((".pyc", ".DS_Store")):
continue
full = Path(root) / fn
# 关键:强制把路径分隔符规范为 /,标记为 Unix 系统
arcname = (skill_dir.name + "/" +
str(full.relative_to(skill_dir))).replace("\\", "/")
z.write(full, arcname)
zi = z.getinfo(arcname)
zi.create_system = 3 # 3 = Unix
return buf.getvalue()
更新函数(别忘传 version 字段,见踩坑 #3):
def update_skill(api_key, skill_id: str, skill_zip: bytes,
changelog: str, version: str):
files = {"file": ("skill.zip", skill_zip, "application/zip")}
data = {"skill_id": skill_id, "changelog": changelog, "version": version}
headers = {"Authorization": f"Bearer {api_key}"}
r = requests.post(
"https://xiaping.coze.site/api/upload",
headers=headers, files=files, data=data, timeout=60,
)
print(f"HTTP {r.status_code}")
print(json.dumps(r.json(), ensure_ascii=False, indent=2))
return r
踩过的 7 个坑
坑 1:Windows 路径分隔符 → Xiaping 服务器解压失败
在 Windows 上用 zipfile.ZipFile.write() 不做处理的话,zip 条目里的路径分隔符会是反斜杠 \。Xiaping 后端跑 Linux unzip,遇到这种 zip 会直接报错:
warning: xxx.zip appears to use backslashes as path separators
Command failed: unzip -o ...
修复:在 write 时手动 .replace("\\", "/"),并把每个 ZipInfo.create_system = 3(标记为 Unix)。
坑 2:ClawHub 发布时"假装"达到 GitHub rate limit
第一次在 ClawHub 发布时报错"GitHub API rate limit exceeded",但查 rate limit 配额明明还剩 179/180。真正的原因是 ClawHub 在查 GitHub tag 时找不到——它会把任何 GitHub 查询失败都渲染成 "rate limit"。
修复:先 gh release create v<version> 创建 tag,再跑 clawhub publish,就不会报这个错。
坑 3:Xiaping /api/upload 不传 version 会自动递增
最开始的脚本只传了 skill_id + changelog,服务器自动把版本从 0.1.3 递增到 0.1.4,完全忽略我想发的 1.0.1。
修复:在 data 字典里显式加 "version": version。服务器会诚实接受。
坑 4:ClawHub 的安全扫描把"文档自相矛盾"标为可疑
我的 SKILL.md 还残留着旧版本的"晶体共享池"描述(会推送数据到外部),而 PRIVACY.md 已经写明"v1.0.1 起 push 函数已移除,只读拉取"。审核员发现两份文档打架,直接把 skill 标记为"可疑·中等置信度"。
修复:写个 grep 自查命令,确保所有文档对"有没有外发数据"这件事口径一致。顺便把不一致的内容统一重写,升个小版本(v1.0.2)重发。
# 每次发版前跑一遍
grep -rn "requests.post\|urlopen.*POST\|upload" scripts/ core/
坑 5:ClawHub LICENSE 文件不能自带
ClawHub 全站强制 MIT-0,你自己放 LICENSE 文件会被拒绝打包。但 GitHub 那边又需要 LICENSE 做合规。
修复:.clawhubignore 把 LICENSE 屏蔽(或者像我一样在打包脚本里过滤)。GitHub 仓库根目录照常放 LICENSE。
坑 6:SKILL.md 的 tags 字段格式
ClawHub 要求逗号分隔("a,b,c"),Xiaping 要求 JSON 数组(["a","b","c"])。
修复:发布脚本里按平台序列化:
# ClawHub CLI
"--tags", "ai-agent,prediction,football"
# Xiaping API
data["tags"] = json.dumps(["ai-agent", "prediction", "football"])
坑 7:Xiaping 首次扫描失败后不会自动重扫
这是最坑的一个。因为坑 1 导致我的第一次上传(0.1.0)扫描失败,平台把 security_status 定格在 warning 状态。后续发了 v1.0.1/1.0.2/1.0.3 五六个版本,所有新版本 zip 都已经修复了路径问题,但平台的扫描状态从不更新——它只扫第一次。
探了 20 个候选 rescan 端点全 404,又问了 Coze 母平台的 AI 客服也没答案。结论是 Xiaping 没有公开的重扫机制。
修复:接受现实。warning 是软标记(不是 unsafe/blocked),下载仍然正常工作。靠真实评测和下载把 skill 从 trial 推到 approved 之后,这个标记的影响就可以忽略。
健康巡检脚本
三平台发布完跑一次 skill_health.py,输出长这样:
╔══════════════════════════════════════════════════════════╗
║ Skill Health Report · zhuge-skill ║
╚══════════════════════════════════════════════════════════╝
[✓] GitHub yangfei222666-9/zhuge-skill
latest release : v1.0.3 (just now)
commit age : 2m ago
stars / issues : 0 / 0
[✓] ClawHub @yangfei222666-9/zhuge-skill
latest version : 1.0.3
updated : 2026-04-17T07:47:24Z
[⚠] Xiaping ce55308c...
current version: 1.0.3 (8 total)
avg stars : 4.0/5 (1 comments)
security_scan : [⚠] warning (scanned 4h ago)
↳ report is for v0.1.0 — likely stale
review_task : (none — 尚未进入正式评测队列)
版本一致性: github=1.0.3 · clawhub=1.0.3 · xiaping=1.0.3 ✓
核心逻辑很简单——各平台拉一次 meta,在终端拼成一张表。有两个可以复用的点:
自动检测 Xiaping 扫描报告过期:
m = re.search(r"-(\d+\.\d+\.\d+)\.zip", meta.get("security_report", ""))
if m and m.group(1) != meta.get("current_version"):
flag(f"stale scan: report is for {m.group(1)}, current is {meta['current_version']}")
Windows 下调用 clawhub 要 shell=True(因为 clawhub 是 .cmd wrapper):
subprocess.check_output(
f"clawhub inspect {slug}",
shell=True, text=True, encoding="utf-8", timeout=20,
)
小结
- 把三平台看作同一条流水线,不要当 3 个独立 CI。共享一个打包函数、共享一份 metadata,每个平台只在"最后一公里"做差异化
- ZIP 必须用 Linux-style 路径 + Unix system flag,跨平台最隐蔽的坑
- 先 tag 再 publish,所有挂 GitHub 的二级平台都是这个顺序
- 文档口径统一比"写得漂亮"更重要,尤其是涉及隐私/网络行为的声明,审核员真的会细读
- 健康巡检脚本的 ROI 非常高,几十行 Python 就能让你避免"发了新版但某个平台没同步"的尴尬
链接
- 项目:github.com/yangfei2226…
- 已在 ClawHub 和 Xiaping 同步发布(搜
zhuge-skill)
欢迎来拍砖,或者分享你自己的多平台发布流水线。