Google tools from the terminal, at maximum performance. grr-cli is one published Rust package built around the grr command-line binary. Fourteen Google APIs share one OAuth login and one command tree — generated straight from Google's own Discovery Service — while stdout stays clean and machine-readable.
Project site: grr-cli.pages.dev · Privacy
grr is an independent project and is not affiliated with or endorsed by Google.
grr auth login
grr gmail users messages list --user-id me --q "in:inbox" --max-results 5
grr drive files list --page-size 10
grr api describe calendar.events.list
grr schemaPrebuilt binaries — macOS arm64, Linux x86_64, Linux aarch64, Windows x86_64, and Windows on ARM (aarch64): GitHub Releases
Release binaries are zero-config — an OAuth client is compiled in, so grr auth login works immediately. Archives are .tar.zst (zstd level 22) on unix and .zip on Windows; binaries are UPX-packed with --best --lzma where the compressor supports it — the workflow verifies the packed copy still executes and falls back to the unpacked binary with a warning if not.
From source (the default CLI build requires Rust nightly — see Development):
cargo install --git https://github.com/debanjanbasu/grr-clior from a local clone at the repository root:
git clone https://github.com/debanjanbasu/grr-cli
cd grr-cli
cargo install --path .A source build has no OAuth client compiled in — see Authentication and configuration.
Package managers:
winget install debanjanbasu.grr # Windows (manifest not yet published — submission automated via REPO_WINGET_TOKEN; see Packaging & status)
cargo install grr-cli # crates.ioHomebrew (macOS + Linux), from the debanjanbasu/tap tap:
brew tap debanjanbasu/tap
brew trust debanjanbasu/tap
brew install grrbrew trust is required, not decorative: since Homebrew 4.4 third-party taps are untrusted by default, and brew install refuses to install a formula from an untrusted tap until you trust it once. Skipping that line is the most common first-run failure.
The crates.io CLI build needs nightly Rust and RUSTFLAGS="--cfg reqwest_unstable" for HTTP/3; the prebuilt releases avoid that source-build step. cargo install also produces a binary with no embedded OAuth client, so budget five minutes for grr auth setup (or a .env) on that path.
Shell completions — grr completions <shell> prints scripts from the same tree as grr --help. Bash and zsh: write the script once (refresh after upgrading); both load it lazily (the zsh script is ~2.6 MB). PowerShell and Elvish snippets below run the generator on each shell startup.
# zsh: any directory on $fpath; if ~/.zfunc is not on it yet, add
# `fpath=(~/.zfunc $fpath)` to ~/.zshrc ABOVE compinit (or oh-my-zsh's source line)
mkdir -p ~/.zfunc && grr completions zsh > ~/.zfunc/_grr
# bash (bash-completion 2.x loads this lazily)
mkdir -p ~/.local/share/bash-completion/completions
grr completions bash > ~/.local/share/bash-completion/completions/grr
# PowerShell: add to $PROFILE
grr completions powershell | Out-String | Invoke-Expression
# elvish: add to rc.elv
eval (grr completions elvish | slurp)Fish is not supported yet: clap_complete's fish generator stops below two levels of subcommand nesting, and grr's methods sit three to five levels deep.
brew tap debanjanbasu/tap
brew trust debanjanbasu/tap
brew install grr
grr auth login
grr gmail users getProfile --user-id meThere is nothing to configure first: a release binary already carries an OAuth client, so grr auth login opens the browser, you consent, and every service works. Headless machine? grr auth login --device prints a URL + code instead of opening a browser — that flow authenticates as a separate client of type TVs and Limited Input devices (Google's device endpoint rejects the Desktop client with Invalid client type), which release binaries also carry; a source build supplies it with GRR_DEVICE_CLIENT_ID / GRR_DEVICE_CLIENT_SECRET. Google also caps what that flow may ask for: only drive.file, calendar, calendar.readonly, the legacy Calendar/Contacts feeds, userinfo and openid are grantable, so a device login asks for the same flag-derived set the browser flow would (--service / --all-scopes work here too) intersected with that allowlist — one scope outside it fails the whole request. Gmail, Chat, Docs, Sheets, Slides, Tasks, Forms, Apps Script, Analytics and the admin APIs still need the browser flow, and --device --all-scopes will not get them.
Building from source instead of installing a release? Read Authentication and configuration before your first grr invocation.
Release binaries are zero-config. build.rs reads GRR_CLIENT_ID / GRR_CLIENT_SECRET from the build environment — GitHub Actions repo secrets for the official builds — and compiles them in, so GitHub Releases and Homebrew installs need no config file and no console visit.
Source builds bring their own client. cargo install grr-cli compiles the published sources with nothing in the build environment, so that binary has no embedded client. Any one of these fixes it:
- A
.envnext toCargo.toml— copy.env.exampleto.env, fill in both values, rebuild.build.rsreads the environment first and.envsecond, so every build after that is zero-config too..envis gitignored; never commit it. - Environment variables for a single build —
GRR_CLIENT_ID=… GRR_CLIENT_SECRET=… cargo build --release. grr auth setup— no rebuild required, because it writes~/.grr/config.toml(Windows:%USERPROFILE%\.grr\config.toml) instead.
Create and store an OAuth client. Release builds ship with one, so this is only needed when using
your own client
Usage: grr auth setup [OPTIONS]
Options:
--client-id <CLIENT_ID> Google OAuth client id (…apps.googleusercontent.com).
Prompted for when omitted
--client-secret <CLIENT_SECRET> Google OAuth client secret. Prompted for when omitted
--print-only Print the instructions and the resolved path, but write
nothing. Useful for a first look, and for agents that only
want the recipe
--force Overwrite an existing ~/.grr/config.toml instead of refusing
--enable-apis Try `gcloud services enable` for the APIs when gcloud is on
PATH
-f, --format <FORMAT> Output format [default: json] [possible values: json, jsonl,
table, pretty]
--readonly Refuse every write method (POST/PATCH/PUT/DELETE); only reads
run
--deny-service <SERVICE> Refuse every method of this service (repeatable)
--deny-verb <VERB> Refuse this HTTP verb (repeatable)
-h, --help Print help
It checks the client-id shape before writing anything (must end in .apps.googleusercontent.com, and a truncated paste is rejected), rejects a truncated secret, writes the file mode 0600 on unix, and refuses to clobber an existing config.toml without --force. It prints the exact Cloud Console URLs and the gcloud services enable line covering every API behind a command — gmail, calendar-json, drive, people, chat, forms, tasks, docs, sheets, slides, script, analyticsadmin, analyticsdata, and searchconsole …googleapis.com — and --print-only hands you that recipe without touching disk, which is the right first command when you have no client at all:
grr auth setup --print-onlyBecause it runs before any client is resolved, grr auth setup works even when nothing is configured yet: it is the escape hatch, not a last resort. Under a non-TTY stdin (CI, agents, pipes) it errors instead of hanging, so pass --client-id and --client-secret explicitly rather than relying on the prompts.
Resolution order at runtime: GRR_OAUTH__CLIENT_ID / GRR_OAUTH__CLIENT_SECRET → ~/.grr/config.toml → the client compiled into the binary (env wins over the file; the compiled-in client is the fallback). GRR_CONFIG_PATH moves the file; RUST_LOG sets the log level.
Embedding a client secret is acceptable here because Google treats installed-app client secrets as non-confidential — the flow is protected by PKCE, which is always on. The secret lives in GitHub Actions repo secrets and is compiled in at release time; it must never enter the repository or a CI log.
To create a client from scratch, follow docs/gcp-setup.md — you only need that if you are building from source or deliberately want your own client.
The project/fork is Google Rust Rewrite; the Google consent-screen application is named Rust Rewrite. The consent screen is where that shorter name appears.
grr ask "<request>" routes plain words to a typed method + parameters and prints the plan; --run executes it through the same call path as grr api. Routing is three System One judgments — service, then method, then parameters — each a small HTTP request. The model only ever selects among candidates the CLI supplies: the 14 services, a service's methods, a parameter's enum values or spans found in your request. It cannot invent a method id or a value — the CLI decides what runs.
Set TYPESAFE_API_KEY and it works against TypeSafe's hosted Jev:
export TYPESAFE_API_KEY=...
grr ask "show my unread messages"The env route is preferred: a key in a config file can leak with the file. The [systemone] block in ~/.grr/config.toml is the only other surface:
| Key | Default | Meaning |
|---|---|---|
endpoint |
https://api.typesafe.ai/v1/systemone |
any provider speaking the System One contract (POST state + typed questions, back structured answers) |
model |
jev-latest |
model name sent to that endpoint |
api-key |
— | sent as Authorization: Bearer; empty behaves as absent |
confidence-threshold |
0.6 |
below this method-choice confidence the plan is flagged, never blocked |
Any key also accepts the generic GRR_SYSTEMONE__ENDPOINT / GRR_SYSTEMONE__MODEL / GRR_SYSTEMONE__API_KEY env form (the same GRR_ route as GRR_OAUTH__*), and TYPESAFE_API_KEY overrides api-key. The key never appears in the printed plan or in a request body — it rides only in the Authorization header.
Pointing endpoint/model at your own provider changes only which model ranks the CLI-supplied candidates; the candidate sets, the typed parameter fill, and what actually executes stay grr's. grr ask honors the global safety flags (--readonly, --deny-service <name>, --deny-verb <VERB>) whether or not you pass --run, and the gate runs as soon as the method is resolved — before the parameter-fill judgment and before any Google request is built or sent.
grr gmail users messages list --user-id me --q "from:github.com" --max-results 10
grr gmail users messages get --user-id me --id 191f8ab2 --param-format metadata
grr gmail users threads list --user-id me --max-results 10
grr calendar events list --calendar-id primary --time-min 2026-10-01T00:00:00Z --max-results 10
grr drive files list --q "name contains 'report'" --page-size 10
grr drive files export --file-id <id> --mime-type application/pdf
grr chat spaces messages create --parent spaces/AAAA --body-file ./message.json
grr sheets spreadsheets values get --spreadsheet-id 1AbC… --range Sheet1!A1:B10
grr tasks tasklists list
grr transport
grr schemaEvery command that prints data takes -f/--format json|jsonl|table|pretty (default json); grr transport, grr completions and grr skills install print plain text instead. For gmail users messages get, the Gmail MIME parameter named format arrives as --param-format full|metadata|minimal|raw (see the collision rule below), while -f/--format still selects the printed output format. Logs go to stderr, so stdout is always parseable:
grr gmail users messages list --user-id me --q "in:inbox" --max-results 1 | jq -r '.[0].id'grr --version prints the semver on its first line (scripts depend on that), then the crab mascot: in full 24-bit-colour pixel art on a terminal that advertises COLORTERM/direct colour, and as plain ASCII everywhere else — a pipe, TERM=dumb, or NO_COLOR. Set CLICOLOR_FORCE=1 to force the colour logo through a pipe (grr --version | less -R); NO_COLOR still wins over it.
The command tree is generated from the committed Discovery index by scripts/generate-commands.ts (CI-gated with --check), so this table is deliberately coarse: grr --help and grr schema are the source of truth, and every one of the 401 methods is a real, typed command.
| Group | What it covers | Methods |
|---|---|---|
grr auth |
login [--device] [--account name] [--service name] [--all-scopes], status [--account name], setup [--client-id] [--client-secret] [--print-only] [--force] [--enable-apis] — all accept -f json|jsonl|table|pretty; status reports the credential's own scope set under scopes_granted (what Google actually approved — login's scopes_requested is what was asked for) |
— |
grr ask |
"<request>" [--run] [--dry-run] [--service] [--method] — natural language to a typed method + params via a System One model; prints the plan by default |
- |
grr mcp |
an MCP (JSON-RPC over stdio) server exposing every method as a typed tool; speaks the stateless 2026-07-28 revision only — server/discover instead of the initialize handshake; --readonly for read-only |
- |
grr api |
list [--service X] [--filter substr] [--grouped], describe <id>, call <id> …, refresh [--service X] — every method by id, the flat escape hatch |
401 |
grr schema |
the whole command tree as JSON | — |
grr completions |
<bash|elvish|powershell|zsh> — a shell completion script for the whole tree, offline; see Install |
— |
grr transport |
negotiated HTTP version + runtime features | — |
grr skills |
install [--dir PATH] [--claude] [--force], list — the 16 packaged skill files (the core skill, one per service, and their index) into the user-level skills directory (~/.agents/skills, offline; --claude mirrors to ~/.claude/skills) |
— |
grr gmail |
messages, threads, drafts, labels, history, attachments, filters, forwarding, POP/IMAP, send-as, CSE, delegates, watches | 79 |
grr calendar |
calendars, events, instances, ACL, free/busy, colors, settings | 38 |
grr drive |
files, permissions, comments, replies, revisions, changes, drives, apps, approvals | 64 |
grr people |
contacts, connections, contact groups, other contacts, directory people | 24 |
grr chat |
spaces, members, messages, reactions, media, custom emoji, read state | 54 |
grr forms |
form bodies, responses, watches, publish settings | 10 |
grr tasks |
tasklists and the tasks inside them | 14 |
grr docs |
documents get / create / batchUpdate |
3 |
grr sheets |
spreadsheets, values, batch operations, developer metadata | 17 |
grr slides |
presentations, pages, thumbnails | 5 |
grr script |
script projects, deployments, versions, processes, running functions | 16 |
grr analyticsadmin |
accounts, properties, data streams, custom dimensions and metrics, conversion events | 55 |
grr analyticsdata |
standard, realtime, pivot and batch reports, audience exports | 11 |
grr searchconsole |
search analytics queries, sitemaps, sites, URL inspection | 11 |
Safety profiles. --readonly, --deny-service <name> and --deny-verb <VERB> are global flags honored by the generated tree, grr api call, grr ask and grr mcp alike — they parse before or after the subcommand, and turn destructive methods into actionable errors:
grr --readonly gmail users messages delete --user-id me --id abc # refused: DELETE
grr --deny-service chat chat spaces list # refused: the chat serviceEvery service command above is generated from the committed index by scripts/generate-commands.ts — there are no hand-written per-service commands and no hand-written per-service clients. scripts/generate-commands.ts reads the committed Discovery index (src/discovery/*.json, ~430 KiB across the 14 services) and emits the whole tree into src/commands/generated.rs — 401 leaves, 994 typed flags, using clap's builder API. A daily workflow refetches Google's Discovery Service, regenerates the index along with the command tree, the per-service agent skills, and the site's coverage table, and opens a PR, so new API surface reaches you without waiting for a grr release. The tree and the index are generated artifacts: never hand-edit them.
The rules, so you can predict any command without memorizing it:
- The naming rule. A leaf mirrors its Discovery method id:
gmail.users.messages.list→grr gmail users messages list. Resources nest as subcommands; each leaf also carries its bare method name as a visible alias (list,get— camelCase methods keep their casing, e.g.getProfile). - Typed flags per method. Parameter names come from the same ids:
userId→--user-id,maxResults→--max-results. Integers are parsed asi64, booleans are presence flags, repeated parameters repeat (--label-ids a --label-ids b), enum parameters validate their values, and required parameters are enforced by clap. - The collision rule. A parameter named
format,query,params,body-file,dry-run, orhelpwould collide with the shared escape hatches, so it is exposed as--param-format/--param-query/ … (seeRESERVED_FLAG_IDSinscripts/script-utils.ts). - Untyped bodies. Discovery's request schemas are not part of the index, so
POST/PATCH/PUTbodies pass through--params <JSON>(merged; typed flags win) or--body-file <PATH|->verbatim. - Every leaf also carries the escape hatches:
--params <JSON>,--body-file, repeatable--query KEY=VALUE,--dry-run, and-f json|jsonl|table|pretty.
Dispatch resolves the leaf's id against the embedded index and funnels into one shared call path — the same engine grr api call uses. The two routes are interchangeable:
grr api call gmail.users.messages.list --param userId=me --dry-run
grr gmail users messages list --user-id me --dry-run
# byte-identical outputgrr api stays as the flat escape hatch: list [--service] [--filter] [--grouped] to browse, describe <id> for parameters and scopes, call <id> to invoke, refresh [--service] to pull the index forward between releases. Because the index is embedded rather than fetched, both grr api list and grr schema work with no network at all.
Consent starts small: grr auth login requests Gmail, Drive, Calendar, Tasks, Docs, Sheets, Slides and the sign-in scopes, and grr auth login --service <name> adds one service's scopes from the embedded index (--all-scopes asks for the whole ~112-scope union). That default exists because Google's consent screen stops submitting past roughly 27 ticked boxes on an unverified project — with no error at all — so asking for everything makes a first-time login impossible. Every login carries include_granted_scopes, so widening merges with what the account already granted instead of replacing it. Chat's app-authentication scopes (chat.bot, chat.app.*, chat.import) are never requested: a user consent screen cannot show them, and one of them fails the whole authorization with invalid_scope. Everything the index can express stays reachable that way, so a call is never refused for want of a narrower sibling. grr api describe still reports each method's least-privilege scope for a human deciding what a call touches. A 403 therefore means a scope outside the current grant (the message names it — run grr auth login --service <name> for that service) or an API that is not enabled on your Cloud project. Every API behind a command (tasks.googleapis.com, docs.googleapis.com, sheets.googleapis.com, slides.googleapis.com) must still be enabled on your Cloud project — see docs/gcp-setup.md.
- Zero-config. Release binaries carry an OAuth client compiled in by
build.rs, so a fresh install runsgrr auth loginwith nothing to configure.~/.grr/config.tomlis the override, not the prerequisite, and holds only the OAuth client (ID, optionally a secret) plus — if you pointgrr askat your own model — its[systemone]block (seegrr askand System One). Redirect URI, pool sizes, timeouts, and retry policy are compile-time constants tuned for Google's frontends (src/core/http.rs), and the consent set is derived from the embedded index rather than a knob.GRR_CONFIG_PATHoverrides the file location,RUST_LOGthe log level (GRR_OAUTH__*env vars exist for headless overrides) — nothing else is configurable, on purpose. - Generated, namespaced services. The command tree is compiled from the Discovery index — the same machine-readable description Google publishes — so method additions land as a daily PR instead of a hand-written backlog. Mail is
grr gmail …; Calendar, Drive, People, Chat, Forms, Tasks, Docs, Sheets, and Slides live alongside it, and account-level concerns stay top-level (grr auth,grr api,grr ask,grr mcp,grr transport,grr schema,grr completions,grr skills). One login covers the default services;grr auth login --service <name>widens it to any other. - Self-migrating installs. A newer binary brings the agent skills it installed up to date on its own: files you never touched are replaced, files you edited get a 3-way merge against the bytes grr last wrote, and a conflict leaves your file untouched with the incoming version beside it (
.grr-incoming). Files grr did not install are never touched. The check is lazy — a version compare; nothing is hashed until something actually needs merging. - stdout purity. Logs go to stderr, results go to stdout, so
| jqalways works.-f jsonlstreams arrays one object per line. - Keyring-first token storage. Tokens live in the OS keyring (Windows Credential Manager, macOS Keychain, Linux Secret Service via D-Bus), with automatic fallback to
<cache dir>/grr/token.jsonon headless systems. A token found in the fallback file auto-imports into the keyring on first sight.GRR_TOKEN_STORE=fileskips the keyring entirely — the switch for scripts, agents, and CI, where an OS approval prompt is a hang rather than a question. - Agent-first.
grr schemadumps the complete command tree as JSON with zero configuration — the machine-readable contract for AI agents, discoverable without touching a config file or scraping--help. One fast CLI replaces per-service MCP servers: no MCP setup, justgrr schema. There is also a packaged agent skill at skills/grr/SKILL.md — see Agent skills.
grr ships a packaged agent skill: skills/grr/SKILL.md — the discovery-first discipline (schema → api list → api describe → --dry-run), the method-id naming rule, and the output contract in one file, written for any AI agent. The core skill plus the 14 generated per-service skills are compiled into the binary, so grr skills install writes them into ~/.agents/skills/ with no network (Codex, opencode, Cursor, Gemini CLI, Copilot CLI and omp all read that location; --claude mirrors into ~/.claude/skills/ for Claude Code, which reads nothing else):
grr skills install # all 16 into ~/.agents/skills/ (idempotent; identical files are a no-op)
grr skills install --claude # also mirror into ~/.claude/skills/
grr skills install --force # replace files that differ from the packaged copy
grr skills list # the packaged set, and what each target directory holdsor install straight from the repository with the skills CLI:
npx skills add https://github.com/debanjanbasu/grr-cliThe site's agents guide carries the same contract for humans and harness authors.
- HTTP/3 (QUIC) by default — prior-knowledge h3 with one authenticated probe at startup and silent HTTP/2 fallback;
grr transportshows what was actually negotiated. - Tokio multi-threaded runtime, auto-sized to cores — no thread pool to tune.
- In-flight request valve — a semaphore (not a thread pool) caps concurrent HTTP requests at 64, staying under Gmail's per-user rate limits so bursts don't self-DOS into 429s. 429s are retried honoring
Retry-After(waits capped at 30s) — a header-less 429 retries on the transient backoff (100–800 ms) instead of surfacing; aRetry-Afterpast the cap is reported (Rate limited: retry after Ns) rather than slept through, and HTTP-dateRetry-Aftervalues are honored. Whole-request timeout is 30s. API requests ride the same probed engine asgrr transport: one engine per service origin, probed once per process, HTTP/2-prior-knowledge fallback rebuilt automatically if the probe fails; a 401 on the data plane fails fast with thegrr auth loginhint. - Compression always on — gzip, deflate, zstd, and brotli response decompression.
- io_uring file I/O is a Linux-only target-specific dependency, auto-detected at runtime; it is not a Cargo feature.
Measured startup, binary size, and request-latency numbers against the other Google Workspace CLIs live at grr-cli.pages.dev/compare/, checked nightly by an automated workflow and re-measured whenever a new version ships.
grr-cli is one published crate at the repository root. src/lib.rs builds the library target grr_cli; src/main.rs is a thin wrapper over src/cli.rs, and the binary is named grr.
.
├── src/
│ ├── core/ # auth/device/oauth/server/store, http.rs,
│ │ # config.rs, config_loader.rs, error.rs,
│ │ # runtime.rs (the io_uring probe)
│ ├── commands/ # auth.rs, api.rs, setup.rs, transport.rs,
│ │ # skills/, mcp.rs, ask.rs, completions.rs,
│ │ # safety.rs (the static
│ │ # + generated.rs (GENERATED — the
│ │ # whole service tree, ~470 KiB) and gen_dispatch.rs
│ │ # (resolves leaf ids, funnels into the shared path)
│ ├── discovery.rs # loader over the embedded index
│ ├── discovery/ # generated *.json index (~430 KiB, committed,
│ │ # refreshed daily by workflow PR)
│ ├── schema.rs
│ ├── output.rs
│ ├── cli.rs
│ ├── lib.rs
│ └── main.rs
└── tests/ # 5 integration test files
One shared core, no per-service client modules: the CLI speaks Discovery through a single call path (src/commands/api.rs), and the typed request/response models of the 0.3.x library era are gone. The repository also contains the assets/, site/ (the Astro site at grr-cli.pages.dev, deployed from main by Cloudflare Pages), packaging/, and scripts/ material used for the project site and distribution.
grr requires Rust nightly — the build script fails with a clear message on any other toolchain. rust-toolchain.toml floats on the latest nightly and supplies the components needed by the build, while .cargo/config.toml sets the reqwest_unstable cfg for HTTP/3.
cargo build
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --all --check
cargo run -- api list
node scripts/fetch-discovery.ts # refresh the Discovery index
node scripts/generate-commands.ts # regenerate the service command tree
node scripts/generate-skills.ts # regenerate skills/<service>/SKILL.md
node scripts/generate-coverage.ts # regenerate the site coverage table
node scripts/generate-changelog.ts # regenerate CHANGELOG.md + site dataEach generator takes --check and exits 1 when its output is stale — that is the CI gate. Change the index or the generator, never src/commands/generated.rs by hand.
- Tests never touch real credentials — token paths are injected, and wiremock serves the API endpoints.
RUST_LOG=debugtraces requests; the default level mutes quinn_udp's harmless IPv6 warnings (info,quinn_udp=error).- A local build needs an OAuth client before a live call can work: copy
.env.exampleto.envand fill it in (compiled in bybuild.rs), or rungrr auth setupto write~/.grr/config.toml. See CONTRIBUTING.md. cargo run -- api listandcargo run -- schemaneed no client at all — the index is embedded.
| Channel | Install | Status |
|---|---|---|
| GitHub Releases | 5-platform binaries (macOS arm64, Linux x86_64, Linux aarch64, Windows x86_64, Windows on ARM) built on v* tags, UPX-packed, .tar.zst on unix and .zip on Windows |
live — every push to main releases; releases always carries the newest |
| crates.io | cargo install grr-cli (binary installs as grr; needs nightly + RUSTFLAGS="--cfg reqwest_unstable" for the default CLI HTTP/3 build, and brings no embedded OAuth client) |
live — one crate, published on every release. Trusted publishing uses OIDC (no stored API tokens) |
| winget | winget install debanjanbasu.grr |
not yet published — node scripts/generate-winget.ts <tag> derives the x64 + ARM64 manifests from the released archives (grr-<tag>-winget-manifests.tar.gz) and release-binaries.yml auto-submits them to microsoft/winget-pkgs via komac (PR #457479, awaiting moderator merge) |
| Homebrew | brew tap debanjanbasu/tap && brew trust debanjanbasu/tap && brew install grr (tap: debanjanbasu/homebrew-tap, formula Formula/grr.rb, updated automatically on every release) |
live (arm64 macOS + x86_64/arm64 Linux) |
The project publishes one package, grr-cli, whose binary is grr. v* tags trigger the release workflow, and crates.io publishing is handled through trusted publishing.
MIT — see LICENSE.