| name | google-workspace |
| description | Interact with Google Workspace (Drive, Gmail, Calendar, Sheets, Docs, Tasks, Chat, Meet, Forms, Keep, Slides, People, Classroom) via the `gws` CLI. Use when the user asks about Google Drive files, sending/reading email, calendar events, spreadsheets, documents, tasks, contacts, chat, or any Google Workspace operation. |
Google Workspace — gws CLI
gws is a unified CLI for all Google Workspace APIs. Prefer +helper commands for common tasks — they handle pagination, formatting, encoding, and threading automatically. Fall back to the raw API (gws <service> <resource> [sub-resource] <method>) for advanced or uncommon operations.
Local Installation Notes
mise which gws
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file gws --version
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file gws auth status
Authentication
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file gws auth status
gws auth login -s drive,gmail,calendar,sheets,tasks
gws auth list
gws auth default work@company.com
gws auth setup
gws auth export
gws --account personal@gmail.com gmail +triage
Current expected auth: encrypted credentials in ~/.config/gws/credentials.enc, token cache present, token_valid: true, and scopes include gmail.modify, calendar, drive, spreadsheets, and tasks.
Scope limit: Unverified OAuth apps are limited to ~25 scopes. Always use -s service1,service2 to select only what you need.
Helpers (Preferred)
Helpers are high-level commands prefixed with +. They batch API calls, handle encoding, and produce clean output. Always use these first.
Gmail Helpers
gws gmail +triage
gws gmail +triage --query 'after:2026/03/18'
gws gmail +triage --query 'from:boss is:unread' --max 5
gws gmail +triage --format table
gws gmail +triage --labels
gws gmail +send --to alice@example.com --subject 'Hello' --body 'Hi Alice!'
gws gmail +send --to alice@example.com --subject 'Report' --body '<b>See attached</b>' --html
gws gmail +send --to a@x.com --cc b@x.com --bcc c@x.com --subject 'FYI' --body 'Note'
gws gmail +send --to alice@example.com --subject 'Hello' --body 'Hi Alice!' --draft
gws gmail +reply --message-id MSG_ID --body 'Thanks, got it!' --draft
gws gmail +reply-all --message-id MSG_ID --body 'Sounds good!' --draft
gws gmail +forward --message-id MSG_ID --to dave@example.com --draft
gws gmail +reply --message-id MSG_ID --body 'Thanks, got it!'
gws gmail +reply --message-id MSG_ID --body 'Looping in Carol' --cc carol@example.com
gws gmail +reply-all --message-id MSG_ID --body 'Sounds good!'
gws gmail +reply-all --message-id MSG_ID --body 'Updated' --remove bob@example.com
gws gmail +forward --message-id MSG_ID --to dave@example.com
gws gmail +forward --message-id MSG_ID --to dave@example.com --body 'FYI see below'
gws gmail +read --id MSG_ID
gws gmail +read --id MSG_ID --headers
gws gmail +read --id MSG_ID --html
gws gmail +read --id MSG_ID --format json | jq '.body'
gws gmail +watch --project my-gcp-project --label-ids INBOX --once
Calendar Helpers
gws calendar +agenda
gws calendar +agenda --today
gws calendar +agenda --tomorrow
gws calendar +agenda --week
gws calendar +agenda --days 3
gws calendar +agenda --today --format table
gws calendar +agenda --calendar 'Work'
gws calendar +agenda --today --timezone America/New_York
gws calendar +insert --summary 'Standup' \
--start '2026-03-20T09:00:00+11:00' \
--end '2026-03-20T09:30:00+11:00'
gws calendar +insert --summary 'Review' \
--start '2026-03-20T14:00:00+11:00' \
--end '2026-03-20T15:00:00+11:00' \
--attendee alice@example.com --meet
Drive Helpers
gws drive +upload ./report.pdf
gws drive +upload ./report.pdf --parent FOLDER_ID
gws drive +upload ./data.csv --name 'Sales Data.csv'
Sheets Helpers
gws sheets +read --spreadsheet SHEET_ID --range 'Sheet1!A1:D10'
gws sheets +read --spreadsheet SHEET_ID --range Sheet1
gws sheets +append --spreadsheet SHEET_ID --values 'Alice,100,true'
gws sheets +append --spreadsheet SHEET_ID --json-values '[["a","b"],["c","d"]]'
Docs Helpers
gws docs +write --document DOC_ID --text 'Hello, world!'
Raw API (Fallback)
Use the raw API when helpers don't cover the operation (e.g., deleting, modifying labels, downloading files, complex queries).
Syntax
gws <service> <resource> [sub-resource] <method> [flags]
Global Flags
| Flag | Description |
|---|
--dry-run | Validate locally without calling the API |
--format <FMT> | Output: json (default), table, yaml, csv |
--page-all | Auto-paginate (NDJSON, one JSON object per line) |
--page-limit <N> | Max pages (default: 10) |
--page-delay <MS> | Delay between pages in ms (default: 100) |
--account <EMAIL> | Override the active account for this command |
--sanitize <TEMPLATE> | Sanitize responses via Model Armor template |
--params '{"k":"v"}' | URL/query parameters |
--json '{"k":"v"}' | Request body (JSON) |
-o, --output <PATH> | Save binary response to file |
--upload <PATH> | Upload file content (multipart) |
Discovering APIs
Always discover before guessing. The CLI is self-documenting:
gws drive --help
gws schema drive.files.list
gws schema gmail.users.messages.get
Gmail (Raw)
gws gmail users messages get --params '{"userId": "me", "id": "MSG_ID", "format": "full"}'
gws gmail users messages list --params '{"userId": "me", "q": "from:boss is:unread", "maxResults": 10}'
gws gmail users labels list --params '{"userId": "me"}'
gws gmail users messages modify --params '{"userId": "me", "id": "MSG_ID"}' \
--json '{"addLabelIds": ["STARRED"], "removeLabelIds": ["UNREAD"]}'
gws gmail users messages trash --params '{"userId": "me", "id": "MSG_ID"}'
gws gmail users messages untrash --params '{"userId": "me", "id": "MSG_ID"}'
Calendar (Raw)
gws calendar events list --params '{"calendarId": "primary", "timeMin": "2026-03-18T00:00:00Z", "maxResults": 10, "singleEvents": true, "orderBy": "startTime"}'
gws calendar events get --params '{"calendarId": "primary", "eventId": "EVENT_ID"}'
gws calendar events delete --params '{"calendarId": "primary", "eventId": "EVENT_ID"}'
gws calendar calendarList list
gws calendar freebusy query --json '{
"timeMin": "2026-03-18T00:00:00Z",
"timeMax": "2026-03-18T23:59:59Z",
"items": [{"id": "primary"}]
}'
Drive (Raw)
gws drive files list --params '{"pageSize": 10}'
gws drive files list --params '{"q": "name contains '\''budget'\''", "pageSize": 10}'
gws drive files get --params '{"fileId": "FILE_ID", "fields": "id,name,mimeType,size,modifiedTime"}'
gws drive files get --params '{"fileId": "FILE_ID", "alt": "media"}' -o ./file.pdf
gws drive files export --params '{"fileId": "FILE_ID", "mimeType": "application/pdf"}' -o ./doc.pdf
gws drive files create --json '{"name": "New Folder", "mimeType": "application/vnd.google-apps.folder"}'
gws drive files list --params '{"pageSize": 100}' --page-all | jq -r '.files[].name'
Sheets (Raw)
Important: Sheets ranges use ! which bash interprets as history expansion. Always use single quotes.
gws sheets spreadsheets values update \
--params '{"spreadsheetId": "SHEET_ID", "range": "Sheet1!A1", "valueInputOption": "USER_ENTERED"}' \
--json '{"values": [["Name", "Score"], ["Alice", 95], ["Bob", 87]]}'
gws sheets spreadsheets create --json '{"properties": {"title": "Q1 Budget"}}'
gws sheets spreadsheets get --params '{"spreadsheetId": "SHEET_ID", "fields": "sheets.properties"}'
Docs (Raw)
gws docs documents get --params '{"documentId": "DOC_ID"}'
gws docs documents create --json '{"title": "Meeting Notes"}'
gws docs documents batchUpdate --params '{"documentId": "DOC_ID"}' \
--json '{"requests": [{"insertText": {"location": {"index": 1}, "text": "Hello World\n"}}]}'
Tasks (Raw)
gws tasks tasklists list
gws tasks tasks list --params '{"tasklist": "TASKLIST_ID"}'
gws tasks tasks insert --params '{"tasklist": "TASKLIST_ID"}' --json '{"title": "Review PR", "due": "2026-03-20T00:00:00Z"}'
gws tasks tasks update --params '{"tasklist": "TASKLIST_ID", "task": "TASK_ID"}' --json '{"status": "completed"}'
Chat (Raw)
gws chat spaces list
gws chat spaces messages create --params '{"parent": "spaces/SPACE_ID"}' --json '{"text": "Deploy complete."}'
People (Raw)
gws people people connections list --params '{"resourceName": "people/me", "personFields": "names,emailAddresses,phoneNumbers"}'
gws people people get --params '{"resourceName": "people/PERSON_ID", "personFields": "names,emailAddresses"}'
gws people people searchContacts --params '{"query": "Alice", "readMask": "names,emailAddresses"}'
Slides (Raw)
gws slides presentations get --params '{"presentationId": "PRES_ID"}'
gws slides presentations create --json '{"title": "Q1 Review"}'
Forms (Raw)
gws forms forms get --params '{"formId": "FORM_ID"}'
gws forms forms responses list --params '{"formId": "FORM_ID"}'
Meet (Raw)
gws meet conferenceRecords list
gws meet spaces create --json '{"config": {"accessType": "OPEN"}}'
Keep (Raw)
gws keep notes list
gws keep notes get --params '{"name": "notes/NOTE_ID"}'
Timezone
The user is in Australia/Sydney (AEDT/AEST). Always:
- Pass
--timezone Australia/Sydney when using gws calendar +agenda.
- Use
Australia/Sydney offsets when creating events with +insert or raw API calls (e.g. +11:00 during AEDT, +10:00 during AEST).
- When using the raw Calendar API, pass
"timeZone": "Australia/Sydney" in params where supported.
- When interpreting API responses, convert any UTC or non-Sydney timestamps to
Australia/Sydney before presenting them to the user.
- All day/time references in responses (e.g. "today", "this weekend", "tomorrow") must be relative to Sydney local time.
Calendar Date Accuracy
When the user asks about events for a specific date range (e.g. "this weekend", "next week", "tomorrow"):
- Compute exact dates first — before interpreting results, explicitly determine the calendar dates for the requested range from the current date. For example, if today is Wednesday March 18, "this weekend" = Saturday March 21 + Sunday March 22.
- Filter by actual date, not position in results — do NOT assume events returned from the API fall within the requested range. Check each event's start date against the computed date range and only include events that genuinely fall within it.
- Use
--days carefully — +agenda --days N returns the next N days starting from today, which may extend beyond the requested range. Always cross-check the actual dates of returned events.
- Verify day-of-week — when presenting events, confirm the day-of-week matches the date (e.g. March 23 2026 = Monday, not Sunday). Never assume consecutive dates in results map to consecutive weekend days.
Rules
- Use the headless backend in Pi — prefix
gws commands with GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file so they do not hang on macOS Keychain prompts.
- Use helpers first —
+triage, +read, +agenda, +send, +reply, +upload, etc. are faster and handle encoding/pagination automatically. Use +read to read email bodies instead of manually fetching and base64-decoding via the raw API. Fall back to raw API only when needed.
- Discover before guessing — run
gws <service> --help and gws schema <method> before constructing raw API calls.
- Confirm before mutating — show the user the command (or use
--dry-run) before any create/update/delete. Read operations don't need confirmation. Use --draft with +send/+reply/+forward to save as draft instead of sending immediately when the user wants to review first.
- Verify after acting — fetch the resource after a write to confirm it took effect.
- Use
--page-all for bulk reads — pipe to jq for filtering large result sets.
- Never output tokens or secrets — don't echo
GOOGLE_WORKSPACE_CLI_TOKEN or credential file contents.
- Single-quote Sheets ranges — the
! in Sheet1!A1 triggers bash history expansion in double quotes.
- Use
--format table for human-readable output when displaying results to the user.
- Respect timezone — see the Timezone section above. All dates and times shown to the user must be in Australia/Sydney.