rho-coding-agent 0.15.1

A lightweight agent harness inspired by Pi
name: release-please

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  actions: write
  contents: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    outputs:
      release_created: ${{ steps.release.outputs.release_created }}
      tag_name: ${{ steps.release.outputs.tag_name }}
    steps:
      - id: release
        uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          command: manifest
          config-file: .release-please-config.json
      - uses: actions/checkout@v4
        if: ${{ steps.release.outputs.release_created == 'true' }}
        with:
          ref: ${{ steps.release.outputs.tag_name }}
      - name: Install Rust
        if: ${{ steps.release.outputs.release_created == 'true' }}
        uses: dtolnay/rust-toolchain@stable
      - name: Cache Cargo registry
        if: ${{ steps.release.outputs.release_created == 'true' }}
        uses: swatinem/rust-cache@v2
      - name: Publish crate
        if: ${{ steps.release.outputs.release_created == 'true' }}
        run: cargo publish --locked --token "$CARGO_REGISTRY_TOKEN"
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
      - name: Dispatch Arch package workflow
        if: ${{ steps.release.outputs.release_created == 'true' }}
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ steps.release.outputs.tag_name }}
        run: gh workflow run publish-arch-package.yml --ref "$TAG"

  release-binaries:
    needs: release
    if: ${{ needs.release.outputs.release_created == 'true' }}
    name: release binary ${{ matrix.target }}
    permissions:
      contents: write
      pull-requests: write
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-22.04
            target: x86_64-unknown-linux-gnu
            cache-targets: false
          - os: macos-13
            target: x86_64-apple-darwin
            cache-targets: true
          - os: macos-14
            target: aarch64-apple-darwin
            cache-targets: true
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            cache-targets: true
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.release.outputs.tag_name }}
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Cache Cargo registry
        uses: swatinem/rust-cache@v2
        with:
          cache-targets: ${{ matrix.cache-targets }}
          shared-key: release-binaries-${{ matrix.target }}
      - name: Build binary
        run: cargo build --release --locked --bin rho --target ${{ matrix.target }}
      - name: Sign Windows binary
        if: runner.os == 'Windows'
        shell: pwsh
        env:
          WINDOWS_SIGNING_CERT_BASE64: ${{ secrets.WINDOWS_SIGNING_CERT_BASE64 }}
          WINDOWS_SIGNING_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERT_PASSWORD }}
        run: |
          if ([string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERT_BASE64)) {
            Write-Host "WINDOWS_SIGNING_CERT_BASE64 is not set; skipping Authenticode signing."
            exit 0
          }
          $CertPath = Join-Path $env:RUNNER_TEMP "rho-signing-cert.pfx"
          [IO.File]::WriteAllBytes($CertPath, [Convert]::FromBase64String($env:WINDOWS_SIGNING_CERT_BASE64))
          $Signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter signtool.exe |
            Where-Object { $_.FullName -match "x64" } |
            Select-Object -First 1 -ExpandProperty FullName
          if (-not $Signtool) {
            throw "signtool.exe was not found"
          }
          & $Signtool sign /f $CertPath /p $env:WINDOWS_SIGNING_CERT_PASSWORD /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 target/${{ matrix.target }}/release/rho.exe
          & $Signtool verify /pa target/${{ matrix.target }}/release/rho.exe
      - name: Package Unix binary
        if: runner.os != 'Windows'
        run: |
          mkdir -p dist
          cp target/${{ matrix.target }}/release/rho dist/rho
          tar -C dist -czf rho-${{ matrix.target }}.tar.gz rho
          shasum -a 256 rho-${{ matrix.target }}.tar.gz > rho-${{ matrix.target }}.tar.gz.sha256
      - name: Package Windows binary
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path dist | Out-Null
          Copy-Item target/${{ matrix.target }}/release/rho.exe dist/rho.exe
          Compress-Archive -Path dist/rho.exe -DestinationPath rho-${{ matrix.target }}.zip -Force
          $Hash = (Get-FileHash -Algorithm SHA256 rho-${{ matrix.target }}.zip).Hash.ToLowerInvariant()
          "$Hash  rho-${{ matrix.target }}.zip" | Out-File -Encoding ascii rho-${{ matrix.target }}.zip.sha256
      - name: Upload Unix release assets
        if: runner.os != 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ needs.release.outputs.tag_name }}
        run: gh release upload "$TAG" rho-${{ matrix.target }}.tar.gz rho-${{ matrix.target }}.tar.gz.sha256 --clobber
      - name: Upload Windows release assets
        if: runner.os == 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ needs.release.outputs.tag_name }}
        shell: pwsh
        run: gh release upload "$env:TAG" rho-${{ matrix.target }}.zip rho-${{ matrix.target }}.zip.sha256 --clobber
      - name: Checkout main for Scoop manifest update
        if: runner.os == 'Windows'
        uses: actions/checkout@v4
        with:
          ref: main
      - name: Download Windows checksum
        if: runner.os == 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ needs.release.outputs.tag_name }}
        shell: pwsh
        run: gh release download "$env:TAG" --pattern "rho-${{ matrix.target }}.zip.sha256"
      - name: Update Scoop manifest
        if: runner.os == 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
        shell: pwsh
        run: |
          $Tag = "${{ needs.release.outputs.tag_name }}"
          $Version = $Tag -replace '^rho-coding-agent-v', ''
          $Asset = "rho-${{ matrix.target }}.zip"
          $Hash = ((Get-Content "$Asset.sha256" -Raw) -split '\s+')[0]
          $Url = "https://github.com/${{ github.repository }}/releases/download/$Tag/$Asset"

          $ManifestPath = "bucket/rho.json"
          $Manifest = Get-Content $ManifestPath -Raw | ConvertFrom-Json
          $Manifest.version = $Version
          $Manifest.architecture.'64bit'.url = $Url
          $Manifest.architecture.'64bit'.hash = $Hash
          $Manifest | ConvertTo-Json -Depth 10 | Set-Content -Path $ManifestPath -Encoding UTF8

          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add $ManifestPath
          if (git diff --cached --quiet) {
            Write-Host "Scoop manifest is already up to date."
            exit 0
          }
          $Branch = "build/update-scoop-manifest-$Tag"
          if (git ls-remote --heads origin $Branch) {
            git fetch origin "${Branch}:refs/remotes/origin/${Branch}"
          }
          git checkout -B $Branch
          git commit -m "build(windows): update scoop manifest for $Tag"
          git push --force-with-lease origin "HEAD:$Branch"

          $Title = "build(windows): update scoop manifest for $Tag"
          $BodyFile = New-TemporaryFile
          Set-Content -Path $BodyFile -Value "Updates the Scoop manifest for $Tag."
          $ExistingPr = gh pr list --head $Branch --base main --json number --jq '.[0].number'
          if ($ExistingPr) {
            gh pr edit $ExistingPr --title $Title --body-file $BodyFile
          } else {
            gh pr create --base main --head $Branch --title $Title --body-file $BodyFile
          }