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.go — google.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
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:
calendar.readonlyscope"Calendars.Readscope"core.Providerinterface only hasFetchEvents()Scope changes
Google
calendar.readonlycalendar.events(read + write events)Locations:
cmd/tsk/cmd/auth.go—google.ConfigFromJSON(b, calendar.CalendarReadonlyScope)internal/adapter/google/adapter.go— same scope in adapter initOutlook
Calendars.ReadCalendars.ReadWriteLocations:
cmd/tsk/cmd/auth.go—"https://graph.microsoft.com/Calendars.Read"internal/adapter/outlook/adapter.go— same scope in OAuth configRe-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.Providerinterface needs new methods:A
RespondOptionsstruct for optional response metadata (message, proposed time):A new
EventInputstruct would hold the writable fields:Backward compatibility
Adapters that don't support writes (e.g., future iCloud adapter with read-only app password #3 ) could embed a
NoWriteSupportstruct that returnsErrNotSupportedfor all write methods. This keeps the interface uniform without forcing every adapter to implement writes.New CLI commands
TUI integration
The interactive UI could add keyboard shortcuts for quick responses:
a— accept eventd— decline eventt— mark tentativep— propose new time (opens a time picker prompt)x— delete event (with confirmation)Google implementation
Events.Insert(calendarId, event)Events.Update(calendarId, eventId, event)orEvents.Patch(calendarId, eventId, event)Events.Delete(calendarId, eventId)attendees[].responseStatusand optionalcommentfor the current usercounterProposalwhen patching attendee response. Less well-documented, works but clunkyOutlook implementation
client.Me().Calendars().ByCalendarId(id).Events().Post(event)client.Me().Events().ByEventId(id).Patch(event)client.Me().Events().ByEventId(id).Delete()client.Me().Events().ByEventId(id).Accept().Post(body)— body hasComment(string) andSendResponse(bool)client.Me().Events().ByEventId(id).Decline().Post(body)— same fields plus optionalProposedNewTimeclient.Me().Events().ByEventId(id).TentativelyAccept().Post(body)— same fields plus optionalProposedNewTimeProposedNewTimewithStartandEndDateTimeTimeZoneobjects, included in Decline or TentativelyAccept bodyOutlook 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
docs/google_setup.md
calendar.readonlytocalendar.eventsdocs/outlook_setup.md
Calendars.ReadtoCalendars.ReadWriteCalendars.ReadWritemay require admin consent in some organizationsconfig.example.yaml
Edge cases
Calendars.ReadWritein Outlook may require admin consent in stricter organizations, unlikeCalendars.Readwhich is user-consentable. This needs to be documented.--read-onlyflag or per-profile config option so users can opt out of write permissions if they prefer the safety net.--proposeflag 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:
createcommand,EventInputstruct, calendar picker. Straightforward API on both providers. Recurring event creation adds complexity.Generated by Claude Opus 4.6