Skip to content

feat(calendar): add --with-zoom / --regenerate-zoom / --remove-zoom#590

Open
mvanhorn wants to merge 1 commit into
openclaw:mainfrom
mvanhorn:feat/calendar-with-zoom
Open

feat(calendar): add --with-zoom / --regenerate-zoom / --remove-zoom#590
mvanhorn wants to merge 1 commit into
openclaw:mainfrom
mvanhorn:feat/calendar-with-zoom

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

Summary

gog calendar can now create, regenerate, and remove Zoom video conferences as a first-party flag family, parallel to the existing --with-meet surface. Adds gog zoom auth setup and gog zoom auth doctor for Zoom Server-to-Server OAuth credential storage. Closes #589.

Why this matters

"Today an agent can attach Google Meet links via gog in one tool call. When a client requests Zoom, the agent has no programmatic surface - the Zoom for Google Workspace add-on is UI-only and can't be triggered via the Calendar API's conferenceData.createRequest (or at least, not reliably across third-party add-ons)." - @alexisperumal in #589

The Zoom for Google Workspace add-on is UI-only. Calendar API's conferenceData.createRequest does not reliably trigger third-party add-ons. The "agent schedules a Zoom meeting and attaches it to a Google Calendar event" niche is uncovered upstream: agent-plex/openclaw-zoom covers Team Chat (different product), AIGC-Hackers/openclaw-zoom-agent joins existing meetings (does not create or schedule).

Architectural premise validated empirically before implementation. The reporter's research showed that conferenceSolution.key.type = "addOn" is the generic legacy string (not Zoom-specific) and that the Zoom-for-Calendar add-on populates conferenceData.entryPoints + conferenceSolution.{key,name,iconUri}. The open question was whether Google's Calendar API accepts these fields from a non-add-on OAuth client. I tested it: created a Calendar event with conferenceSolution.key.type = "addOn" + name = "Zoom Meeting" + a Zoom-shaped entryPoint via a vanilla OAuth client; Google preserved all fields on read-back. The direct-write architecture works; no parameters.addOnParameters shim is required for API acceptance. UI parity (whether the Calendar UI renders Zoom branding vs a generic conference card) is the remaining unknown - see "Known caveat" below.

Demo

Simulated demo (the implementer does not have a Zoom Pro account in the loop, so this is hand-crafted terminal animation, not a live capture). Live capture against the real gog binary post-gog zoom auth setup is a 2-minute exercise for anyone with a Zoom Pro account.

demo

Changes

  • New internal/zoom/ package: minimum-viable Zoom Meetings client (S2S OAuth token exchange + cached refresh, POST /users/{userId}/meetings, DELETE /meetings/{meetingId}). Production transport is http.DefaultTransport; tests inject a http.RoundTripper. No InsecureSkipVerify.
  • New gog zoom auth setup / gog zoom auth doctor subcommands. User-level OAuth scopes only (meeting:write, meeting:read, user:read). Admin scopes are explicitly deferred; agent-fleet --zoom-host delegation can land in a follow-up that gates *:admin behind an explicit --admin flag.
  • Secret storage reuses internal/secrets with namespaced keys (zoom-account/<alias>/client-secret, zoom-account/<alias>/access-token). Non-secret account metadata lives at ~/.config/gogcli/zoom/<alias>.json (dir 0700, file 0600). No second keyring.
  • buildConferenceData rewritten to take a conferenceChoice struct. Meet path is unchanged; Zoom path writes the addOn-shaped conferenceData after creating the Zoom meeting via the Zoom API.
  • Flag-mutex matrix enforced at parse time for the seven incompatible pairs in calendar_edit.go (with-zoom + regenerate-zoom, with-zoom + remove-zoom, regenerate-zoom + remove-zoom, with-zoom + with-meet, with-zoom + regenerate-meet, regenerate-zoom + with-meet, regenerate-zoom + regenerate-meet).
  • Cross-provider runtime check: --with-zoom on an event that already has a Meet conference returns usage("event already has a Meet conference; use --remove-meet first, then --with-zoom"). (--remove-meet itself is out of scope for this PR; see "Deferred" below.)
  • Cancel-before-create failure semantics for --regenerate-zoom: cancel old meeting → on 404/410 proceed; on other error abort, Calendar event unchanged. Create new meeting → patch Calendar. If the Calendar patch fails, the newly-created Zoom meeting is cancelled.
  • --remove-zoom cancels the Zoom meeting (404/410 = success, other error = stderr warning + proceed) then clears the Calendar conferenceData.
  • redactZoomURL in internal/zoom/redact.go masks the pwd= query parameter on every join URL reaching stdout/stderr. --include-passwords flag (or GOG_ZOOM_INCLUDE_PASSWORDS=1) opts out for debugging.
  • Every destructive Zoom API call writes a structured audit line to stderr: [zoom] meeting=<id> action=<cancel|delete|regenerate> ts=<rfc3339> cmd=<argv0>.
  • make fmt, make lint, make build, make test, make docs-check, make ci all pass locally on ubuntu/macos (windows untested locally; CI will exercise it).

Testing

  • Unit tests parallel to the existing Meet test names in internal/cmd/calendar_create_update_test.go: TestCalendarCreateCmd_WithZoomAndAttachments, TestCalendarUpdateCmd_WithZoom, idempotency variants, TestCalendarUpdateCmd_RegenerateZoomReplacesConference, TestCalendarUpdateCmd_RemoveZoom.
  • Cross-provider + auth-error tests: TestCalendarUpdateCmd_WithZoomOnExistingMeetEventRejects, TestCalendarUpdateCmd_WithZoomNoCredentialsErrors, TestCalendarUpdateCmd_RegenerateZoomWithUnparseablePriorMeetingWarns.
  • One mutex test per rejected pair.
  • internal/zoom/client_test.go exercises token-refresh, 404/410-treated-as-success on delete, and 5xx error-path via httptest.
  • Empirical Phase 0 test against a real Google Calendar account confirmed Google preserves key.type="addOn" + name/iconUri/entryPoints on round-trip from a non-add-on OAuth client.

Known caveat

Calendar UI Zoom branding parity was not visually verified during Phase 0. The API accepts and stores the addOn-shaped conferenceData; whether Google renders the Zoom card vs a generic conference card depends on whether the Calendar UI gates on parameters.addOnParameters.parameters (add-on-internal Apps Script state). If maintainer testing reveals the UI shows a generic card, a follow-up can populate plausible addOnParameters values - the empirical fallback path noted in #589's Open Question 3.

Deferred to follow-up PRs

To keep this PR close to the project's median size and easy to review:

  • Standalone gog zoom meeting create|get|list|update|delete subtree (the issue's Tier 2 "nice-to-have, could be follow-up PR").
  • --remove-meet (real gap, but adjacent to this work; separate minimal PR keeps the diff focused).
  • Optional --zoom-no-notes and --zoom-host <email> flags (latter needs the *:admin scope split).
  • gog zoom auth tokens subcommand.

Open Questions answered in #589

  1. Flag naming: chose the concrete --with-zoom family over a generic --conference-provider zoom form. Rationale: discoverability + mirrors the existing --with-meet precedent. A generic abstraction is more durable if a second non-Meet provider lands; happy to refactor at review time if you prefer that direction.

  2. Implementation architecture: direct Zoom API write (path a in the issue), empirically validated. Calendar createRequest passthrough (path b) was rejected because it depends on third-party add-ons implementing Google's Conference Add-on createConference callback - Zoom's add-on does not.

  3. addOnParameters server-side validation: not required for API acceptance per Phase 0 test. UI rendering parity is the open follow-up question (see "Known caveat").

Fixes #589

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(calendar): add --with-zoom / --regenerate-zoom / --remove-zoom (parity with --with-meet)

1 participant