diff --git a/skills/productivity/google-workspace/SKILL.md b/skills/productivity/google-workspace/SKILL.md index c94014a1e..e4553e425 100644 --- a/skills/productivity/google-workspace/SKILL.md +++ b/skills/productivity/google-workspace/SKILL.md @@ -47,7 +47,10 @@ Install `gws`: ```bash cargo install google-workspace-cli -# or via npm: npm install -g @anthropic/google-workspace-cli +# or via npm (recommended, downloads prebuilt binary): +npm install -g @googleworkspace/cli +# or via Homebrew: +brew install googleworkspace-cli ``` Verify: `gws --version` @@ -205,7 +208,19 @@ $GBRIDGE sheets +read --spreadsheet SHEET_ID --range "Sheet1!A1:D10" ## Output Format -All commands return JSON via `gws --format json`. Output structure varies by `gws` helper. +All commands return JSON via `gws --format json`. Key output shapes: + +- **Gmail search/triage**: Array of message summaries (sender, subject, date, snippet) +- **Gmail get/read**: Message object with headers and body text +- **Gmail send/reply**: Confirmation with message ID +- **Calendar list/agenda**: Array of event objects (summary, start, end, location) +- **Calendar create**: Confirmation with event ID and htmlLink +- **Drive search**: Array of file objects (id, name, mimeType, webViewLink) +- **Sheets get/read**: 2D array of cell values +- **Docs get**: Full document JSON (use `body.content` for text extraction) +- **Contacts list**: Array of person objects with names, emails, phones + +Parse output with `jq` or read JSON directly. ## Rules @@ -221,7 +236,7 @@ All commands return JSON via `gws --format json`. Output structure varies by `gw |---------|-----| | `NOT_AUTHENTICATED` | Run setup Steps 2-5 | | `REFRESH_FAILED` | Token revoked — redo Steps 3-5 | -| `gws: command not found` | Install: `cargo install google-workspace-cli` | +| `gws: command not found` | Install: `npm install -g @googleworkspace/cli` | | `HttpError 403` | Missing scope — `$GSETUP --revoke` then redo Steps 3-5 | | `HttpError 403: Access Not Configured` | Enable API in Google Cloud Console | | Advanced Protection blocks auth | Admin must allowlist the OAuth client ID | diff --git a/skills/productivity/google-workspace/scripts/google_api.py b/skills/productivity/google-workspace/scripts/google_api.py index e288ec1ae..ae8732f4b 100644 --- a/skills/productivity/google-workspace/scripts/google_api.py +++ b/skills/productivity/google-workspace/scripts/google_api.py @@ -80,15 +80,30 @@ def gmail_modify(args): # -- Calendar -- def calendar_list(args): - cmd = ["calendar", "+agenda", "--format", "json"] - if args.start and args.end: - # Calculate days between start and end for --days flag - cmd += ["--days", "7"] + if args.start or args.end: + # Specific date range — use raw Calendar API for precise timeMin/timeMax + from datetime import datetime, timedelta, timezone as tz + now = datetime.now(tz.utc) + time_min = args.start or now.isoformat() + time_max = args.end or (now + timedelta(days=7)).isoformat() + gws( + "calendar", "events", "list", + "--params", json.dumps({ + "calendarId": args.calendar, + "timeMin": time_min, + "timeMax": time_max, + "maxResults": args.max, + "singleEvents": True, + "orderBy": "startTime", + }), + "--format", "json", + ) else: - cmd += ["--days", "7"] - if args.calendar != "primary": - cmd += ["--calendar", args.calendar] - gws(*cmd) + # No date range — use +agenda helper (defaults to 7 days) + cmd = ["calendar", "+agenda", "--days", "7", "--format", "json"] + if args.calendar != "primary": + cmd += ["--calendar", args.calendar] + gws(*cmd) def calendar_create(args): cmd = [ diff --git a/skills/productivity/google-workspace/scripts/setup.py b/skills/productivity/google-workspace/scripts/setup.py index 0cc862bd6..cb8c38cb9 100644 --- a/skills/productivity/google-workspace/scripts/setup.py +++ b/skills/productivity/google-workspace/scripts/setup.py @@ -142,7 +142,9 @@ def check_auth(): if creds.valid: missing_scopes = _missing_scopes_from_payload(payload) if missing_scopes: - print(f"AUTHENTICATED (partial): Token valid but missing {len(missing_scopes)} scopes") + print(f"AUTHENTICATED (partial): Token valid but missing {len(missing_scopes)} scopes:") + for s in missing_scopes: + print(f" - {s}") print(f"AUTHENTICATED: Token valid at {TOKEN_PATH}") return True @@ -152,7 +154,9 @@ def check_auth(): TOKEN_PATH.write_text(creds.to_json()) missing_scopes = _missing_scopes_from_payload(_load_token_payload(TOKEN_PATH)) if missing_scopes: - print(f"AUTHENTICATED (partial): Token refreshed but missing {len(missing_scopes)} scopes") + print(f"AUTHENTICATED (partial): Token refreshed but missing {len(missing_scopes)} scopes:") + for s in missing_scopes: + print(f" - {s}") print(f"AUTHENTICATED: Token refreshed at {TOKEN_PATH}") return True except Exception as e: