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-photos
Run scripts/upgrade.sh to 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 draft over send for new compositions — let the user review first.
-
Reply/Forward defaults to draft — use --send only when explicitly requested.
-
Trash is reversible — empty-trash is 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.json and credentials.json from 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.