diff --git a/.agents/skills/gog-searchconsole/SKILL.md b/.agents/skills/gog-searchconsole/SKILL.md
index 1d2542af3..d0cc78b7a 100644
--- a/.agents/skills/gog-searchconsole/SKILL.md
+++ b/.agents/skills/gog-searchconsole/SKILL.md
@@ -28,6 +28,7 @@ gog --readonly --account user@example.com searchconsole --help
| Command | Purpose |
| --- | --- |
+| `inspect` | Inspect URL index status (URL Inspection API) |
| `query` | Run a Search Analytics query |
| `searchanalytics` | Search Analytics queries |
| `sitemaps` | List/get/submit/delete sitemaps |
diff --git a/.agents/skills/gog-sheets/SKILL.md b/.agents/skills/gog-sheets/SKILL.md
index a1a86b29e..34f6075dc 100644
--- a/.agents/skills/gog-sheets/SKILL.md
+++ b/.agents/skills/gog-sheets/SKILL.md
@@ -41,6 +41,7 @@ gog --readonly --account user@example.com sheets get SHEET_ID 'Sheet1!A1:D20' --
| `datasource` | Manage Connected Sheets data sources and extracts |
| `delete-dimension` | Delete rows or columns while preserving intersecting tables |
| `delete-tab` | Delete a tab/sheet from a spreadsheet (use --force to skip confirmation) |
+| `duplicate-tab` | Duplicate a tab within a spreadsheet |
| `export` | Export a Google Sheet (pdf\|xlsx\|csv) via Drive |
| `filter` | Manage basic filters |
| `find-replace` | Find and replace text across a spreadsheet |
diff --git a/.agents/skills/gog-slides/SKILL.md b/.agents/skills/gog-slides/SKILL.md
index 5c095d761..291381886 100644
--- a/.agents/skills/gog-slides/SKILL.md
+++ b/.agents/skills/gog-slides/SKILL.md
@@ -46,6 +46,7 @@ gog --readonly --account user@example.com slides --help
| `locate` | Locate text in shapes and table cells with object IDs and UTF-16 ranges |
| `move-slide` | Move a slide to a zero-based insertion index |
| `new-slide` | Create a native themed slide |
+| `paragraph-style` | Set paragraph alignment, spacing, indentation, or direction |
| `raw` | Dump raw Google Slides API response as JSON (Presentations.Get; lossless; for scripting and LLM consumption) |
| `read-slide` | Read slide content: speaker notes, text elements, and images |
| `replace-slide` | Replace an existing slide image from a local file or public URL |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6aecf09ea..5318c3945 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,6 +2,8 @@ name: ci
on:
push:
+ branches:
+ - main
pull_request:
permissions:
@@ -34,8 +36,23 @@ jobs:
run: make lint
- name: Deadcode
run: make deadcode
- - name: Docs check
- run: make docs-check
+ - name: Docs, skills, and Docker version checks
+ run: make docs-check agent-skills-check docker-version-check
+
+ minimum-go:
+ runs-on: ubuntu-latest
+ env:
+ GOTOOLCHAIN: local
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
+ with:
+ go-version: "1.26.8"
+ cache: true
+ - name: Test minimum supported Go series
+ run: go test ./...
+ - name: Build
+ run: go build ./cmd/gog
worker:
runs-on: ubuntu-latest
@@ -47,11 +64,13 @@ jobs:
- name: Enable Corepack (pnpm)
run: |
corepack enable
- corepack prepare pnpm@11.25.0 --activate
+ corepack prepare pnpm@11.26.0 --activate
- name: Install dependencies
run: pnpm -C internal/tracking/worker install --frozen-lockfile
- name: Lint
run: pnpm -C internal/tracking/worker lint
+ - name: Typecheck
+ run: pnpm -C internal/tracking/worker typecheck
- name: Build
run: pnpm -C internal/tracking/worker build
- name: Test
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aec54f654..4bb7ace92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,26 @@
# Changelog
-## 0.39.2 - Unreleased
+## 0.40.1 - Unreleased
+
+- Search Console: add `searchconsole inspect` for per-URL index status via the URL Inspection API (coverage state, indexing/page-fetch/robots.txt state, canonical, sitemaps, last crawl time), using the existing `webmasters` OAuth scope. (#1094) — thanks @laihenyi.
+- Search Console: preserve permission-denied exit codes when adding API setup or scope guidance. (#1094)
+- Dependencies and CI: refresh Google protocol modules and tracking-worker tooling within release-age limits; test the Go 1.26 minimum, check worker types and generated skills, and avoid duplicate pull-request test runs. (#1131)
+
+## 0.40.0 - 2026-09-11
+
+**Highlights:** Preserve Slides styling, format paragraphs, and reuse cached Discovery documents; duplicate or inspect individual spreadsheet tabs and sort Drive files.
+
+- Slides: preserve leading text styling during `insert-text --replace`, including template inheritance, with revision protection and correct empty-target and UTF-16 handling. (#1111, #1122) — thanks @sebsnyk.
+- Slides: add `paragraph-style` for shape and table-cell alignment, spacing, indentation, and direction, with explicit field masks and zero-value support. (#1098, #1123) — thanks @sebsnyk.
+- API: cache Discovery documents for 24 hours with bounded disk usage, isolated endpoint/version keys, and explicit `--no-cache` bypass; keep API responses and authorization checks uncached. (#1106, #1121) — thanks @gurgeous.
+- Sheets: duplicate a tab within its spreadsheet, with an explicit destination name and optional insertion index. (#1102, #1116) — thanks @gurgeous.
+- Sheets: filter raw API reads by exact tab title with `--sheet`, including A1-like names and apostrophes, while retaining spreadsheet metadata and the grid-data opt-in. (#1104, #1115) — thanks @gurgeous.
+- Drive: add opt-in `ls --sort` and `--order` controls while preserving the existing modification-time default and native pagination. (#1105, #1117) — thanks @gurgeous.
+- Slides: delete several page elements in one guarded API batch, retain single-element output compatibility, and document replacing WordArt elements. (#1109) — thanks @sebsnyk.
+- Sheets: preserve zero-valued dimension indices so inserting before the first row or column succeeds. (#1107, #1114) — thanks @gurgeous.
+- Docs: replace the reserved `default` alias command with the account manager's **Set default** action and explain account-selection precedence. (#1092, #1093) — thanks @gianpaj and @goutamadwant.
+- Dependencies: refresh Go networking, authentication and cryptography modules and tracking-worker tooling, including the patched Sharp dependency, while retaining Go 1.26 compatibility and the worker's 24-hour release-age policy.
+- Tooling: refresh goimports and deadcode to x/tools v0.50.0 while retaining the Go 1.26 minimum. (#1120)
## 0.39.1 - 2026-09-05
diff --git a/Makefile b/Makefile
index 05ab8c5dc..61c2cf80a 100644
--- a/Makefile
+++ b/Makefile
@@ -25,7 +25,7 @@ GOIMPORTS := $(TOOLS_DIR)/goimports
GOLANGCI_LINT := $(TOOLS_DIR)/golangci-lint
DEADCODE := $(TOOLS_DIR)/deadcode
TOOLS_STAMP := $(TOOLS_DIR)/.versions
-TOOLS_VERSION := gofumpt=v0.11.0;goimports=v0.49.0;golangci-lint=v2.13.2;deadcode=v0.49.0
+TOOLS_VERSION := gofumpt=v0.11.0;goimports=v0.50.0;golangci-lint=v2.13.2;deadcode=v0.50.0
# Allow passing CLI args as extra "targets":
# make gogcli -- --help
@@ -91,9 +91,9 @@ tools:
else \
set -e; \
GOBIN=$(TOOLS_DIR) go install mvdan.cc/gofumpt@v0.11.0; \
- GOBIN=$(TOOLS_DIR) go install golang.org/x/tools/cmd/goimports@v0.49.0; \
+ GOBIN=$(TOOLS_DIR) go install golang.org/x/tools/cmd/goimports@v0.50.0; \
GOBIN=$(TOOLS_DIR) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2; \
- GOBIN=$(TOOLS_DIR) go install golang.org/x/tools/cmd/deadcode@v0.49.0; \
+ GOBIN=$(TOOLS_DIR) go install golang.org/x/tools/cmd/deadcode@v0.50.0; \
printf '%s\n' "$(TOOLS_VERSION)" > "$(TOOLS_STAMP)"; \
fi
@@ -164,5 +164,6 @@ ci: pnpm-gate docker-version-check fmt-check lint deadcode test docs-check agent
worker-ci:
@pnpm -C internal/tracking/worker lint
+ @pnpm -C internal/tracking/worker typecheck
@pnpm -C internal/tracking/worker build
@pnpm -C internal/tracking/worker test
diff --git a/README.md b/README.md
index f1e0250d5..20f567f76 100644
--- a/README.md
+++ b/README.md
@@ -163,7 +163,7 @@ binary.
| meet | yes | Meet REST API | `https://www.googleapis.com/auth/meetings.space.created`
`https://www.googleapis.com/auth/meetings.space.readonly`
`https://www.googleapis.com/auth/meetings.space.settings` | |
| appscript | yes | Apps Script API | `https://www.googleapis.com/auth/script.projects`
`https://www.googleapis.com/auth/script.deployments`
`https://www.googleapis.com/auth/script.processes` | |
| analytics | yes | Analytics Admin API, Analytics Data API | `https://www.googleapis.com/auth/analytics.readonly` | GA4 account summaries + reporting |
-| searchconsole | yes | Search Console API | `https://www.googleapis.com/auth/webmasters` | Search Analytics + sitemap management |
+| searchconsole | yes | Search Console API | `https://www.googleapis.com/auth/webmasters` | Search Analytics + sitemap management + URL Inspection |
| adsense | no | AdSense Management API | `https://www.googleapis.com/auth/adsense.readonly` | Consumer OAuth; explicit opt-in with --services adsense; read-only |
| ads | yes | Google Ads API | `https://www.googleapis.com/auth/adwords` | OAuth scope only |
| groups | no | Cloud Identity API | `https://www.googleapis.com/auth/cloud-identity.groups.readonly` | Workspace only |
diff --git a/docs/automation.md b/docs/automation.md
index 5c52a4c79..265d31960 100644
--- a/docs/automation.md
+++ b/docs/automation.md
@@ -187,6 +187,20 @@ fi
New classifications may be added. Keep a generic non-zero fallback.
+## Discovery document cache
+
+`gog api call` and `gog api describe` share a 24-hour on-disk cache of
+Discovery documents under the configured cache directory's `discovery`
+subdirectory. It keeps at most 32 documents and 64 MiB, evicting the oldest
+documents; individual documents are limited to 16 MiB. API versions, endpoint
+overrides and service-hosted fallback behavior use separate cache keys.
+
+Pass `--no-cache` to either command to fetch without reading or writing this
+cache. Missing, expired or corrupt entries are fetched again. Cache failures
+do not prevent network access, and failed fetches are not cached. `api list`
+and actual API responses remain uncached; authorization and command-policy
+checks still run on every call.
+
## MCP discovery
MCP uses its standard `tools/list` request for client-side tool discovery. To
diff --git a/docs/commands.generated.md b/docs/commands.generated.md
index 0540cd1eb..22a5592a3 100644
--- a/docs/commands.generated.md
+++ b/docs/commands.generated.md
@@ -64,7 +64,7 @@ Generated from `gog schema --json`.
- [`gog analytics (ga) report [flags]`](commands/gog-analytics-report.md) - Run a GA4 report (Analytics Data API)
- [`gog api [flags]`](commands/gog-api.md) - Google Discovery APIs and generic method calls
- [`gog api call [flags]`](commands/gog-api-call.md) - Call a Discovery-described API method
- - [`gog api describe []`](commands/gog-api-describe.md) - Describe a Discovery API or method
+ - [`gog api describe [] [flags]`](commands/gog-api-describe.md) - Describe a Discovery API or method
- [`gog api list [flags]`](commands/gog-api-list.md) - List Google Discovery APIs
- [`gog appscript (script,apps-script) [flags]`](commands/gog-appscript.md) - Google Apps Script
- [`gog appscript (script,apps-script) content (cat) `](commands/gog-appscript-content.md) - Get Apps Script project content
@@ -574,6 +574,7 @@ Generated from `gog schema --json`.
- [`gog schema (help-json,helpjson) [ ...] [flags]`](commands/gog-schema.md) - Machine-readable command/flag schema
- [`gog search (find) ... [flags]`](commands/gog-search.md) - Search Drive files (alias for 'drive search')
- [`gog searchconsole (gsc,search-console,webmasters) [flags]`](commands/gog-searchconsole.md) - Google Search Console
+ - [`gog searchconsole (gsc,search-console,webmasters) inspect [flags]`](commands/gog-searchconsole-inspect.md) - Inspect URL index status (URL Inspection API)
- [`gog searchconsole (gsc,search-console,webmasters) query (report) [flags]`](commands/gog-searchconsole-query.md) - Run a Search Analytics query
- [`gog searchconsole (gsc,search-console,webmasters) searchanalytics (analytics) `](commands/gog-searchconsole-searchanalytics.md) - Search Analytics queries
- [`gog searchconsole (gsc,search-console,webmasters) searchanalytics (analytics) query (run) [flags]`](commands/gog-searchconsole-searchanalytics-query.md) - Run a Search Analytics query
@@ -621,6 +622,7 @@ Generated from `gog schema --json`.
- [`gog sheets (sheet) datasource (data-source,data-sources,connected-sheets) update [flags]`](commands/gog-sheets-datasource-update.md) - Update one BigQuery Connected Sheets data source
- [`gog sheets (sheet) delete-dimension (delete-dim) --dimension=STRING [flags]`](commands/gog-sheets-delete-dimension.md) - Delete rows or columns while preserving intersecting tables
- [`gog sheets (sheet) delete-tab (delete-sheet) `](commands/gog-sheets-delete-tab.md) - Delete a tab/sheet from a spreadsheet (use --force to skip confirmation)
+ - [`gog sheets (sheet) duplicate-tab [flags]`](commands/gog-sheets-duplicate-tab.md) - Duplicate a tab within a spreadsheet
- [`gog sheets (sheet) export (download,dl) [flags]`](commands/gog-sheets-export.md) - Export a Google Sheet (pdf|xlsx|csv) via Drive
- [`gog sheets (sheet) filter (filters,basic-filter,basic-filters) `](commands/gog-sheets-filter.md) - Manage basic filters
- [`gog sheets (sheet) filter (filters,basic-filter,basic-filters) set (create,add) `](commands/gog-sheets-filter-set.md) - Set a basic filter on a range; replacing an existing filter requires confirmation (or --force)
@@ -680,7 +682,7 @@ Generated from `gog schema --json`.
- [`gog slides (slide) element alt-text [flags]`](commands/gog-slides-element-alt-text.md) - Set or clear element accessibility text
- [`gog slides (slide) element create-line [flags]`](commands/gog-slides-element-create-line.md) - Create a native line on a slide
- [`gog slides (slide) element create-shape [flags]`](commands/gog-slides-element-create-shape.md) - Create a native shape on a slide
- - [`gog slides (slide) element delete (rm) `](commands/gog-slides-element-delete.md) - Delete one page element
+ - [`gog slides (slide) element delete (rm) ...`](commands/gog-slides-element-delete.md) - Delete one or more page elements
- [`gog slides (slide) element group ... [flags]`](commands/gog-slides-element-group.md) - Group two or more elements
- [`gog slides (slide) element style [flags]`](commands/gog-slides-element-style.md) - Style a shape fill/outline or a line
- [`gog slides (slide) element transform (move,resize,rotate) [flags]`](commands/gog-slides-element-transform.md) - Move, resize, rotate, or replace an element transform
@@ -695,6 +697,7 @@ Generated from `gog schema --json`.
- [`gog slides (slide) locate (find-element) [flags]`](commands/gog-slides-locate.md) - Locate text in shapes and table cells with object IDs and UTF-16 ranges
- [`gog slides (slide) move-slide --to-index=TO-INDEX `](commands/gog-slides-move-slide.md) - Move a slide to a zero-based insertion index
- [`gog slides (slide) new-slide [flags]`](commands/gog-slides-new-slide.md) - Create a native themed slide
+ - [`gog slides (slide) paragraph-style [flags]`](commands/gog-slides-paragraph-style.md) - Set paragraph alignment, spacing, indentation, or direction
- [`gog slides (slide) raw [flags]`](commands/gog-slides-raw.md) - Dump raw Google Slides API response as JSON (Presentations.Get; lossless; for scripting and LLM consumption)
- [`gog slides (slide) read-slide [flags]`](commands/gog-slides-read-slide.md) - Read slide content: speaker notes, text elements, and images
- [`gog slides (slide) replace-slide [] [flags]`](commands/gog-slides-replace-slide.md) - Replace an existing slide image from a local file or public URL
diff --git a/docs/commands/README.md b/docs/commands/README.md
index 85210816b..149c1aae3 100644
--- a/docs/commands/README.md
+++ b/docs/commands/README.md
@@ -2,7 +2,7 @@
Every `gog` command has a generated docs page. The source of truth is the live CLI schema; run `make docs-commands` after changing command names, flags, help text, aliases, or arguments.
-Generated pages: 766.
+Generated pages: 769.
## Top-level Commands
@@ -628,6 +628,7 @@ Generated pages: 766.
- [gog schema](gog-schema.md) - Machine-readable command/flag schema
- [gog search](gog-search.md) - Search Drive files (alias for 'drive search')
- [gog searchconsole](gog-searchconsole.md) - Google Search Console
+ - [gog searchconsole inspect](gog-searchconsole-inspect.md) - Inspect URL index status (URL Inspection API)
- [gog searchconsole query](gog-searchconsole-query.md) - Run a Search Analytics query
- [gog searchconsole searchanalytics](gog-searchconsole-searchanalytics.md) - Search Analytics queries
- [gog searchconsole searchanalytics query](gog-searchconsole-searchanalytics-query.md) - Run a Search Analytics query
@@ -675,6 +676,7 @@ Generated pages: 766.
- [gog sheets datasource update](gog-sheets-datasource-update.md) - Update one BigQuery Connected Sheets data source
- [gog sheets delete-dimension](gog-sheets-delete-dimension.md) - Delete rows or columns while preserving intersecting tables
- [gog sheets delete-tab](gog-sheets-delete-tab.md) - Delete a tab/sheet from a spreadsheet (use --force to skip confirmation)
+ - [gog sheets duplicate-tab](gog-sheets-duplicate-tab.md) - Duplicate a tab within a spreadsheet
- [gog sheets export](gog-sheets-export.md) - Export a Google Sheet (pdf|xlsx|csv) via Drive
- [gog sheets filter](gog-sheets-filter.md) - Manage basic filters
- [gog sheets filter set](gog-sheets-filter-set.md) - Set a basic filter on a range; replacing an existing filter requires confirmation (or --force)
@@ -734,7 +736,7 @@ Generated pages: 766.
- [gog slides element alt-text](gog-slides-element-alt-text.md) - Set or clear element accessibility text
- [gog slides element create-line](gog-slides-element-create-line.md) - Create a native line on a slide
- [gog slides element create-shape](gog-slides-element-create-shape.md) - Create a native shape on a slide
- - [gog slides element delete](gog-slides-element-delete.md) - Delete one page element
+ - [gog slides element delete](gog-slides-element-delete.md) - Delete one or more page elements
- [gog slides element group](gog-slides-element-group.md) - Group two or more elements
- [gog slides element style](gog-slides-element-style.md) - Style a shape fill/outline or a line
- [gog slides element transform](gog-slides-element-transform.md) - Move, resize, rotate, or replace an element transform
@@ -749,6 +751,7 @@ Generated pages: 766.
- [gog slides locate](gog-slides-locate.md) - Locate text in shapes and table cells with object IDs and UTF-16 ranges
- [gog slides move-slide](gog-slides-move-slide.md) - Move a slide to a zero-based insertion index
- [gog slides new-slide](gog-slides-new-slide.md) - Create a native themed slide
+ - [gog slides paragraph-style](gog-slides-paragraph-style.md) - Set paragraph alignment, spacing, indentation, or direction
- [gog slides raw](gog-slides-raw.md) - Dump raw Google Slides API response as JSON (Presentations.Get; lossless; for scripting and LLM consumption)
- [gog slides read-slide](gog-slides-read-slide.md) - Read slide content: speaker notes, text elements, and images
- [gog slides replace-slide](gog-slides-replace-slide.md) - Replace an existing slide image from a local file or public URL
diff --git a/docs/commands/gog-api-call.md b/docs/commands/gog-api-call.md
index 1156d49b7..81e36eea2 100644
--- a/docs/commands/gog-api-call.md
+++ b/docs/commands/gog-api-call.md
@@ -33,6 +33,7 @@ gog api call [flags]
| `-h`
`--help` | `kong.helpFlag` | | Show context-sensitive help. |
| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) |
| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
+| `--no-cache` | `bool` | | Fetch the Discovery document without reading or writing the 24-hour disk cache |
| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
| `--params` | `string` | {} | JSON object of path and query parameters |
| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
diff --git a/docs/commands/gog-api-describe.md b/docs/commands/gog-api-describe.md
index fde5cd300..f1f1e7c6f 100644
--- a/docs/commands/gog-api-describe.md
+++ b/docs/commands/gog-api-describe.md
@@ -7,7 +7,7 @@ Describe a Discovery API or method
## Usage
```bash
-gog api describe []
+gog api describe [] [flags]
```
## Parent
@@ -31,6 +31,7 @@ gog api describe []
| `-h`
`--help` | `kong.helpFlag` | | Show context-sensitive help. |
| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) |
| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
+| `--no-cache` | `bool` | | Fetch the Discovery document without reading or writing the 24-hour disk cache |
| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
| `--quota-project` | `string` | | Google Cloud project to bill for API usage (sent as X-Goog-User-Project; some APIs require it with --access-token or ADC) |
diff --git a/docs/commands/gog-drive-ls.md b/docs/commands/gog-drive-ls.md
index 38d5445ab..3b846d1c4 100644
--- a/docs/commands/gog-drive-ls.md
+++ b/docs/commands/gog-drive-ls.md
@@ -36,6 +36,7 @@ gog drive (drv) ls [flags]
| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
| `--max`
`--limit` | `int64` | 20 | Max results |
| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
+| `--order` | `string` | desc | Sort direction |
| `--page`
`--cursor` | `string` | | Page token |
| `--parent` | `string` | | Folder ID to list (default: root) |
| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
@@ -44,6 +45,7 @@ gog drive (drv) ls [flags]
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
+| `--sort` | `string` | modifiedTime | Drive API sort key |
| `-v`
`--verbose` | `bool` | | Enable verbose logging |
| `--version` | `kong.VersionFlag` | | Print version and exit |
| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
diff --git a/docs/commands/gog-ls.md b/docs/commands/gog-ls.md
index 1828b9015..eda5e87d7 100644
--- a/docs/commands/gog-ls.md
+++ b/docs/commands/gog-ls.md
@@ -36,6 +36,7 @@ gog ls (list) [flags]
| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
| `--max`
`--limit` | `int64` | 20 | Max results |
| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
+| `--order` | `string` | desc | Sort direction |
| `--page`
`--cursor` | `string` | | Page token |
| `--parent` | `string` | | Folder ID to list (default: root) |
| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
@@ -44,6 +45,7 @@ gog ls (list) [flags]
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
+| `--sort` | `string` | modifiedTime | Drive API sort key |
| `-v`
`--verbose` | `bool` | | Enable verbose logging |
| `--version` | `kong.VersionFlag` | | Print version and exit |
| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
diff --git a/docs/commands/gog-searchconsole-inspect.md b/docs/commands/gog-searchconsole-inspect.md
new file mode 100644
index 000000000..865581ba0
--- /dev/null
+++ b/docs/commands/gog-searchconsole-inspect.md
@@ -0,0 +1,48 @@
+# `gog searchconsole inspect`
+
+> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
+
+Inspect URL index status (URL Inspection API)
+
+## Usage
+
+```bash
+gog searchconsole (gsc,search-console,webmasters) inspect [flags]
+```
+
+## Parent
+
+- [gog searchconsole](gog-searchconsole.md)
+
+## Flags
+
+| Flag | Type | Default | Help |
+| --- | --- | --- | --- |
+| `--access-token` | `string` | | Use provided access token directly (bypasses stored refresh tokens; token expires in ~1h) |
+| `-a`
`--account`
`--acct` | `string` | | Account email, alias, or auto for authenticated Google API commands |
+| `--client` | `string` | | OAuth client name (selects stored credentials + token bucket) |
+| `--color` | `string` | auto | Color output: auto\|always\|never |
+| `--disable-commands` | `string` | | Comma-separated list of disabled commands; dot paths allowed |
+| `-n`
`--dry-run`
`--dryrun`
`--noop`
`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully |
+| `--enable-commands` | `string` | | Comma-separated list of enabled command prefixes; dot paths allowed (restricts CLI) |
+| `--enable-commands-exact` | `string` | | Comma-separated list of exact enabled commands; dot paths allowed and parent commands do not enable children |
+| `-y`
`--force`
`--assume-yes`
`--yes` | `bool` | | Skip confirmations for destructive commands |
+| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) |
+| `-h`
`--help` | `kong.helpFlag` | | Show context-sensitive help. |
+| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) |
+| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
+| `--language` | `string` | en-US | BCP-47 language for issue messages (e.g. zh-TW) |
+| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
+| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
+| `--quota-project` | `string` | | Google Cloud project to bill for API usage (sent as X-Goog-User-Project; some APIs require it with --access-token or ADC) |
+| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
+| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
+| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
+| `-v`
`--verbose` | `bool` | | Enable verbose logging |
+| `--version` | `kong.VersionFlag` | | Print version and exit |
+| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
+
+## See Also
+
+- [gog searchconsole](gog-searchconsole.md)
+- [Command index](README.md)
diff --git a/docs/commands/gog-searchconsole.md b/docs/commands/gog-searchconsole.md
index 48691adc0..a37d54a6a 100644
--- a/docs/commands/gog-searchconsole.md
+++ b/docs/commands/gog-searchconsole.md
@@ -16,6 +16,7 @@ gog searchconsole (gsc,search-console,webmasters) [flags]
## Subcommands
+- [gog searchconsole inspect](gog-searchconsole-inspect.md) - Inspect URL index status (URL Inspection API)
- [gog searchconsole query](gog-searchconsole-query.md) - Run a Search Analytics query
- [gog searchconsole searchanalytics](gog-searchconsole-searchanalytics.md) - Search Analytics queries
- [gog searchconsole sitemaps](gog-searchconsole-sitemaps.md) - List/get/submit/delete sitemaps
diff --git a/docs/commands/gog-sheets-duplicate-tab.md b/docs/commands/gog-sheets-duplicate-tab.md
new file mode 100644
index 000000000..ac6296020
--- /dev/null
+++ b/docs/commands/gog-sheets-duplicate-tab.md
@@ -0,0 +1,48 @@
+# `gog sheets duplicate-tab`
+
+> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
+
+Duplicate a tab within a spreadsheet
+
+## Usage
+
+```bash
+gog sheets (sheet) duplicate-tab [flags]
+```
+
+## Parent
+
+- [gog sheets](gog-sheets.md)
+
+## Flags
+
+| Flag | Type | Default | Help |
+| --- | --- | --- | --- |
+| `--access-token` | `string` | | Use provided access token directly (bypasses stored refresh tokens; token expires in ~1h) |
+| `-a`
`--account`
`--acct` | `string` | | Account email, alias, or auto for authenticated Google API commands |
+| `--client` | `string` | | OAuth client name (selects stored credentials + token bucket) |
+| `--color` | `string` | auto | Color output: auto\|always\|never |
+| `--disable-commands` | `string` | | Comma-separated list of disabled commands; dot paths allowed |
+| `-n`
`--dry-run`
`--dryrun`
`--noop`
`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully |
+| `--enable-commands` | `string` | | Comma-separated list of enabled command prefixes; dot paths allowed (restricts CLI) |
+| `--enable-commands-exact` | `string` | | Comma-separated list of exact enabled commands; dot paths allowed and parent commands do not enable children |
+| `-y`
`--force`
`--assume-yes`
`--yes` | `bool` | | Skip confirmations for destructive commands |
+| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) |
+| `-h`
`--help` | `kong.helpFlag` | | Show context-sensitive help. |
+| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) |
+| `--index` | `*int64` | | Zero-based insertion index (default: directly after the source tab) |
+| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
+| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
+| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
+| `--quota-project` | `string` | | Google Cloud project to bill for API usage (sent as X-Goog-User-Project; some APIs require it with --access-token or ADC) |
+| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
+| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
+| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
+| `-v`
`--verbose` | `bool` | | Enable verbose logging |
+| `--version` | `kong.VersionFlag` | | Print version and exit |
+| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
+
+## See Also
+
+- [gog sheets](gog-sheets.md)
+- [Command index](README.md)
diff --git a/docs/commands/gog-sheets-raw.md b/docs/commands/gog-sheets-raw.md
index 9308120ea..3b119f953 100644
--- a/docs/commands/gog-sheets-raw.md
+++ b/docs/commands/gog-sheets-raw.md
@@ -39,6 +39,7 @@ gog sheets (sheet) raw [flags]
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
+| `--sheet` | `string` | | Return only this sheet (exact tab title); spreadsheet-level metadata remains included |
| `-v`
`--verbose` | `bool` | | Enable verbose logging |
| `--version` | `kong.VersionFlag` | | Print version and exit |
| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
diff --git a/docs/commands/gog-sheets.md b/docs/commands/gog-sheets.md
index 3a67954a2..daafa520f 100644
--- a/docs/commands/gog-sheets.md
+++ b/docs/commands/gog-sheets.md
@@ -29,6 +29,7 @@ gog sheets (sheet) [flags]
- [gog sheets datasource](gog-sheets-datasource.md) - Manage Connected Sheets data sources and extracts
- [gog sheets delete-dimension](gog-sheets-delete-dimension.md) - Delete rows or columns while preserving intersecting tables
- [gog sheets delete-tab](gog-sheets-delete-tab.md) - Delete a tab/sheet from a spreadsheet (use --force to skip confirmation)
+- [gog sheets duplicate-tab](gog-sheets-duplicate-tab.md) - Duplicate a tab within a spreadsheet
- [gog sheets export](gog-sheets-export.md) - Export a Google Sheet (pdf|xlsx|csv) via Drive
- [gog sheets filter](gog-sheets-filter.md) - Manage basic filters
- [gog sheets find-replace](gog-sheets-find-replace.md) - Find and replace text across a spreadsheet
diff --git a/docs/commands/gog-slides-element-delete.md b/docs/commands/gog-slides-element-delete.md
index ed01c4815..10d20aa70 100644
--- a/docs/commands/gog-slides-element-delete.md
+++ b/docs/commands/gog-slides-element-delete.md
@@ -2,12 +2,12 @@
> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
-Delete one page element
+Delete one or more page elements
## Usage
```bash
-gog slides (slide) element delete (rm)
+gog slides (slide) element delete (rm) ...
```
## Parent
diff --git a/docs/commands/gog-slides-element.md b/docs/commands/gog-slides-element.md
index d3e1b4048..57f8c4a31 100644
--- a/docs/commands/gog-slides-element.md
+++ b/docs/commands/gog-slides-element.md
@@ -19,7 +19,7 @@ gog slides (slide) element
- [gog slides element alt-text](gog-slides-element-alt-text.md) - Set or clear element accessibility text
- [gog slides element create-line](gog-slides-element-create-line.md) - Create a native line on a slide
- [gog slides element create-shape](gog-slides-element-create-shape.md) - Create a native shape on a slide
-- [gog slides element delete](gog-slides-element-delete.md) - Delete one page element
+- [gog slides element delete](gog-slides-element-delete.md) - Delete one or more page elements
- [gog slides element group](gog-slides-element-group.md) - Group two or more elements
- [gog slides element style](gog-slides-element-style.md) - Style a shape fill/outline or a line
- [gog slides element transform](gog-slides-element-transform.md) - Move, resize, rotate, or replace an element transform
diff --git a/docs/commands/gog-slides-insert-text.md b/docs/commands/gog-slides-insert-text.md
index 6c1207911..b0a054753 100644
--- a/docs/commands/gog-slides-insert-text.md
+++ b/docs/commands/gog-slides-insert-text.md
@@ -37,7 +37,7 @@ gog slides (slide) insert-text [flags]
| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
| `--quota-project` | `string` | | Google Cloud project to bill for API usage (sent as X-Goog-User-Project; some APIs require it with --access-token or ADC) |
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
-| `--replace` | `bool` | | Clear existing text in the element before inserting (emits DeleteText + InsertText in the same batch) |
+| `--replace` | `bool` | | Replace existing text while inheriting its leading text style (revision-checked atomic batch) |
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
| `--row` | `*int64` | | 0-based table row index for cell-targeted text; requires --col |
| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
diff --git a/docs/commands/gog-slides-paragraph-style.md b/docs/commands/gog-slides-paragraph-style.md
new file mode 100644
index 000000000..e0e78879d
--- /dev/null
+++ b/docs/commands/gog-slides-paragraph-style.md
@@ -0,0 +1,58 @@
+# `gog slides paragraph-style`
+
+> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
+
+Set paragraph alignment, spacing, indentation, or direction
+
+## Usage
+
+```bash
+gog slides (slide) paragraph-style [flags]
+```
+
+## Parent
+
+- [gog slides](gog-slides.md)
+
+## Flags
+
+| Flag | Type | Default | Help |
+| --- | --- | --- | --- |
+| `--access-token` | `string` | | Use provided access token directly (bypasses stored refresh tokens; token expires in ~1h) |
+| `-a`
`--account`
`--acct` | `string` | | Account email, alias, or auto for authenticated Google API commands |
+| `--align` | `string` | | Paragraph alignment: START, CENTER, END, JUSTIFIED |
+| `--client` | `string` | | OAuth client name (selects stored credentials + token bucket) |
+| `--col` | `*int64` | | Zero-based table column; requires --row |
+| `--color` | `string` | auto | Color output: auto\|always\|never |
+| `--direction` | `string` | | Text direction: LEFT_TO_RIGHT or RIGHT_TO_LEFT |
+| `--disable-commands` | `string` | | Comma-separated list of disabled commands; dot paths allowed |
+| `-n`
`--dry-run`
`--dryrun`
`--noop`
`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully |
+| `--enable-commands` | `string` | | Comma-separated list of enabled command prefixes; dot paths allowed (restricts CLI) |
+| `--enable-commands-exact` | `string` | | Comma-separated list of exact enabled commands; dot paths allowed and parent commands do not enable children |
+| `-y`
`--force`
`--assume-yes`
`--yes` | `bool` | | Skip confirmations for destructive commands |
+| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) |
+| `-h`
`--help` | `kong.helpFlag` | | Show context-sensitive help. |
+| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) |
+| `--indent-end` | `*float64` | | Paragraph end indentation in points |
+| `--indent-first-line` | `*float64` | | First-line indentation in points |
+| `--indent-start` | `*float64` | | Paragraph start indentation in points |
+| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
+| `--line-spacing` | `*float64` | | Line spacing percent (100 is normal) |
+| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
+| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
+| `--quota-project` | `string` | | Google Cloud project to bill for API usage (sent as X-Goog-User-Project; some APIs require it with --access-token or ADC) |
+| `--range` | `string` | | UTF-16 range as start:end (default: all text); styles every intersecting paragraph |
+| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
+| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
+| `--row` | `*int64` | | Zero-based table row; requires --col |
+| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
+| `--space-above` | `*float64` | | Space above each paragraph in points |
+| `--space-below` | `*float64` | | Space below each paragraph in points |
+| `-v`
`--verbose` | `bool` | | Enable verbose logging |
+| `--version` | `kong.VersionFlag` | | Print version and exit |
+| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
+
+## See Also
+
+- [gog slides](gog-slides.md)
+- [Command index](README.md)
diff --git a/docs/commands/gog-slides.md b/docs/commands/gog-slides.md
index a7643ec5f..db5c55d89 100644
--- a/docs/commands/gog-slides.md
+++ b/docs/commands/gog-slides.md
@@ -34,6 +34,7 @@ gog slides (slide) [flags]
- [gog slides locate](gog-slides-locate.md) - Locate text in shapes and table cells with object IDs and UTF-16 ranges
- [gog slides move-slide](gog-slides-move-slide.md) - Move a slide to a zero-based insertion index
- [gog slides new-slide](gog-slides-new-slide.md) - Create a native themed slide
+- [gog slides paragraph-style](gog-slides-paragraph-style.md) - Set paragraph alignment, spacing, indentation, or direction
- [gog slides raw](gog-slides-raw.md) - Dump raw Google Slides API response as JSON (Presentations.Get; lossless; for scripting and LLM consumption)
- [gog slides read-slide](gog-slides-read-slide.md) - Read slide content: speaker notes, text elements, and images
- [gog slides replace-slide](gog-slides-replace-slide.md) - Replace an existing slide image from a local file or public URL
diff --git a/docs/docs-editing.md b/docs/docs-editing.md
index 88a19c838..5f92856ca 100644
--- a/docs/docs-editing.md
+++ b/docs/docs-editing.md
@@ -18,7 +18,7 @@ gog docs write --append --markdown --text '## Status'
Replace the document body with Markdown from a file:
```bash
-gog docs write --replace --markdown --content-file README.md
+gog docs write --replace --markdown --file README.md
```
The local Markdown renderer keeps headings adjacent to their following body,
diff --git a/docs/examples.md b/docs/examples.md
index b1a8cd227..cafeb7a92 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -66,6 +66,8 @@ See [Drive audits](drive-audits.md), [polling](polling.md), and
```bash
# Read-only folder audits.
+gog drive ls --sort modifiedByMeTime --order desc
+gog drive ls --sort name_natural --order asc
gog drive tree --parent --depth 2
gog drive du --parent --max 20 --json
gog drive inventory --parent --json
@@ -192,6 +194,7 @@ See [batch updates](sheets-batch-update.md), [tables](sheets-tables.md), and
```bash
gog sheets get 'Sheet1!A1:D20' --json
+gog sheets duplicate-tab Sheet1 "Sheet1 backup" --json
gog sheets update 'Sheet1!B13' \
--values-json @formula.json --fail-on-formula-error --json
gog sheets batch-update --data-json @updates.json --json
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 5f686f01a..4ef3384f2 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -107,13 +107,26 @@ gog auth doctor --check
## 5. Set a default account
+For a persistent default, open the account manager and click **Set default**
+beside the account you want to use:
+
+```bash
+gog auth manage
+```
+
+The stored default applies when neither `--account` nor `GOG_ACCOUNT` is set.
+If you previously exported `GOG_ACCOUNT`, run `unset GOG_ACCOUNT` to use the
+stored default.
+
+Alternatively, select an account for your shell with:
+
```bash
export GOG_ACCOUNT=you@gmail.com
-# or persist a default with gog auth alias
-gog auth alias set default you@gmail.com
```
-Now you can drop `--account` from every command.
+Either approach lets you omit `--account` from commands. Account aliases such
+as `work` are shortcuts for explicit selection; `default` and `auto` are
+reserved names, not aliases you can create.
## 6. Run real commands
diff --git a/docs/raw-api.md b/docs/raw-api.md
index 26812526f..1c3058aba 100644
--- a/docs/raw-api.md
+++ b/docs/raw-api.md
@@ -32,8 +32,15 @@ gog docs raw --tab "Notes" --pretty
gog docs raw --all-tabs --json > doc-tabs-api.json
gog gmail raw --format metadata --json
gog sheets raw --include-grid-data --json
+gog sheets raw --sheet "Quarterly Data" --include-grid-data --json
```
+`gog sheets raw --sheet` selects one exact tab title on the server, reducing
+the response before it is downloaded. Titles such as `A1` and titles containing
+apostrophes are treated as tab names. Spreadsheet-level metadata remains in
+the raw response; grid data still requires `--include-grid-data`. Omitting
+`--sheet` returns every tab as before.
+
`gog docs raw --tab` resolves a tab title or ID and projects that tab into the
legacy top-level `Document` fields such as `body`, `lists`, and
`inlineObjects`. `--all-tabs` keeps the canonical `Documents.Get` response and
diff --git a/docs/refactor/README.md b/docs/refactor/README.md
index 504ac0b85..8da44006d 100644
--- a/docs/refactor/README.md
+++ b/docs/refactor/README.md
@@ -1,19 +1,17 @@
---
-summary: "Refactor notes (implementation status + next wins)"
+summary: "Shared implementation patterns"
read_when:
- Touching exports/output/templates
- Planning cleanup work
---
-# Refactor notes
+# Shared implementation patterns
-Shipped (today)
+These notes describe existing code to reuse when changing adjacent commands:
-- `exports.md`: Drive-backed export command pattern (`docs|slides|sheets`).
-- `output.md`: shared table + paging helpers.
-- `templates.md`: googleauth HTML templates via `//go:embed`.
-
-Backlog / next wins
-
-- `options.md`: ideas; pick + execute when touching adjacent code.
+- [Exports](exports.md): Drive-backed Docs, Slides, and Sheets downloads.
+- [Output](output.md): invocation-aware table, result, and paging output.
+- [Templates](templates.md): embedded Google authorization HTML.
+- [Ownership](options.md): service setup, pagination, and retry boundaries.
+See [Architecture and contracts](../spec.md) for the command and package layout.
diff --git a/docs/refactor/exports.md b/docs/refactor/exports.md
index e56b7ba8a..0a97dccb0 100644
--- a/docs/refactor/exports.md
+++ b/docs/refactor/exports.md
@@ -11,8 +11,8 @@ Goal: one implementation for “export Google *Thing* via Drive”.
## Current pattern
-- Shared builder: `internal/cmd/export_via_drive.go:newExportViaDriveCmd`
-- Shared download: `internal/cmd/drive.go:downloadDriveFile` (handles Drive “native” exports + normal files)
+- Shared orchestration: `internal/cmd/export_via_drive.go:exportViaDrive`
+- Shared download: `internal/cmd/drive_download.go:downloadDriveFile` (handles Drive “native” exports + normal files)
Each service command is a thin wrapper:
@@ -26,7 +26,7 @@ Exception: `gog docs export --tab ` exports a single Google Docs ta
- Arg is always the Drive file id (Doc/Sheet/Slides id).
- Type guard: compare `mimeType` and error with `file is not a (mimeType="...")`.
-- `--out` defaults to `$(os.UserConfigDir())/gogcli/drive-downloads/` (via `internal/config:EnsureDriveDownloadsDir`).
+- `--out` defaults to `$(os.UserConfigDir())/gogcli/drive-downloads/` (via the resolved config layout).
- `--out` can be an existing directory, a new directory ending in `/`, or an explicit file path (via `internal/cmd/drive_download_helpers.go:resolveDriveDownloadDestPath`); missing parent directories are created only when writing the download.
- `--out -` writes export bytes to stdout; JSON mode rejects it to avoid mixing metadata with bytes.
- Output
@@ -37,5 +37,5 @@ Exception: `gog docs export --tab ` exports a single Google Docs ta
## Add a new export command
1) Pick expected Drive mime type + allowed formats.
-2) Add a new `newXExportCmd` calling `newExportViaDriveCmd(...)`.
+2) Add a Kong command struct whose `Run` method calls `exportViaDrive` with `exportViaDriveOptions`.
3) Add/extend tests in `internal/cmd/execute_drive_*_test.go` style (fake Drive server).
diff --git a/docs/refactor/options.md b/docs/refactor/options.md
index 1b9105de7..c6ec93650 100644
--- a/docs/refactor/options.md
+++ b/docs/refactor/options.md
@@ -1,25 +1,26 @@
---
-summary: "Refactor options (next wins)"
+summary: "Shared command helpers and ownership boundaries"
read_when:
- Planning cleanup work
- Touching retry/logging/output plumbing
---
-# Refactor options (next wins)
+# Shared command helpers
-Small wins
+Before adding a helper, check the existing owner:
-- “List + page” helper: generic wrapper for `--max/--page` + `nextPageToken` output.
-- Standardize list headers: consistent column naming (ID/NAME/EMAIL/etc).
-- “Output row” helpers: centralize `sanitizeTab` use for tabular output.
+- `internal/cmd/service_helpers.go` selects accounts and initializes services.
+- `internal/cmd/paging.go` collects bounded or unbounded result pages;
+ `paging_guard.go` rejects repeated tokens for command-specific loops.
+- `internal/cmd/output_helpers.go` routes table and result output through the
+ invocation context and prints pagination hints on stderr.
+- `internal/cmd/drive_download.go` resolves Drive export formats;
+ `export_via_drive.go` orchestrates typed document exports.
+- `internal/googleapi/transport.go` handles HTTP retries and replayable bodies.
+ `WithoutRetries` is available for operations whose callers require a single
+ attempt; do not add a second generic retry loop around it.
-Medium wins
-
-- Drive “export format” registry: single map for docs/sheets/slides format help + validation.
-- Shared “service bootstrap” helpers: reduce per-command boilerplate for `requireAccount` + `newXService`.
-- Test harness helpers: one fake Google API server util (Drive/Gmail/Calendar/Tasks) with common JSON assertions.
-
-Bigger wins
-
-- API client retry unification: one retry stack (transport vs explicit); delete the other; push logs behind `--verbose`.
-- Command grouping / UX: consolidate “download/export” story; ensure help text + flags match across services.
+Keep service-specific pagination limits, partial-result rules, and output
+shapes at the command boundary. Share mechanics without silently changing
+those contracts. Existing fake-server tests next to the affected commands
+provide request and response examples.
diff --git a/docs/refactor/output.md b/docs/refactor/output.md
index 58c64602e..6dc980ac0 100644
--- a/docs/refactor/output.md
+++ b/docs/refactor/output.md
@@ -13,7 +13,7 @@ Goal: kill copy/paste; keep output consistent.
Use `internal/cmd/output_helpers.go:tableWriter(ctx)`:
-- `--plain`: `os.Stdout` (no alignment, TSV-friendly)
+- `--plain`: the invocation stdout writer (no alignment, TSV-friendly)
- default: `tabwriter.Writer` (aligned columns)
Call pattern:
diff --git a/docs/slides-structure.md b/docs/slides-structure.md
index 73cf3fe56..640314427 100644
--- a/docs/slides-structure.md
+++ b/docs/slides-structure.md
@@ -108,6 +108,7 @@ gog slides element ungroup ...
gog slides element alt-text \
--title "Chart" --description "Quarterly revenue by region"
gog slides element delete --force
+gog slides element delete --force
```
`z-order` targets must share one slide and must not be grouped. `group` needs at
@@ -115,3 +116,9 @@ least two ungrouped elements on one slide; Slides does not permit every element
kind to be grouped. Passing an empty `--title=` or `--description=` clears that
alt-text field. Element deletion is destructive and requires confirmation or
`--force` in non-interactive use. Every mutation supports `--dry-run --json`.
+
+Multiple deletion targets are submitted together in one atomic API batch;
+duplicate IDs are rejected before submission. A single target retains the
+existing `objectId` JSON field; multiple targets return `objectIds` instead.
+Text replacement does not edit WordArt text. To replace a WordArt heading,
+delete its element and create a text box with the desired text.
diff --git a/docs/slides-text-editing.md b/docs/slides-text-editing.md
index 7e84a9cd7..ad72ad3b9 100644
--- a/docs/slides-text-editing.md
+++ b/docs/slides-text-editing.md
@@ -1,5 +1,12 @@
# Slides text editing
+`insert-text --replace` inserts before removing the old text so replacement
+text inherits the leading visible text's style, including template inheritance.
+An empty target receives only an insertion. The command reads the target first
+and pins its revision for the atomic update. Google-stripped control/private-use
+characters are removed before calculating UTF-16 deletion offsets; an empty
+replacement clears the target. Dry runs remain auth-free previews.
+
Slides text ranges use UTF-16 code-unit indexes. Find exact element IDs and
ranges before changing a deck:
@@ -29,6 +36,22 @@ auth or API access.
## Replace text safely
+`paragraph-style` formats all paragraphs in one shape, or the paragraphs
+intersecting an optional UTF-16 range. It also supports one table cell:
+
+```bash
+gog slides paragraph-style --align START --line-spacing 120
+gog slides paragraph-style --range 0:20 --space-above 0 --space-below 8
+gog slides paragraph-style --indent-start 18 --indent-first-line 0
+gog slides paragraph-style --row 0 --col 1 --align CENTER
+```
+
+Spacing and indentation are in points; line spacing is a percentage (100 is
+normal). Only supplied fields are changed, including explicit zero values.
+Use `--direction LEFT_TO_RIGHT` or `RIGHT_TO_LEFT` for paragraph direction.
+Table cells are validated against the current presentation and updated with
+revision protection. Dry runs require no authentication.
+
`replace-text` requires an explicit scope:
```bash
diff --git a/docs/spec.md b/docs/spec.md
index 1e516e6c6..34842cc19 100644
--- a/docs/spec.md
+++ b/docs/spec.md
@@ -1,597 +1,90 @@
-# gogcli spec
+# Architecture and contracts
-## Goal
+`gog` is a Go CLI for Google Workspace and related APIs. The command tree is
+built with Kong in `internal/cmd`; `cmd/gog` is the process entrypoint. The Go
+minimum and preferred toolchain are declared in `go.mod`.
-Build a single, clean, modern Go CLI that talks to:
+The running command tree is the source of truth for command names, arguments,
+flags, and aliases:
-- Gmail API
-- Google Calendar API
-- Google Chat API
-- Google Classroom API
-- Google Drive API
-- Google Drive Labels API
-- Google Docs API
-- Google Sheets API
-- Google Forms API
-- Google Maps Places API
-- Google Photos Library API
-- Google Photos Picker API
-- Apps Script API
-- Google Tasks API
-- Cloud Identity API (Groups)
-- Google People API (Contacts + directory)
-- Google Keep API (Workspace-only, service account)
+```bash
+gog schema --json
+gog schema gmail search --json
+gog help docs write
+```
-This replaces the existing separate CLIs (`gmcli`, `gccli`, `gdcli`) and the Python contacts server conceptually, but:
+Generated [command references](commands/README.md) and
+[agent skills](agent-skills.md) come from that schema. Update command structs
+first, then regenerate with `make docs-commands` and `make agent-skills`.
-- no backwards compatibility
-- no migration tooling
+## Command boundaries
-## Non-goals
+Commands validate local inputs before creating API clients or mutating state.
+Shared service helpers select the account and construct authenticated clients;
+request planners in packages such as `docsedit`, `docssed`, and `sheetsdimension`
+keep API request construction separate from command orchestration.
-- Preserving legacy command names/flags/output formats
-- Importing existing `~/.gmcli`, `~/.gccli`, `~/.gdcli` state
-- Exposing the whole CLI through a generic MCP command-execution bridge
+Docs editing commands are grouped by operation in `internal/cmd/docs_write.go`,
+`docs_write_markdown.go`, `docs_update.go`, `docs_insert.go`, `docs_delete.go`,
+and `docs_find_replace.go`. `docs_edit.go` retains the `docs edit` command
+wrapper and shared deprecated-tab flag resolution. See
+[Docs editing](docs-editing.md) for the user-facing behavior and
+[persisted batches](docs-batch.md) for revision and commit semantics.
-## MCP server
+Whole-document Docs, Sheets, and Slides exports share
+`internal/cmd/export_via_drive.go`. The experimental single-tab Docs export
+uses the Docs web endpoint instead; see [export conventions](https://github.com/openclaw/gogcli/blob/main/docs/refactor/exports.md).
-`gog mcp` runs a typed MCP server over stdio for agent clients that need a
-permissioned Google Workspace tool surface. It intentionally does not expose a
-generic shell/argv bridge. Each MCP tool has a fixed schema and maps to a
-specific `gog` operation.
+The generic Discovery API surface has its own request validation, host guards,
+and bounded document cache in `internal/discoveryapi`. See [Raw API](raw-api.md)
+and [Automation](automation.md#discovery-document-cache).
-MCP defaults are read-only. Write tools are hidden unless the server is started
-with `--allow-write`, and `--allow-tool` can further narrow the registered tool
-set by tool name or service prefix. Parent root context such as `--account`,
-`--home`, output mode, `--no-input`, untrusted wrapping, and command safety
-flags is preserved for subprocess calls.
+## Authentication and storage
-## Language/runtime
+`internal/googleauth` owns OAuth flows, service scopes, and account selection;
+`internal/googleapi` constructs service clients and owns the shared retry
+transport. Browser, manual, remote, and account-manager authorization use S256
+PKCE. Stored tokens track Google's OIDC subject when available and preserve
+legacy email-keyed lookup compatibility.
-- Go `1.26` (see `go.mod`)
+`internal/config` resolves configuration, data, state, and cache paths.
+`internal/secrets` owns platform keyring and encrypted file storage. Do not
+introduce a separate plaintext token store or bypass the existing keyring
+locking and timeout behavior.
-## CLI framework
+The detailed contracts live in:
-- `github.com/alecthomas/kong`
-- Root command: `gog`
-- Global flag:
- - `--color=auto|always|never` (default `auto`)
- - `--json` (JSON output to stdout)
- - `--plain` (TSV output to stdout; stable/parseable; disables colors)
- - `--force` (skip confirmations for destructive commands)
- - `--no-input` (never prompt; fail instead)
- - `--version` (print version)
+- [OAuth clients](auth-clients.md): account/client routing, scopes, service accounts.
+- [Paths and state](paths.md): overrides, XDG layout, and legacy reads.
+- [Quickstart](quickstart.md): OAuth setup and authorization.
+- The generated [OAuth service table](https://github.com/openclaw/gogcli#supported-oauth-services).
-Notes:
+## Output and safety
-- We run `SilenceUsage: true` and print errors ourselves (colored when possible).
-- `NO_COLOR` is respected.
+`internal/outfmt` and the command output helpers keep JSON and TSV on stdout;
+`internal/ui` sends progress and diagnostics to stderr. Use the invocation's
+context writer so embedded commands and tests preserve output routing.
-Environment:
+[Automation](automation.md) defines JSON projection, output precedence, exit
+codes, retries, dry runs, and read-only behavior. [Safety profiles](safety-profiles.md)
+define baked command policy and locked flags. Existing CLI flags, aliases,
+configuration keys, and storage formats are compatibility contracts.
-- `GOG_COLOR=auto|always|never` (default `auto`, overridden by `--color`)
-- `GOG_JSON=1` (default JSON output; overridden by flags)
-- `GOG_PLAIN=1` (default plain output; overridden by flags)
+The [MCP server](mcp.md) exposes a typed stdio tool surface with read-only
+defaults and explicit write authorization. It does not expose arbitrary shell
+or argv execution.
-## Output (TTY-aware colors)
+## Development gates
-- `github.com/muesli/termenv` is used to detect rich TTY capabilities and render colored output.
-- Colors are enabled when:
- - output is a rich terminal and `--color=auto`, and `NO_COLOR` is not set; or
- - `--color=always`
-- Colors are disabled when:
- - `--color=never`; or
- - `NO_COLOR` is set
+`Makefile` owns development-tool pins and local gates; `.golangci.yml` configures
+linting against the minimum Go version. `make fmt` applies goimports and
+gofumpt; `make fmt-check` reports formatting differences without editing files.
-Implementation: `internal/ui/ui.go`.
+Run `make ci` for formatting, lint, deadcode, Go and script tests, Docker
+version consistency, documentation coverage, and generated agent skills. The
+tracking worker has a separate pnpm workspace and `make worker-ci` gate.
+GitHub workflows under `.github/workflows` define the platform matrix.
-## Auth + secret storage
-
-### OAuth client credentials (non-secret-ish)
-
-- Stored on disk in the per-user config directory:
- - `$(os.UserConfigDir())/gogcli/credentials.json` (default client)
- - `$(os.UserConfigDir())/gogcli/credentials-.json` (named clients)
-- Written with mode `0600`.
-- Command:
- - `gog auth credentials `
- - `gog --client auth credentials `
- - `gog auth credentials list`
- - `gog auth credentials remove [|all]`
-- Supports Google’s downloaded JSON format:
- - `installed.client_id/client_secret` or `web.client_id/client_secret`
-
-Implementation: `internal/config/*`.
-
-### Refresh tokens (secrets)
-
-- Stored in OS credential store via `github.com/99designs/keyring`.
-- Key namespace is `gogcli` by default (keyring `ServiceName`); override with `GOG_KEYRING_SERVICE_NAME`.
-- Key format: `token::` (default client uses `token:default:`)
-- Canonical identity key format for new tokens with an OIDC subject: `token-sub::`. Email-keyed entries remain as compatibility lookup keys.
-- Legacy key format: `token:` (migrated on first read)
-- Stored payload is JSON (refresh token + metadata like OIDC subject, current email, selected services/scopes).
-- Email is treated as display/contact state; Google's OIDC `sub` is used to detect the same account after an email rename and migrate aliases/defaults/client mappings on reauthorization.
-- Keyring operations are bounded by a timeout (default `30s` on macOS and `10s` elsewhere, configurable via `GOG_KEYRING_OPEN_TIMEOUT`) so non-surfacing permission prompts and unresponsive backends return actionable guidance instead of hanging indefinitely.
-- Fallback: if no OS credential store is available, keyring may use its encrypted "file" backend:
- - Directory: `$(os.UserConfigDir())/gogcli/keyring/` (one file per key; gog-managed key names are encoded for portable filenames)
- - Password: prompts on TTY; for non-interactive runs set `GOG_KEYRING_PASSWORD`
-
-Current minimal management commands (implemented):
-
-- `gog auth tokens list` (keys only; does not decrypt token payloads)
-- `gog auth tokens delete `
-- `gog auth list` reports unreadable token entries instead of failing the whole listing, so one bad file-keyring entry does not hide other accounts.
-
-Implementation: `internal/secrets/store.go`.
-
-### OAuth flow
-
-- Desktop OAuth 2.0 flow using local HTTP redirect on an ephemeral port.
-- Supports a browserless/manual flow (paste redirect URL) for headless environments.
-- Supports a remote/server-friendly 2-step manual flow:
- - Step 1 prints an auth URL (`gog auth add ... --remote --step 1`)
- - Step 2 exchanges the pasted redirect URL and requires `state` validation (`--remote --step 2 --auth-url ...`)
- - Browser, manual, remote, and account-manager flows bind authorization
- requests and token exchanges with S256 PKCE.
- - Remote steps must share the same config home and OAuth client. Unfinished
- pre-v0.24.0 flows must restart at step 1.
-- Refresh token issuance:
- - requests `access_type=offline`
- - supports `--force-consent` to force the consent prompt when Google doesn't return a refresh token
- - uses `include_granted_scopes=true` to support incremental auth re-runs for full-scope authorization
- - omits `include_granted_scopes` for limited scope presets and reauthorization of known narrow Gmail grants so broader historical permissions are not silently restored
-
-Scope selection note:
-
-- The consent screen shows the scopes the CLI requested.
-- Users cannot selectively un-check individual requested scopes in the consent screen; they either approve all requested scopes or cancel.
-- To request fewer scopes, choose fewer services via `gog auth add --services ...`, use `gog auth add --readonly`, or select a limited service preset such as `--gmail-scope readonly|send|read-send`.
-
-## Config layout
-
-- Base config dir: `$(os.UserConfigDir())/gogcli/`
-- Files:
- - `config.json` (optional preferences; JSON5; comments and trailing commas allowed)
- - `credentials.json` (OAuth client id/secret; default client)
- - `credentials-.json` (OAuth client id/secret; named clients)
-- State:
- - `state/gmail-watch/.json` (Gmail watch state)
- - `oauth-manual-state-.json` (temporary manual OAuth state and PKCE verifier cache; expires quickly; no tokens)
-- Secrets:
- - refresh tokens in keyring
-
-We intentionally avoid storing refresh tokens in plain JSON on disk.
-
-Environment:
-
-- `GOG_ACCOUNT=you@gmail.com` (email or alias; used when `--account` is not set; otherwise uses keyring default or a single stored token)
-- `GOG_CLIENT=work` (select OAuth client bucket; see `--client`)
-- `GOG_KEYRING_PASSWORD=...` (used when keyring falls back to encrypted file backend in non-interactive environments)
-- `GOG_KEYRING_BACKEND={auto|keychain|file}` (force backend; use `file` to avoid Keychain prompts and pair with `GOG_KEYRING_PASSWORD` for non-interactive)
-- `GOG_KEYCHAIN_TRUST_APPLICATION={auto|true|false}` (control macOS Keychain application trust; auto enables it only for a stably signed binary)
-- `GOG_KEYRING_SERVICE_NAME=...` (override keyring namespace/service name; default `gogcli`)
-- `GOG_KEYRING_OPEN_TIMEOUT=30s` (max time to wait for a keyring open/operation — e.g. a macOS Keychain permission prompt — before failing; Go duration, default `30s` on macOS and `10s` elsewhere)
-- `GOG_TIMEZONE=America/New_York` (default output timezone; IANA name or `UTC`; `local` forces local timezone)
-- `GOG_ENABLE_COMMANDS=calendar,tasks,gmail.search` (optional prefix allowlist; dot paths allowed; parent paths allow children)
-- `GOG_ENABLE_COMMANDS_EXACT=calendar.events,gmail.search` (optional exact allowlist; dot paths allowed; parent paths do not allow children)
-- `GOG_DISABLE_COMMANDS=gmail.send,gmail.drafts.send` (optional denylist; dot paths allowed)
-- `GOG_GMAIL_NO_SEND=1` (block Gmail send operations)
-- `config.json` can also set `keyring_backend` (JSON5; env vars take precedence)
-- `config.json` can also set `default_timezone` (IANA name or `UTC`)
-- `config.json` can also set `places_api_key` (or use `GOG_PLACES_API_KEY` / `GOOGLE_PLACES_API_KEY`) for Calendar Places lookups.
-- `config.json` can also set `account_aliases` for `gog auth alias` (JSON5)
-- `config.json` can also set `account_clients` (email -> client) and `client_domains` (domain -> client)
-- `config.json` can also set `gmail_no_send` and `no_send_accounts` for send guards
-
-Flag aliases:
-- `--out` also accepts `--output`.
-- `--out-dir` also accepts `--output-dir` (Gmail thread attachment downloads).
-- Drive download/export commands accept `--out -` to write file bytes to stdout; `--json --out -` is rejected.
-
-## Commands (current + planned)
-
-### Implemented
-
-- `gog auth credentials `
-- `gog auth credentials list`
-- `gog auth credentials remove [|all]`
-- `gog --client auth credentials `
-- `gog auth add [--services user|all-user|all|gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,photos,photospicker,appscript,analytics,searchconsole,ads,youtube] [--readonly] [--drive-scope full|readonly|file] [--gmail-scope full|readonly|send|read-send] [--extra-scopes CSV] [--manual] [--remote] [--step 1|2] [--auth-url URL] [--listen-addr HOST[:PORT]] [--redirect-host HOST] [--timeout DURATION] [--force-consent]`
-- `gog auth services [--markdown]`
-- `gog auth manage [--services ...] [--listen-addr HOST[:PORT]] [--redirect-host HOST] [--dry-run]` (interactive browser flow; real execution fails with usage exit code 2 under `--no-input`)
-- `gog auth keep --key ` (Google Keep; Workspace only)
-- `gog auth list`
-- `gog auth doctor [--check]` (diagnose missing OAuth client credentials, keyring/password drift, and refresh-token failures)
-- `gog auth alias list`
-- `gog auth alias set `
-- `gog auth alias unset `
-- `gog auth status`
-- `gog auth remove `
-- `gog auth tokens list`
-- `gog auth tokens delete `
-- `gog config get `
-- `gog config keys`
-- `gog config list`
-- `gog config path`
-- `gog config set `
-- `gog config unset `
-- `gog version`
-- `gog drive ls [--all] [--parent ID] [--max N] [--page TOKEN] [--query Q] [--[no-]all-drives]` (`--all` and `--parent` are mutually exclusive)
-- `gog drive search [--raw-query] [--max N] [--page TOKEN] [--[no-]all-drives]`
-- `gog drive get `
-- `gog drive download [--out PATH|-] [--format F]` (`--format` only applies to Google Workspace files; `--format md` exports a Google Doc as Markdown)
-- `gog drive upload [--name N] [--parent ID] [--convert] [--convert-to doc|sheet|slides] [--keep-frontmatter]` (Markdown → Google Doc with `--convert` or `--convert-to doc`: leading `---`/`---` frontmatter is stripped before upload unless `--keep-frontmatter`; delimiter-based, not a full YAML parse; large non-JSON uploads print progress to stderr)
-- `gog drive sync push --parent ID [--dry-run] [--[no-]all-drives]` (recursively reconciles local contents without deleting remote-only files; duplicate names, wrong types, Google-native files, and local symlinks fail before mutation)
-- `gog drive mkdir [--parent ID]`
-- `gog drive delete [--permanent]`
-- `gog drive move --parent ID`
-- `gog drive rename `
-- `gog drive shortcut create --parent ID [--name N]`
-- `gog drive share --to anyone|user|domain [--email addr] [--domain example.com] [--role reader|writer|commenter] [--discoverable]`
-- `gog drive permissions [--max N] [--page TOKEN]`
-- `gog drive unshare `
-- `gog drive url `
-- `gog drive drives [--max N] [--page TOKEN] [--query Q]`
-- `gog drive changes start-token [--drive DRIVE_ID]`
-- `gog drive changes list --token TOKEN [--max N] [--all] [--drive DRIVE_ID]`
-- `gog drive changes poll --state-file PATH [--interval DURATION] [--on-change COMMAND] [--filter-file FILE_ID] [--drive DRIVE_ID]`
-- `gog drive changes serve --state-file PATH (--channel-token TOKEN|--channel-token-file PATH) [--listen ADDR] [--notification-timeout DURATION] [--on-change COMMAND] [--filter-file FILE_ID] [--auto-renew --webhook-url HTTPS_URL]`
-- `gog drive changes watch --token TOKEN --webhook-url URL [--channel-id ID] [--channel-token TOKEN]`
-- `gog drive changes stop `
-- `gog drive activity query [--file FILE_ID|--folder FOLDER_ID] [--actions edit,share] [--from RFC3339] [--to RFC3339] [--filter FILTER]`
-- `gog drive audit sharing [--file FILE_ID|--parent FOLDER_ID] [--depth N] [--max N] [--internal-domain DOMAIN] [--public-only|--external-only] [--fail-found]`
-- `gog drive audit user [--file FILE_ID|--parent FOLDER_ID] [--depth N] [--max N] [--fail-found]`
-- `gog drive bulk remove-public [--file FILE_ID|--parent FOLDER_ID] [--depth N] [--dry-run] [--force]`
-- `gog drive bulk update-role --from reader|commenter|writer --to reader|commenter|writer [--file FILE_ID|--parent FOLDER_ID] [--type user|group|domain|anyone] [--target EMAIL_OR_DOMAIN] [--dry-run] [--force]`
-- `gog drive labels list [--max N] [--page TOKEN] [--customer CUSTOMERS_ID] [--published-only]` (requires a Google Workspace customer)
-- `gog drive labels get [--view basic|full]` (requires a Google Workspace customer)
-- `gog drive labels file list [--max N] [--page TOKEN]`
-- `gog drive labels file apply [--text FIELD=VALUE] [--selection FIELD=CHOICE[,CHOICE]] [--integer FIELD=N] [--date FIELD=YYYY-MM-DD] [--user FIELD=email] [--unset FIELD] [--fields-json JSON]`
-- `gog drive labels file remove `
-
-Drive hierarchy semantics:
-
-- Files and folders are identified by stable opaque IDs, not paths.
-- New files have one parent folder. The API still returns `parents` as an array
- so legacy My Drive records with multiple parents can be read; `drive move`
- removes every old parent and installs exactly the requested parent.
-- An item visible from another folder is represented by a separate shortcut
- file with its own ID, name, parent, and permissions. Shortcut metadata exposes
- `shortcutDetails.targetId`, `targetMimeType`, and `targetResourceKey`.
-- Mutations apply to the exact ID passed. Commands do not silently dereference
- shortcut IDs to their targets.
-- `drive tree`, `drive inventory`, and `drive du` treat shortcuts as leaves,
- including shortcuts whose targets are folders.
-- Tree and inventory output one row per discovered placement. Size summaries
- aggregate each placement independently, even when legacy parent links expose
- the same folder ID through multiple paths.
-- Folder scans reject an ancestor cycle instead of following it indefinitely.
-
-- `gog slides thumbnail [--size small|medium|large] [--format png|jpeg] [--out PATH]`
-- `gog slides element ...` (native page-element lifecycle; exact batch payloads available with `--dry-run --json`)
-- `gog calendar calendars`
-- `gog calendar subscribe `
-- `gog calendar unsubscribe `
-- `gog calendar create-calendar [--description D] [--timezone TZ] [--location L]`
-- `gog calendar delete-calendar `
-- `gog calendar acl `
-- `gog calendar events [--cal ID_OR_NAME] [--calendars CSV] [--all] [--from RFC3339] [--to RFC3339] [--max N] [--page TOKEN] [--query Q] [--event-types TYPES] [--weekday]`
- - `--event-types` filters to one or more event types (repeatable or comma-separated): `default`, `birthday`, `focus-time`, `from-gmail`, `out-of-office`, `working-location`. Unset returns all types (the API default).
-- `gog calendar event|get `
-- `GOG_CALENDAR_WEEKDAY=1` defaults `--weekday` for `gog calendar events`
-- `gog calendar create --summary S --from DT --to DT [--timezone TZ] [--start-timezone TZ] [--end-timezone TZ] [--description D] [--location L|--location-search Q|--place-id ID] [--place-language LANG] [--place-region REGION] [--attendees a@b.com,c@d.com] [--all-day] [--event-type TYPE]`
-- `gog calendar update [--summary S] [--from DT] [--to DT] [--start-timezone TZ] [--end-timezone TZ] [--description D] [--location L|--location-search Q|--place-id ID] [--place-language LANG] [--place-region REGION] [--attendees ...] [--add-attendee ...] [--attachment URL ...] [--all-day] [--with-meet|--regenerate-meet] [--event-type TYPE]`
-- `gog calendar delete `
-- `gog calendar freebusy [calendarIds] [--cal ID_OR_NAME] [--calendars CSV] [--all] --from RFC3339 --to RFC3339`
-- `gog calendar conflicts [--cal ID_OR_NAME] [--calendars CSV] [--all] [--from RFC3339|date|relative] [--to RFC3339|date|relative] [--today|--week|--days N]`
-- `gog calendar respond --status accepted|declined|tentative [--send-updates all|none|externalOnly]`
-
-`calendar unsubscribe` removes only the selected entry from the caller's
-calendar list. `calendar delete-calendar` permanently deletes an owned
-secondary calendar; Google may briefly retain a stale calendar-list row after
-the authoritative calendar resource is gone.
-
-Google Calendar appointment schedules are not exposed by the Calendar API, so
-the CLI cannot list or manage them.
-
-- `gog maps places search [--language LANG] [--region REGION] [--fields FIELD_MASK] [--max N]`
-- `gog maps places details [--language LANG] [--region REGION] [--fields FIELD_MASK]`
-- `gog maps directions --origin ORIGIN --destination DESTINATION [--mode driving|walking|bicycling|transit] [--language LANG] [--region REGION]`
-- `gog maps distance --origins CSV --destinations CSV [--mode driving|walking|bicycling|transit] [--units metric|imperial] [--language LANG] [--region REGION]`
-- `gog maps geocode [--language LANG] [--region REGION]`
-- `gog maps reverse-geocode --lat FLOAT --lng FLOAT [--language LANG] [--region REGION]`
-- `gog photos list [--max N] [--page TOKEN]`
-- `gog photos search [--album ALBUM_ID] [--media-type PHOTO|VIDEO|ALL_MEDIA] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--include-archived] [--max N] [--page TOKEN]`
-- `gog photos get `
-- `gog photos download [--out PATH|-] [--video]`
-- `gog photos picker create [--max-items N] [--open]`
-- `gog photos picker get `
-- `gog photos picker wait [--timeout DURATION]`
-- `gog photos picker list [--max N] [--page TOKEN] [--all]`
-- `gog photos picker download [--out PATH|-] [--overwrite]`
-- `gog photos picker delete `
-- `gog time now [--timezone TZ]`
-- `gog classroom courses [--state ...] [--max N] [--page TOKEN]`
-- `gog classroom courses get `
-- `gog classroom courses create --name NAME [--owner me] [--state ACTIVE|...]`
-- `gog classroom courses update [--name ...] [--state ...]`
-- `gog classroom courses delete `
-- `gog classroom courses archive `
-- `gog classroom courses unarchive `
-- `gog classroom courses join [--role student|teacher] [--user me]`
-- `gog classroom courses leave [--role student|teacher] [--user me]`
-- `gog classroom courses url `
-
-Course state mutations wait for the requested state to become visible through
-the Classroom API before returning success. If Google still serves stale state
-after the bounded retry window, the command exits with retryable code `8`.
-
-- `gog classroom students [--max N] [--page TOKEN]`
-- `gog classroom students get `
-- `gog classroom students add [--enrollment-code CODE]`
-- `gog classroom students remove `
-- `gog classroom teachers [--max N] [--page TOKEN]`
-- `gog classroom teachers get `
-- `gog classroom teachers add `
-- `gog classroom teachers remove `
-- `gog classroom roster [--students] [--teachers]`
-- `gog classroom coursework [--state ...] [--topic TOPIC_ID] [--scan-pages N] [--max N] [--page TOKEN]`
-- `gog classroom coursework get `
-- `gog classroom coursework create --title TITLE [--type ASSIGNMENT|...]`
-- `gog classroom coursework update [--title ...]`
-- `gog classroom coursework delete `
-- `gog classroom coursework assignees [--mode ...] [--add-student ...]`
-- `gog classroom materials [--state ...] [--topic TOPIC_ID] [--scan-pages N] [--max N] [--page TOKEN]`
-- `gog classroom materials get `
-- `gog classroom materials create --title TITLE`
-- `gog classroom materials update [--title ...]`
-- `gog classroom materials delete