grr-cli 2.5.0

Google tools from the terminal, at maximum performance: 401 methods across 14 Google APIs in one generated command tree, zero-config, over HTTP/3
Documentation
name: Demo recording

# Self-maintaining demo: the cast is GENERATED from the current source, so it
# can never drift from what the binary actually does. A push that changes the
# CLI surface (src/), the docs the demo quotes, or the generator itself
# re-records demo/demo.cast in CI and opens a PR; the weekly cron keeps it
# fresh against dependency and toolchain drift.
#
# Recording in CI (--ci-record) covers everything the runner can do:
#   - the offline/dry-run segments always run (no credentials);
#   - `grr ask` runs for real when the TYPESAFE_API_KEY secret is present;
#   - the opencode agent segment is attempted live (opencode's free default
#     models, or credentials passed via env secrets); if that fails, the
#     committed cast's agent segment is CARRIED FORWARD so a CI regeneration
#     can never strip the agent demo from the site;
#   - `grr transport` needs a Google token and stays a --local-only segment
#     (the committed cast keeps it via the same carry-forward, so it is only
#     refreshed by explicit local runs).

on:
  push:
    branches: [main]
    paths:
      - demo/**
      - scripts/generate-demo.ts
      - src/**
      - Cargo.toml
      - .github/workflows/demo.yml
  schedule:
    # 03:41 UTC Sundays — off the hour, off the weekday peak.
    - cron: "41 3 * * 0"
  release:
    # The cast quotes `grr --version` and closes with the version, so a
    # published release is exactly when the recording must be redone — the
    # push on the version bump covers the bump itself, this covers the tag.
    types: [published]
  workflow_dispatch:
    inputs:
      tag:
        description: "Release tag to attach the GIF to (defaults to the latest release)"
        required: false
        type: string

permissions:
  contents: read

concurrency:
  group: demo-recording
  cancel-in-progress: true

jobs:
  record:
    name: re-record the demo
    runs-on: ubuntu-24.04
    timeout-minutes: 45
    permissions:
      contents: write
      pull-requests: write
      # gh workflow run below — waking the merge poller.
      actions: write
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2

      # The demo must show the CURRENT build, so the binary is built from
      # source — the same nightly + build-std setup the release uses.
      - uses: dtolnay/rust-toolchain@nightly
        timeout-minutes: 10
        with:
          components: rust-src
      - uses: Swatinem/rust-cache@v2
        timeout-minutes: 10
      - name: Build grr from source
        run: cargo build --release
        timeout-minutes: 30

      - uses: actions/setup-node@v7
        timeout-minutes: 5
        with:
          node-version: "26"

      # opencode is the agentic harness the agent segment drives grr with.
      # Installed best-effort: the segment also has a carry-forward path.
      - name: Install opencode (best effort)
        continue-on-error: true
        timeout-minutes: 5
        run: npm install -g opencode-ai@latest || npm install -g opencode || true

      - name: Re-record the cast
        id: record
        timeout-minutes: 15
        env:
          GRR_BIN: ${{ github.workspace }}/target/release/grr
          # The System One key lets the natural-language segment record for
          # real. Never logged; consumed by the `grr ask` child process.
          TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
        run: |
          node scripts/generate-demo.ts --ci-record

      - name: Validate the new cast
        timeout-minutes: 5
        run: node scripts/generate-demo.ts --validate

      # The GIF is the shareable form of the demo: Reddit and HN posts show
      # inline media, and a link alone gets scrolled past. Rendered here so
      # it can never drift from the cast — and attached to the latest
      # release, so there is always a stable URL and it never grows the git
      # history. Rendering params are the size/readability point: the raw
      # recording is 19 MB at native fps; 10 fps at 1.3x is 8 MB.
      - name: Install agg (cast → gif)
        # asciinema's agg is NOT on crates.io (a `cargo install agg` resolves a
        # wrong/old crate and exits 101), so install the static binary from its
        # GitHub release instead. `gh release download` selects the asset by
        # glob, so there is no API JSON to parse and no interpreter in the loop.
        timeout-minutes: 5
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          dir="$RUNNER_TEMP/agg"
          mkdir -p "$dir"
          gh release download -R asciinema/agg -p '*x86_64-unknown-linux-gnu' -D "$dir"
          chmod +x "$dir"/agg-*
          sudo mv "$dir"/agg-* /usr/local/bin/agg
          agg --version

      - name: Render and attach the demo GIF
        timeout-minutes: 10
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
        run: |
          agg -q --fps-cap 10 --speed 1.3 --idle-time-limit 0.8 --font-size 13 demo/demo.cast grr-demo.gif
          ls -lh grr-demo.gif
          # The dispatching release names itself so a delayed run after a newer
          # release cannot attach this cast to the wrong one; falling back to
          # `gh release view` keeps a manual/weekly run working.
          tag="${{ inputs.tag }}"
          if [ -z "$tag" ]; then tag="$(gh release view --json tagName --jq .tagName)"; fi
          gh release upload "$tag" grr-demo.gif --clobber
          echo "attached grr-demo.gif to $tag"

      - name: Open a PR when the recording changed
        id: pr
        uses: peter-evans/create-pull-request@v8
        timeout-minutes: 5
        with:
          author: "debanjanbasu <10209115+debanjanbasu@users.noreply.github.com>"
          committer: "debanjanbasu <10209115+debanjanbasu@users.noreply.github.com>"
          branch: chore/demo-recording
          title: "chore(demo): re-record the terminal demo from source"
          commit-message: "chore(demo): re-record the terminal demo from source"
          labels: dependencies
          add-paths: |
            demo/demo.cast
          body: |
            Automated re-recording of `demo/demo.cast` — run by
            `.github/workflows/demo.yml` against the current source.

            Every segment is real output from the just-built binary; the
            agent segment is either a fresh live run (when opencode worked
            on the runner) or carried forward from the previous cast. Review
            the diff for surprising removals — a vanished segment means the
            binary changed behaviour and the site copy may need a follow-up.

      - name: Wake the merge poller
        # CI on a token-authored PR waits in action_required until the poller
        # approves it (GitHub's 2026-06 bot-event gate). Waking the poller now
        # instead of waiting for the next cron tick removes the measured 5-7
        # minutes of approval latency before any CI compute starts. The poller
        # is idempotent, and the cron remains the safety net.
        if: steps.pr.outputs.pull-request-number != ''
        timeout-minutes: 2
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
        run: gh workflow run automation-merge.yml --ref main || echo "::warning::poller dispatch failed"