GWS-CLI — Google Workspace CLI Playbook for Solanasis
What: The official Google Workspace CLI (
gws) — a single command-line tool that exposes every Google Workspace API (Calendar, Gmail, Drive, Sheets, Docs, and more).Why: Replaces the Google Calendar MCP connector that only works in interactive Claude.ai sessions. With
gws, Claude Code can access Google Workspace via Bash in both interactive AND headless (claude -p) scheduled tasks.Status: Pre-1.0 (v0.22.3+, updated April 2026). Apache 2.0 license. Published by
googleworkspaceon GitHub but labeled “not an officially supported Google product.” 22k+ stars, extremely active development.
1. Installation
Prerequisites:
- Node.js 18+ (this machine: v24.14)
- A GCP (Google Cloud Platform) project with OAuth credentials (see Section 3)
Install:
npm install -g @googleworkspace/cliVerify:
gws --version
gws --helpAlternative install methods:
- Cargo (Rust):
cargo install --git https://github.com/googleworkspace/cli --locked - Pre-built binaries: https://github.com/googleworkspace/cli/releases
2. Authentication Setup
Step 1: GCP Project Setup
You need a Google Cloud Platform project with OAuth 2.0 credentials. If you already have one for Solanasis, skip to Step 2.
- Go to https://console.cloud.google.com/
- Create a new project (or select existing):
solanasis-gws-cli - Enable the APIs you need:
- APIs & Services > Library > search and enable:
- Google Calendar API
- Gmail API
- Google Drive API
- Google Sheets API
- Google People API (Contacts)
- Google Tasks API
- APIs & Services > Library > search and enable:
- Configure OAuth consent screen:
- APIs & Services > OAuth consent screen
- User Type: External (or Internal if using Google Workspace org)
- App name:
Solanasis GWS CLI - Scopes: Add the scopes for the APIs you enabled
- Test users: Add
mr.sunshine@solanasis.com(required while app is in “Testing” mode)
- Create OAuth Client ID:
- APIs & Services > Credentials > Create Credentials > OAuth client ID
- Application type: Desktop app
- Name:
gws-cli - Download the JSON credentials file
Step 2: Install gcloud CLI (required for gws auth setup)
# Install via winget (Windows):
winget install Google.CloudSDK --accept-package-agreements --accept-source-agreements
# Installs to: C:\Program Files (x86)\Google\Cloud SDK\google-cloud-sdk\
# May need shell restart for PATH to updateStep 3: Run Auth Setup (REQUIRES RDP — needs browser)
This step must be done via RDP (Remote Desktop) on the server, not SSH. The OAuth flow opens a browser window that you need to interact with.
- RDP into the server
- Open PowerShell or CMD
- Run:
"C:\Program Files (x86)\Google\Cloud SDK\google-cloud-sdk\bin\gcloud.cmd" auth login- Complete the browser OAuth flow with your Google account
- Then run:
gws auth setupgws auth setupwalks through: project selection → API enabling → OAuth client creation →gws auth login- Select/create GCP project, enable the APIs you need (Calendar, Gmail, Drive, Sheets, Slides, etc.)
- Complete the second browser OAuth flow for
gws auth login
Windows gotcha: The redirect URI uses a random port (e.g., http://localhost:51065). If you get a redirect error:
- Copy the full redirect URI from the console output
- Go to GCP Console > APIs & Services > Credentials > your OAuth client
- Add the URI to “Authorized redirect URIs”
- Re-run
gws auth login
Step 4: Verify
gws auth status
# Should show auth_method: oauth2, user: mr.sunshine@solanasis.com
gws calendar +agenda --today --timezone America/Denver
# Should show today's calendar eventsCredential Storage
- Location:
~/.config/gws/credentials.enc(encrypted) - Encrypted with AES-256-GCM
- Encryption key stored in Windows Credential Manager (interactive sessions) or file-based fallback (headless)
- Automatic token refresh — no manual re-auth needed (unless revoked)
Headless/SSH/Scheduled Task Auth
Important: Windows Credential Manager requires an interactive logon session. SSH and Task Scheduler sessions will get ERROR_NO_SUCH_LOGON_SESSION when trying to access the keyring.
Fix: Set the environment variable to use file-based key storage:
export GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=fileThe Python wrapper scripts (run-daily-briefing.py, etc.) set this automatically via os.environ.setdefault(). For ad-hoc SSH use, set it in your shell profile or prefix commands with the env var.
With this set, gws stores the encryption key at ~/.config/gws/.encryption_key instead of Windows Credential Manager. Both work — the file backend just skips the OS keyring.
Re-authentication
If credentials expire or are revoked, RDP into the server and re-run:
gws auth loginThis is the only step that requires RDP — once authenticated, the refresh token handles everything headlessly.
Current Solanasis Setup (as of 2026-03-25)
- Account:
mr.sunshine@solanasis.com - GCP Project:
solanasis-gws-cli - Auth method: OAuth2 with encrypted refresh token
- Credentials:
~/.config/gws/credentials.enc(AES-256-GCM) - Scopes: Calendar, Gmail, Drive, Sheets, Slides, Cloud Platform, profile
- Headless fix: Python wrappers set
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=fileautomatically - Re-auth: RDP into server →
gws auth login(only if token is revoked)
3. Google Calendar Commands
Quick Commands (Helper Skills)
# Today's agenda
gws calendar +agenda --today --timezone America/Denver
# This week's agenda in table format
gws calendar +agenda --week --format table --timezone America/Denver
# Next 3 days
gws calendar +agenda --days 3 --timezone America/Denver
# Create an event via natural language
gws calendar events quickAdd --params '{"calendarId": "primary", "text": "Coffee with Caryn tomorrow at 10am"}'Full API Commands
# List events (raw API)
gws calendar events list --params '{"calendarId": "primary", "timeMin": "2026-03-23T00:00:00-06:00", "timeMax": "2026-03-24T00:00:00-06:00", "singleEvents": true, "orderBy": "startTime"}'
# Get a specific event
gws calendar events get --params '{"calendarId": "primary", "eventId": "abc123"}'
# Create an event
gws calendar events insert --params '{"calendarId": "primary"}' --json '{
"summary": "ORB Assessment Call",
"start": {"dateTime": "2026-03-25T10:00:00-06:00"},
"end": {"dateTime": "2026-03-25T11:00:00-06:00"},
"attendees": [{"email": "client@example.com"}]
}'
# Free/busy query
gws calendar freebusy query --json '{
"timeMin": "2026-03-24T08:00:00-06:00",
"timeMax": "2026-03-24T18:00:00-06:00",
"items": [{"id": "primary"}]
}'
# List all calendars
gws calendar calendarList list
# Delete an event
gws calendar events delete --params '{"calendarId": "primary", "eventId": "abc123"}'Timezone Handling
gwsauto-detects your timezone from Google Calendar Settings (24-hour cache)- Override with
--timezone America/Denveror--tz America/Denver - All Solanasis scheduled tasks should explicitly pass
--timezone America/Denver
4. Beyond Calendar — Full Workspace Access
gws auto-discovers every Google Workspace API. Here are the most useful for Solanasis:
Gmail
# Triage inbox — see unread messages with AI summary
gws gmail +triage
# Send an email
gws gmail +send --to "client@example.com" --subject "ORB Follow-Up" --body "Thanks for the call..."
# Read a specific message
gws gmail +read --message-id "abc123"
# Search messages
gws gmail messages list --params '{"q": "from:client@example.com after:2026/03/20"}'Google Drive
# List recent files
gws drive files list --params '{"orderBy": "modifiedTime desc", "pageSize": 10}'
# Search for files
gws drive files list --params '{"q": "name contains '\''ORB'\'' and mimeType='\''application/pdf'\''"}'
# Upload a file
gws drive files create --upload path/to/file.pdf --params '{"name": "ORB-Assessment-Report.pdf"}'Google Sheets
# Read a range
gws sheets +read --spreadsheet-id "SHEET_ID" --range "Sheet1!A1:D10"
# Append data
gws sheets +append --spreadsheet-id "SHEET_ID" --range "Sheet1" --values '[["Name", "Email", "Status"]]'Google Docs
# Append content to a document
gws docs +write --document-id "DOC_ID" --text "New section content here..."Google Tasks
# List task lists
gws tasks tasklists list
# List tasks in a list
gws tasks tasks list --params '{"tasklist": "TASKLIST_ID"}'People / Contacts
# Search contacts
gws people people searchContacts --params '{"query": "Caryn", "readMask": "names,emailAddresses,phoneNumbers"}'
# List connections
gws people people connections list --params '{"resourceName": "people/me", "personFields": "names,emailAddresses"}'Full Service List
| Service | Skill Prefix | Key Commands |
|---|---|---|
| Calendar | gws calendar | +agenda, +insert, events CRUD, freebusy |
| Gmail | gws gmail | +triage, +send, +read, +reply, +forward |
| Drive | gws drive | files CRUD, upload, sharing |
| Sheets | gws sheets | +read, +append |
| Docs | gws docs | +write |
| Slides | gws slides | presentation tools |
| Tasks | gws tasks | task list management |
| People | gws people | contact search, profiles |
| Chat | gws chat | +send to spaces |
| Meet | gws meet | conferencing |
| Forms | gws forms | form creation, responses |
| Admin | gws admin | audit logs, usage analytics |
5. Integration with Claude Code
Approach A: Direct CLI via Bash (Recommended)
Claude Code can call gws directly via the Bash tool. No MCP config needed.
# In Claude Code, just ask:
"What's on my calendar today?"
# Claude runs:
gws calendar +agenda --today --timezone America/Denver
Why this is better than MCP:
- Zero context overhead — MCP loads 200-400 tool definitions upfront,
gwsloads nothing - Works in
claude -pheadless mode — no cloud MCP connector needed - Full API coverage — auto-discovers new endpoints as Google adds them
- On-demand discovery — Claude can run
gws schema calendar.events.listto learn the API
Setup: Just install gws and authenticate. Claude Code inherits your shell environment.
Approach B: MCP Server Mode (Alternative)
If you prefer structured tool interfaces:
# Add to Claude Code MCP config
claude mcp add --transport stdio gws-workspace -- gws mcp -s calendar,gmail --tool-mode compact--tool-mode compact reduces from 200+ tools to ~26 (one per service + discovery meta-tool).
When to use Approach B:
- You want explicit tool schemas for type safety
- You’re building a more structured automation pipeline
- You want Claude to discover available operations without running
gws schema
Recommendation: Start with Approach A. Switch to B only if you find the direct CLI approach insufficient.
6. Integration with Solanasis Scheduled Tasks
Daily GTM Briefing
The daily briefing (run-daily-briefing.py) previously relied on Google Calendar MCP tools that aren’t available in claude -p. With gws:
Before (broken in headless mode):
mcp__claude_ai_Google_Calendar__gcal_list_events
After (works everywhere):
gws calendar +agenda --today --timezone America/DenverThe Bash tool is already in the ALLOWED_TOOLS list, so no additional tool permissions needed.
How the Briefing Agent Uses Calendar
The briefing prompt instructs Claude to cross-reference calendar events with meeting notes. With gws, Claude:
- Runs
gws calendar +agenda --today --timezone America/Denvervia Bash - Parses the output to identify today’s meetings
- Cross-references with Fireflies-synced meeting notes
- Highlights follow-ups and prep needed for upcoming meetings
7. Windows-Specific Notes
JSON Quoting
# Git Bash (what this machine uses) — single quotes work:
gws calendar events list --params '{"calendarId": "primary"}'
# PowerShell — must escape double quotes:
gws calendar events list --params "{`"calendarId`": `"primary`"}"
# CMD — must escape double quotes differently:
gws calendar events list --params "{\"calendarId\": \"primary\"}"Credential Storage
- Uses Windows Credential Manager for encryption key
- Credentials at
%USERPROFILE%\.config\gws\credentials.json - If Windows Credential Manager has issues (like we saw with git), set:
export GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file
Python Scripting
import subprocess
import json
result = subprocess.run(
["gws", "calendar", "+agenda", "--today", "--timezone", "America/Denver"],
capture_output=True, encoding="utf-8", errors="replace"
)
events = result.stdout # Parse as needed8. Security Considerations
- OAuth scope minimization: Only enable the APIs you actually use in GCP Console. Calendar + Gmail is sufficient for current scheduled tasks.
- Service account vs OAuth: OAuth is fine for a single-user setup like Solanasis. Service accounts are for multi-user or domain-wide scenarios.
- Credential encryption:
gwsencrypts credentials at rest with AES-256-GCM. The key lives in the OS keyring (Windows Credential Manager). - Sensitive data in context: When Claude reads emails or documents via
gws, that content passes through the context window. Be mindful with client-sensitive data. - GCP project in “Testing” mode: Limits to 100 users and tokens expire after 7 days. For production use, submit for Google verification.
- Never commit credentials: The
~/.config/gws/directory is outside the repo. Never copy credential files into the workspace.
9. Limitations & Gotchas
- Pre-1.0 software: “Expect breaking changes as we march toward v1.0.” Pin to a version if stability matters.
gws auth setupon Windows: Has been reported as broken. Use the manual GCP Console setup (Section 2, Step 1) instead.- Redirect URI port: Changes per
gws auth loginsession. You may need to update the authorized redirect URIs in GCP Console each time. - Rate limits: Standard Google Workspace API quotas apply. Use
--page-delayfor bulk operations. - No built-in retry: API errors return structured exit codes but don’t auto-retry. Your scripts should handle retries if needed.
- Not officially supported: No Google SLA or support channels. File issues on GitHub.
- Extremely rapid development: 7 releases in 10 days (March 2026). Check for breaking changes after updates.
10. Quick Reference Card
# Auth
gws auth login # Interactive OAuth
gws auth status # Check current user
gws auth logout # Clear credentials
# Calendar
gws calendar +agenda --today # Today's events
gws calendar +agenda --week # Week view
gws calendar events quickAdd ... # Natural language event creation
# Gmail
gws gmail +triage # Inbox overview
gws gmail +send --to X --subject Y --body Z
# Drive
gws drive files list # List files
gws drive files create --upload F # Upload file
# Sheets
gws sheets +read --spreadsheet-id ID --range R
gws sheets +append --spreadsheet-id ID --range R --values V
# Discovery
gws schema calendar.events.list # Show API schema for any method
gws --help # Full helpResources
- GitHub: https://github.com/googleworkspace/cli
- Releases: https://github.com/googleworkspace/cli/releases
- Skills docs: https://github.com/googleworkspace/cli/blob/main/docs/skills.md
- Calendar skill: https://github.com/googleworkspace/cli/blob/main/skills/gws-calendar/SKILL.md
11. Document Generation Workflow
Overview
GWS CLI enables a template-driven document generation workflow: design templates visually in Google Slides/Docs with {{PLACEHOLDER}} markers, then use CLI commands to copy the template, replace placeholders with content, and export to PDF.
This is the recommended approach for client deliverables (proposals, reports, one-pagers) because:
- Templates are designed visually (not in code) — easy to iterate
- Any team member can update the design without touching code
- The code only fills in content — thin and maintainable
- Output includes both a live Google Doc/Slides link AND a PDF
Step 1: Copy Template
gws drive files copy \
--params '{"fileId": "TEMPLATE_FILE_ID"}' \
--json '{"name": "Proposal - Acme Corp - 2026-04-11", "parents": ["OUTPUT_FOLDER_ID"]}'Returns a JSON response with the new file’s id.
Step 2: Replace Placeholders (Slides)
gws slides presentations batchUpdate \
--params '{"presentationId": "NEW_COPY_ID"}' \
--json '{
"requests": [
{
"replaceAllText": {
"containsText": {"text": "{{CLIENT_NAME}}", "matchCase": true},
"replaceText": "Acme Corp"
}
},
{
"replaceAllText": {
"containsText": {"text": "{{PROPOSAL_DATE}}", "matchCase": true},
"replaceText": "April 11, 2026"
}
}
]
}'For Google Docs, use the same pattern with docs documents batchUpdate and documentId:
gws docs documents batchUpdate \
--params '{"documentId": "NEW_COPY_ID"}' \
--json '{
"requests": [
{
"replaceAllText": {
"containsText": {"text": "{{CLIENT_NAME}}", "matchCase": true},
"replaceText": "Acme Corp"
}
}
]
}'Step 3: Replace Images (Slides Only)
gws slides presentations batchUpdate \
--params '{"presentationId": "NEW_COPY_ID"}' \
--json '{
"requests": [
{
"replaceAllShapesWithImage": {
"imageUrl": "https://solanasis.com/images/logo/solanasis-logo-horizontal.png",
"imageReplaceMethod": "CENTER_INSIDE",
"containsText": {"text": "{{LOGO}}", "matchCase": true}
}
}
]
}'Note: imageUrl must be publicly accessible. Use Google Drive share links for non-public images.
POC Discovery: Image URLs must be publicly accessible from Google’s servers. URLs behind Cloudflare WAF return 403. Use Google Drive-hosted images instead.
Step 4: Export to PDF
gws drive files export \
--params '{"fileId": "NEW_COPY_ID", "mimeType": "application/pdf"}' \
> output.pdfLimits: 10 MB maximum export size. For large documents with many images, compress images before inserting.
Step 5: Email the PDF (Optional)
gws gmail +send \
--to "client@example.com" \
--subject "Your Solanasis Proposal" \
--body "Please find the attached proposal." \
-a output.pdfAutomated Workflow
The generate-doc.py script automates steps 1-5:
# Preflight check
secret run solanasis-scripts -- python generate-doc.py --check-only
# Generate a proposal
secret run solanasis-scripts -- python generate-doc.py proposal --client "Acme Corp"
# Generate + email
secret run solanasis-scripts -- python generate-doc.py proposal \
--client "Acme Corp" --email cto@acme.com --output proposal.pdfPOC Discovery: Image URLs must be publicly accessible from Google’s servers. URLs behind Cloudflare WAF return 403. Use Google Drive-hosted images instead.
Placeholder Convention
All placeholders use {{DOUBLE_CURLY_UPPERCASE}} format:
{{CLIENT_NAME}}— Client/prospect name{{PROPOSAL_DATE}}/{{REPORT_DATE}}/{{DATE}}— Document date{{CONSULTANT}}— Consultant name (default: Dmitri Zasage){{COMPANY_SIZE}}— Employee count{{INDUSTRY}}— Client industry{{ENGAGEMENT_TYPE}}— Service type (ORB, DR Verification, etc.){{LOGO}}— Image placeholder (Slides only){{PRICING}}— Engagement pricing
Template IDs and placeholder definitions are stored in solanasis-scripts/config/template_ids.json.
12. WSL Credential Setup
GWS CLI OAuth flow requires a browser, which is not available in WSL. The solution is to authenticate on Windows and copy credentials to WSL.
Copy Credentials from Windows to WSL
mkdir -p ~/.config/gws
cp /mnt/c/Users/zasya/.config/gws/client_secret.json ~/.config/gws/
cp /mnt/c/Users/zasya/.config/gws/credentials.enc ~/.config/gws/
cp /mnt/c/Users/zasya/.config/gws/.encryption_key ~/.config/gws/
cp /mnt/c/Users/zasya/.config/gws/token_cache.json ~/.config/gws/Set File-Based Keyring Backend
WSL does not have Windows Credential Manager. Set the file-based backend:
export GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=fileAdd to ~/.bashrc or ~/.zshrc for persistence. Python wrapper scripts set this automatically via os.environ.setdefault().
Verify
gws auth status
# Should show: auth_method: oauth2, user: mr.sunshine@solanasis.com
gws calendar +agenda --today --timezone America/Denver
# Should show today's eventsWindows Firewall Gotcha
If you try gws auth login directly in WSL, it will fail because:
- The OAuth flow opens a redirect URL on a random localhost port
- Windows Firewall blocks WSL from binding to that port
- The browser (on the Windows side) cannot reach the WSL callback
Solution: Always authenticate on Windows first, then copy credentials to WSL. Re-auth is only needed if the refresh token expires or is revoked (rare).
Re-authentication Flow
- RDP into the server (or open Windows terminal)
- Run:
gws auth login - Complete browser OAuth flow
- Copy updated credentials to WSL (repeat the copy commands above)
13. Gmail Advanced
HTML Email
gws gmail +send \
--to "client@example.com" \
--subject "Solanasis ORB Results" \
--html "<h1>Assessment Complete</h1><p>Please find your report attached.</p>"Multiple Attachments
gws gmail +send \
--to "client@example.com" \
--subject "Solanasis Deliverables" \
--body "Please find the attached deliverables." \
-a report.pdf \
-a executive-summary.pdf \
-a risk-register.xlsxAttachment limits: 25 MB total (Gmail API supports up to 35 MB via upload endpoint).
Reply with Attachment
gws gmail +reply \
--message-id "MSG_ID" \
--body "Here is the updated report." \
-a updated-report.pdfForward with Attachment
gws gmail +forward \
--message-id "MSG_ID" \
--to "colleague@solanasis.com" \
--body "FYI - client deliverable attached." \
-a deliverable.pdfClient Outreach Email Pattern
For automated client outreach after generating a proposal:
# Step 1: Generate the proposal
python generate-doc.py proposal --client "Acme Corp" --output /tmp/acme-proposal.pdf
# Step 2: Send with professional email
gws gmail +send \
--to "cto@acmecorp.com" \
--subject "Solanasis Operational Resilience Proposal for Acme Corp" \
--html "<p>Hi [Name],</p><p>Following our conversation, please find the attached proposal for Solanasis Operational Resilience services.</p><p>I am available to discuss at your convenience.</p><p>Best regards,<br>Dmitri Zasage<br>CEO, Solanasis LLC</p>" \
-a /tmp/acme-proposal.pdfAlternative: Brevo SMTP
For automated/headless email delivery without interactive confirmation prompts, use the existing Brevo SMTP sender:
from brevo.smtp_sender import SmtpSender
sender = SmtpSender(brand="solanasis")
sender.send(to="client@example.com", subject="Your Proposal", body="...", html="...")See solanasis-scripts/brevo/smtp_sender.py for full API.
Last updated: 2026-04-11 Playbook version: 2.0