grr-cli 0.4.0

Google tools from the terminal, at maximum performance: zero-config Gmail, Calendar, Drive, Contacts, Chat and Forms over HTTP/3
---
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>