name: Reusable Skill Release on: workflow_call: inputs: artifact_platform: required: false type: string default: windows pyarmor_platform: required: false type: string default: windows.x86_64 upload_url: required: false type: string default: https://jc2009.com/api/upload sync_url: required: false type: string default: https://jc2009.com/api/skill/update prune_url: required: false type: string default: https://jc2009.com/api/artifacts/prune-old-versions jobs: build-and-deploy: runs-on: ubuntu-latest env: ARTIFACT_PLATFORM: ${{ inputs.artifact_platform }} PYARMOR_PLATFORM: ${{ inputs.pyarmor_platform }} PIP_BREAK_SYSTEM_PACKAGES: "1" steps: - uses: http://120.25.191.12:3000/admin/actions-checkout@v4 # Pin PyArmor 8.5.3 — matches desktop python_env; PyArmor 9.x trial often fails on multi-file gen. - name: Setup Tools run: pip install "pyarmor==8.5.3" requests python-frontmatter --break-system-packages -i https://pypi.tuna.tsinghua.edu.cn/simple - name: Encrypt Source Code run: | mkdir -p dist/package pyarmor gen --platform "${PYARMOR_PLATFORM}" -O dist/package scripts/*.py cp SKILL.md dist/package/ - name: Parse Metadata and Pack id: build_task run: | python -c " import frontmatter, os, json, shutil post = frontmatter.load('SKILL.md') metadata = dict(post.metadata or {}) metadata['readme_md'] = (post.content or '').strip() openclaw_meta = metadata.get('metadata', {}).get('openclaw', {}) slug = (openclaw_meta.get('slug') or metadata.get('slug') or metadata.get('name') or '').strip() if not slug: raise Exception('SKILL.md 缺少 slug/name') ref_name = (os.environ.get('GITHUB_REF_NAME') or '').strip() if not ref_name.startswith('v'): raise Exception(f'非法标签: {ref_name}') version = ref_name.lstrip('v') metadata['version'] = version artifact_platform = (os.environ.get('ARTIFACT_PLATFORM') or 'windows').strip() zip_base = f'{slug}-{artifact_platform}' with open(os.environ['GITHUB_OUTPUT'], 'a') as f: f.write(f'slug={slug}\n') f.write(f'version={version}\n') f.write(f'zip_base={zip_base}\n') f.write(f'artifact_platform={artifact_platform}\n') f.write(f'metadata={json.dumps(metadata, ensure_ascii=False)}\n') shutil.make_archive(zip_base, 'zip', 'dist/package') " - name: Sync Database env: METADATA_JSON: ${{ steps.build_task.outputs.metadata }} SYNC_URL: ${{ inputs.sync_url }} run: | python -c " import requests, json, os metadata = json.loads(os.environ['METADATA_JSON']) res = requests.post(os.environ['SYNC_URL'], json=metadata) if res.status_code != 200: exit(1) body = res.json() if body.get('code') != 200: exit(1) " - name: Upload Encrypted ZIP env: SLUG: ${{ steps.build_task.outputs.slug }} VERSION: ${{ steps.build_task.outputs.version }} ZIP_BASE: ${{ steps.build_task.outputs.zip_base }} ARTIFACT_PLATFORM: ${{ steps.build_task.outputs.artifact_platform }} UPLOAD_URL: ${{ inputs.upload_url }} run: | python -c " import requests, os slug = os.environ['SLUG'] version = os.environ['VERSION'] zip_path = f\"{os.environ['ZIP_BASE']}.zip\" payload = { 'plugin_name': slug, 'version': version, 'artifact_type': 'skill', 'artifact_platform': os.environ.get('ARTIFACT_PLATFORM', 'windows'), } filename = os.path.basename(zip_path) with open(zip_path, 'rb') as f: res = requests.post(os.environ['UPLOAD_URL'], data=payload, files={'file': (filename, f)}) if res.status_code != 200: exit(1) body = res.json() if body.get('code') != 200: exit(1) " - name: Prune Old Versions env: SLUG: ${{ steps.build_task.outputs.slug }} VERSION: ${{ steps.build_task.outputs.version }} PRUNE_URL: ${{ inputs.prune_url }} run: | python -c " import requests, os payload = { 'name': os.environ['SLUG'], 'artifact_type': 'skill', 'keep_count': 1, 'protect_version': os.environ['VERSION'] } res = requests.post(os.environ['PRUNE_URL'], json=payload) if res.status_code != 200: exit(1) body = res.json() if body.get('code') != 200: exit(1) "