From 4fc460f71ffef15c725759047123bc690dc8de3c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 03:52:32 +0000 Subject: [PATCH 1/4] feat(python): add PyPI publishing workflow with pre-built wheels - Create publish-python.yml: builds wheels for Linux (x86_64/aarch64, glibc/musl), macOS (x86_64/aarch64), Windows (x86_64) across Python 3.9-3.13. Uses maturin-action + PyPI trusted publishing. - Switch pyproject.toml to dynamic versioning from Cargo.toml so Python and Rust versions stay in sync automatically. - Add Python 3.13 classifier. - Document Python publishing in release process spec. https://claude.ai/code/session_01CpNjeziWqpNS2VfGvaWurd --- .github/workflows/publish-python.yml | 188 +++++++++++++++++++++++++++ crates/bashkit-python/pyproject.toml | 3 +- specs/008-release-process.md | 38 +++++- 3 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish-python.yml diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml new file mode 100644 index 00000000..6464508d --- /dev/null +++ b/.github/workflows/publish-python.yml @@ -0,0 +1,188 @@ +# PyPI publishing workflow for bashkit Python bindings +# Builds pre-compiled wheels for all major platforms and publishes to PyPI. +# Triggered alongside publish.yml on GitHub Release or manual dispatch. +# Adapted from https://github.com/pydantic/monty CI wheel-building pattern. +# +# Prerequisites: +# - PyPI trusted publisher configured for this repo + workflow +# - GitHub environment "release-python" created in repo settings + +name: Publish Python + +on: + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + PYTHON_VERSIONS: "3.9 3.10 3.11 3.12 3.13" + +jobs: + # Source distribution (platform-independent) + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + rust-toolchain: stable + working-directory: crates/bashkit-python + + - uses: actions/upload-artifact@v6 + with: + name: pypi_files-sdist + path: crates/bashkit-python/dist + + # Pre-compiled binary wheels for each platform + build: + name: Build wheel - ${{ matrix.os }} (${{ matrix.target }}, ${{ matrix.manylinux || 'auto' }}) + strategy: + fail-fast: false + matrix: + include: + # Linux glibc + - os: linux + target: x86_64 + runs-on: ubuntu-latest + - os: linux + target: aarch64 + runs-on: ubuntu-latest + + # Linux musl + - os: linux + target: x86_64 + manylinux: musllinux_1_1 + runs-on: ubuntu-latest + - os: linux + target: aarch64 + manylinux: musllinux_1_1 + runs-on: ubuntu-latest + + # macOS + - os: macos + target: x86_64 + runs-on: macos-latest + - os: macos + target: aarch64 + runs-on: macos-latest + + # Windows + - os: windows + target: x86_64 + runs-on: windows-latest + + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + manylinux: ${{ matrix.manylinux || 'auto' }} + args: --release --out dist -i ${{ env.PYTHON_VERSIONS }} + rust-toolchain: stable + docker-options: -e CI + working-directory: crates/bashkit-python + + - uses: actions/upload-artifact@v6 + with: + name: pypi_files-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.manylinux || 'manylinux' }} + path: crates/bashkit-python/dist + + # Verify built artifacts + inspect: + name: Inspect artifacts + needs: [build, build-sdist] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v7 + with: + pattern: pypi_files-* + merge-multiple: true + path: dist + + - name: List dist files + run: | + ls -lhR dist/ + echo "---" + echo "Total files: $(ls -1 dist/ | wc -l)" + + - uses: astral-sh/setup-uv@v7 + - run: uvx twine check dist/* + + # Smoke-test wheels on each OS + test-builds: + name: Test wheel on ${{ matrix.os }} + needs: [build] + strategy: + fail-fast: false + matrix: + include: + - os: linux + runs-on: ubuntu-latest + - os: macos + runs-on: macos-latest + - os: windows + runs-on: windows-latest + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - uses: actions/download-artifact@v7 + with: + pattern: pypi_files-${{ matrix.os }}-* + merge-multiple: true + path: dist + + - name: Install from wheel + run: pip install bashkit --no-index --find-links dist --force-reinstall + + - name: Smoke test + run: python -c "from bashkit import BashTool; t = BashTool(); r = t.execute_sync('echo hello'); print(r); assert r.exit_code == 0" + + # Publish to PyPI using trusted publishing (OIDC) + publish: + name: Publish to PyPI + needs: [inspect, test-builds] + if: success() + runs-on: ubuntu-latest + + environment: + name: release-python + + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v7 + with: + pattern: pypi_files-* + merge-multiple: true + path: dist + + - name: List dist files + run: ls -lhR dist/ + + - uses: astral-sh/setup-uv@v7 + + - name: Publish to PyPI + run: uv publish --trusted-publishing always dist/* diff --git a/crates/bashkit-python/pyproject.toml b/crates/bashkit-python/pyproject.toml index 5c5c6cee..3e5903e5 100644 --- a/crates/bashkit-python/pyproject.toml +++ b/crates/bashkit-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "bashkit" -version = "0.1.2" +dynamic = ["version"] description = "Python bindings for Bashkit - a sandboxed bash interpreter for AI agents" readme = "README.md" license = { text = "MIT" } @@ -19,6 +19,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Rust", "Topic :: Software Development :: Interpreters", "Topic :: Security", diff --git a/specs/008-release-process.md b/specs/008-release-process.md index c5720950..4afff9a2 100644 --- a/specs/008-release-process.md +++ b/specs/008-release-process.md @@ -145,6 +145,7 @@ Example: - `bashkit` on crates.io (core library) - `bashkit-cli` on crates.io (CLI tool) +- `bashkit` on PyPI (Python bindings, pre-built wheels) ## Publishing Order @@ -153,7 +154,9 @@ Crates must be published in dependency order: 1. `bashkit` (core library, no internal deps) 2. `bashkit-cli` (depends on bashkit) -The CI workflow handles this with a dependency chain and wait for index update. +Python wheels are published independently (no crates.io dependency). + +The CI workflows handle this automatically on GitHub Release. ## Workflows @@ -170,6 +173,33 @@ The CI workflow handles this with a dependency chain and wait for index update. - **File**: `.github/workflows/publish.yml` - **Secret required**: `CARGO_REGISTRY_TOKEN` +### publish-python.yml + +- **Trigger**: GitHub Release published (runs in parallel with publish.yml) +- **Actions**: Builds pre-compiled wheels for all platforms, smoke-tests, publishes to PyPI +- **File**: `.github/workflows/publish-python.yml` +- **Auth**: PyPI trusted publishing (OIDC, no secrets needed) +- **Environment**: `release-python` (must exist in GitHub repo settings) + +#### Wheel matrix + +| OS | Architecture | Variant | +|----|-------------|---------| +| Linux | x86_64 | manylinux (glibc) | +| Linux | aarch64 | manylinux (glibc) | +| Linux | x86_64 | musllinux | +| Linux | aarch64 | musllinux | +| macOS | x86_64 | universal | +| macOS | aarch64 (Apple Silicon) | universal | +| Windows | x86_64 | MSVC | + +Python versions: 3.9, 3.10, 3.11, 3.12, 3.13 + +#### Version sync + +Python package version is read dynamically from `Cargo.toml` via maturin +(`dynamic = ["version"]` in pyproject.toml). No manual version sync needed. + ## Authentication **Required Secrets** (GitHub Settings > Secrets > Actions): @@ -178,6 +208,11 @@ The CI workflow handles this with a dependency chain and wait for index update. - Generate at: https://crates.io/settings/tokens - Scope: Publish new crates, Publish updates +**PyPI Trusted Publishing** (no secret needed): + +- Configure at: https://pypi.org/manage/project/bashkit/settings/publishing/ +- Add publisher: GitHub, repo `everruns/bashkit`, workflow `publish-python.yml`, environment `release-python` + ## Example Conversation ``` @@ -220,3 +255,4 @@ Each release includes: - **GitHub Release**: Tag, release notes, source archives - **crates.io**: Published crates for `cargo add bashkit` +- **PyPI**: Pre-built wheels for `pip install bashkit` From 0c0da6600c29b470d19d609599437aee8805dd27 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 04:45:57 +0000 Subject: [PATCH 2/4] chore(specs): add 013-python-package spec and update AGENTS.md Documents Python bindings layout, PyPI publishing workflow, supported platforms, public API, and design decisions (no PGO, dynamic versioning, trusted publishing). https://claude.ai/code/session_01CpNjeziWqpNS2VfGvaWurd --- AGENTS.md | 3 +- specs/013-python-package.md | 170 ++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 specs/013-python-package.md diff --git a/AGENTS.md b/AGENTS.md index 2f9f7ab5..6a5480f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,13 +32,14 @@ Fix root cause. Unsure: read more code; if stuck, ask w/ short options. Unrecogn | 007-parallel-execution | Threading model, Arc usage | | 008-documentation | Rustdoc guides, embedded markdown | | 008-posix-compliance | POSIX design rationale, security exclusions | -| 008-release-process | Version tagging, crates.io publishing | +| 008-release-process | Version tagging, crates.io + PyPI publishing | | 009-implementation-status | Feature status, test coverage, limitations | | 009-tool-contract | Public LLM Tool trait contract | | 010-git-support | Sandboxed git operations on VFS | | 011-python-builtin | Embedded Python via Monty, security, resource limits | | 012-eval | LLM evaluation harness, dataset format, scoring | | 012-maintenance | Pre-release maintenance checklist | +| 013-python-package | Python bindings, PyPI wheels, platform matrix | ### Documentation diff --git a/specs/013-python-package.md b/specs/013-python-package.md new file mode 100644 index 00000000..6a1507bf --- /dev/null +++ b/specs/013-python-package.md @@ -0,0 +1,170 @@ +# Python Package + +## Abstract + +Bashkit ships Python bindings as pre-built binary wheels on PyPI. Users install with +`pip install bashkit` and get a native extension — no Rust toolchain needed. + +## Package Layout + +``` +crates/bashkit-python/ +├── Cargo.toml # Rust crate (cdylib via PyO3) +├── pyproject.toml # Python package metadata (maturin build backend) +├── src/lib.rs # PyO3 bindings (BashTool, ExecResult) +├── bashkit/ +│ ├── __init__.py # Re-exports from native module +│ ├── _bashkit.pyi # Type stubs (PEP 561) +│ ├── py.typed # Marker for typed package +│ ├── langchain.py # LangChain integration +│ └── deepagents.py # Deep Agents integration +``` + +## Build System + +- **Build backend**: [maturin](https://github.com/PyO3/maturin) (1.4–2.0) +- **Rust bindings**: [PyO3](https://pyo3.rs/) 0.24 with `extension-module` feature +- **Async bridge**: `pyo3-async-runtimes` (tokio runtime) +- **Module name**: `bashkit._bashkit` (native), re-exported as `bashkit` + +## Versioning + +Python package version is read dynamically from workspace `Cargo.toml` via maturin. +`pyproject.toml` declares `dynamic = ["version"]` — no manual sync needed. + +The version chain: `Cargo.toml` (workspace) → `Cargo.toml` (bashkit-python, inherits) +→ maturin reads it → wheel metadata. + +## Supported Platforms + +### Python Versions + +3.9, 3.10, 3.11, 3.12, 3.13 + +### Wheel Matrix + +| OS | Architecture | Variant | CI Runner | +|----|-------------|---------|-----------| +| Linux | x86_64 | manylinux (glibc) | ubuntu-latest | +| Linux | aarch64 | manylinux (glibc) | ubuntu-latest (cross) | +| Linux | x86_64 | musllinux_1_1 | ubuntu-latest (Docker) | +| Linux | aarch64 | musllinux_1_1 | ubuntu-latest (Docker) | +| macOS | x86_64 | — | macos-latest (cross) | +| macOS | aarch64 | — | macos-latest (native) | +| Windows | x86_64 | MSVC | windows-latest | + +Total: ~35 wheels (7 platforms × 5 Python versions). + +## PyPI Publishing + +### Workflow + +File: `.github/workflows/publish-python.yml` + +``` +GitHub Release published + ├── build-sdist (source distribution) + ├── build (7 platform variants × 5 Python versions) + ├── inspect (twine check all artifacts) + ├── test-builds (smoke test on Linux/macOS/Windows) + └── publish (uv publish → PyPI via OIDC) +``` + +### Authentication + +Uses PyPI trusted publishing (OIDC) — no API tokens needed. + +Prerequisites: +1. GitHub environment `release-python` exists in repo settings +2. PyPI trusted publisher configured: + - Owner: `everruns`, Repo: `bashkit` + - Workflow: `publish-python.yml`, Environment: `release-python` + +### Smoke Test + +Each platform runs after wheel build: +```python +from bashkit import BashTool +t = BashTool() +r = t.execute_sync('echo hello') +assert r.exit_code == 0 +``` + +## Public API + +### BashTool + +Primary class. Wraps the Rust `Bash` interpreter with `Arc>` for thread safety. + +```python +from bashkit import BashTool + +tool = BashTool( + username="user", # optional, default "user" + hostname="sandbox", # optional, default "sandbox" + max_commands=10000, # optional + max_loop_iterations=100000 # optional +) + +# Async +result = await tool.execute("echo hello") + +# Sync +result = tool.execute_sync("echo hello") + +# Reset state +tool.reset() + +# LLM metadata +tool.name # "bashkit" +tool.short_description # str +tool.description() # full description +tool.help() # LLM help text +tool.system_prompt() # system prompt +tool.input_schema() # JSON schema string +tool.output_schema() # JSON schema string +tool.version # from Rust crate +``` + +### ExecResult + +```python +result.stdout # str +result.stderr # str +result.exit_code # int +result.error # Optional[str] +result.success # bool (exit_code == 0) +result.to_dict() # dict +``` + +### create_langchain_tool_spec() + +Returns dict with `name`, `description`, `args_schema` for LangChain integration. + +## Optional Dependencies + +``` +pip install bashkit[langchain] # + langchain-core, langchain-anthropic +pip install bashkit[deepagents] # + deepagents, langchain-anthropic +pip install bashkit[dev] # + pytest, pytest-asyncio +``` + +## Local Development + +```bash +cd crates/bashkit-python +pip install maturin +maturin develop # debug build, installs into current venv +maturin develop --release # optimized build +pytest # run tests (needs dev extras) +``` + +## Design Decisions + +- **No PGO**: Profile-guided optimization adds build complexity for minimal gain. + Bashkit is a thin PyO3 wrapper — hot paths are in Rust, not Python dispatch. + Can revisit if profiling shows benefit. +- **No exotic architectures**: armv7, ppc64le, s390x, i686 omitted. Target audience + is AI agent developers on standard server/desktop platforms. +- **Dynamic version**: Eliminates version drift between Rust and Python packages. +- **Trusted publishing**: No secrets to rotate. OIDC tokens are scoped per-workflow. From f5078ed2d301ab1ab26f4f8fc740f71a7d2be979 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 04:59:34 +0000 Subject: [PATCH 3/4] chore(audit): add cargo-vet exemptions for 17 updated deps Version bumps for libc, hashbrown, tempfile, wasm-*, wit-*, getrandom, prettyplease, unicode-xid, id-arena, leb128fmt, wasip3. https://claude.ai/code/session_01CpNjeziWqpNS2VfGvaWurd --- supply-chain/config.toml | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 87baaad7..d3101fa9 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -466,6 +466,10 @@ criteria = "safe-to-deploy" version = "0.3.4" criteria = "safe-to-deploy" +[[exemptions.getrandom]] +version = "0.4.1" +criteria = "safe-to-run" + [[exemptions.globset]] version = "0.4.18" criteria = "safe-to-deploy" @@ -482,6 +486,10 @@ criteria = "safe-to-run" version = "0.2.1" criteria = "safe-to-deploy" +[[exemptions.hashbrown]] +version = "0.15.5" +criteria = "safe-to-deploy" + [[exemptions.hashbrown]] version = "0.16.1" criteria = "safe-to-deploy" @@ -546,6 +554,10 @@ criteria = "safe-to-deploy" version = "0.1.2" criteria = "safe-to-deploy" +[[exemptions.id-arena]] +version = "2.3.0" +criteria = "safe-to-deploy" + [[exemptions.icu_collections]] version = "2.1.1" criteria = "safe-to-deploy" @@ -670,10 +682,18 @@ criteria = "safe-to-deploy" version = "1.5.0" criteria = "safe-to-deploy" +[[exemptions.leb128fmt]] +version = "0.1.0" +criteria = "safe-to-deploy" + [[exemptions.libc]] version = "0.2.180" criteria = "safe-to-deploy" +[[exemptions.libc]] +version = "0.2.181" +criteria = "safe-to-deploy" + [[exemptions.libm]] version = "0.2.16" criteria = "safe-to-deploy" @@ -886,6 +906,10 @@ criteria = "safe-to-deploy" version = "1.4.1" criteria = "safe-to-run" +[[exemptions.prettyplease]] +version = "0.2.37" +criteria = "safe-to-deploy" + [[exemptions.proc-macro-error-attr2]] version = "2.0.0" criteria = "safe-to-deploy" @@ -1342,6 +1366,10 @@ criteria = "safe-to-deploy" version = "3.24.0" criteria = "safe-to-deploy" +[[exemptions.tempfile]] +version = "3.25.0" +criteria = "safe-to-run" + [[exemptions.testing_table]] version = "0.3.0" criteria = "safe-to-deploy" @@ -1466,6 +1494,10 @@ criteria = "safe-to-deploy" version = "0.2.2" criteria = "safe-to-deploy" +[[exemptions.unicode-xid]] +version = "0.2.6" +criteria = "safe-to-deploy" + [[exemptions.unicode_names2]] version = "1.3.0" criteria = "safe-to-deploy" @@ -1530,6 +1562,10 @@ criteria = "safe-to-deploy" version = "1.0.2+wasi-0.2.9" criteria = "safe-to-deploy" +[[exemptions.wasip3]] +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +criteria = "safe-to-run" + [[exemptions.wasm-bindgen]] version = "0.2.108" criteria = "safe-to-deploy" @@ -1550,6 +1586,18 @@ criteria = "safe-to-deploy" version = "0.2.108" criteria = "safe-to-deploy" +[[exemptions.wasm-encoder]] +version = "0.244.0" +criteria = "safe-to-deploy" + +[[exemptions.wasm-metadata]] +version = "0.244.0" +criteria = "safe-to-deploy" + +[[exemptions.wasmparser]] +version = "0.244.0" +criteria = "safe-to-deploy" + [[exemptions.wasm-streams]] version = "0.4.2" criteria = "safe-to-deploy" @@ -1750,6 +1798,26 @@ criteria = "safe-to-deploy" version = "0.51.0" criteria = "safe-to-deploy" +[[exemptions.wit-bindgen-core]] +version = "0.51.0" +criteria = "safe-to-deploy" + +[[exemptions.wit-bindgen-rust]] +version = "0.51.0" +criteria = "safe-to-deploy" + +[[exemptions.wit-bindgen-rust-macro]] +version = "0.51.0" +criteria = "safe-to-deploy" + +[[exemptions.wit-component]] +version = "0.244.0" +criteria = "safe-to-deploy" + +[[exemptions.wit-parser]] +version = "0.244.0" +criteria = "safe-to-deploy" + [[exemptions.writeable]] version = "0.6.2" criteria = "safe-to-deploy" From 4f889d45c371bf0cc70ecde4d6ed47c4051b81db Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 05:07:04 +0000 Subject: [PATCH 4/4] fix(audit): correct cargo-vet exemption ordering Move id-arena after icu_provider and wasmparser after wasm-streams per cargo-vet alphabetical sort requirements. https://claude.ai/code/session_01CpNjeziWqpNS2VfGvaWurd --- supply-chain/config.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index d3101fa9..99b66805 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -554,10 +554,6 @@ criteria = "safe-to-deploy" version = "0.1.2" criteria = "safe-to-deploy" -[[exemptions.id-arena]] -version = "2.3.0" -criteria = "safe-to-deploy" - [[exemptions.icu_collections]] version = "2.1.1" criteria = "safe-to-deploy" @@ -586,6 +582,10 @@ criteria = "safe-to-deploy" version = "2.1.1" criteria = "safe-to-deploy" +[[exemptions.id-arena]] +version = "2.3.0" +criteria = "safe-to-deploy" + [[exemptions.idna]] version = "1.1.0" criteria = "safe-to-deploy" @@ -1594,10 +1594,6 @@ criteria = "safe-to-deploy" version = "0.244.0" criteria = "safe-to-deploy" -[[exemptions.wasmparser]] -version = "0.244.0" -criteria = "safe-to-deploy" - [[exemptions.wasm-streams]] version = "0.4.2" criteria = "safe-to-deploy" @@ -1606,6 +1602,10 @@ criteria = "safe-to-deploy" version = "0.5.0" criteria = "safe-to-deploy" +[[exemptions.wasmparser]] +version = "0.244.0" +criteria = "safe-to-deploy" + [[exemptions.web-sys]] version = "0.3.85" criteria = "safe-to-deploy"