| disable-model-invocation | true |
| name | gws |
| description | Google Workspace CLI for immediate, interactive operations on Gmail, Drive, Sheets, Calendar, Docs, Tasks, Contacts, and 25+ Google APIs. Use for any "do it now" Google task: read/send email, check calendar, upload to Drive, edit Sheets, manage contacts. NOT for scheduled automations (use /clasp). Triggers: "check my email", "calendar today", "upload to Drive", "send email", "agenda", "inbox", "contatti", any Google service interaction.
|
| argument-hint | <what-to-do> (e.g. 'check my calendar for today') |
| allowed-tools | Bash(gws:*), Read, Write, Edit, Glob, Grep |
Google Workspace CLI (gws)
Interact with Google Workspace services directly from the terminal. gws dynamically
discovers all Google Workspace APIs and exposes them as a unified CLI.
Dynamic Context
- gws installed: !
which gws 2>/dev/null && gws --version 2>/dev/null || echo "NOT INSTALLED"
- Auth status: !
gws auth status 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Method: {d.get(\"auth_method\",\"none\")} Account: {d.get(\"account\",\"unknown\")}')" 2>/dev/null || echo "NOT AUTHENTICATED"
- Accounts: !
gws auth list 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); accs=d.get('accounts',[]); print(', '.join(accs) if accs else 'No accounts')" 2>/dev/null || echo "Unknown"
- Default account: !
gws auth list 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('default','none'))" 2>/dev/null || echo "Unknown"
Steps
0. Preflight Checks
-
Check if gws is installed. If not:
npm install -g @googleworkspace/cli
-
Check auth status. If auth_method is none:
- Tell the user to run
! gws auth login --account their@email.com
- If they need a GCP project first:
! gws auth setup
- STOP and wait โ do not proceed until auth is confirmed
-
If multiple accounts, check which is the default and confirm with the user if needed.
1. Understand the Request
Identify:
- Which Google service? Gmail, Drive, Sheets, Calendar, Docs, Tasks, Chat, Forms, etc.
- Read or write? Read-only operations are safe; write operations need user confirmation
- Helper available? Check if a
+helper command exists (faster and simpler)
- API params needed? What IDs, ranges, queries, etc. are required
2. Execute
Use the appropriate gws command. Prefer helper commands when available โ they're simpler and handle encoding/formatting automatically.
Always add --format table when showing results to the user โ it produces readable output instead of raw JSON. Only use --format json when you need to process the data programmatically.
IMPORTANT SAFETY RULES:
- Always use
--dry-run first for write operations (send email, create event, delete file) and show the user what will happen
- Never send emails, delete files, or modify data without explicit user confirmation
- For read-only operations (list, get, search, triage), execute directly
- Never output secrets (API keys, tokens) directly
Complete Service Reference
Gmail
Helpers:
gws gmail +send --to alice@example.com --subject 'Hello' --body 'Hi Alice!'
gws gmail +send --to alice@example.com --subject 'Report' --body 'See attached' -a report.pdf
gws gmail +send --to alice@example.com --subject 'FYI' --body 'Hi' --cc bob@co.com --bcc eve@co.com
gws gmail +send --to alice@example.com --subject 'News' --body '<b>Bold</b> text' --html
gws gmail +read --id MSG_ID
gws gmail +read --id MSG_ID --headers
gws gmail +read --id MSG_ID --html
gws gmail +reply --message-id MSG_ID --body 'Thanks for the update!'
gws gmail +reply --message-id MSG_ID --body 'Looping in Carol' --cc carol@example.com
gws gmail +reply --message-id MSG_ID --body '<b>Bold reply</b>' --html
gws gmail +reply --message-id MSG_ID --body 'Updated version' -a updated.docx
gws gmail +reply-all --message-id MSG_ID --body 'Sounds good to me!'
gws gmail +reply-all --message-id MSG_ID --body 'Updated' --remove bob@example.com
gws gmail +forward --message-id MSG_ID --to colleague@company.com
gws gmail +forward --message-id MSG_ID --to dave@co.com --body 'FYI see below'
gws gmail +forward --message-id MSG_ID --to dave@co.com -a extra-notes.pdf
gws gmail +triage
gws gmail +triage --max 5 --query 'from:boss'
gws gmail +triage --labels
gws gmail +watch --project my-gcp-project
gws gmail +watch --project my-project --once --cleanup
Raw API:
gws gmail users messages list --params '{"userId": "me", "maxResults": 10}'
gws gmail users messages list --params '{"userId": "me", "q": "from:boss@company.com is:unread"}'
gws gmail users messages get --params '{"userId": "me", "id": "MSG_ID"}'
gws gmail users labels list --params '{"userId": "me"}'
gws gmail users getProfile --params '{"userId": "me"}'
Google Drive
Helpers:
gws drive +upload ./report.pdf
gws drive +upload ./report.pdf --parent FOLDER_ID --name 'Q4 Report.pdf'
Raw API:
gws drive files list --params '{"pageSize": 10}'
gws drive files list --params '{"q": "name contains '\''report'\'' and mimeType = '\''application/pdf'\''", "pageSize": 20}'
gws drive files get --params '{"fileId": "FILE_ID"}'
gws drive files get --params '{"fileId": "FILE_ID", "alt": "media"}' --output ./downloaded-file.pdf
gws drive files create --json '{"name": "New Folder", "mimeType": "application/vnd.google-apps.folder"}'
gws drive files update --params '{"fileId": "FILE_ID", "addParents": "NEW_FOLDER_ID", "removeParents": "OLD_FOLDER_ID"}'
gws drive permissions create --params '{"fileId": "FILE_ID"}' --json '{"role": "reader", "type": "user", "emailAddress": "user@example.com"}'
gws drive files list --params '{"q": "'\''FOLDER_ID'\'' in parents", "pageSize": 50}'
gws drive files export --params '{"fileId": "FILE_ID", "mimeType": "application/pdf"}' --output ./doc.pdf
Google Sheets
Helpers:
gws sheets +read --spreadsheet SPREADSHEET_ID --range 'Sheet1!A1:D10'
gws sheets +read --spreadsheet SPREADSHEET_ID --range Sheet1
gws sheets +append --spreadsheet SPREADSHEET_ID --values 'Alice,100,true'
gws sheets +append --spreadsheet SPREADSHEET_ID --json-values '[["Alice",100],["Bob",200]]'
Raw API:
gws sheets spreadsheets get --params '{"spreadsheetId": "ID"}'
gws sheets spreadsheets values update --params '{"spreadsheetId": "ID", "range": "Sheet1!A1", "valueInputOption": "USER_ENTERED"}' --json '{"values": [["Name", "Score"], ["Alice", 95]]}'
gws sheets spreadsheets values batchGet --params '{"spreadsheetId": "ID", "ranges": ["Sheet1!A1:B5", "Sheet2!A1:C3"]}'
gws sheets spreadsheets create --json '{"properties": {"title": "My Sheet"}}'
Google 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 --calendar 'Work'
gws calendar +insert --summary 'Team Standup' \
--start '2026-03-24T09:00:00+01:00' \
--end '2026-03-24T09:30:00+01:00' \
--attendee alice@example.com \
--location 'Room 3' \
--description 'Weekly sync'
Raw API:
gws calendar events list --params '{"calendarId": "primary", "timeMin": "2026-03-23T00: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 freebusy query --json '{"timeMin": "2026-03-24T08:00:00Z", "timeMax": "2026-03-24T18:00:00Z", "items": [{"id": "primary"}]}'
gws calendar calendarList list
gws calendar events quickAdd --params '{"calendarId": "primary", "text": "Lunch with Alice tomorrow at noon"}'
Google Docs
Helpers:
gws docs +write --document DOC_ID --text 'New paragraph here'
Raw API:
gws docs documents get --params '{"documentId": "DOC_ID"}'
gws docs documents batchUpdate --params '{"documentId": "DOC_ID"}' --json '{"requests": [{"insertText": {"location": {"index": 1}, "text": "Hello World\n"}}]}'
Google Tasks
gws tasks tasklists list
gws tasks tasks list --params '{"tasklist": "@default"}'
gws tasks tasks insert --params '{"tasklist": "@default"}' --json '{"title": "Review PR #42", "notes": "Check the auth changes", "due": "2026-03-25T00:00:00Z"}'
gws tasks tasks patch --params '{"tasklist": "@default", "task": "TASK_ID"}' --json '{"status": "completed"}'
Google Chat
Helpers:
gws chat +send --space spaces/SPACE_ID --text 'Hello team!'
Raw API:
gws chat spaces list
gws chat spaces messages list --params '{"parent": "spaces/SPACE_ID"}'
Google Forms
gws forms forms get --params '{"formId": "FORM_ID"}'
gws forms forms responses list --params '{"formId": "FORM_ID"}'
Google Slides
gws slides presentations get --params '{"presentationId": "PRES_ID"}'
gws slides presentations create --json '{"title": "Q4 Review"}'
Contacts (People API)
gws people people connections list --params '{"resourceName": "people/me", "personFields": "names,emailAddresses,phoneNumbers", "pageSize": 50}'
gws people people searchContacts --params '{"query": "Alice", "readMask": "names,emailAddresses"}'
Apps Script
Helpers:
gws script +push --script SCRIPT_ID --dir ./src
Raw API:
gws apps-script projects getContent --params '{"scriptId": "SCRIPT_ID"}'
gws apps-script scripts run --params '{"scriptId": "SCRIPT_ID"}' --json '{"function": "myFunction"}'
Events (Workspace Events API)
Helpers:
gws events +subscribe --target '//chat.googleapis.com/spaces/SPACE' \
--event-types 'google.workspace.chat.message.v1.created' --project my-project
gws events +renew --name subscriptions/SUB_ID
gws events +renew --all --within 2d
Model Armor (Content Safety)
Helpers for screening AI prompts/responses through safety templates:
gws modelarmor +sanitize-prompt --template projects/P/locations/L/templates/T --text 'user input'
gws modelarmor +sanitize-response --template projects/P/locations/L/templates/T --text 'model output'
gws modelarmor +create-template --project P --location us-central1 --template-id my-tmpl --preset jailbreak
Any gws command can also screen its response through Model Armor using the global --sanitize flag:
gws gmail +triage --sanitize projects/P/locations/L/templates/T
Additional Services
These services are available via raw API (gws <service> <resource> <method>). Use gws schema to discover parameters:
| Service | Alias | What it does |
|---|
google-keep | keep | Notes and lists |
meet | โ | Conference records and meeting details |
admin | directory | Users, groups, devices, org units |
admin-reports | reports | Audit logs, usage reports |
vault | โ | eDiscovery matters and holds |
classroom | โ | Courses, invitations, user profiles |
cloudidentity | โ | Identity, devices, groups, SSO profiles |
alertcenter | โ | Security alerts and notifications |
groupssettings | โ | Group access settings |
licensing | โ | License assignments |
reseller | โ | Customer and subscription management |
Cross-Service Workflows
Pre-built multi-service operations:
gws workflow +standup-report
gws workflow +meeting-prep
gws workflow +email-to-task --message-id MSG_ID
gws workflow +weekly-digest
gws workflow +file-announce --file-id FILE_ID --space spaces/SPACE_ID
API Discovery (Schema)
gws can discover any API schema dynamically:
gws schema drive.files.list
gws schema gmail.users.messages.list
gws schema sheets.spreadsheets.values.update
gws schema calendar.events.insert --resolve-refs
Use this when you need to find the exact parameters for an API call. The schema
shows required/optional params, request body structure, and response format.
Output Formats
All commands support multiple output formats:
gws calendar +agenda --format table
gws calendar +agenda --format json
gws calendar +agenda --format yaml
gws calendar +agenda --format csv
Use --format table when showing results to the user.
Use --format json when processing data programmatically.
Pagination
For large result sets:
gws drive files list --params '{"pageSize": 100}' --page-all
gws drive files list --params '{"pageSize": 100}' --page-all --page-limit 5
gws drive files list --params '{"pageSize": 100}' --page-all --page-delay 200
Multi-Account Support
gws auth list
gws auth default --account user@example.com
GOOGLE_WORKSPACE_CLI_ACCOUNT=other@example.com gws gmail +triage
Common Patterns
Check email and summarize
gws gmail +triage --format table
Find a file and share it
gws drive files list --params '{"q": "name contains '\''budget'\''", "pageSize": 5}' --format table
gws drive permissions create --params '{"fileId": "FILE_ID"}' --json '{"role": "reader", "type": "user", "emailAddress": "colleague@company.com"}'
Read sheet data and send summary email
gws sheets +read --spreadsheet ID --range 'Sheet1!A1:D10' --format table
gws gmail +send --to boss@company.com --subject 'Weekly Summary' --body 'Here are the numbers...'
Morning briefing
gws workflow +standup-report --format table
gws gmail +triage --format table
MCP Server Mode
gws can also run as an MCP server, giving Claude direct tool access to Google APIs:
gws mcp -s drive,gmail,calendar,sheets
gws mcp -s drive,gmail,calendar --tool-mode compact
gws mcp -s drive,gmail,calendar -w -e
gws mcp -s all
To configure in Claude Code settings, add to mcpServers:
{
"gws": {
"command": "gws",
"args": ["mcp", "-s", "drive,gmail,calendar,sheets,docs,tasks", "-w", "-e"]
}
}
Note: MCP mode gives Claude direct API access without needing Bash commands.
The skill approach (this file) uses Bash commands instead, which gives more
visibility and control over what's being executed.
Shell Gotchas
Sheets ranges with !: zsh interprets ! as history expansion. Use double quotes for ranges:
gws sheets +read --spreadsheet ID --range "Sheet1!A1:D10"
gws sheets +read --spreadsheet ID --range 'Sheet1!A1:D10'
JSON in flags: Use single quotes around --params and --json to preserve inner double quotes:
gws drive files list --params '{"pageSize": 10}'
Scope Management
Unverified (testing mode) apps are limited to ~25 OAuth scopes. If you hit this limit:
gws auth login --scopes drive,gmail,sheets
gws auth login -s drive,gmail,calendar
Headless / CI Authentication
gws auth export --unmasked > credentials.json
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json
gws gmail +triage
Exit Codes
| Code | Meaning |
|---|
| 0 | Success |
| 1 | API error (Google 4xx/5xx response) |
| 2 | Auth error (credentials missing/expired) |
| 3 | Validation error (bad args, unknown service) |
| 4 | Discovery error (API schema fetch failed) |
| 5 | Internal error (unexpected failure) |
Environment Variables
| Variable | Purpose |
|---|
GOOGLE_WORKSPACE_CLI_TOKEN | Pre-obtained OAuth2 access token (highest priority) |
GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE | Path to OAuth/service account JSON |
GOOGLE_WORKSPACE_CLI_CLIENT_ID | OAuth client ID (alt to client_secret.json) |
GOOGLE_WORKSPACE_CLI_CLIENT_SECRET | OAuth client secret |
GOOGLE_WORKSPACE_CLI_ACCOUNT | Default account email for multi-account |
GOOGLE_WORKSPACE_CLI_CONFIG_DIR | Override config dir (default: ~/.config/gws) |
GOOGLE_WORKSPACE_CLI_LOG | Stderr log level (e.g. gws=debug) |
Variables also load from .env file if present in the working directory.
Official Persona Recipes
gws ships with pre-built persona workflows that combine multiple services. These are
useful patterns to follow when the user's request matches a persona:
| Persona | Services Used | Key Workflow |
|---|
| Exec Assistant | Gmail, Calendar, Drive, Chat | Morning standup -> inbox triage -> meeting prep -> schedule management |
| Project Manager | Sheets, Chat, Calendar, Tasks | Task tracking in Sheets -> status updates in Chat -> calendar blocking |
| IT Admin | Admin, Reports, Licensing | User management -> audit logs -> permission reviews |
| Sales Ops | Gmail, Sheets, Docs, Drive | CRM updates in Sheets -> proposal generation in Docs -> email follow-ups |
| Content Creator | Docs, Drive, Slides, Gmail | Draft in Docs -> organize in Drive -> share via email |
| Team Lead | Calendar, Chat, Tasks, Gmail | Weekly planning -> team updates in Chat -> task assignment |
| Researcher | Drive, Docs, Sheets, Gmail | Collect sources in Drive -> notes in Docs -> data in Sheets |
| HR Coordinator | Admin, Calendar, Gmail, Docs | Onboarding users -> scheduling interviews -> offer letter generation |
| Customer Support | Gmail, Sheets, Chat, Tasks | Email triage -> log in Sheets -> escalate in Chat -> track in Tasks |
| Event Coordinator | Calendar, Gmail, Sheets, Forms | Schedule events -> send invites -> track RSVPs in Sheets |
When a user's request aligns with one of these patterns, follow the workflow sequence.
Troubleshooting
| Problem | Solution |
|---|
| "auth_method: none" | Run gws auth login --account your@email.com |
| "This app isn't verified" | Click Advanced -> Go to [project name] (unsafe) -> Allow |
| Wrong account used | Check gws auth list, set default with gws auth default --account ... |
| API not enabled | gws prints enable URL in stderr โ click link, wait 10s, retry |
| "Access blocked" on login | Add account to OAuth consent screen test users |
redirect_uri_mismatch | OAuth client must be type "Desktop app" |
| "Permission denied" on admin APIs | Need Workspace admin privileges |
| Rate limited | Add --page-delay for pagination, reduce pageSize |
| "Invalid grant" | Token expired โ run gws auth login again |
gcloud not found for setup | Install gcloud, or set up OAuth manually in Cloud Console |
Important Reminders
- Always
--dry-run before write operations โ show the user what will happen
- Never send emails, delete files, or modify calendar without explicit confirmation
- Never output secrets (API keys, tokens) directly โ use
gws auth export instead
- Use
--format table when showing results to the user for readability
- Use helpers (
+send, +read, +reply, +forward, +triage, +append, etc.) when available โ simpler and safer
- Use
gws schema to discover exact API parameters when unsure
- gws is for NOW, clasp is for LATER โ immediate actions vs persistent automations
- Single-quote JSON, double-quote shell strings containing
! (Sheets ranges)
- For bugs or feature requests: https://github.com/googleworkspace/cli/issues