| name | google-workspace |
| description | Gmail, Calendar, Drive, Docs, Sheets, and Contacts via gog CLI. |
| version | 2.0.0 |
| author | openclaw |
| license | MIT |
| platforms | ["linux","macos","windows"] |
| metadata | {"hermes":{"tags":["Google","Gmail","Calendar","Drive","Sheets","Docs","Contacts","Email","OAuth"],"homepage":"https://gogcli.sh","related_skills":["himalaya"]}} |
Google Workspace
Gmail, Calendar, Drive, Contacts, Sheets, and Docs via the gog CLI. After
setup you call gog commands directly — no wrapper scripts.
References
references/gmail-search-syntax.md — Gmail search operators
My Calendars
Calendar IDs are stored as sops secrets and exposed as environment variables (see Required Secrets).
| Label | Env var / Calendar ID | Notes |
|---|
| Personal | $CAL_PERSONAL (alias: primary) | Primary personal calendar |
| Serebris | $CAL_SEREBRIS | Shared calendar: Rafa: prefix = me only, Raquel: prefix = partner only, unprefixed = both of us |
| Legal | $CAL_LEGAL | Legal matters |
| Birthdays | $CAL_BIRTHDAYS | Birthday reminders |
| Holidays in Spain | es.spain#holiday@group.v.calendar.google.com | Spanish public holidays (public ID, not a secret) |
| Work | $CAL_WORK | Work calendar — read-only |
When aggregating the schedule, query all six Google Calendar API calendars and merge results by time. For Serebris, include events prefixed Rafa: and all unprefixed events (those prefixed Raquel: apply to my partner only, not to me). The authorized account for all Google Calendar API calls is ereslibre@gmail.com.
Required Secrets
These sops secrets must exist and be exported as environment variables before using calendar features:
| Env var | Sops secret path | Value |
|---|
CAL_PERSONAL | hermes/calendars/personal | Personal Google Calendar ID |
CAL_SEREBRIS | hermes/calendars/serebris | Serebris shared calendar ID |
CAL_LEGAL | hermes/calendars/legal | Legal calendar ID |
CAL_BIRTHDAYS | hermes/calendars/birthdays | Birthdays calendar ID |
CAL_WORK | hermes/calendars/work | Work Google Calendar ID |
Scripts
scripts/setup.py — one-time OAuth setup
First-Time Setup
Define a shorthand:
GSETUP="python ${HERMES_HOME:-$HOME/.hermes}/skills/productivity/google-workspace/scripts/setup.py"
Step 0: Check if already set up
$GSETUP --check
If it prints AUTHENTICATED, skip to Usage.
Step 1: Triage
Ask the user:
"What Google services do you need?"
- Email only → use the
himalaya skill instead (simpler App Password setup).
- Everything else → continue here.
Step 2: Create OAuth credentials (one-time, ~5 minutes)
Tell the user:
- Create or select a Google Cloud project:
https://console.cloud.google.com/projectselector2/home/dashboard
- Enable APIs: Gmail, Google Calendar, Google Drive, Google Sheets, Google Docs, People API
https://console.cloud.google.com/apis/library
- Create credentials → OAuth 2.0 Client ID → Application type: Desktop app
https://console.cloud.google.com/apis/credentials
- If the app is in Testing, add your Google account as a test user:
https://console.cloud.google.com/auth/audience
- Download the JSON file and share the file path with me.
Note: if the path starts with /, send it in a sentence to avoid slash-command
interpretation: "The path is: /home/user/Downloads/client_secret.json"
Once they provide the path:
$GSETUP --credentials /path/to/client_secret.json
Step 3: Authorize
Ask for the user's Google account email address, then run the interactive flow:
$GSETUP --auth-flow --email user@gmail.com
gog will print a URL. Send it to the user. Tell them:
- Open the URL in a browser and authorize.
- The browser will redirect to
http://127.0.0.1:PORT/... and show an error — that's expected.
- Copy the full redirect URL from the browser address bar and paste it at the prompt.
If the user gets 403: access_denied, send them to add themselves as a test user:
https://console.cloud.google.com/auth/audience
Step 4: Verify
$GSETUP --check
Should print AUTHENTICATED. Note the email for use in commands below.
Revoking
$GSETUP --revoke --email user@gmail.com
Usage
Call gog directly. Always pass --account EMAIL (replace with the authorized
account) and --no-input for automation. Use --json to get structured output.
Set a shorthand to avoid repeating the account:
GOG="gog --account user@gmail.com --no-input"
Or export GOG_ACCOUNT=user@gmail.com to omit --account from every command.
Gmail
gog gmail search 'is:unread' --max 10 --json --account EMAIL --no-input
gog gmail search 'newer_than:7d from:boss' --max 20 --json --account EMAIL --no-input
gog gmail messages search 'in:inbox' --max 20 --json --account EMAIL --no-input
gog gmail get MESSAGE_ID --json --account EMAIL --no-input
gog gmail send --to a@b.com --subject "Hi" --body-file - --account EMAIL <<'EOF'
Hi,
Message body here.
Regards
EOF
gog gmail send --to a@b.com --subject "Hi" --body "Hello" --account EMAIL
gog gmail send --to a@b.com --subject "Hi" --body-html "<p>Hello</p>" --account EMAIL
gog gmail send --to a@b.com --subject "Re: Hi" --body "Thanks" \
--reply-to-message-id MESSAGE_ID --account EMAIL
gog gmail drafts create --to a@b.com --subject "Draft" --body "..." --json --account EMAIL
gog gmail drafts send DRAFT_ID --account EMAIL
gog gmail labels list --json --account EMAIL --no-input
Calendar
gog calendar calendars --json --account ereslibre@gmail.com --no-input
for CAL in \
"$CAL_PERSONAL" \
"$CAL_SEREBRIS" \
"$CAL_LEGAL" \
"$CAL_BIRTHDAYS" \
"$CAL_WORK" \
"es.spain#holiday@group.v.calendar.google.com"
do
gog calendar events "$CAL" --from DATE --to DATE --json --account ereslibre@gmail.com --no-input
done
gog calendar events "$CAL_PERSONAL" --from 2026-06-01 --to 2026-06-30 --json --account ereslibre@gmail.com --no-input
gog calendar events "$CAL_SEREBRIS" \
--from 2026-06-01 --to 2026-06-30 --json --account ereslibre@gmail.com --no-input
gog calendar events "$CAL_PERSONAL" --from 2026-06-24T09:00:00+02:00 --to 2026-06-24T18:00:00+02:00 \
--json --account ereslibre@gmail.com --no-input
gog calendar create primary --summary "Team Standup" \
--from 2026-06-25T10:00:00+02:00 --to 2026-06-25T10:30:00+02:00 \
--json --account EMAIL
gog calendar create primary --summary "Review" \
--from 2026-06-25T14:00:00+02:00 --to 2026-06-25T15:00:00+02:00 \
--location "Room 1" --attendee alice@co.com --attendee bob@co.com \
--json --account EMAIL
gog calendar update primary EVENT_ID --summary "New Title" --account EMAIL
gog calendar colors --account EMAIL
gog calendar delete primary EVENT_ID --force --account EMAIL
gog calendar freebusy alice@co.com,bob@co.com \
--from 2026-06-25T00:00:00Z --to 2026-06-26T00:00:00Z \
--json --account EMAIL --no-input
Drive
gog drive search "quarterly report" --max 10 --json --account EMAIL --no-input
gog drive get FILE_ID --json --account EMAIL --no-input
gog drive upload /path/to/file.pdf --json --account EMAIL
gog drive upload /path/to/image.png --parent FOLDER_ID --name "Logo.png" --json --account EMAIL
gog drive download FILE_ID --out /path/to/output.pdf --account EMAIL
gog drive download DOC_ID --format txt --out /tmp/doc.txt --account EMAIL
gog drive mkdir "Reports" --json --account EMAIL
gog drive mkdir "Q4" --parent FOLDER_ID --json --account EMAIL
gog drive share FILE_ID --to user --email alice@example.com --role reader --json --account EMAIL
gog drive share FILE_ID --to anyone --role reader --account EMAIL
gog drive delete FILE_ID --force --account EMAIL
Contacts
gog contacts list --max 50 --json --account EMAIL --no-input
gog contacts search "John" --json --account EMAIL --no-input
Sheets
gog sheets get SHEET_ID "Sheet1!A1:D10" --json --account EMAIL --no-input
gog sheets update SHEET_ID "Sheet1!A1:B2" \
--values-json '[["Name","Score"],["Alice","95"]]' \
--input USER_ENTERED --account EMAIL
gog sheets append SHEET_ID "Sheet1!A:C" \
--values-json '[["new","row","data"]]' \
--insert INSERT_ROWS --account EMAIL
gog sheets clear SHEET_ID "Sheet1!A2:Z" --account EMAIL
gog sheets create "Q4 Budget" --json --account EMAIL
Docs
gog docs cat DOC_ID --account EMAIL --no-input
gog docs export DOC_ID --format txt --out /tmp/doc.txt --account EMAIL
gog docs export DOC_ID --format pdf --out /tmp/doc.pdf --account EMAIL
gog docs create "Meeting Notes" --json --account EMAIL
gog docs write DOC_ID --append --text "New paragraph." --account EMAIL
gog docs write DOC_ID --append --markdown --text "## Status\n\n- Item one" --account EMAIL
Email Formatting
- Prefer plain text. Use
--body-file - with a heredoc for multi-paragraph messages.
--body does not unescape \n. Use a heredoc or $'Line 1\n\nLine 2' for inline newlines.
- Use
--body-html only when rich formatting is needed.
Rules
- Never send email, create/delete calendar events, delete Drive files, or share files
without confirming with the user first. Show what will be done and get approval.
Prefer
drive delete (permanent) only after explicit confirmation; otherwise omit
--force on calendar delete and let the user confirm.
- Check auth before first use — run
$GSETUP --check. If it fails, guide setup.
- Use the Gmail search syntax reference for complex queries:
skill_view("google-workspace", file_path="references/gmail-search-syntax.md")
- Calendar times must include timezone — ISO 8601 with offset or UTC
Z.
- Avoid rapid sequential API calls — batch reads when possible.
Troubleshooting
| Problem | Fix |
|---|
NOT_AUTHENTICATED | Run setup Steps 2–5 above |
gog: command not found | gog not in PATH; check system packages |
403: access_denied | Add Google account as test user in Cloud Console |
| Auth code expired | Re-run --auth-url, use the newest redirect URL only |
permission denied or scope error | $GSETUP --revoke --email EMAIL then redo Steps 3–5 with full services |
GOG_KEYRING_PASSWORD not set | Add hermes/gogcli_keyring_password to sops secrets |
CAL_* env vars empty/unset | Add the corresponding hermes/calendars/* entries to sops secrets and export them |
| Rotated OAuth client secret | Download new client_secret.json → $GSETUP --revoke --email ereslibre@gmail.com → $GSETUP --credentials /path/to/new.json → $GSETUP --auth-flow --email ereslibre@gmail.com |
| Calendar not found / permission error | Run gog calendar calendars --json --account ereslibre@gmail.com --no-input to list accessible calendars |