grr-cli 2.5.0

Google tools from the terminal, at maximum performance: 401 methods across 14 Google APIs in one generated command tree, zero-config, over HTTP/3
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
---
import BaseLayout from '../layouts/BaseLayout.astro';
import ArchitectureCards from '../components/ArchitectureCards.astro';
import ApiSurface from '../components/ApiSurface.astro';
import CodeBlock from '../components/CodeBlock.astro';
import CommandInventory from '../components/CommandInventory.astro';
import FeatureRow from '../components/FeatureRow.astro';
import InlineCode from '../components/InlineCode.astro';
import InstallMarquee from '../components/InstallMarquee.astro';
import Mascot from '../components/Mascot.astro';
import SectionIntro from '../components/SectionIntro.astro';
import DemoPlayer from '../components/DemoPlayer.astro';
import Stats from '../components/Stats.astro';
import { demoDurationSeconds } from '../lib/demo-duration';
import heroTerminal from '../assets/hero-terminal.svg?raw';
import { discoveryMethodTotal, discoveryServiceCount, discoveryServices } from '../data/discovery-coverage';
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 glyphContacts from '../assets/glyph-contacts.svg';
import glyphChat from '../assets/glyph-chat.svg';


// The per-service line under each card in the inventory panel; the count is
// read from the coverage table rather than typed, so it cannot drift. An
// unknown service name is a hard build failure, not a silent 0: a rename must
// break the build rather than print a wrong number.
const methodCountFor = (name: string): number => {
  const service = discoveryServices.find((entry) => entry.name === name);
  if (!service) {
    throw new Error(`index.astro: no "${name}" service in the Discovery coverage table`);
  }
  return service.methods;
};
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,
    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 users messages list --user-id me --q "in:inbox" --max-results 5',
    commands: [
      'grr gmail users messages list --user-id me --q "in:inbox" --max-results 5',
      'grr gmail users settings filters list --user-id me',
      'grr gmail users threads list --user-id me --max-results 10',
      'grr gmail users settings sendAs verify --user-id me --send-as-email alias@example.com',
    ],
  },
  {
    name: 'Calendar',
    eyebrow: '02 / time',
    art: artCalendar,
    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 list --calendar-id primary --max-results 10',
    commands: [
      'grr calendar events list --calendar-id primary --max-results 10',
      'grr calendar events patch --calendar-id primary --event-id <event-id> --params \'{"summary":"Updated"}\'',
      'grr calendar events watch --calendar-id primary --params \'{"type":"web_hook","address":"https://example.com/hook"}\'',
    ],
  },
  {
    name: 'Drive',
    eyebrow: '03 / files',
    art: artDrive,
    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 files list --q "trashed = true" --page-size 20',
    commands: [
      'grr drive files list --q "trashed = true" --page-size 20',
      'grr drive comments create --file-id <file-id> --params \'{"content":"Note"}\'',
      'grr drive about get',
    ],
  },
  {
    name: 'Contacts',
    eyebrow: '04 / people',
    art: artContacts,
    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 people people connections list --resource-name people/me --page-size 20',
    commands: [
      'grr people people connections list --resource-name people/me --page-size 20',
      'grr people people getBatchGet --resource-names people/123',
      'grr people people updateContactPhoto --resource-name people/123 --body-file photo.json',
    ],
  },
  {
    name: 'Chat',
    eyebrow: '05 / rooms',
    art: artChat,
    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 list --page-size 10',
    commands: [
      'grr chat spaces list --page-size 10',
      'grr chat spaces members create --parent spaces/<space-id> --params \'{"member":{"name":"users/user@example.com"}}\'',
      'grr chat spaces messages reactions create --parent spaces/<space-id>/messages/<message-id> --params \'{"emoji":"👍"}\'',
    ],
  },
  {
    name: 'Forms',
    eyebrow: '06 / signals',
    art: artForms,
    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 forms get --form-id <form-id>',
    commands: [
      'grr forms forms get --form-id <form-id>',
      'grr forms forms responses list --form-id <form-id> --page-size 50',
      'grr forms forms watches renew --form-id <form-id> --watch-id <watch-id>',
    ],
  },
];
const commandSurfaces = [
  {
    name: 'Gmail',
    commands: `users: messages, threads, drafts, labels, history, settings (filters, forwarding, sendAs, CSE), watch · ${methodCountFor('gmail')} methods`,
  },
  {
    name: 'Calendar',
    commands: `calendars, events, calendarList, freebusy, acl, colors, settings, channels · ${methodCountFor('calendar')} methods`,
  },
  {
    name: 'Drive',
    commands: `files, permissions, comments, replies, revisions, changes, drives, accessproposals, approvals, teamdrives · ${methodCountFor('drive')} methods`,
  },
  {
    name: 'People',
    commands: `people, contactGroups, otherContacts · ${methodCountFor('people')} methods`,
  },
  {
    name: 'Chat',
    commands: `spaces, messages, members, reactions, spaceEvents, customEmojis, media, availability · ${methodCountFor('chat')} methods`,
  },
  {
    name: 'Forms',
    commands: `forms, responses, watches · ${methodCountFor('forms')} methods`,
  },
  {
    name: 'Tasks',
    commands: `tasklists, tasks · ${methodCountFor('tasks')} methods`,
  },
  {
    name: 'Docs',
    commands: `documents · ${methodCountFor('docs')} methods`,
  },
  {
    name: 'Sheets',
    commands: `spreadsheets, values, sheets, developerMetadata · ${methodCountFor('sheets')} methods`,
  },
  {
    name: 'Slides',
    commands: `presentations, pages · ${methodCountFor('slides')} methods`,
  },
  {
    name: 'Apps Script',
    commands: `projects, deployments, versions, processes, scripts · ${methodCountFor('script')} methods`,
  },
  {
    name: 'Analytics Admin',
    commands: `accounts, properties, dataStreams, customDimensions, customMetrics, conversionEvents, keyEvents, firebaseLinks, googleAdsLinks · ${methodCountFor('analyticsadmin')} methods`,
  },
  {
    name: 'Analytics Data',
    commands: `properties, audienceExports · ${methodCountFor('analyticsdata')} methods`,
  },
  {
    name: 'Search Console',
    commands: `searchanalytics, sitemaps, sites, urlInspection, mobileFriendlyTest · ${methodCountFor('searchconsole')} methods`,
  },
];
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
{"authenticated":true,"account":null,"token_backend":"os-keyring","token_preview":"ya29.a0AfH6SMBxLqKm..."}

$ grr gmail users messages list --user-id me --q "in:inbox" --max-results 1
{"messages":[{"id":"19f8ab2c","threadId":"19f8ab2c"}],"resultSizeEstimate":1}

$ grr gmail users messages get --user-id me --id 19f8ab2c --param-format metadata
{"id":"19f8ab2c","threadId":"19f8ab2c","labelIds":["UNREAD","INBOX"],"payload":{"headers":[{"name":"Subject","value":"Build notes"}]},"sizeEstimate":4821}`;
const pipeExample = `$ grr gmail users messages list --user-id me --q "from:github.com" --max-results 10 | jq -r '.messages[0].id'
19f8ab2c

$ 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)`;
const askPlan = `$ grr ask "show my unread gmail"
{"body":null,"confidence":0.88,"dryRun":true,"httpMethod":"GET",
 "leastPrivilegeScope":"https://www.googleapis.com/auth/gmail.readonly",
 "lowConfidence":false,"method":"gmail.users.messages.list",
 "params":{"userId":"me"},"service":"gmail","serviceConfidence":1.0,
 "url":"https://gmail.googleapis.com/gmail/v1/users/me/messages"}
# nothing was sent — rerun with --run to execute this request`;
const faqs = [
  {
    question: 'What is grr?',
    answer: `grr is a Rust CLI for Google services from the terminal. One generated command tree covers ${discoveryMethodTotal} methods across the ${discoveryServiceCount} consumer Google Workspace APIs — Gmail, Calendar, Drive, Contacts, Chat, Forms, Tasks, Docs, Sheets, Slides, Apps Script, Analytics Admin, Analytics Data and Search Console — in one namespaced binary with machine-readable output and a single OAuth login.`,
  },
  {
    question: 'How does OAuth work?',
    answer: 'Release builds need no setup at all: a Google OAuth client is compiled into the binary, so `grr auth login` just works. Building from source? Run `grr auth setup`, which creates ~/.grr/config.toml for you, or bring your own client. PKCE is always enabled and 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 after an update?',
    answer: `Only when you ask for more: the default consent set is deliberately small — Gmail, Drive, Calendar, Tasks, Docs, Sheets, Slides and sign-in — because Google's consent screen stops submitting past roughly 27 ticked boxes on an unverified project. \`grr auth login --service <name>\` asks for one service's full scope set instead, and \`--all-scopes\` asks for every scope the ${discoveryMethodTotal} methods name. Each login merges with the account's existing grant, so widening the request needs one fresh \`grr auth login\` and nothing else.`,
  },
  {
    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 package, or the grr-cli crate from crates.io. winget manifests are generated per release, but no public submission has been published yet, so use the Windows release archive today. A crates.io install compiles from source and needs nightly Rust plus the `reqwest_unstable` cfg — see "Why does it need nightly Rust?". The install page has the exact commands for macOS, Linux, Windows x86_64 and Windows on ARM.',
  },
  {
    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.',
  },
  {
    question: 'What is `grr api`?',
    answer: 'A generic escape hatch generated from Google\'s own Discovery Service, embedded in the binary so it works offline. `grr api list` browses every method, `grr api describe` shows its parameters and scopes, and `grr api call` invokes one by id. Every service in the index — including Tasks, Docs, Sheets and Slides — already has a full typed command tree, so `grr api` is for composing ids dynamically or reaching the long tail of methods without a dedicated namespace. Use --dry-run to see the exact request first.',
  },
  {
    question: 'Can an AI agent use it?',
    answer: 'Yes, and the docs are written for that. Output is JSON on stdout with logs on stderr, so `grr ... | jq` and agent tool calls both get predictable data. An agent can discover capabilities with `grr api list` instead of assuming them, inspect any call with --dry-run, and read the exact scope a method declares before calling it — consent itself is granted once, for the small default set, and any other service is one `grr auth login --service <name>` away. See the docs page for agents.',
  },
];
const storySteps = [
  {
    index: '01',
    title: 'Waves when you arrive.',
    copy: `One OAuth login unlocks all ${discoveryServiceCount} Google Workspace APIs in a single namespaced binary.`,
  },
  {
    index: '02',
    title: 'Sits with you while you work.',
    copy: 'Commands stay close to the work they support, and the output stays parseable.',
  },
  {
    index: '03',
    title: 'Sleeps while your scripts run.',
    copy: 'No analytics SDK, no telemetry. Tokens rest in the operating system keyring.',
  },
  {
    index: '04',
    title: 'Celebrates when you ship.',
    copy: 'Output that plays nicely with jq, by default.',
  },
];
---

<BaseLayout
  title="grr"
  description={`grr is a zero-config Rust CLI for Google Workspace: a generated command tree over ${discoveryServiceCount} APIs with ${discoveryMethodTotal} methods, an MCP server, safety profiles, and grr ask turning plain English into a typed plan — clean JSON on stdout.`}
>
  <section class="hero section" aria-labelledby="hero-title">
    <div class="container hero-grid grid grid-cols-1 items-center gap-[clamp(28px,4vw,64px)] lg:grid-cols-[1.02fr_0.98fr]">
      <div class="hero-copy">
        <span class="eyebrow hero-fade">Rust Rewrite · all {discoveryServiceCount} APIs · one login</span>
        <h1 id="hero-title" class="mt-2.5 mb-4 text-[clamp(30px,2.2rem+2.2vw,54px)] leading-[1.06]">
          <span class="hero-line"><span class="hero-line-inner">Google tools,</span></span>
          <span class="hero-line"><span class="hero-line-inner hero-line-accent">at terminal speed.</span></span>
        </h1>
        <p class="hero-lede hero-fade m-0 max-w-[44ch] text-[15px] text-base-content/70">Zero-config, maximum-performance Google tools from the terminal. One binary, all {discoveryServiceCount} Google Workspace APIs, and output that plays nicely with jq.</p>
        <div class="hero-actions hero-fade mt-[22px] flex flex-wrap gap-3">
          <a class="btn btn-primary" href={releaseUrl} target="_blank" rel="noreferrer">GitHub releases <span aria-hidden="true">↗</span></a>
          <a class="btn btn-outline" href={`${base}install/`}>Install grr <span aria-hidden="true">→</span></a>
        </div>
        <p class="hero-note hero-fade m-0 mt-3.5 text-[13px] text-(--muted)">Release binaries ship with the OAuth client built in — <code>grr auth login</code> and you are in.</p>
      </div>
      <div class="hero-visual">
        <div class="hero-stage">
          <div class="hero-terminal-art" set:html={heroTerminal} />
          <div class="hero-contact-shadow" aria-hidden="true"></div>
          <span class="hero-spark hero-spark-a" aria-hidden="true"></span>
          <span class="hero-spark hero-spark-b" aria-hidden="true"></span>
          <span class="hero-spark hero-spark-c" aria-hidden="true"></span>
          <div class="hero-mascot">
            <Mascot label="Pixel-art orange crab mascot waving from the grr terminal" pose="wave" uid="hero" />
          </div>
        </div>
      </div>
    </div>
  </section>

  <InstallMarquee />

  <Stats />

  <section class="demo-section section" aria-labelledby="demo-title">
    <div class="container demo-grid">
      <div class="demo-copy reveal">
        <span class="eyebrow">watch it work</span>
        <h2 id="demo-title">A real terminal. A real agent. {demoDurationSeconds} seconds.</h2>
        <p>
          Every frame below is grr's actual output: the generated command tree, a live
          <code>grr transport</code> probe over HTTP/3, a <code>grr ask</code> plan turning plain
          English into a typed method and parameters — and opencode, an agentic harness,
          driving grr the way an AI agent would.
        </p>
        <p class="demo-note">
          Recorded once with <code>scripts/generate-demo.ts</code>; no hand-editing, no mocks —
          only the pacing is synthetic.
        </p>
      </div>
      <div class="demo-visual">
        <DemoPlayer />
      </div>
    </div>
  </section>

  <section class="ask-section section" aria-labelledby="ask-title">
    <div class="container demo-grid">
      <div class="ask-visual reveal">
        <CodeBlock code={askPlan} label="grr ask · the plan" copyable={true} />
      </div>
      <div class="demo-copy reveal">
        <span class="eyebrow">the only one you can just ask</span>
        <h2 id="ask-title">Plain English in. A typed plan out.</h2>
        <p>
          <code>grr ask "show my unread gmail"</code> is all it takes. A System One model —
          TypeSafe's Jev by default, any provider speaking the same contract works by config —
          classifies the request hierarchically: the service ({discoveryServiceCount} options), then the method,
          then one typed question per required parameter. Every candidate comes from
          the discovery index, so the model can only <em>select</em>. It can never invent a
          method, a flag, or a value.
        </p>
        <p>
          What you get back is a plan, not a guess: the resolved method, the parameters, the
          URL, the scopes, the confidence — as JSON. Nothing is sent until you say
          <code>--run</code>. No other Google Workspace CLI does this; for an AI agent it is
          the fastest path from intent to a verifiable request.
        </p>
        <p class="demo-note">
          The safety gate fires the moment the method resolves — <code>--readonly</code> and
          friends cover <code>grr ask</code> like everything else.
        </p>
      </div>
    </div>
  </section>

  <section class="services-section section" id="services" aria-labelledby="services-title">
    <div class="container">
      <SectionIntro
        id="services-title"
        eyebrow={`all ${discoveryServiceCount} APIs · 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 grid gap-20 lg:gap-24">
        {services.map((service, index) => <FeatureRow service={service} reverse={index % 2 === 1} />)}
      </div>
      <CommandInventory surfaces={commandSurfaces} />
      <ApiSurface />
    </div>
  </section>

  <section class="story-section section" aria-labelledby="story-title">
    <div class="container">
      <div class="section-heading story-heading mb-3">
        <span class="eyebrow">scrollytelling</span>
        <h2 id="story-title">One crab, four moods.</h2>
      </div>
      <div class="story-grid grid grid-cols-1 items-start gap-[clamp(32px,5vw,88px)] lg:grid-cols-[minmax(0,1fr)_minmax(260px,380px)]">
        <div class="story-steps grid gap-[clamp(160px,28vh,300px)] py-[16vh] max-md:gap-[clamp(96px,18vh,180px)] max-md:py-[8vh]">
          {storySteps.map((step) => (
            <div class="story-step max-w-[36ch]">
              <span class="story-step-index block text-[13px] text-(--accent-text)" aria-hidden="true">// {step.index}</span>
              <h3 class="mt-1.5 mb-2 text-[clamp(19px,2.2vw,24px)] leading-[1.2]">{step.title}</h3>
              <p class="m-0 text-sm text-base-content/70">{step.copy}</p>
            </div>
          ))}
        </div>
        <div class="story-stage">
          <Mascot variant="story" label="The grr crab mascot waves, waits, sleeps, and celebrates as you scroll" uid="story" />
          <div class="story-meter" aria-hidden="true">
            <span class="story-tick story-tick-wave"></span>
            <span class="story-tick story-tick-idle"></span>
            <span class="story-tick story-tick-sleep"></span>
            <span class="story-tick story-tick-celebrate"></span>
          </div>
          <p class="story-captions m-0" aria-hidden="true">
            <span class="story-cap story-cap-wave">hi</span>
            <span class="story-cap story-cap-idle">your terminal, your data</span>
            <span class="story-cap story-cap-sleep">zero config, zero noise</span>
            <span class="story-cap story-cap-celebrate">ship it</span>
          </p>
        </div>
      </div>
    </div>
  </section>

  <section class="architecture-section section" aria-labelledby="features-title">
    <div class="container">
      <div class="architecture-heading reveal max-w-[62ch]">
        <span class="eyebrow">built for the shell</span>
        <h2 id="features-title" class="mt-1.5 mb-2 text-[clamp(24px,3vw,36px)] leading-[1.12]">Less glue.<br />More signal.</h2>
        <p class="m-0 text-sm text-(--muted)">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 mt-12 grid grid-cols-1 items-center gap-10 lg:grid-cols-2">
        <div class="session-copy reveal">
          <span class="eyebrow">one clean session</span>
          <h3 class="mt-1.5 mb-2 text-[clamp(19px,2.2vw,24px)] leading-[1.2]">From inbox to insight, without the tab.</h3>
          <p class="m-0 text-sm text-base-content/70">Search a message, fetch exactly the format you need, and keep the output parseable. The command tree is available as JSON for agents with <code>grr schema</code>.</p>
          <a class="btn btn-outline mt-4" 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 grid grid-cols-1 items-center gap-10 lg:grid-cols-2">
      <div class="machine-copy reveal">
        <span class="eyebrow">machine-friendly by default</span>
        <h2 id="session-title" class="mt-1.5 mb-2 text-[clamp(22px,2.6vw,32px)] leading-[1.15]">Your shell is the API client.</h2>
        <p class="m-0 text-sm text-base-content/70">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 mt-7 grid grid-cols-1 gap-5 md:grid-cols-3">
        <a class="link-card reveal" href="https://crates.io/crates/grr-cli" target="_blank" rel="noreferrer">
          <strong class="mb-1.5 block text-[15px] text-(--accent-text)">crates.io ↗</strong>
          <span class="text-xs text-(--muted)">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 class="mb-1.5 block text-[15px] text-(--accent-text)">docs.rs ↗</strong>
          <span class="text-xs text-(--muted)">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 class="mb-1.5 block text-[15px] text-(--accent-text)">GitHub ↗</strong>
          <span class="text-xs text-(--muted)">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 grid grid-cols-1 gap-10 lg:grid-cols-2">
      <div class="faq-heading reveal">
        <span class="eyebrow">questions, answered</span>
        <h2 id="faq-title" class="mt-1.5 mb-2 text-[clamp(22px,2.6vw,32px)] leading-[1.15]">No mystery meat.</h2>
        <p class="m-0 text-sm text-(--muted)">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 grid">
        {faqs.map((faq) => (
          <details class="faq-item collapse collapse-arrow reveal rounded-none border-t border-base-300 last:border-b">
            <summary class="collapse-title min-h-12 px-1 text-sm"><InlineCode text={faq.question} /></summary>
            <div class="faq-answer collapse-content max-w-[64ch] px-1 text-[13px] leading-[1.7] text-(--muted)"><InlineCode text={faq.answer} /></div>
          </details>
        ))}
      </div>
    </div>
  </section>

  <section class="cta-section section" aria-labelledby="cta-title">
    <div class="container cta-grid grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
      <div class="cta-copy reveal">
        <h2 id="cta-title" class="m-0 mb-3 text-[clamp(24px,3vw,36px)] leading-[1.12]">Your Google workflows deserve a better terminal.</h2>
        <p class="m-0 text-sm text-base-content/70">Grab a binary, point it at your own OAuth client, and let grr get out of the way.</p>
        <div class="hero-actions mt-5 flex flex-wrap gap-3">
          <a class="btn btn-primary" href={`${base}install/`}>Choose your install path <span aria-hidden="true">→</span></a>
          <a class="btn btn-outline" href={releaseUrl} target="_blank" rel="noreferrer">GitHub releases <span aria-hidden="true">↗</span></a>
        </div>
        <p class="legal-note m-0 mt-4 text-xs text-(--quiet)">grr is an independent project and is not affiliated with, sponsored by, or endorsed by Google LLC.</p>
      </div>
      <div class="cta-visual reveal">
        <div class="install-slab" role="img" aria-label={'A floating card showing the command: RUSTFLAGS="--cfg reqwest_unstable" cargo install grr-cli'}>
          <div class="slab-base" aria-hidden="true"></div>
          <code class="slab-face" style="white-space: normal;">$ RUSTFLAGS="--cfg reqwest_unstable" cargo install grr-cli</code>
        </div>
        <div class="cta-mascot">
          <Mascot label="The grr crab mascot celebrating" pose="celebrate" uid="cta" />
        </div>
        <div class="slab-contact-shadow" aria-hidden="true"></div>
      </div>
    </div>
  </section>
</BaseLayout>