hadron-server #638 ships a Google Calendar surface backed by hadron-memory/hadrontool-google-cal. Per the feature-complete-CLI strategy, the server surface isn't done until the CLI covers it. The hadron connection grant group (spec-042) already handles the delegation side — the calendar.read / calendar.freebusy scopes it lists are now live and enforced.
New GraphQL surface to cover
Queries: calendars(connectionRef), calendarEvents(connectionRef, calendarId, start, end, query, top), calendarEvent(connectionRef, eventId, calendarId), calendarSchedule(connectionRef, start!, end!, schedules).
Mutations: calendarCreateEvent, calendarUpdateEvent, calendarDeleteEvent, calendarRespondToEvent (all owner-only; idempotencyKey supported on create/update/delete).
Proposed command group
hadron calendar list --connection <ref> # calendars (id, name, primary, canEdit, timeZone)
hadron calendar events --connection <ref> [--calendar <id>] [--from <rfc3339>] [--to <rfc3339>] [--query <text>] [--top N]
hadron calendar event get <eventId> --connection <ref> [--calendar <id>]
hadron calendar event create --connection <ref> --title <t> --from <t> --to <t> [--all-day] [--tz <zone>] [--attendee a@b.c ...] [--location] [--description] [--no-notify] [--idempotency-key k]
hadron calendar event update <eventId> --connection <ref> [same flags, all optional]
hadron calendar event delete <eventId> --connection <ref> [--no-notify]
hadron calendar event respond <eventId> --connection <ref> --response accepted|declined|tentative
hadron calendar freebusy --connection <ref> --from <t> --to <t> [--schedule <id> ...]
Naming/flag details are open — follow the existing cobra/genqlient conventions (internal/cmd/connection is the closest sibling; --json output, api.CollectAll where paginated, api.MapError).
Contract notes for the implementation
- Event boundaries are either/or: timed =
dateTime + timeZone (a zone-less dateTime without a zone is rejected server-side, never guessed); all-day = date (YYYY-MM-DD). Suggest --all-day making --from/--to parse as dates.
response enum on the wire is ACCEPTED|DECLINED|TENTATIVE (GraphQL) — NONE exists in the enum for reads but is not settable (typed validation_error).
calendarEvents expands recurring events into instances (seriesId marks them); top is clamped to 100 server-side.
- Typed errors ride GraphQL extensions:
EMAIL_TOOL_NOT_CONFIGURED / EMAIL_TOOL_UNREACHABLE / EMAIL_TOOL_ERROR + extensions.emailErrorCode (connection_not_found, connection_unauthorized, provider_rate_limited, not_found, validation_error) — map them to friendly CLI messages.
- Authorization is server-side: interactive owner passes; App-token callers need a ConnectionGrant with
calendar.read (reads) / calendar.freebusy (freebusy); calendar mutations are owner-only in v1.
Open question: discovering the connectionRef
Every command needs a --connection <ref>, but there's no owner-facing "list my connections" query — exchangeConnections(orgId) is org-ADMIN gated. Today users copy the id from the portal. Worth deciding whether core should grow an owner-scoped connections list (which hadron connection list would then wrap) — flagging rather than prescribing, since it's a server API decision.
🤖 Generated with Claude Code
hadron-server #638 ships a Google Calendar surface backed by hadron-memory/hadrontool-google-cal. Per the feature-complete-CLI strategy, the server surface isn't done until the CLI covers it. The
hadron connection grantgroup (spec-042) already handles the delegation side — thecalendar.read/calendar.freebusyscopes it lists are now live and enforced.New GraphQL surface to cover
Queries:
calendars(connectionRef),calendarEvents(connectionRef, calendarId, start, end, query, top),calendarEvent(connectionRef, eventId, calendarId),calendarSchedule(connectionRef, start!, end!, schedules).Mutations:
calendarCreateEvent,calendarUpdateEvent,calendarDeleteEvent,calendarRespondToEvent(all owner-only;idempotencyKeysupported on create/update/delete).Proposed command group
Naming/flag details are open — follow the existing cobra/genqlient conventions (
internal/cmd/connectionis the closest sibling;--jsonoutput,api.CollectAllwhere paginated,api.MapError).Contract notes for the implementation
dateTime+timeZone(a zone-less dateTime without a zone is rejected server-side, never guessed); all-day =date(YYYY-MM-DD). Suggest--all-daymaking--from/--toparse as dates.responseenum on the wire isACCEPTED|DECLINED|TENTATIVE(GraphQL) —NONEexists in the enum for reads but is not settable (typed validation_error).calendarEventsexpands recurring events into instances (seriesIdmarks them);topis clamped to 100 server-side.EMAIL_TOOL_NOT_CONFIGURED/EMAIL_TOOL_UNREACHABLE/EMAIL_TOOL_ERROR+extensions.emailErrorCode(connection_not_found,connection_unauthorized,provider_rate_limited,not_found,validation_error) — map them to friendly CLI messages.calendar.read(reads) /calendar.freebusy(freebusy); calendar mutations are owner-only in v1.Open question: discovering the connectionRef
Every command needs a
--connection <ref>, but there's no owner-facing "list my connections" query —exchangeConnections(orgId)is org-ADMIN gated. Today users copy the id from the portal. Worth deciding whether core should grow an owner-scoped connections list (whichhadron connection listwould then wrap) — flagging rather than prescribing, since it's a server API decision.🤖 Generated with Claude Code