https://github.com/steipete/gogcli
Google Suite CLI: Gmail, GCal, GDrive, GContacts.
https://github.com/steipete/gogcli
gcal gcontacts gdrive gmail google
Last synced: 6 days ago
JSON representation
Google Suite CLI: Gmail, GCal, GDrive, GContacts.
- Host: GitHub
- URL: https://github.com/steipete/gogcli
- Owner: steipete
- License: other
- Created: 2025-12-12T12:36:45.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-01-17T03:50:47.000Z (3 months ago)
- Last Synced: 2026-01-17T04:48:11.358Z (3 months ago)
- Topics: gcal, gcontacts, gdrive, gmail, google
- Language: Go
- Homepage: https://gogcli.sh
- Size: 1.01 MB
- Stars: 468
- Watchers: 3
- Forks: 49
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
- my-awesome-starred - steipete/gogcli - Google Suite CLI: Gmail, GCal, GDrive, GContacts. (Go)
- awesome-ccamel - steipete/gogcli - Google Suite CLI: Gmail, GCal, GDrive, GContacts. (Go)
- awesome - steipete/gogcli - Google Suite CLI: Gmail, GCal, GDrive, GContacts. (Go)
README
# 🧠gogcli — Google in your terminal.

Fast, script-friendly CLI for Gmail, Calendar, Chat, Classroom, Drive, Docs, Slides, Sheets, Forms, Apps Script, Contacts, Tasks, People, Admin, Groups (Workspace), and Keep (Workspace-only). JSON-first output, multiple accounts, and flexible auth built in.
## Features
- **Gmail** - search threads/messages, send mail, view attachments, manage labels/drafts/filters/delegation/vacation settings, auto-reply once to matching mail, modify single messages, export filters, inspect history, and run Pub/Sub watch webhooks
- **Email tracking** - track opens for `gog gmail send --track` with a small Cloudflare Worker backend
- **Calendar** - list/create/update/delete events, manage invitations, aliases, subscriptions, team calendars, free/busy/conflicts, propose new times, focus/OOO/working-location events, recurrence, and reminders
- **Classroom** - manage courses, roster, coursework/materials, submissions, announcements, topics, invitations, guardians, profiles
- **Chat** - list/find/create spaces, list messages/threads, send messages and DMs, and manage emoji reactions (Workspace-only)
- **Drive** - list/search/upload/download files, replace uploads in-place, convert uploads (including Markdown to Google Doc), manage permissions/comments, organize folders, and list shared drives
- **Contacts** - search/create/update contacts, including addresses, relations, org/title metadata, custom fields, Workspace directory, and other contacts
- **Tasks** - manage tasklists and tasks: get/create/add/update/done/undo/delete/clear, plus repeat schedule materialization with RRULE aliases
- **Sheets** - read/write/update spreadsheets, insert rows/cols, manage tabs and named ranges, format/merge/freeze/resize cells, read/write notes, inspect formats, find/replace text, list links, and create/export sheets
- **Forms** - create/update forms, manage questions, inspect responses, and manage watches
- **Apps Script** - create/get/bind projects, inspect content, and run functions
- **Docs/Slides** - create/copy/export docs/slides, edit Docs by tab, import Markdown, do richer find-replace, export Docs as Markdown/HTML, and generate Slides from Markdown or templates
- **People** - profile lookup and directory search helpers
- **Keep (Workspace only)** - list/get/search/create/delete notes and download attachments (service account + domain-wide delegation)
- **Admin (Workspace only)** - Workspace Admin users/groups commands for common directory operations
- **Groups** - list groups you belong to, view group members (Google Workspace)
- **Local time** - quick local/UTC time display for scripts and agents
- **Multiple accounts** - manage multiple Google accounts simultaneously, with account aliases and per-client OAuth buckets
- **Command allowlist** - restrict top-level commands for sandboxed/agent runs
- **Secure credential storage** using OS keyring or encrypted on-disk keyring (configurable)
- **Auto-refreshing tokens** - authenticate once, use indefinitely
- **Flexible auth** - OAuth refresh tokens, ADC, direct access tokens, service accounts, manual/remote flows, `--extra-scopes`, and proxy-safe callbacks
- **Least-privilege auth** - `--readonly`, `--drive-scope`, and `--gmail-scope` to request fewer scopes
- **Workspace service accounts** - domain-wide delegation auth (preferred when configured)
- **Parseable output** - JSON mode for scripting and automation (Calendar adds day-of-week fields)
## Installation
### Homebrew
```bash
brew install gogcli
```
### Arch User Repository
```bash
yay -S gogcli
```
### Build from Source
```bash
git clone https://github.com/steipete/gogcli.git
cd gogcli
make
```
Run:
```bash
./bin/gog --help
```
Help:
- `gog --help` shows top-level command groups.
- Drill down with `gog --help` (and deeper subcommands).
- For the full expanded command list: `GOG_HELP=full gog --help`.
- Make shortcut: `make gog -- --help` (or `make gog -- gmail --help`).
- `make gog-help` shows CLI help (note: `make gog --help` is Make’s own help; use `--`).
- Version: `gog --version` or `gog version`.
## Quick Start
### 1. Get OAuth2 Credentials
Before adding an account, create OAuth2 credentials from Google Cloud Console:
1. Open the Google Cloud Console credentials page: https://console.cloud.google.com/apis/credentials
1. Create a project: https://console.cloud.google.com/projectcreate
2. Enable the APIs you need:
- Admin SDK API: https://console.cloud.google.com/apis/api/admin.googleapis.com
- Apps Script API: https://console.cloud.google.com/apis/api/script.googleapis.com
- Cloud Identity API (Groups): https://console.cloud.google.com/apis/api/cloudidentity.googleapis.com
- Gmail API: https://console.cloud.google.com/apis/api/gmail.googleapis.com
- Google Calendar API: https://console.cloud.google.com/apis/api/calendar-json.googleapis.com
- Google Chat API: https://console.cloud.google.com/apis/api/chat.googleapis.com
- Google Docs API: https://console.cloud.google.com/apis/api/docs.googleapis.com
- Google Drive API: https://console.cloud.google.com/apis/api/drive.googleapis.com
- Google Classroom API: https://console.cloud.google.com/apis/api/classroom.googleapis.com
- Google Keep API: https://console.cloud.google.com/apis/api/keep.googleapis.com
- People API (Contacts): https://console.cloud.google.com/apis/api/people.googleapis.com
- Google Tasks API: https://console.cloud.google.com/apis/api/tasks.googleapis.com
- Google Sheets API: https://console.cloud.google.com/apis/api/sheets.googleapis.com
- Google Forms API: https://console.cloud.google.com/apis/api/forms.googleapis.com
- Google Slides API: https://console.cloud.google.com/apis/api/slides.googleapis.com
3. Configure OAuth consent screen: https://console.cloud.google.com/auth/branding
4. If your app is in "Testing", add test users: https://console.cloud.google.com/auth/audience
5. Create OAuth client:
- Go to https://console.cloud.google.com/auth/clients
- Click "Create Client"
- Application type: "Desktop app"
- Download the JSON file (usually named `client_secret_....apps.googleusercontent.com.json`)
### 2. Store Credentials
```bash
gog auth credentials ~/Downloads/client_secret_....json
```
For multiple OAuth clients/projects:
```bash
gog --client work auth credentials ~/Downloads/work-client.json
gog auth credentials list
```
### 3. Authorize Your Account
```bash
gog auth add you@gmail.com
```
This will open a browser window for OAuth authorization. The refresh token is stored securely in your system keychain.
Headless / remote server flows (no browser on the server):
Manual interactive flow (recommended):
```bash
gog auth add you@gmail.com --services user --manual
```
- The CLI prints an auth URL. Open it in a local browser.
- After approval, copy the full loopback redirect URL from the browser address bar.
- Paste that URL back into the terminal when prompted.
Split remote flow (`--remote`, useful for two-step/scripted handoff):
```bash
# Step 1: print auth URL (open it locally in a browser)
gog auth add you@gmail.com --services user --remote --step 1
# Step 2: paste the full redirect URL from your browser address bar
gog auth add you@gmail.com --services user --remote --step 2 --auth-url 'http://127.0.0.1:/oauth2/callback?code=...&state=...'
```
- The `state` is cached on disk for a short time (about 10 minutes). If it expires, rerun step 1.
- Remote step 2 requires a redirect URL that includes `state` (state check mandatory).
Browser OAuth behind proxies / remote tunnels:
```bash
gog auth add you@gmail.com --listen-addr 0.0.0.0:8080 --redirect-host gog.example.com
gog auth manage --listen-addr 0.0.0.0:8080 --redirect-host gog.example.com
```
- `--listen-addr` changes where the local callback server binds.
- `--redirect-host` builds `https:///oauth2/callback` for the OAuth redirect URI.
- The redirect URI must also be registered in your OAuth client settings.
Direct access token flow (headless/CI, no stored refresh token):
```bash
gog --access-token "$(gcloud auth print-access-token)" gmail labels list
```
- Also available as `GOG_ACCESS_TOKEN`
- Bypasses stored refresh tokens and keyring lookup
- Token expires in about 1 hour; no auto-refresh
### 4. Test Authentication
```bash
export GOG_ACCOUNT=you@gmail.com
gog gmail labels list
```
## Authentication & Secrets
### Accounts and tokens
`gog` stores your OAuth refresh tokens in a “keyring” backend. Default is `auto` (best available backend for your OS/environment).
Before you can run `gog auth add`, you must store OAuth client credentials once via `gog auth credentials ` (download a Desktop app OAuth client JSON from the Cloud Console). For multiple clients, use `gog --client auth credentials ...`; tokens are isolated per client.
List accounts:
```bash
gog auth list
```
Verify tokens are usable (helps spot revoked/expired tokens):
```bash
gog auth list --check
```
Accounts can be authorized either via OAuth refresh tokens or Workspace service accounts (domain-wide delegation). If a service account key is configured for an account, it takes precedence over OAuth refresh tokens (see `gog auth list`).
Show current auth state/services for the active account:
```bash
gog auth status
```
### Multiple OAuth clients
Use `--client` (or `GOG_CLIENT`) to select a named OAuth client:
```bash
gog --client work auth credentials ~/Downloads/work.json
gog --client work auth add you@company.com
```
Optional domain mapping for auto-selection:
```bash
gog --client work auth credentials ~/Downloads/work.json --domain example.com
```
How it works:
- Default client is `default` (stored in `credentials.json`).
- Named clients are stored as `credentials-.json`.
- Tokens are isolated per client (`token::`); defaults are per client too.
Client selection order (when `--client` is not set):
1) `--client` / `GOG_CLIENT`
2) `account_clients` config (email -> client)
3) `client_domains` config (domain -> client)
4) Credentials file named after the email domain (`credentials-example.com.json`)
5) `default`
Config example (JSON5):
```json5
{
account_clients: { "you@company.com": "work" },
client_domains: { "example.com": "work" },
}
```
List stored credentials:
```bash
gog auth credentials list
```
See `docs/auth-clients.md` for the full client selection and mapping rules.
### Keyring backend: Keychain vs encrypted file
Backends:
- `auto` (default): picks the best backend for the platform.
- `keychain`: macOS Keychain (recommended on macOS; avoids password management).
- `file`: encrypted on-disk keyring (requires a password).
Set backend via command (writes `keyring_backend` into `config.json`):
```bash
gog auth keyring file
gog auth keyring keychain
gog auth keyring auto
```
Show current backend + source (env/config/default) and config path:
```bash
gog auth keyring
```
Non-interactive runs (CI/ssh): file backend requires `GOG_KEYRING_PASSWORD`.
```bash
export GOG_KEYRING_PASSWORD='...'
gog --no-input auth status
```
Force backend via env (overrides config):
```bash
export GOG_KEYRING_BACKEND=file
```
Precedence: `GOG_KEYRING_BACKEND` env var overrides `config.json`.
## Configuration
### Account Selection
Specify the account using either a flag or environment variable:
```bash
# Via flag
gog gmail search 'newer_than:7d' --account you@gmail.com
# Via alias
gog auth alias set work work@company.com
gog gmail search 'newer_than:7d' --account work
# Via environment
export GOG_ACCOUNT=you@gmail.com
gog gmail search 'newer_than:7d'
# Auto-select (default account or the single stored token)
gog gmail labels list --account auto
```
List configured accounts:
```bash
gog auth list
```
### Output
- Default: human-friendly tables on stdout.
- `--plain`: stable TSV on stdout (tabs preserved; best for piping to tools that expect `\t`).
- `--json`: JSON on stdout (best for scripting).
- Human-facing hints/progress go to stderr.
- Colors are enabled only in rich TTY output and are disabled automatically for `--json` and `--plain`.
### Service Scopes
By default, `gog auth add` requests access to the **user** services (see `gog auth services` for the current list and scopes).
To request fewer scopes:
```bash
gog auth add you@gmail.com --services drive,calendar
```
To request read-only scopes (write operations will fail with 403 insufficient scopes):
```bash
gog auth add you@gmail.com --services drive,calendar --readonly
```
To control Drive’s scope (default: `full`):
```bash
gog auth add you@gmail.com --services drive --drive-scope full
gog auth add you@gmail.com --services drive --drive-scope readonly
gog auth add you@gmail.com --services drive --drive-scope file
```
To control Gmail’s scope (default: `full`):
```bash
gog auth add you@gmail.com --services gmail --gmail-scope full
gog auth add you@gmail.com --services gmail --gmail-scope readonly
# Example: readonly on both Gmail and Drive
gog auth add you@gmail.com --services gmail,drive --gmail-scope readonly --drive-scope readonly
# Example: append one custom scope beyond the built-in Gmail scope set
gog auth add you@gmail.com --services gmail --extra-scopes https://www.googleapis.com/auth/gmail.labels
```
Notes:
- `--drive-scope readonly` is enough for listing/downloading/exporting via Drive (write operations will 403).
- `--drive-scope file` is write-capable (limited to files created/opened by this app) and can’t be combined with `--readonly`.
- `--gmail-scope readonly` requests `gmail.readonly` (no modify/settings write scopes).
- `--extra-scopes` appends additional OAuth scope URIs after the built-in service scope set; remote step-1 guidance replays it so step 2 requests the same token.
- For `--readonly`, `--drive-scope readonly|file`, or `--gmail-scope readonly`, auth disables Google `include_granted_scopes` to prevent old broader grants from silently accumulating.
If you need to add services later and Google doesn't return a refresh token, re-run with `--force-consent`:
```bash
gog auth add you@gmail.com --services user --force-consent
# Or add just Sheets
gog auth add you@gmail.com --services sheets --force-consent
```
`--services all` is accepted as an alias for `user` for backwards compatibility.
Docs commands are implemented via the Drive API, and `docs` requests both Drive and Docs API scopes.
Service scope matrix (auto-generated; run `go run scripts/gen-auth-services-md.go`):
| Service | User | APIs | Scopes | Notes |
| --- | --- | --- | --- | --- |
| gmail | yes | Gmail API | `https://www.googleapis.com/auth/gmail.modify`
`https://www.googleapis.com/auth/gmail.settings.basic`
`https://www.googleapis.com/auth/gmail.settings.sharing` | |
| calendar | yes | Calendar API | `https://www.googleapis.com/auth/calendar` | |
| chat | yes | Chat API | `https://www.googleapis.com/auth/chat.spaces`
`https://www.googleapis.com/auth/chat.messages`
`https://www.googleapis.com/auth/chat.memberships`
`https://www.googleapis.com/auth/chat.users.readstate.readonly` | |
| classroom | yes | Classroom API | `https://www.googleapis.com/auth/classroom.courses`
`https://www.googleapis.com/auth/classroom.rosters`
`https://www.googleapis.com/auth/classroom.coursework.students`
`https://www.googleapis.com/auth/classroom.coursework.me`
`https://www.googleapis.com/auth/classroom.courseworkmaterials`
`https://www.googleapis.com/auth/classroom.announcements`
`https://www.googleapis.com/auth/classroom.topics`
`https://www.googleapis.com/auth/classroom.guardianlinks.students`
`https://www.googleapis.com/auth/classroom.profile.emails`
`https://www.googleapis.com/auth/classroom.profile.photos` | |
| drive | yes | Drive API | `https://www.googleapis.com/auth/drive` | |
| docs | yes | Docs API, Drive API | `https://www.googleapis.com/auth/drive`
`https://www.googleapis.com/auth/documents` | Export/copy/create via Drive |
| slides | yes | Slides API, Drive API | `https://www.googleapis.com/auth/drive`
`https://www.googleapis.com/auth/presentations` | Create/edit presentations |
| contacts | yes | People API | `https://www.googleapis.com/auth/contacts`
`https://www.googleapis.com/auth/contacts.other.readonly`
`https://www.googleapis.com/auth/directory.readonly` | Contacts + other contacts + directory |
| tasks | yes | Tasks API | `https://www.googleapis.com/auth/tasks` | |
| sheets | yes | Sheets API, Drive API | `https://www.googleapis.com/auth/drive`
`https://www.googleapis.com/auth/spreadsheets` | Export via Drive |
| people | yes | People API | `profile` | OIDC profile scope |
| forms | yes | Forms API | `https://www.googleapis.com/auth/forms.body`
`https://www.googleapis.com/auth/forms.responses.readonly` | |
| 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` | |
| 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 |
| keep | no | Keep API | `https://www.googleapis.com/auth/keep` | Workspace only; service account (domain-wide delegation) |
| admin | no | Admin SDK Directory API | `https://www.googleapis.com/auth/admin.directory.user`
`https://www.googleapis.com/auth/admin.directory.group`
`https://www.googleapis.com/auth/admin.directory.group.member` | Workspace only; service account with domain-wide delegation required |
### Service Accounts (Workspace only)
A service account is a non-human Google identity that belongs to a Google Cloud project. In Google Workspace, a service account can impersonate a user via **domain-wide delegation** (admin-controlled) and access APIs like Gmail/Calendar/Drive as that user.
In `gog`, service accounts are an **optional auth method** that can be configured per account email. If a service account key is configured for an account, it takes precedence over OAuth refresh tokens (see `gog auth list`).
#### 1) Create a Service Account (Google Cloud)
1. Create (or pick) a Google Cloud project.
2. Enable the APIs you’ll use (e.g. Gmail, Calendar, Drive, Sheets, Docs, People, Tasks, Cloud Identity).
3. Go to **IAM & Admin → Service Accounts** and create a service account.
4. In the service account details, enable **Domain-wide delegation**.
5. Create a key (**Keys → Add key → Create new key → JSON**) and download the JSON key file.
#### 2) Allowlist scopes (Google Workspace Admin Console)
Domain-wide delegation is enforced by Workspace admin settings.
1. Open **Admin console → Security → API controls → Domain-wide delegation**.
2. Add a new API client:
- Client ID: use the service account’s “Client ID” from Google Cloud.
- OAuth scopes: comma-separated list of scopes you want to allow (copy from `gog auth services` and/or your `gog auth add --services ...` usage).
If a scope is missing from the allowlist, service-account token minting can fail (or API calls will 403 with insufficient permissions).
#### 3) Configure `gog` to use the service account
Store the key for the user you want to impersonate:
```bash
gog auth service-account set you@yourdomain.com --key ~/Downloads/service-account.json
```
Verify `gog` is preferring the service account for that account:
```bash
gog --account you@yourdomain.com auth status
gog auth list
```
### Google Keep (Workspace only)
Keep requires Workspace + domain-wide delegation. You can configure it via the generic service-account command above (recommended), or the legacy Keep helper:
```bash
gog auth service-account set you@yourdomain.com --key ~/Downloads/service-account.json
gog keep list --account you@yourdomain.com
gog keep get --account you@yourdomain.com
gog keep create --title "Todo" --item "Milk" --item "Eggs" --account you@yourdomain.com
gog keep delete --account you@yourdomain.com --force
```
### Environment Variables
- `GOG_ACCOUNT` - Default account email or alias to use (avoids repeating `--account`; otherwise uses keyring default or a single stored token)
- `GOG_ACCESS_TOKEN` - Use a provided access token directly (headless/CI; no auto-refresh)
- `GOG_CLIENT` - OAuth client name (selects stored credentials + token bucket)
- `GOG_JSON` - Default JSON output
- `GOG_PLAIN` - Default plain output
- `GOG_COLOR` - Color mode: `auto` (default), `always`, or `never`
- `GOG_TIMEZONE` - Default output timezone for Calendar/Gmail (IANA name, `UTC`, or `local`)
- `GOG_ENABLE_COMMANDS` - Comma-separated allowlist of commands; dot paths allowed (e.g., `calendar,tasks,gmail.search`)
- `GOG_DISABLE_COMMANDS` - Comma-separated denylist of commands; dot paths allowed (e.g., `gmail.send,gmail.drafts.send`)
- `GOG_GMAIL_NO_SEND` - Block Gmail send operations
- `GOG_KEYRING_SERVICE_NAME` - Override the keyring namespace/service name (default: `gogcli`)
### Config File (JSON5)
Find the actual config path in `gog --help` or `gog auth keyring`.
Typical paths:
- macOS: `~/Library/Application Support/gogcli/config.json`
- Linux: `~/.config/gogcli/config.json` (or `$XDG_CONFIG_HOME/gogcli/config.json`)
- Windows: `%AppData%\\gogcli\\config.json`
Example (JSON5 supports comments and trailing commas):
```json5
{
// Avoid macOS Keychain prompts
keyring_backend: "file",
// Default output timezone for Calendar/Gmail (IANA, UTC, or local)
default_timezone: "UTC",
// Optional account aliases
account_aliases: {
work: "work@company.com",
personal: "me@gmail.com",
},
// Optional per-account OAuth client selection
account_clients: {
"work@company.com": "work",
},
// Optional domain -> client mapping
client_domains: {
"example.com": "work",
},
// Optional safety guard: block Gmail send operations
gmail_no_send: true,
no_send_accounts: {
"agent@example.com": true,
},
}
```
### Config Commands
```bash
gog config path
gog config list
gog config keys
gog config get timezone
gog config set timezone UTC
gog config unset timezone
```
### Account Aliases
```bash
gog auth alias set work work@company.com
gog auth alias list
gog auth alias unset work
```
Aliases work anywhere you pass `--account` or `GOG_ACCOUNT` (reserved: `auto`, `default`).
### Command Guards (Sandboxing)
```bash
# Only allow calendar + tasks commands for an agent
gog --enable-commands calendar,tasks calendar events --today
# Allow one Gmail read path, but block Gmail writes
gog --enable-commands gmail.search --disable-commands gmail.send gmail search from:me
# Same via env
export GOG_ENABLE_COMMANDS=calendar,tasks
export GOG_DISABLE_COMMANDS=gmail.send,gmail.drafts.send
gog tasks list
# Extra Gmail send guard
gog --gmail-no-send gmail send --to someone@example.com --subject Test --body Test
gog config no-send set agent@example.com
```
## Security
### Credential Storage
OAuth credentials are stored securely in your system's keychain:
- **macOS**: Keychain Access
- **Linux**: Secret Service (GNOME Keyring, KWallet)
- **Windows**: Credential Manager
The CLI uses [github.com/99designs/keyring](https://github.com/99designs/keyring) for secure storage.
If no OS keychain backend is available (e.g., Linux/WSL/container), keyring can fall back to an encrypted on-disk store and may prompt for a password; for non-interactive runs set `GOG_KEYRING_PASSWORD`.
### Keychain Prompts (macOS)
macOS Keychain may prompt more than you’d expect when the “app identity” keeps changing (different binary path, `go run` temp builds, rebuilding to new `./bin/gog`, multiple copies). Keychain treats those as different apps, so it asks again.
Options:
- **Default (recommended):** keep using Keychain (secure) and run a stable `gog` binary path to reduce repeat prompts.
- **Force Keychain:** `GOG_KEYRING_BACKEND=keychain` (disables any file-backend fallback).
- **Avoid Keychain prompts entirely:** `GOG_KEYRING_BACKEND=file` (stores encrypted entries on disk under your config dir).
- To avoid password prompts too (CI/non-interactive): set `GOG_KEYRING_PASSWORD=...` (tradeoff: secret in env).
- **Use a separate keyring namespace:** `GOG_KEYRING_SERVICE_NAME=custom-gog` (default: `gogcli`).
### Best Practices
- **Never commit OAuth client credentials** to version control
- Store client credentials outside your project directory
- Use different OAuth clients for development and production
- Re-authorize with `--force-consent` if you suspect token compromise
- Remove unused accounts with `gog auth remove `
### OAuth Client IDs in Open Source
Some open source Google CLIs ship a pre-configured OAuth client ID/secret copied from other desktop apps to avoid OAuth consent verification, testing-user limits, or quota issues. This makes the consent screen/security emails show the other app’s name and can stop working at any time.
`gogcli` does not do this. Supported auth:
- Your own OAuth Desktop client JSON via `gog auth credentials ...` + `gog auth add ...`
- Google Workspace service accounts with domain-wide delegation (Workspace only)
## Commands
Flag aliases:
- `--out` also accepts `--output`.
- `--out-dir` also accepts `--output-dir` (Gmail thread attachment downloads).
### Authentication
```bash
gog auth credentials # Store OAuth client credentials
gog auth credentials list # List stored OAuth client credentials
gog auth credentials remove work # Remove one OAuth client plus its tokens/domain mappings
gog auth credentials remove all # Remove all stored OAuth clients plus their tokens/domain mappings
gog --client work auth credentials # Store named OAuth client credentials
gog auth add # Authorize and store refresh token
gog auth add --services gmail --gmail-scope readonly # Gmail read-only token
gog auth add --listen-addr 0.0.0.0:8080 --redirect-host gog.example.com
gog auth service-account set --key # Configure service account impersonation (Workspace only)
gog auth service-account status # Show service account status
gog auth service-account unset # Remove service account
gog auth keep --key # Legacy alias (Keep)
gog auth keyring [backend] # Show/set keyring backend (auto|keychain|file)
gog auth status # Show current auth state/services
gog auth services # List available services and OAuth scopes
gog auth list # List stored accounts
gog auth list --check # Validate stored refresh tokens
gog auth remove # Remove a stored refresh token
gog auth manage # Open accounts manager in browser
gog auth manage --listen-addr 0.0.0.0:8080 --redirect-host gog.example.com
gog auth tokens # Manage stored refresh tokens
```
### Keep (Workspace only)
```bash
gog keep list --account you@yourdomain.com
gog keep get --account you@yourdomain.com
gog keep search --account you@yourdomain.com
gog keep create --title "Todo" --item "Milk" --item "Eggs" --account you@yourdomain.com
gog keep create --title "Note" --text "Remember this" --account you@yourdomain.com
gog keep delete --account you@yourdomain.com --force
gog keep attachment --account you@yourdomain.com --out ./attachment.bin
```
### Gmail
```bash
# Search and read
gog gmail search 'newer_than:7d' --max 10
gog gmail thread get
gog gmail thread get --download # Download attachments to current dir
gog gmail thread get --download --out-dir ./attachments
gog gmail get
gog gmail get --format metadata
gog gmail attachment
gog gmail attachment --out ./attachment.bin
gog gmail url # Print Gmail web URL
gog gmail thread modify --add STARRED --remove INBOX
# Send and compose
gog gmail send --to a@b.com --subject "Hi" --body "Plain fallback"
gog gmail send --to a@b.com --subject "Hi" --body-file ./message.txt
gog gmail send --to a@b.com --subject "Hi" --body-file - # Read body from stdin
gog gmail send --to a@b.com --subject "Hi" --body "Plain fallback" --body-html "
Hello
"
gog gmail forward --to a@b.com --note "FYI"
gog gmail forward --to a@b.com --skip-attachments
# Reply + include quoted original message (auto-generates HTML quote unless you pass --body-html)
gog gmail send --reply-to-message-id --quote --to a@b.com --subject "Re: Hi" --body "My reply"
# Draft reply + quote (create requires explicit reply target)
gog gmail drafts create --reply-to-message-id --quote --subject "Re: Hi" --body "My reply"
# Draft reply + quote (update accepts explicit target; else falls back to latest non-draft, non-self message in thread)
gog gmail drafts update --reply-to-message-id --quote --subject "Re: Hi" --body "My reply"
gog gmail drafts update --quote --subject "Re: Hi" --body "My reply"
gog gmail drafts list
gog gmail drafts create --subject "Draft" --body "Body"
gog gmail drafts create --to a@b.com --subject "Draft" --body "Body"
gog gmail drafts update --subject "Draft" --body "Body"
gog gmail drafts update --to a@b.com --subject "Draft" --body "Body"
gog gmail drafts send
gog gmail autoreply 'from:alerts@example.com newer_than:7d' --body-file ./reply.txt --label AutoReplied --dry-run
# Labels
gog gmail labels list
gog gmail labels get INBOX --json # Includes message counts
gog gmail labels create "My Label"
gog gmail labels rename "Old Label" "New Label"
gog gmail labels style "My Label" --text-color "#ffffff" --background-color "#4285f4"
gog gmail labels modify --add STARRED --remove INBOX
gog gmail labels delete # Deletes user label (guards system labels; confirm)
# Batch operations
gog gmail batch delete
gog gmail batch modify --add STARRED --remove INBOX
# Filters
gog gmail filters list
gog gmail filters create --from 'noreply@example.com' --add-label 'Notifications'
gog gmail filters delete
gog gmail filters export --out ./filters.json
# Settings
gog gmail autoforward get
gog gmail autoforward enable --email forward@example.com
gog gmail autoforward disable
gog gmail forwarding list
gog gmail forwarding add --email forward@example.com
gog gmail sendas list
gog gmail sendas create --email alias@example.com
gog gmail vacation get
gog gmail vacation enable --subject "Out of office" --message "..."
gog gmail vacation disable
# Delegation (G Suite/Workspace)
gog gmail delegates list
gog gmail delegates add --email delegate@example.com
gog gmail delegates remove --email delegate@example.com
# Watch (Pub/Sub push)
gog gmail watch start --topic projects/
/topics/ --label INBOX
gog gmail watch serve --bind 127.0.0.1 --token --hook-url http://127.0.0.1:18789/hooks/agent
gog gmail watch serve --bind 0.0.0.0 --verify-oidc --oidc-email --hook-url
gog gmail watch serve --bind 127.0.0.1 --token --fetch-delay 5 --hook-url http://127.0.0.1:18789/hooks/agent
gog gmail watch serve --bind 127.0.0.1 --token --exclude-labels SPAM,TRASH --hook-url http://127.0.0.1:18789/hooks/agent
gog gmail history --since
```
Gmail watch (Pub/Sub push):
- Create Pub/Sub topic + push subscription (OIDC preferred; shared token ok for dev).
- Full flow + payload details: `docs/watch.md`.
- `watch serve --fetch-delay` defaults to `3s` and helps avoid Gmail History indexing races after push delivery.
- `watch serve --exclude-labels` defaults to `SPAM,TRASH`; IDs are case-sensitive.
### Email Tracking
Track when recipients open your emails:
```bash
# Set up local tracking config (per-account; generates keys; follow printed deploy steps)
gog gmail track setup --worker-url https://gog-email-tracker..workers.dev
# Send with tracking
gog gmail send --to recipient@example.com --subject "Hello" --body-html "
Hi!
" --track
# Check opens
gog gmail track opens
gog gmail track opens --to recipient@example.com
# View status
gog gmail track status
```
Docs: `docs/email-tracking.md` (setup/deploy) + `docs/email-tracking-worker.md` (internals).
**Notes:** `--track` requires exactly 1 recipient (no cc/bcc) and an HTML body (`--body-html` or `--quote`). Use `--track-split` to send per-recipient messages with individual tracking ids. The tracking worker stores IP/user-agent + coarse geo by default.
### Calendar
```bash
# Calendars
gog calendar calendars
gog calendar create-calendar "Team Calendar" --timezone Europe/London
gog calendar acl # List access control rules
gog calendar colors # List available event/calendar colors
gog calendar time --timezone America/New_York
gog calendar users # List workspace users (use email as calendar ID)
# Events (with timezone-aware time flags)
gog calendar events --today # Today's events
gog calendar events --tomorrow # Tomorrow's events
gog calendar events --week # This week (Mon-Sun by default; use --week-start)
gog calendar events --days 3 # Next 3 days
gog calendar events --from today --to friday # Relative dates
gog calendar events --from today --to friday --weekday # Include weekday columns
gog calendar events --from 2025-01-01T00:00:00Z --to 2025-01-08T00:00:00Z
gog calendar events --all # Fetch events from all calendars
gog calendar events --calendars 1,3 # Fetch events from calendar indices (see gog calendar calendars)
gog calendar events --cal Work --cal Personal # Fetch events from calendars by name/ID
gog calendar event
gog calendar get # Alias for event
gog calendar search "meeting" --today
gog calendar search "meeting" --tomorrow
gog calendar search "meeting" --days 365
gog calendar search "meeting" --from 2025-01-01T00:00:00Z --to 2025-01-31T00:00:00Z --max 50
# Search defaults to 30 days ago through 90 days ahead unless you set --from/--to/--today/--week/--days.
# Tip: set GOG_CALENDAR_WEEKDAY=1 to default --weekday for calendar events output.
# JSON event output includes timezone and localized times (useful for agents).
gog calendar get --json
# {
# "event": {
# "id": "...",
# "summary": "...",
# "startDayOfWeek": "Friday",
# "endDayOfWeek": "Friday",
# "timezone": "America/Los_Angeles",
# "eventTimezone": "America/New_York",
# "startLocal": "2026-01-23T20:45:00-08:00",
# "endLocal": "2026-01-23T22:45:00-08:00",
# "start": { "dateTime": "2026-01-23T23:45:00-05:00" },
# "end": { "dateTime": "2026-01-24T01:45:00-05:00" }
# }
# }
# Team calendars (requires Cloud Identity API for Google Workspace)
gog calendar team --today # Show team's events for today
gog calendar team --week # Show team's events for the week (use --week-start)
gog calendar team --freebusy # Show only busy/free blocks (faster)
gog calendar team --query "standup" # Filter by event title
# Create and update
gog calendar create \
--summary "Meeting" \
--from 2025-01-15T10:00:00Z \
--to 2025-01-15T11:00:00Z
gog calendar create \
--summary "Team Sync" \
--from 2025-01-15T14:00:00Z \
--to 2025-01-15T15:00:00Z \
--attendees "alice@example.com,bob@example.com" \
--location "Zoom"
gog calendar update \
--summary "Updated Meeting" \
--from 2025-01-15T11:00:00Z \
--to 2025-01-15T12:00:00Z
# Send notifications when creating/updating
gog calendar create \
--summary "Team Sync" \
--from 2025-01-15T14:00:00Z \
--to 2025-01-15T15:00:00Z \
--send-updates all
gog calendar update \
--send-updates externalOnly
# Default: no attendee notifications unless you pass --send-updates.
gog calendar delete \
--send-updates all --force
# Recurrence + reminders
gog calendar create \
--summary "Payment" \
--from 2025-02-11T09:00:00-03:00 \
--to 2025-02-11T09:15:00-03:00 \
--rrule "RRULE:FREQ=MONTHLY;BYMONTHDAY=11" \
--reminder "email:3d" \
--reminder "popup:30m"
# Special event types via --event-type (focus-time/out-of-office/working-location)
gog calendar create primary \
--event-type focus-time \
--from 2025-01-15T13:00:00Z \
--to 2025-01-15T14:00:00Z
gog calendar create primary \
--event-type out-of-office \
--from 2025-01-20 \
--to 2025-01-21 \
--all-day
gog calendar create primary \
--event-type working-location \
--working-location-type office \
--working-office-label "HQ" \
--from 2025-01-22 \
--to 2025-01-23
# Dedicated shortcuts (same event types, more opinionated defaults)
gog calendar focus-time --from 2025-01-15T13:00:00Z --to 2025-01-15T14:00:00Z
gog calendar out-of-office --from 2025-01-20 --to 2025-01-21 --all-day
gog calendar working-location --type office --office-label "HQ" --from 2025-01-22 --to 2025-01-23
# Add attendees without replacing existing attendees/RSVP state
gog calendar update \
--add-attendee "alice@example.com,bob@example.com"
gog calendar delete
# Invitations
gog calendar respond --status accepted
gog calendar respond --status declined
gog calendar respond --status tentative
gog calendar respond --status declined --send-updates externalOnly
# Propose a new time (browser-only flow; API limitation)
gog calendar propose-time
gog calendar propose-time --open
gog calendar propose-time --decline --comment "Can we do 5pm?"
# Availability
gog calendar freebusy --calendars "primary,work@example.com" \
--from 2025-01-15T00:00:00Z \
--to 2025-01-16T00:00:00Z
gog calendar freebusy --cal Work --from 2025-01-15T00:00:00Z --to 2025-01-16T00:00:00Z
gog calendar conflicts --calendars "primary,work@example.com" \
--today # Today's conflicts
gog calendar conflicts --all --today # Check conflicts across all calendars
```
### Time
```bash
gog time now
gog time now --timezone UTC
```
### Drive
When you turn a Markdown file into a Google Doc, use **`--convert`** (extension-based) or **`--convert-to doc`**. Leading YAML frontmatter between **`---`** lines is **removed before upload** unless you pass **`--keep-frontmatter`**. That step only looks for opening and closing delimiter lines—it is **not** a full YAML parse, so odd edge cases may need **`--keep-frontmatter`** or editing the file first.
```bash
# List and search
gog drive ls --max 20
gog drive ls --parent --max 20
gog drive ls --all --max 20 # List across all accessible files (cannot combine with --parent)
gog drive ls --no-all-drives # Only list from "My Drive"
gog drive search "invoice" --max 20
gog drive search "invoice" --no-all-drives
gog drive search "mimeType = 'application/pdf'" --raw-query
gog drive get # Get file metadata
gog drive url # Print Drive web URL
gog drive copy "Copy Name"
# Upload and download
gog drive upload ./path/to/file --parent
gog drive upload ./path/to/file --replace # Replace file content in-place (preserves shared link)
gog drive upload ./report.docx --convert
gog drive upload ./chart.png --convert-to sheet
gog drive upload ./report.docx --convert --name report.docx
gog drive upload ./notes.md --convert # Markdown → Google Doc (or use --convert-to doc)
gog drive download --out ./downloaded.bin
gog drive download --format pdf --out ./exported.pdf # Google Workspace files only
gog drive download --format docx --out ./doc.docx
gog drive download --format md --out ./note.md # Google Doc → Markdown
gog drive download --format pptx --out ./slides.pptx
# Organize
gog drive mkdir "New Folder"
gog drive mkdir "New Folder" --parent
gog drive rename "New Name"
gog drive move --parent
gog drive delete # Move to trash
gog drive delete --permanent # Permanently delete
# Permissions
gog drive permissions
gog drive share --to user --email user@example.com --role reader
gog drive share --to user --email user@example.com --role writer
gog drive share --to user --email reviewer@example.com --role commenter
gog drive share --to domain --domain example.com --role reader
gog drive unshare --permission-id
# Shared drives (Team Drives)
gog drive drives --max 100
```
### Docs / Slides / Sheets
```bash
# Docs
gog docs info
gog docs cat --max-bytes 10000
gog docs create "My Doc"
gog docs create "My Doc" --file ./doc.md # Import markdown
gog docs create "My Doc" --pageless
gog docs copy "My Doc Copy"
gog docs export --format pdf --out ./doc.pdf
gog docs list-tabs
gog docs cat --tab "Notes"
gog docs cat --all-tabs
gog docs update --text "Append this later"
gog docs update --text "Only in this tab" --tab-id t.notes
gog docs update --file ./insert.txt --index 25 --pageless
gog docs write --text "Fresh content"
gog docs write --text "Rewrite one tab" --tab-id t.notes
gog docs write --file ./body.txt --append --pageless
gog docs write --file ./body.md --replace --markdown
gog docs find-replace "old" "new"
gog docs find-replace "old" "new" --tab-id t.notes
# Slides
gog slides info
gog slides create "My Deck"
gog slides create-from-markdown "My Deck" --content-file ./slides.md
gog slides create-from-template "My Deck" --replace "name=John" --replace "date=2026-02-15"
gog slides copy "My Deck Copy"
gog slides export --format pdf --out ./deck.pdf
gog slides list-slides
gog slides add-slide ./slide.png --notes "Speaker notes"
gog slides update-notes --notes "Updated notes"
gog slides replace-slide ./new-slide.png --notes "New notes"
# Sheets
gog sheets copy "My Sheet Copy"
gog sheets export --format pdf --out ./sheet.pdf
gog sheets format 'Sheet1!A1:B2' --format-json '{"textFormat":{"bold":true}}' --format-fields 'userEnteredFormat.textFormat.bold'
gog sheets format 'Sheet1!A1:B2' --format-json '{"borders":{"top":{"style":"SOLID"}}}' --format-fields 'userEnteredFormat.borders.top.style'
gog sheets merge 'Sheet1!A1:B2'
gog sheets number-format 'Sheet1!C:C' --type CURRENCY --pattern '$#,##0.00'
gog sheets freeze --rows 1 --cols 1
gog sheets resize-columns 'Sheet1!A:C' --auto
gog sheets read-format 'Sheet1!A1:B2'
gog sheets insert "Sheet1" rows 2 --count 3
gog sheets notes 'Sheet1!A1:B10'
gog sheets find-replace "old" "new"
gog sheets find-replace "old" "new" --sheet Sheet1 --match-entire
gog sheets links 'Sheet1!A1:B10'
gog sheets add-tab --index 0
gog sheets rename-tab
gog sheets delete-tab --force
```
### Contacts
```bash
# Personal contacts
gog contacts list --max 50
gog contacts search "Ada" --max 50
gog contacts get people/
gog contacts get user@example.com # Get by email
# Other contacts (people you've interacted with)
gog contacts other list --max 50
gog contacts other search "John" --max 50
# Create and update
gog contacts create \
--given "John" \
--family "Doe" \
--email "john@example.com" \
--phone "+1234567890" \
--address "12 St James's Square, London" \
--gender "male" \
--relation "spouse=Jane Doe"
gog contacts update people/ \
--given "Jane" \
--email "jane@example.com" \
--address "1 Infinite Loop, Cupertino" \
--birthday "1990-05-12" \
--gender "female" \
--notes "Met at WWDC" \
--relation "friend=Bob"
# Update via JSON (see docs/contacts-json-update.md)
gog contacts get people/ --json | \
jq '(.contact.urls //= []) | (.contact.urls += [{"value":"obsidian://open?vault=notes&file=People/John%20Doe","type":"profile"}])' | \
gog contacts update people/ --from-file -
gog contacts delete people/
# Workspace directory (requires Google Workspace)
gog contacts directory list --max 50
gog contacts directory search "Jane" --max 50
```
### Tasks
```bash
# Task lists
gog tasks lists --max 50
gog tasks lists create
# Tasks in a list
gog tasks list --max 50
gog tasks get
gog tasks add --title "Task title"
gog tasks add --title "Weekly sync" --due 2025-02-01 --repeat weekly --repeat-count 4
gog tasks add --title "Daily standup" --due 2025-02-01 --repeat daily --repeat-until 2025-02-05
gog tasks add --title "Bi-weekly review" --due 2025-02-01 --recur-rrule "FREQ=WEEKLY;INTERVAL=2" --repeat-count 3
gog tasks update --title "New title"
gog tasks done
gog tasks undo
gog tasks delete
gog tasks clear
# Note: Google Tasks treats due dates as date-only; time components may be ignored.
# Note: Public Google Tasks API does not expose true recurring-task metadata; `--repeat*`/`--recur*` materialize concrete tasks.
# See docs/dates.md for all supported date/time input formats across commands.
```
### Sheets
```bash
# Read
gog sheets metadata
gog sheets get 'Sheet1!A1:B10'
gog sheets get MyNamedRange
# Export (via Drive)
gog sheets export --format pdf --out ./sheet.pdf
gog sheets export --format xlsx --out ./sheet.xlsx
# Write
gog sheets update 'A1' 'val1|val2,val3|val4'
gog sheets update 'A1' --values-json '[["a","b"],["c","d"]]'
gog sheets update 'Sheet1!A1:C1' 'new|row|data' --copy-validation-from 'Sheet1!A2:C2'
gog sheets update MyNamedRange 'new|row|data'
gog sheets update 'Sheet1!A1:C1' 'new|row|data' --copy-validation-from MyValidationNamedRange
gog sheets append 'Sheet1!A:C' 'new|row|data'
gog sheets append 'Sheet1!A:C' 'new|row|data' --copy-validation-from 'Sheet1!A2:C2'
gog sheets find-replace "old" "new"
gog sheets find-replace "old" "new" --sheet Sheet1 --regex
gog sheets update-note 'Sheet1!A1' --note ''
gog sheets append MyNamedRange 'new|row|data'
gog sheets clear 'Sheet1!A1:B10'
gog sheets clear MyNamedRange
# Format
gog sheets format 'Sheet1!A1:B2' --format-json '{"textFormat":{"bold":true}}' --format-fields 'userEnteredFormat.textFormat.bold'
gog sheets format MyNamedRange --format-json '{"textFormat":{"bold":true}}' --format-fields 'userEnteredFormat.textFormat.bold'
gog sheets format 'Sheet1!A1:B2' --format-json '{"borders":{"top":{"style":"SOLID"}}}' --format-fields 'userEnteredFormat.borders.top.style'
gog sheets merge 'Sheet1!A1:B2'
gog sheets unmerge 'Sheet1!A1:B2'
gog sheets number-format 'Sheet1!C:C' --type CURRENCY --pattern '$#,##0.00'
gog sheets freeze --rows 1 --cols 1
gog sheets resize-columns 'Sheet1!A:C' --auto
gog sheets resize-rows 'Sheet1!1:10' --height 36
gog sheets read-format 'Sheet1!A1:B2'
gog sheets read-format 'Sheet1!A1:B2' --effective
# Named ranges
gog sheets named-ranges
gog sheets named-ranges get MyNamedRange
gog sheets named-ranges add MyNamedRange 'Sheet1!A1:B2'
gog sheets named-ranges add MyCols 'Sheet1!A:C'
gog sheets named-ranges update MyNamedRange --name MyNamedRange2
gog sheets named-ranges delete MyNamedRange2
# Charts
gog sheets chart list
gog sheets chart get --json > chart.json
gog sheets chart create --spec-json @chart.json
gog sheets chart create --spec-json '{"title":"Revenue","basicChart":{"chartType":"COLUMN"}}' --sheet Sheet1 --anchor E10
gog sheets chart update --spec-json '{"title":"New Title","basicChart":{"chartType":"PIE"}}'
gog sheets chart delete
# Insert rows/cols
gog sheets insert "Sheet1" rows 2 --count 3
gog sheets insert "Sheet1" cols 3 --after
# Notes
gog sheets notes 'Sheet1!A1:B10'
gog sheets links 'Sheet1!A1:B10' # Includes rich-text links
# Create
gog sheets create "My New Spreadsheet" --sheets "Sheet1,Sheet2"
# Tab management
gog sheets add-tab --index 0
gog sheets rename-tab