grr-cli 2.1.4

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
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import DocsShell from '../../components/DocsShell.astro';
import CodeBlock from '../../components/CodeBlock.astro';
import InlineCode from '../../components/InlineCode.astro';
import PageHero from '../../components/PageHero.astro';
import SectionIntro from '../../components/SectionIntro.astro';
import { discoveryMethodTotal, discoveryServiceCount, discoveryServices } from '../../data/discovery-coverage';

// Every per-service count in the copy below is read from the committed index,
// so a service that gains or loses methods updates this page with it.
const serviceMethods = (name: string) => discoveryServices.find((service) => service.name === name)?.methods ?? 0;

const base = import.meta.env.BASE_URL;

const safety = `# --readonly refuses every write verb, before or after the subcommand
grr --readonly gmail users messages delete --user-id me --id abc
# -> Error: 'gmail.users.messages.delete' is a DELETE (write); --readonly refuses writes.

grr --deny-service chat chat spaces list
# -> Error: 'chat.spaces.list' belongs to service chat, which --deny-service refuses.

# reads are never refused
grr --readonly gmail users messages list --user-id me --dry-run   # runs, JSON out
`;
const naming = `# A method id, with dots turned into spaces, IS the command.
gmail.users.messages.list        grr gmail users messages list
calendar.events.list             grr calendar events list
drive.files.list                 grr drive files list
tasks.tasklists.list             grr tasks tasklists list
sheets.spreadsheets.values.get   grr sheets spreadsheets values get

# Resources nest as subcommands; the method is the leaf. Each leaf also
# carries its bare name as a visible alias, shown in --help.
grr gmail --help                  # "users" and its children, alphabetically
grr gmail users --help            # "messages", "drafts", "labels", ...

# camelCase method names keep their casing as aliases:
grr gmail users getProfile --user-id me`;
const flags = `# Flags are generated from Discovery parameter names: camelCase -> kebab-case.
grr gmail users messages list --user-id me \\
    --q "is:unread" \\
    --max-results 5 \\
    --label-ids INBOX --label-ids UNREAD \\
    --include-spam-trash

# --max-results is parsed as an integer, --include-spam-trash is a presence
# flag, --label-ids repeats. Enum parameters validate; required ones are
# enforced before anything is sent.

# Collision rule: a parameter literally named "format" or "query" becomes
# --param-format / --param-query (the -f output flag keeps --format).
grr gmail users messages get --user-id me --id <id> --param-format metadata

# Bodies are untyped by design -- Discovery request schemas are not in the
# index -- so they pass through --params or --body-file verbatim.
grr calendar events insert --calendar-id primary --body-file ./event.json`;
const services = [
  {
    name: 'Gmail',
    namespace: 'grr gmail',
    description:
      `${serviceMethods('gmail')} methods. Messages, threads, drafts, labels, history, attachments, filters, forwarding, POP/IMAP, send-as, CSE, delegates, and watches — the deepest tree.`,
    commands: `grr gmail users messages list --user-id me --q "in:inbox" --max-results 5
grr gmail users messages get --user-id me --id <message-id> --param-format metadata
grr gmail users messages attachments get --user-id me --message-id <message-id> --id <attachment-id>
grr gmail users threads list --user-id me --max-results 10
grr gmail users labels list --user-id me
grr gmail users drafts create --user-id me --body-file ./draft.json
grr gmail users settings filters list --user-id me`,
  },
  {
    name: 'Calendar',
    namespace: 'grr calendar',
    description:
      `${serviceMethods('calendar')} methods. Calendars, events, recurring instances, ACL, free/busy, colors, and settings. The calendar id is a parameter like any other — \`primary\` where one is accepted.`,
    commands: `grr calendar events list --calendar-id primary --max-results 10
grr calendar events get --calendar-id primary --event-id <event-id>
grr calendar events insert --calendar-id primary --body-file ./event.json
grr calendar calendarList list
grr calendar freebusy query --body-file ./freebusy.json
grr calendar colors get`,
  },
  {
    name: 'Drive',
    namespace: 'grr drive',
    description:
      `${serviceMethods('drive')} methods. Files, permissions, comments, replies, revisions, changes, drives, and approvals — including the long tail no CLI ever wraps by hand.`,
    commands: `grr drive files list --q "name contains 'report'" --page-size 10
grr drive files get --file-id <file-id>
grr drive files export --file-id <document-id> --mime-type application/pdf
grr drive permissions list --file-id <file-id>
grr drive comments list --file-id <file-id>
grr drive changes list --page-token <token>
grr drive files emptyTrash`,
  },
  {
    name: 'People',
    namespace: 'grr people',
    description:
      `${serviceMethods('people')} methods. The People API verbatim: resource names look like people/c12345, and the API nests one \`people\` level, so the command says it twice.`,
    commands: `grr people people connections list --resource-name people/me
grr people people get --resource-name people/c12345
grr people contactGroups list
grr people otherContacts list`,
  },
  {
    name: 'Chat',
    namespace: 'grr chat',
    description:
      `${serviceMethods('chat')} methods. Spaces, members, messages, reactions, media, custom emoji, and read state. Space-scoped methods take --parent spaces/… .`,
    commands: `grr chat spaces list --page-size 10
grr chat spaces messages create --parent spaces/AAAA --body-file ./message.json
grr chat spaces messages list --parent spaces/AAAA --page-size 25
grr chat spaces members list --parent spaces/AAAA`,
  },
  {
    name: 'Forms',
    namespace: 'grr forms',
    description:
      `${serviceMethods('forms')} methods. Form bodies, responses, publish settings, and push-notification watches. The API nests one \`forms\` level, like People does with \`people\`.`,
    commands: `grr forms forms get --form-id <form-id>
grr forms forms responses list --form-id <form-id>
grr forms forms watches list --form-id <form-id>`,
  },
  {
    name: 'Tasks',
    namespace: 'grr tasks',
    description:
      `${serviceMethods('tasks')} methods. Task lists and the tasks inside them. Note the scope caveat: this build does not consent to the Tasks scope, so live calls can 403.`,
    commands: `grr tasks tasklists list
grr tasks tasks list --tasklist @default
grr tasks tasks insert --tasklist @default --body-file ./task.json`,
  },
  {
    name: 'Sheets',
    namespace: 'grr sheets',
    description:
      `${serviceMethods('sheets')} methods. Spreadsheets, values (read, write, append, batch), and developer metadata. Colon-verb paths like values/{range}:append are handled for you.`,
    commands: `grr sheets spreadsheets values get --spreadsheet-id <id> --range Sheet1!A1:B10
grr sheets spreadsheets values update --spreadsheet-id <id> --range Sheet1!A1 --body-file ./values.json
grr sheets spreadsheets values append --spreadsheet-id <id> --range Sheet1!A:A --value-input-option USER_ENTERED
grr sheets spreadsheets get --spreadsheet-id <id>`,
  },
  {
    name: 'Docs',
    namespace: 'grr docs',
    description:
      `${serviceMethods('docs')} methods. Documents get, create, and batchUpdate — the smallest service in the index.`,
    commands: `grr docs documents get --document-id <id>
grr docs documents create --body-file ./new-doc.json
grr docs documents batchUpdate --document-id <id> --body-file ./requests.json`,
  },
  {
    name: 'Slides',
    namespace: 'grr slides',
    description:
      `${serviceMethods('slides')} methods. Presentations, pages, and thumbnails. camelCase methods keep their casing as aliases: getThumbnail, not get-thumbnail.`,
    commands: `grr slides presentations get --presentation-id <id>
grr slides presentations pages getThumbnail --presentation-id <id> --page-object-id g1
grr slides presentations batchUpdate --presentation-id <id> --body-file ./requests.json`,
  },
];
const formats = `# Every command accepts -f/--format. JSON is the default. Google's list
# methods return their own wrapper object -- messages, files, items -- and
# grr prints it verbatim rather than unwrapping it, so table shows the
# object Field/Value and jsonl emits that one object:
$ grr gmail users messages list --user-id me --max-results 5 -f table
+--------------------+-------------------------------------------------+
| Field              | Value                                           |
+--------------------+-------------------------------------------------+
| messages           | [{"id":"19f8ab2c","threadId":"19f8ab2c"}, ...]  |
| resultSizeEstimate | 5                                               |
+--------------------+-------------------------------------------------+

$ grr gmail users messages list --user-id me -f jsonl
{"messages":[{"id":"19f8ab2c","threadId":"19f8ab2c"}],"resultSizeEstimate":1}

# grr api list is a bare array, so it tables one row per method and streams
# one element per line -- the shape jsonl is actually for:
$ grr api list --service docs -f jsonl
{"http":"POST","id":"docs.documents.batchUpdate","summary":"Applies one or more updates to the document."}
{"http":"POST","id":"docs.documents.create","summary":"Creates a blank document using the title given in the request."}
{"http":"GET","id":"docs.documents.get","summary":"Gets the latest version of the specified document."}

$ grr api list --service docs -f pretty`;
const account = `$ grr auth status
{"authenticated":true,"email":"you@example.com","messages_total":48213,"threads_total":9120,"history_id":"1280000"}

$ grr transport
negotiated_protocol: HTTP_3
http3_requested: true
http3_effective: true
fell_back: false
...

$ grr schema | jq -r '.subcommands[].name'
["auth","api","mcp","transport","schema","completions","ask","skills","analyticsadmin","analyticsdata","calendar","chat","docs","drive","forms","gmail","people","script","searchconsole","sheets","slides","tasks"]`;
const pipes = `# stdout is data, stderr is logs -- so this is always valid JSON.
$ grr gmail users messages list --user-id me --q "from:github.com" --max-results 10 | jq -r '.messages[0].id'
19f8ab2c

# --dry-run prints the resolved request as JSON and sends nothing.
$ grr gmail users messages list --user-id me --q "is:unread" --dry-run | jq -r '.url'
https://gmail.googleapis.com/gmail/v1/users/me/messages?q=is%3Aunread

# The same method, reached through the flat escape hatch, byte-identical:
$ grr api call gmail.users.messages.list --param userId=me --param 'q=is:unread' --dry-run`;
const schemaBlock = `$ grr schema > tree.json        # ~369 KB, valid JSON, well under 100 ms
$ grr schema | jq '.subcommands[] | select(.name == "gmail") | .subcommands[].name'
["users"]
$ grr schema | jq '[.. | objects | select(.name? == "gmail.users.messages.list") | .args[].name]'`;
const skillsBlock = `# The ${1 + discoveryServiceCount} packaged skills -- the core skill plus one per service -- are ${2 + discoveryServiceCount} files
# with their index, and are compiled into the binary, so install is offline.
$ grr skills install            # all of them into ~/.agents/skills/ (the cross-client location)
$ grr skills install --claude   # also mirror into ~/.claude/skills/ (all Claude Code reads)
$ grr skills install --force    # replace files that differ from the packaged copy
$ grr skills list               # the packaged set, and what each target directory holds
# on upgrade, installed skills migrate themselves: untouched files are
# replaced, locally edited files are 3-way merged, conflicts land beside
# yours as .grr-incoming (lazy — nothing is hashed unless a merge is due)`;
const systemone = `# ~/.grr/config.toml -- every key is optional; the defaults are shown.
[systemone]
endpoint = "https://api.typesafe.ai/v1/systemone"
model = "jev-latest"
confidence-threshold = 0.6
# api-key = "..."   # avoid: prefer TYPESAFE_API_KEY so the key stays out of the file`;
const systemoneEnv = `# Preferred key route: it never sits in a file that can be committed.
export TYPESAFE_API_KEY="..."
grr ask "show my unread messages"          # prints the plan, sends nothing to Google
grr ask "show my unread messages" --run    # executes the resolved request

# The generic GRR_ prefix reaches the rest of the section too:
# GRR_SYSTEMONE__ENDPOINT, GRR_SYSTEMONE__MODEL, GRR_SYSTEMONE__API_KEY`;
---

<BaseLayout
  title="Commands"
  description={`The full grr command surface: ${discoveryServiceCount} services, one generated tree — the naming rule that predicts any command from its method id, typed flags per method, and the account-level commands.`}
>
  <DocsShell currentPath={`${base}docs/commands/`}>
    <PageHero eyebrow="commands" title={`${discoveryServiceCount} services, one generated tree.`}>
      Every service command is generated from Google&rsquo;s Discovery Service at build time &mdash; {discoveryMethodTotal} methods, a typed flag per parameter, zero hand-written per-service commands. That makes the tree predictable instead of memorized. For the flat, id-based view of the same index, see <a href={`${base}docs/discovery/`}>Discovery API</a>.
      <Fragment slot="aside">{discoveryMethodTotal} methods · {discoveryServiceCount} services · generated, not curated</Fragment>
    </PageHero>

    <section class="section" aria-labelledby="naming-title">
      <div class="container">
        <SectionIntro
          id="naming-title"
          eyebrow="the naming rule"
          title="Predict any command from its method id."
          copy="There is nothing to memorize: a Discovery method id, with dots turned into spaces, is the command. The same rule generates every flag."
        />
        <div class="container prose reveal max-w-[68ch]">
          <CodeBlock code={naming} label="method id → command" />
          <p>Top level: <code>grr auth</code>, <code>grr api</code>, <code>grr mcp</code>, <code>grr schema</code>, <code>grr completions</code>, <code>grr transport</code>, <code>grr ask</code>, <code>grr skills</code>, plus one subcommand per service. The tree is emitted into <code>src/commands/generated.rs</code> by a generator that reads the committed Discovery index, and a daily workflow opens a PR when Google adds a method &mdash; so new surface arrives without a grr release.</p>
          <p>Flag names come from the same ids, camelCase collapsed to kebab-case, typed by what Discovery declares:</p>
          <ul>
            <li><strong>Integers</strong> are parsed as numbers: <code>--max-results 10</code>.</li>
            <li><strong>Booleans</strong> are presence flags: <code>--include-spam-trash</code>, not <code>--include-spam-trash true</code>.</li>
            <li><strong>Repeated parameters repeat</strong>: <code>--label-ids INBOX --label-ids UNREAD</code>. (Single-string parameters that take comma lists inside the value, like Drive&rsquo;s <code>--order-by</code>, are passed through as-is.)</li>
            <li><strong>Enum parameters validate</strong> their values; a bad one is caught before anything is sent.</li>
            <li><strong>Required parameters are enforced</strong> by clap &mdash; you cannot build a request missing <code>--user-id</code>.</li>
            <li><strong>Bodies are untyped, deliberately.</strong> Discovery&rsquo;s request schemas are not part of the distilled index, so <code>POST</code>/<code>PATCH</code>/<code>PUT</code> bodies pass through <code>--params</code> (a JSON object) or <code>--body-file</code> (a file, or <code>-</code> for stdin) verbatim. Typed flags win over <code>--params</code> on conflict.</li>
          </ul>
          <CodeBlock code={flags} label="typed flags · collision rule · bodies" />
          <p>Every leaf also carries the same escape hatches <code>grr api call</code> has: <code>--params</code>, <code>--body-file</code>, repeatable <code>--query KEY=VALUE</code> for raw query pairs, <code>--dry-run</code>, and <code>-f/--format</code>. Dispatch resolves the leaf&rsquo;s id against the embedded index and funnels into one shared call path &mdash; the two routes below produce byte-identical output:</p>
          <CodeBlock code={pipes} label="piping · dry runs · parity" />
        </div>
      </div>
    </section>

    <section class="section" aria-labelledby="surface-title">
      <div class="container">
        <SectionIntro
          id="surface-title"
          eyebrow="the services"
          title="Every service, a few real commands."
          copy="Verified against the binary and the committed index. Run grr <service> --help for the authoritative version of any branch — these are samples, not the census."
        />
        <div class="install-grid mt-7 grid grid-cols-1 gap-5 lg:grid-cols-2">
          {services.map((service) => (
            <article id={`svc-${service.name.toLowerCase()}`} class="install-card install-card-wide card card-border border-base-300 bg-base-100">
              <div class="card-body gap-1 p-5">
                <h2 class="card-title m-0 text-base">{service.name}</h2>
                <p class="m-0 text-[13px] text-base-content/70"><InlineCode text={service.description} /></p>
                <CodeBlock code={service.commands} label={service.namespace} />
              </div>
            </article>
          ))}
        </div>
      </div>
    </section>

    <section class="section" aria-labelledby="format-title">
      <div class="container">
        <SectionIntro
          id="format-title"
          eyebrow="output"
          title="Four formats, one flag."
          copy="Every command takes -f/--format, including the generated leaves. There is no global --format flag: it would collide by id with per-method parameters named format."
        />
      </div>
      <div class="container prose reveal max-w-[68ch]">
        <p class="lead"><code>json</code> (default), <code>jsonl</code>, <code>table</code>, <code>pretty</code>. JSON is the default because the common consumer is a pipe, not a person.</p>
        <p><code>jsonl</code> emits one JSON document per line for array results, which is what you want when streaming into <code>jq</code> without loading everything. <code>table</code> renders arrays as one row per element with the union of keys as columns, a single object as a Field/Value table, and nested values as compact JSON inside a cell.</p>
        <CodeBlock code={formats} label="output formats" />
      </div>
    </section>

    <section class="section" aria-labelledby="account-title">
      <div class="container">
        <SectionIntro
          id="account-title"
          eyebrow="account level"
          title="Commands outside the service tree."
          copy="These sit at the top level because they are not about one API. grr schema is the one to build against."
        />
        <div class="install-grid mt-7 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr auth</code></h2>
              <p class="m-0 text-[13px] text-base-content/70"><code>login</code> runs the PKCE browser flow, or the device flow with <code>--device</code>; <code>--account work</code> keeps a separate token per account. <code>status</code> makes a live profile call through the shared path and reports the account plus the <code>history_id</code> cursor. <code>setup</code> creates and stores your own OAuth client.</p>
            </div>
          </article>
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr transport</code></h2>
              <p class="m-0 text-[13px] text-base-content/70">Prints the negotiated protocol, whether HTTP/3 was requested and effective, whether it fell back, the core count, and io_uring support. Plain text, not JSON &mdash; it is a diagnostic, not data.</p>
            </div>
          </article>
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr schema</code></h2>
              <p class="m-0 text-[13px] text-base-content/70">Dumps the entire command tree as JSON: every subcommand, every argument, whether it is required, whether it takes a value, its default, and its possible values. Because the tree itself is generated from the index, schema is a faithful census of all {discoveryMethodTotal} methods.</p>
            </div>
          </article>
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr completions</code></h2>
              <p class="m-0 text-[13px] text-base-content/70">Prints a bash, zsh, PowerShell or elvish completion script generated from the same tree as schema, so every service command, alias and flag completes offline with zero configuration. Bash and zsh load installed scripts lazily; PowerShell and elvish snippets run the generator at shell startup. Fish is not supported yet: its generator stops two levels deep. <a class="link link-hover" href="https://github.com/debanjanbasu/grr-cli#install">Setup per shell &rarr;</a></p>
            </div>
          </article>
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr api</code></h2>
              <p class="m-0 text-[13px] text-base-content/70"><code>list</code>, <code>describe</code>, <code>call</code>, and <code>refresh</code> against the embedded Discovery index &mdash; the flat escape hatch that reaches any method by id. Same index, same call path as the tree.</p>
            </div>
          </article>
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr skills</code></h2>
              <p class="m-0 text-[13px] text-base-content/70"><code>install</code> writes the packaged skills &mdash; the core skill plus one per service, {2 + discoveryServiceCount} files with their index &mdash; into <code>~/.agents/skills/</code>, the cross-client user-level location, with no network; <code>--claude</code> mirrors into <code>~/.claude/skills/</code>, which is all Claude Code reads. Identical files are a no-op, a locally edited file is refused without <code>--force</code>. <code>list</code> reports what each target holds.</p>
            </div>
          </article>
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr mcp</code></h2>
              <p class="m-0 text-[13px] text-base-content/70">A Model Context Protocol server over stdio: every method as a typed MCP tool with an inputSchema from Discovery. <code>tools/call</code> shares the engine with <code>grr api call</code>; <code>--readonly</code> refuses writes. Point Claude Desktop, Gemini CLI, VS Code or Cursor at it.</p>
            </div>
          </article>
          <article class="install-card card card-border border-base-300 bg-base-100">
            <div class="card-body gap-1 p-5">
              <h2 class="card-title m-0 text-base"><code>grr ask</code></h2>
              <p class="m-0 text-[13px] text-base-content/70">Natural language in, a typed method + parameters out: a System One model (Jev by default) classifies the request hierarchically &mdash; service, then method, then one typed question per required parameter, every candidate code-supplied. Prints the plan to Google; <code>--run</code> executes. <a class="link link-hover" href="#system-one">Configure System One &rarr;</a></p>
            </div>
          </article>
        </div>
      </div>
      <div class="container prose reveal max-w-[68ch] mt-8">
        <CodeBlock code={account} label="account · transport · schema" />
        <p><code>grr schema</code> is the canonical reference. It is honest about its own size: roughly 369 KB of valid JSON, emitted in well under 100 ms, with zero configuration and before any OAuth exists. Generate a wrapper or an agent tool definition from it &mdash; because it is derived from the same tree the binary dispatches on, it cannot drift from the commands that actually exist.</p>
        <CodeBlock code={schemaBlock} label="grr schema · the canonical reference" />
        <p>The same zero-configuration promise covers the packaged agent skills: they are compiled into the binary, so <code>grr skills install</code> writes the whole set into the user-level skills directory with no network &mdash; global only, never a project directory.</p>
        <CodeBlock code={skillsBlock} label="grr skills · offline install" />
      </div>
    </section>

    <section class="section" aria-labelledby="system-one">
      <div class="container">
        <SectionIntro
          id="system-one"
          eyebrow="natural language"
          title="Configuring System One for grr ask."
          copy="grr ask is the one command with a tunable surface: endpoint, model, key and confidence threshold. Code chooses the candidates; the model only selects among them."
        />
      </div>
      <div class="container prose reveal max-w-[68ch]">
        <p class="lead"><code>grr ask</code> runs three small judgments against a System One provider, each one HTTP request: a Choice over the {discoveryServiceCount} services, then a Choice over the chosen service&rsquo;s methods, then one typed question per required parameter. The CLI supplies every candidate from the embedded Discovery index, so the model can only <em>select</em> a service, a method, or a value it was handed &mdash; it cannot invent one. The plan is the default output; <code>--run</code> executes the resolved request through the same call path as <code>grr api call</code>.</p>
        <p>The defaults point at TypeSafe&rsquo;s hosted endpoint and its Jev model, so the only thing you must supply is a key. The provider is agnostic: any endpoint that speaks the same contract &mdash; POST a <code>state</code> plus typed <code>questions</code>, answer with an <code>answers</code> map &mdash; works by pointing <code>endpoint</code> and <code>model</code> at it.</p>
        <CodeBlock code={systemone} label="~/.grr/config.toml · [systemone]" />
        <p>Keys are kebab-case (<code>api-key</code>, <code>confidence-threshold</code>); snake_case aliases keep TS-era configs working, and any field left unset falls back to its default. <strong>The env route is preferred</strong> for the key, because a key in a config file can leak with the file:</p>
        <CodeBlock code={systemoneEnv} label="the preferred key route" />
        <ul>
          <li><strong>What the model decides:</strong> which service, which method, and which value for each required parameter &mdash; always a choice among code-supplied options &mdash; plus a confidence for each.</li>
          <li><strong>What the CLI decides:</strong> the candidate sets, the parameter types and coercion, the scope note, and the safety gate. <code>--readonly</code>/<code>--deny-service</code>/<code>--deny-verb</code> fire the moment the method resolves, before the parameter judgment. A choice confidence below the threshold (default <code>0.6</code>) is flagged on stderr, never blocked.</li>
          <li><strong>The key rides only in the Authorization header</strong> &mdash; never in a request body, the plan output, or an error message. A missing key fails with the ways to supply one rather than a bare 401.</li>
          <li><strong>Planning still needs the provider.</strong> Even without <code>--run</code>, the routing judgments are HTTP calls, so <code>grr ask</code> needs a reachable endpoint and a valid key; <code>--service</code> skips the first judgment and <code>--method</code> skips both, going straight to parameter fill.</li>
        </ul>
      </div>
    </section>

    <section class="section" aria-labelledby="safety-title">
      <div class="container prose reveal max-w-[68ch]">
        <h2 id="safety-title">Safety profiles, everywhere at once</h2>
        <p><code>--readonly</code>, <code>--deny-service</code> (a service name) and <code>--deny-verb</code> (an HTTP verb) are global flags honored by the generated tree, <code>grr api call</code> and the MCP server alike. They parse before or after the subcommand, and turn a destructive method into an actionable error naming the method, the verb, and the way out.</p>
        <CodeBlock code={safety} label="safety profiles" />
      </div>
    </section>

    <section class="section" aria-labelledby="conventions-title">
      <div class="container prose reveal max-w-[68ch]">
        <h2 id="conventions-title">Conventions worth knowing</h2>
        <ul>
          <li><strong>Results on stdout, logs on stderr.</strong> The tracing subscriber is wired to stderr explicitly, so <code>grr … | jq</code> never receives a log line. The one stdout exception is <code>grr auth login --device</code>, which prints its human-readable URL and code before the final JSON.</li>
          <li><strong>Paging is yours to drive.</strong> The tree does not follow page tokens: <code>--max-results</code> is the API&rsquo;s own page size, and <code>--page-token</code> takes the <code>nextPageToken</code> from the previous response. That keeps a list command from turning into an unbounded crawl.</li>
          <li><strong>Booleans are presence flags</strong> (<code>--include-spam-trash</code>), a deliberate change from value-taking booleans: a flag is either passed or absent, so a script never has to guess.</li>
          <li><strong>Errors are actionable.</strong> A 403 names the missing scope, an unknown method id returns ranked suggestions, a missing required parameter names itself. Non-zero exit on failure, always.</li>
          <li><strong>Three commands print plain text, not JSON:</strong> <code>grr transport</code>, <code>grr skills install</code> (a human install summary), and the device-login line above. <code>grr skills list</code> is JSON. <code>--dry-run</code> output <em>is</em> JSON, so it pipes.</li>
          <li><strong>Timestamps are RFC 3339</strong> (<code>--time-min 2026-10-01T00:00:00Z</code>), or a bare <code>YYYY-MM-DD</code> where the API accepts a date.</li>
          <li><strong><code>grr --version</code> is a banner.</strong> The semver is its first line, which is what the benchmark script and the Homebrew formula read; below it sits the crab mascot, drawn in 24-bit-colour pixel art on a terminal that advertises <code>COLORTERM</code> or direct colour and as plain ASCII everywhere else. A pipe, <code>TERM=dumb</code> or <code>NO_COLOR</code> all give you the ASCII crab &mdash; so scripts are never handed escape sequences. <code>CLICOLOR_FORCE=1</code> forces the colour logo through a pipe.</li>
        </ul>
      </div>
    </section>

    <section class="section" aria-labelledby="next-title">
      <div class="container prose reveal max-w-[68ch]">
        <h2 id="next-title">Where the tree comes from</h2>
        <p>Every leaf above is generated from the committed Discovery index at build time and refreshed daily by a workflow PR. The same index drives the flat <code>grr api</code> surface, so the census never splits in two.</p>
        <ul>
          <li><a href={`${base}docs/discovery/`}>Discovery API — the index behind the whole tree</a></li>
          <li><a href={`${base}docs/agents/`}>Agents and harnesses — output contract and discovery discipline</a></li>
          <li><a href={`${base}docs/getting-started/`}>Getting started — install, login, and verify</a></li>
        </ul>
      </div>
    </section>
  </DocsShell>
</BaseLayout>