diff --git a/.github/workflows/backmerge.yaml b/.github/workflows/backmerge.yaml new file mode 100644 index 0000000..0a8277e --- /dev/null +++ b/.github/workflows/backmerge.yaml @@ -0,0 +1,41 @@ +# Backmerge releases from main to next +# +# This workflow ensures that any commits on `main` (releases, version bumps) +# are automatically merged back into `next` to keep branches in sync. +# +# This prevents divergence where main has commits that next doesn't have. + +name: Backmerge to next + +on: + push: + branches: [main] + +permissions: + contents: write + +jobs: + backmerge: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: next + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge main into next + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git fetch origin main + + if git merge-base --is-ancestor origin/main HEAD; then + echo "next already contains all commits from main. Nothing to merge." + exit 0 + fi + + git merge origin/main -m "chore: backmerge from main" + git push origin next diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..64484ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,77 @@ +# Continuous Integration for the python-sdk. +# +# Runs the pytest suite across every supported Python version and verifies the +# package builds and passes twine's metadata checks. Mirrors the gating the +# other ChatBotKit SDKs have (go/terraform `ci.yml`, node `publish.yml`). +name: CI + +on: + push: + branches: + - main + - next + paths-ignore: + - '**.md' + - 'LICENSE' + pull_request: + branches: + - main + - next + paths-ignore: + - '**.md' + - 'LICENSE' + +permissions: + contents: read + +jobs: + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install + run: | + python -m pip install --upgrade pip + pip install -e '.[dev,agent,examples]' + + - name: Test + run: pytest + + build: + name: Build & metadata check + runs-on: ubuntu-latest + needs: test + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: pip + + - name: Install build tooling + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build sdist and wheel + run: python -m build + + - name: Check metadata + run: twine check dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0be7a90 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,82 @@ +# Release workflow for the python-sdk. +# +# Triggered by tag-release.yml (workflow_dispatch at the new tag) or by a +# manually pushed `v*` tag. Builds the sdist/wheel, publishes to PyPI (only if +# a PYPI_API_TOKEN secret is configured), and creates a GitHub Release whose +# body is the matching section from CHANGELOG.md with auto-generated commit +# notes appended. +# +# To enable PyPI publishing, add a `PYPI_API_TOKEN` secret (a PyPI API token) +# to this repository. Without it, the build still runs and the GitHub Release is +# still created; only the upload step is skipped. +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Build sdist and wheel + run: | + python -m pip install --upgrade pip + pip install build twine + python -m build + twine check dist/* + + - name: Detect PyPI token + id: token + env: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + run: | + if [ -n "$PYPI_API_TOKEN" ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "PYPI_API_TOKEN not set - skipping PyPI upload." >&2 + fi + + - name: Publish to PyPI + if: steps.token.outputs.present == 'true' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload --non-interactive dist/* + + - name: Extract changelog section for this version + id: changelog + run: | + VERSION="${GITHUB_REF_NAME#v}" + if [ -f CHANGELOG.md ]; then + awk -v ver="$VERSION" ' + $0 ~ ("^## \\[" ver "\\]") { capture = 1; next } + capture && /^## \[/ { exit } + capture { print } + ' CHANGELOG.md > release-notes.md + fi + if [ ! -s release-notes.md ]; then + echo "Release v${VERSION}. See the full changelog in CHANGELOG.md." > release-notes.md + fi + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + body_path: release-notes.md + generate_release_notes: true diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml new file mode 100644 index 0000000..e6f8f23 --- /dev/null +++ b/.github/workflows/tag-release.yml @@ -0,0 +1,66 @@ +# Auto-tag when merging to main +# +# Reads the version from pyproject.toml and, if the matching tag does not +# already exist, creates and pushes it, then triggers the release workflow +# (build + publish to PyPI + GitHub Release). +name: Tag Release + +on: + push: + branches: + - main + +permissions: + contents: write + actions: write + +jobs: + tag: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version from pyproject.toml + id: version + run: | + VERSION=$(grep -m1 '^version = ' pyproject.toml | sed -E 's/^version = "(.*)"/\1/') + if [ -z "$VERSION" ]; then + echo "Could not read version from pyproject.toml" >&2 + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Version: $VERSION" + + - name: Check if tag exists + id: check_tag + run: | + if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "Tag v${{ steps.version.outputs.version }} already exists" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "Tag v${{ steps.version.outputs.version }} does not exist" + fi + + - name: Create and push tag + if: steps.check_tag.outputs.exists == 'false' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" + git push origin "v${{ steps.version.outputs.version }}" + + - name: Trigger release workflow + if: steps.check_tag.outputs.exists == 'false' + uses: actions/github-script@v7 + with: + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'release.yml', + ref: 'v${{ steps.version.outputs.version }}' + }) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f3a0331 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to the ChatBotKit Python SDK are documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.0] - 2026-06-22 + +### Added + +- `skill_server` integration client (`client.integration.skill_server`) with + `list`, `fetch`, `create`, `update`, and `delete`. The Skill Server + integration exposes a skillset's abilities as a text-first HTTP API. +- `site` client under `space` (`client.space.site`) with `list`, `fetch`, + `create`, `update`, and `delete`, keyed by the parent space ID. A space site + binds a `