google-workspace
Google Workspace Skill
Unified Google Workspace management for AI agents. One skill, one auth, eight services.
Replaces:
gmail,google-calendar,google-contacts,google-drive,google-photosRunscripts/upgrade.shto migrate from the old per-service skills.
Services
| Service | Script | Capabilities |
|---|---|---|
| Gmail | scripts/gmail.py |
Search, read, thread, draft, send, reply, reply-all, forward, trash, filters, empty-trash/spam |
| Calendar | scripts/google_calendar.py |
List, get, search, create (RRULE), update, delete, quick-add, secondary calendars, Drive attachments |
| Contacts | scripts/contacts.py |
Search, list, get, create, update, delete contacts |
| Drive | scripts/google_drive.py |
List, search, upload, download, export, mkdir, move, copy, rename, trash, delete, empty-trash, share, revisions, comments |
| Docs | scripts/google_docs.py |
Read text, create, append, replace, insert-heading, format-text, insert-image, comments |
| Sheets | scripts/google_sheets.py |
Read, create, append, update, clear, tabs, format-range, freeze-panes, auto-resize |
| Photos | scripts/google_photos.py |
List, search, download, upload media, manage albums |
| NotebookLM | scripts/google_notebooklm.py |
Create/manage notebooks, add sources (URL/file/text/Drive), chat, generate audio/reports/quizzes/flashcards |
Prerequisites
- Google Cloud Project with these APIs enabled:
- Gmail API
- Google Calendar API
- Google People API
- Google Drive API
- Google Docs API
- Google Sheets API
- Photos Library API
- OAuth 2.0 Credentials β Desktop app client (
credentials.json).
Setup
One-Time Setup (all services at once)
uv run scripts/setup_workspace.py
This authenticates once with all scopes and stores a unified token at ~/.google_workspace/.
Manual Setup (gcloud ADC)
gcloud auth application-default login \
--scopes https://mail.google.com/,\
https://www.googleapis.com/auth/calendar,\
https://www.googleapis.com/auth/contacts,\
https://www.googleapis.com/auth/drive,\
https://www.googleapis.com/auth/documents,\
https://www.googleapis.com/auth/spreadsheets,\
https://www.googleapis.com/auth/photoslibrary,\
https://www.googleapis.com/auth/photoslibrary.sharing,\
https://www.googleapis.com/auth/cloud-platform
Install Token Maintenance (recommended)
Prevents hourly token expiry by running a systemd timer that refreshes the access token every 45 minutes:
bash scripts/install_services.sh
systemctl --user enable --now workspace-token-maintainer.timer
Verify
Run the preflight check first. It validates credentials, token, scopes, systemd timer, and API reachability in one command:
# Full check (credentials + token + API pings for all services)
uv run scripts/preflight.py
# Quick check (credentials + token only, no API pings)
uv run scripts/preflight.py --quick
The output is structured JSON with ok, error, and fix fields for each check. If all checks pass, exit code is 0.
You can also verify individual services:
uv run scripts/gmail.py verify
uv run scripts/google_calendar.py verify
uv run scripts/contacts.py verify
uv run scripts/google_drive.py verify
uv run scripts/google_docs.py verify
uv run scripts/google_sheets.py verify
uv run scripts/google_photos.py verify
Credential Lookup Order
Every script checks credentials in this order:
- Service-specific env var (e.g.,
GMAIL_CREDENTIALS_DIR) β for overrides. - Unified directory
~/.google_workspace/β primary location. - Legacy per-service directory (e.g.,
~/.gmail_credentials/) β backward compat.
No migration required. If you already have legacy credentials, they still work.
π Notice: No More SQLite Cache
The local SQLite caching system (scripts/cache.py) has been removed in favor of live API queries.
Do not attempt to use cache.py, sync, or search against a local database. Always use the live API commands provided by the individual service scripts (e.g., gmail.py search --query "...").
Gmail
Full CRUD Gmail management. Search, read, compose, reply, forward, and manage messages.
Search for Emails
# Unread emails from a sender
uv run scripts/gmail.py search --query "from:obiwan@jedi.org is:unread"
# Emails with attachments
uv run scripts/gmail.py search --query "has:attachment subject:plans" --limit 5
Read a Message / Thread
uv run scripts/gmail.py read --id "18e..."
uv run scripts/gmail.py thread --id "18e..."
# Prefer HTML body
uv run scripts/gmail.py read --id "18e..." --html
Create a Draft
Safest option β creates a draft for the user to review before sending.
uv run scripts/gmail.py draft \
--to "yoda@dagobah.net" \
--subject "Training Schedule" \
--body "Master, when shall we begin the next session?" \
--cc "mace@jedi.org"
# Create HTML draft
uv run scripts/gmail.py draft --to "yoda@dagobah.net" --subject "HTML Test" --body "<b>Bold</b>" --html
Send an Email
uv run scripts/gmail.py send \
--to "yoda@dagobah.net" \
--subject "Urgent: Sith Sighting" \
--body "Master, I sense a disturbance in the Force."
# Send HTML
uv run scripts/gmail.py send --to "yoda@dagobah.net" --subject "HTML" --body "<h1>Alert</h1>" --html
Reply / Reply All / Forward
# Reply (draft by default, --send for immediate)
uv run scripts/gmail.py reply --id "18e..." --body "Acknowledged." --send
# Reply with HTML
uv run scripts/gmail.py reply --id "18e..." --body "<b>Agreed.</b>" --send --html
# Reply all
uv run scripts/gmail.py reply-all --id "18e..." --body "Council noted." --send
# Forward
uv run scripts/gmail.py forward --id "18e..." --to "luke@tatooine.net" --body "FYI"
uv run scripts/gmail.py forward --id "18e..." --to "luke@tatooine.net" --send
Trash / Labels / Attachments / Filters
uv run scripts/gmail.py trash --id "18e..."
uv run scripts/gmail.py untrash --id "18e..."
# Empty Trash or Spam (permanent β no recovery)
uv run scripts/gmail.py empty-trash
uv run scripts/gmail.py empty-spam
uv run scripts/gmail.py labels
uv run scripts/gmail.py modify-labels --id "18e..." --add STARRED --remove UNREAD
uv run scripts/gmail.py attachments --id "18e..." --output-dir ./downloads
# Filters β auto-route or label incoming mail
uv run scripts/gmail.py list-filters
uv run scripts/gmail.py create-filter \
--from "noreply@galactic-senate.gov" --archive --mark-read
uv run scripts/gmail.py create-filter \
--subject "URGENT" --add-label "IMPORTANT"
uv run scripts/gmail.py delete-filter --filter-id "ANe1BmhV..."
Safety Guidelines
- Prefer
draftoversendfor new compositions β let the user review first. - Reply/Forward defaults to draft β use
--sendonly when explicitly requested. - Trash is reversible β
empty-trashis permanent, so confirm intent.
Token Maintenance & Persistence
7-Day Expiry Fix (Important)
If you are using a personal Gmail account with a "Testing" GCP project, your refresh token will expire every 7 days.
To fix this, follow the guide at references/GCP_SETUP.md to set up a proper app or use an Internal app (Workspace users).
Background Refresh
To keep your 1-hour access token fresh and avoid CLI latency:
# Install the maintenance service
cp scripts/workspace-token-maintainer.service ~/.config/systemd/user/
cp scripts/workspace-token-maintainer.timer ~/.config/systemd/user/
systemctl --user enable --now workspace-token-maintainer.timer
This runs scripts/maintain_token.py every 45 minutes to refresh the token on disk.
Calendar
Full CRUD Calendar management. List, create, update, delete, and search events.
List / Search Events
# Next 5 events
uv run scripts/google_calendar.py list --limit 5
# Events in a date range
uv run scripts/google_calendar.py list \
--after "2026-02-16T00:00:00Z" --before "2026-02-22T23:59:59Z"
# Search by text
uv run scripts/google_calendar.py search --query "Training" --limit 5
Create / Update / Delete Events
# Timed event
uv run scripts/google_calendar.py create \
--summary "Jedi Council Meeting" \
--start "2026-05-04T10:00:00" --end "2026-05-04T11:00:00" \
--location "Council Chamber, Coruscant" \
--attendees yoda@dagobah.net mace@jedi.org
# All-day event
uv run scripts/google_calendar.py create \
--summary "May the 4th" --start "2026-05-04" --end "2026-05-05" --all-day
# Recurring event (RRULE β RFC 5545)
uv run scripts/google_calendar.py create \
--summary "Weekly Stand-up" \
--start "2026-03-03T09:00:00" --end "2026-03-03T09:30:00" \
--rrule "RRULE:FREQ=WEEKLY;BYDAY=MO"
uv run scripts/google_calendar.py create \
--summary "Monthly Report" \
--start "2026-03-01T14:00:00" --end "2026-03-01T15:00:00" \
--rrule "RRULE:FREQ=MONTHLY;BYMONTHDAY=1;COUNT=12"
# Event with Drive file attachment
uv run scripts/google_calendar.py create \
--summary "Design Review" \
--start "2026-03-10T14:00:00" --end "2026-03-10T15:00:00" \
--drive-files "FILE_ID_1" "FILE_ID_2"
# Update (patch semantics β only provided fields change)
uv run scripts/google_calendar.py update --id "abc123" --summary "Updated Meeting"
# Delete
uv run scripts/google_calendar.py delete --id "abc123"
Quick Add / Calendar Management
uv run scripts/google_calendar.py quick --text "Lunch with Padme tomorrow at noon"
# List all calendars
uv run scripts/google_calendar.py calendars
# Subscribe to a secondary calendar (e.g. public holiday calendar)
uv run scripts/google_calendar.py add-calendar \
--calendar-id "en.usa#holiday@group.v.calendar.google.com"
# Unsubscribe
uv run scripts/google_calendar.py remove-calendar \
--calendar-id "en.usa#holiday@group.v.calendar.google.com"
Contacts
Full CRUD Contacts management via the People API.
Search / List / Get
uv run scripts/contacts.py search --query "Han Solo"
uv run scripts/contacts.py list --limit 50
uv run scripts/contacts.py get --id "people/c12345"
Create / Update / Delete
uv run scripts/contacts.py create \
--first "Lando" --last "Calrissian" \
--email "lando@cloudcity.com" --phone "555-0123" \
--org "Cloud City Administration" --title "Baron Administrator"
# Update (patch semantics, etag-based conflict detection)
uv run scripts/contacts.py update --id "people/c12345" --phone "555-9999" --title "General"
uv run scripts/contacts.py delete --id "people/c12345"
Drive
Full CRUD Drive management. List, search, upload, download, export, organize, and share files.
List / Search / Get
uv run scripts/google_drive.py list
uv run scripts/google_drive.py list --folder "FOLDER_ID" --limit 20
uv run scripts/google_drive.py search --query "Death Star plans"
uv run scripts/google_drive.py search --query "budget" --mime-type "application/vnd.google-apps.spreadsheet"
uv run scripts/google_drive.py get --id "FILE_ID"
Upload / Download / Export
uv run scripts/google_drive.py upload --file "./blueprints.pdf" --folder "FOLDER_ID"
uv run scripts/google_drive.py download --id "FILE_ID" --output "./local_copy.pdf"
# Export Google Workspace docs
uv run scripts/google_drive.py export --id "DOC_ID" --output "./report.docx" --format docx
uv run scripts/google_drive.py export --id "SHEET_ID" --output "./data.csv" --format csv
Export Formats: Google Docs (pdf, docx, txt, html, md), Sheets (pdf, xlsx, csv), Slides (pdf, pptx), Drawings (pdf, png, svg).
Organize (mkdir, move, copy, rename)
uv run scripts/google_drive.py mkdir --name "Project Stardust" --parent "PARENT_ID"
uv run scripts/google_drive.py move --id "FILE_ID" --to "DEST_FOLDER_ID"
uv run scripts/google_drive.py copy --id "FILE_ID" --name "Copy of Plans"
uv run scripts/google_drive.py rename --id "FILE_ID" --name "Updated Plans v2"
Trash / Delete / Share / Revisions / Comments
# Soft delete (reversible)
uv run scripts/google_drive.py trash --id "FILE_ID"
uv run scripts/google_drive.py untrash --id "FILE_ID"
# Empty entire trash (permanent, no recovery)
uv run scripts/google_drive.py empty-trash
# Permanent delete of a specific file
uv run scripts/google_drive.py delete --id "FILE_ID"
# Share
uv run scripts/google_drive.py share --id "FILE_ID" --email "luke@tatooine.net" --role writer
uv run scripts/google_drive.py share --id "FILE_ID" --type anyone --role reader
uv run scripts/google_drive.py permissions --id "FILE_ID"
uv run scripts/google_drive.py unshare --id "FILE_ID" --permission-id "PERM_ID"
# Revision history
uv run scripts/google_drive.py list-revisions --id "FILE_ID"
uv run scripts/google_drive.py restore-revision --id "FILE_ID" --revision-id "REV_ID"
# Comments
uv run scripts/google_drive.py list-comments --id "FILE_ID"
uv run scripts/google_drive.py add-comment --id "FILE_ID" --content "LGTM, approved."
Docs
Google Docs content manipulation. Read text, create documents, and modify text.
Read / Create
# Read text from a document
uv run scripts/google_docs.py read --id "DOC_ID"
# Create a new document
uv run scripts/google_docs.py create --title "Project Requirements"
Edit Text
# Append text to the end of the document
uv run scripts/google_docs.py append --id "DOC_ID" --text "Additional requirements:\n1. Must be fast."
# Replace text
uv run scripts/google_docs.py replace --id "DOC_ID" --old "[COMPANY_NAME]" --new "Acme Corp"
# Insert a heading
uv run scripts/google_docs.py insert-heading --id "DOC_ID" --text "Project Overview" --level 1
# Format a text range (index-based)
uv run scripts/google_docs.py format-text --id "DOC_ID" --start 0 --end 20 --bold
uv run scripts/google_docs.py format-text --id "DOC_ID" --start 0 --end 20 --italic --underline
# Insert a public image
uv run scripts/google_docs.py insert-image --id "DOC_ID" --uri "https://example.com/banner.png"
Comments
uv run scripts/google_docs.py list-comments --id "DOC_ID"
uv run scripts/google_docs.py add-comment --id "DOC_ID" --content "Please review this section."
Sheets
Google Sheets manipulation. Read, append, update, and clear ranges.
Read / Create
# Read values from a specific range
uv run scripts/google_sheets.py read --id "SHEET_ID" --range "Sheet1!A1:D10"
# Create a new spreadsheet
uv run scripts/google_sheets.py create --title "Q3 Budget"
Update / Append / Clear / Format
# Append a row (expects a 2D JSON array)
uv run scripts/google_sheets.py append --id "SHEET_ID" --range "Sheet1!A:A" --values '[["2026-03-01", "Rent", 1200]]'
# Update a specific range
uv run scripts/google_sheets.py update --id "SHEET_ID" --range "Sheet1!A2:C2" --values '[["2026-03-02", "Utilities", 150]]'
# Clear a range
uv run scripts/google_sheets.py clear --id "SHEET_ID" --range "Sheet1!A2:D10"
# Worksheet tab management
uv run scripts/google_sheets.py list-sheets --id "SHEET_ID"
uv run scripts/google_sheets.py add-sheet --id "SHEET_ID" --title "February"
uv run scripts/google_sheets.py rename-sheet --id "SHEET_ID" --title "January" --new-title "Jan 2026"
uv run scripts/google_sheets.py delete-sheet --id "SHEET_ID" --title "February"
# Format a range (bold header, background color)
uv run scripts/google_sheets.py format-range \
--id "SHEET_ID" --range "Sheet1!A1:D1" --bold --background-color "#4A90D9"
# Freeze the first row and first column
uv run scripts/google_sheets.py freeze-panes --id "SHEET_ID" --rows 1 --cols 1
# Auto-resize columns to fit content
uv run scripts/google_sheets.py auto-resize --id "SHEET_ID" --range "Sheet1!A:D"
Photos
Google Photos management. Browse, search, download, upload, and organize albums.
API Limitation: The Photos Library API only allows modifying media items uploaded by this application.
List / Search / Get / Download
uv run scripts/google_photos.py list
uv run scripts/google_photos.py search --date-start "2026-01-01" --date-end "2026-02-16"
uv run scripts/google_photos.py search --categories LANDSCAPES FOOD
uv run scripts/google_photos.py get --id "MEDIA_ID"
uv run scripts/google_photos.py download --id "MEDIA_ID" --output "./sunset.jpg"
Search Categories: ANIMALS, ARTS, BIRTHDAYS, CITYSCAPES, CRAFTS, DOCUMENTS, FASHION, FLOWERS, FOOD, GARDENS, HOLIDAYS, HOUSES, LANDMARKS, LANDSCAPES, NIGHT, PEOPLE, PERFORMANCES, PETS, RECEIPTS, SCREENSHOTS, SELFIES, SPORT, TRAVEL, UTILITY, WEDDINGS, WHITEBOARDS.
Upload
uv run scripts/google_photos.py upload \
--file "./hologram.jpg" --description "Leia's holographic message" --album "ALBUM_ID"
Albums
uv run scripts/google_photos.py albums
uv run scripts/google_photos.py album-get --id "ALBUM_ID"
uv run scripts/google_photos.py album-create --title "Tatooine Sunsets"
uv run scripts/google_photos.py album-add --album "ALBUM_ID" --items "ITEM_1" "ITEM_2"
uv run scripts/google_photos.py album-remove --album "ALBUM_ID" --items "ITEM_1"
Upgrading from v1 (per-service skills)
If you previously installed the separate gmail, google-calendar, google-contacts, google-drive, and/or google-photos skills:
# Dry run β shows what will change without modifying anything
bash scripts/upgrade.sh --dry-run
# Run the migration
bash scripts/upgrade.sh
The upgrade script:
- Consolidates credentials β copies
token.jsonandcredentials.jsonfrom any legacy dir (~/.gmail_credentials/, etc.) into~/.google_workspace/. - Removes old skill installations β deletes old per-service skill directories from
.agent/skills/if present. - Preserves legacy dirs β does not delete
~/.gmail_credentials/etc., so existing tools that depend on them still work. - Verifies auth β confirms the unified token works for all services.
NotebookLM
Manage Google NotebookLM notebooks, sources, artifacts, and chat.
Auth: NotebookLM uses browser cookie auth (not OAuth). Auth state is stored at ~/.notebooklm/storage_state.json.
Setup
# Interactive setup: copies your __Secure-1PSID cookie from an open browser session
uv run scripts/google_notebooklm.py setup
# Or set the cookie value directly
uv run scripts/google_notebooklm.py setup --cookie "YOUR_COOKIE_VALUE"
Common Commands
# List notebooks
uv run scripts/google_notebooklm.py list
# Create a notebook
uv run scripts/google_notebooklm.py create --title "Death Star Schematics"
# Add sources
uv run scripts/google_notebooklm.py add-url --id NOTEBOOK_ID --url "https://example.com/article"
uv run scripts/google_notebooklm.py add-file --id NOTEBOOK_ID --file ./document.pdf
uv run scripts/google_notebooklm.py add-text --id NOTEBOOK_ID --title "Notes" --text "Key findings..."
uv run scripts/google_notebooklm.py add-drive --id NOTEBOOK_ID --drive-id "DRIVE_FILE_ID"
# Chat with a notebook
uv run scripts/google_notebooklm.py chat --id NOTEBOOK_ID --message "Summarize the main themes"
# Generate artifacts
uv run scripts/google_notebooklm.py generate-audio --id NOTEBOOK_ID
uv run scripts/google_notebooklm.py generate-report --id NOTEBOOK_ID --type "study_guide"
uv run scripts/google_notebooklm.py generate-quiz --id NOTEBOOK_ID
uv run scripts/google_notebooklm.py generate-flashcards --id NOTEBOOK_ID
# Download generated artifacts
uv run scripts/google_notebooklm.py list-artifacts --id NOTEBOOK_ID
uv run scripts/google_notebooklm.py download-audio --id NOTEBOOK_ID --output ./overview.mp3
uv run scripts/google_notebooklm.py download-report --id NOTEBOOK_ID --output ./report.md
JSON Output
All commands produce JSON for easy parsing. See the individual service sections above for output schemas.
All error outputs include structured JSON with status, error, and type fields. Auth errors additionally include a fix field with the exact command to run:
{
"status": "error",
"message": "Gmail API not authenticated.",
"type": "AuthError"
}
Troubleshooting
Token expired / 401 errors
Access tokens expire after 1 hour. If the systemd timer is running, tokens refresh automatically. If not:
# Check timer status
systemctl --user status workspace-token-maintainer.timer
# Install and enable if missing
bash scripts/install_services.sh
systemctl --user enable --now workspace-token-maintainer.timer
7-day refresh token expiry (@gmail.com accounts)
GCP projects in "Testing" status cause refresh tokens to expire every 7 days. Options:
- Publish the app in GCP Console (OAuth consent screen > Publish) to get permanent refresh tokens.
- Re-authenticate weekly:
uv run scripts/setup_workspace.py - Use a Google Workspace account instead of @gmail.com.
Missing scopes
If a service returns scope errors, re-run the unified setup to request all scopes:
uv run scripts/setup_workspace.py
credentials.json not found
Download OAuth client secret from GCP Console > APIs & Services > Credentials, then save to ~/.google_workspace/credentials.json. See references/GCP_SETUP.md for step-by-step instructions.