Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 41 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,50 @@ on:
pull_request:
branches: [main]

workflow_call:
inputs:
ref:
type: string
required: true

permissions:
contents: read

jobs:
sensitive-identifiers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- name: Scan tracked files for account and conversation identifiers
run: python scripts/check_sensitive_identifiers.py
run: |
python scripts/check_sensitive_identifiers.py
python scripts/release_metadata.py --check

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
fetch-depth: 0

- name: Install Node.js (for execjs)
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "20"
node-version: "24"

- uses: actions/setup-python@v5
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -49,29 +68,37 @@ jobs:
run: uv run pytest -q

build:
needs: [test, sensitive-identifiers]
needs: [test, sensitive-identifiers, openclaw-plugin]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- name: Build sdist + wheel
run: |
pip install build
python -m build
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dist
path: dist/

openclaw-plugin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
node-version: "24"
- name: Validate OpenClaw plugin package
run: npm test
96 changes: 77 additions & 19 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,99 @@ name: Publish to PyPI

on:
push:
tags:
- "v*.*.*"
tags: ['v*.*.*']
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pypi-${{ github.ref }}
cancel-in-progress: false

jobs:
build:
release-ref:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- name: Build sdist + wheel
python-version: '3.11'
- name: Require a matching stable release tag
env:
RELEASE_REF: ${{ github.ref }}
RELEASE_TAG: ${{ github.ref_name }}
run: |
pip install build
python -m build
- name: Upload dist
uses: actions/upload-artifact@v4
[[ "$RELEASE_REF" == refs/tags/v* ]] || { echo 'Publishing requires a version tag'; exit 1; }
python scripts/release_metadata.py --check --tag "$RELEASE_TAG"
git merge-base --is-ancestor HEAD origin/main
checks:
needs: release-ref
uses: ./.github/workflows/ci.yml
with:
ref: ${{ github.ref }}
existing-artifacts:
needs: checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist/

path: dist
- name: Reject conflicting already published bytes
env:
RELEASE_TAG: ${{ github.ref_name }}
run: python scripts/verify_pypi_release.py "${RELEASE_TAG#v}" --dist dist --existing-only
publish:
needs: build
needs: existing-artifacts
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/goofish-cli/
permissions:
contents: read
id-token: write
steps:
- name: Download dist
uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist/
- name: Publish to PyPI (Trusted Publisher)
uses: pypa/gh-action-pypi-publish@release/v1
path: dist
- name: Publish checked distributions via OIDC
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
skip-existing: true
verify:
needs: publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24'
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist
- name: Install release verification runtime
run: |
pip install uv
uv venv
uv pip install 'mcp>=1.2,<2'
- name: Verify actual PyPI CLI, MCP and artifact hashes
env:
RELEASE_TAG: ${{ github.ref_name }}
run: uv run python scripts/verify_pypi_release.py "${RELEASE_TAG#v}" --dist dist
90 changes: 90 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Automatic PyPI release

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: automatic-pypi-release
cancel-in-progress: false

jobs:
release:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
actions: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Prepare version and changelog PR
id: plan
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
with:
target-branch: main
skip-github-release: true
- name: Validate generated PR identity
if: steps.plan.outputs.pr != ''
id: candidate
env:
GH_TOKEN: ${{ github.token }}
RELEASE_PR: ${{ steps.plan.outputs.pr }}
run: python scripts/release_candidate.py prepare
- name: Check out immutable candidate
if: steps.candidate.outputs.sha != ''
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ steps.candidate.outputs.sha }}
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24'
- name: Restrict candidate to release metadata
if: steps.candidate.outputs.sha != ''
env:
RELEASE_BASE_SHA: ${{ steps.candidate.outputs.base }}
RELEASE_VERSION: ${{ steps.candidate.outputs.version }}
run: python scripts/release_metadata.py --check --base "$RELEASE_BASE_SHA" --tag "v$RELEASE_VERSION"
- name: Validate release source
run: |
pip install uv
uv venv
uv pip install -e '.[dev]'
uv run pytest -q
uv run ruff check src tests
python scripts/check_sensitive_identifiers.py
python scripts/release_metadata.py --check
npm test
- name: Merge verified metadata PR
if: steps.candidate.outputs.sha != ''
env:
GH_TOKEN: ${{ github.token }}
RELEASE_PR_NUMBER: ${{ steps.candidate.outputs.number }}
RELEASE_PR_SHA: ${{ steps.candidate.outputs.sha }}
RELEASE_BASE_SHA: ${{ steps.candidate.outputs.base }}
run: python scripts/release_candidate.py merge
- name: Create release and tag
id: publish
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
with:
target-branch: main
skip-github-pull-request: true
- name: Dispatch trusted PyPI publisher
if: steps.publish.outputs.release_created == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.publish.outputs.tag_name }}
run: gh workflow run publish.yml --repo "$GITHUB_REPOSITORY" --ref "$RELEASE_TAG"
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.5.0"
}
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ clawhub package publish ./openclaw-goofish-<version>.tgz \

"我只是小改" 也不能跳过。参考 [真实验证准则](./docs/architecture.md#验证准则)。

## 版本与发布

PyPI 由程序自动维护版本 PR、验证、合并、生成 tag 并发布;ClawHub 保持手动。触发规则、权限和失败恢复见 [发布流程](docs/releases.md)。新增自动化不能依赖个人 token,也不能跳过 tag、工件哈希或 PyPI 实装验证。

## 加命令:典型流程

1. 在 `src/goofish_cli/commands/<namespace>/<cmd>.py` 写一个函数
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ claude /plugin marketplace add fancyboi999/goofish-cli
OpenClaw `2026.6.1` 及以上可把本仓库作为 compatible bundle 加载。已发布到
[ClawHub](https://clawhub.ai/plugins/openclaw-goofish),安装:

<!-- x-release-please-start-version -->
```bash
openclaw plugins install clawhub:openclaw-goofish

Expand All @@ -143,6 +144,7 @@ uvx --from goofish-cli==0.5.0 goofish auth login --qr
openclaw plugins inspect goofish --json
openclaw gateway restart
```
<!-- x-release-please-end -->

本地开发无需发布:

Expand Down
2 changes: 2 additions & 0 deletions docs/mcp-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

ClawHub 发布后安装:

<!-- x-release-please-start-version -->
```bash
openclaw plugins install clawhub:openclaw-goofish
uvx --from goofish-cli==0.5.0 goofish auth login --qr
openclaw plugins inspect goofish --json
openclaw gateway restart
```
<!-- x-release-please-end -->

本地开发使用 link 安装:

Expand Down
37 changes: 37 additions & 0 deletions docs/releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 发布

PyPI 自动发布,ClawHub 手动发布。业务 PR 合入 main 后,release-please 根据 Conventional Commits 判断下一版本,维护发布 PR。程序只合并机器人生成、通过检查且仅修改版本、安装示例和 CHANGELOG 的发布 PR;生成 tag/GitHub Release 后自动启动 PyPI 发布。无需人工改版本、推 tag 或上传 Python 包。

`fix:` 通常递增 patch,`feat:` 通常递增 minor。0.x 的破坏性变更递增 minor。仓库的 Python、插件元数据、MCP 精确版本锁定和安装文档由同一发布 PR 同步;当前 ClawHub 上的旧 bundle 仍锁定其已发布 Python 版本,不会随 PyPI 自动更新。

## 流程与权限

`.github/workflows/release.yml` 使用仓库自己的 GITHUB_TOKEN 创建和合并发布 PR,不保存个人访问 token。仓库 Settings → Actions → General 必须允许 GitHub Actions 创建 PR;程序不会绕过 branch protection 或人工审核规则。后续若收紧 main 的规则,自动合并受到相同限制。

发布 PR 验证按完整 SHA 执行。非机器人、外部仓库、非 main 基线或版本文件范围外的 PR 不进入自动合并;版本 JSON/TOML 中除版本外的内容、MCP 其他参数以及文档非版本内容也不得改变。检查后候选或 main 更新会中止合并,下一次执行重新生成并验证。

GITHUB_TOKEN 创建的 tag 不会触发另一个 push workflow,所以程序显式 dispatch `publish.yml` 到版本 tag;不依赖 tag 事件级联。发布始终由 `publish.yml` 执行,保留既有 PyPI Trusted Publisher 的 workflow 身份。

## 上传与回读

`publish.yml` 只接受与元数据一致的稳定版本 tag,并要求该提交属于 main 历史。对应 tag 必须通过 Python 3.11/3.12、lint、敏感标识扫描、版本契约、插件打包检查和构建,才进入上传。手工推 tag 和手工运行 publisher 也受这些检查约束;不能从 main 分支直接运行 publisher 上传。

上传前对照 PyPI 已存在文件的 SHA256;不一致则拒绝。上传使用专用 pypi environment 和 OIDC,无长期 PyPI API token。只有上传 job 有 id-token:write;安装验证 job 没有该权限。

上传后从 PyPI 安装精确版本,验证 CLI 版本、MCP 握手和核心工具目录,并对照发布工件哈希。索引尚未可见时有限重试;失败明确保留失败状态,不能把上传成功当成安装验证成功。

## 恢复

自动发布流程按仓库串行,publisher 按 tag 串行。发布准备、合并、生成 tag、上传、安装验证是独立阶段。查看两个 workflow 的实际结论,不以 GitHub Release 或 tag 存在代替 PyPI 成功。

同一发布运行可重跑失败 job。已上传文件仅在哈希与检查过的产物一致时跳过;缺失文件继续上传。产物冲突不能用删 tag、覆盖文件或再次 bump 版本掩盖。若 tag/release 已生成而发布未启动,可执行:

```bash
gh workflow run publish.yml --ref v<版本>
```

重新执行 `release.yml` 会继续处理已合并且待生成 tag 的发布 PR,不需要重新提交业务代码。只希望暂停自动版本发布时,禁用 Automatic PyPI release 工作流;手动 publisher 通道仍存在。

## ClawHub

ClawHub 不在任何自动 workflow 中上传。选择一个已通过 PyPI 安装验证的版本 tag,从干净源码打包,再按 CONTRIBUTING.md 运行真实 embedded-agent 工具过滤验收、dry-run、平台安全检查和注册表下载哈希核验后手动发布。命令见 CONTRIBUTING.md。不会自动生成或上传 ClawHub token。
Loading
Loading