---
import BaseLayout from '../layouts/BaseLayout.astro';
import ArchitectureCards from '../components/ArchitectureCards.astro';
import CodeBlock from '../components/CodeBlock.astro';
import CommandInventory from '../components/CommandInventory.astro';
import FeatureRow from '../components/FeatureRow.astro';
import Mascot from '../components/Mascot.astro';
import SectionIntro from '../components/SectionIntro.astro';
import Stats from '../components/Stats.astro';
import heroTerminal from '../assets/hero-terminal.svg';
import artMail from '../assets/art-mail.svg';
import artCalendar from '../assets/art-calendar.svg';
import artDrive from '../assets/art-drive.svg';
import artContacts from '../assets/art-contacts.svg';
import artChat from '../assets/art-chat.svg';
import artForms from '../assets/art-forms.svg';
import glyphMail from '../assets/glyph-mail.svg';
import glyphCalendar from '../assets/glyph-calendar.svg';
import glyphDrive from '../assets/glyph-drive.svg';
import glyphContacts from '../assets/glyph-contacts.svg';
import glyphChat from '../assets/glyph-chat.svg';
import glyphForms from '../assets/glyph-forms.svg';
const base = import.meta.env.BASE_URL;
const releaseUrl = 'https://github.com/debanjanbasu/grr-cli/releases';
const services = [
{
name: 'Gmail',
eyebrow: '01 / inbox',
art: artMail,
glyph: glyphMail,
accent: '#f97316',
description: 'Search, read, compose, label, filter, route mail, and batch without leaving your shell.',
subfeatures: ['Search and read messages', 'Compose, label, and route mail', 'Batch and filter messages', 'Manage threads, history, and send-as'],
command: 'grr gmail message search "in:inbox" --max 5',
commands: [
'grr gmail message search "in:inbox" --max 5',
'grr gmail msg filter-list',
'grr gmail thread list',
'grr gmail send-as verify alias@example.com',
],
},
{
name: 'Calendar',
eyebrow: '02 / time',
art: artCalendar,
glyph: glyphCalendar,
accent: '#fb923c',
description: 'List calendars, manage events, watch changes, and check free-busy windows in one pass.',
subfeatures: ['List calendars and events', 'Create, update, move, and delete', 'Watch changes and free-busy', 'Manage sharing, settings, and colors'],
command: 'grr calendar events --max 10',
commands: [
'grr calendar events --max 10',
'grr calendar patch <calendar-id> <event-id> --summary "Updated"',
'grr calendar watch <calendar-id> --webhook https://example.com/hook',
],
},
{
name: 'Drive',
eyebrow: '03 / files',
art: artDrive,
glyph: glyphDrive,
accent: '#ea580c',
description: 'Find files, inspect metadata, manage sharing and comments, and control storage.',
subfeatures: ['Find files and inspect metadata', 'Upload, download, and export', 'Manage sharing and comments', 'Restore, rename, and control storage'],
command: 'grr drive list --trashed --max 20',
commands: [
'grr drive list --trashed --max 20',
'grr drive comment-add <file-id> --text "Note"',
'grr drive quota',
],
},
{
name: 'Contacts',
eyebrow: '04 / people',
art: artContacts,
glyph: glyphContacts,
accent: '#c2410c',
description: 'Query people, organize groups, batch-read profiles, and keep photos close to your workflows.',
subfeatures: ['Search and organize people', 'Create, update, and manage groups', 'Batch-read profiles and photos', 'Adopt contacts and keep photos close'],
command: 'grr contacts list --max 20',
commands: [
'grr contacts list --max 20',
'grr contacts get-batch --people people/123',
'grr contacts photo-set <people/123> --path photo.jpg',
],
},
{
name: 'Chat',
eyebrow: '05 / rooms',
art: artChat,
glyph: glyphChat,
accent: '#fdba74',
description: 'Browse spaces, manage members, read messages, and react without context switching.',
subfeatures: ['Browse spaces and members', 'Add and remove members', 'Read and send messages', 'React and unreact to messages'],
command: 'grr chat spaces --max 10',
commands: [
'grr chat spaces --max 10',
'grr chat member-add <space-id> --user user@example.com',
'grr chat react <space-id> <message-id> --emoji "๐"',
],
},
{
name: 'Forms',
eyebrow: '06 / signals',
art: artForms,
glyph: glyphForms,
accent: '#c2410c',
description: 'Read and update forms, pull responses, and manage watch channels for lightweight automation.',
subfeatures: ['Read and update forms', 'Pull response data', 'Create and manage watch channels', 'Renew and delete watches'],
command: 'grr forms get <form-id>',
commands: [
'grr forms get <form-id>',
'grr forms responses <form-id> --max 50',
'grr forms watch-renew <form-id> <watch-id>',
],
},
];
const commandSurfaces = [
{
name: 'Gmail',
commands: 'message, label, draft, send, thread, history, send-as, profile, watch, import, msg; msg: label, trash, untrash, delete, batch-label, batch-delete, filter-list, filter, filter-create, filter-delete, forwarding-list, forwarding-create, forwarding-delete, autoforwarding, autoforwarding-set, pop, pop-set, imap, imap-set; thread: list, label, trash, untrash, delete; send-as: list, get, create, update, delete, verify',
},
{
name: 'Calendar',
commands: 'list, events, get, new, create, update, delete, free-busy, set, instances, patch, move, watch, stop, colors, settings, share, shares, unshare',
},
{
name: 'Drive',
commands: 'list, search, get, mkdir, trash, restore, copy, empty-trash, download, upload, rename, delete, export, share, shares, unshare, comments, comment, comment-add, comment-delete, revisions, revision, quota',
},
{
name: 'Contacts',
commands: 'list, search, get, create, update, delete, groups, group, group-new, group-rename, group-delete, group-add, group-remove, get-batch, others, adopt, photos, photo-set, photo-remove',
},
{
name: 'Chat',
commands: 'spaces, space, space-new, space-rename, space-delete, members, member, member-add, member-remove, messages, send, react, reactions, unreact',
},
{
name: 'Forms',
commands: 'get, responses, new, update, watch, watches, watch-delete, watch-renew',
},
];
const architecture = [
{
title: 'HTTP/3 when available',
copy: 'The transport probes authenticated QUIC at startup and falls back silently to HTTP/2 when it cannot negotiate.',
glyph: glyphChat,
},
{
title: 'nightly Rust',
copy: 'The CLI enables HTTP/3 through reqwest and QUIC support, which is gated behind Rust nightly and the reqwest_unstable cfg.',
glyph: glyphCalendar,
},
{
title: 'keyring tokens',
copy: 'PKCE is always enabled; tokens are kept in the operating system keyring, with a local file fallback for headless systems.',
glyph: glyphContacts,
},
{
title: 'streaming output',
copy: 'Results go to stdout and logs go to stderr, so jq, scripts, and agents get predictable data by default.',
glyph: glyphMail,
},
];
const sessionExample = `$ grr auth login
โ browser consent complete
$ grr gmail message search "in:inbox" --max 1
[{"id":"191f8ab2","threadId":"191f8ab2","subject":"Build notes"}]
$ grr gmail message get 191f8ab2 --message-format metadata --body --max-length 2000
{"message":{...},"body":"..."}`;
const pipeExample = `$ grr gmail message search "from:github.com" --max 10 | jq -r '.[0].id'
191f8ab2
$ grr transport
{"http_version":"h3","fallback":"h2"}`;
const libraryExample = `[dependencies]
grr-cli = { version = "0.4.0", default-features = false, features = ["gmail"] }`;
const faqs = [
{
question: 'What is grr?',
answer: 'grr is a Rust CLI for Google services from the terminal. It combines Gmail, Calendar, Drive, Contacts, Chat, and Forms in one namespaced binary with machine-readable output and a single OAuth login.',
},
{
question: 'How does OAuth work?',
answer: 'You create a Google Cloud Desktop OAuth client, put its client ID in ~/.grr/config.toml, and run grr auth login. PKCE is always enabled; tokens are kept in the operating system keyring, with a local file fallback for headless systems. grr auth login --device prints a URL and code for machines without a browser.',
},
{
question: 'Do I need to log in again for 0.4?',
answer: 'Yes. Version 0.4 adds chat.delete, chat.memberships, chat.messages.reactions, and contacts.other.readonly scopes. Run grr auth login again to grant the expanded permissions; device flow is available for headless machines.',
},
{
question: 'Is grr affiliated with Google?',
answer: 'No. grr is an independent project. It is not affiliated with, sponsored by, or endorsed by Google LLC. It uses public Google APIs and your own Google account credentials.',
},
{
question: 'How do I install it?',
answer: 'Use a prebuilt GitHub Release, the documented Homebrew or winget package, or cargo install grr-cli from crates.io. The install page has the exact commands, including the nightly Rust setup needed by the CLI HTTP/3 build.',
},
{
question: 'Why does it need nightly Rust?',
answer: 'The CLI enables HTTP/3 through reqwest and QUIC support, which is gated behind Rust nightly and the reqwest_unstable cfg. The repo pins the required toolchain and sets the cfg for in-repo builds; the CLI falls back to HTTP/2 at runtime if QUIC is unavailable.',
},
];
---
<BaseLayout
title="Google tools from the terminal"
description="grr is a zero-config Rust CLI for Gmail, Calendar, Drive, Contacts, Chat, and Forms, with clean machine-readable output and HTTP/3 when available."
>
<section class="hero section" aria-labelledby="hero-title">
<div class="container hero-grid">
<div class="hero-copy reveal">
<span class="eyebrow"><span class="status-dot" aria-hidden="true"></span> Six services ยท one login</span>
<h1 id="hero-title">Google tools,<br /><span>at terminal speed.</span></h1>
<p class="hero-lede">Zero-config, maximum-performance Google tools from the terminal. One binary, six services, and output that plays nicely with jq.</p>
<div class="hero-actions">
<a class="button button-primary" href={releaseUrl} target="_blank" rel="noreferrer">GitHub releases <span aria-hidden="true">โ</span></a>
<a class="button button-secondary" href={`${base}install/`}>Install grr <span aria-hidden="true">โ</span></a>
</div>
<p class="hero-note">Configure one OAuth client ID. The CLI handles the rest.</p>
</div>
<div class="hero-visual reveal">
<div class="hero-stage">
<div class="hero-stage-sheen" aria-hidden="true"></div>
<span class="scene-label">grr // online</span>
<img class="hero-terminal-art" src={heroTerminal.src} alt="A dimensional grr terminal workspace" width="960" height="640" fetchpriority="high" decoding="async" />
<div class="hero-contact-shadow" aria-hidden="true"></div>
<div class="hero-mascot">
<Mascot label="Pixel-art orange crab mascot perched on the grr terminal" />
</div>
<span class="hero-stage-caption">one login ยท six services</span>
</div>
</div>
</div>
</section>
<Stats />
<section class="services-section section" id="services" aria-labelledby="services-title">
<div class="container">
<SectionIntro
id="services-title"
eyebrow="six services ยท one login"
title="The useful parts of your Google account, namespaced."
copy="Keep commands close to the work they support. Every data command accepts JSON, JSONL, table, or pretty output; JSON is the default."
/>
<div class="feature-rows">
{services.map((service, index) => <FeatureRow service={service} reverse={index % 2 === 1} />)}
</div>
<CommandInventory surfaces={commandSurfaces} />
</div>
</section>
<section class="crate-section section" aria-labelledby="library-title">
<div class="container crate-grid">
<div class="crate-copy reveal">
<span class="eyebrow">use it as a library</span>
<h2 id="library-title">One crate. Only the services you need.</h2>
<p>The published package is <code>grr-cli</code>. Its default features are <code>default = ["cli", "http3"]</code>; the <code>cli</code> feature enables the complete binary and all six services (the <code>grr</code> binary uses <code>required-features = ["cli"]</code>), while <code>gmail</code>, <code>calendar</code>, <code>drive</code>, <code>people</code>, <code>chat</code>, and <code>forms</code> can be selected independently. With <code>default-features = false</code>, the library builds on stable Rust over HTTP/2. HTTP/3 is the <code>http3</code> feature and needs nightly Rust plus the <code>reqwest_unstable</code> cfg.</p>
</div>
<div class="code-slab reveal">
<CodeBlock code={libraryExample} label="cargo ยท library" />
</div>
</div>
</section>
<section class="architecture-section section" aria-labelledby="features-title">
<div class="container">
<div class="architecture-heading reveal">
<span class="eyebrow">built for the shell</span>
<h2 id="features-title">Less glue.<br />More signal.</h2>
<p>grr is deliberately small at the interface: predictable commands, fast transport, and no hidden service setup.</p>
</div>
<ArchitectureCards cards={architecture} />
<div class="session-panel">
<div class="session-copy reveal">
<span class="eyebrow">one clean session</span>
<h3>From inbox to insight, without the tab.</h3>
<p>Search a message, fetch a bounded body, and keep the output parseable. The command tree is available as JSON for agents with <code>grr schema</code>.</p>
<a class="button button-secondary" href="https://github.com/debanjanbasu/grr-cli#usage-highlights" target="_blank" rel="noreferrer">Browse command examples <span aria-hidden="true">โ</span></a>
</div>
<div class="session-terminal reveal">
<CodeBlock code={sessionExample} label="terminal ยท grr" />
</div>
</div>
</div>
</section>
<section class="machine-section section" aria-labelledby="session-title">
<div class="container machine-grid">
<div class="machine-copy reveal">
<span class="eyebrow">machine-friendly by default</span>
<h2 id="session-title">Your shell is the API client.</h2>
<p>Use a human-readable table when you need it, or keep the default JSON and let the next tool take it from there.</p>
</div>
<div class="machine-terminal reveal">
<CodeBlock code={pipeExample} label="pipe-friendly output" />
</div>
</div>
</section>
<section class="link-band section" aria-labelledby="links-title">
<div class="container">
<div class="section-heading reveal">
<span class="eyebrow">go deeper</span>
<h2 id="links-title">The source is the docs.</h2>
</div>
<div class="link-grid">
<a class="link-card reveal" href="https://crates.io/crates/grr-cli" target="_blank" rel="noreferrer">
<strong>crates.io โ</strong>
<span>Package metadata, versions, and the published crate.</span>
</a>
<a class="link-card reveal" href="https://docs.rs/grr-cli" target="_blank" rel="noreferrer">
<strong>docs.rs โ</strong>
<span>Generated API documentation for the published crates.</span>
</a>
<a class="link-card reveal" href="https://github.com/debanjanbasu/grr-cli" target="_blank" rel="noreferrer">
<strong>GitHub โ</strong>
<span>README, releases, source, and the issue tracker.</span>
</a>
</div>
</div>
</section>
<section class="faq-section section" aria-labelledby="faq-title">
<div class="container faq-grid">
<div class="faq-heading reveal">
<span class="eyebrow">questions, answered</span>
<h2 id="faq-title">No mystery meat.</h2>
<p>The short version of the things people usually want to know before putting a Google account behind a terminal tool.</p>
</div>
<div class="faq-list">
{faqs.map((faq) => (
<details class="faq-item reveal">
<summary>{faq.question}</summary>
<div class="faq-answer">{faq.answer}</div>
</details>
))}
</div>
</div>
</section>
<section class="cta-section section" aria-labelledby="cta-title">
<div class="container">
<div class="cta-panel reveal">
<div class="cta-sheen" aria-hidden="true"></div>
<h2 id="cta-title">Your Google workflows deserve a better terminal.</h2>
<p>Grab a binary, point it at your own OAuth client, and let grr get out of the way.</p>
<a class="button button-dark" href={`${base}install/`}>Choose your install path <span aria-hidden="true">โ</span></a>
<p class="legal-note">grr is an independent project and is not affiliated with, sponsored by, or endorsed by Google LLC.</p>
</div>
</div>
</section>
</BaseLayout>