{“content”:”---\nname: google-workspace\ndescription: Gmail, Calendar, Drive, Contacts, Sheets, and Docs integration for Hermes. Uses Hermes-managed OAuth2 setup, prefers the Google Workspace CLI (gws) when available for broader API coverage, and falls back to the Python client libraries otherwise.\nversion: 1.0.0\nauthor: Nous Research\nlicense: MIT\nmetadata:\n hermes:\n tags: [Google, Gmail, Calendar, Drive, Sheets, Docs, Contacts, Email, OAuth]\n homepage: https://github.com/NousResearch/hermes-agent\n related_skills: [himalaya]\n---\n\n# Google Workspace\n\nGmail, Calendar, Drive, Contacts, Sheets, and Docs — through Hermes-managed OAuth and a thin CLI wrapper. When gws is installed, the skill uses it as the execution backend for broader Google Workspace coverage; otherwise it falls back to the bundled Python client implementation.\n\n## References\n\n- references/gmail-search-syntax.md — Gmail search operators (is:unread, from:, newer_than:, etc.)\n\n## Scripts\n\n- scripts/setup.py — OAuth2 setup (run once to authorize)\n- scripts/google_api.py — compatibility wrapper CLI. It prefers gws for operations when available, while preserving Hermes’ existing JSON output contract.\n\n## First-Time Setup\n\nThe setup is fully non-interactive — you drive it step by step so it works\non CLI, Telegram, Discord, or any platform.\n\nDefine a shorthand first:\n\nbash\nGSETUP=\"python ${HERMES_HOME:-$HOME/.hermes}/skills/productivity/google-workspace/scripts/setup.py\"\n\n\n### Step 0: Check if already set up\n\nbash\n$GSETUP --check\n\n\nIf it prints AUTHENTICATED, skip to Usage — setup is already done.\n\n### Step 1: Triage — ask the user what they need\n\nBefore starting OAuth setup, ask the user TWO questions:\n\nQuestion 1: “What Google services do you need? Just email, or also\nCalendar/Drive/Sheets/Docs?”\n\n- Email only → They don’t need this skill at all. Use the himalaya skill\n instead — it works with a Gmail App Password (Settings → Security → App\n Passwords) and takes 2 minutes to set up. No Google Cloud project needed.\n Load the himalaya skill and follow its setup instructions.\n\n- Email + Calendar → Continue with this skill, but use\n --services email,calendar during auth so the consent screen only asks for\n the scopes they actually need.\n\n- Calendar/Drive/Sheets/Docs only → Continue with this skill and use a\n narrower --services set like calendar,drive,sheets,docs.\n\n- Full Workspace access → Continue with this skill and use the default\n all service set.\n\nQuestion 2: “Does your Google account use Advanced Protection (hardware\nsecurity keys required to sign in)? If you’re not sure, you probably don’t\n— it’s something you would have explicitly enrolled in.”\n\n- No / Not sure → Normal setup. Continue below.\n- Yes → Their Workspace admin must add the OAuth client ID to the org’s\n allowed apps list before Step 4 will work. Let them know upfront.\n\n### Step 2: Create OAuth credentials (one-time, ~5 minutes)\n\nTell the user:\n\n> You need a Google Cloud OAuth client. This is a one-time setup:\n>\n> 1. Create or select a project:\n> https://console.cloud.google.com/projectselector2/home/dashboard\n> 2. Enable the required APIs from the API Library:\n> https://console.cloud.google.com/apis/library\n> Enable: Gmail API, Google Calendar API, Google Drive API,\n> Google Sheets API, Google Docs API, People API\n> 3. Create the OAuth client here:\n> https://console.cloud.google.com/apis/credentials\n> Credentials → Create Credentials → OAuth 2.0 Client ID\n> 4. Application type: “Desktop app” → Create\n> 5. If the app is still in Testing, add the user’s Google account as a test user here:\n> https://console.cloud.google.com/auth/audience\n> Audience → Test users → Add users\n> 6. Download the JSON file and tell me the file path\n>\n> Important Hermes CLI note: if the file path starts with /, do NOT send only the bare path as its own message in the CLI, because it can be mistaken for a slash command. Send it in a sentence instead, like:\n> The JSON file path is: /home/user/Downloads/client_secret_....json\n\nOnce they provide the path:\n\nbash\n$GSETUP --client-secret /path/to/client_secret.json\n\n\nIf they paste the raw client ID / client secret values instead of a file path,\nwrite a valid Desktop OAuth JSON file for them yourself, save it somewhere\nexplicit (for example ~/Downloads/hermes-google-client-secret.json), then run\n--client-secret against that file.\n\n### Step 3: Get authorization URL\n\nUse the service set chosen in Step 1. Examples:\n\nbash\n$GSETUP --auth-url --services email,calendar --format json\n$GSETUP --auth-url --services calendar,drive,sheets,docs --format json\n$GSETUP --auth-url --services all --format json\n\n\nThis returns JSON with an auth_url field and also saves the exact URL to\n~/.hermes/google_oauth_last_url.txt.\n\nAgent rules for this step:\n- Extract the auth_url field and send that exact URL to the user as a single line.\n- Tell the user that the browser will likely fail on http://localhost:1 after approval, and that this is expected.\n- Tell them to copy the ENTIRE redirected URL from the browser address bar.\n- If the user gets Error 403: access_denied, send them directly to https://console.cloud.google.com/auth/audience to add themselves as a test user.\n\n### Step 4: Exchange the code\n\nThe user will paste back either a URL like http://localhost:1/?code=4/0A...&scope=...\nor just the code string. Either works. The --auth-url step stores a temporary\npending OAuth session locally so --auth-code can complete the PKCE exchange\nlater, even on headless systems:\n\nbash\n$GSETUP --auth-code \"THE_URL_OR_CODE_THE_USER_PASTED\" --format json\n\n\nIf --auth-code fails because the code expired, was already used, or came from\nan older browser tab, it now returns a fresh fresh_auth_url. In that case,\nimmediately send the new URL to the user and have them retry with the newest\nbrowser redirect only.\n\n### Step 5: Verify\n\nbash\n$GSETUP --check\n\n\nShould print AUTHENTICATED. Setup is complete — token refreshes automatically from now on.\n\n### Notes\n\n- Token is stored at ~/.hermes/google_token.json and auto-refreshes.\n- Pending OAuth session state/verifier are stored temporarily at ~/.hermes/google_oauth_pending.json until exchange completes.\n- If gws is installed, google_api.py points it at the same ~/.hermes/google_token.json credentials file. Users do not need to run a separate gws auth login flow.\n- To revoke: $GSETUP --revoke\n\n## Multi-Account Support\n\nEach account needs its own token file and client secret file. Two env vars control this:\n\nbash\nGOOGLE_TOKEN_FILE=~/.hermes/google_token_tttttttttze.json\nGOOGLE_CLIENT_SECRET_FILE=~/.hermes/google_client_secret_tttttttttze.json\n\n\nBoth google_api.py and setup.py support these env vars. Without them, they default to ~/.hermes/google_token.json and ~/.hermes/google_client_secret.json.\n\n## Token File Locations (Sozo’s Setup)\n\n- ida.czia@gmail.com: ~/.hermes/google_token.json (default)\n- tttttttttze@gmail.com: ~/.hermes/google_token_tttttttttze.json\n\n## Usage\n\nAll commands go through the API script. Set GAPI as a shorthand:\n\nbash\nGAPI=\"python ${HERMES_HOME:-$HOME/.hermes}/skills/productivity/google-workspace/scripts/google_api.py\"\n\n\nFor the second account, prefix the env vars:\n\nbash\nGOOGLE_TOKEN_FILE=~/.hermes/google_token_tttttttttze.json \\\nGOOGLE_CLIENT_SECRET_FILE=~/.hermes/google_client_secret_tttttttttze.json \\\n$GAPI gmail search \"is:unread\" --max 10\n\n\n### Gmail\n\nbash\n# Search (returns JSON array with id, from, subject, date, snippet)\n$GAPI gmail search \"is:unread\" --max 10\n$GAPI gmail search \"from:boss@company.com newer_than:1d\"\n$GAPI gmail search \"has:attachment filename:pdf newer_than:7d\"\n\n# Read full message (returns JSON with body text)\n$GAPI gmail get MESSAGE_ID\n\n# Send\n$GAPI gmail send --to user@example.com --subject \"Hello\" --body \"Message text\"\n$GAPI gmail send --to user@example.com --subject \"Report\" --body \"<h1>Q4</h1><p>Details...</p>\" --html\n$GAPI gmail send --to user@example.com --subject \"Hello\" --from '\"Research Agent\" <user@example.com>' --body \"Message text\"\n\n# Reply (automatically threads and sets In-Reply-To)\n$GAPI gmail reply MESSAGE_ID --body \"Thanks, that works for me.\"\n$GAPI gmail reply MESSAGE_ID --from '\"Support Bot\" <user@example.com>' --body \"Thanks\"\n\n# Labels\n$GAPI gmail labels\n$GAPI gmail modify MESSAGE_ID --add-labels LABEL_ID\n$GAPI gmail modify MESSAGE_ID --remove-labels UNREAD\n\n\n### Calendar\n\nbash\n# List events (defaults to next 7 days)\n$GAPI calendar list\n$GAPI calendar list --start 2026-03-01T00:00:00Z --end 2026-03-07T23:59:59Z\n\n# Create event (ISO 8601 with timezone required)\n$GAPI calendar create --summary \"Team Standup\" --start 2026-03-01T10:00:00-06:00 --end 2026-03-01T10:30:00-06:00\n$GAPI calendar create --summary \"Lunch\" --start 2026-03-01T12:00:00Z --end 2026-03-01T13:00:00Z --location \"Cafe\"\n$GAPI calendar create --summary \"Review\" --start 2026-03-01T14:00:00Z --end 2026-03-01T15:00:00Z --attendees \"alice@co.com,bob@co.com\"\n\n# Delete event\n$GAPI calendar delete EVENT_ID\n\n\n### Drive\n\nbash\n$GAPI drive search \"quarterly report\" --max 10\n$GAPI drive search \"mimeType='application/pdf'\" --raw-query --max 5\n\n\n### Contacts\n\nbash\n$GAPI contacts list --max 20\n\n\n### Sheets\n\nbash\n# Read\n$GAPI sheets get SHEET_ID \"Sheet1!A1:D10\"\n\n# Write\n$GAPI sheets update SHEET_ID \"Sheet1!A1:B2\" --values '[[\"Name\",\"Score\"],[\"Alice\",\"95\"]]'\n\n# Append rows\n$GAPI sheets append SHEET_ID \"Sheet1!A:C\" --values '[[\"new\",\"row\",\"data\"]]'\n\n\n### Docs\n\nbash\n$GAPI docs get DOC_ID\n\n\n## Output Format\n\nAll commands return JSON. Parse with jq or read directly. Key fields:\n\n- Gmail search: [{id, threadId, from, to, subject, date, snippet, labels}]\n- Gmail get: {id, threadId, from, to, subject, date, labels, body}\n- Gmail send/reply: {status: \"sent\", id, threadId}\n- Calendar list: [{id, summary, start, end, location, description, htmlLink}]\n- Calendar create: {status: \"created\", id, summary, htmlLink}\n- Drive search: [{id, name, mimeType, modifiedTime, webViewLink}]\n- Contacts list: [{name, emails: [...], phones: [...]}]\n- Sheets get: [[cell, cell, ...], ...]\n\n## Rules\n\n1. Never send email or create/delete events without confirming with the user first. Show the draft content and ask for approval.\n2. Check auth before first use — run setup.py --check. If it fails, guide the user through setup.\n3. Use the Gmail search syntax reference for complex queries — load it with skill_view(\"google-workspace\", file_path=\"references/gmail-search-syntax.md\").\n4. Calendar times must include timezone — always use ISO 8601 with offset (e.g., 2026-03-01T10:00:00-06:00) or UTC (Z).\n5. Respect rate limits — avoid rapid-fire sequential API calls. Batch reads when possible.\n\n## Troubleshooting\n\n| Problem | Fix |\n|---------|-----|\n| NOT_AUTHENTICATED | Run setup Steps 2-5 above |\n| REFRESH_FAILED | Token revoked or expired — redo Steps 3-5 |\n| HttpError 403: Insufficient Permission | Missing API scope — $GSETUP --revoke then redo Steps 3-5 |\n| HttpError 403: Access Not Configured | API not enabled — user needs to enable it in Google Cloud Console |\n| ModuleNotFoundError | Run $GSETUP --install-deps |\n| Advanced Protection blocks auth | Workspace admin must allowlist the OAuth client ID |\n| invalid_grant: code_verifier or verifier is not needed | PKCE bug in setup.py — use google_oauth_direct.py instead (see below) |\n| Browser mismatch during OAuth | Copy-paste the auth URL manually into the correct Chrome profile instead of letting it auto-open. |\n| Windows Console UnicodeEncodeError | Windows CMD/PowerShell default CP1252 encoding crashes on Chinese/emoji. Write to a UTF-8 text file instead of printing. |\n\n## Alternative OAuth Script (PKCE Workaround)\n\nIf --auth-code fails with invalid_grant: code_verifier or verifier is not needed, use the standalone script instead:\n\nbash\npython3 ~/.hermes/google_oauth_direct.py\n# → prints auth URL. Send to user, user clicks, pastes back http://localhost:1/?code=...\npython3 ~/.hermes/google_oauth_direct.py \"4/0Aci...\"\n# → exchanges code, saves token to ~/.hermes/google_token_tttttttttze.json\n\n\nThe script is at: ~/.hermes/google_oauth_direct.py\n\n## Revoking Access\n\nbash\n$GSETUP --revoke\n\n”}