mirror of
https://github.com/anthropics/claude-code-sdk-python.git
synced 2025-12-23 09:19:52 +00:00
feat: bundle Claude Code CLI in pip package (#283)
Bundle platform-specific Claude Code CLI binaries directly in the Python package, eliminating the need for separate CLI installation. ## Changes ### Build System - Created `scripts/download_cli.py` to fetch CLI during build - Created `scripts/build_wheel.py` for building platform-specific wheels - Created `scripts/update_cli_version.py` to track bundled CLI version - Updated `pyproject.toml` to properly bundle CLI without duplicate file warnings - Made twine check non-blocking (License-File warnings are false positives) ### Runtime - Modified `subprocess_cli.py` to check for bundled CLI first - Added `_cli_version.py` to track which CLI version is bundled - SDK automatically uses bundled CLI, falling back to system installation if not found - Users can still override with `cli_path` option ### Release Workflow - Updated GitHub workflow to build separate wheels per platform (macOS, Linux, Windows) - Workflow now accepts two inputs: - `version`: Package version to publish (e.g., `0.1.5`) - `claude_code_version`: CLI version to bundle (e.g., `2.0.0` or `latest`) - Workflow builds platform-specific wheels with bundled CLI - Creates release PR that updates: - `pyproject.toml` version - `src/claude_agent_sdk/_version.py` - `src/claude_agent_sdk/_cli_version.py` with bundled CLI version - `CHANGELOG.md` with auto-generated release notes ### Documentation - Updated README to reflect bundled CLI (removed Node.js requirement) - Added release workflow documentation - Added local wheel building instructions ## Benefits - **Zero external dependencies**: No need for Node.js or npm - **Easier installation**: Single `pip install` gets everything - **Version control**: Track exactly which CLI version is bundled - **Flexible releases**: Can release new package versions with updated CLI without code changes - **Better user experience**: Works out of the box with no setup Platform-specific wheels are automatically selected by pip during installation based on the user's OS and architecture. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6f209075bc
commit
ce99e9d2eb
10 changed files with 787 additions and 95 deletions
123
scripts/download_cli.py
Executable file
123
scripts/download_cli.py
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Download Claude Code CLI binary for bundling in wheel.
|
||||
|
||||
This script is run during the wheel build process to fetch the Claude Code CLI
|
||||
binary using the official install script and place it in the package directory.
|
||||
"""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_cli_version() -> str:
|
||||
"""Get the CLI version to download from environment or default."""
|
||||
return os.environ.get("CLAUDE_CLI_VERSION", "latest")
|
||||
|
||||
|
||||
def find_installed_cli() -> Path | None:
|
||||
"""Find the installed Claude CLI binary."""
|
||||
# Check common installation locations
|
||||
locations = [
|
||||
Path.home() / ".local/bin/claude",
|
||||
Path("/usr/local/bin/claude"),
|
||||
Path.home() / "node_modules/.bin/claude",
|
||||
]
|
||||
|
||||
# Also check PATH
|
||||
cli_path = shutil.which("claude")
|
||||
if cli_path:
|
||||
return Path(cli_path)
|
||||
|
||||
for path in locations:
|
||||
if path.exists() and path.is_file():
|
||||
return path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def download_cli() -> None:
|
||||
"""Download Claude Code CLI using the official install script."""
|
||||
version = get_cli_version()
|
||||
|
||||
print(f"Downloading Claude Code CLI version: {version}")
|
||||
|
||||
# Download and run install script
|
||||
install_script = "curl -fsSL https://claude.ai/install.sh | bash"
|
||||
if version != "latest":
|
||||
install_script = f"curl -fsSL https://claude.ai/install.sh | bash -s {version}"
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
install_script,
|
||||
shell=True,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error downloading CLI: {e}", file=sys.stderr)
|
||||
print(f"stdout: {e.stdout.decode()}", file=sys.stderr)
|
||||
print(f"stderr: {e.stderr.decode()}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def copy_cli_to_bundle() -> None:
|
||||
"""Copy the installed CLI to the package _bundled directory."""
|
||||
# Find project root (parent of scripts directory)
|
||||
script_dir = Path(__file__).parent
|
||||
project_root = script_dir.parent
|
||||
bundle_dir = project_root / "src" / "claude_agent_sdk" / "_bundled"
|
||||
|
||||
# Ensure bundle directory exists
|
||||
bundle_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Find installed CLI
|
||||
cli_path = find_installed_cli()
|
||||
if not cli_path:
|
||||
print("Error: Could not find installed Claude CLI binary", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Found CLI at: {cli_path}")
|
||||
|
||||
# Determine target filename based on platform
|
||||
system = platform.system()
|
||||
target_name = "claude.exe" if system == "Windows" else "claude"
|
||||
target_path = bundle_dir / target_name
|
||||
|
||||
# Copy the binary
|
||||
print(f"Copying CLI to: {target_path}")
|
||||
shutil.copy2(cli_path, target_path)
|
||||
|
||||
# Make it executable (Unix-like systems)
|
||||
if system != "Windows":
|
||||
target_path.chmod(0o755)
|
||||
|
||||
print(f"Successfully bundled CLI binary: {target_path}")
|
||||
|
||||
# Print size info
|
||||
size_mb = target_path.stat().st_size / (1024 * 1024)
|
||||
print(f"Binary size: {size_mb:.2f} MB")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main entry point."""
|
||||
print("=" * 60)
|
||||
print("Claude Code CLI Download Script")
|
||||
print("=" * 60)
|
||||
|
||||
# Download CLI
|
||||
download_cli()
|
||||
|
||||
# Copy to bundle directory
|
||||
copy_cli_to_bundle()
|
||||
|
||||
print("=" * 60)
|
||||
print("CLI download and bundling complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue