docs: add feature coverage pages

This commit is contained in:
Peter Steinberger 2026-05-05 07:17:38 +01:00
parent 8b78a94c76
commit e322aad2e9
No known key found for this signature in database
10 changed files with 638 additions and 3 deletions

View File

@ -74,6 +74,7 @@ docs-site: docs-commands
@node scripts/build-docs-site.mjs
docs-check: docs-site
@node scripts/check-docs-coverage.mjs
tools:
@mkdir -p $(TOOLS_DIR)

View File

@ -8,6 +8,8 @@ Keep, and related agent workflows.
- Install and authenticate from the repository
[README](https://github.com/steipete/gogcli#readme).
- Read [Install and Runtime Packages](install.md) when installing from
Homebrew, Docker, GitHub releases, Windows ZIPs, or source.
- Read [Auth Clients](auth-clients.md) when setting up OAuth clients, service
accounts, or Workspace domain-wide delegation.
- Read [Command Guards and Baked Safety Profiles](safety-profiles.md) when
@ -20,6 +22,27 @@ Keep, and related agent workflows.
- Open the [Command Index](commands/README.md) for generated docs for every CLI
command.
## Feature Pages
- [Install and Runtime Packages](install.md)
- [Auth Clients](auth-clients.md)
- [Command Guards and Baked Safety Profiles](safety-profiles.md)
- [Raw API Dumps](raw-api.md)
- [Raw API Sensitive Field Audit](raw-audit.md)
- [Gmail Workflows](gmail-workflows.md)
- [Gmail watch](watch.md)
- [Email Tracking](email-tracking.md)
- [Drive Audits](drive-audits.md)
- [Contacts Dedupe Preview](contacts-dedupe.md)
- [Contacts JSON Update](contacts-json-update.md)
- [Google Docs Editing](docs-editing.md)
- [Sheets Tables](sheets-tables.md)
- [Sheets Formatting](sheets-formatting.md)
- [Slides from Markdown](slides-markdown.md)
- [Slides Template Replacement](slides-template-replacement.md)
- [Backups](backup.md)
- [Date and Time Input Formats](dates.md)
## Common Paths
```bash
@ -40,6 +63,9 @@ commands, flags, aliases, arguments, or help text, run:
make docs-commands
```
`make docs-check` verifies that every schema command has a generated page and
that required feature pages are present and linked from this overview.
Then build the GitHub Pages site locally:
```bash

68
docs/contacts-dedupe.md Normal file
View File

@ -0,0 +1,68 @@
# Contacts Dedupe Preview
read_when:
- Finding duplicate Google Contacts.
- Reviewing or changing `gog contacts dedupe`.
`gog contacts dedupe` finds likely duplicate personal contacts and prints a
merge plan. It is preview-only: it does not merge, update, or delete contacts.
## Command Page
- [`gog contacts dedupe`](commands/gog-contacts-dedupe.md)
## Basic Use
```bash
gog contacts dedupe
gog contacts dedupe --json
gog contacts dedupe --max 500 --json
```
Default matching uses normalized email and phone values:
```bash
gog contacts dedupe --match email,phone
```
Name matching is opt-in because it can produce false positives:
```bash
gog contacts dedupe --match email,phone,name
```
## Output
The command groups contacts that share a matching key. JSON output includes:
- `scanned`: number of contacts examined
- `groups`: likely duplicate groups
- `primary`: the contact gog would keep first in a hypothetical merge plan
- `merged`: merged emails/phones for preview
- `matched_on`: duplicate email/phone/name keys that caused the group
- `members`: all contacts in the group
## Safety
`contacts dedupe` is read-only. There is no apply flag.
Use `--dry-run` in automation anyway when you want a uniform safety habit across
commands:
```bash
gog contacts dedupe --dry-run --json
```
Use `--fail-empty` in scheduled checks when "no duplicates" should be reported
as a distinct exit code:
```bash
gog contacts dedupe --fail-empty
```
## Related Pages
- [Raw API Dumps](raw-api.md)
- [Generated Contacts command pages](commands/gog-contacts.md)
- [`gog contacts export`](commands/gog-contacts-export.md)
- [`gog contacts raw`](commands/gog-contacts-raw.md)

94
docs/docs-editing.md Normal file
View File

@ -0,0 +1,94 @@
# Google Docs Editing
read_when:
- Editing Google Docs content, tabs, formatting, comments, or raw Docs output.
- Reviewing Docs write, format, find-replace, or tab commands.
Docs commands cover document creation, export, content writes, find/replace,
comments, tabs, formatting, and raw API inspection.
## Write Markdown
Append Markdown and convert it to Google Docs formatting:
```bash
gog docs write <docId> --append --markdown --text '## Status'
```
Replace the document body with Markdown from a file:
```bash
gog docs write <docId> --replace --markdown --content-file README.md
```
Command pages:
- [`gog docs write`](commands/gog-docs-write.md)
- [`gog docs export`](commands/gog-docs-export.md)
- [`gog docs cat`](commands/gog-docs-cat.md)
## Format Text
Apply text or paragraph formatting:
```bash
gog docs format <docId> --match Status --bold --font-size 18
gog docs format <docId> --match "Action item" --text-color '#b00020'
gog docs format <docId> --match Heading --alignment center --line-spacing 120
```
Use `--match-all` when every occurrence should be formatted.
Command page:
- [`gog docs format`](commands/gog-docs-format.md)
## Tabs
Manage Google Docs tabs:
```bash
gog docs list-tabs <docId>
gog docs add-tab <docId> --title "Notes"
gog docs rename-tab <docId> <tabId> "Archive"
gog docs delete-tab <docId> <tabId> --force
```
Tab-aware commands accept `--tab` by title or ID:
```bash
gog docs write <docId> --append --tab "Notes" --text "Follow-up"
gog docs find-replace <docId> old new --tab "Notes" --dry-run
```
Command pages:
- [`gog docs list-tabs`](commands/gog-docs-list-tabs.md)
- [`gog docs add-tab`](commands/gog-docs-add-tab.md)
- [`gog docs rename-tab`](commands/gog-docs-rename-tab.md)
- [`gog docs delete-tab`](commands/gog-docs-delete-tab.md)
## Find and Replace
```bash
gog docs find-replace <docId> old new --dry-run
gog docs find-replace <docId> old '' --first
gog docs find-replace <docId> PLACEHOLDER --content-file replacement.md --format markdown
```
`--dry-run` is read-only and reports match counts. Empty replacement strings are
allowed and delete matches.
Command page:
- [`gog docs find-replace`](commands/gog-docs-find-replace.md)
## Raw Docs Output
Use raw output when a script needs the Google Docs API object:
```bash
gog docs raw <docId> --pretty
```
See [Raw API Dumps](raw-api.md) for lossless-output safety notes.

75
docs/drive-audits.md Normal file
View File

@ -0,0 +1,75 @@
# Drive Audits
read_when:
- Auditing Drive folder contents, size, or inventory without changing files.
- Reviewing `drive tree`, `drive du`, or `drive inventory`.
Drive audit commands are read-only reporting helpers. They are meant for cleanup
planning, migration review, and automation that needs stable JSON without
writing back to Drive.
## Commands
- [`gog drive tree`](commands/gog-drive-tree.md)
- [`gog drive du`](commands/gog-drive-du.md)
- [`gog drive inventory`](commands/gog-drive-inventory.md)
- [`gog drive ls`](commands/gog-drive-ls.md)
- [`gog drive get`](commands/gog-drive-get.md)
- [`gog drive raw`](commands/gog-drive-raw.md)
## Folder Tree
Print a readable folder tree:
```bash
gog drive tree --parent <folderId> --depth 2
```
Use JSON when another tool should consume the result:
```bash
gog drive tree --parent <folderId> --depth 3 --json
```
## Size Summary
Summarize folder sizes:
```bash
gog drive du --parent <folderId> --max 20
gog drive du --parent <folderId> --depth 2 --sort size --json
```
`drive du` counts files under folders and sorts by `size`, `path`, or `files`.
## Inventory Export
Export a read-only item inventory:
```bash
gog drive inventory --parent <folderId> --json
gog drive inventory --parent <folderId> --max 0 --depth 0 --json > drive-inventory.json
```
Use inventory output when you need a machine-readable list of Drive objects for
review, diffing, or downstream cleanup scripts.
## Shared Drives
The audit commands include shared drives by default where the underlying Drive
API supports it. Pass `--no-all-drives` to restrict a scan to My Drive:
```bash
gog drive inventory --parent root --no-all-drives --json
```
## Custom Fields
For object-level inspection, use `drive get --fields`:
```bash
gog drive get <fileId> --fields 'id,name,mimeType,size,owners,emailAddress' --json
```
Use [`gog drive raw`](commands/gog-drive-raw.md) when you need the raw Drive API
object, with the sensitive-field behavior described in [Raw API Dumps](raw-api.md).

88
docs/gmail-workflows.md Normal file
View File

@ -0,0 +1,88 @@
# Gmail Workflows
read_when:
- Working with Gmail content, filters, watches, labels, or agent-safe reads.
- Reviewing Gmail commands that cross from read-only into send or modify flows.
Gmail is one of gog's broadest surfaces. Use command-specific pages for exact
flags, and use this page to choose the right workflow shape.
## Search and Read
```bash
gog gmail search 'newer_than:7d' --max 10 --json
gog gmail get <messageId> --json
gog gmail thread get <threadId> --json
```
For agents, logs, or issue reports, prefer sanitized content:
```bash
gog gmail get <messageId> --sanitize-content --json
gog gmail thread get <threadId> --sanitize-content --json
```
`--sanitize-content` strips unsafe/raw payload details while keeping useful
message text for automation.
## Filters
Export filters as Gmail WebUI-compatible XML:
```bash
gog gmail settings filters export --out filters.xml
```
Keep API JSON when a script needs the Gmail API shape:
```bash
gog gmail settings filters export --format json --json
```
Command pages:
- [`gog gmail settings filters export`](commands/gog-gmail-settings-filters-export.md)
- [`gog gmail settings filters list`](commands/gog-gmail-settings-filters-list.md)
- [`gog gmail settings filters create`](commands/gog-gmail-settings-filters-create.md)
- [`gog gmail settings filters delete`](commands/gog-gmail-settings-filters-delete.md)
## Send Guardrails
Block send operations globally for one run:
```bash
gog --gmail-no-send gmail send --to you@example.com --subject test --text body
```
Or use the environment variable in agent shells:
```bash
export GOG_GMAIL_NO_SEND=1
```
For account-specific send blocking, use the no-send config commands:
- [`gog config no-send set`](commands/gog-config-no-send-set.md)
- [`gog config no-send list`](commands/gog-config-no-send-list.md)
- [`gog config no-send remove`](commands/gog-config-no-send-remove.md)
## Watches and Push
Gmail watch/PubSub workflows are documented in [Gmail watch](watch.md).
Key command pages:
- [`gog gmail watch start`](commands/gog-gmail-settings-watch-start.md)
- [`gog gmail watch serve`](commands/gog-gmail-settings-watch-serve.md)
- [`gog gmail watch renew`](commands/gog-gmail-settings-watch-renew.md)
- [`gog gmail history`](commands/gog-gmail-history.md)
## Email Tracking
Open tracking is documented in [Email Tracking](email-tracking.md) and
[Email Tracking Worker](email-tracking-worker.md).
## Raw Gmail
Use [`gog gmail raw`](commands/gog-gmail-raw.md) when you need the underlying
Gmail API `Message` object. See [Raw API Dumps](raw-api.md) for safety notes.

82
docs/install.md Normal file
View File

@ -0,0 +1,82 @@
# Install and Runtime Packages
read_when:
- Updating release packages, Docker images, or install instructions.
- Debugging version mismatches between source, Homebrew, and downloaded assets.
`gog` ships as a single binary. The visible version is injected at build time:
release builds use the tag, while local builds use `git describe`.
## Homebrew
```bash
brew install gogcli
gog --version
```
The Homebrew formula lives in `steipete/homebrew-tap` and installs the `gog`
binary. Release verification should install or upgrade the tap formula and run:
```bash
brew test steipete/tap/gogcli
gog --version
```
## GitHub Releases
Release assets are uploaded by GoReleaser:
- `gogcli_<version>_darwin_amd64.tar.gz`
- `gogcli_<version>_darwin_arm64.tar.gz`
- `gogcli_<version>_linux_amd64.tar.gz`
- `gogcli_<version>_linux_arm64.tar.gz`
- `gogcli_<version>_windows_amd64.zip`
- `gogcli_<version>_windows_arm64.zip`
- `checksums.txt`
Windows users download the matching ZIP, extract `gog.exe`, and add the
directory to `PATH`.
## Docker
Release tags publish a GitHub Container Registry image:
```bash
docker run --rm ghcr.io/steipete/gogcli:latest version
docker run --rm ghcr.io/steipete/gogcli:v0.15.0 version
```
Authenticated container runs should mount a persistent config directory and use
the encrypted file keyring:
```bash
docker volume create gogcli-config
docker run --rm -it \
-e GOG_KEYRING_BACKEND=file \
-e GOG_KEYRING_PASSWORD \
-v gogcli-config:/home/gog/.config/gogcli \
ghcr.io/steipete/gogcli:latest \
auth add you@gmail.com --services gmail,calendar,drive
```
Keep `GOG_KEYRING_PASSWORD` in the shell session or CI secret store. Do not bake
it into images, scripts, or checked-in profiles.
## Source Builds
```bash
git clone https://github.com/steipete/gogcli.git
cd gogcli
make
./bin/gog --version
```
Source builds require the Go version declared in `go.mod`.
## Related Command Pages
- [`gog version`](commands/gog-version.md)
- [`gog auth keyring`](commands/gog-auth-keyring.md)
- [`gog auth credentials`](commands/gog-auth-credentials.md)
- [`gog auth add`](commands/gog-auth-add.md)

64
docs/raw-api.md Normal file
View File

@ -0,0 +1,64 @@
# Raw API Dumps
read_when:
- Using `gog <service> raw` commands for lossless Google API JSON.
- Passing Google API responses into scripts, debuggers, or LLM workflows.
- Reviewing sensitive-field behavior for raw output.
Raw commands return the canonical Google API response shape instead of gog's
normal curated table/JSON output. They are useful when a script needs a field
that gog does not model yet, or when debugging an API object exactly as Google
returns it.
## Commands
- [`gog calendar raw`](commands/gog-calendar-raw.md)
- [`gog contacts raw`](commands/gog-contacts-raw.md)
- [`gog docs raw`](commands/gog-docs-raw.md)
- [`gog drive raw`](commands/gog-drive-raw.md)
- [`gog forms raw`](commands/gog-forms-raw.md)
- [`gog gmail raw`](commands/gog-gmail-raw.md)
- [`gog people raw`](commands/gog-people-raw.md)
- [`gog sheets raw`](commands/gog-sheets-raw.md)
- [`gog slides raw`](commands/gog-slides-raw.md)
- [`gog tasks raw`](commands/gog-tasks-raw.md)
## Examples
```bash
gog drive raw <fileId> --pretty
gog docs raw <docId> --json > doc-api.json
gog gmail raw <messageId> --format metadata --json
gog sheets raw <spreadsheetId> --include-grid-data --json
```
Use service-native field masks when available:
```bash
gog drive raw <fileId> --fields 'id,name,mimeType,owners(emailAddress)' --json
gog contacts raw people/c123 --person-fields names,emailAddresses,phoneNumbers --json
```
## Safety Model
Raw output is intentionally less opinionated than normal gog output. It may
include private document content, contact data, event attendees, Gmail payloads,
or service-specific metadata.
Drive has the highest capability-URL risk. By default, `gog drive raw` redacts
fields such as `thumbnailLink`, `webContentLink`, `exportLinks`, `resourceKey`,
`properties`, `appProperties`, and embedded thumbnail bytes unless the user
explicitly names fields via `--fields`.
Sheets warns when grid data or developer metadata could expose sensitive data,
but keeps output lossless. Docs and Slides may include short-lived image URLs.
For the full sensitive-field review, read [Raw API Sensitive Field Audit](raw-audit.md).
## Automation Tips
- Prefer `--json` for scripts.
- Prefer `--pretty` for humans.
- Use narrow `--fields` or service-specific field masks whenever possible.
- Do not pipe raw output into logs or LLMs unless you are comfortable with the
object's full Google API payload.

View File

@ -8,10 +8,10 @@ const outDir = path.join(root, "dist", "docs-site");
const repoEditBase = "https://github.com/steipete/gogcli/edit/main/docs";
const sections = [
["Start", ["README.md", "auth-clients.md", "spec.md", "dates.md"]],
["Start", ["README.md", "install.md", "auth-clients.md", "spec.md", "dates.md"]],
["Commands", rels("commands")],
["Gmail", ["gmail-autoreply.md", "watch.md", "email-tracking.md", "email-tracking-worker.md"]],
["Workspace", ["backup.md", "sheets-tables.md", "contacts-json-update.md", "slides-markdown.md", "slides-template-replacement.md", "sedmat.md"]],
["Gmail", ["gmail-workflows.md", "gmail-autoreply.md", "watch.md", "email-tracking.md", "email-tracking-worker.md"]],
["Workspace", ["raw-api.md", "raw-audit.md", "drive-audits.md", "contacts-dedupe.md", "contacts-json-update.md", "docs-editing.md", "sheets-tables.md", "sheets-formatting.md", "slides-markdown.md", "slides-template-replacement.md", "backup.md", "sedmat.md"]],
["Safety", ["safety-profiles.md", "RELEASING.md"]],
];

View File

@ -0,0 +1,137 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
const root = process.cwd();
const bin = process.env.GOG_BIN || path.join(root, "bin", "gog");
const docsDir = path.join(root, "docs");
const commandsDir = path.join(docsDir, "commands");
const requiredFeatureDocs = [
"install.md",
"auth-clients.md",
"safety-profiles.md",
"raw-api.md",
"raw-audit.md",
"gmail-workflows.md",
"watch.md",
"email-tracking.md",
"drive-audits.md",
"contacts-dedupe.md",
"contacts-json-update.md",
"docs-editing.md",
"sheets-tables.md",
"sheets-formatting.md",
"slides-markdown.md",
"slides-template-replacement.md",
"backup.md",
"dates.md",
];
const schema = JSON.parse(execFileSync(bin, ["schema", "--json"], { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }));
const commands = Array.from(walk(schema.command || {}));
const seenSlugs = new Set();
const missingCommandPages = [];
for (const command of commands) {
const base = commandSlug(command);
let slug = base;
let suffix = 2;
while (seenSlugs.has(slug)) {
slug = `${base}-${suffix}`;
suffix += 1;
}
seenSlugs.add(slug);
const page = path.join(commandsDir, `${slug}.md`);
if (!fs.existsSync(page)) {
missingCommandPages.push(path.relative(root, page));
}
}
const docsReadme = fs.readFileSync(path.join(docsDir, "README.md"), "utf8");
const missingFeaturePages = [];
const unlinkedFeaturePages = [];
const brokenLinks = checkMarkdownLinks(docsDir);
for (const rel of requiredFeatureDocs) {
const page = path.join(docsDir, rel);
if (!fs.existsSync(page)) {
missingFeaturePages.push(`docs/${rel}`);
continue;
}
if (!docsReadme.includes(`(${rel})`)) {
unlinkedFeaturePages.push(`docs/${rel}`);
}
}
if (missingCommandPages.length || missingFeaturePages.length || unlinkedFeaturePages.length || brokenLinks.length) {
for (const name of missingCommandPages) console.error(`missing command doc: ${name}`);
for (const name of missingFeaturePages) console.error(`missing feature doc: ${name}`);
for (const name of unlinkedFeaturePages) console.error(`feature doc not linked from docs/README.md: ${name}`);
for (const item of brokenLinks) console.error(`broken docs link: ${item}`);
process.exit(1);
}
console.log(`docs coverage ok: ${commands.length} command pages, ${requiredFeatureDocs.length} feature pages`);
function* walk(command) {
yield command;
for (const child of command.subcommands || []) {
yield* walk(child);
}
}
function canonicalTokens(commandPath) {
return (commandPath || "")
.split(/\s+/)
.filter((part) => part && !(part.startsWith("(") && part.endsWith(")")));
}
function canonicalPath(command) {
return canonicalTokens(command.path || command.name || "").join(" ");
}
function commandSlug(command) {
const slug = canonicalPath(command)
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return slug || "gog";
}
function checkMarkdownLinks(dir) {
const broken = [];
for (const file of allMarkdown(dir)) {
const markdown = fs.readFileSync(file, "utf8");
const linkPattern = /!?\[[^\]]*\]\(([^)]+)\)/g;
let match;
while ((match = linkPattern.exec(markdown)) !== null) {
const rawTarget = match[1].trim().replace(/^<|>$/g, "");
if (!rawTarget || rawTarget.startsWith("#")) continue;
if (/^[a-z][a-z0-9+.-]*:/i.test(rawTarget)) continue;
const targetWithoutTitle = rawTarget.split(/\s+["'][^"']*["']\s*$/)[0];
const targetPath = targetWithoutTitle.split("#")[0];
if (!targetPath) continue;
if (/^(url|path|file)$/i.test(targetPath)) continue;
const resolved = path.resolve(path.dirname(file), targetPath);
if (!fs.existsSync(resolved)) {
broken.push(`${path.relative(root, file)} -> ${targetPath}`);
}
}
}
return broken;
}
function allMarkdown(dir) {
return fs
.readdirSync(dir, { withFileTypes: true })
.flatMap((entry) => {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) return allMarkdown(full);
return entry.name.endsWith(".md") ? [full] : [];
});
}