Five 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 / arm64)
The project’s Homebrew tap serves arm64 macOS and x86_64/arm64 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/tap
brew trust debanjanbasu/tap
brew install grr
Windows (x64 and ARM64)
Every release generates the Windows x64 and ARM64 manifests from the shipped archives and auto-submits them to microsoft/winget-pkgs via komac (currently awaiting initial moderator merge on PR #457479). Until that first merge lands, install the Windows x64 or ARM64 release archive from GitHub Releases.
winget
winget install debanjanbasu.grr
Prebuilt archives
Five targets are published: macos-aarch64, linux-x86_64, and linux-aarch64 as .tar.zst (zstd level 22), and windows-x86_64 and windows-aarch64 as .zip. The binary inside is grr (grr.exe on Windows) — no renaming on extraction. 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.
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
./target/release/grr
step two
Log in once. One credential, 14 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. GRR_TOKEN_STORE=file skips the keyring altogether — what a script, an agent, or CI wants, since an OS approval prompt is a hang rather than a question.
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. gmail list returns the API's own wrapper object.
$ grr gmail users messages list --user-id me --max-results 5
{"messages":[{"id":"19f8ab2c","threadId":"19f8ab2c"}],"resultSizeEstimate":5}
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 command 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_TOKEN_STORE=file keeps grr away from the OS keyring: the token goes to <cache dir>/grr/token.json, with no approval dialog that only a human at the keyboard can clear — the right default for a script, an agent, or CI.
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 service APIs behind every command in the index (gmail, calendar-json, drive, people, chat, forms, tasks, docs, sheets, slides, script, analyticsadmin, analyticsdata, searchconsole) 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 asks for a small default scope set at grr auth login — Gmail, Drive, Calendar, Tasks, Docs, Sheets, Slides, and the sign-in scopes — and widens it with grr auth login --service <name> when a task needs more (--all-scopes requests the whole index). The default is small on purpose: Google’s consent screen stops submitting past roughly 27 ticked boxes on an unverified project, so requesting everything makes a first-time login impossible. Consent is still not per-method, and every login merges with the account’s existing grant, so widening never drops access.
Before a write, grr api call … --dry-run prints the exact URL, body, and scopes the method declares without sending anything. Scope is reported for inspection, not consulted as a gate — the dry run is still the cheapest way to see what a call touches.
The default grant covers Gmail, Calendar, Drive, Tasks, Docs, Sheets, Slides and sign-in; every other service — People, Chat, Forms, Apps Script, Search Console, Analytics Admin, Analytics Data, the admin surfaces — is one grr auth login --service <name> away, and a live call succeeds as soon as the API is enabled on your Cloud project. A 403 names the scope: widen the grant with that service command, or check the API is enabled. Discovery API has the full breakdown.
And when you are ready to check a flag rather than trust a page: grr schema dumps the whole command tree as JSON, and grr <service> --help is authoritative.