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 googleworkspace on 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/cli

Verify:

gws --version
gws --help

Alternative install methods:


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.

  1. Go to https://console.cloud.google.com/
  2. Create a new project (or select existing): solanasis-gws-cli
  3. 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
  4. 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)
  5. 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 update

Step 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.

  1. RDP into the server
  2. Open PowerShell or CMD
  3. Run:
"C:\Program Files (x86)\Google\Cloud SDK\google-cloud-sdk\bin\gcloud.cmd" auth login
  1. Complete the browser OAuth flow with your Google account
  2. Then run:
gws auth setup
  1. gws auth setup walks through: project selection → API enabling → OAuth client creation → gws auth login
  2. Select/create GCP project, enable the APIs you need (Calendar, Gmail, Drive, Sheets, Slides, etc.)
  3. 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:

  1. Copy the full redirect URI from the console output
  2. Go to GCP Console > APIs & Services > Credentials > your OAuth client
  3. Add the URI to “Authorized redirect URIs”
  4. 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 events

Credential 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=file

The 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 login

This 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=file automatically
  • 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

  • gws auto-detects your timezone from Google Calendar Settings (24-hour cache)
  • Override with --timezone America/Denver or --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

ServiceSkill PrefixKey Commands
Calendargws calendar+agenda, +insert, events CRUD, freebusy
Gmailgws gmail+triage, +send, +read, +reply, +forward
Drivegws drivefiles CRUD, upload, sharing
Sheetsgws sheets+read, +append
Docsgws docs+write
Slidesgws slidespresentation tools
Tasksgws taskstask list management
Peoplegws peoplecontact search, profiles
Chatgws chat+send to spaces
Meetgws meetconferencing
Formsgws formsform creation, responses
Admingws adminaudit logs, usage analytics

5. Integration with Claude Code

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, gws loads nothing
  • Works in claude -p headless 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.list to 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/Denver

The 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:

  1. Runs gws calendar +agenda --today --timezone America/Denver via Bash
  2. Parses the output to identify today’s meetings
  3. Cross-references with Fireflies-synced meeting notes
  4. 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 needed

8. 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: gws encrypts 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 setup on Windows: Has been reported as broken. Use the manual GCP Console setup (Section 2, Step 1) instead.
  • Redirect URI port: Changes per gws auth login session. You may need to update the authorized redirect URIs in GCP Console each time.
  • Rate limits: Standard Google Workspace API quotas apply. Use --page-delay for 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 help

Resources


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.pdf

Limits: 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.pdf

Automated 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.pdf

POC 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=file

Add 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 events

Windows Firewall Gotcha

If you try gws auth login directly in WSL, it will fail because:

  1. The OAuth flow opens a redirect URL on a random localhost port
  2. Windows Firewall blocks WSL from binding to that port
  3. 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

  1. RDP into the server (or open Windows terminal)
  2. Run: gws auth login
  3. Complete browser OAuth flow
  4. 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.xlsx

Attachment 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.pdf

Forward with Attachment

gws gmail +forward \
  --message-id "MSG_ID" \
  --to "colleague@solanasis.com" \
  --body "FYI - client deliverable attached." \
  -a deliverable.pdf

Client 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.pdf

Alternative: 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