
Google Workspace
- 285 installs
- 40 repo stars
- Updated August 4, 2026
- akillness/oh-my-skills
google-workspace is an agent skill that routes Google Workspace automation across Docs, Sheets, Gmail, Calendar, Drive, Forms, Chat, and Admin SDK by selecting Apps Script, REST APIs, or admin-only flows for developers i
About
google-workspace is an Apache-2.0 skill (version 1.1.0) from akillness/oh-my-skills that plans Google Workspace automations before writing code. It classifies requests across nine product surfaces—Docs, Sheets, Slides, Drive, Gmail, Calendar, Forms, Chat, and Admin SDK—and chooses Apps Script for bound triggers, direct REST plus google-api-python-client for backend services, or Admin SDK for directory tasks. Auth guidance covers OAuth user flows, service accounts, and domain-wide delegation with narrow scopes. Workflow packets include Form→Sheet→Gmail intake, Sheet approval queues, and reporting flows with idempotency and quota checks. Three reference files detail auth routing, service selection, and cross-service patterns. Invoke when automating spreadsheets, shared drives, inbox workflows, or domain admin changes.
- Unified Google Workspace actions for agent workflows
- Supports document, spreadsheet, and email automation
- Reduces custom OAuth and API boilerplate per task
- Fits SaaS teams embedding productivity assistants
Google Workspace by the numbers
- 285 all-time installs (skills.sh)
- Ranked #497 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akillness/oh-my-skills --skill google-workspaceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 285 |
|---|---|
| repo stars | ★ 40 |
| Last updated | August 4, 2026 |
| Repository | akillness/oh-my-skills ↗ |
How do you automate Google Workspace APIs?
Connect agents to Google Workspace so they can read Gmail, edit Docs, update Sheets, and manage Drive files inside automated business workflows.
Who is it for?
Integration engineers automating multi-app Google Workspace flows who need auth, scope, and Apps Script versus REST routing before implementation.
Skip if: Teams wanting only generic Zapier tutorials without Google API scope planning or Workspace-specific quota analysis.
When should I use this skill?
User mentions Google Sheets automation, Gmail send, Calendar events, Drive sharing, Forms pipelines, Apps Script, or Admin SDK tasks.
What you get
Execution packet with chosen API surface, auth model, scopes, object IDs, workflow sequence, and verification steps for Workspace automation.
- integration execution packet
- scope and auth plan
- Workspace automation scripts
By the numbers
- Skill metadata version 1.1.0 under Apache-2.0 license
- Routes automation across 9 Google Workspace product surfaces
- Includes 3 reference guides for auth, services, and workflow quotas
Files
Google Workspace
Use this skill to route Google Workspace work before writing code or pasting API snippets. The key decision is usually which Workspace surface should own the job:
- Apps Script for Workspace-native automation close to Sheets / Docs / Forms / triggers / menus
- Direct REST API + client libraries for backend services, multi-system integrations, stronger deployment control, or language flexibility
- Admin SDK / admin-only APIs for users, groups, org units, directory data, or domain-wide governance tasks
When to use this skill
- Create or update Docs, Sheets, Slides, Drive files/folders, Gmail messages, Calendar events, Forms, or Chat messages
- Design a Workspace automation flow such as Form → Sheet → Gmail / Calendar / Drive
- Decide between Apps Script and direct REST APIs
- Choose the right auth model: OAuth user flow, service account, or domain-wide delegation
- Review scopes, sharing model, quota/execution risk, or safety boundaries before implementation
- Handle Workspace admin tasks that ordinary Docs/Sheets/Gmail code should not absorb
Instructions
Step 1: Classify the request
Capture five things before touching any API:
1. Primary product surface — Docs, Sheets, Slides, Drive, Gmail, Calendar, Forms, Chat, or Admin SDK 2. Operation — create, read, update, search, share, send, schedule, export, batch update, administer 3. Actor / identity — end user, service account, delegated admin, or Apps Script owner 4. Execution home — inside Workspace UI/runtime, backend service, cron/batch worker, or mixed workflow 5. Trigger / cadence — manual, form submit, scheduled, web request, bulk migration, admin event
If the user describes multiple Workspace apps, treat it as a workflow problem first, not a single-API problem.
Step 2: Choose the execution surface
Use Apps Script when
- the workflow is centered around Sheets / Docs / Forms / Gmail and should live inside Workspace
- you need triggers, custom menus, sidebars, or other Workspace-native scripting behavior
- a bound script is simpler than provisioning a separate backend
- JavaScript-in-Workspace ergonomics matter more than backend deployment control
Use direct REST APIs when
- the workflow lives in a backend, integration service, CLI, or batch job
- you need stronger control over deployment, testing, retries, or non-JS stacks
- the task spans Workspace plus external systems and should not depend on a bound script runtime
- Apps Script quotas or runtime limits are likely to be the bottleneck
Use Admin SDK / admin APIs when
- the job is about users, groups, org units, roles, or domain administration
- domain-wide delegated access is part of the task
- the request is clearly beyond ordinary content operations like Docs/Sheets editing
Use an external automation layer only as a route-out
- If the user really needs a no-code / cross-app orchestrator, mention tools like Zapier / Make / n8n as substitutes.
- Do not turn this skill into a generic no-code platform tutorial.
Step 3: Pick the auth model
| Auth model | Use when | Typical surfaces | Notes |
|---|---|---|---|
| OAuth user flow | Acting on a real user’s mailbox, calendar, Drive, docs, or personal content | Gmail, Calendar, Drive, Docs, Sheets, Slides, Forms, Chat | Best default for user-owned data |
| Service account | Backend automation owns the data or operates on shared resources | Drive, backend-generated docs/sheets, service workflows | May need shared-drive/file access design |
| Service account + domain-wide delegation | Admin or org-wide actions must impersonate users safely | Admin SDK, org-wide Gmail/Calendar/Drive workflows | Higher security burden; keep scope narrow |
| Apps Script-bound auth | The workflow is intentionally inside Workspace and tied to the script owner / install model | Sheets, Docs, Forms, Gmail, Calendar, Drive | Fast to start, but runtime/quotas still matter |
Rules:
- Choose the narrowest scopes that satisfy the task.
- Keep credentials and tokens out of the repo.
- If the task touches a user’s mailbox/calendar/files, say whose identity is acting.
- If admin powers are required, separate that packet from ordinary content-editing logic.
Step 4: Route to the right Workspace product
| Product | Good for | Common operations | Route out when |
|---|---|---|---|
| Docs | formatted text documents | create docs, replace placeholders, insert text/tables/images, export | the deliverable is really a slide deck or spreadsheet |
| Sheets | tabular workflows and lightweight ops databases | append/update rows, formatting, formulas, filters, approvals, dashboards | the user needs durable database design or BI modeling |
| Slides | generated presentations and updateable decks | create slides, replace placeholders, thumbnails, batch updates | the real job is deck strategy/content design rather than API execution |
| Drive | storage, sharing, folder hierarchy, export/import | upload files, create folders, search files, permissions, shared drives | the main problem is information architecture rather than Drive API calls |
| Gmail | mailbox automation and outbound messages | drafts, send, labels, search, thread actions | the main job is email writing strategy rather than mailbox ops |
| Calendar | scheduling and event automation | create events, invite attendees, sync timing, Meet links | the core issue is meeting policy or roadmap planning |
| Forms | intake, surveys, quizzes, response pipelines | create/update forms, response collection, Form → Sheet workflows | the job is survey design methodology rather than implementation |
| Chat | workspace messaging automations | post messages, bot flows, notifications | the user needs broader chatops platform design |
| Admin SDK | directory and domain administration | users, groups, org units, licenses, settings | ordinary content workflows are being over-administered |
Step 5: Use workflow packets for common cross-service jobs
Form intake packet
- Forms captures submissions
- Sheets stores / reviews responses
- Gmail or Chat sends follow-up notifications
- Calendar schedules interviews, meetings, or reminders when needed
Approval / operations packet
- Sheet stores queue/state
- Apps Script or backend worker evaluates rows / statuses
- Gmail or Chat sends approval prompts / updates
- Drive stores generated artifacts or attachments
Reporting packet
- Sheets holds metrics or extracts
- Docs / Slides publish the narrative output
- Drive manages shared access and exports
- Calendar / Gmail handles scheduled distribution
For multi-step flows, define:
- system of record
- trigger source
- actor identity
- idempotency / duplicate-submission rule
- failure notification path
Step 6: Check quota and runtime risk
Before finalizing the plan, ask whether the workflow is:
- high-volume or bulk-edit heavy
- schedule-driven with frequent triggers
- likely to hit Apps Script execution/runtime limits
- dependent on large Drive traversals, Gmail sends, or spreadsheet write bursts
If yes:
- prefer batch operations where supported
- avoid row-by-row chatty loops when one batch call exists
- consider moving from Apps Script to a backend worker if runtime ceilings become the real constraint
Step 7: Produce a concrete execution packet
Return a compact plan with:
- chosen Workspace surface
- chosen auth model
- required scopes / admin boundaries
- primary object identifiers needed (docId, spreadsheetId, folderId, user email, calendarId, formId)
- implementation sequence
- verification step
- route-outs / risks
Examples
Example 1: Form → Sheet → email workflow
Prompt: “Create an employee feedback form, collect responses in Sheets, and send a confirmation email.”
Use this skill to:
- classify it as a multi-service workflow
- choose Apps Script or a lightweight backend depending on where the automation should live
- identify Forms + Sheets + Gmail as the service bundle
- specify auth/scopes and trigger behavior before implementation
Example 2: Shared Drive onboarding packet
Prompt: “Create a shared Drive folder structure for a new client, share it with three users, and generate a kickoff doc.”
Use this skill to:
- route Drive as the primary surface and Docs as the secondary artifact layer
- choose OAuth or service-account access based on ownership model
- list folder IDs, user emails, and sharing rules before coding
Example 3: Workspace admin request
Prompt: “Suspend a departing user, move them to a different org unit, and audit their group membership.”
Use this skill to:
- route immediately to Admin SDK
- require admin/delegated identity clarification in the execution packet
- keep admin actions separate from ordinary Docs/Drive editing logic
Example 4: Sheet-heavy approvals automation
Prompt: “Turn this spreadsheet into an approvals queue with reminder emails and status updates.”
Use this skill to:
- decide whether bound Apps Script is enough
- define status columns, reminder cadence, and Gmail integration
- check quota/runtime risk before promising a trigger-heavy solution
Best practices
1. Start with surface choice, not code snippets. 2. Separate content operations from admin operations. 3. Use Apps Script for Workspace-native ergonomics; use direct APIs when backend control or scale matters more. 4. Keep scopes narrow and state exactly whose identity is acting. 5. For multi-app workflows, define the source of truth and trigger source first. 6. Prefer batch updates over chatty per-row / per-cell loops when the API supports batching. 7. If the problem is actually writing emails, documents, surveys, dashboards, or presentations, route to the corresponding content skill after the Workspace execution packet is clear.
References
references/auth-and-scope-routing.md— OAuth vs service account vs domain-wide delegation routingreferences/service-selection-matrix.md— product-by-product routing across Docs / Sheets / Drive / Gmail / Calendar / Forms / Admin SDKreferences/workflow-patterns-and-quotas.md— common cross-service packets plus quota/runtime cautions- Google Workspace developer docs: https://developers.google.com/workspace
- Auth overview: https://developers.google.com/workspace/guides/auth-overview
- Apps Script samples: https://developers.google.com/apps-script/samples
- Apps Script quotas: https://developers.google.com/apps-script/guides/services/quotas
- Forms REST vs Apps Script comparison: https://developers.google.com/workspace/forms/api/guides/compare-rest-apps-script
{
"skill_name": "google-workspace",
"version": "1.1.0",
"evals": [
{
"id": 1,
"prompt": "I need an internal approvals workflow where a Google Form writes to Sheets, reviewers get reminder emails, and approved rows create calendar events. What Workspace surface should own this and what auth model should I start with?",
"expected_behavior": "Treats the request as a multi-service workflow, identifies Forms + Sheets + Gmail + Calendar, chooses Apps Script or a lightweight backend based on runtime ownership, and explains the auth choice before any API-specific code.",
"assertions": [
"Classifies the job as a cross-service workflow rather than a single API call",
"Names the relevant Workspace products",
"Chooses an execution surface with rationale",
"States an auth model and why it fits"
]
},
{
"id": 2,
"prompt": "Create a Google Doc titled 'Q1 2026 Report', replace {{owner}} with 'Alice', and store the document in a shared Drive folder for the team.",
"expected_behavior": "Routes to Docs plus Drive, identifies required object IDs or lookup steps, and produces an execution packet covering document creation, placeholder replacement, destination folder, and sharing/ownership considerations.",
"assertions": [
"Routes to Docs as the primary content surface and Drive as the storage/sharing surface",
"Mentions document creation plus placeholder replacement or batch update",
"Calls out the destination folder / shared Drive requirement",
"Notes ownership or permission considerations"
]
},
{
"id": 3,
"prompt": "We run a backend service that sends weekly Gmail summaries and updates a Sheet with delivery status. Should this live in Apps Script or direct REST APIs?",
"expected_behavior": "Prefers direct REST APIs for a backend-owned scheduled integration, explains why Apps Script is less ideal here, and names Gmail plus Sheets as the service bundle.",
"assertions": [
"Chooses direct REST APIs or an equivalent backend-owned route",
"Explains why Apps Script is not the best default for this case",
"Names Gmail and Sheets",
"Mentions scheduling / backend ownership as decision factors"
]
},
{
"id": 4,
"prompt": "Suspend a departing employee, move them to a different org unit, and review their group memberships across the Workspace domain.",
"expected_behavior": "Routes immediately to Admin SDK / admin APIs, separates admin identity from ordinary content operations, and calls out delegated admin or domain-wide delegation needs.",
"assertions": [
"Routes to Admin SDK rather than Docs/Drive/Gmail content APIs",
"Separates admin operations from ordinary content tasks",
"Calls out delegated admin or domain-wide delegation",
"Mentions users, groups, or org units explicitly"
]
},
{
"id": 5,
"prompt": "구글 스프레드시트 승인 큐를 만들고 상태가 바뀌면 지메일 알림을 보내고 싶어. Apps Script로 끝내도 될까?",
"expected_behavior": "Understands the Korean prompt, treats it as a Sheets + Gmail approvals workflow, evaluates whether Apps Script is sufficient, and mentions quota/runtime considerations for trigger-heavy flows.",
"assertions": [
"Correctly identifies Sheets and Gmail from the Korean prompt",
"Evaluates Apps Script as a possible route rather than blindly assuming it",
"Mentions trigger or quota/runtime considerations",
"Produces a workflow-oriented answer instead of isolated API snippets"
]
}
]
}
Auth and scope routing
Choose the actor first
Before implementation, decide who is acting:
- a real end user
- a backend/service identity
- a delegated domain admin
- an Apps Script owner / installer
Routing table
| Model | Best for | Common products | Main caveat |
|---|---|---|---|
| OAuth user flow | user-owned mailbox, calendar, Drive, docs, sheets | Gmail, Calendar, Drive, Docs, Sheets, Slides, Forms, Chat | consent + token storage must be handled cleanly |
| Service account | backend-owned or shared-resource workflows | Drive, generated docs/sheets, integrations | direct access to user-owned data may not work without sharing/delegation |
| Service account + domain-wide delegation | org-wide impersonation or admin tasks | Admin SDK, org-wide mail/calendar/drive ops | high-privilege path; narrow scopes and document who is impersonated |
| Apps Script auth | scripts intentionally living inside Workspace | Sheets, Docs, Forms, Gmail, Calendar, Drive | easy startup, but runtime and quota constraints still apply |
Scope rules
- Use the narrowest scopes that still satisfy the task.
- Separate admin scopes from ordinary content scopes.
- If the task touches someone’s mailbox or calendar, state whose identity is performing the action.
- If the user only says “send email” or “schedule meeting,” do not assume a service account can safely act as them.
Source-backed notes
- Google Workspace auth overview explicitly calls out OAuth client IDs, service accounts, scopes, and domain-wide delegation as the core decision points.
- The “Develop on Google Workspace” getting-started docs point to creating access credentials after API enablement rather than treating auth as a postscript.
Quick checks
1. Which Google account or admin identity is acting? 2. Is this user-owned content or org-admin work? 3. Does the workflow need consent, impersonation, or only shared-resource access? 4. Are the requested scopes broader than the task actually needs?
Service selection matrix
Primary surfaces
| Surface | Use when | Common operations | Avoid when |
|---|---|---|---|
| Docs | narrative or formatted documents | create docs, replace placeholders, insert text/tables/images | the real artifact is a spreadsheet or deck |
| Sheets | rows, tables, lightweight ops queues, dashboards | append rows, formulas, formatting, filters, approvals | the problem is durable database design or BI modeling |
| Slides | generated decks and presentation updates | create slides, replace placeholders, thumbnails | the main need is deck strategy/content authoring |
| Drive | file storage, folder structure, permissions | upload/search/share/export, shared drives | information architecture is the real issue |
| Gmail | mailbox automation and outbound messages | drafts, send, labels, thread actions, search | the task is copy strategy rather than mailbox ops |
| Calendar | event automation and scheduling | create/update events, attendees, Meet links | the issue is scheduling policy or planning, not API execution |
| Forms | intake, surveys, quizzes, response collection | create forms, structure questions, receive responses | the user needs survey methodology rather than implementation |
| Chat | notifications and workspace messaging flows | post messages, notify rooms/spaces, lightweight bot flows | a broader chatops platform decision is needed |
| Admin SDK | users/groups/org units/domain settings | suspend users, group membership, org structure | ordinary content work is being over-administered |
Surface choice heuristics
- If the workflow is inside Workspace and UI-triggered, Apps Script is often the shortest path.
- If the workflow is backend-owned or integrates with non-Google systems, direct REST APIs are usually cleaner.
- If the workflow spans multiple Workspace apps, define the system of record first.
- If the request includes “shared drive,” “mailbox,” “calendar invite,” or “domain user,” call out the identity and permission model explicitly.
Source-backed notes
- Google’s Apps Script samples page frames Apps Script around automating tasks, extending Workspace UIs, and integrating Google/external services.
- Google’s Forms comparison guide explicitly says there is overlap between REST and Apps Script service surfaces, which is a useful model for the broader Workspace lane too.
Workflow patterns and quotas
Common workflow packets
Form intake packet
- Forms collects responses
- Sheets stores and reviews responses
- Gmail or Chat sends confirmation / review notifications
- Calendar optionally books follow-up meetings
Approval queue packet
- Sheets acts as queue/status board
- Apps Script or backend worker processes transitions
- Gmail / Chat sends reminder and approval messages
- Drive stores generated attachments or exported artifacts
Reporting packet
- Sheets aggregates metrics or imports exports
- Docs or Slides publishes the narrative output
- Drive manages shared access and exports
- Calendar / Gmail handles scheduled distribution
Quota / runtime routing
Use Apps Script carefully when the workflow is:
- high-volume
- bulk-update heavy
- schedule-driven at short intervals
- dependent on large Drive traversals or many Gmail sends
Google’s Apps Script quota documentation explicitly warns that daily quotas and limitations can interrupt scripts. That makes quota review part of the plan, not a postmortem step.
Practical rules
1. Prefer batch operations where the API supports them. 2. Avoid row-by-row or cell-by-cell loops when one batch update can do the job. 3. If execution ceilings become the real bottleneck, move the workflow to a backend worker and keep Apps Script as the trigger/UI layer only if needed. 4. Treat quota-sensitive jobs as operational workflows with retries, notifications, and idempotency rules.
Indexed-snippet evidence worth remembering
- Yahoo Japan indexed snippet on Reddit quota discussion: “Apps Script quotas can not be raised.”
- Yahoo Japan indexed snippet on Stack Overflow / official comparison results: users repeatedly distinguish Apps Script convenience from direct REST API control.
Those snippets are not direct primary-source verification, but they are useful signals for real operator pain: convenience pushes teams toward Apps Script, while quotas and control push them back toward direct APIs or backend workers.
#!/usr/bin/env bash
# auth-setup.sh — Google Workspace authentication setup helper
# Usage:
# bash auth-setup.sh --oauth2 credentials.json
# bash auth-setup.sh --service-account service-account-key.json [--subject admin@yourdomain.com]
# bash auth-setup.sh --check
set -euo pipefail
MODE=""
CREDS_FILE=""
SUBJECT=""
TOKEN_DIR="${HOME}/.config/gws-agent"
usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Options:
--oauth2 <credentials.json> OAuth2 user auth (interactive browser flow)
--service-account <key.json> Service account auth
--subject <email> Impersonate user (for domain-wide delegation)
--check Check current auth status
--token-dir <dir> Custom token storage dir (default: ~/.config/gws-agent)
-h, --help Show this help
Examples:
$0 --oauth2 ~/credentials.json
$0 --service-account ~/sa-key.json --subject admin@company.com
$0 --check
EOF
}
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--oauth2) MODE="oauth2"; CREDS_FILE="${2:-}"; shift 2 ;;
--service-account) MODE="sa"; CREDS_FILE="${2:-}"; shift 2 ;;
--subject) SUBJECT="${2:-}"; shift 2 ;;
--token-dir) TOKEN_DIR="${2:-}"; shift 2 ;;
--check) MODE="check"; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1"; usage; exit 1 ;;
esac
done
# Verify Python and libraries
check_deps() {
if ! command -v python3 &>/dev/null; then
echo "❌ python3 not found. Install Python 3.8+"
exit 1
fi
python3 -c "import googleapiclient, google_auth_oauthlib" 2>/dev/null || {
echo "📦 Installing Google API client libraries..."
pip install --quiet google-api-python-client google-auth-httplib2 google-auth-oauthlib
}
}
# Check auth status
check_auth() {
TOKEN_FILE="${TOKEN_DIR}/token.json"
SA_FILE="${TOKEN_DIR}/service-account.json"
echo "=== Google Workspace Auth Status ==="
echo "Token dir: ${TOKEN_DIR}"
if [[ -f "$TOKEN_FILE" ]]; then
echo "✅ OAuth2 token found: $TOKEN_FILE"
python3 - <<'PYEOF'
import json, sys
from pathlib import Path
token_file = Path.home() / '.config/gws-agent/token.json'
try:
t = json.loads(token_file.read_text())
print(f" Account: {t.get('client_id','unknown')[:20]}...")
print(f" Scopes: {', '.join(t.get('scopes',[]))}")
print(f" Expired: {t.get('expiry','unknown')}")
except Exception as e:
print(f" (Could not parse token: {e})")
PYEOF
else
echo "⚠️ No OAuth2 token at: $TOKEN_FILE"
echo " Run: $0 --oauth2 credentials.json"
fi
if [[ -f "$SA_FILE" ]]; then
echo "✅ Service account key found: $SA_FILE"
python3 -c "
import json
from pathlib import Path
sa = json.loads(Path.home().joinpath('.config/gws-agent/service-account.json').read_text())
print(f' Email: {sa.get(\"client_email\",\"unknown\")}')
print(f' Project: {sa.get(\"project_id\",\"unknown\")}')
"
else
echo "⚠️ No service account key at: $SA_FILE"
fi
echo "====================================="
}
# OAuth2 setup
setup_oauth2() {
if [[ -z "$CREDS_FILE" || ! -f "$CREDS_FILE" ]]; then
echo "❌ credentials.json not found: '${CREDS_FILE}'"
echo ""
echo "To get credentials.json:"
echo " 1. Go to https://console.cloud.google.com/apis/credentials"
echo " 2. Create credentials > OAuth 2.0 Client ID > Desktop app"
echo " 3. Download JSON and provide its path"
exit 1
fi
mkdir -p "$TOKEN_DIR"
cp "$CREDS_FILE" "${TOKEN_DIR}/credentials.json"
echo "🔐 Starting OAuth2 browser flow..."
python3 - <<PYEOF
import os, json
from pathlib import Path
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
SCOPES = [
'https://www.googleapis.com/auth/documents',
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/presentations',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/chat.messages',
'https://www.googleapis.com/auth/forms.body',
]
token_dir = Path(os.environ.get('TOKEN_DIR', Path.home() / '.config/gws-agent'))
creds_file = token_dir / 'credentials.json'
token_file = token_dir / 'token.json'
flow = InstalledAppFlow.from_client_secrets_file(str(creds_file), SCOPES)
creds = flow.run_local_server(port=0)
import json
token_file.write_text(json.dumps({
'token': creds.token,
'refresh_token': creds.refresh_token,
'token_uri': creds.token_uri,
'client_id': creds.client_id,
'client_secret': creds.client_secret,
'scopes': list(creds.scopes or []),
}))
print(f"✅ Token saved to: {token_file}")
print(f" Authorized scopes: {len(creds.scopes or [])} scopes")
PYEOF
}
# Service account setup
setup_service_account() {
if [[ -z "$CREDS_FILE" || ! -f "$CREDS_FILE" ]]; then
echo "❌ Service account key file not found: '${CREDS_FILE}'"
echo ""
echo "To get a service account key:"
echo " 1. Go to https://console.cloud.google.com/iam-admin/serviceaccounts"
echo " 2. Create service account > Add key > JSON"
echo " 3. For Admin SDK: grant domain-wide delegation in Google Admin console"
exit 1
fi
mkdir -p "$TOKEN_DIR"
cp "$CREDS_FILE" "${TOKEN_DIR}/service-account.json"
python3 - <<PYEOF
import json, os
from pathlib import Path
sa_file = Path(os.environ.get('TOKEN_DIR', Path.home() / '.config/gws-agent')) / 'service-account.json'
sa = json.loads(sa_file.read_text())
print(f"✅ Service account key saved")
print(f" Email: {sa.get('client_email','?')}")
print(f" Project: {sa.get('project_id','?')}")
subject = os.environ.get('SUBJECT','')
if subject:
print(f" Subject: {subject} (domain-wide delegation)")
PYEOF
if [[ -n "$SUBJECT" ]]; then
echo "$SUBJECT" > "${TOKEN_DIR}/.subject"
echo "✅ Delegation subject saved: $SUBJECT"
fi
}
# Main
check_deps
export TOKEN_DIR SUBJECT
case "$MODE" in
oauth2) setup_oauth2 ;;
sa) setup_service_account ;;
check) check_auth ;;
"") usage; exit 0 ;;
esac
#!/usr/bin/env python3
"""
gws-helper.py — Google Workspace AI agent helper
Builds authenticated service clients for all Workspace APIs.
Usage (as a library):
from gws_helper import GWSClient
gws = GWSClient()
doc = gws.docs.documents().create(body={'title': 'Test'}).execute()
Usage (CLI):
python3 gws-helper.py --check
python3 gws-helper.py --create-doc "My Document"
python3 gws-helper.py --list-drive
python3 gws-helper.py --send-email --to alice@example.com --subject "Hello" --body "World"
"""
import argparse
import base64
import json
import os
import sys
import time
from email.mime.text import MIMEText
from pathlib import Path
from typing import Optional
# ---------------------------------------------------------------------------
# Auth helpers
# ---------------------------------------------------------------------------
TOKEN_DIR = Path(os.environ.get('GWS_TOKEN_DIR', Path.home() / '.config/gws-agent'))
SCOPES = [
'https://www.googleapis.com/auth/documents',
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/presentations',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/chat.messages',
'https://www.googleapis.com/auth/forms.body',
]
def _load_oauth2_creds():
try:
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
except ImportError:
raise RuntimeError("Run: pip install google-api-python-client google-auth-oauthlib")
token_file = TOKEN_DIR / 'token.json'
creds = None
if token_file.exists():
creds = Credentials.from_authorized_user_file(str(token_file), SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
creds_file = TOKEN_DIR / 'credentials.json'
if not creds_file.exists():
raise FileNotFoundError(
f"credentials.json not found at {creds_file}\n"
"Run: bash scripts/auth-setup.sh --oauth2 credentials.json"
)
flow = InstalledAppFlow.from_client_secrets_file(str(creds_file), SCOPES)
creds = flow.run_local_server(port=0)
token_file.write_text(creds.to_json())
return creds
def _load_sa_creds(subject: Optional[str] = None):
try:
from google.oauth2 import service_account
except ImportError:
raise RuntimeError("Run: pip install google-auth")
sa_file = TOKEN_DIR / 'service-account.json'
if not sa_file.exists():
raise FileNotFoundError(
f"Service account key not found at {sa_file}\n"
"Run: bash scripts/auth-setup.sh --service-account <key.json>"
)
sa_scopes = [
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.group',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/documents',
'https://www.googleapis.com/auth/spreadsheets',
]
creds = service_account.Credentials.from_service_account_file(
str(sa_file), scopes=sa_scopes
)
# Use saved subject if available
if not subject:
subj_file = TOKEN_DIR / '.subject'
if subj_file.exists():
subject = subj_file.read_text().strip()
if subject:
creds = creds.with_subject(subject)
return creds
def get_credentials(mode: str = 'auto', subject: Optional[str] = None):
"""Return Google credentials. mode: 'oauth2' | 'sa' | 'auto'"""
if mode == 'sa':
return _load_sa_creds(subject)
if mode == 'oauth2':
return _load_oauth2_creds()
# auto: try SA first, fall back to OAuth2
sa_file = TOKEN_DIR / 'service-account.json'
if sa_file.exists():
return _load_sa_creds(subject)
return _load_oauth2_creds()
# ---------------------------------------------------------------------------
# Client builder
# ---------------------------------------------------------------------------
class GWSClient:
"""Authenticated Google Workspace service clients."""
def __init__(self, auth_mode: str = 'auto', subject: Optional[str] = None):
try:
from googleapiclient.discovery import build
except ImportError:
raise RuntimeError("Run: pip install google-api-python-client")
creds = get_credentials(mode=auth_mode, subject=subject)
self._build = lambda svc, ver: build(svc, ver, credentials=creds)
self._creds = creds
@property
def docs(self):
return self._build('docs', 'v1')
@property
def sheets(self):
return self._build('sheets', 'v4')
@property
def slides(self):
return self._build('slides', 'v1')
@property
def drive(self):
return self._build('drive', 'v3')
@property
def gmail(self):
return self._build('gmail', 'v1')
@property
def calendar(self):
return self._build('calendar', 'v3')
@property
def chat(self):
return self._build('chat', 'v1')
@property
def forms(self):
return self._build('forms', 'v1')
@property
def admin(self):
return self._build('admin', 'directory_v1')
@property
def script(self):
return self._build('script', 'v1')
# ---------------------------------------------------------------------------
# Utility functions
# ---------------------------------------------------------------------------
def api_call_with_retry(func, *args, max_retries: int = 5, **kwargs):
"""Execute a Google API call with exponential backoff on 429/503."""
from googleapiclient.errors import HttpError
for attempt in range(max_retries):
try:
return func(*args, **kwargs).execute()
except HttpError as e:
if e.resp.status in (429, 503) and attempt < max_retries - 1:
wait = (2 ** attempt) + 0.1
print(f" Rate limit (attempt {attempt+1}/{max_retries}), waiting {wait:.1f}s...")
time.sleep(wait)
else:
raise
def create_doc_from_template(drive, docs, template_id: str, replacements: dict,
dest_folder_id: Optional[str] = None) -> str:
"""Clone a template Google Doc and fill placeholders. Returns new doc ID."""
body = {'name': replacements.get('{{title}}', 'New Document')}
if dest_folder_id:
body['parents'] = [dest_folder_id]
copy = api_call_with_retry(drive.files().copy, fileId=template_id, body=body)
new_id = copy['id']
requests = [
{'replaceAllText': {
'containsText': {'text': k, 'matchCase': False},
'replaceText': v
}}
for k, v in replacements.items()
]
if requests:
api_call_with_retry(
docs.documents().batchUpdate,
documentId=new_id, body={'requests': requests}
)
return new_id
def send_email(gmail, to: str, subject: str, body: str,
attachments: Optional[list] = None) -> dict:
"""Send a plain text email, optionally with file attachments."""
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
if attachments:
msg = MIMEMultipart()
msg['to'] = to
msg['subject'] = subject
msg.attach(MIMEText(body))
for path in attachments:
path = Path(path)
with open(path, 'rb') as f:
part = MIMEApplication(f.read(), Name=path.name)
part['Content-Disposition'] = f'attachment; filename="{path.name}"'
msg.attach(part)
else:
msg = MIMEText(body)
msg['to'] = to
msg['subject'] = subject
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode()
return api_call_with_retry(
gmail.users().messages().send, userId='me', body={'raw': raw}
)
def append_rows_to_sheet(sheets, spreadsheet_id: str, sheet_name: str, rows: list) -> dict:
"""Append rows to a Google Sheet."""
return api_call_with_retry(
sheets.spreadsheets().values().append,
spreadsheetId=spreadsheet_id,
range=f'{sheet_name}!A1',
valueInputOption='USER_ENTERED',
insertDataOption='INSERT_ROWS',
body={'values': rows}
)
def export_doc_to_pdf(drive, doc_id: str, output_path: str) -> str:
"""Export a Google Doc to PDF and save locally."""
import io
from googleapiclient.http import MediaIoBaseDownload
request = drive.files().export_media(fileId=doc_id, mimeType='application/pdf')
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, request)
done = False
while not done:
_, done = downloader.next_chunk()
with open(output_path, 'wb') as f:
f.write(fh.getvalue())
return output_path
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description='Google Workspace AI agent helper')
parser.add_argument('--auth-mode', choices=['auto', 'oauth2', 'sa'], default='auto')
parser.add_argument('--subject', help='Delegation subject email')
parser.add_argument('--check', action='store_true', help='Check auth and list APIs')
# Docs
parser.add_argument('--create-doc', metavar='TITLE', help='Create a new Google Doc')
# Sheets
parser.add_argument('--create-sheet', metavar='TITLE', help='Create a new Google Sheet')
# Drive
parser.add_argument('--list-drive', action='store_true', help='List recent Drive files')
parser.add_argument('--create-folder', metavar='NAME', help='Create a Drive folder')
parser.add_argument('--share', metavar='FILE_ID', help='Share a file (use with --email)')
parser.add_argument('--email', help='Email address for sharing')
# Gmail
parser.add_argument('--send-email', action='store_true', help='Send an email')
parser.add_argument('--to', help='Recipient email address')
parser.add_argument('--subject', dest='email_subject', help='Email subject')
parser.add_argument('--body', help='Email body text')
parser.add_argument('--attach', nargs='+', help='File paths to attach')
# Calendar
parser.add_argument('--list-events', action='store_true', help='List today\'s events')
args = parser.parse_args()
if args.check:
from pathlib import Path
token_dir = TOKEN_DIR
print(f"Token dir: {token_dir}")
print(f"OAuth2 token: {'✅ found' if (token_dir/'token.json').exists() else '❌ missing'}")
print(f"Service account: {'✅ found' if (token_dir/'service-account.json').exists() else '❌ missing'}")
return
gws = GWSClient(auth_mode=args.auth_mode, subject=args.subject)
if args.create_doc:
doc = gws.docs.documents().create(body={'title': args.create_doc}).execute()
print(json.dumps({
'documentId': doc['documentId'],
'title': doc.get('title'),
'url': f"https://docs.google.com/document/d/{doc['documentId']}/edit"
}, indent=2))
elif args.create_sheet:
ss = gws.sheets.spreadsheets().create(body={
'properties': {'title': args.create_sheet}
}).execute()
print(json.dumps({
'spreadsheetId': ss['spreadsheetId'],
'title': ss['properties']['title'],
'url': ss['spreadsheetUrl']
}, indent=2))
elif args.list_drive:
results = gws.drive.files().list(
pageSize=20,
fields='files(id, name, mimeType, modifiedTime, webViewLink)',
orderBy='modifiedTime desc'
).execute()
files = results.get('files', [])
print(json.dumps(files, indent=2))
elif args.create_folder:
folder = gws.drive.files().create(body={
'name': args.create_folder,
'mimeType': 'application/vnd.google-apps.folder'
}, fields='id,name,webViewLink').execute()
print(json.dumps(folder, indent=2))
elif args.share and args.email:
perm = gws.drive.permissions().create(
fileId=args.share,
body={'type': 'user', 'role': 'writer', 'emailAddress': args.email},
sendNotificationEmail=True
).execute()
print(f"✅ Shared {args.share} with {args.email} (permissionId={perm['id']})")
elif args.send_email:
if not (args.to and args.email_subject and args.body):
print("❌ --send-email requires --to, --subject, and --body")
sys.exit(1)
send_email(gws.gmail, args.to, args.email_subject, args.body,
attachments=args.attach)
print(f"✅ Email sent to {args.to}")
elif args.list_events:
from datetime import datetime, timedelta
now = datetime.utcnow().isoformat() + 'Z'
end = (datetime.utcnow() + timedelta(hours=24)).isoformat() + 'Z'
result = gws.calendar.events().list(
calendarId='primary',
timeMin=now, timeMax=end,
singleEvents=True, orderBy='startTime'
).execute()
events = result.get('items', [])
for ev in events:
start = ev.get('start', {})
dt = start.get('dateTime', start.get('date', '?'))
print(f" {dt} — {ev.get('summary', '(no title)')}")
else:
parser.print_help()
if __name__ == '__main__':
main()
N:google-workspace
D:Plan and execute Google Workspace operations across Docs, Sheets, Slides, Drive, Gmail, Calendar, Forms, Chat, and Admin SDK by choosing the right surface first: Apps Script, direct REST API, or admin-only APIs. Use when the user needs Workspace automation, spreadsheet/document/file workflows, Gmail sending, calendar scheduling, Forms pipelines, Drive sharing, or domain user/admin changes.
G:google-workspace google-docs google-sheets google-slides google-drive gmail google-calendar google-chat google-forms admin-sdk apps-script spreadsheet document shared-drive send-email approval-flow domain-user
F:Claude|Gemini|Codex|OpenCode
T:Bash|Read|Write|Edit|Glob|Grep
U[5]:
Route work across Docs, Sheets, Slides, Drive, Gmail, Calendar, Forms, Chat, and Admin SDK
Choose Apps Script vs direct REST API vs admin-only APIs before coding
Pick the auth model: OAuth, service account, domain-wide delegation, or Apps Script auth
Design cross-service workflows like Form→Sheet→Gmail/Calendar or approval queues
Review quota/runtime risk and route-outs before implementation
S[4]{n,action,details}:
1,Classify Request,Identify products operation actor execution home and trigger cadence
2,Choose Surface,Pick Apps Script direct REST API or Admin SDK based on workflow ownership and limits
3,Choose Auth,Match OAuth service account or delegated admin access to the task
4,Execute Packet,Return the service bundle scopes IDs sequence verification and risks
R[4]:
Start with surface choice and auth model instead of jumping to snippets
Separate admin operations from ordinary content editing workflows
Prefer batch operations and watch Apps Script quota/runtime limits on heavy jobs
Route no-code cross-app orchestration requests out instead of turning this into a Zapier or n8n tutorial
Related skills
How it compares
Use google-workspace for API routing and auth planning; use document-format skills when the deliverable is file content design rather than API integration.
FAQ
When does google-workspace recommend Apps Script?
google-workspace recommends Apps Script when workflows center on Sheets, Docs, or Forms with triggers, custom menus, or bound scripts inside Workspace. Backend services spanning external systems should use direct REST APIs with google-api-python-client instead.
Which Google products does google-workspace cover?
google-workspace routes automation across Docs, Sheets, Slides, Drive, Gmail, Calendar, Forms, Chat, and Admin SDK. Version 1.1.0 includes three reference files for auth models, per-product routing, and quota-aware workflow patterns.