SKILL.md
SKILL.mdBrowse 7 files
3,706 tokens
13,926 bytes
Token encoding: o200k_base
Snapshot 24fd22b
1---2name: google-workspace3description: "Gmail, Calendar, Drive, Docs, Sheets via gws CLI or Python."4version: 1.2.05author: Nous Research6license: MIT7platforms: [linux, macos, windows]8required_credential_files:9 - path: google_token.json10 description: Google OAuth2 token (created by setup script)11 - path: google_client_secret.json12 description: Google OAuth2 client credentials (downloaded from Google Cloud Console)13metadata:14 hermes:15 tags: [Google, Gmail, Calendar, Drive, Sheets, Docs, Contacts, Email, OAuth]16 homepage: https://github.com/NousResearch/hermes-agent17 related_skills: [himalaya]18---19 20# Google Workspace21 22Gmail, 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.23 24## References25 26- `references/gmail-search-syntax.md` — Gmail search operators (is:unread, from:, newer_than:, etc.)27- `references/daily-brief.md` — daily/morning brief procedure: schedule + conflicts + meeting prep + urgent mail from Gmail and Calendar. Load it when the user asks for a morning brief, meeting preparation, or "what's on my calendar and what email needs attention."28 29## Scripts30 31- `scripts/setup.py` — OAuth2 setup (run once to authorize)32- `scripts/google_api.py` — compatibility wrapper CLI. It prefers `gws` for operations when available, while preserving Hermes' existing JSON output contract.33 34## First-Time Setup35 36The setup is fully non-interactive — you drive it step by step so it works37on CLI, Telegram, Discord, or any platform.38 39Define a shorthand first:40 41```bash42GSETUP="python ${HERMES_HOME:-$HOME/.hermes}/skills/productivity/google-workspace/scripts/setup.py"43```44 45### Step 0: Check if already set up46 47```bash48$GSETUP --check49```50 51If it prints `AUTHENTICATED`, skip to Usage — setup is already done.52 53### Step 1: Triage — ask the user what they need54 55Before starting OAuth setup, ask the user TWO questions:56 57**Question 1: "What Google services do you need? Just email, or also58Calendar/Drive/Sheets/Docs?"**59 60- **Email only** → They don't need this skill at all. Use the `himalaya` skill61 instead — it works with a Gmail App Password (Settings → Security → App62 Passwords) and takes 2 minutes to set up. No Google Cloud project needed.63 Load the himalaya skill and follow its setup instructions.64 65- **Email + Calendar** → Continue with this skill, but use66 `--services email,calendar` during auth so the consent screen only asks for67 the scopes they actually need.68 69- **Calendar/Drive/Sheets/Docs only** → Continue with this skill and use a70 narrower `--services` set like `calendar,drive,sheets,docs`.71 72- **Full Workspace access** → Continue with this skill and use the default73 `all` service set.74 75**Question 2: "Does your Google account use Advanced Protection (hardware76security keys required to sign in)? If you're not sure, you probably don't77— it's something you would have explicitly enrolled in."**78 79- **No / Not sure** → Normal setup. Continue below.80- **Yes** → Their Workspace admin must add the OAuth client ID to the org's81 allowed apps list before Step 4 will work. Let them know upfront.82 83### Step 2: Create OAuth credentials (one-time, ~5 minutes)84 85Tell the user:86 87> You need a Google Cloud OAuth client. This is a one-time setup:88>89> 1. Create or select a project:90> https://console.cloud.google.com/projectselector2/home/dashboard91> 2. Enable the required APIs from the API Library:92> https://console.cloud.google.com/apis/library93> Enable: Gmail API, Google Calendar API, Google Drive API,94> Google Sheets API, Google Docs API, People API95> 3. Create the OAuth client here:96> https://console.cloud.google.com/apis/credentials97> Credentials → Create Credentials → OAuth 2.0 Client ID98> 4. Application type: "Desktop app" → Create99> 5. If the app is still in Testing, add the user's Google account as a test user here:100> https://console.cloud.google.com/auth/audience101> Audience → Test users → Add users102> 6. Download the JSON file and tell me the file path103>104> 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:105> `The JSON file path is: ~/Downloads/client_secret_....json`106 107Once they provide the path:108 109```bash110$GSETUP --client-secret /path/to/client_secret.json111```112 113If they paste the raw client ID / client secret values instead of a file path,114write a valid Desktop OAuth JSON file for them yourself, save it somewhere115explicit (for example `~/Downloads/hermes-google-client-secret.json`), then run116`--client-secret` against that file.117 118### Step 3: Get authorization URL119 120Use the service set chosen in Step 1. Examples:121 122```bash123$GSETUP --auth-url --services email,calendar --format json124$GSETUP --auth-url --services calendar,drive,sheets,docs --format json125$GSETUP --auth-url --services all --format json126```127 128This returns JSON with an `auth_url` field and also saves the exact URL to129`~/.hermes/google_oauth_last_url.txt`.130 131Agent rules for this step:132- Extract the `auth_url` field and send that exact URL to the user as a single line.133- Tell the user that the browser will likely fail on `http://localhost:1` after approval, and that this is expected.134- Tell them to copy the ENTIRE redirected URL from the browser address bar.135- 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.136 137### Step 4: Exchange the code138 139The user will paste back either a URL like `http://localhost:1/?code=4/0A...&scope=...`140or just the code string. Either works. The `--auth-url` step stores a temporary141pending OAuth session locally so `--auth-code` can complete the PKCE exchange142later, even on headless systems:143 144```bash145$GSETUP --auth-code "THE_URL_OR_CODE_THE_USER_PASTED" --format json146```147 148If `--auth-code` fails because the code expired, was already used, or came from149an older browser tab, it now returns a fresh `fresh_auth_url`. In that case,150immediately send the new URL to the user and have them retry with the newest151browser redirect only.152 153### Step 5: Verify154 155```bash156$GSETUP --check157```158 159Should print `AUTHENTICATED`. Setup is complete — token refreshes automatically from now on.160 161### Notes162 163- Token is stored at `~/.hermes/google_token.json` and auto-refreshes.164- Pending OAuth session state/verifier are stored temporarily at `~/.hermes/google_oauth_pending.json` until exchange completes.165- 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.166- To revoke: `$GSETUP --revoke`167 168## Usage169 170All commands go through the API script. Set `GAPI` as a shorthand:171 172```bash173GAPI="python ${HERMES_HOME:-$HOME/.hermes}/skills/productivity/google-workspace/scripts/google_api.py"174```175 176### Gmail177 178```bash179# Search (returns JSON array with id, from, subject, date, snippet)180$GAPI gmail search "is:unread" --max 10181$GAPI gmail search "from:boss@company.com newer_than:1d"182$GAPI gmail search "has:attachment filename:pdf newer_than:7d"183 184# Read full message (returns JSON with body text)185$GAPI gmail get MESSAGE_ID186 187# Send188$GAPI gmail send --to user@example.com --subject "Hello" --body "Message text"189$GAPI gmail send --to user@example.com --subject "Report" --body "<h1>Q4</h1><p>Details...</p>" --html190$GAPI gmail send --to user@example.com --subject "Hello" --from '"Research Agent" <user@example.com>' --body "Message text"191 192# Reply (automatically threads and sets In-Reply-To)193$GAPI gmail reply MESSAGE_ID --body "Thanks, that works for me."194$GAPI gmail reply MESSAGE_ID --from '"Support Bot" <user@example.com>' --body "Thanks"195 196# Labels197$GAPI gmail labels198$GAPI gmail modify MESSAGE_ID --add-labels LABEL_ID199$GAPI gmail modify MESSAGE_ID --remove-labels UNREAD200```201 202### Calendar203 204```bash205# List events (defaults to next 7 days)206$GAPI calendar list207$GAPI calendar list --start 2026-03-01T00:00:00Z --end 2026-03-07T23:59:59Z208 209# Create event (ISO 8601 with timezone required)210$GAPI calendar create --summary "Team Standup" --start 2026-03-01T10:00:00-06:00 --end 2026-03-01T10:30:00-06:00211$GAPI calendar create --summary "Lunch" --start 2026-03-01T12:00:00Z --end 2026-03-01T13:00:00Z --location "Cafe"212$GAPI calendar create --summary "Review" --start 2026-03-01T14:00:00Z --end 2026-03-01T15:00:00Z --attendees "alice@co.com,bob@co.com"213 214# Delete event215$GAPI calendar delete EVENT_ID216```217 218### Drive219 220```bash221# Search existing files222$GAPI drive search "quarterly report" --max 10223$GAPI drive search "mimeType='application/pdf'" --raw-query --max 5224 225# Get metadata for a single file226$GAPI drive get FILE_ID227 228# Upload a local file (auto-detects MIME type)229$GAPI drive upload /path/to/report.pdf230$GAPI drive upload /path/to/image.png --name "Logo.png" --parent FOLDER_ID231 232# Download (binary files download as-is; Google-native files export to a233# sensible default — Docs→pdf, Sheets→csv, Slides→pdf, Drawings→png)234$GAPI drive download FILE_ID235$GAPI drive download DOC_ID --output ~/doc.pdf236$GAPI drive download DOC_ID --export-mime text/plain --output ~/doc.txt237 238# Create a folder239$GAPI drive create-folder "Reports"240$GAPI drive create-folder "Q4" --parent FOLDER_ID241 242# Share243$GAPI drive share FILE_ID --email alice@example.com --role reader244$GAPI drive share FILE_ID --email alice@example.com --role writer --notify245$GAPI drive share FILE_ID --type anyone --role reader # anyone with link246$GAPI drive share FILE_ID --type domain --domain example.com --role reader247 248# Delete — defaults to trash (reversible). Use --permanent to skip the trash.249$GAPI drive delete FILE_ID250$GAPI drive delete FILE_ID --permanent251```252 253### Contacts254 255```bash256$GAPI contacts list --max 20257```258 259### Sheets260 261```bash262# Create a new spreadsheet263$GAPI sheets create --title "Q4 Budget"264$GAPI sheets create --title "Inventory" --sheet-name "Stock"265 266# Read267$GAPI sheets get SHEET_ID "Sheet1!A1:D10"268 269# Write270$GAPI sheets update SHEET_ID "Sheet1!A1:B2" --values '[["Name","Score"],["Alice","95"]]'271 272# Append rows273$GAPI sheets append SHEET_ID "Sheet1!A:C" --values '[["new","row","data"]]'274```275 276### Docs277 278```bash279# Read (a tabbed Doc returns a "tabs" array; single-tab and legacy Docs also return "body")280$GAPI docs get DOC_ID281$GAPI docs get DOC_ID --tab TAB_ID # read one tab of a tabbed Doc282 283# Create a new Doc (optionally seeded with body text)284$GAPI docs create --title "Meeting Notes"285$GAPI docs create --title "Draft" --body "First paragraph..."286 287# Append text to the end of an existing Doc288$GAPI docs append DOC_ID --text "Additional content to append"289$GAPI docs append DOC_ID --tab TAB_ID --text "..." # --tab required when the Doc has multiple tabs290```291 292## Output Format293 294All commands return JSON. Parse with `jq` or read directly. Key fields:295 296- **Gmail search**: `[{id, threadId, from, to, subject, date, snippet, labels}]`297- **Gmail get**: `{id, threadId, from, to, subject, date, labels, body}`298- **Gmail send/reply**: `{status: "sent", id, threadId}`299- **Calendar list**: `[{id, summary, start, end, location, description, htmlLink}]`300- **Calendar create**: `{status: "created", id, summary, htmlLink}`301- **Drive search**: `[{id, name, mimeType, modifiedTime, webViewLink}]`302- **Drive get**: `{id, name, mimeType, modifiedTime, size, webViewLink, parents, owners}`303- **Drive upload**: `{status: "uploaded", id, name, mimeType, webViewLink}`304- **Drive download**: `{status: "downloaded", id, name, path, mimeType}`305- **Drive create-folder**: `{status: "created", id, name, webViewLink}`306- **Drive share**: `{status: "shared", permissionId, fileId, role, type}`307- **Drive delete**: `{status: "trashed" | "deleted", fileId, permanent}`308- **Contacts list**: `[{name, emails: [...], phones: [...]}]`309- **Sheets get**: `[[cell, cell, ...], ...]`310- **Sheets create**: `{status: "created", spreadsheetId, title, spreadsheetUrl}`311- **Docs create**: `{status: "created", documentId, title, url}`312- **Docs append**: `{status: "appended", documentId, inserted_at, characters}`313 314## Rules315 3161. **Never send email, create/delete calendar events, delete Drive files, share files, or modify Docs/Sheets without confirming with the user first.** Show what will be done (recipients, file IDs, content, share role) and ask for approval. For `drive delete`, prefer the default trash (reversible) over `--permanent`.3172. **Check auth before first use** — run `setup.py --check`. If it fails, guide the user through setup.3183. **Use the Gmail search syntax reference** for complex queries — load it with `skill_view("google-workspace", file_path="references/gmail-search-syntax.md")`.3194. **Calendar times must include timezone** — always use ISO 8601 with offset (e.g., `2026-03-01T10:00:00-06:00`) or UTC (`Z`).3205. **Respect rate limits** — avoid rapid-fire sequential API calls. Batch reads when possible.321 322## Troubleshooting323 324| Problem | Fix |325|---------|-----|326| `NOT_AUTHENTICATED` | Run setup Steps 2-5 above |327| `REFRESH_FAILED` | Token revoked or expired — redo Steps 3-5 |328| `HttpError 403: Insufficient Permission` | Missing API scope — `$GSETUP --revoke` then redo Steps 3-5 |329| `AUTHENTICATED (partial)` or "Token missing scopes" | New write capabilities (Drive write/delete, Docs create/edit) require re-authorization. `$GSETUP --revoke` then redo Steps 3-5 to grant the upgraded scopes. |330| `HttpError 403: Access Not Configured` | API not enabled — user needs to enable it in Google Cloud Console |331| `ModuleNotFoundError` | Run `$GSETUP --install-deps` |332| Advanced Protection blocks auth | Workspace admin must allowlist the OAuth client ID |333 334## Revoking Access335 336```bash337$GSETUP --revoke338```339 Discovery context
Discovered by repository scan. No exact path reference found in the snapshot’s root AGENTS.md.