agentusage 1.3.0

Local-first agent usage tracking
name: Automatic Release

on:
  push:
    branches: [main]

permissions:
  contents: write

concurrency:
  group: automatic-release-main
  cancel-in-progress: false

jobs:
  version:
    name: Version and publish source
    if: ${{ !contains(github.event.head_commit.message, 'chore(release):') }}
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      revision: ${{ steps.revision.outputs.revision }}
    steps:
      - name: Check out Rust project only
        uses: actions/checkout@v6
        with:
          sparse-checkout: |
            .github
            Cargo.toml
            Cargo.lock
            CHANGELOG.md
            LICENSE
            Makefile
            README.md
            docs
            src
          fetch-depth: 0

      - name: Install stable Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: Install cargo-edit
        run: cargo install cargo-edit --locked

      - name: Bump minor version
        run: cargo set-version --bump minor

      - name: Verify release source
        run: |
          cargo fmt --all -- --check
          cargo check --locked
          cargo test --locked
          cargo clippy --all-targets --all-features --locked -- -D warnings
          cargo package --locked --allow-dirty --no-verify

      - name: Read version
        id: version
        shell: bash
        run: echo "version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')" >> "$GITHUB_OUTPUT"

      - name: Update changelog
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          python3 - <<'PY'
          import datetime
          import os
          import pathlib
          import subprocess

          path = pathlib.Path("CHANGELOG.md")
          text = path.read_text() if path.exists() else "# Changelog\n\n## [Unreleased]\n"
          previous = subprocess.run(
              ["git", "describe", "--tags", "--abbrev=0"],
              capture_output=True,
              text=True,
              check=False,
          ).stdout.strip()
          revision_range = f"{previous}..HEAD" if previous else "HEAD"
          entries = subprocess.run(
              ["git", "log", revision_range, "--pretty=format:- %s (%h)"],
              capture_output=True,
              text=True,
              check=True,
          ).stdout.strip() or "- Maintenance release"
          section = (
              f"## [{os.environ['VERSION']}] - "
              f"{datetime.date.today().isoformat()}\n\n{entries}\n"
          )
          release_notes = (
              f"# agentusage {os.environ['VERSION']}\n\n"
              f"## Changes\n\n{entries}\n"
          )
          pathlib.Path("RELEASE_NOTES.md").write_text(release_notes)
          marker = "## [Unreleased]"
          if marker in text:
              insert_at = text.index(marker) + len(marker)
              text = text[:insert_at] + "\n\n" + section + text[insert_at:]
          else:
              text = text.rstrip() + "\n\n" + section
          path.write_text(text)
          PY

      - name: Upload release notes
        uses: actions/upload-artifact@v6
        with:
          name: release-notes
          path: RELEASE_NOTES.md

      - name: Remove transient release notes
        run: rm RELEASE_NOTES.md

      - name: Commit version and tag
        shell: bash
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add Cargo.toml Cargo.lock CHANGELOG.md
          git commit -m "chore(release): v${VERSION} [skip ci]"
          git tag "v${VERSION}"
          git push origin HEAD:main --follow-tags

      - name: Capture release revision
        id: revision
        run: echo "revision=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

      - name: Publish crate
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish --locked

  build:
    name: Build ${{ matrix.target }}
    needs: version
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            archive: agentusage-linux-x86_64.tar.gz
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            archive: agentusage-linux-aarch64.tar.gz
            cross: true
          - target: aarch64-apple-darwin
            os: macos-14
            archive: agentusage-macos-aarch64.tar.gz
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            archive: agentusage-windows-x86_64.zip

    steps:
      - name: Check out released revision only
        uses: actions/checkout@v6
        with:
          ref: ${{ needs.version.outputs.revision }}
          sparse-checkout: |
            Cargo.toml
            Cargo.lock
            CHANGELOG.md
            LICENSE
            README.md
            src

      - name: Install stable Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Install cross
        if: matrix.cross == true
        uses: taiki-e/install-action@cross

      - name: Build Unix target
        if: runner.os != 'Windows'
        shell: bash
        run: |
          if [ "${{ matrix.cross }}" = "true" ]; then
            cross build --release --locked --target "${{ matrix.target }}"
          else
            cargo build --release --locked --target "${{ matrix.target }}"
          fi

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

      - name: Package Unix archive
        if: runner.os != 'Windows'
        shell: bash
        run: |
          mkdir dist
          cp "target/${{ matrix.target }}/release/agentusage" dist/agentusage
          cp "target/${{ matrix.target }}/release/au" dist/au
          cp CHANGELOG.md LICENSE README.md dist/
          tar -C dist -czf "${{ matrix.archive }}" agentusage au CHANGELOG.md LICENSE README.md

      - name: Package Windows archive
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force dist | Out-Null
          Copy-Item "target/${{ matrix.target }}/release/agentusage.exe" dist/agentusage.exe
          Copy-Item "target/${{ matrix.target }}/release/au.exe" dist/au.exe
          Copy-Item CHANGELOG.md,LICENSE,README.md dist/
          Compress-Archive -Path dist/agentusage.exe,dist/au.exe,dist/CHANGELOG.md,dist/LICENSE,dist/README.md -DestinationPath "${{ matrix.archive }}"

      - name: Upload archive
        uses: actions/upload-artifact@v6
        with:
          name: ${{ matrix.archive }}
          path: ${{ matrix.archive }}

  github-release:
    name: Publish GitHub release
    needs: [version, build]
    runs-on: ubuntu-latest
    steps:
      - name: Download archives
        uses: actions/download-artifact@v7
        with:
          path: dist
          merge-multiple: true

      - name: Create checksums
        working-directory: dist
        run: sha256sum *.tar.gz *.zip > SHA256SUMS

      - name: Publish GitHub release
        uses: softprops/action-gh-release@v3
        with:
          tag_name: v${{ needs.version.outputs.version }}
          body_path: dist/RELEASE_NOTES.md
          files: |
            dist/*.tar.gz
            dist/*.zip
            dist/SHA256SUMS