Skip to content

Allow creating, updating, and responding to calendar events #1

@theakshaypant

Description

@theakshaypant

Summary

tsk is currently read-only by design. This feature would add write capabilities — creating events, updating existing ones, responding to invitations (accept/decline/tentative), and deleting events. Requires upgrading API permissions from read-only to read-write scopes for both Google and Outlook, and updating all related documentation.

Current state

tsk is explicitly advertised as read-only:

  • README: "Look, don't touch. tsk is read-only"
  • Google setup doc: "It requests the calendar.readonly scope"
  • Outlook setup doc: "It requests the Calendars.Read scope"
  • core.Provider interface only has FetchEvents()

Scope changes

Google

Current New
calendar.readonly calendar.events (read + write events)

Locations:

  • cmd/tsk/cmd/auth.gogoogle.ConfigFromJSON(b, calendar.CalendarReadonlyScope)
  • internal/adapter/google/adapter.go — same scope in adapter init

Outlook

Current New
Calendars.Read Calendars.ReadWrite

Locations:

  • cmd/tsk/cmd/auth.go"https://graph.microsoft.com/Calendars.Read"
  • internal/adapter/outlook/adapter.go — same scope in OAuth config

Re-authentication required

Existing tokens were granted with read-only scopes. Users will need to delete their token file and re-authenticate after upgrading to pick up the new permissions. The upgrade path should be documented clearly.

Provider interface changes

The core.Provider interface needs new methods:

type Provider interface {
    ID() string
    Name() string
    FetchEvents(ctx context.Context, opts FetchOptions) ([]Event, error)

    // Write operations
    CreateEvent(ctx context.Context, event EventInput) (*Event, error)
    UpdateEvent(ctx context.Context, eventID string, event EventInput) (*Event, error)
    DeleteEvent(ctx context.Context, eventID string) error
    RespondToEvent(ctx context.Context, eventID string, response EventStatus, opts *RespondOptions) error
}

A RespondOptions struct for optional response metadata (message, proposed time):

type RespondOptions struct {
    Message string        // optional message to the organizer
    Propose *TimeProposal // optional proposed new time (decline/tentative only)
}

type TimeProposal struct {
    Start time.Time
    End   time.Time
}

A new EventInput struct would hold the writable fields:

type EventInput struct {
    Title       string
    Description string
    Location    string
    Start       time.Time
    End         time.Time
    IsAllDay    bool
    CalendarID  string            // which calendar to create in
    Attendees   []string          // email addresses
    MeetingLink string            // optional video link
    Recurrence  *RecurrenceRule   // optional, for recurring events
}

Backward compatibility

Adapters that don't support writes (e.g., future iCloud adapter with read-only app password #3 ) could embed a NoWriteSupport struct that returns ErrNotSupported for all write methods. This keeps the interface uniform without forcing every adapter to implement writes.

New CLI commands

# Create an event
tsk create "Team standup" --start "2026-03-02 09:00" --end "2026-03-02 09:30" --calendar "Work"
tsk create "Lunch" --start tomorrow --duration 1h

# Update an event
tsk update <event-id> --title "Updated standup" --location "Room 3"

# Respond to an invitation
tsk respond <event-id> --accept
tsk respond <event-id> --decline
tsk respond <event-id> --tentative

# Respond with a message to the organizer
tsk respond <event-id> --accept --message "Looking forward to it"
tsk respond <event-id> --decline --message "Out that day, sorry"

# Decline and propose a new time
tsk respond <event-id> --decline --propose "2026-03-02 14:00-15:00"
tsk respond <event-id> --tentative --propose "2026-03-03 10:00-11:00" --message "This slot works better"

# Quick respond from the TUI
# (keyboard shortcuts in the event detail panel)

# Delete an event
tsk delete <event-id>
tsk delete <event-id> --confirm   # skip confirmation prompt

TUI integration

The interactive UI could add keyboard shortcuts for quick responses:

  • a — accept event
  • d — decline event
  • t — mark tentative
  • p — propose new time (opens a time picker prompt)
  • x — delete event (with confirmation)

Google implementation

Operation API Call
Create Events.Insert(calendarId, event)
Update Events.Update(calendarId, eventId, event) or Events.Patch(calendarId, eventId, event)
Delete Events.Delete(calendarId, eventId)
Respond Update the event's attendees[].responseStatus and optional comment for the current user
Propose new time Not a first-class API — uses counterProposal when patching attendee response. Less well-documented, works but clunky

Outlook implementation

Operation Graph SDK Call
Create client.Me().Calendars().ByCalendarId(id).Events().Post(event)
Update client.Me().Events().ByEventId(id).Patch(event)
Delete client.Me().Events().ByEventId(id).Delete()
Accept client.Me().Events().ByEventId(id).Accept().Post(body) — body has Comment (string) and SendResponse (bool)
Decline client.Me().Events().ByEventId(id).Decline().Post(body) — same fields plus optional ProposedNewTime
Tentative client.Me().Events().ByEventId(id).TentativelyAccept().Post(body) — same fields plus optional ProposedNewTime
Propose new time ProposedNewTime with Start and End DateTimeTimeZone objects, included in Decline or TentativelyAccept body

Outlook has first-class support for all response types — message, propose new time, and send/don't send notification are all part of the request body. Cleaner than Google's approach of patching attendee status.

Documentation updates

README.md

  • Remove or rephrase the "Look, don't touch" read-only disclaimer
  • Add write command examples to the Usage section

docs/google_setup.md

  • Update scope from calendar.readonly to calendar.events
  • Note that re-authentication is required after upgrade
  • Update the "tsk only needs read access" disclaimer

docs/outlook_setup.md

  • Update scope from Calendars.Read to Calendars.ReadWrite
  • Note that re-authentication is required after upgrade
  • Update the "tsk only needs read access" disclaimer
  • Update API permissions section — Calendars.ReadWrite may require admin consent in some organizations

config.example.yaml

  • No changes needed unless we add per-profile read-only mode

Edge cases

  • Recurring events: Creating/updating a single instance vs. the whole series. Both Google and Outlook support this but the UX needs careful thought (e.g., "Edit this event or all future events?").
  • Shared calendars: Write permissions depend on the calendar owner's sharing settings. Need clear error messages when writes are rejected.
  • Organizer vs. attendee: Some operations (update, delete) only work if the user is the organizer. Responding only works if the user is an attendee.
  • Admin consent: Calendars.ReadWrite in Outlook may require admin consent in stricter organizations, unlike Calendars.Read which is user-consentable. This needs to be documented.
  • Read-only mode: Consider keeping a --read-only flag or per-profile config option so users can opt out of write permissions if they prefer the safety net.
  • Propose new time: Only meaningful when declining or responding tentative. The --propose flag should be rejected if used with --accept. Outlook supports this natively; Google's support is limited and may not deliver the proposal as cleanly to the organizer.

Effort

Large. This touches the core interface, both adapters, CLI commands, TUI shortcuts, and all documentation. The API calls themselves are straightforward, but the UX (recurring event editing, confirmation prompts, error handling for permission issues) adds significant complexity. Recommend shipping in phases:

Phase Scope Effort Notes
Phase 1 Respond to events (accept/decline/tentative, message, propose new time) Medium Smallest useful write operation. Outlook has first-class endpoints; Google needs attendee patch. Scope upgrade + re-auth flow needed.
Phase 2 Create events Medium New create command, EventInput struct, calendar picker. Straightforward API on both providers. Recurring event creation adds complexity.
Phase 3 Update and delete events Medium-Large Full CRUD. "This event or all future events?" UX for recurring series. Organizer-only guard rails. Delete confirmation prompts.

Generated by Claude Opus 4.6

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions