grr-cli 0.13.1

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: Refresh discovery index

# Google's Discovery documents change whenever they add a method. The index
# in src/discovery/ is committed so builds stay hermetic and `grr api list`
# works offline, which means something has to notice upstream drift.
#
# This workflow is that something: a daily (and manual) run that refetches
# the documents, and opens a pull request when the distilled index differs.
# Same shape as the Dependabot automations: the repository never edits
# itself, a human (or auto-merge) reviews the diff.
#
# Four artifacts are derived from that index, and all of them move together:
# src/commands/generated.rs (the CLI tree), skills/** (the agent skills), and
# site/src/data/discovery-coverage.ts (the counts the site prints). The site
# table used to be transcribed by hand, which is how it ended up advertising
# Discovery revisions a month stale.

on:
  schedule:
    # 04:17 UTC. Off the hour on purpose — every scheduled workflow in the
    # world fires at :00 and GitHub queues them.
    - cron: "17 4 * * *"
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: discovery-refresh
  cancel-in-progress: true

jobs:
  refresh:
    name: refresh
    runs-on: ubuntu-26.04-arm
    timeout-minutes: 15
    permissions:
      contents: write
      pull-requests: write
      # gh workflow run below — waking the merge poller.
      actions: write
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2

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

      - name: Refetch, distil, and regenerate the command tree
        id: refresh
        timeout-minutes: 5
        run: |
          node scripts/fetch-discovery.ts
          # The CLI command tree, the agent skills, and the site's coverage
          # table are all generated from the index, so an index change must
          # carry all of them in the same PR — they can never drift apart.
          node scripts/generate-commands.ts
          node scripts/generate-skills.ts
          node scripts/generate-coverage.ts
          # `git status --porcelain` rather than `git diff --quiet`, because a
          # brand-new service makes Google ship a discovery doc that has no
          # tracked file yet: `git diff` sees nothing for an untracked path, so
          # the one update most worth a PR would have opened none.
          if [ -z "$(git status --porcelain -- src/discovery src/commands/generated.rs skills site/src/data/discovery-coverage.ts)" ]; then
            echo "changed=false" >> "$GITHUB_OUTPUT"
            echo "Discovery index is already current."
          else
            echo "changed=true" >> "$GITHUB_OUTPUT"
            git --no-pager diff --stat -- src/discovery src/commands/generated.rs site/src/data/discovery-coverage.ts
          fi

      - uses: dtolnay/rust-toolchain@nightly
        if: steps.refresh.outputs.changed == 'true'
        timeout-minutes: 5
        with:
          components: rust-src

      - uses: Swatinem/rust-cache@v2
        if: steps.refresh.outputs.changed == 'true'
        timeout-minutes: 5

      - name: Verify the Rust side still builds against the new index
        if: steps.refresh.outputs.changed == 'true'
        timeout-minutes: 10
        run: cargo test --lib

      - name: Summarise the delta for the PR body
        if: steps.refresh.outputs.changed == 'true'
        id: summary
        timeout-minutes: 2
        run: |
          TABLE=$(node -e "
            const fs=require('fs');
            const cur=JSON.parse(fs.readFileSync('src/discovery/manifest.json','utf8'));
            const rows=Object.entries(cur.services)
              .map(([n,i]) => '| '+n+' | '+i.methods+' | '+i.scopes+' | '+i.revision+' |');
            console.log(rows.join('\n'));
          ")
          {
            echo "summary<<EOF"
            echo "$TABLE"
            echo "EOF"
          } >> "$GITHUB_OUTPUT"
          {
            echo '### Discovery index after refresh'
            echo
            echo '| service | methods | scopes | revision |'
            echo '|---|---|---|---|'
            echo "$TABLE"
          } >> "$GITHUB_STEP_SUMMARY"

      - name: Open a pull request
        id: pr
        if: steps.refresh.outputs.changed == 'true'
        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/discovery-refresh
          title: "chore(discovery): refresh Google Discovery index"
          commit-message: "chore(discovery): refresh Google Discovery index"
          labels: dependencies
          # Spell out the same paths the changed-detection step checked, so
          # the default commit-all cannot sweep an unrelated modified file
          # into the PR. Exactly one convention across every automation PR.
          add-paths: |
            src/discovery
            src/commands/generated.rs
            skills
            site/src/data/discovery-coverage.ts
          body: |
            Automated refresh of `src/discovery/` from Google's Discovery
            Service, run by `.github/workflows/discovery.yml`. The
            regenerated command tree (`src/commands/generated.rs`), the agent
            skills (`skills/**`), and the site's coverage table
            (`site/src/data/discovery-coverage.ts`) ride along, so the PR
            always contains the index and everything derived from it in
            lockstep.

            The Rust tests ran against this index, so a method whose shape
            changed incompatibly would have failed the build rather than
            shipping.

            | service | methods | scopes | discovery revision |
            |---|---|---|---|
            ${{ steps.summary.outputs.summary }}

            Review the diff for surprising removals — a method disappearing
            usually means Google renamed it upstream.

      - 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"