From 1b45b44e39ed38d00032a12842b8814bf95921a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 14:51:39 +0000 Subject: [PATCH] feat(python): publish sealg to PyPI so `uvx sealg` resolves Wire a PyPI release path for the Python client so it is installable by name rather than only from a checkout or git subdirectory. - publish-python.yaml: build sdist+wheel and publish via PyPI Trusted Publishing (OIDC, no stored token), triggered on a dedicated `sealg-py-v*` tag so it never collides with the Rust binary's `v*` release. A tag/version match guard fails before publishing on a mismatch; workflow_dispatch does a build-only validation run. - pyproject.toml: add readme long-description, MIT license + bundled LICENSE, authors, project URLs, keywords, and classifiers for a complete PyPI page. - RELEASING.md: document the one-time Trusted Publishing setup and the `sealg-py-vX.Y.Z` release steps. - READMEs: lead install with `uvx sealg`; keep the checkout/git fallbacks. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EpwFmgQPfugKFF9zugay6Y --- .github/workflows/publish-python.yaml | 68 +++++++++++++++++++++++++++ README.md | 12 +++++ RELEASING.md | 35 ++++++++++++++ python/LICENSE | 21 +++++++++ python/README.md | 12 ++++- python/pyproject.toml | 18 +++++++ 6 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish-python.yaml create mode 100644 python/LICENSE diff --git a/.github/workflows/publish-python.yaml b/.github/workflows/publish-python.yaml new file mode 100644 index 0000000..0fab22b --- /dev/null +++ b/.github/workflows/publish-python.yaml @@ -0,0 +1,68 @@ +name: Publish Python (sealg) + +# Publishes the Python `sealg` package (python/) to PyPI so `uvx sealg` resolves. +# +# Trigger: push a tag `sealg-py-vX.Y.Z` (pre-releases: `sealg-py-vX.Y.Z-rc.1`). +# The Python package versions independently of the Rust binary (which releases on +# `vX.Y.Z` via release.yml), so it gets its own tag namespace - a Rust release +# never republishes the Python package and vice versa. +# +# Auth is PyPI Trusted Publishing (OIDC): no API token is stored. One-time setup +# on PyPI is required before the first run - see RELEASING.md ("Publish the +# Python client"). + +on: + push: + tags: + - "sealg-py-v[0-9]+.[0-9]+.[0-9]+" + - "sealg-py-v[0-9]+.[0-9]+.[0-9]+-*" + # Manual build-only run to validate packaging without publishing. + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Verify tag matches package version + # Only enforced on tag pushes; skipped for workflow_dispatch (build-only). + if: startsWith(github.ref, 'refs/tags/') + run: | + tag_ver="${GITHUB_REF_NAME#sealg-py-v}" + pkg_ver="$(python3 -c 'import tomllib,pathlib; print(tomllib.loads(pathlib.Path("python/pyproject.toml").read_text())["project"]["version"])')" + if [ "$tag_ver" != "$pkg_ver" ]; then + echo "::error::tag $GITHUB_REF_NAME implies version '$tag_ver' but python/pyproject.toml is '$pkg_ver'. Bump the version before tagging." + exit 1 + fi + echo "tag and package agree on version $pkg_ver" + + - name: Build sdist + wheel + run: uv build python/ --out-dir dist + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: sealg-dist + path: dist/ + + publish: + # Tag pushes publish; the manual build-only run stops after `build`. + if: startsWith(github.ref, 'refs/tags/') + needs: build + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write # OIDC token for PyPI Trusted Publishing + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: sealg-dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/README.md b/README.md index 5a7c98c..4200a8c 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,18 @@ cargo run -p sealg -- call some_tool --args '{"query": "hello"}' cargo run -p sealg -- list --gateway-url https://dashboard.sealgate.ai ``` +### Python client + +A `uvx`-installable Python client exposing the same `sealg` surface lives in +[`python/`](python/) and is published to PyPI: + +```bash +uvx sealg doctor +``` + +It mirrors the Rust binary's commands and exit codes; the two are kept in sync +by `scripts/check_wire_contract.py`. See [`python/README.md`](python/README.md). + ## Agent Skills Claude Code skills live in `.claude/skills/`. Invoke them with `/skill-name` diff --git a/RELEASING.md b/RELEASING.md index faf8303..e27bd5c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -50,6 +50,41 @@ the **Actions** tab; all platforms build in parallel. Once CI completes, visit **Releases** on GitHub, confirm the per-platform archives are attached, edit the release notes if desired, and publish. +## Publish the Python client + +The Python `sealg` package (`python/`) is published to PyPI so `uvx sealg` +resolves. It versions independently of the Rust binary and has its own tag +namespace, `sealg-py-vX.Y.Z`, driving +[`publish-python.yaml`](.github/workflows/publish-python.yaml). + +### One-time PyPI setup (Trusted Publishing) + +No API token is stored; the workflow authenticates via OIDC. Configure this +once on PyPI (a maintainer action, not something CI can do): + +1. Create the project on PyPI (or, for the very first upload, add the trusted + publisher as a *pending* publisher at + https://pypi.org/manage/account/publishing/). +2. Add a GitHub trusted publisher with: + - Owner `Edison-Watch`, repository `cli` + - Workflow `publish-python.yaml` + - Environment `pypi` +3. Create a GitHub Environment named `pypi` in the repo settings (optionally + restrict it to tags). + +### Cut a Python release + +```bash +# 1. Bump python/pyproject.toml `version` (e.g. 0.1.0 -> 0.1.1) and commit. +# 2. Tag with the matching version and push: +git tag sealg-py-v0.1.1 +git push origin sealg-py-v0.1.1 +``` + +The workflow verifies the tag matches `python/pyproject.toml` (a mismatch fails +before publishing), builds the sdist + wheel, and uploads to PyPI. Run it via +**workflow_dispatch** first to build-and-validate without publishing. + ## Code Signing (optional) `sealg` is a headless binary, so signing is not required to run it. If you diff --git a/python/LICENSE b/python/LICENSE new file mode 100644 index 0000000..951ddd2 --- /dev/null +++ b/python/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Eito Miyamura + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/python/README.md b/python/README.md index 3efbdda..a744f10 100644 --- a/python/README.md +++ b/python/README.md @@ -13,12 +13,22 @@ pre-commit + CI check): every shared wire constant lives once in ## Install and run +Published on PyPI, so no checkout is needed: + +``` +uvx sealg doctor # run without installing +uv tool install sealg # or install it on PATH +``` + +From a local checkout, or to pin to an unreleased commit: + ``` uvx --from python/ sealg doctor # from a checkout uvx --from 'git+https://github.com/Edison-Watch/cli#subdirectory=python' sealg list ``` -Or `pip install ./python` into a virtualenv, then run `sealg`. +Or `pip install sealg` (or `pip install ./python`) into a virtualenv, then run +`sealg`. ## Commands diff --git a/python/pyproject.toml b/python/pyproject.toml index 71aadcc..4691b18 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -2,12 +2,30 @@ name = "sealg" description = "Python client for the SealGate gateway - governed access to every tool via one CLI" version = "0.1.0" +readme = "README.md" +license = "MIT" +license-files = ["LICENSE"] requires-python = ">=3.11" +authors = [{ name = "Eito Miyamura" }] +keywords = ["sealgate", "mcp", "gateway", "agent", "security", "cli"] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Topic :: Security", + "Topic :: Software Development :: Libraries", +] dependencies = [ "httpx>=0.27.0", "typer>=0.12.0", ] +[project.urls] +Homepage = "https://sealgate.ai" +Repository = "https://github.com/Edison-Watch/cli" +Issues = "https://github.com/Edison-Watch/cli/issues" + # The command this package installs: `sealg list`, `sealg call ...`, `sealg doctor`. [project.scripts] sealg = "sealg.cli:app"