getting started

Install, log in once, and read some mail.

Four platforms, one login, and output you can pipe. This page is the whole first-run path; everything after it lives in Commands and Discovery API.

release binaries are zero-config · no OAuth setup needed
step one

Install the binary.

The published executable is named grr, even though the crates.io package is grr-cli because the short crate name was already taken.

macOS (Apple silicon) and Linux (x86_64)

The project’s Homebrew tap serves arm64 macOS and x86_64 Linux builds. Tap it, trust it, then install by formula name.

Since Homebrew 4.4 third-party taps are untrusted by default, so brew trust is required before Homebrew will install a formula from the tap. Skipping it fails the install, it is not a warning.

homebrew
brew tap debanjanbasu/homebrew
brew trust debanjanbasu/homebrew
brew install grr

Windows (x64 and ARM64)

Both Windows architectures are available through winget, and both are also published as release archives.

winget
winget install debanjanbasu.grr

Prebuilt archives

Four targets are published: macos-aarch64 and linux-x86_64 as .tar.zst (zstd level 22), and windows-x86_64 and windows-aarch64 as .zip. Archives ship a SHA256SUMS file.

Windows keeps .zip deliberately: Explorer’s built-in extractor only reads deflate, so an lzma2-in-zip archive would be unopenable without 7-Zip.

Open GitHub Releases

crates.io

The default CLI enables HTTP/3, and reqwest’s QUIC support is gated behind Rust nightly plus the reqwest_unstable cfg, so a source install needs both.

cargo · unix
RUSTFLAGS="--cfg reqwest_unstable" cargo install grr-cli --locked
cargo · powershell
$env:RUSTFLAGS='--cfg reqwest_unstable'
cargo install grr-cli --locked

From a local clone

The repository pins nightly in rust-toolchain.toml and its Cargo config already sets the unstable cfg and build-std flags, so a clone builds without extra ceremony.

local clone
git clone https://github.com/debanjanbasu/grr-cli.git
cd grr-cli
cargo build --release --locked
./target/release/grr
step two

Log in once. One credential, ten services.

Release binaries are compiled with a Google OAuth client, so there is nothing to configure. Source builds ship no client and need grr auth setup first.

PKCE is always enabled. The browser flow binds a loopback listener on port 3434 and completes when Google redirects back; --device prints a URL and a code instead, for SSH sessions and containers.

Tokens are written to the operating system keyring — Windows Credential Manager, macOS Keychain, or the Linux Secret Service over D-Bus. On a headless machine with no keyring daemon, grr falls back to <cache dir>/grr/token.json and imports it back into the keyring later. Check which one you got from the token_backend field that grr auth login prints.

first run
# 1. Log in once. Release binaries ship with an OAuth client compiled in.
$ grr auth login
{"authenticated":true,"token_backend":"os-keyring","token_preview":"ya29.a0AfH6SMBx..."}

# 2. Prove the credential works.
$ grr auth status
{"authenticated":true,"email":"you@example.com","messages_total":48213,...}

# 3. Do something real.
$ grr gmail users messages list --user-id me --max-results 5
[{"id":"19f8ab2c","threadId":"19f8ab2c","snippet":"Build notes ..."}, ...]
step three

Verify before you automate.

Two commands tell you whether the install is healthy: grr auth status proves the credential, grr transport proves the wire.

grr auth status

Makes a live Gmail users.getProfile call, so it fails loudly when the token is missing, expired without a refresh token, or revoked. The exit status is the signal, not the prose.

Returns authenticated, email, messages_total, threads_total, and history_id. Save history_id: it is the cursor for grr gmail users history list.

grr transport

Reports the negotiated protocol and the runtime features detected on this machine. HTTP/3 is always requested and always compiled in; fell_back tells you whether QUIC was actually used.

This is the one command that prints a plain-text report rather than JSON, so do not pipe it into a JSON parser.

grr transport
$ grr transport
negotiated_protocol: HTTP_3
http3_requested: true
http3_effective: true
fell_back: false
cpus: 10
io_uring: false
http3_always_compiled: true
runtime: tokio multi-thread (auto-sized to cores)

A negotiated_protocol of HTTP_3 means QUIC worked end to end. HTTP_2 with fell_back: true is normal behind a proxy or on a network that blocks UDP; nothing is broken, you are just on the fallback path.

Headless and CI

Two commands behave differently when there is no terminal, and both refuse to hang rather than block forever on a prompt.

  • grr auth login --device prints the verification URL and a user code, then polls until you approve. It is the only login path that works over plain SSH.
  • grr auth setup needs --client-id and --client-secret when stdin is not a TTY, and exits with an error instead of waiting for input. Add --print-only to get the recipe and the config path without writing anything.
  • grr auth setup --force overwrites an existing ~/.grr/config.toml. Without it, an existing file is never replaced.
  • --enable-apis shells out to gcloud services enable for the ten service APIs (gmail, calendar-json, drive, people, chat, forms, tasks, docs, sheets, slides) when gcloud is on PATH. It is best-effort: a failure warns and setup still succeeds.
headless · setup
# Headless machine: print a URL + code instead of opening a browser.
$ grr auth login --device
Visit https://google.com/device and enter code: ABCD-EFGH

# Bring your own OAuth client (source builds only; needs a TTY otherwise).
$ grr auth setup --print-only
{"instructions":"grr needs one Google OAuth client. ...","config_path":"...","wrote_config":false,...}

Client ids are shape-checked before anything is written: the value must end in .apps.googleusercontent.com and the secret must be at least ten characters. A truncated paste is caught immediately rather than becoming an opaque 400 from Google’s token endpoint later.

Where configuration comes from

Three sources, highest priority first:

  • Environment variables: GRR_OAUTH__CLIENT_ID and GRR_OAUTH__CLIENT_SECRET (double underscore nests, single underscore becomes a dash).
  • ~/.grr/config.toml, or whatever GRR_CONFIG_PATH points at.
  • The OAuth client compiled into the binary at build time. This is the zero-config path, and it only applies when nothing else supplied a client id.

There is no transport, concurrency, or output setting to tune. Those are compile-time constants; the only runtime knob is the OAuth client.

Know which scopes you actually hold

A release build consents to sixteen scopes. That set does not cover everything in the Discovery index — and every method reports the narrowest scope it accepts, so you can check before you rely on one.

Before a write, grr api call … --dry-run prints the exact URL, body, and scopes the method declares without sending anything. It is the cheapest way to find out whether a method is inside your consent boundary before you rely on it.

grr api call · --dry-run
$ grr api call gmail.users.messages.list --param userId=me --dry-run
{
  "method": "GET",
  "url": "https://gmail.googleapis.com/gmail/v1/users/me/messages",
  "body": null,
  "scopesRequested": ["https://mail.google.com/", "..."],
  "leastPrivilegeScope": "https://www.googleapis.com/auth/gmail.readonly",
  "dryRun": true
}

Tasks is the one service that needs a scope this build does not hold (…/auth/tasks), so its methods are visible in grr api list and describable, but will not authorize until the scope set is extended and you re-run grr auth login. Discovery API has the full breakdown.