From 22f67d309ca7ffa1231c540a9594d5eb104abe53 Mon Sep 17 00:00:00 2001 From: Rohit Malhotra Date: Thu, 9 Oct 2025 15:02:02 -0400 Subject: [PATCH] CLI(V1): Multi platform build + release support (#11244) Co-authored-by: Yakshith Co-authored-by: openhands --- .github/workflows/cli-build-test.yml | 54 +++++++++++++++++++++++++--- .github/workflows/pypi-release.yml | 33 +++++++++++++++++ openhands-cli/build.py | 4 +-- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cli-build-test.yml b/.github/workflows/cli-build-test.yml index af6658c407..b1fc1ac6f4 100644 --- a/.github/workflows/cli-build-test.yml +++ b/.github/workflows/cli-build-test.yml @@ -1,13 +1,13 @@ # Workflow that builds and tests the CLI binary executable name: CLI - Build and Test Binary -# Run on pushes to main branch and all pull requests, but only when CLI files change +# Run on pushes to main branch and CLI tags, and on pull requests when CLI files change on: push: branches: - main - paths: - - "openhands-cli/**" + tags: + - "*-cli" pull_request: paths: - "openhands-cli/**" @@ -20,7 +20,10 @@ concurrency: jobs: build-and-test-binary: name: Build and test binary executable - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} steps: - name: Checkout repository @@ -56,3 +59,46 @@ jobs: fi echo "✅ Build & test finished without ❌ markers" + + - name: Upload binary artifact (for releases only) + if: startsWith(github.ref, 'refs/tags/') + uses: actions/upload-artifact@v4 + with: + name: openhands-cli-${{ matrix.os }} + path: openhands-cli/dist/openhands* + retention-days: 30 + + create-github-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: build-and-test-binary + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release assets + run: | + mkdir -p release-assets + # Rename binaries to include OS in filename + if [ -f artifacts/openhands-cli-ubuntu-latest/openhands ]; then + cp artifacts/openhands-cli-ubuntu-latest/openhands release-assets/openhands-linux + fi + if [ -f artifacts/openhands-cli-macos-latest/openhands ]; then + cp artifacts/openhands-cli-macos-latest/openhands release-assets/openhands-macos + fi + ls -la release-assets/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: release-assets/* + draft: true + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.CLI_RELEASE_TOKEN }} diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 57098f286e..4713f6ea22 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -16,6 +16,8 @@ on: jobs: release: runs-on: blacksmith-4vcpu-ubuntu-2204 + # Only run for tags that don't contain '-cli' + if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-cli') steps: - uses: actions/checkout@v4 - uses: useblacksmith/setup-python@v6 @@ -32,3 +34,34 @@ jobs: run: ./build.sh - name: publish run: poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }} + + release-cli: + name: Publish CLI to PyPI + runs-on: ubuntu-latest + # Only run for tags that contain '-cli' + if: startsWith(github.ref, 'refs/tags/') && contains(github.ref, '-cli') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Build CLI package + working-directory: openhands-cli + run: | + # Clean dist directory to avoid conflicts with binary builds + rm -rf dist/ + uv build + + - name: Publish CLI to PyPI + working-directory: openhands-cli + run: | + uv publish --token ${{ secrets.PYPI_TOKEN }} diff --git a/openhands-cli/build.py b/openhands-cli/build.py index a608e5ec03..70b0e205cd 100755 --- a/openhands-cli/build.py +++ b/openhands-cli/build.py @@ -25,10 +25,8 @@ dummy_agent = get_default_agent( llm=LLM( model='dummy-model', api_key='dummy-key', - metadata=get_llm_metadata(model_name='dummy-model', agent_name='openhands'), + metadata=get_llm_metadata(model_name='dummy-model', llm_type='openhands'), ), - working_dir=WORK_DIR, - persistence_dir=PERSISTENCE_DIR, cli_mode=True, )