diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7817fa3..cb6f9ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: permissions: contents: write + id-token: write packages: write jobs: @@ -116,3 +117,49 @@ jobs: with: body_path: dist/release-body.md files: dist/asp-compose-${{ needs.prepare.outputs.version }}.tar.gz + + publish-cli: + name: Publish CLI to PyPI + runs-on: ubuntu-latest + needs: + - prepare + - release + environment: + name: pypi + url: https://pypi.org/p/asp-cli + defaults: + run: + working-directory: cli + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - name: Check CLI version matches release tag + run: | + python - <<'PY' + import tomllib + from pathlib import Path + + release_version = "${{ needs.prepare.outputs.version }}" + pyproject = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8")) + cli_version = pyproject["project"]["version"] + if cli_version != release_version: + raise SystemExit( + f"cli/pyproject.toml version {cli_version!r} must match release tag version {release_version!r}" + ) + PY + - name: Build CLI package + run: uv build + - name: Check package metadata + run: uvx twine check dist/* + - name: Publish CLI package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: cli/dist diff --git a/cli/README.md b/cli/README.md index 7bfc3f7..78fa5a2 100644 --- a/cli/README.md +++ b/cli/README.md @@ -25,3 +25,48 @@ For automation and skills, prefer stable JSON output: ```powershell asp case list --output json ``` + +## Publish to PyPI + +The GitHub release workflow publishes `asp-cli` to PyPI automatically when a `v` tag is pushed. The CLI package version does not include the leading `v` and must match the main release version, so update `version` in `pyproject.toml` before creating the tag. + +For example, release tag `v0.1.0` publishes PyPI version `0.1.0`. + +### Automatic publishing + +The workflow uses PyPI Trusted Publishing, so it does not need a PyPI API token in the repository or GitHub Secrets. Configure PyPI once with a trusted publisher: + +- PyPI project: `asp-cli` +- Owner/repository: `FunnyWolf/agentic-soc-platform` +- Workflow: `release.yml` +- Environment: `pypi` + +If the PyPI project does not exist yet, add the same entry under PyPI's pending publishers before the first release. + +Release steps: + +```powershell +# Update cli\pyproject.toml first, for example: version = "0.1.0" +git tag v0.1.0 +git push origin v0.1.0 +``` + +### Manual fallback + +Manual publishing requires a PyPI API token. Keep it local and do not commit it: + +```powershell +cd cli +Remove-Item -Recurse -Force dist -ErrorAction SilentlyContinue +uv build +uvx twine check dist\* +$env:UV_PUBLISH_TOKEN = "pypi-..." +uv publish --token $env:UV_PUBLISH_TOKEN +``` + +After publishing, verify the package can be installed: + +```powershell +pipx install --force asp-cli +asp --version +``` diff --git a/cli/src/asp_cli/__init__.py b/cli/src/asp_cli/__init__.py index 3dc1f76..03363fe 100644 --- a/cli/src/asp_cli/__init__.py +++ b/cli/src/asp_cli/__init__.py @@ -1 +1,23 @@ -__version__ = "0.1.0" +from __future__ import annotations + +import tomllib +from importlib.metadata import PackageNotFoundError, version +from pathlib import Path + + +def _source_version() -> str | None: + pyproject_path = Path(__file__).resolve().parents[2] / "pyproject.toml" + if not pyproject_path.exists(): + return None + + pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8")) + project_version = pyproject.get("project", {}).get("version") + if not isinstance(project_version, str): + raise RuntimeError("Missing project.version in cli pyproject.toml") + return project_version + + +try: + __version__ = _source_version() or version("asp-cli") +except PackageNotFoundError as exc: + raise RuntimeError("Cannot resolve asp-cli package version") from exc diff --git a/cli/src/asp_cli/main.py b/cli/src/asp_cli/main.py index 22875df..85704f6 100644 --- a/cli/src/asp_cli/main.py +++ b/cli/src/asp_cli/main.py @@ -44,7 +44,7 @@ class RuntimeOptions: verbose: bool -app = typer.Typer(no_args_is_help=True, help="ASP command line client.") +app = typer.Typer(no_args_is_help=True, invoke_without_command=True, help="ASP command line client.") auth_app = typer.Typer(no_args_is_help=True, help="Authenticate and inspect the current ASP session.") config_app = typer.Typer(no_args_is_help=True, help="Read and write ASP CLI settings.") completion_app = typer.Typer(no_args_is_help=True, help="Show shell completion installation commands.")