
Google Workspace Cli
- 635 installs
- 23.5k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
google-workspace-cli is a Claude skill that bundles 10 Google Workspace CLI persona profiles with scoped gws commands and recipes for developers who automate Gmail, Calendar, Tasks, Sheets, and Drive from coding agents.
About
google-workspace-cli is a Claude skill that packages 10 role-based Google Workspace CLI persona profiles—such as Executive Assistant—with curated gws helper commands and recipes for Gmail, Calendar, Tasks, Sheets, and Drive. Each persona scopes which CLI invocations an agent should run, from `gws helpers morning-briefing` and `gws helpers find-time` to `gws gmail users.messages send` and `gws helpers eod-wrap`. Developers reach for google-workspace-cli when they want repeatable Workspace automation inside Claude Code or Cursor instead of ad-hoc API scripting. The skill maps daily coordination tasks—schedule overviews, meeting prep, email sends, end-of-day summaries—to concrete command sequences agents can invoke safely. It assumes the Google Workspace CLI (`gws`) is installed and authenticated, and focuses on operational productivity rather than building SaaS features.
- 10 role-based persona bundles including Executive Assistant, Project Manager and more
- Pre-scoped recipes and top commands for each persona such as morning-briefing, find-time, standup-report and project-sta
- Daily workflow templates that chain helpers, gmail, tasks and sheets commands
- Reduces prompt engineering by giving agents a focused Google Workspace skill profile
- Works with Claude Code, Cursor and other agents that can invoke CLI tools
Google Workspace Cli by the numbers
- 635 all-time installs (skills.sh)
- Ranked #379 of 2,725 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alirezarezvani/claude-skills --skill google-workspace-cliAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 635 |
|---|---|
| repo stars | ★ 23.5k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
How do you automate Gmail and Calendar from a coding agent?
Get 10 ready-made Google Workspace personas that bundle CLI commands and recipes for Gmail, Calendar, Tasks, Sheets and Drive directly into their coding agents.
Who is it for?
Developers who already use the Google Workspace CLI and want role-scoped command bundles inside Claude or Cursor.
Skip if: Teams without Google Workspace CLI access or developers building custom Gmail API integrations from scratch.
When should I use this skill?
The user asks to send Gmail, schedule meetings, run morning briefings, or automate Sheets/Drive tasks via gws from an agent.
What you get
10 scoped persona profiles, gws command cheat sheets, and recipe workflows for Workspace automation.
- persona command bundles
- gws recipe workflows
By the numbers
- Bundles 10 role-based Google Workspace CLI persona profiles
- Covers 5 Workspace surfaces: Gmail, Calendar, Tasks, Sheets, and Drive
Files
Google Workspace CLI
Expert guidance and automation for Google Workspace administration using the open-source gws CLI (github.com/googleworkspace/cli, Apache-2.0). The CLI builds its command surface dynamically from Google's Discovery Service, so it covers every supported Workspace API plus +-prefixed helper commands. This skill adds local Python tools (doctor, auth guide, recipe catalog, security audit, output analyzer).
Verify before scripting:gwsgenerates commands at runtime from Google's API discovery documents, and the CLI is pre-v1.0. Always confirm a command's exact surface withgws --help,gws <service> --help, orgws schema <service>.<resource>.<method>before putting it in automation. Commands in this skill marked (verify) are illustrative of thegws <service> <resource> <method>pattern and must be checked against your installed version.
---
Quick Start
Check Installation
# Verify gws is installed and authenticated
python3 scripts/gws_doctor.pySend an Email
gws gmail +send --to "team@company.com" \
--subject "Weekly Update" --body "Here's this week's summary..."List Drive Files
gws drive files list --params '{"pageSize": 20}' | python3 scripts/output_analyzer.py --select "name,mimeType,modifiedTime" --format table---
Installation
npm (recommended; requires Node.js 18+)
npm install -g @googleworkspace/cli
gws --versionHomebrew (macOS/Linux)
brew install googleworkspace-cliCargo (from source)
cargo install --git https://github.com/googleworkspace/cli --locked
gws --versionPre-built Binaries
Download from github.com/googleworkspace/cli/releases for macOS, Linux, or Windows. Nix users: nix run github:googleworkspace/cli.
Verify Installation
python3 scripts/gws_doctor.py
# Checks: PATH, version, auth status, service connectivity---
Authentication
OAuth Setup (Interactive)
# Step 1: Create Google Cloud project and OAuth credentials
python3 scripts/auth_setup_guide.py --guide oauth
# Step 2: Run interactive auth setup (uses gcloud if available)
gws auth setup
# Step 3: Log in, requesting only the scopes you need
gws auth login -s drive,gmail,sheetsHeadless/CI
# Generate setup instructions
python3 scripts/auth_setup_guide.py --guide service-account
# Export credentials from an interactive machine, then point the CLI at them
gws auth export --unmasked > credentials.json
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.jsonEnvironment Variables
# Generate .env template
python3 scripts/auth_setup_guide.py --generate-env| Variable | Purpose |
|---|---|
GOOGLE_WORKSPACE_CLI_CLIENT_ID | OAuth client ID |
GOOGLE_WORKSPACE_CLI_CLIENT_SECRET | OAuth client secret |
GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE | Path to exported credentials JSON |
GOOGLE_WORKSPACE_CLI_TOKEN | Pre-obtained OAuth token |
GOOGLE_WORKSPACE_CLI_CONFIG_DIR | Override default config location |
GOOGLE_WORKSPACE_CLI_LOG | Enable debug logging |
Validate Authentication
python3 scripts/auth_setup_guide.py --validate --json
# Tests each service endpoint---
Workflow 1: Gmail Automation
Goal: Automate email operations — send, search, label, and filter management.
Send, Reply, Forward (helper commands)
# Send a new email
gws gmail +send --to "client@example.com" \
--subject "Proposal" --body "Please find attached..."
# Reply to a message (auto-threading); check exact flags with: gws gmail +reply --help
gws gmail +reply ...
# Forward a message; check exact flags with: gws gmail +forward --help
gws gmail +forward ...
# Unread inbox summary
gws gmail +triageSearch and Inspect (discovery commands)
Discovery commands follow gws <service> <resource> <method> and take request parameters as JSON via --params (query/path params) and --json (request body). Inspect any method's exact schema first:
# What does messages.list accept? (verify)
gws schema gmail.users.messages.list
# Search emails (verify against the schema above)
gws gmail users messages list --params '{"userId": "me", "q": "from:client@example.com after:2025/01/01"}' \
| python3 scripts/output_analyzer.py --count
# List labels (verify)
gws gmail users labels list --params '{"userId": "me"}'Bulk Operations
Use --dry-run first, and --page-all to paginate (one JSON line per page):
# Preview, then archive read emails older than 30 days (verify method schema first)
gws gmail users messages list --params '{"userId": "me", "q": "is:read older_than:30d"}' --page-all \
| python3 scripts/output_analyzer.py --select "id" --format json
# Then feed ids to gmail users messages modify (see: gws schema gmail.users.messages.modify)---
Workflow 2: Drive & Sheets
Goal: Manage files, create spreadsheets, configure sharing, and export data.
File Operations
# List files
gws drive files list --params '{"pageSize": 50}' \
| python3 scripts/output_analyzer.py --select "name,mimeType,size" --format table
# Upload a file (helper)
gws drive +upload ./report.pdf --name "Q1 Report"
# Create a Google Sheet
gws sheets spreadsheets create --json '{"properties": {"title": "Budget 2026"}}'
# Download/export — inspect the method first (verify)
gws schema drive.files.exportSharing (verify schemas first)
# Inspect the permissions API surface
gws schema drive.permissions.create
# Share with user (verify against schema)
gws drive permissions create --params '{"fileId": "<FILE_ID>"}' \
--json '{"type": "user", "role": "writer", "emailAddress": "colleague@company.com"}'
# List who has access (verify)
gws drive permissions list --params '{"fileId": "<FILE_ID>"}'Sheets Data
# Read values (helper); check exact flags with: gws sheets +read --help
gws sheets +read ...
# Append a row (helper); check exact flags with: gws sheets +append --help
gws sheets +append ...
# Or use discovery methods (verify):
gws schema sheets.spreadsheets.values.update
gws sheets spreadsheets values get --params '{"spreadsheetId": "<SHEET_ID>", "range": "Sheet1!A1:D10"}'---
Workflow 3: Calendar & Meetings
Goal: Schedule events, find available times, and generate standup reports.
Event Management
# Create an event (helper); check exact flags with: gws calendar +insert --help
gws calendar +insert ...
# Upcoming events (helper, timezone-aware)
gws calendar +agenda
# Or via discovery (verify):
gws schema calendar.events.insert
gws calendar events list --params '{"calendarId": "primary", "maxResults": 10}'Find Available Time
# Free/busy via the Calendar API (verify schema first)
gws schema calendar.freebusy.query
gws calendar freebusy query --json '{"timeMin": "...", "timeMax": "...", "items": [{"id": "alice@co.com"}]}'Standup Report (workflow helpers)
# Today's meetings + tasks
gws workflow +standup-report \
| python3 scripts/output_analyzer.py --format table
# Next meeting prep; check exact flags with: gws workflow +meeting-prep --help
gws workflow +meeting-prep---
Workflow 4: Security Audit
Goal: Audit Google Workspace security configuration and generate remediation commands.
Run Full Audit
# Full audit across all services
python3 scripts/workspace_audit.py --json
# Audit specific services
python3 scripts/workspace_audit.py --services gmail,drive,calendar
# Demo mode (no gws required)
python3 scripts/workspace_audit.py --demoAudit Checks
| Area | Check | Risk |
|---|---|---|
| Drive | External sharing enabled | Data exfiltration |
| Gmail | Auto-forwarding rules | Data exfiltration |
| Gmail | DMARC/SPF/DKIM records | Email spoofing |
| Calendar | Default sharing visibility | Information leak |
| OAuth | Third-party app grants | Unauthorized access |
| Admin | Super admin count | Privilege escalation |
| Admin | 2-Step verification enforcement | Account takeover |
Review and Remediate
# Review findings
python3 scripts/workspace_audit.py --json | python3 scripts/output_analyzer.py \
--filter "status=FAIL" --select "area,check,remediation"
# Execute remediation (example: check current Drive settings first; verify)
gws drive about get --params '{"fields": "*"}'
# Follow remediation commands from audit output (verify each against gws --help)---
Python Tools
| Script | Purpose | Usage |
|---|---|---|
gws_doctor.py | Pre-flight diagnostics | python3 scripts/gws_doctor.py [--json] [--services gmail,drive] |
auth_setup_guide.py | Guided auth setup | python3 scripts/auth_setup_guide.py --guide oauth |
gws_recipe_runner.py | Recipe catalog & runner | python3 scripts/gws_recipe_runner.py --list [--persona pm] |
workspace_audit.py | Security/config audit | python3 scripts/workspace_audit.py [--json] [--demo] |
output_analyzer.py | JSON/NDJSON analysis | `gws ... --json \ |
All scripts are stdlib-only, support --json output, and include demo mode with embedded sample data.
---
Best Practices
Security
1. Use OAuth with minimal scopes — request only what each workflow needs 2. Store tokens in the system keyring, never in plain text files 3. Rotate service account keys every 90 days 4. Audit third-party OAuth app grants quarterly 5. Use --dry-run before bulk destructive operations
Automation
1. All gws output is structured JSON — pipe it through output_analyzer.py for filtering and aggregation 2. Use gws workflow +* helpers for multi-step operations instead of chaining raw commands 3. Use the local recipe catalog (gws_recipe_runner.py) as command templates, then verify each against gws --help 4. --page-all emits one JSON line per page (NDJSON) for streaming large result sets 5. Use --dry-run to preview any request before executing it
Performance
1. Request only needed fields via the API's fields parameter in --params (reduces payload size) 2. Use pageSize in --params to cap results when browsing 3. Use --page-all only when you need complete datasets; tune with --page-limit / --page-delay 4. Prefer + helpers (single optimized calls) over hand-chained API calls 5. Cache frequently accessed data (e.g., label IDs, folder IDs) in variables
---
Limitations
| Constraint | Impact |
|---|---|
| OAuth tokens expire after 1 hour | Re-auth needed for long-running scripts |
| API rate limits (per-user, per-service) | Bulk operations may hit 429 errors |
| Scope requirements vary by service | Must request correct scopes during auth |
| Pre-v1.0 CLI status | Breaking changes possible between releases |
| Google Cloud project required | Free, but requires setup in Cloud Console |
| Admin API needs admin privileges | Some audit checks require Workspace Admin role |
Required Scopes by Service
# List scopes for specific services
python3 scripts/auth_setup_guide.py --scopes gmail,drive,calendar,sheets| Service | Key Scopes |
|---|---|
| Gmail | gmail.modify, gmail.send, gmail.labels |
| Drive | drive.file, drive.metadata.readonly |
| Sheets | spreadsheets |
| Calendar | calendar, calendar.events |
| Admin | admin.directory.user.readonly, admin.directory.group |
| Tasks | tasks |
Google Workspace CLI Persona Profiles
10 role-based bundles that scope recipes and commands to your daily workflow.
These are command templates, not verified invocations. ThegwsCLI (github.com/googleworkspace/cli) generates its command surface dynamically from Google's Discovery Service and is pre-v1.0. Before running any command below, verify its exact syntax withgws --help,gws <service> --help, orgws schema <service>.<resource>.<method>. Verified upstream patterns: discovery commands aregws <service> <resource> <method> --params '{...}' --json '{...}'; helpers are+-prefixed (gws gmail +send,gws calendar +agenda,gws workflow +standup-report).
---
1. Executive Assistant
Description: Managing schedules, emails, and communications for executives.
Top Commands:
python3 scripts/gws_recipe_runner.py --describe morning-briefing— Start the day with schedule + inbox overviewgws calendar freebusy query ...(verify:gws schema calendar.freebusy.query) — Find available slots for meetingsgws workflow +meeting-prep— Prepare for the next meetinggws gmail users.messages send me— Send emails on behalfpython3 scripts/gws_recipe_runner.py --describe eod-wrap— End of day summary
Recommended Recipes: morning-briefing, today-schedule, find-time, send-email, reply-to-thread, meeting-prep, eod-wrap, quick-event, inbox-zero, standup-report
Daily Workflow: 1. Run morning-briefing at 8:00 AM 2. Process inbox with inbox-zero 3. Schedule meetings with find-time + create-event 4. Prep for meetings with meeting-prep 5. Close day with eod-wrap
---
2. Project Manager
Description: Tracking tasks, meetings, and project deliverables.
Top Commands:
gws workflow +standup-report— Generate standup updatesgws calendar freebusy query ...(verify:gws schema calendar.freebusy.query) — Schedule sprint ceremoniesgws tasks tasks insert— Create and assign tasksgws sheets spreadsheets.values get— Read project trackerspython3 scripts/gws_recipe_runner.py --describe project-status— Aggregate project status
Recommended Recipes: standup-report, create-event, find-time, task-create, task-progress, project-status, weekly-summary, share-folder, sheet-read, morning-briefing
Daily Workflow: 1. Run standup-report before standup 2. Update project tracker via sheet-write 3. Create action items with task-create 4. Run weekly-summary on Fridays 5. Share updates via chat-message
---
3. HR
Description: Managing people, onboarding, and team communications.
Top Commands:
gws admin users list— List all domain usersgws admin users get <email>— Look up employee detailsgws docs documents create— Create onboarding docsgws drive permissions create— Share folders with new hiresgws people people.connections list— Export contact directory
Recommended Recipes: list-users, user-info, send-email, create-event, create-doc, share-folder, chat-message, list-groups, export-contacts, today-schedule
Daily Workflow: 1. Check new hire onboarding queue 2. Create welcome docs with create-doc 3. Set up 1:1s with create-event 4. Share team folders with share-folder 5. Send announcements via send-email
---
4. Sales
Description: Managing client communications, proposals, and scheduling.
Top Commands:
gws gmail users.messages send me— Send proposals and follow-upsgws gmail users.messages list me --query— Search client conversationsgws calendar freebusy query ...(verify:gws schema calendar.freebusy.query) — Schedule client meetingsgws docs documents create— Create proposalsgws sheets spreadsheets.values update— Update pipeline tracker
Recommended Recipes: send-email, search-emails, create-event, find-time, create-doc, share-file, sheet-read, sheet-write, export-file, morning-briefing
Daily Workflow: 1. Run morning-briefing for meeting overview 2. Search emails for client updates 3. Update pipeline in Sheets 4. Send proposals via send-email + share-file 5. Schedule follow-ups with create-event
---
5. IT Admin
Description: Managing Workspace configuration, security, and user administration.
Top Commands:
gws admin users list --domain— Audit user accountsgws admin activities list login— Monitor login activitygws admin groups list— Manage groupspython3 workspace_audit.py— Run security auditgws drive files list --orderBy "quotaBytesUsed desc"— Find storage hogs
Recommended Recipes: list-users, list-groups, user-info, audit-logins, drive-activity, find-large-files, cleanup-trash, label-manager, filter-setup, share-folder
Daily Workflow: 1. Check audit-logins for suspicious activity 2. Run workspace_audit.py weekly 3. Process user provisioning requests 4. Monitor storage with find-large-files 5. Review group memberships
---
6. Developer
Description: Using Workspace APIs for automation and data integration.
Top Commands:
gws sheets spreadsheets.values get— Read config/data from Sheetsgws sheets spreadsheets.values update— Write results to Sheetsgws drive files create --upload— Upload build artifactsgws chat spaces.messages create— Post deployment notificationsgws tasks tasks insert— Create tasks from CI/CD
Recommended Recipes: sheet-read, sheet-write, sheet-append, upload-file, create-doc, chat-message, task-create, list-files, export-file, send-email
Daily Workflow: 1. Read config from Sheets API 2. Run automated reports to Sheets 3. Post updates to Chat spaces 4. Upload artifacts to Drive 5. Create tasks for bugs/issues
---
7. Marketing
Description: Managing campaigns, content creation, and team coordination.
Top Commands:
gws docs documents create— Draft blog posts and briefsgws drive files create --upload— Upload creative assetsgws sheets spreadsheets.values append— Log campaign metricsgws gmail users.messages send me— Send campaign emailsgws chat spaces.messages create— Coordinate with team
Recommended Recipes: send-email, create-doc, share-file, upload-file, create-sheet, sheet-write, chat-message, create-event, email-stats, weekly-summary
Daily Workflow: 1. Check email-stats for campaign performance 2. Create content in Docs 3. Upload assets to shared Drive folders 4. Update metrics in Sheets 5. Coordinate launches via Chat
---
8. Finance
Description: Managing spreadsheets, financial reports, and data analysis.
Top Commands:
gws sheets spreadsheets.values get— Pull financial datagws sheets spreadsheets.values update— Update forecastsgws sheets spreadsheets create— Create new reportsgws drive files export— Export reports as PDFgws drive permissions create— Share with auditors
Recommended Recipes: sheet-read, sheet-write, sheet-append, create-sheet, export-file, share-file, send-email, find-large-files, drive-activity, weekly-summary
Daily Workflow: 1. Pull latest data into Sheets 2. Update financial models 3. Generate PDF reports with export-file 4. Share reports with stakeholders 5. Weekly summary for leadership
---
9. Legal
Description: Managing documents, contracts, and compliance.
Top Commands:
gws docs documents create— Draft contractsgws drive files export— Export final versions as PDFgws drive permissions create— Manage document accessgws gmail users.messages list me --query— Search for compliance emailsgws admin activities list— Audit trail for compliance
Recommended Recipes: create-doc, share-file, export-file, search-emails, send-email, upload-file, list-files, drive-activity, audit-logins, find-large-files
Daily Workflow: 1. Draft and review documents 2. Search email for contract references 3. Export finalized docs as PDF 4. Set precise sharing permissions 5. Maintain audit trail
---
10. Customer Support
Description: Managing customer communications and ticket tracking.
Top Commands:
gws gmail users.messages list me --query— Search customer emailsgws gmail users.messages reply me— Reply to ticketsgws gmail users.labels create— Organize by ticket statusgws tasks tasks insert— Create follow-up tasksgws chat spaces.messages create— Escalate to team
Recommended Recipes: search-emails, send-email, reply-to-thread, label-manager, filter-setup, task-create, chat-message, unread-digest, inbox-zero, morning-briefing
Daily Workflow: 1. Run morning-briefing for ticket overview 2. Process inbox with label-based triage 3. Reply to open tickets 4. Escalate via Chat for urgent issues 5. Create follow-up tasks for pending items
{
"_comment": "Google Workspace CLI automation config template. Copy and customize for your environment.",
"auth": {
"method": "oauth",
"client_id": "",
"client_secret": "",
"token_path": "~/.config/gws/token.json",
"service_account_key": "",
"delegated_user": ""
},
"defaults": {
"output_format": "json",
"pagination_limit": 100,
"timeout_ms": 30000,
"log_level": "warn"
},
"persona": "developer",
"scopes": [
"gmail.modify",
"gmail.send",
"drive.file",
"drive.metadata.readonly",
"spreadsheets",
"calendar",
"calendar.events",
"tasks"
],
"scheduled_tasks": [
{
"name": "morning-briefing",
"recipe": "morning-briefing",
"schedule": "0 8 * * 1-5",
"output": "~/workspace-reports/morning-{date}.json"
},
{
"name": "eod-wrap",
"recipe": "eod-wrap",
"schedule": "0 17 * * 1-5",
"output": "~/workspace-reports/eod-{date}.json"
},
{
"name": "weekly-summary",
"recipe": "weekly-summary",
"schedule": "0 9 * * 5",
"output": "~/workspace-reports/weekly-{date}.json"
},
{
"name": "security-audit",
"command": "python3 scripts/workspace_audit.py --json",
"schedule": "0 10 * * 1",
"output": "~/workspace-reports/audit-{date}.json"
}
],
"aliases": {
"inbox": "gws gmail users.messages list me --query 'is:inbox' --limit 20 --json",
"unread": "gws gmail users.messages list me --query 'is:unread' --limit 20 --json",
"files": "gws drive files list --limit 20 --json",
"events": "gws calendar events list primary --timeMin $(date -u +%Y-%m-%dT%H:%M:%SZ) --maxResults 10 --json",
"tasks": "gws tasks tasks list @default --json"
}
}
Google Workspace CLI Command Reference
Working reference for the gws CLI (github.com/googleworkspace/cli) covering services, helper commands, global flags, and environment variables.
Verify against your installed version.gwsbuilds its command surface dynamically from Google's Discovery Service and is pre-v1.0. Treat command syntax in this document as a template: confirm exact syntax withgws --help,gws <service> --help, orgws schema <service>.<resource>.<method>before scripting. Verified-from-upstream facts: install vianpm install -g @googleworkspace/cli; discovery commands followgws <service> <resource> <method>with--params(query/path params as JSON) and--json(request body); helpers are+-prefixed (e.g.gws gmail +send); all output is structured JSON.
---
Global Flags (verified)
| Flag | Description |
|---|---|
--params <json> | Query/path parameters as JSON for discovery commands |
--json <json> | Request body as JSON for discovery commands |
--dry-run | Preview the request without executing |
--page-all | Auto-paginate; one JSON line per page (NDJSON) |
--page-limit <n> | Max pages to fetch |
--page-delay <ms> | Delay between pages |
--sanitize | Scan responses via a Model Armor template |
---
Environment Variables (verified)
| Variable | Description |
|---|---|
GOOGLE_WORKSPACE_CLI_CLIENT_ID | OAuth client ID |
GOOGLE_WORKSPACE_CLI_CLIENT_SECRET | OAuth client secret |
GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE | Path to credentials JSON (from gws auth export) |
GOOGLE_WORKSPACE_CLI_TOKEN | Pre-obtained OAuth token |
GOOGLE_WORKSPACE_CLI_CONFIG_DIR | Override default config location |
GOOGLE_WORKSPACE_CLI_LOG | Enable debug logging |
---
Services
Gmail
gws gmail users.messages list me --query "<query>" --json
gws gmail users.messages get me <messageId> --json
gws gmail users.messages send me --to <email> --subject <subj> --body <body>
gws gmail users.messages reply me --thread-id <id> --body <body>
gws gmail users.messages forward me --message-id <id> --to <email>
gws gmail users.messages modify me <id> --addLabelIds <label> --removeLabelIds INBOX
gws gmail users.messages trash me <id>
gws gmail users.labels list me --json
gws gmail users.labels create me --name <name>
gws gmail users.settings.filters create me --criteria <json> --action <json>
gws gmail users.settings.forwardingAddresses list me --json
gws gmail users getProfile me --jsonGoogle Drive
gws drive files list --json --limit <n>
gws drive files list --query "name contains '<term>'" --json
gws drive files list --parents <folderId> --json
gws drive files get <fileId> --json
gws drive files create --name <name> --upload <path> --parents <folderId>
gws drive files create --name <name> --mimeType application/vnd.google-apps.folder
gws drive files update <fileId> --upload <path>
gws drive files delete <fileId>
gws drive files export <fileId> --mime <mimeType> --output <path>
gws drive files copy <fileId> --name <newName>
gws drive permissions list <fileId> --json
gws drive permissions create <fileId> --type <user|group|domain> --role <reader|writer|owner> --emailAddress <email>
gws drive permissions delete <fileId> <permissionId>
gws drive about get --json
gws drive files emptyTrashGoogle Sheets
gws sheets spreadsheets create --title <title> --json
gws sheets spreadsheets get <spreadsheetId> --json
gws sheets spreadsheets.values get <spreadsheetId> --range <range> --json
gws sheets spreadsheets.values update <spreadsheetId> --range <range> --values <json>
gws sheets spreadsheets.values append <spreadsheetId> --range <range> --values <json>
gws sheets spreadsheets.values clear <spreadsheetId> --range <range>
gws sheets spreadsheets.values batchGet <spreadsheetId> --ranges <range1>,<range2> --json
gws sheets spreadsheets.values batchUpdate <spreadsheetId> --data <json>Google Calendar
gws calendar calendarList list --json
gws calendar calendarList get <calendarId> --json
gws calendar events list <calendarId> --timeMin <datetime> --timeMax <datetime> --json
gws calendar events get <calendarId> <eventId> --json
gws calendar events insert <calendarId> --summary <title> --start <datetime> --end <datetime> --attendees <emails>
gws calendar events update <calendarId> <eventId> --summary <title>
gws calendar events patch <calendarId> <eventId> --start <datetime> --end <datetime>
gws calendar events delete <calendarId> <eventId>
gws calendar freebusy query --timeMin <start> --timeMax <end> --items <calendarId1>,<calendarId2> --jsonGoogle Docs
gws docs documents create --title <title> --json
gws docs documents get <documentId> --json
gws docs documents batchUpdate <documentId> --requests <json>Google Slides
gws slides presentations create --title <title> --json
gws slides presentations get <presentationId> --json
gws slides presentations.pages get <presentationId> <pageId> --json
gws slides presentations.pages getThumbnail <presentationId> <pageId> --jsonGoogle Chat
gws chat spaces list --json
gws chat spaces get <spaceName> --json
gws chat spaces.messages create <spaceName> --text <message>
gws chat spaces.messages list <spaceName> --json
gws chat spaces.messages get <messageName> --json
gws chat spaces.members list <spaceName> --jsonGoogle Tasks
gws tasks tasklists list --json
gws tasks tasklists get <tasklistId> --json
gws tasks tasklists insert --title <title> --json
gws tasks tasks list <tasklistId> --json
gws tasks tasks get <tasklistId> <taskId> --json
gws tasks tasks insert <tasklistId> --title <title> --due <datetime>
gws tasks tasks update <tasklistId> <taskId> --status completed
gws tasks tasks delete <tasklistId> <taskId>Admin SDK (Directory)
gws admin users list --domain <domain> --json
gws admin users get <email> --json
gws admin users insert --primaryEmail <email> --name.givenName <first> --name.familyName <last>
gws admin users update <email> --suspended true
gws admin groups list --domain <domain> --json
gws admin groups get <email> --json
gws admin groups insert --email <email> --name <name>
gws admin groups.members list <groupEmail> --json
gws admin groups.members insert <groupEmail> --email <memberEmail> --role MEMBER
gws admin orgunits list --customerId my_customer --jsonGoogle Groups
gws groups groups list --domain <domain> --json
gws groups groups get <email> --json
gws groups memberships list <groupEmail> --jsonGoogle People (Contacts)
gws people people.connections list me --personFields names,emailAddresses --json
gws people people get <resourceName> --personFields names,emailAddresses,phoneNumbers --json
gws people people searchContacts --query <term> --readMask names,emailAddresses --jsonGoogle Meet
gws meet spaces create --json
gws meet spaces get <spaceName> --json
gws meet conferenceRecords list --jsonGoogle Classroom
gws classroom courses list --json
gws classroom courses get <courseId> --json
gws classroom courses.courseWork list <courseId> --json
gws classroom courses.students list <courseId> --jsonGoogle Forms
gws forms forms get <formId> --json
gws forms forms.responses list <formId> --jsonGoogle Keep
gws keep notes list --json
gws keep notes get <noteId> --jsonGoogle Sites
gws sites sites list --json
gws sites sites get <siteId> --jsonGoogle Vault
gws vault matters list --json
gws vault matters get <matterId> --json
gws vault matters.holds list <matterId> --jsonAdmin Reports / Activities
gws admin activities list <applicationName> --json
gws admin activities list login --json
gws admin activities list drive --json
gws admin activities list admin --json---
Helper Commands (verified +-prefixed surface)
Helpers are prefixed with + so they never collide with Discovery-generated method names. Check each helper's flags with gws <service> +<helper> --help.
| Service | Helper | Description |
|---|---|---|
| gmail | +send | Send an email (gws gmail +send --to a@b.com --subject "Hi" --body "Hello") |
| gmail | +reply | Reply to a message (auto-threading) |
| gmail | +reply-all | Reply-all to a message |
| gmail | +forward | Forward a message |
| gmail | +triage | Unread inbox summary |
| gmail | +watch | Watch for new emails as NDJSON |
| sheets | +append | Append a row |
| sheets | +read | Read values |
| docs | +write | Append text |
| chat | +send | Send a space message |
| drive | +upload | Upload a file (gws drive +upload ./report.pdf --name "Q1 Report") |
| calendar | +insert | Create an event |
| calendar | +agenda | Show upcoming events (timezone-aware) |
| script | +push | Replace all Apps Script files |
| workflow | +standup-report | Today's meetings + tasks |
| workflow | +meeting-prep | Next meeting prep |
| workflow | +email-to-task | Convert Gmail to Tasks |
| workflow | +weekly-digest | Weekly summary |
| workflow | +file-announce | Announce Drive file in Chat |
| events | +subscribe | Subscribe to Workspace events |
| events | +renew | Renew event subscriptions |
| modelarmor | +sanitize-prompt | Sanitize user prompt |
| modelarmor | +sanitize-response | Sanitize model response |
| modelarmor | +create-template | Create Model Armor template |
---
Schema Introspection
# View the API schema for any service method
gws schema gmail.users.messages.list
gws schema drive.files.create
gws schema calendar.events.insert
# Discover available schema/introspection options for your version
gws schema --help---
Authentication Commands (verified)
gws auth setup # Interactive OAuth setup (uses gcloud if available)
gws auth login # Log in / re-consent
gws auth login -s drive,gmail,sheets # Request specific scopes
gws auth export --unmasked # Export credentials for headless reuse
gws auth --help # Discover further auth subcommands in your version---
Recipe Commands (local catalog, not built into gws)
Recipes ship with this skill as a local catalog of command templates:
python3 scripts/gws_recipe_runner.py --list # List all 43 recipe templates
python3 scripts/gws_recipe_runner.py --search "email" # Search by keyword
python3 scripts/gws_recipe_runner.py --describe standup-report # Show recipe details
python3 scripts/gws_recipe_runner.py --run <name> --dry-run # Preview recipe commands---
Persona Commands (local catalog, not built into gws)
python3 scripts/gws_recipe_runner.py --personas # List all 10 personas
python3 scripts/gws_recipe_runner.py --list --persona pm # Recipes for a personaGoogle Workspace CLI Recipes Cookbook
Catalog of 43 recipe command templates (a local catalog shipped with this skill, not built into the gws CLI) organized by category, with command sequences and persona mapping.
These are command templates, not verified invocations. ThegwsCLI (github.com/googleworkspace/cli) generates its command surface dynamically from Google's Discovery Service and is pre-v1.0. Before running any command below, verify its exact syntax withgws --help,gws <service> --help, orgws schema <service>.<resource>.<method>. Verified upstream patterns: discovery commands aregws <service> <resource> <method> --params '{...}' --json '{...}'; helpers are+-prefixed (gws gmail +send,gws calendar +agenda,gws workflow +standup-report).
---
Recipe Categories
| Category | Count | Description |
|---|---|---|
| 8 | Gmail operations — send, search, label, filter | |
| Files | 7 | Drive file management — upload, share, export |
| Calendar | 6 | Events, scheduling, meeting prep |
| Reporting | 5 | Activity summaries and analytics |
| Collaboration | 5 | Chat, Docs, Tasks teamwork |
| Data | 4 | Sheets read/write and contacts |
| Admin | 4 | User and group management |
| Cross-Service | 4 | Multi-service workflows |
---
Email Recipes (8)
send-email
Send an email with optional attachments.
gws gmail users.messages send me --to "recipient@example.com" \
--subject "Subject" --body "Body text" [--attachment file.pdf]reply-to-thread
Reply to an existing email thread.
gws gmail users.messages reply me --thread-id <THREAD_ID> --body "Reply text"forward-email
Forward an email to another recipient.
gws gmail users.messages forward me --message-id <MSG_ID> --to "forward@example.com"search-emails
Search emails using Gmail query syntax.
gws gmail users.messages list me --query "from:sender@example.com after:2025/01/01" --jsonQuery examples: is:unread, has:attachment, label:important, newer_than:7d
archive-old
Archive read emails older than N days.
gws gmail users.messages list me --query "is:read older_than:30d" --json
# Extract IDs, then batch modify to remove INBOX labellabel-manager
Create and organize Gmail labels.
gws gmail users.labels list me --json
gws gmail users.labels create me --name "Projects/Alpha"filter-setup
Create auto-labeling filters.
gws gmail users.settings.filters create me \
--criteria '{"from":"notifications@service.com"}' \
--action '{"addLabelIds":["Label_123"],"removeLabelIds":["INBOX"]}'unread-digest
Get digest of unread emails.
gws gmail users.messages list me --query "is:unread" --limit 20 --json---
Files Recipes (7)
upload-file
Upload a file to Google Drive.
gws drive files create --name "Report Q1" --upload report.pdf --parents <FOLDER_ID>create-sheet
Create a new Google Spreadsheet.
gws sheets spreadsheets create --title "Budget 2026" --jsonshare-file
Share a Drive file with a user or domain.
gws drive permissions create <FILE_ID> --type user --role writer --emailAddress "user@example.com"export-file
Export a Google Doc/Sheet as PDF.
gws drive files export <FILE_ID> --mime "application/pdf" --output report.pdflist-files
List files in a Drive folder.
gws drive files list --parents <FOLDER_ID> --jsonfind-large-files
Find the largest files in Drive.
gws drive files list --orderBy "quotaBytesUsed desc" --limit 20 --jsoncleanup-trash
Empty Drive trash.
gws drive files emptyTrash---
Calendar Recipes (6)
create-event
Create a calendar event with attendees.
gws calendar events insert primary \
--summary "Sprint Planning" \
--start "2026-03-15T10:00:00" --end "2026-03-15T11:00:00" \
--attendees "team@company.com" --location "Room A"quick-event
Create event from natural language.
gws calendar +insert ... # see: gws calendar +insert --helpfind-time
Find available time slots for a meeting.
gws calendar freebusy query --json '{"timeMin": "...", "timeMax": "...", "items": [{"id": "alice@co.com"}]}' # verify: gws schema calendar.freebusy.querytoday-schedule
Show today's calendar events.
gws calendar events list primary \
--timeMin "$(date -u +%Y-%m-%dT00:00:00Z)" \
--timeMax "$(date -u +%Y-%m-%dT23:59:59Z)" --jsonmeeting-prep
Prepare for an upcoming meeting.
gws workflow +meeting-prepOutput: Agenda, attendee list, related Drive files, previous meeting notes.
reschedule
Move an event to a new time.
gws calendar events patch primary <EVENT_ID> \
--start "2026-03-16T14:00:00" --end "2026-03-16T15:00:00"---
Reporting Recipes (5)
standup-report
Generate daily standup from calendar and tasks.
gws workflow +standup-reportOutput: Yesterday's events, today's schedule, pending tasks, blockers.
weekly-summary
Summarize week's emails, events, and tasks.
gws workflow +weekly-digestdrive-activity
Report on Drive file activity.
gws drive activities list --jsonemail-stats
Email volume statistics for the past 7 days.
gws gmail users.messages list me --query "newer_than:7d" --json | python3 output_analyzer.py --counttask-progress
Report on task completion.
gws tasks tasks list <TASKLIST_ID> --json | python3 output_analyzer.py --group-by "status"---
Collaboration Recipes (5)
share-folder
Share a Drive folder with a team.
gws drive permissions create <FOLDER_ID> --type group --role writer --emailAddress "team@company.com"create-doc
Create a Google Doc with initial content.
gws docs documents create --title "Meeting Notes - March 15" --jsonchat-message
Send a message to a Google Chat space.
gws chat spaces.messages create <SPACE_NAME> --text "Deployment complete!"list-spaces
List Google Chat spaces.
gws chat spaces list --jsontask-create
Create a task in Google Tasks.
gws tasks tasks insert <TASKLIST_ID> --title "Review PR #42" --due "2026-03-16"---
Data Recipes (4)
sheet-read
Read data from a spreadsheet range.
gws sheets spreadsheets.values get <SHEET_ID> --range "Sheet1!A1:D10" --jsonsheet-write
Write data to a spreadsheet.
gws sheets spreadsheets.values update <SHEET_ID> --range "Sheet1!A1" \
--values '[["Name","Score"],["Alice",95],["Bob",87]]'sheet-append
Append rows to a spreadsheet.
gws sheets spreadsheets.values append <SHEET_ID> --range "Sheet1!A1" \
--values '[["Charlie",92]]'export-contacts
Export contacts list.
gws people people.connections list me --personFields names,emailAddresses --json---
Admin Recipes (4)
list-users
List all users in the Workspace domain.
gws admin users list --domain company.com --jsonPrerequisites: Admin SDK API enabled, admin.directory.user.readonly scope.
list-groups
List all groups in the domain.
gws admin groups list --domain company.com --jsonuser-info
Get detailed user information.
gws admin users get user@company.com --jsonaudit-logins
Audit recent login activity.
gws admin activities list login --json---
Cross-Service Recipes (4)
morning-briefing
Today's events + unread emails + pending tasks.
python3 scripts/gws_recipe_runner.py --run morning-briefing --dry-run # prints the command sequenceCombines: Calendar events, Gmail unread count, Tasks pending.
eod-wrap
End-of-day summary: completed, pending, tomorrow's schedule.
python3 scripts/gws_recipe_runner.py --run eod-wrap --dry-runproject-status
Aggregate project status from Drive, Sheets, Tasks.
python3 scripts/gws_recipe_runner.py --run project-status --dry-runinbox-zero
Process inbox to zero: label, archive, reply, or create task.
python3 scripts/gws_recipe_runner.py --run inbox-zero --dry-run---
Persona Mapping
| Persona | Top Recipes |
|---|---|
| Executive Assistant | morning-briefing, today-schedule, find-time, send-email, meeting-prep, eod-wrap |
| Project Manager | standup-report, create-event, find-time, task-create, project-status, weekly-summary |
| HR | list-users, user-info, send-email, create-event, create-doc, export-contacts |
| Sales | send-email, search-emails, create-event, find-time, create-doc, share-file |
| IT Admin | list-users, list-groups, audit-logins, drive-activity, find-large-files, cleanup-trash |
| Developer | sheet-read, sheet-write, upload-file, chat-message, task-create, send-email |
| Marketing | send-email, create-doc, share-file, upload-file, create-sheet, chat-message |
| Finance | sheet-read, sheet-write, sheet-append, create-sheet, export-file, share-file |
| Legal | create-doc, share-file, export-file, search-emails, upload-file, audit-logins |
| Customer Support | search-emails, send-email, reply-to-thread, label-manager, task-create, inbox-zero |
Google Workspace CLI Troubleshooting
Common errors, fixes, and platform-specific guidance for the gws CLI (github.com/googleworkspace/cli).
Verify against your installed version.gwsis pre-v1.0 and generates its command surface dynamically from Google's Discovery Service. Confirm anygwscommand below withgws --help/gws auth --helpbefore relying on it.
---
Installation Issues
gws not found on PATH
Error: command not found: gws
Fixes:
# Check if installed
npm list -g @googleworkspace/cli 2>/dev/null || echo "Not installed via npm"
which gws || echo "Not on PATH"
# Install via npm
npm install -g @googleworkspace/cli
# If npm global bin not on PATH
export PATH="$(npm config get prefix)/bin:$PATH"
# Add to ~/.zshrc or ~/.bashrc for persistencenpm permission errors
Error: EACCES: permission denied
Fixes:
# Option 1: Fix npm prefix (recommended)
mkdir -p ~/.npm-global
npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH
# Option 2: Use npx without installing
npx @googleworkspace/cli --versionCargo build failures
Error: error[E0463]: can't find crate
Fixes:
# Ensure Rust is up to date
rustup update stable
# Clean build
cargo clean && cargo install --git https://github.com/googleworkspace/cli --locked---
Authentication Errors
Token expired
Error: 401 Unauthorized: Token has been expired or revoked
Cause: OAuth tokens expire after 1 hour.
Fix:
gws auth login # Re-authenticate (see: gws auth --help)
# If that fails, redo setup:
gws auth setupInsufficient scopes
Error: 403 Forbidden: Request had insufficient authentication scopes
Fix:
# Re-auth requesting the scopes you need
gws auth login -s gmail,drive,calendar,sheets,tasks
# Or list required scopes for a service
python3 scripts/auth_setup_guide.py --scopes gmail,driveKeyring/keychain errors
Error: Failed to access keyring or SecKeychainFindGenericPassword failed
Fixes:
# macOS: Unlock keychain
security unlock-keychain ~/Library/Keychains/login.keychain-db
# Linux: Install keyring backend
sudo apt install gnome-keyring # or libsecret
# Fallback: Use file-based token storage
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json # from: gws auth export --unmasked
gws auth setupService account delegation errors
Error: 403: Not Authorized to access this resource/api
Fix: 1. Verify domain-wide delegation is enabled on the service account 2. Verify client ID is authorized in Admin Console > Security > API Controls 3. Verify scopes match exactly (no trailing slashes) 4. Verify the delegated user is a valid admin account
# Debug — confirm how your gws version configures service accounts first:
gws auth --help
echo $GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE # Should point to valid credentials JSON---
API Errors
Rate limit exceeded (429)
Error: 429 Too Many Requests: Rate Limit Exceeded
Cause: Google Workspace APIs have per-user, per-service rate limits.
Fix:
# Add delays between bulk operations
for id in $(cat file_ids.txt); do
gws drive files get $id --json >> results.json
sleep 0.5 # 500ms delay
done
# Use --limit to reduce result size
gws drive files list --limit 100 --json
# For admin operations, batch in groups of 50Rate limits by service:
| Service | Limit |
|---|---|
| Gmail | 250 quota units/second/user |
| Drive | 1,000 requests/100 seconds/user |
| Sheets | 60 read requests/minute/user |
| Calendar | 500 requests/100 seconds/user |
| Admin SDK | 2,400 requests/minute |
Permission denied (403)
Error: 403 Forbidden: The caller does not have permission
Causes and fixes: 1. Wrong scope — Re-auth with correct scopes 2. Not the file owner — Request access from the owner 3. Domain policy — Check Admin Console sharing policies 4. API not enabled — Enable the API in Google Cloud Console
# Check which APIs are enabled
gws schema --list
# Enable an API
# Go to: console.cloud.google.com > APIs & Services > LibraryNot found (404)
Error: 404 Not Found: File not found
Causes: 1. File was deleted or moved to trash 2. File ID is incorrect 3. No permission to see the file
# Check trash
gws drive files list --query "trashed=true and name='filename'" --json
# Verify file ID
gws drive files get <fileId> --json---
Output Parsing Issues
NDJSON vs JSON array
Problem: Output format varies between commands and versions.
# Force JSON array output
gws drive files list --json
# Force NDJSON output
gws drive files list --format ndjson
# Handle both in output_analyzer.py (automatic detection)
gws drive files list --json | python3 scripts/output_analyzer.py --countPagination
Problem: Only partial results returned.
# Fetch all pages
gws drive files list --page-all --json
# Or set a high limit
gws drive files list --limit 1000 --json
# Check if more pages exist (look for nextPageToken in output)
gws drive files list --params '{"pageSize": 100}' | grep nextPageTokenEmpty response
Problem: Command returns empty or {}.
# Check auth (see: gws auth --help for your version's status command)
gws auth login
# Enable debug logging
GOOGLE_WORKSPACE_CLI_LOG=debug gws drive files list --params '{"pageSize": 1}'
# Check if the service is accessible (verify: gws schema drive.about.get)
gws drive about get --params '{"fields": "*"}'---
Platform-Specific Issues
macOS
Keychain access prompts:
# Allow gws to access keychain without repeated prompts
# In Keychain Access.app, find "gws" entries and set "Allow all applications"
# Or use file-based storage
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json # from: gws auth export --unmaskedBrowser not opening for OAuth:
# If default browser doesn't open
gws auth setup --no-browser
# Copy the URL manually and paste in browserLinux
Headless OAuth (no browser):
# Use out-of-band flow
gws auth setup --no-browser
# Prints a URL — open on another machine, paste code back
# Or export credentials from an interactive machine (documented headless flow)
gws auth export --unmasked > credentials.json
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.jsonMissing keyring backend:
# Install a keyring backend
sudo apt install gnome-keyring libsecret-1-dev
# Or use file-based storage
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json # from: gws auth export --unmaskedWindows
PATH issues:
# Add npm global bin to PATH
$env:PATH += ";$(npm config get prefix)\bin"
# Or use npx
npx @googleworkspace/cli --versionPowerShell quoting:
# Use single quotes for JSON arguments
gws gmail users.settings.filters create me `
--criteria '{"from":"test@example.com"}' `
--action '{"addLabelIds":["Label_1"]}'---
Getting Help
# General help
gws --help
gws <service> --help
gws <service> <resource> --help
# API schema for a method
gws schema gmail.users.messages.send
# Version info
gws --version
# Debug mode
gws --verbose <command>
# Report issues
# https://github.com/googleworkspace/cli/issues#!/usr/bin/env python3
"""
Google Workspace CLI Auth Setup Guide — Guided authentication configuration.
Prints step-by-step instructions for OAuth and service account setup,
generates .env templates, lists required scopes, and validates auth.
Usage:
python3 auth_setup_guide.py --guide oauth
python3 auth_setup_guide.py --guide service-account
python3 auth_setup_guide.py --scopes gmail,drive,calendar
python3 auth_setup_guide.py --generate-env
python3 auth_setup_guide.py --validate [--json]
python3 auth_setup_guide.py --check [--json]
"""
import argparse
import json
import shutil
import subprocess
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Dict
SERVICE_SCOPES: Dict[str, List[str]] = {
"gmail": [
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.labels",
"https://www.googleapis.com/auth/gmail.settings.basic",
],
"drive": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.metadata.readonly",
],
"sheets": [
"https://www.googleapis.com/auth/spreadsheets",
],
"calendar": [
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.events",
],
"tasks": [
"https://www.googleapis.com/auth/tasks",
],
"chat": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.messages",
],
"docs": [
"https://www.googleapis.com/auth/documents",
],
"admin": [
"https://www.googleapis.com/auth/admin.directory.user.readonly",
"https://www.googleapis.com/auth/admin.directory.group",
"https://www.googleapis.com/auth/admin.directory.orgunit.readonly",
],
"meet": [
"https://www.googleapis.com/auth/meetings.space.created",
],
}
OAUTH_GUIDE = """
=== Google Workspace CLI: OAuth Setup Guide ===
Step 1: Create a Google Cloud Project
1. Go to https://console.cloud.google.com/
2. Click "Select a project" -> "New Project"
3. Name it (e.g., "gws-cli-access") and click Create
4. Note the Project ID
Step 2: Enable Required APIs
1. Go to APIs & Services -> Library
2. Search and enable each API you need:
- Gmail API
- Google Drive API
- Google Sheets API
- Google Calendar API
- Tasks API
- Admin SDK API (for admin operations)
Step 3: Configure OAuth Consent Screen
1. Go to APIs & Services -> OAuth consent screen
2. Select "Internal" (for Workspace) or "External" (for personal)
3. Fill in app name, support email
4. Add scopes for the services you need
5. Save and continue
Step 4: Create OAuth Credentials
1. Go to APIs & Services -> Credentials
2. Click "Create Credentials" -> "OAuth client ID"
3. Application type: "Desktop app"
4. Name it "gws-cli"
5. Download the JSON file
Step 5: Configure gws CLI
1. Set environment variables:
export GOOGLE_WORKSPACE_CLI_CLIENT_ID=<your-client-id>
export GOOGLE_WORKSPACE_CLI_CLIENT_SECRET=<your-client-secret>
2. Or point the CLI at a credentials JSON file:
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json
Step 6: Authenticate
gws auth setup # interactive setup (uses gcloud if available)
gws auth login -s gmail,drive,calendar # request only needed scopes
Step 7: Verify (check exact syntax with: gws gmail --help)
gws gmail users getProfile --params '{"userId": "me"}'
"""
SERVICE_ACCOUNT_GUIDE = """
=== Google Workspace CLI: Service Account Setup Guide ===
Step 1: Create a Google Cloud Project
(Same as OAuth Step 1)
Step 2: Create a Service Account
1. Go to IAM & Admin -> Service Accounts
2. Click "Create Service Account"
3. Name: "gws-cli-service"
4. Grant roles as needed (no role needed for Workspace API access)
5. Click "Done"
Step 3: Create Key
1. Click on the service account
2. Go to "Keys" tab
3. Add Key -> Create new key -> JSON
4. Download and store securely
Step 4: Enable Domain-Wide Delegation
1. On the service account page, click "Edit"
2. Check "Enable Google Workspace domain-wide delegation"
3. Save
4. Note the Client ID (numeric)
Step 5: Authorize in Google Admin
1. Go to admin.google.com
2. Security -> API Controls -> Domain-wide Delegation
3. Add new:
- Client ID: <numeric client ID from Step 4>
- Scopes: (paste required scopes)
4. Authorize
Step 6: Configure gws CLI for headless use
NOTE: The documented headless flow for gws is to export credentials from an
interactive machine and reuse them (see: gws auth --help):
gws auth export --unmasked > credentials.json
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json
Or supply a pre-obtained OAuth token:
export GOOGLE_WORKSPACE_CLI_TOKEN=<token>
Verify service-account/domain-wide-delegation support against your installed
version (gws auth --help) before relying on it.
Step 7: Verify (check exact syntax with: gws gmail --help)
gws gmail users getProfile --params '{"userId": "me"}'
"""
ENV_TEMPLATE = """# Google Workspace CLI Configuration
# Copy to .env and fill in values
# OAuth Credentials (for interactive auth)
GOOGLE_WORKSPACE_CLI_CLIENT_ID=
GOOGLE_WORKSPACE_CLI_CLIENT_SECRET=
# Headless/CI auth (export from an interactive machine: gws auth export --unmasked)
# GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json
# GOOGLE_WORKSPACE_CLI_TOKEN=
# Optional
# GOOGLE_WORKSPACE_CLI_CONFIG_DIR=~/.config/gws
# GOOGLE_WORKSPACE_CLI_LOG=debug
"""
@dataclass
class ValidationResult:
service: str
status: str # PASS, FAIL
message: str
@dataclass
class ValidationReport:
auth_method: str = ""
user: str = ""
results: List[dict] = field(default_factory=list)
summary: str = ""
demo_mode: bool = False
DEMO_VALIDATION = ValidationReport(
auth_method="oauth",
user="admin@company.com",
results=[
{"service": "gmail", "status": "PASS", "message": "Gmail API accessible"},
{"service": "drive", "status": "PASS", "message": "Drive API accessible"},
{"service": "calendar", "status": "PASS", "message": "Calendar API accessible"},
{"service": "sheets", "status": "PASS", "message": "Sheets API accessible"},
{"service": "tasks", "status": "FAIL", "message": "Scope not authorized"},
],
summary="4/5 services validated (demo mode)",
demo_mode=True,
)
def check_auth_status() -> dict:
"""Check current gws auth status."""
try:
result = subprocess.run(
["gws", "auth", "status", "--json"],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return {"status": "authenticated", "raw": result.stdout.strip()}
return {"status": "unknown",
"note": "could not determine auth status ('gws auth status' may not exist "
"in your version; check 'gws auth --help')",
"error": result.stderr.strip()[:200]}
except (FileNotFoundError, OSError):
return {"status": "gws_not_found"}
def validate_services(services: List[str]) -> ValidationReport:
"""Validate auth by testing each service."""
report = ValidationReport()
auth = check_auth_status()
if auth.get("status") == "gws_not_found":
report.summary = "gws CLI not installed"
return report
if auth.get("status") == "not_authenticated":
report.auth_method = "none"
report.summary = "Not authenticated"
return report
report.auth_method = auth.get("method", "oauth")
report.user = auth.get("user", auth.get("email", "unknown"))
service_cmds = {
"gmail": ["gws", "gmail", "users", "getProfile", "--params", '{"userId": "me"}'],
"drive": ["gws", "drive", "files", "list", "--params", '{"pageSize": 1}'],
"calendar": ["gws", "calendar", "calendarList", "list", "--params", '{"maxResults": 1}'],
"sheets": ["gws", "schema", "sheets.spreadsheets.get"],
"tasks": ["gws", "tasks", "tasklists", "list", "--params", '{"maxResults": 1}'],
}
for svc in services:
cmd = service_cmds.get(svc)
if not cmd:
report.results.append(asdict(
ValidationResult(svc, "WARN", f"No test available for {svc}")
))
continue
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
if result.returncode == 0:
report.results.append(asdict(
ValidationResult(svc, "PASS", f"{svc.title()} API accessible")
))
else:
report.results.append(asdict(
ValidationResult(svc, "FAIL", result.stderr.strip()[:100])
))
except (subprocess.TimeoutExpired, OSError) as e:
report.results.append(asdict(
ValidationResult(svc, "FAIL", str(e)[:100])
))
passed = sum(1 for r in report.results if r["status"] == "PASS")
total = len(report.results)
report.summary = f"{passed}/{total} services validated"
return report
def main():
parser = argparse.ArgumentParser(
description="Guided authentication setup for Google Workspace CLI (gws)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --guide oauth # OAuth setup instructions
%(prog)s --guide service-account # Service account setup
%(prog)s --scopes gmail,drive # Show required scopes
%(prog)s --generate-env # Generate .env template
%(prog)s --check # Check current auth status
%(prog)s --validate --json # Validate all services (JSON)
""",
)
parser.add_argument("--guide", choices=["oauth", "service-account"],
help="Print setup guide")
parser.add_argument("--scopes", help="Comma-separated services to show scopes for")
parser.add_argument("--generate-env", action="store_true",
help="Generate .env template")
parser.add_argument("--check", action="store_true",
help="Check current auth status")
parser.add_argument("--validate", action="store_true",
help="Validate auth by testing services")
parser.add_argument("--services", default="gmail,drive,calendar,sheets,tasks",
help="Services to validate (default: gmail,drive,calendar,sheets,tasks)")
parser.add_argument("--json", action="store_true", help="Output JSON")
args = parser.parse_args()
if not any([args.guide, args.scopes, args.generate_env, args.check, args.validate]):
parser.print_help()
return
if args.guide:
if args.guide == "oauth":
print(OAUTH_GUIDE)
else:
print(SERVICE_ACCOUNT_GUIDE)
return
if args.scopes:
services = [s.strip() for s in args.scopes.split(",") if s.strip()]
if args.json:
output = {}
for svc in services:
output[svc] = SERVICE_SCOPES.get(svc, [])
print(json.dumps(output, indent=2))
else:
print(f"\n{'='*60}")
print(f" REQUIRED OAUTH SCOPES")
print(f"{'='*60}\n")
for svc in services:
scopes = SERVICE_SCOPES.get(svc, [])
print(f" {svc.upper()}:")
if scopes:
for scope in scopes:
print(f" - {scope}")
else:
print(f" (no scopes defined for '{svc}')")
print()
# Print combined for easy copy-paste
all_scopes = []
for svc in services:
all_scopes.extend(SERVICE_SCOPES.get(svc, []))
if all_scopes:
print(f" COMBINED (for consent screen):")
print(f" {','.join(all_scopes)}")
print(f"\n{'='*60}\n")
return
if args.generate_env:
print(ENV_TEMPLATE)
return
if args.check:
if shutil.which("gws"):
status = check_auth_status()
else:
status = {"status": "gws_not_found",
"note": "Install gws first: npm install -g @googleworkspace/cli OR https://github.com/googleworkspace/cli/releases"}
if args.json:
print(json.dumps(status, indent=2))
else:
print(f"\nAuth Status: {status.get('status', 'unknown')}")
for k, v in status.items():
if k != "status":
print(f" {k}: {v}")
print()
return
if args.validate:
services = [s.strip() for s in args.services.split(",") if s.strip()]
if not shutil.which("gws"):
report = DEMO_VALIDATION
else:
report = validate_services(services)
if args.json:
print(json.dumps(asdict(report), indent=2))
else:
print(f"\n{'='*60}")
print(f" AUTH VALIDATION REPORT")
if report.demo_mode:
print(f" (DEMO MODE)")
print(f"{'='*60}\n")
if report.user:
print(f" User: {report.user}")
print(f" Method: {report.auth_method}\n")
for r in report.results:
icon = "PASS" if r["status"] == "PASS" else "FAIL"
print(f" [{icon}] {r['service']}: {r['message']}")
print(f"\n {report.summary}")
print(f"\n{'='*60}\n")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Google Workspace CLI Doctor — Pre-flight diagnostics for gws CLI.
Checks installation, version, authentication status, and service
connectivity. Runs in demo mode with embedded sample data when gws
is not installed.
Usage:
python3 gws_doctor.py
python3 gws_doctor.py --json
python3 gws_doctor.py --services gmail,drive,calendar
"""
import argparse
import json
import shutil
import subprocess
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Optional
@dataclass
class Check:
name: str
status: str # PASS, WARN, FAIL
message: str
fix: str = ""
@dataclass
class DiagnosticReport:
gws_installed: bool = False
gws_version: str = ""
auth_status: str = ""
checks: List[dict] = field(default_factory=list)
summary: str = ""
demo_mode: bool = False
DEMO_CHECKS = [
Check("gws-installed", "PASS", "gws v0.9.2 found at /usr/local/bin/gws"),
Check("gws-version", "PASS", "Version 0.9.2 (latest)"),
Check("auth-status", "PASS", "Authenticated as admin@company.com"),
Check("token-expiry", "WARN", "Token expires in 23 minutes",
"Run 'gws auth refresh' to extend token lifetime"),
Check("gmail-access", "PASS", "Gmail API accessible — user profile retrieved"),
Check("drive-access", "PASS", "Drive API accessible — root folder listed"),
Check("calendar-access", "PASS", "Calendar API accessible — primary calendar found"),
Check("sheets-access", "PASS", "Sheets API accessible"),
Check("tasks-access", "FAIL", "Tasks API not authorized",
"Run 'gws auth setup' and add 'tasks' scope"),
]
SERVICE_TEST_COMMANDS = {
"gmail": ["gws", "gmail", "users", "getProfile", "--params", '{"userId": "me"}'],
"drive": ["gws", "drive", "files", "list", "--params", '{"pageSize": 1}'],
"calendar": ["gws", "calendar", "calendarList", "list", "--params", '{"maxResults": 1}'],
"sheets": ["gws", "schema", "sheets.spreadsheets.get"],
"tasks": ["gws", "tasks", "tasklists", "list", "--params", '{"maxResults": 1}'],
"chat": ["gws", "chat", "spaces", "list", "--params", '{"pageSize": 1}'],
"docs": ["gws", "schema", "docs.documents.get"],
}
def check_installation() -> Check:
"""Check if gws is installed and on PATH."""
path = shutil.which("gws")
if path:
return Check("gws-installed", "PASS", f"gws found at {path}")
return Check("gws-installed", "FAIL", "gws not found on PATH",
"Install via: npm install -g @googleworkspace/cli OR download from https://github.com/googleworkspace/cli/releases")
def check_version() -> Check:
"""Get gws version."""
try:
result = subprocess.run(
["gws", "--version"], capture_output=True, text=True, timeout=10
)
version = result.stdout.strip()
if version:
return Check("gws-version", "PASS", f"Version: {version}")
return Check("gws-version", "WARN", "Could not parse version output")
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
return Check("gws-version", "FAIL", f"Version check failed: {e}")
def check_auth() -> Check:
"""Check authentication status."""
try:
result = subprocess.run(
["gws", "auth", "status", "--json"],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
try:
data = json.loads(result.stdout)
user = data.get("user", data.get("email", "unknown"))
return Check("auth-status", "PASS", f"Authenticated as {user}")
except json.JSONDecodeError:
return Check("auth-status", "PASS", "Authenticated (could not parse details)")
return Check("auth-status", "WARN",
"Could not confirm authentication ('gws auth status' may not exist "
"in your version; check 'gws auth --help')",
"Run 'gws auth setup' then 'gws auth login' to configure authentication")
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
return Check("auth-status", "FAIL", f"Auth check failed: {e}",
"Run 'gws auth setup' then 'gws auth login' to configure authentication")
def check_service(service: str) -> Check:
"""Test connectivity to a specific service."""
cmd = SERVICE_TEST_COMMANDS.get(service)
if not cmd:
return Check(f"{service}-access", "WARN", f"No test command for {service}")
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
if result.returncode == 0:
return Check(f"{service}-access", "PASS", f"{service.title()} API accessible")
stderr = result.stderr.strip()[:100]
if "403" in stderr or "permission" in stderr.lower():
return Check(f"{service}-access", "FAIL",
f"{service.title()} API permission denied",
f"Add '{service}' scope: gws auth setup --scopes {service}")
return Check(f"{service}-access", "FAIL",
f"{service.title()} API error: {stderr}",
f"Check scope and permissions for {service}")
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
return Check(f"{service}-access", "FAIL", f"{service.title()} test failed: {e}")
def run_diagnostics(services: List[str]) -> DiagnosticReport:
"""Run all diagnostic checks."""
report = DiagnosticReport()
checks = []
# Installation check
install_check = check_installation()
checks.append(install_check)
report.gws_installed = install_check.status == "PASS"
if not report.gws_installed:
report.checks = [asdict(c) for c in checks]
report.summary = "FAIL: gws is not installed"
return report
# Version check
version_check = check_version()
checks.append(version_check)
if version_check.status == "PASS":
report.gws_version = version_check.message.replace("Version: ", "")
# Auth check
auth_check = check_auth()
checks.append(auth_check)
report.auth_status = auth_check.status
if auth_check.status != "PASS":
report.checks = [asdict(c) for c in checks]
report.summary = "FAIL: Authentication not configured"
return report
# Service checks
for svc in services:
checks.append(check_service(svc))
report.checks = [asdict(c) for c in checks]
# Summary
fails = sum(1 for c in checks if c.status == "FAIL")
warns = sum(1 for c in checks if c.status == "WARN")
passes = sum(1 for c in checks if c.status == "PASS")
if fails > 0:
report.summary = f"ISSUES FOUND: {passes} passed, {warns} warnings, {fails} failures"
elif warns > 0:
report.summary = f"MOSTLY OK: {passes} passed, {warns} warnings"
else:
report.summary = f"ALL CLEAR: {passes}/{passes} checks passed"
return report
def run_demo() -> DiagnosticReport:
"""Return demo report with embedded sample data."""
report = DiagnosticReport(
gws_installed=True,
gws_version="0.9.2",
auth_status="PASS",
checks=[asdict(c) for c in DEMO_CHECKS],
summary="MOSTLY OK: 7 passed, 1 warning, 1 failure (demo mode)",
demo_mode=True,
)
return report
def main():
parser = argparse.ArgumentParser(
description="Pre-flight diagnostics for Google Workspace CLI (gws)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s # Run all checks
%(prog)s --json # JSON output
%(prog)s --services gmail,drive # Check specific services only
%(prog)s --demo # Demo mode (no gws required)
""",
)
parser.add_argument("--json", action="store_true", help="Output JSON")
parser.add_argument(
"--services", default="gmail,drive,calendar,sheets,tasks",
help="Comma-separated services to check (default: gmail,drive,calendar,sheets,tasks)"
)
parser.add_argument("--demo", action="store_true", help="Run with demo data")
args = parser.parse_args()
services = [s.strip() for s in args.services.split(",") if s.strip()]
# Use demo mode if requested or gws not installed
if args.demo or not shutil.which("gws"):
report = run_demo()
else:
report = run_diagnostics(services)
if args.json:
print(json.dumps(asdict(report), indent=2))
else:
print(f"\n{'='*60}")
print(f" GWS CLI DIAGNOSTIC REPORT")
if report.demo_mode:
print(f" (DEMO MODE — sample data)")
print(f"{'='*60}\n")
for c in report.checks:
icon = {"PASS": "PASS", "WARN": "WARN", "FAIL": "FAIL"}.get(c["status"], "????")
print(f" [{icon}] {c['name']}: {c['message']}")
if c.get("fix") and c["status"] != "PASS":
print(f" -> {c['fix']}")
print(f"\n {'-'*56}")
print(f" {report.summary}")
print(f"\n{'='*60}\n")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Google Workspace CLI Recipe Runner — Catalog, search, and execute gws command templates.
Browse 43 recipe command templates (a LOCAL catalog shipped with this skill —
NOT built into the gws CLI), filter by persona, search by keyword, and run
with dry-run support.
IMPORTANT: The gws CLI (github.com/googleworkspace/cli) generates its command
surface dynamically from Google's Discovery Service. Command strings in this
catalog are templates — verify each against `gws --help`, `gws <service> --help`,
or `gws schema <service>.<resource>.<method>` before relying on it.
Usage:
python3 gws_recipe_runner.py --list
python3 gws_recipe_runner.py --search "email"
python3 gws_recipe_runner.py --describe standup-report
python3 gws_recipe_runner.py --run standup-report --dry-run
python3 gws_recipe_runner.py --persona pm --list
python3 gws_recipe_runner.py --list --json
"""
import argparse
import json
import subprocess
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Optional
TEMPLATE_NOTE = ("NOTE: Recipe commands are templates from this local catalog (not shipped by "
"the gws CLI). Verify each against 'gws --help' / 'gws schema' before use.")
@dataclass
class Recipe:
name: str
description: str
category: str
services: List[str]
commands: List[str]
prerequisites: str = ""
RECIPES: Dict[str, Recipe] = {
# Email (8)
"send-email": Recipe("send-email", "Send an email with optional attachments", "email",
["gmail"], ["gws gmail users.messages send me --to {to} --subject {subject} --body {body}"]),
"reply-to-thread": Recipe("reply-to-thread", "Reply to an existing email thread", "email",
["gmail"], ["gws gmail users.messages reply me --thread-id {thread_id} --body {body}"]),
"forward-email": Recipe("forward-email", "Forward an email to another recipient", "email",
["gmail"], ["gws gmail users.messages forward me --message-id {msg_id} --to {to}"]),
"search-emails": Recipe("search-emails", "Search emails with Gmail query syntax", "email",
["gmail"], ["gws gmail users.messages list me --query {query} --json"]),
"archive-old": Recipe("archive-old", "Archive read emails older than N days", "email",
["gmail"], [
"gws gmail users.messages list me --query 'is:read older_than:{days}d' --json",
"# Pipe IDs to batch modify to remove INBOX label",
]),
"label-manager": Recipe("label-manager", "Create, list, and organize Gmail labels", "email",
["gmail"], ["gws gmail users.labels list me --json", "gws gmail users.labels create me --name {name}"]),
"filter-setup": Recipe("filter-setup", "Create email filters for auto-labeling", "email",
["gmail"], ["gws gmail users.settings.filters create me --criteria {criteria} --action {action}"]),
"unread-digest": Recipe("unread-digest", "Get digest of unread emails", "email",
["gmail"], ["gws gmail users.messages list me --query 'is:unread' --limit 20 --json"]),
# Files (7)
"upload-file": Recipe("upload-file", "Upload a file to Google Drive", "files",
["drive"], ["gws drive files create --name {name} --upload {path} --parents {folder_id}"]),
"create-sheet": Recipe("create-sheet", "Create a new Google Spreadsheet", "files",
["sheets"], ["gws sheets spreadsheets create --title {title} --json"]),
"share-file": Recipe("share-file", "Share a Drive file with a user or domain", "files",
["drive"], ["gws drive permissions create {file_id} --type user --role writer --emailAddress {email}"]),
"export-file": Recipe("export-file", "Export a Google Doc/Sheet as PDF", "files",
["drive"], ["gws drive files export {file_id} --mime application/pdf --output {output}"]),
"list-files": Recipe("list-files", "List files in a Drive folder", "files",
["drive"], ["gws drive files list --parents {folder_id} --json"]),
"find-large-files": Recipe("find-large-files", "Find largest files in Drive", "files",
["drive"], ["gws drive files list --orderBy 'quotaBytesUsed desc' --limit 20 --json"]),
"cleanup-trash": Recipe("cleanup-trash", "Empty Drive trash", "files",
["drive"], ["gws drive files emptyTrash"]),
# Calendar (6)
"create-event": Recipe("create-event", "Create a calendar event with attendees", "calendar",
["calendar"], [
"gws calendar events insert primary --summary {title} "
"--start {start} --end {end} --attendees {attendees}"
]),
"quick-event": Recipe("quick-event", "Create an event via the calendar helper", "calendar",
["calendar"], ["gws calendar +insert {details} # see: gws calendar +insert --help"]),
"find-time": Recipe("find-time", "Find available time slots via free/busy", "calendar",
["calendar"], ["gws calendar freebusy query --json '<freebusy-request>' # verify: gws schema calendar.freebusy.query"]),
"today-schedule": Recipe("today-schedule", "Show today's calendar events", "calendar",
["calendar"], ["gws calendar events list primary --timeMin {today_start} --timeMax {today_end} --json"]),
"meeting-prep": Recipe("meeting-prep", "Prepare for an upcoming meeting (agenda + attendees)", "calendar",
["calendar"], ["gws workflow +meeting-prep"]),
"reschedule": Recipe("reschedule", "Move an event to a new time", "calendar",
["calendar"], ["gws calendar events patch primary {event_id} --start {new_start} --end {new_end}"]),
# Reporting (5)
"standup-report": Recipe("standup-report", "Generate daily standup from calendar and tasks", "reporting",
["calendar", "tasks"], ["gws workflow +standup-report"]),
"weekly-summary": Recipe("weekly-summary", "Summarize week's emails, events, and tasks", "reporting",
["gmail", "calendar", "tasks"], ["gws workflow +weekly-digest"]),
"drive-activity": Recipe("drive-activity", "Report on Drive file activity", "reporting",
["drive"], ["gws drive activities list --json"]),
"email-stats": Recipe("email-stats", "Email volume statistics", "reporting",
["gmail"], [
"gws gmail users.messages list me --query 'newer_than:7d' --json",
"# Pipe through output_analyzer.py --count",
]),
"task-progress": Recipe("task-progress", "Report on task completion", "reporting",
["tasks"], ["gws tasks tasks list {tasklist_id} --json"]),
# Collaboration (5)
"share-folder": Recipe("share-folder", "Share a Drive folder with a team", "collaboration",
["drive"], ["gws drive permissions create {folder_id} --type group --role writer --emailAddress {group}"]),
"create-doc": Recipe("create-doc", "Create a Google Doc with initial content", "collaboration",
["docs"], ["gws docs documents create --title {title} --json"]),
"chat-message": Recipe("chat-message", "Send a message to a Google Chat space", "collaboration",
["chat"], ["gws chat spaces.messages create {space} --text {message}"]),
"list-spaces": Recipe("list-spaces", "List Google Chat spaces", "collaboration",
["chat"], ["gws chat spaces list --json"]),
"task-create": Recipe("task-create", "Create a task in Google Tasks", "collaboration",
["tasks"], ["gws tasks tasks insert {tasklist_id} --title {title} --due {due_date}"]),
# Data (4)
"sheet-read": Recipe("sheet-read", "Read data from a spreadsheet range", "data",
["sheets"], ["gws sheets spreadsheets.values get {sheet_id} --range {range} --json"]),
"sheet-write": Recipe("sheet-write", "Write data to a spreadsheet", "data",
["sheets"], ["gws sheets spreadsheets.values update {sheet_id} --range {range} --values {data}"]),
"sheet-append": Recipe("sheet-append", "Append rows to a spreadsheet", "data",
["sheets"], ["gws sheets spreadsheets.values append {sheet_id} --range {range} --values {data}"]),
"export-contacts": Recipe("export-contacts", "Export contacts list", "data",
["people"], ["gws people people.connections list me --personFields names,emailAddresses --json"]),
# Admin (4)
"list-users": Recipe("list-users", "List all users in the Workspace domain", "admin",
["admin"], ["gws admin users list --domain {domain} --json"],
"Requires Admin SDK API and admin.directory.user.readonly scope"),
"list-groups": Recipe("list-groups", "List all groups in the domain", "admin",
["admin"], ["gws admin groups list --domain {domain} --json"]),
"user-info": Recipe("user-info", "Get detailed user information", "admin",
["admin"], ["gws admin users get {email} --json"]),
"audit-logins": Recipe("audit-logins", "Audit recent login activity", "admin",
["admin"], ["gws admin activities list login --json"]),
# Cross-Service (4)
"morning-briefing": Recipe("morning-briefing", "Today's events + unread emails + pending tasks", "cross-service",
["gmail", "calendar", "tasks"], [
"gws calendar events list primary --timeMin {today} --maxResults 10 --json",
"gws gmail users.messages list me --query 'is:unread' --limit 10 --json",
"gws tasks tasks list {default_tasklist} --json",
]),
"eod-wrap": Recipe("eod-wrap", "End-of-day wrap up: summarize completed, pending, tomorrow", "cross-service",
["calendar", "tasks"], [
"gws calendar events list primary --timeMin {today_start} --timeMax {today_end} --json",
"gws tasks tasks list {default_tasklist} --json",
]),
"project-status": Recipe("project-status", "Aggregate project status from Drive, Sheets, Tasks", "cross-service",
["drive", "sheets", "tasks"], [
"gws drive files list --query 'name contains {project}' --json",
"gws tasks tasks list {tasklist_id} --json",
]),
"inbox-zero": Recipe("inbox-zero", "Process inbox to zero: label, archive, reply, task", "cross-service",
["gmail", "tasks"], [
"gws gmail users.messages list me --query 'is:inbox' --json",
"# Process each: label, archive, or create task",
]),
}
PERSONAS: Dict[str, Dict] = {
"executive-assistant": {
"description": "Executive assistant managing schedules, emails, and communications",
"recipes": ["morning-briefing", "today-schedule", "find-time", "send-email", "reply-to-thread",
"standup-report", "meeting-prep", "eod-wrap", "quick-event", "inbox-zero"],
},
"pm": {
"description": "Project manager tracking tasks, meetings, and deliverables",
"recipes": ["standup-report", "create-event", "find-time", "task-create", "task-progress",
"project-status", "weekly-summary", "share-folder", "sheet-read", "morning-briefing"],
},
"hr": {
"description": "HR managing people, onboarding, and communications",
"recipes": ["list-users", "user-info", "send-email", "create-event", "create-doc",
"share-folder", "chat-message", "list-groups", "export-contacts", "today-schedule"],
},
"sales": {
"description": "Sales rep managing client communications and proposals",
"recipes": ["send-email", "search-emails", "create-event", "find-time", "create-doc",
"share-file", "sheet-read", "sheet-write", "export-file", "morning-briefing"],
},
"it-admin": {
"description": "IT administrator managing Workspace configuration and security",
"recipes": ["list-users", "list-groups", "user-info", "audit-logins", "drive-activity",
"find-large-files", "cleanup-trash", "label-manager", "filter-setup", "share-folder"],
},
"developer": {
"description": "Developer using Workspace APIs for automation",
"recipes": ["sheet-read", "sheet-write", "sheet-append", "upload-file", "create-doc",
"chat-message", "task-create", "list-files", "export-file", "send-email"],
},
"marketing": {
"description": "Marketing team member managing campaigns and content",
"recipes": ["send-email", "create-doc", "share-file", "upload-file", "create-sheet",
"sheet-write", "chat-message", "create-event", "email-stats", "weekly-summary"],
},
"finance": {
"description": "Finance team managing spreadsheets and reports",
"recipes": ["sheet-read", "sheet-write", "sheet-append", "create-sheet", "export-file",
"share-file", "send-email", "find-large-files", "drive-activity", "weekly-summary"],
},
"legal": {
"description": "Legal team managing documents and compliance",
"recipes": ["create-doc", "share-file", "export-file", "search-emails", "send-email",
"upload-file", "list-files", "drive-activity", "audit-logins", "find-large-files"],
},
"support": {
"description": "Customer support managing tickets and communications",
"recipes": ["search-emails", "send-email", "reply-to-thread", "label-manager", "filter-setup",
"task-create", "chat-message", "unread-digest", "inbox-zero", "morning-briefing"],
},
}
def list_recipes(persona: Optional[str], output_json: bool):
"""List all recipes, optionally filtered by persona."""
if persona:
if persona not in PERSONAS:
print(f"Unknown persona: {persona}. Available: {', '.join(PERSONAS.keys())}")
sys.exit(1)
recipe_names = PERSONAS[persona]["recipes"]
recipes = {k: v for k, v in RECIPES.items() if k in recipe_names}
title = f"Recipes for {persona.upper()}: {PERSONAS[persona]['description']}"
else:
recipes = RECIPES
title = "All 43 Google Workspace CLI Recipes"
if output_json:
output = []
for name, r in recipes.items():
output.append(asdict(r))
print(json.dumps(output, indent=2))
return
print(f"\n{'='*60}")
print(f" {title}")
print(f"{'='*60}\n")
by_category: Dict[str, list] = {}
for name, r in recipes.items():
by_category.setdefault(r.category, []).append(r)
for cat, cat_recipes in sorted(by_category.items()):
print(f" {cat.upper()} ({len(cat_recipes)})")
for r in cat_recipes:
svcs = ",".join(r.services)
print(f" {r.name:<24} {r.description:<40} [{svcs}]")
print()
print(f" Total: {len(recipes)} recipes")
print(f"\n{'='*60}\n")
def search_recipes(keyword: str, output_json: bool):
"""Search recipes by keyword."""
keyword_lower = keyword.lower()
matches = {k: v for k, v in RECIPES.items()
if keyword_lower in k.lower()
or keyword_lower in v.description.lower()
or keyword_lower in v.category.lower()
or any(keyword_lower in s for s in v.services)}
if output_json:
print(json.dumps([asdict(r) for r in matches.values()], indent=2))
return
print(f"\n Search results for '{keyword}': {len(matches)} matches\n")
for name, r in matches.items():
print(f" {r.name:<24} {r.description}")
print()
def describe_recipe(name: str, output_json: bool):
"""Show full details for a recipe."""
recipe = RECIPES.get(name)
if not recipe:
print(f"Unknown recipe: {name}")
print(f"Use --list to see available recipes")
sys.exit(1)
if output_json:
print(json.dumps(asdict(recipe), indent=2))
return
print(f"\n{'='*60}")
print(f" Recipe: {recipe.name}")
print(f"{'='*60}\n")
print(f" Description: {recipe.description}")
print(f" Category: {recipe.category}")
print(f" Services: {', '.join(recipe.services)}")
if recipe.prerequisites:
print(f" Prerequisites: {recipe.prerequisites}")
print(f"\n Commands:")
for i, cmd in enumerate(recipe.commands, 1):
print(f" {i}. {cmd}")
print(f"\n {TEMPLATE_NOTE}")
print(f"\n{'='*60}\n")
def run_recipe(name: str, dry_run: bool):
"""Execute a recipe (or print commands in dry-run mode)."""
recipe = RECIPES.get(name)
if not recipe:
print(f"Unknown recipe: {name}")
sys.exit(1)
if dry_run:
print(f"\n [DRY RUN] Recipe: {recipe.name}\n")
for i, cmd in enumerate(recipe.commands, 1):
print(f" {i}. {cmd}")
print(f"\n {TEMPLATE_NOTE}")
print(f"\n (No commands executed)")
return
print(f"\n Executing recipe: {recipe.name}")
print(f" {TEMPLATE_NOTE}\n")
for cmd in recipe.commands:
if cmd.startswith("#"):
print(f" {cmd}")
continue
print(f" $ {cmd}")
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
if result.stdout:
print(result.stdout)
if result.returncode != 0 and result.stderr:
print(f" Error: {result.stderr.strip()[:200]}")
except subprocess.TimeoutExpired:
print(f" Timeout after 30s")
except OSError as e:
print(f" Execution error: {e}")
def list_personas(output_json: bool):
"""List all available personas."""
if output_json:
print(json.dumps(PERSONAS, indent=2))
return
print(f"\n{'='*60}")
print(f" 10 PERSONA BUNDLES")
print(f"{'='*60}\n")
for name, p in PERSONAS.items():
print(f" {name:<24} {p['description']}")
print(f" {'':24} Recipes: {', '.join(p['recipes'][:5])}...")
print()
print(f"{'='*60}\n")
def main():
parser = argparse.ArgumentParser(
description="Catalog, search, and execute Google Workspace CLI recipes",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --list # List all 43 recipes
%(prog)s --list --persona pm # Recipes for project managers
%(prog)s --search "email" # Search by keyword
%(prog)s --describe standup-report # Full recipe details
%(prog)s --run standup-report --dry-run # Preview recipe commands
%(prog)s --personas # List all 10 personas
%(prog)s --list --json # JSON output
""",
)
parser.add_argument("--list", action="store_true", help="List all recipes")
parser.add_argument("--search", help="Search recipes by keyword")
parser.add_argument("--describe", help="Show full details for a recipe")
parser.add_argument("--run", help="Execute a recipe")
parser.add_argument("--dry-run", action="store_true", help="Print commands without executing")
parser.add_argument("--persona", help="Filter recipes by persona")
parser.add_argument("--personas", action="store_true", help="List all personas")
parser.add_argument("--json", action="store_true", help="Output JSON")
args = parser.parse_args()
if not any([args.list, args.search, args.describe, args.run, args.personas]):
parser.print_help()
return
if args.personas:
list_personas(args.json)
return
if args.list:
list_recipes(args.persona, args.json)
return
if args.search:
search_recipes(args.search, args.json)
return
if args.describe:
describe_recipe(args.describe, args.json)
return
if args.run:
run_recipe(args.run, args.dry_run)
return
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Google Workspace CLI Output Analyzer — Parse, filter, and aggregate JSON/NDJSON output.
Reads JSON arrays or NDJSON streams from stdin or file, applies filters,
projections, sorting, grouping, and outputs in table/csv/json format.
Usage:
gws drive files list | python3 output_analyzer.py --count
gws drive files list | python3 output_analyzer.py --filter "mimeType=application/pdf"
gws drive files list | python3 output_analyzer.py --select "name,size" --format table
python3 output_analyzer.py --input results.json --group-by "mimeType"
python3 output_analyzer.py --demo --select "name,mimeType,size" --format table
"""
import argparse
import csv
import io
import json
import sys
from dataclasses import dataclass
from typing import List, Dict, Any, Optional
DEMO_DATA = [
{"id": "1", "name": "Q1 Report.pdf", "mimeType": "application/pdf", "size": "245760",
"modifiedTime": "2026-03-10T14:30:00Z", "shared": True, "owners": [{"displayName": "Alice"}]},
{"id": "2", "name": "Budget 2026.xlsx", "mimeType": "application/vnd.google-apps.spreadsheet",
"size": "0", "modifiedTime": "2026-03-09T09:15:00Z", "shared": True,
"owners": [{"displayName": "Bob"}]},
{"id": "3", "name": "Meeting Notes.docx", "mimeType": "application/vnd.google-apps.document",
"size": "0", "modifiedTime": "2026-03-08T16:00:00Z", "shared": False,
"owners": [{"displayName": "Alice"}]},
{"id": "4", "name": "Logo.png", "mimeType": "image/png", "size": "102400",
"modifiedTime": "2026-03-07T11:00:00Z", "shared": False,
"owners": [{"displayName": "Charlie"}]},
{"id": "5", "name": "Presentation.pptx", "mimeType": "application/vnd.google-apps.presentation",
"size": "0", "modifiedTime": "2026-03-06T10:00:00Z", "shared": True,
"owners": [{"displayName": "Alice"}]},
{"id": "6", "name": "Invoice-001.pdf", "mimeType": "application/pdf", "size": "89000",
"modifiedTime": "2026-03-05T08:30:00Z", "shared": False,
"owners": [{"displayName": "Bob"}]},
{"id": "7", "name": "Project Plan.xlsx", "mimeType": "application/vnd.google-apps.spreadsheet",
"size": "0", "modifiedTime": "2026-03-04T13:45:00Z", "shared": True,
"owners": [{"displayName": "Charlie"}]},
{"id": "8", "name": "Contract Draft.docx", "mimeType": "application/vnd.google-apps.document",
"size": "0", "modifiedTime": "2026-03-03T09:00:00Z", "shared": False,
"owners": [{"displayName": "Alice"}]},
]
def read_input(input_file: Optional[str]) -> List[Dict[str, Any]]:
"""Read JSON array or NDJSON from file or stdin."""
if input_file:
with open(input_file, "r") as f:
text = f.read().strip()
else:
if sys.stdin.isatty():
return []
text = sys.stdin.read().strip()
if not text:
return []
# Try JSON array first
try:
data = json.loads(text)
if isinstance(data, list):
return data
if isinstance(data, dict):
# Some gws commands wrap results in a key
for key in ("files", "messages", "events", "items", "results",
"spreadsheets", "spaces", "tasks", "users", "groups"):
if key in data and isinstance(data[key], list):
return data[key]
return [data]
except json.JSONDecodeError:
pass
# Try NDJSON
records = []
for line in text.split("\n"):
line = line.strip()
if line:
try:
records.append(json.loads(line))
except json.JSONDecodeError:
continue
return records
def get_nested(obj: Dict, path: str) -> Any:
"""Get a nested value by dot-separated path."""
parts = path.split(".")
current = obj
for part in parts:
if isinstance(current, dict):
current = current.get(part)
elif isinstance(current, list) and part.isdigit():
idx = int(part)
current = current[idx] if idx < len(current) else None
else:
return None
if current is None:
return None
return current
def apply_filter(records: List[Dict], filter_expr: str) -> List[Dict]:
"""Filter records by field=value expression."""
if "=" not in filter_expr:
return records
field_path, value = filter_expr.split("=", 1)
result = []
for rec in records:
rec_val = get_nested(rec, field_path)
if rec_val is None:
continue
rec_str = str(rec_val).lower()
if rec_str == value.lower() or value.lower() in rec_str:
result.append(rec)
return result
def apply_select(records: List[Dict], fields: str) -> List[Dict]:
"""Project specific fields from records."""
field_list = [f.strip() for f in fields.split(",")]
result = []
for rec in records:
projected = {}
for f in field_list:
projected[f] = get_nested(rec, f)
result.append(projected)
return result
def apply_sort(records: List[Dict], sort_field: str, reverse: bool = False) -> List[Dict]:
"""Sort records by a field."""
def sort_key(rec):
val = get_nested(rec, sort_field)
if val is None:
return ""
if isinstance(val, (int, float)):
return val
try:
return float(val)
except (ValueError, TypeError):
return str(val).lower()
return sorted(records, key=sort_key, reverse=reverse)
def apply_group_by(records: List[Dict], field: str) -> Dict[str, int]:
"""Group records by a field and count."""
groups: Dict[str, int] = {}
for rec in records:
val = get_nested(rec, field)
key = str(val) if val is not None else "(null)"
groups[key] = groups.get(key, 0) + 1
return dict(sorted(groups.items(), key=lambda x: x[1], reverse=True))
def compute_stats(records: List[Dict], field: str) -> Dict[str, Any]:
"""Compute min/max/avg/sum for a numeric field."""
values = []
for rec in records:
val = get_nested(rec, field)
if val is not None:
try:
values.append(float(val))
except (ValueError, TypeError):
continue
if not values:
return {"field": field, "count": 0, "error": "No numeric values found"}
return {
"field": field,
"count": len(values),
"min": min(values),
"max": max(values),
"sum": sum(values),
"avg": sum(values) / len(values),
}
def format_table(records: List[Dict]) -> str:
"""Format records as an aligned text table."""
if not records:
return "(no records)"
headers = list(records[0].keys())
# Calculate column widths
widths = {h: len(h) for h in headers}
for rec in records:
for h in headers:
val = str(rec.get(h, ""))
if len(val) > 60:
val = val[:57] + "..."
widths[h] = max(widths[h], len(val))
# Header
header_line = " ".join(h.ljust(widths[h]) for h in headers)
sep_line = " ".join("-" * widths[h] for h in headers)
lines = [header_line, sep_line]
# Rows
for rec in records:
row = []
for h in headers:
val = str(rec.get(h, ""))
if len(val) > 60:
val = val[:57] + "..."
row.append(val.ljust(widths[h]))
lines.append(" ".join(row))
return "\n".join(lines)
def format_csv_output(records: List[Dict]) -> str:
"""Format records as CSV."""
if not records:
return ""
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
return output.getvalue()
def main():
parser = argparse.ArgumentParser(
description="Parse, filter, and aggregate JSON/NDJSON from gws CLI output",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
gws drive files list | %(prog)s --count
gws drive files list | %(prog)s --filter "mimeType=pdf" --select "name,size"
gws drive files list | %(prog)s --group-by "mimeType" --format table
gws drive files list | %(prog)s --sort "size" --reverse --format table
gws drive files list | %(prog)s --stats "size"
%(prog)s --input results.json --select "name,mimeType" --format csv
%(prog)s --demo --select "name,mimeType,size" --format table
""",
)
parser.add_argument("--input", help="Input file (default: stdin)")
parser.add_argument("--demo", action="store_true", help="Use demo data")
parser.add_argument("--count", action="store_true", help="Count records")
parser.add_argument("--filter", help="Filter by field=value")
parser.add_argument("--select", help="Comma-separated fields to project")
parser.add_argument("--sort", help="Sort by field")
parser.add_argument("--reverse", action="store_true", help="Reverse sort order")
parser.add_argument("--group-by", help="Group by field and count")
parser.add_argument("--stats", help="Compute stats for a numeric field")
parser.add_argument("--format", choices=["json", "table", "csv"], default="json",
help="Output format (default: json)")
parser.add_argument("--json", action="store_true",
help="Shorthand for --format json")
args = parser.parse_args()
if args.json:
args.format = "json"
# Read input
if args.demo:
records = DEMO_DATA[:]
else:
records = read_input(args.input)
if not records and not args.demo:
# If no pipe input and no file, use demo
records = DEMO_DATA[:]
print("(No input detected, using demo data)\n", file=sys.stderr)
# Apply operations in order
if args.filter:
records = apply_filter(records, args.filter)
if args.sort:
records = apply_sort(records, args.sort, args.reverse)
# Count
if args.count:
if args.format == "json":
print(json.dumps({"count": len(records)}))
else:
print(f"Count: {len(records)}")
return
# Group by
if args.group_by:
groups = apply_group_by(records, args.group_by)
if args.format == "json":
print(json.dumps(groups, indent=2))
elif args.format == "csv":
print(f"{args.group_by},count")
for k, v in groups.items():
print(f"{k},{v}")
else:
print(f"\n Group by: {args.group_by}\n")
for k, v in groups.items():
print(f" {k:<50} {v}")
print(f"\n Total groups: {len(groups)}")
return
# Stats
if args.stats:
stats = compute_stats(records, args.stats)
if args.format == "json":
print(json.dumps(stats, indent=2))
else:
print(f"\n Stats for '{args.stats}':")
for k, v in stats.items():
if isinstance(v, float):
print(f" {k}: {v:,.2f}")
else:
print(f" {k}: {v}")
return
# Select fields
if args.select:
records = apply_select(records, args.select)
# Output
if args.format == "json":
print(json.dumps(records, indent=2))
elif args.format == "csv":
print(format_csv_output(records))
else:
print(f"\n{format_table(records)}\n")
print(f" ({len(records)} records)\n")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Google Workspace Security Audit — Audit Workspace configuration for security risks.
Checks Drive external sharing, Gmail forwarding rules, OAuth app grants,
Calendar visibility, admin settings, and generates remediation commands.
Runs in demo mode with embedded sample data when gws is not installed.
Usage:
python3 workspace_audit.py
python3 workspace_audit.py --json
python3 workspace_audit.py --services gmail,drive,calendar
python3 workspace_audit.py --demo
"""
import argparse
import json
import shutil
import subprocess
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Optional
@dataclass
class AuditFinding:
area: str
check: str
status: str # PASS, WARN, FAIL
message: str
risk: str = ""
remediation: str = ""
@dataclass
class AuditReport:
findings: List[dict] = field(default_factory=list)
score: int = 0
max_score: int = 100
grade: str = ""
summary: str = ""
demo_mode: bool = False
DEMO_FINDINGS = [
AuditFinding("drive", "External sharing", "WARN",
"External sharing is enabled for the domain",
"Data exfiltration via shared links",
"Review sharing settings in Admin Console > Apps > Google Workspace > Drive"),
AuditFinding("drive", "Link sharing defaults", "FAIL",
"Default link sharing is set to 'Anyone with the link'",
"Sensitive files accessible without authentication",
"Restrict default link sharing: Admin Console > Apps > Google Workspace > Drive > Sharing settings"),
AuditFinding("gmail", "Auto-forwarding", "PASS",
"No auto-forwarding rules detected for admin accounts"),
AuditFinding("gmail", "SPF record", "PASS",
"SPF record configured correctly"),
AuditFinding("gmail", "DMARC record", "WARN",
"DMARC policy is set to 'none' (monitoring only)",
"Email spoofing not actively blocked",
"Update DMARC DNS record: v=DMARC1; p=quarantine; rua=mailto:dmarc@company.com"),
AuditFinding("gmail", "DKIM signing", "PASS",
"DKIM signing is enabled"),
AuditFinding("calendar", "Default visibility", "WARN",
"Calendar default visibility is 'See all event details'",
"Meeting details visible to all domain users",
"Admin Console > Apps > Calendar > Sharing settings > Set to 'Free/Busy'"),
AuditFinding("calendar", "External sharing", "PASS",
"External calendar sharing is restricted"),
AuditFinding("oauth", "Third-party apps", "FAIL",
"12 third-party OAuth apps with broad access detected",
"Unauthorized data access via OAuth grants",
"Review: Admin Console > Security > API controls > App access control"),
AuditFinding("oauth", "High-risk apps", "WARN",
"3 apps have Drive full access scope",
"Apps can read/modify all Drive files",
"Audit each app via the Directory API tokens resource (verify: gws schema admin.tokens.list)"),
AuditFinding("admin", "Super admin count", "WARN",
"4 super admin accounts detected (recommended: 2-3)",
"Increased attack surface for privilege escalation",
"Reduce super admins: list them via the Directory API (verify: gws schema admin.users.list)"),
AuditFinding("admin", "2-Step verification", "PASS",
"2-Step verification enforced for all users"),
AuditFinding("admin", "Password policy", "PASS",
"Minimum password length: 12 characters"),
AuditFinding("admin", "Login challenges", "PASS",
"Suspicious login challenges enabled"),
]
def run_gws_command(cmd: List[str]) -> Optional[str]:
"""Run a gws command and return stdout, or None on failure."""
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=20)
if result.returncode == 0:
return result.stdout
return None
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
return None
def audit_drive() -> List[AuditFinding]:
"""Audit Drive sharing and security settings."""
findings = []
# Check sharing settings
output = run_gws_command(["gws", "drive", "about", "get", "--params", '{"fields": "*"}'])
if output:
try:
data = json.loads(output)
# Check if external sharing is enabled
if data.get("canShareOutsideDomain", True):
findings.append(AuditFinding(
"drive", "External sharing", "WARN",
"External sharing is enabled",
"Data exfiltration via shared links",
"Review Admin Console > Apps > Drive > Sharing settings"
))
else:
findings.append(AuditFinding(
"drive", "External sharing", "PASS",
"External sharing is restricted"
))
except json.JSONDecodeError:
findings.append(AuditFinding(
"drive", "External sharing", "WARN",
"Could not parse Drive settings"
))
else:
findings.append(AuditFinding(
"drive", "External sharing", "WARN",
"Could not retrieve Drive settings"
))
return findings
def audit_gmail() -> List[AuditFinding]:
"""Audit Gmail forwarding and email security."""
findings = []
# Check forwarding rules
output = run_gws_command(["gws", "gmail", "users", "settings", "forwardingAddresses", "list",
"--params", '{"userId": "me"}'])
if output:
try:
data = json.loads(output)
addrs = data if isinstance(data, list) else data.get("forwardingAddresses", [])
if addrs:
findings.append(AuditFinding(
"gmail", "Auto-forwarding", "WARN",
f"{len(addrs)} forwarding addresses configured",
"Data exfiltration via email forwarding",
"Review forwarding addresses (verify: gws schema gmail.users.settings.forwardingAddresses.list)"
))
else:
findings.append(AuditFinding(
"gmail", "Auto-forwarding", "PASS",
"No forwarding addresses configured"
))
except json.JSONDecodeError:
pass
else:
findings.append(AuditFinding(
"gmail", "Auto-forwarding", "WARN",
"Could not check forwarding settings"
))
return findings
def audit_calendar() -> List[AuditFinding]:
"""Audit Calendar sharing settings."""
findings = []
output = run_gws_command(["gws", "calendar", "calendarList", "get", "--params", '{"calendarId": "primary"}'])
if output:
findings.append(AuditFinding(
"calendar", "Primary calendar", "PASS",
"Primary calendar accessible"
))
else:
findings.append(AuditFinding(
"calendar", "Primary calendar", "WARN",
"Could not access primary calendar"
))
return findings
def run_live_audit(services: List[str]) -> AuditReport:
"""Run live audit against actual gws installation."""
report = AuditReport()
all_findings = []
audit_map = {
"drive": audit_drive,
"gmail": audit_gmail,
"calendar": audit_calendar,
}
for svc in services:
fn = audit_map.get(svc)
if fn:
all_findings.extend(fn())
report.findings = [asdict(f) for f in all_findings]
report = calculate_score(report)
return report
def run_demo_audit() -> AuditReport:
"""Return demo audit report with embedded sample data."""
report = AuditReport(
findings=[asdict(f) for f in DEMO_FINDINGS],
demo_mode=True,
)
report = calculate_score(report)
return report
def calculate_score(report: AuditReport) -> AuditReport:
"""Calculate audit score and grade."""
total = len(report.findings)
if total == 0:
report.score = 0
report.grade = "N/A"
report.summary = "No checks performed"
return report
passes = sum(1 for f in report.findings if f["status"] == "PASS")
warns = sum(1 for f in report.findings if f["status"] == "WARN")
fails = sum(1 for f in report.findings if f["status"] == "FAIL")
# Score: PASS=100, WARN=50, FAIL=0
score = int(((passes * 100) + (warns * 50)) / total)
report.score = score
report.max_score = 100
if score >= 90:
report.grade = "A"
elif score >= 75:
report.grade = "B"
elif score >= 60:
report.grade = "C"
elif score >= 40:
report.grade = "D"
else:
report.grade = "F"
report.summary = f"{passes} passed, {warns} warnings, {fails} failures — Score: {score}/100 (Grade: {report.grade})"
return report
def main():
parser = argparse.ArgumentParser(
description="Security and configuration audit for Google Workspace",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s # Full audit (or demo if gws not installed)
%(prog)s --json # JSON output
%(prog)s --services gmail,drive # Audit specific services
%(prog)s --demo # Demo mode with sample data
""",
)
parser.add_argument("--json", action="store_true", help="Output JSON")
parser.add_argument("--services", default="gmail,drive,calendar",
help="Comma-separated services to audit (default: gmail,drive,calendar)")
parser.add_argument("--demo", action="store_true", help="Run with demo data")
args = parser.parse_args()
services = [s.strip() for s in args.services.split(",") if s.strip()]
if args.demo or not shutil.which("gws"):
report = run_demo_audit()
else:
report = run_live_audit(services)
if args.json:
print(json.dumps(asdict(report), indent=2))
else:
print(f"\n{'='*60}")
print(f" GOOGLE WORKSPACE SECURITY AUDIT")
if report.demo_mode:
print(f" (DEMO MODE — sample data)")
print(f"{'='*60}\n")
print(f" Score: {report.score}/{report.max_score} (Grade: {report.grade})\n")
current_area = ""
for f in report.findings:
if f["area"] != current_area:
current_area = f["area"]
print(f"\n {current_area.upper()}")
print(f" {'-'*40}")
icon = {"PASS": "PASS", "WARN": "WARN", "FAIL": "FAIL"}.get(f["status"], "????")
print(f" [{icon}] {f['check']}: {f['message']}")
if f.get("risk") and f["status"] != "PASS":
print(f" Risk: {f['risk']}")
if f.get("remediation") and f["status"] != "PASS":
print(f" Fix: {f['remediation']}")
print(f"\n {'='*56}")
print(f" {report.summary}")
print(f"\n{'='*60}\n")
if __name__ == "__main__":
main()
Related skills
How it compares
Pick google-workspace-cli when you already use the gws CLI and want persona-scoped recipes; use raw Gmail or Calendar API skills for custom app backends.
FAQ
What does google-workspace-cli include?
google-workspace-cli includes 10 role-based Google Workspace CLI persona profiles that bundle scoped gws commands and recipes for Gmail, Calendar, Tasks, Sheets, and Drive. Personas like Executive Assistant document helpers such as morning-briefing, find-time, meeting-prep, and e
Which Google services does google-workspace-cli cover?
google-workspace-cli covers five Google Workspace surfaces: Gmail, Calendar, Tasks, Sheets, and Drive. Each persona limits which gws helper and send commands an agent should run, reducing guesswork when automating inbox, schedule, and file tasks from Claude or Cursor.
Is Google Workspace Cli safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.