Google Power Tools
/install gws-google-workspace
GWS — Google Workspace CLI
Google's official Workspace CLI (@googleworkspace/cli). Covers Gmail, Drive, Calendar, Sheets, Docs, Slides, Forms, Tasks, People, Meet, and Classroom via Google API Discovery Service.
GitHub: https://github.com/googleworkspace/cli
Setup
export GOOGLE_WORKSPACE_PROJECT_ID=\x3Cyour-project-id>
export GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file
gws auth login # first-time auth
gws auth login --full # re-auth with all scopes if 403 error
Full setup guide: references/setup.md
Global Patterns (ALL commands)
tail -n +2 before jq
gws writes Using keyring backend: file to stdout before JSON. Always skip it:
gws gmail users messages list --params '{"userId":"me"}' 2>/dev/null | tail -n +2 | jq '.'
--params vs --json
--params: URL/query parameters (userId,id,q,maxResults)--json: Request body (removeLabelIds,raw, resource JSON)
gws gmail users messages modify \
--json '{"removeLabelIds":["UNREAD"]}' \
--params '{"userId":"me","id":"MSG_ID"}'
Email body extraction
Most emails have only HTML. Try plain text first, then strip HTML tags:
raw=$(gws gmail users messages get --params '{"userId":"me","id":"MSG_ID","format":"full"}' 2>/dev/null | tail -n +2)
body=$(echo "$raw" | jq -r '(.payload.parts[]? | select(.mimeType=="text/plain") .body.data // empty)' | head -1 | base64 -d 2>/dev/null)
if [ -z "$body" ] || [ ${#body} -lt 10 ]; then
body=$(echo "$raw" | jq -r '(.payload.body.data // (.payload.parts[]? | select(.mimeType=="text/html") .body.data // empty))' | head -1 | base64 -d 2>/dev/null | python3 -c "import sys,re; html=sys.stdin.buffer.read().decode('utf-8',errors='ignore'); print(re.sub(r'\x3C[^>]+>',' ',re.sub(r'\x3Cstyle[^>]*>.*?\x3C/style>','',html,flags=re.S)).strip()[:500])" 2>/dev/null)
fi
Per-command latency (~2-3s)
Use batchModify (up to 1000 IDs) or shell & + wait for parallel execution.
Discover any command
gws schema \x3Cservice> # list all methods
gws schema \x3Cservice.method> # parameter details
gws \x3Csvc> \x3Cmethod> --dry-run # preview without executing
Gmail
Quick commands
gws gmail +triage # unread inbox summary
gws gmail +read --params '{"userId":"me","id":"MSG_ID"}' # read message
gws gmail +send --json '{"to":"[email protected]","subject":"Hi","body":"Text"}' # send email
gws gmail +watch # stream new emails
Common operations
# List unread
gws gmail users messages list --params '{"userId":"me","q":"is:unread","maxResults":20}'
# Read headers (match by NAME, never by array index)
gws gmail users messages get --params '{"userId":"me","id":"ID","format":"metadata","metadataHeaders":["From","Subject","Date"]}' 2>/dev/null | tail -n +2 | jq '.payload.headers | map({(.name): .value}) | add'
# Batch mark as read (up to 1000 IDs)
IDS=$(gws gmail users messages list --params '{"userId":"me","q":"is:unread","maxResults":100}' 2>/dev/null | tail -n +2 | jq -c '[.messages[].id]')
gws gmail users messages batchModify --params '{"userId":"me"}' --json "{\"ids\":$IDS,\"removeLabelIds\":[\"UNREAD\"]}"
# Send with attachment: see references/gmail.md
# Labels, threads, attachments: see references/gmail.md
Search syntax: is:unread, from:xxx, subject:keyword, label:xxx, has:attachment, newer_than:1d, category:primary|social|updates
📖 More: labels, attachments, threads, parallel batch read → references/gmail.md
Drive
Common operations
# List files
gws drive files list --params '{"pageSize":10,"orderBy":"modifiedTime desc"}'
gws drive files list --params '{"q":"mimeType=\"application/vnd.google-apps.spreadsheet\""}'
# Upload (two-step: upload → rename, because files create ignores name)
cd ~/Downloads
gws drive files create --params '{}' --upload file.pdf --upload-content-type application/pdf
FILE_ID=$(gws drive files create --params '{}' --upload file.pdf --upload-content-type application/pdf 2>/dev/null | tail -n +2 | jq -r '.id')
gws drive files update --params "{\"fileId\":\"$FILE_ID\"}" --json '{"name":"file.pdf"}'
# Create folder
gws drive files create --params '{}' --json '{"name":"Folder","mimeType":"application/vnd.google-apps.folder"}'
# Download / Export
gws drive files get --params '{"fileId":"ID","alt":"media"}' --output file.txt
gws drive files export --params '{"fileId":"ID","mimeType":"application/pdf"}' --output doc.pdf
⚠️ --upload and --output only accept relative paths. cd to the directory first.
⚠️ files delete returns a saved_file field — ignore it.
📖 More: copy/move, permissions, storage → references/drive.md
Calendar
All time parameters use RFC 3339 format.
# List events
gws calendar events list --params '{"calendarId":"primary","timeMin":"TODAY_START","timeMax":"TODAY_END","maxResults":20}'
# Create event
gws calendar events insert --json '{"summary":"Meeting","start":{"dateTime":"START_TIME","timeZone":"TIMEZONE"},"end":{"dateTime":"END_TIME","timeZone":"TIMEZONE"}}' --params '{"calendarId":"primary"}'
# Free/busy
gws calendar freebusy query --json '{"timeMin":"START","timeMax":"END","items":[{"id":"primary"}]}'
📖 More: update/delete events, ACL, calendar management → references/calendar.md
Sheets
# Read
gws sheets spreadsheets values get --params '{"spreadsheetId":"ID","range":"Sheet1!A1:B10"}'
# Write values (RAW)
gws sheets spreadsheets values update --params '{"spreadsheetId":"ID","range":"Sheet1!A1","valueInputOption":"RAW"}' --json '{"values":[["Value"]]}'
# Write formulas (must use USER_ENTERED)
gws sheets spreadsheets values update --params '{"spreadsheetId":"ID","range":"Sheet1!C1","valueInputOption":"USER_ENTERED"}' --json '{"values":[["=SUM(A1:B10)"]]}'
# Append rows
gws sheets spreadsheets values append --params '{"spreadsheetId":"ID","range":"Sheet1!A1","valueInputOption":"RAW"}' --json '{"values":[["col1","col2"]]}'
📖 More: row/column ops, formatting, conditional formatting → references/sheets.md
Docs
DOC_ID=$(gws docs documents create --json '{"title":"My Doc"}' --params '{}' 2>/dev/null | tail -n +2 | jq -r '.documentId')
gws docs documents get --params '{"documentId":"ID"}' 2>/dev/null | tail -n +2 | jq '[.body.content[]|select(.paragraph)|.paragraph.elements[]?|select(.textRun)|.textRun.content]|join("")'
gws docs documents batchUpdate --json '{"requests":[{"insertText":{"location":{"index":1},"text":"Hello\
"}}]}' --params '{"documentId":"ID"}'
📖 More: bold, headings, images, bullet lists → references/docs-slides-forms.md
Slides
Coordinates use EMU units (1 inch = 914400 EMU).
# Create presentation
SLIDE_ID=$(gws slides presentations create --json '{"title":"My Slides"}' --params '{}' 2>/dev/null | tail -n +2 | jq -r '.presentationId')
# Add text box
gws slides presentations batchUpdate --json '{"requests":[{"createShape":{"objectId":"s1","shapeType":"TEXT_BOX","elementProperties":{"pageObjectId":"p1","size":{"width":{"magnitude":4000000,"unit":"EMU"},"height":{"magnitude":300000,"unit":"EMU"}},"transform":{"scaleX":1,"scaleY":1,"translateX":100000,"translateY":100000,"unit":"EMU"}}}},{"insertText":{"objectId":"s1","text":"Hello!"}}]}' --params '{"presentationId":"ID"}'
# Add new slide
gws slides presentations batchUpdate --json '{"requests":[{"createSlide":{"objectId":"slide2"}}]}' --params '{"presentationId":"ID"}'
# Insert image
gws slides presentations batchUpdate --json '{"requests":[{"createImage":{"url":"https://example.com/img.png","elementProperties":{"pageObjectId":"p1","size":{"width":{"magnitude":3000000,"unit":"EMU"},"height":{"magnitude":2000000,"unit":"EMU"}}}}}]}' --params '{"presentationId":"ID"}'
📖 More → references/docs-slides-forms.md
Forms
# Create form
FORM_ID=$(gws forms forms create --json '{"info":{"title":"Survey"}}' --params '{}' 2>/dev/null | tail -n +2 | jq -r '.formId')
# Add question (text)
gws forms forms batchUpdate --json '{"requests":[{"createItem":{"location":{"index":0},"item":{"title":"Your name?","questionItem":{"question":{"required":true,"textQuestion":{}}}}}}]}' --params '{"formId":"ID"}'
# Add question (radio / checkbox / dropdown / date / scale)
gws forms forms batchUpdate --json '{"requests":[{"createItem":{"location":{"index":1},"item":{"title":"Rate 1-5","questionItem":{"question":{"required":true,"scaleQuestion":{"low":1,"high":5}}}}}}]}' --params '{"formId":"ID"}'
# View responses
gws forms forms responses list --params '{"formId":"ID"}'
📖 More question types → references/docs-slides-forms.md
Tasks
gws tasks tasklists list 2>/dev/null | tail -n +2 | jq '.items[]|{title,id}'
gws tasks tasks list --params '{"tasklist":"@default"}'
gws tasks tasks insert --json '{"title":"Task","notes":"Desc"}' --params '{"tasklist":"@default"}'
gws tasks tasks patch --params '{"tasklist":"@default","task":"TASK_ID"}' --json '{"status":"completed"}'
gws tasks tasks delete --params '{"tasklist":"@default","task":"TASK_ID"}'
📖 More → references/tasks-people-other.md
People / Contacts
searchContacts requires readMask — omitting causes 400 error.
gws people people get --params '{"resourceName":"people/me","personFields":"names,emailAddresses"}'
gws people people searchContacts --params '{"query":"john","pageSize":10,"readMask":"names,emailAddresses,phoneNumbers"}'
gws people connections list --params '{"resourceName":"people/me","personFields":"names,emailAddresses","pageSize":10}'
gws people people createContact --json '{"names":[{"givenName":"First","familyName":"Last"}],"emailAddresses":[{"value":"[email protected]"}]}' --params '{"readMask":"names,emailAddresses"}'
gws people contactGroups list --params '{"pageSize":10}'
📖 More → references/tasks-people-other.md
Meet / Classroom
gws meet conferenceRecords list --params '{"pageSize":10}'
gws classroom courses list --params '{"pageSize":10}'
gws classroom courses students list --params '{"courseId":"COURSE_ID"}'
gws classroom courses courseWork list --params '{"courseId":"COURSE_ID"}'
📖 More → references/tasks-people-other.md
Workflow Helpers
gws workflow +standup-report # today's meetings + tasks
gws workflow +meeting-prep # next meeting prep
gws workflow +weekly-digest # weekly meetings + unread count
gws workflow +email-to-task --message-id "ID" # Gmail → Tasks
Can't find the right command?
- Discover all methods for a service:
gws schema \x3Cservice>(e.g.gws schema drive,gws schema gmail) - Get parameter details:
gws schema \x3Cservice.method>(e.g.gws schema drive.files.copy) - Dry-run to preview:
gws \x3Csvc> \x3Cmethod> --dry-run - Check references/ for detailed examples per service
- Official docs: https://github.com/googleworkspace/cli
Troubleshooting
| Issue | Solution |
|---|---|
| 403 Insufficient scopes | gws auth login --full |
| No OAuth client | Check ~/.config/gws/client_secret.json |
| API not enabled | Enable in GCP Console → APIs & Services |
| jq parse error | Add tail -n +2 to skip keyring prefix |
| Upload shows "Untitled" | Rename with files update after upload |
| File not found on upload | Use relative paths, cd first |
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install gws-google-workspace - After installation, invoke the skill by name or use
/gws-google-workspace - Provide required inputs per the skill's parameter spec and get structured output
What is Google Power Tools?
Google Workspace CLI. Make sure to use this skill whenever the user mentions Gmail, email, inbox, send email, read email, check mail, draft email, send invit... It is an AI Agent Skill for Claude Code / OpenClaw, with 361 downloads so far.
How do I install Google Power Tools?
Run "/install gws-google-workspace" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Google Power Tools free?
Yes, Google Power Tools is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Google Power Tools support?
Google Power Tools is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Google Power Tools?
It is built and maintained by Siyuan Huang (@cyrushuang1995-cmyk); the current version is v2.1.9.