Skip to content

CLI coverage for the Google Calendar surface (hadrontool-google-cal): hadron calendar command group #230

Description

@shadowbrush

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions