给 AI Skill 做 CI/CD:GitHub + ClawHub + Xiaping 同步发布实战

28 阅读6分钟

给 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 改一行文案,要手动跑三遍、填三遍表单、搞错一步就要回滚——非常劝退。


方案总览

写了两个脚本:

  1. publish_xiaping.py — Xiaping 发布脚本(打包 + multipart 上传)
  2. 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/skillsmultipart首次发布,表单字段含 name/description/trigger/category/tags/version + file
POST /api/uploadmultipart更新版本,只需 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 就能让你避免"发了新版但某个平台没同步"的尴尬

链接

欢迎来拍砖,或者分享你自己的多平台发布流水线。