rsclaw 2026.6.26

AI Agent Engine Compatible with OpenClaw
name: Release CLI

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to release (e.g. v2026.4.6)"
        required: true

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always
  RSCLAW_BUILD_VERSION: ${{ inputs.tag || github.ref_name }}
  RSCLAW_BUILD_DATE: ${{ github.event.head_commit.timestamp || github.event.repository.updated_at }}

jobs:
  build:
    name: CLI ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # Linux runners pinned to 22.04 (glibc 2.35) so the system
          # libc headers don't expose C23 `__isoc23_*` symbols that the
          # zigbuild 2.34 pin can't resolve. The 24.04 runners pulled
          # those in via system libs and broke linking with
          # `__isoc23_strtoull@GLIBC_2.38` and friends on 2026-06-11.
          # Switch back once cargo-zigbuild handles the C23 leak.
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-22.04
            archive: tar.gz
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-22.04-arm
            archive: tar.gz
          - target: x86_64-apple-darwin
            os: macos-14
            archive: tar.gz
          - target: aarch64-apple-darwin
            os: macos-14
            archive: tar.gz
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            archive: zip
          - target: aarch64-pc-windows-msvc
            os: windows-latest
            archive: zip

    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: "1.91"
          targets: ${{ matrix.target }}

      # Fallback for an intermittent dtolnay/rust-toolchain@master regression
      # where `targets:` does not actually install the target on some runner
      # images (E0463 "can't find crate for `core`" on macos-14 x86_64).
      - name: Ensure rust target installed
        shell: bash
        run: rustup target add ${{ matrix.target }} || true

      - uses: Swatinem/rust-cache@v2
        with:
          key: cli-${{ matrix.target }}

      - name: Install protoc (Linux)
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y protobuf-compiler libwayland-dev libpipewire-0.3-dev libegl-dev libxkbcommon-dev libgbm-dev

      - name: Install protoc (macOS)
        if: runner.os == 'macOS'
        run: brew install protobuf

      - name: Install protoc (Windows)
        if: runner.os == 'Windows'
        run: choco install protoc -y

      # Linux glibc pinning via cargo-zigbuild + zig as the linker. Without
      # this, binaries built on ubuntu-24.04 link against glibc 2.39 and fail
      # at runtime on Ubuntu 22.04 boxes ("GLIBC_2.39 not found").
      #
      # Pin = 2.34 — covers Ubuntu 22.04 / Debian 12 / RHEL 9 / Fedora 35+.
      # Bumped from 2.31 on 2026-06-11 because `libpipewire-0.3.so` (pulled
      # in transitively by `xcap` for Wayland screen-capture support on
      # Linux runners) references `dlclose@GLIBC_2.34`, `pthread_once@GLIBC_2.34`,
      # `pthread_attr_setstacksize@GLIBC_2.34`. The 2.31 pin made these
      # undefined and ld.lld failed the link with `--no-allow-shlib-undefined`.
      # Ubuntu 20.04 went EOL in 2025-04 so dropping it is acceptable.
      - name: Install zigbuild (Linux only)
        if: runner.os == 'Linux'
        run: |
          pip install --user ziglang
          cargo install --locked cargo-zigbuild

      - name: Build (Linux, glibc-pinned via zigbuild)
        if: runner.os == 'Linux'
        run: cargo zigbuild --release --target ${{ matrix.target }}.2.34

      - name: Build (macOS)
        if: runner.os == 'macOS'
        run: cargo build --release --target ${{ matrix.target }}

      - name: Build (Windows)
        if: runner.os == 'Windows'
        run: cargo build --release --target ${{ matrix.target }}

      - name: Package (tar.gz)
        if: matrix.archive == 'tar.gz'
        run: |
          cd target/${{ matrix.target }}/release
          tar czf ../../../rsclaw-${{ github.ref_name }}-${{ matrix.target }}.tar.gz rsclaw
          cd ../../..

      - name: Package (zip)
        if: matrix.archive == 'zip'
        shell: pwsh
        run: |
          Compress-Archive -Path "target/${{ matrix.target }}/release/rsclaw.exe" `
            -DestinationPath "rsclaw-${{ github.ref_name }}-${{ matrix.target }}.zip"

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: rsclaw-${{ matrix.target }}
          path: rsclaw-${{ github.ref_name }}-${{ matrix.target }}.${{ matrix.archive }}

  release:
    name: Create CLI Release
    runs-on: ubuntu-latest
    needs: build
    if: always() && !cancelled()
    steps:
      - uses: actions/checkout@v6

      - uses: actions/download-artifact@v8
        with:
          path: artifacts
          merge-multiple: true

      - name: Generate checksums
        run: |
          cd artifacts
          sha256sum rsclaw-* 2>/dev/null > SHA256SUMS.txt || true

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ inputs.tag || github.ref_name }}
          generate_release_notes: true
          files: |
            artifacts/rsclaw-*
            artifacts/SHA256SUMS.txt

  update-tap:
    name: Update homebrew-tap Formula
    runs-on: ubuntu-latest
    needs: release
    # Only on real tag pushes; skip on workflow_dispatch reruns where the
    # tap is already up-to-date or the operator wants manual control.
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
    steps:
      - name: Checkout tap repo
        uses: actions/checkout@v6
        with:
          repository: rsclaw-ai/homebrew-tap
          token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
          path: tap

      - name: Fetch SHA256SUMS from the release we just published
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          tag="${{ github.ref_name }}"
          gh release download "$tag" -R rsclaw-ai/rsclaw -p SHA256SUMS.txt -O sha.txt
          # Each line: "<sha256>  rsclaw-<tag>-<target>.tar.gz"
          extract() { awk -v t="$1" '$2 == "rsclaw-'"$tag"'-"t".tar.gz" {print $1}' sha.txt; }
          {
            echo "SHA_AA_DARWIN=$(extract aarch64-apple-darwin)"
            echo "SHA_X86_DARWIN=$(extract x86_64-apple-darwin)"
            echo "SHA_AA_LINUX=$(extract aarch64-unknown-linux-gnu)"
            echo "SHA_X86_LINUX=$(extract x86_64-unknown-linux-gnu)"
          } >> "$GITHUB_ENV"

      - name: Regenerate Formula
        run: |
          tag="${{ github.ref_name }}"
          cat > tap/Formula/rsclaw.rb <<EOF
          class Rsclaw < Formula
            desc "AI Agent Engine Compatible with OpenClaw"
            homepage "https://github.com/rsclaw-ai/rsclaw"
            license any_of: ["MIT", "Apache-2.0"]

            on_macos do
              if Hardware::CPU.arm?
                url "https://github.com/rsclaw-ai/rsclaw/releases/download/${tag}/rsclaw-${tag}-aarch64-apple-darwin.tar.gz"
                sha256 "${SHA_AA_DARWIN}"
              else
                url "https://github.com/rsclaw-ai/rsclaw/releases/download/${tag}/rsclaw-${tag}-x86_64-apple-darwin.tar.gz"
                sha256 "${SHA_X86_DARWIN}"
              end
            end

            on_linux do
              if Hardware::CPU.arm?
                url "https://github.com/rsclaw-ai/rsclaw/releases/download/${tag}/rsclaw-${tag}-aarch64-unknown-linux-gnu.tar.gz"
                sha256 "${SHA_AA_LINUX}"
              else
                url "https://github.com/rsclaw-ai/rsclaw/releases/download/${tag}/rsclaw-${tag}-x86_64-unknown-linux-gnu.tar.gz"
                sha256 "${SHA_X86_LINUX}"
              end
            end

            def install
              bin.install "rsclaw"
            end

            test do
              assert_match(/\d+\.\d+\.\d+/, shell_output("#{bin}/rsclaw --version"))
            end
          end
          EOF

      - name: Commit + push
        working-directory: tap
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          if git diff --quiet -- Formula/rsclaw.rb; then
            echo "Formula already up-to-date — nothing to push."
            exit 0
          fi
          git add Formula/rsclaw.rb
          git commit -m "rsclaw ${{ github.ref_name }}"
          git push