Skip to content

Commit cff97a1

Browse files
ashwin-antclaude
andcommitted
feat: bundle Claude Code CLI in pip package
Bundle platform-specific Claude Code CLI binaries directly in the Python package, eliminating the need for separate CLI installation. Changes: - Created download_cli.py script to fetch CLI during build - Created build_wheel.py script for building platform-specific wheels - Modified subprocess_cli.py to check for bundled CLI first - Updated GitHub workflow to build separate wheels per platform - Updated pyproject.toml to include _bundled directory in wheels - Updated README to reflect bundled CLI (removed Node.js requirement) The SDK now automatically uses the bundled CLI, falling back to system installation if not found. Users can still override with cli_path option. Platform-specific wheels are built for macOS, Linux, and Windows, and pip automatically selects the correct one during installation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent ff425b2 commit cff97a1

File tree

10 files changed

+806
-114
lines changed

10 files changed

+806
-114
lines changed

.github/workflows/publish.yml

Lines changed: 160 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ on:
44
workflow_dispatch:
55
inputs:
66
version:
7-
description: "Version to publish (e.g., 0.1.0)"
7+
description: 'Package version to publish (e.g., 0.1.4)'
88
required: true
99
type: string
10+
claude_code_version:
11+
description: 'Claude Code CLI version to bundle (e.g., 2.0.0 or latest)'
12+
required: false
13+
type: string
14+
default: 'latest'
1015
jobs:
1116
test:
1217
runs-on: ubuntu-latest
@@ -56,114 +61,165 @@ jobs:
5661
run: |
5762
mypy src/
5863
59-
publish:
64+
build-wheels:
6065
needs: [test, lint]
61-
runs-on: ubuntu-latest
66+
runs-on: ${{ matrix.os }}
67+
strategy:
68+
matrix:
69+
os: [ubuntu-latest, macos-latest, windows-latest]
6270
permissions:
6371
contents: write
6472
pull-requests: write
6573

6674
steps:
67-
- uses: actions/checkout@v4
68-
with:
69-
token: ${{ secrets.GITHUB_TOKEN }}
70-
fetch-depth: 0 # Fetch all history including tags (necessary for changelog generation)
71-
72-
- name: Set up Python
73-
uses: actions/setup-python@v5
74-
with:
75-
python-version: "3.12"
76-
77-
- name: Set version
78-
id: version
79-
run: |
80-
VERSION="${{ github.event.inputs.version }}"
81-
echo "VERSION=$VERSION" >> $GITHUB_ENV
82-
echo "version=$VERSION" >> $GITHUB_OUTPUT
75+
- uses: actions/checkout@v4
76+
with:
77+
fetch-depth: 0 # Fetch all history including tags (necessary for changelog generation)
78+
79+
- name: Set up Python
80+
uses: actions/setup-python@v5
81+
with:
82+
python-version: '3.12'
83+
84+
- name: Install build dependencies
85+
run: |
86+
python -m pip install --upgrade pip
87+
pip install build twine
88+
shell: bash
89+
90+
- name: Build wheel with bundled CLI
91+
run: |
92+
python scripts/build_wheel.py \
93+
--version "${{ github.event.inputs.version }}" \
94+
--cli-version "${{ github.event.inputs.claude_code_version }}" \
95+
--skip-sdist \
96+
--clean
97+
shell: bash
98+
99+
- name: Upload wheel artifact
100+
uses: actions/upload-artifact@v4
101+
with:
102+
name: wheel-${{ matrix.os }}
103+
path: dist/*.whl
104+
if-no-files-found: error
83105

84-
- name: Update version
85-
run: |
86-
python scripts/update_version.py "${{ env.VERSION }}"
87-
88-
- name: Install build dependencies
89-
run: |
90-
python -m pip install --upgrade pip
91-
pip install build twine
92-
93-
- name: Build package
94-
run: python -m build
95-
96-
- name: Check package
97-
run: twine check dist/*
98-
99-
- name: Publish to PyPI
100-
env:
101-
TWINE_USERNAME: __token__
102-
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
103-
run: |
104-
twine upload dist/*
105-
echo "Package published to PyPI"
106-
echo "Install with: pip install claude-agent-sdk==${{ env.VERSION }}"
107-
108-
- name: Get previous release tag
109-
id: previous_tag
110-
run: |
111-
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
112-
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
113-
echo "Previous release: $PREVIOUS_TAG"
114-
115-
- name: Create release branch and commit version changes
116-
run: |
117-
# Create a new branch for the version update
118-
BRANCH_NAME="release/v${{ env.VERSION }}"
119-
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
120-
121-
# Configure git
122-
git config --local user.email "github-actions[bot]@users.noreply.github.com"
123-
git config --local user.name "github-actions[bot]"
124-
125-
# Create and switch to new branch
126-
git checkout -b "$BRANCH_NAME"
127-
128-
# Commit version changes
129-
git add pyproject.toml src/claude_agent_sdk/_version.py
130-
git commit -m "chore: bump version to ${{ env.VERSION }}"
131-
132-
- name: Update changelog with Claude
133-
continue-on-error: true
134-
uses: anthropics/claude-code-action@v1
135-
with:
136-
prompt: "/generate-changelog new version: ${{ env.VERSION }}, old version: ${{ steps.previous_tag.outputs.previous_tag }}"
137-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
138-
github_token: ${{ secrets.GITHUB_TOKEN }}
139-
claude_args: |
140-
--allowedTools 'Bash(git add:*),Bash(git commit:*),Edit'
141-
142-
- name: Push branch and create PR
143-
env:
144-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
145-
run: |
146-
# Push the branch with all commits
147-
git push origin "${{ env.BRANCH_NAME }}"
148-
149-
# Create PR using GitHub CLI
150-
PR_BODY="This PR updates the version to ${{ env.VERSION }} after publishing to PyPI.
151-
152-
## Changes
153-
- Updated version in \`pyproject.toml\`
154-
- Updated version in \`src/claude_agent_sdk/_version.py\`
155-
- Updated \`CHANGELOG.md\` with release notes
156-
157-
## Release Information
158-
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/${{ env.VERSION }}/
159-
- Install with: \`pip install claude-agent-sdk==${{ env.VERSION }}\`
160-
161-
🤖 Generated by GitHub Actions"
162-
163-
PR_URL=$(gh pr create \
164-
--title "chore: release v${{ env.VERSION }}" \
165-
--body "$PR_BODY" \
166-
--base main \
167-
--head "${{ env.BRANCH_NAME }}")
106+
publish:
107+
needs: [build-wheels]
108+
runs-on: ubuntu-latest
109+
permissions:
110+
contents: write
111+
pull-requests: write
168112

169-
echo "PR created: $PR_URL"
113+
steps:
114+
- uses: actions/checkout@v4
115+
with:
116+
token: ${{ secrets.GITHUB_TOKEN }}
117+
118+
- name: Set up Python
119+
uses: actions/setup-python@v5
120+
with:
121+
python-version: '3.12'
122+
123+
- name: Set version
124+
id: version
125+
run: |
126+
VERSION="${{ github.event.inputs.version }}"
127+
echo "VERSION=$VERSION" >> $GITHUB_ENV
128+
echo "version=$VERSION" >> $GITHUB_OUTPUT
129+
130+
- name: Update version
131+
run: |
132+
python scripts/update_version.py "${{ env.VERSION }}"
133+
134+
- name: Update CLI version
135+
run: |
136+
python scripts/update_cli_version.py "${{ github.event.inputs.claude_code_version }}"
137+
138+
- name: Download all wheel artifacts
139+
uses: actions/download-artifact@v4
140+
with:
141+
path: dist
142+
pattern: wheel-*
143+
merge-multiple: true
144+
145+
- name: Install build dependencies
146+
run: |
147+
python -m pip install --upgrade pip
148+
pip install build twine
149+
150+
- name: Build source distribution
151+
run: python -m build --sdist
152+
153+
- name: Publish to PyPI
154+
env:
155+
TWINE_USERNAME: __token__
156+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
157+
run: |
158+
twine upload dist/*
159+
echo "Package published to PyPI"
160+
echo "Install with: pip install claude-agent-sdk==${{ env.VERSION }}"
161+
162+
- name: Get previous release tag
163+
id: previous_tag
164+
run: |
165+
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
166+
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
167+
echo "Previous release: $PREVIOUS_TAG"
168+
169+
- name: Create release branch and commit version changes
170+
run: |
171+
# Create a new branch for the version update
172+
BRANCH_NAME="release/v${{ env.VERSION }}"
173+
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
174+
175+
# Configure git
176+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
177+
git config --local user.name "github-actions[bot]"
178+
179+
# Create and switch to new branch
180+
git checkout -b "$BRANCH_NAME"
181+
182+
# Commit version changes
183+
git add pyproject.toml src/claude_agent_sdk/_version.py src/claude_agent_sdk/_cli_version.py
184+
git commit -m "chore: bump version to ${{ env.VERSION }} with CLI ${{ github.event.inputs.claude_code_version }}"
185+
186+
- name: Update changelog with Claude
187+
continue-on-error: true
188+
uses: anthropics/claude-code-action@v1
189+
with:
190+
prompt: "/generate-changelog new version: ${{ env.VERSION }}, old version: ${{ steps.previous_tag.outputs.previous_tag }}"
191+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
192+
github_token: ${{ secrets.GITHUB_TOKEN }}
193+
claude_args: |
194+
--allowedTools 'Bash(git add:*),Bash(git commit:*),Edit'
195+
196+
- name: Push branch and create PR
197+
env:
198+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
199+
run: |
200+
# Push the branch with all commits
201+
git push origin "${{ env.BRANCH_NAME }}"
202+
203+
# Create PR using GitHub CLI
204+
PR_BODY="This PR updates the version to ${{ env.VERSION }} after publishing to PyPI.
205+
206+
## Changes
207+
- Updated version in \`pyproject.toml\` to ${{ env.VERSION }}
208+
- Updated version in \`src/claude_agent_sdk/_version.py\` to ${{ env.VERSION }}
209+
- Updated bundled CLI version in \`src/claude_agent_sdk/_cli_version.py\` to ${{ github.event.inputs.claude_code_version }}
210+
- Updated \`CHANGELOG.md\` with release notes
211+
212+
## Release Information
213+
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/${{ env.VERSION }}/
214+
- Bundled CLI version: ${{ github.event.inputs.claude_code_version }}
215+
- Install with: \`pip install claude-agent-sdk==${{ env.VERSION }}\`
216+
217+
🤖 Generated by GitHub Actions"
218+
219+
PR_URL=$(gh pr create \
220+
--title "chore: release v${{ env.VERSION }}" \
221+
--body "$PR_BODY" \
222+
--base main \
223+
--head "${{ env.BRANCH_NAME }}")
224+
225+
echo "PR created: $PR_URL"

README.md

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ pip install claude-agent-sdk
99
```
1010

1111
**Prerequisites:**
12+
1213
- Python 3.10+
13-
- Node.js
14-
- Claude Code 2.0.0+: `npm install -g @anthropic-ai/claude-code`
14+
15+
**Note:** The Claude Code CLI is automatically bundled with the package - no separate installation required! The SDK will use the bundled CLI by default. If you prefer to use a system-wide installation or a specific version, you can:
16+
17+
- Install Claude Code separately: `curl -fsSL https://claude.ai/install.sh | bash`
18+
- Specify a custom path: `ClaudeAgentOptions(cli_path="/path/to/claude")`
1519

1620
## Quick Start
1721

@@ -179,7 +183,7 @@ options = ClaudeAgentOptions(
179183

180184
### Hooks
181185

182-
A **hook** is a Python function that the Claude Code *application* (*not* Claude) invokes at specific points of the Claude agent loop. Hooks can provide deterministic processing and automated feedback for Claude. Read more in [Claude Code Hooks Reference](https://docs.anthropic.com/en/docs/claude-code/hooks).
186+
A **hook** is a Python function that the Claude Code _application_ (_not_ Claude) invokes at specific points of the Claude agent loop. Hooks can provide deterministic processing and automated feedback for Claude. Read more in [Claude Code Hooks Reference](https://docs.anthropic.com/en/docs/claude-code/hooks).
183187

184188
For more examples, see examples/hooks.py.
185189

@@ -229,10 +233,10 @@ async with ClaudeSDKClient(options=options) as client:
229233
print(msg)
230234
```
231235

232-
233236
## Types
234237

235238
See [src/claude_agent_sdk/types.py](src/claude_agent_sdk/types.py) for complete type definitions:
239+
236240
- `ClaudeAgentOptions` - Configuration options
237241
- `AssistantMessage`, `UserMessage`, `SystemMessage`, `ResultMessage` - Message types
238242
- `TextBlock`, `ToolUseBlock`, `ToolResultBlock` - Content blocks
@@ -259,7 +263,7 @@ except CLIJSONDecodeError as e:
259263
print(f"Failed to parse response: {e}")
260264
```
261265

262-
See [src/claude_agent_sdk/_errors.py](src/claude_agent_sdk/_errors.py) for all error types.
266+
See [src/claude_agent_sdk/\_errors.py](src/claude_agent_sdk/_errors.py) for all error types.
263267

264268
## Available Tools
265269

@@ -290,6 +294,63 @@ If you're contributing to this project, run the initial setup script to install
290294

291295
This installs a pre-push hook that runs lint checks before pushing, matching the CI workflow. To skip the hook temporarily, use `git push --no-verify`.
292296

297+
### Building Wheels Locally
298+
299+
To build wheels with the bundled Claude Code CLI:
300+
301+
```bash
302+
# Install build dependencies
303+
pip install build twine
304+
305+
# Build wheel with bundled CLI
306+
python scripts/build_wheel.py
307+
308+
# Build with specific version
309+
python scripts/build_wheel.py --version 0.1.4
310+
311+
# Build with specific CLI version
312+
python scripts/build_wheel.py --cli-version 2.0.0
313+
314+
# Clean bundled CLI after building
315+
python scripts/build_wheel.py --clean
316+
317+
# Skip CLI download (use existing)
318+
python scripts/build_wheel.py --skip-download
319+
```
320+
321+
The build script:
322+
323+
1. Downloads Claude Code CLI for your platform
324+
2. Bundles it in the wheel
325+
3. Builds both wheel and source distribution
326+
4. Checks the package with twine
327+
328+
See `python scripts/build_wheel.py --help` for all options.
329+
330+
### Release Workflow
331+
332+
The package is published to PyPI via the GitHub Actions workflow in `.github/workflows/publish.yml`. To create a new release:
333+
334+
1. **Trigger the workflow** manually from the Actions tab with two inputs:
335+
- `version`: The package version to publish (e.g., `0.1.5`)
336+
- `claude_code_version`: The Claude Code CLI version to bundle (e.g., `2.0.0` or `latest`)
337+
338+
2. **The workflow will**:
339+
- Build platform-specific wheels for macOS, Linux, and Windows
340+
- Bundle the specified Claude Code CLI version in each wheel
341+
- Build a source distribution
342+
- Publish all artifacts to PyPI
343+
- Create a release branch with version updates
344+
- Open a PR to main with:
345+
- Updated `pyproject.toml` version
346+
- Updated `src/claude_agent_sdk/_version.py`
347+
- Updated `src/claude_agent_sdk/_cli_version.py` with bundled CLI version
348+
- Auto-generated `CHANGELOG.md` entry
349+
350+
3. **Review and merge** the release PR to update main with the new version information
351+
352+
The workflow tracks both the package version and the bundled CLI version separately, allowing you to release a new package version with an updated CLI without code changes.
353+
293354
## License
294355

295356
MIT

0 commit comments

Comments
 (0)