--- name: google-calendar description: Read and manage Google Calendar events / agenda / free-busy / invitations via the Calendar v3 REST API. Use when the user mentions Google Calendar events, today's agenda, this week's meetings, finding conflicts, listing invitations, checking free time, or scheduling / rescheduling / cancelling a meeting. when_to_use: | Trigger when the user wants to read or manage events on their Google Calendar — list / search / inspect events, build today's or this week's agenda, check free / busy windows, pull invite details, or have the AI create / update / cancel events on their behalf and email invites to attendees. The installed connector always grants `calendar.readonly`; the user opts in to the broader `calendar` scope (full read + write) at install — confirm before destructive writes. connections: [google/calendar] allowed_tools: [Bash] license: Apache-2.0 metadata: author: acedatacloud version: "1.2" --- Drive Google Calendar via `curl + jq`. The user's OAuth bearer token is in `$GOOGLE_CALENDAR_TOKEN`; every call needs it as `Authorization: Bearer $GOOGLE_CALENDAR_TOKEN`. At minimum the token carries `calendar.readonly` plus the identity scopes (`openid email profile`); if the user opted in to write at install time it also carries the broader `calendar` scope (read + write). The Calendar API returns standard JSON; failures surface as `{"error": {"code": 401|403|..., "message": "..."}}` — show that error verbatim. `401` means the token expired (re-install). `403 insufficientPermissions` on a write means the user only granted `calendar.readonly` — ask them to re-install the connector with the read+write box checked. **Always start with `users/me/calendarList`** to learn which calendars the account can see (the user's primary plus any subscribed / shared ones), AND with `users/me/settings/timezone` so you render times in the user's local zone instead of UTC. **Before any destructive write** (creating, moving, or cancelling an event that has attendees) show the exact event details and ask the user to confirm. When attendees are involved, also confirm whether they want Google to email the attendees — that's controlled by the `sendUpdates` query parameter. ## Optional: Google Workspace CLI (`gws`) for agenda + create [`gws`](https://github.com/googleworkspace/cli) is Google's official CLI (not officially supported — community-maintained on the `googleworkspace` org). It dynamically builds its command surface from Google's Discovery Document, exits non-zero on API errors, and ships hand-crafted helper commands (prefixed `+`) for time-aware workflows. **Use `gws` for two specific cases:** - `+agenda` reads the user's account timezone from `Settings.timezone` (cached for 24 h) and renders today's events in that zone, so you don't have to fetch the timezone yourself before formatting times. - `+insert` shapes the create-event JSON for you (attendees, sendUpdates, reminders) so a one-line invocation produces a well-formed request. For everything else (events.list / patch / move / delete, freebusy, calendarList) the curl recipes below are equivalent and shorter — stay on those. ### Install ```sh npm install -g @googleworkspace/cli # or: brew install googleworkspace-cli # Pre-built binaries also at https://github.com/googleworkspace/cli/releases gws --version ``` ### Auth `gws` reads its OAuth bearer token from the `GOOGLE_WORKSPACE_CLI_TOKEN` environment variable. The Calendar token used in this skill is in `$GOOGLE_CALENDAR_TOKEN`, so re-export it once at the top of every shell block that calls `gws`: ```sh export GOOGLE_WORKSPACE_CLI_TOKEN="$GOOGLE_CALENDAR_TOKEN" ``` ### Agenda + create ```sh # Today on the primary calendar, in the account's own timezone gws calendar +agenda # Today / week, with explicit overrides gws calendar +agenda --today --tz America/New_York gws calendar +agenda --range week # Create an event (auto-shapes attendees + sendUpdates JSON) gws calendar +insert --calendar primary \ --json '{ "summary":"Standup", "start":{"dateTime":"2026-05-06T10:00:00-04:00"}, "end": {"dateTime":"2026-05-06T10:30:00-04:00"}, "attendees":[{"email":"alice@example.com"}] }' \ --params '{"sendUpdates":"all"}' ``` Both helpers exit non-zero with a structured JSON error on stderr if Google rejects the request — surface that verbatim. `+insert` against attendees requires the broader `calendar` scope; on `403 insufficientPermissions` ask the user to re-install with read+write checked. ## Recipes ### Verify auth + discover calendars (always run first) ```sh # Account confirmation + calendars the user can read curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ "https://www.googleapis.com/calendar/v3/users/me/calendarList" \ | jq '.items[] | {id, summary, primary, accessRole, timeZone}' # User's preferred display zone (use this when formatting times) curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" \ | jq -r .value ``` The `id` of each calendar (`primary`, or an email-shaped id like `team-monday@group.calendar.google.com`) is what subsequent `calendars/{id}/events` calls take. ### Today's agenda on the primary calendar ```sh TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value) TODAY=$(TZ=$TZ date +%Y-%m-%d) START="${TODAY}T00:00:00Z" END="${TODAY}T23:59:59Z" curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \ --data-urlencode "timeMin=$START" \ --data-urlencode "timeMax=$END" \ --data-urlencode 'singleEvents=true' \ --data-urlencode 'orderBy=startTime' \ --data-urlencode "timeZone=$TZ" \ | jq '.items[] | {summary, start: (.start.dateTime // .start.date), end: (.end.dateTime // .end.date), location, attendees: [.attendees[]?.email], hangout: .hangoutLink, status, htmlLink}' ``` `singleEvents=true` flattens recurring meetings into individual instances — almost always what you want for an agenda. Without it, you'd get the recurrence rule once and have to expand it client-side. ### This week's meetings (Mon–Sun) ```sh TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value) # Bash date math: Monday-of-this-week MON=$(TZ=$TZ date -d "$(TZ=$TZ date +%Y-%m-%d) -$(($(TZ=$TZ date +%u) - 1)) days" +%Y-%m-%d 2>/dev/null \ || TZ=$TZ date -v-mondayw +%Y-%m-%d) # macOS fallback SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null \ || TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d) curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \ --data-urlencode "timeMin=${MON}T00:00:00Z" \ --data-urlencode "timeMax=${SUN}T23:59:59Z" \ --data-urlencode 'singleEvents=true' \ --data-urlencode 'orderBy=startTime' \ | jq -r '.items[] | "\(.start.dateTime // .start.date)\t\(.summary)\t\((.attendees // []) | length) attendees"' ``` ### Search events by query ```sh Q='quarterly review' curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \ --data-urlencode "q=$Q" \ --data-urlencode 'singleEvents=true' \ --data-urlencode 'maxResults=20' \ | jq '.items[] | {start: .start.dateTime, summary, htmlLink}' ``` `q` matches against summary, description, location, attendee emails, and creator/organizer. ### Get one event's full details (incl. attendees, location, link) ```sh EVENT_ID='abc123def4567890ghijklmnop' curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID" \ | jq '{summary, start, end, location, description, attendees, organizer, hangoutLink, conferenceData}' ``` ### Free / busy across multiple calendars (next 7 days) ```sh TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value) NOW=$(TZ=$TZ date -u +%Y-%m-%dT%H:%M:%SZ) NEXT_WEEK=$(TZ=$TZ date -u -d "+7 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ || TZ=$TZ date -u -v+7d +%Y-%m-%dT%H:%M:%SZ) cat > /tmp/freebusy.json < /tmp/_cal_event.json < /tmp/_cal_recur.json < /tmp/_cal_resched.json <