Feature Request: Multi-Calendar Selection for gog calendar events
Proposal
Add support for querying a specific subset of calendars in gog calendar events, via one or more of:
Numeric index (--calendars 1,2,5) — quick selection by position from gog calendar calendars list:
gog calendar events --calendars 1,2,5 --today
Repeated flag (--cal) — robust, handles calendar names containing commas:
gog calendar events --cal "Work" --cal "Family" --cal "Meeting, Planning & Review" --today
Comma-separated names/IDs (--calendars) — concise for simple names:
gog calendar events --calendars "Work,Family,Holidays" --today
All approaches would support mixing: numeric indices, calendar names (case-insensitive), and full calendar IDs.
Rationale
API Efficiency
The Google Calendar API's Events.list endpoint requires a single calendarId per request — there's no native "query multiple calendars" parameter. Currently, users who need events from 2-3 specific calendars must either:
- Use
--all — makes N+1 API calls (1 CalendarList + N Events calls for all calendars), then filter results client-side
- Run multiple commands —
gog calendar events cal1 --today && gog calendar events cal2 --today
For users with many calendars (10-25 is common), option 1 wastes API quota and bandwidth fetching unwanted data. A --calendars flag would make only the necessary calls.
Ergonomics
- Numeric indices — After running
gog calendar calendars, users can quickly select by number: --calendars 1,2,5 instead of copying long IDs
- Name-based lookup — Calendar IDs like
v9c8pfp6n57g49cacq96r8vlh4@group.calendar.google.com are not user-friendly; names like "Work" or "Family" are
- Common workflow — Many users have a "daily driver" set of 2-4 calendars they check regularly
- Scriptability — Easier to maintain scripts with meaningful names than opaque IDs
Current Implementation Understanding
I traced through the codebase to understand how --all works:
Flow (internal/cmd/calendar.go + calendar_list.go)
- Flag validation (calendar.go:156-161) —
--all and calendarId are mutually exclusive
- Routing (calendar.go:184-187) — branches to
listAllCalendarsEvents() vs listCalendarEvents()
- Calendar enumeration (calendar_list.go:~90) — calls
svc.CalendarList.List() to get all calendars
- Event fetching (calendar_list.go:~100-130) — iterates
calResp.Items, calling svc.Events.List(cal.Id) for each
- Error handling — individual calendar failures are logged but don't abort the operation (graceful degradation)
- Output enrichment —
eventWithCalendar wrapper tracks source calendar for display
Existing Multi-Value Patterns
The codebase already has precedents:
| Command |
Flag/Arg |
Type |
Example |
freebusy |
<calendarIds> |
comma-separated |
gog calendar freebusy cal1,cal2 |
conflicts |
--calendars |
comma-separated |
--calendars "primary,work" |
create |
--reminder |
repeated flag |
--reminder popup:30m --reminder email:1d |
Name Resolution Precedent
Gmail labels already implement name-to-ID resolution in gmail_labels_utils.go:
func resolveLabelIDs(labels []string, nameToID map[string]string) []string
This pattern (case-insensitive lookup with fallback to treating input as ID) could be adapted for calendars.
Proposed Design
Command Structure
Two complementary approaches, following existing patterns in the codebase:
Option A: Repeated flag (--cal) — handles names with commas
type CalendarEventsCmd struct {
CalendarID string `arg:"" name:"calendarId" optional:"" help:"Calendar ID (default: primary)"`
Cal []string `name:"cal" help:"Calendar ID or name (can be repeated)"`
All bool `name:"all" help:"Fetch events from all calendars"`
// ... existing fields unchanged ...
}
# Handles calendar names containing commas safely
gog calendar events --cal "Meeting, Planning & Review" --cal "Work" --cal "Family" --today
Option B: Comma-separated (--calendars) — concise for simple names
type CalendarEventsCmd struct {
CalendarID string `arg:"" name:"calendarId" optional:"" help:"Calendar ID (default: primary)"`
Calendars string `name:"calendars" help:"Comma-separated calendar IDs or names"`
All bool `name:"all" help:"Fetch events from all calendars"`
// ... existing fields unchanged ...
}
# Shorter syntax when names don't contain commas
gog calendar events --calendars "Work,Family,Holidays" --today
Both options could coexist (merged into single list before resolution), or just one could be implemented — whichever fits the project's style better.
Mutual Exclusivity
calendarId (positional) ─┬─ mutually exclusive
--cal / --calendars ─┤
--all ─┘
(If both --cal and --calendars are supported, they could be additive rather than exclusive.)
Calendar Resolution (Optional Enhancement)
A utility similar to Gmail's label resolution, extended to support numeric indices:
func resolveCalendarIDs(ctx context.Context, svc *calendar.Service, inputs []string) ([]string, error)
Resolution order for each input:
- Numeric index — if input is a number (e.g.,
"1", "2"), resolve to Nth calendar from CalendarList.List() (1-indexed to match display)
- Name match — case-insensitive match against
cal.Summary
- ID match — case-insensitive match against
cal.Id
- Fallback — treat as literal calendar ID (forward-compatible)
This allows intuitive usage after running gog calendar calendars:
$ gog calendar calendars
ID NAME
primary Personal
abc123@group.calendar.google.com Work
xyz789@group.calendar.google.com Meeting, Planning & Review
$ gog calendar events --calendars 1,2 --today # Personal + Work
$ gog calendar events --calendars "Work,Personal" --today # same result
Corner Cases
| Scenario |
Behavior |
| Empty input |
Error: "no calendars specified" |
Numeric index 1,2,3 |
Resolve to Nth calendar from list (1-indexed) |
| Index out of range |
Error: "calendar index N out of range (have M calendars)" |
| Unknown name/ID |
Treat as literal ID (API will return 404 if invalid) |
| Duplicate entries |
Deduplicate before querying |
| Name with commas |
Use --cal repeated flag: --cal "Planning, Review & Sync" |
Special chars (&, spaces, quotes) |
Work naturally — shell quoting handles them |
| Calendar named "1" or "2" |
Numeric takes precedence; use full ID or --cal "1" for literal name |
Calendar names containing commas are the key reason to support the repeated --cal flag pattern. Examples of real-world calendar names that would break comma-separated parsing:
"Meeting, Planning & Review"
"John, Jane & Family"
"Project X, Phase 2"
With --cal repeated flag, these work safely:
gog calendar events --cal "Meeting, Planning & Review" --cal "Work" --today
Usage Examples
# Numeric indices — quick selection after viewing `gog calendar calendars`
gog calendar events --calendars 1,2,5 --today
gog calendar events --calendars 1 --week
# Repeated flag (--cal) — safe for any calendar name, including those with commas
gog calendar events --cal "Work" --cal "Family" --cal "Holidays" --today
gog calendar events --cal "Meeting, Planning & Review" --cal "Project X, Phase 2" --week
# Comma-separated names (--calendars) — concise when names have no commas
gog calendar events --calendars "Work,Family,Holidays" --today
gog calendar events --calendars "primary,shared@team.com" --week
# Mixed: indices, names, and IDs
gog calendar events --calendars "1,Work,user@example.com" --today
gog calendar events --cal "1" --cal "My Calendar" --cal "user@example.com" --today
# With other flags (composable)
gog calendar events --calendars 1,2 --from 2024-01-01 --to 2024-01-31 --json
Implementation Suggestions
These are just gentle suggestions — you know the codebase best:
-
New utility file — Perhaps calendar_names_utils.go for fetchCalendarNameToID() and resolveCalendarIDs(), mirroring the Gmail label resolution pattern
-
New handler function — Something like listSelectedCalendarsEvents() in calendar_list.go, similar to listAllCalendarsEvents() but accepting a []string of resolved IDs
-
Flag choice — The --cal []string repeated flag (like --reminder) handles edge cases better; --calendars comma-separated is more concise. Either or both would be valuable
-
Merge inputs — If both flags are supported, they could be merged: append(c.Cal, splitCSV(c.Calendars)...)
-
Validation in Run() — Add mutual exclusivity check for --cal/--calendars alongside existing --all / calendarId checks
-
Reuse splitCSV() — Already handles trimming and empty filtering (for --calendars option)
-
Tests — calendar_selected_events_test.go following the pattern of calendar_all_events_test.go
Of course, if there's a simpler approach or architectural considerations I've missed, I trust your judgment entirely.
Summary
| Aspect |
Details |
| Numeric indices |
--calendars 1,2,5 (quick selection from list) |
| Repeated flag |
--cal "cal1" --cal "cal2" (handles commas in names) |
| Comma-separated |
--calendars "cal1,cal2" (concise alternative) |
| Lookup |
By index, name (case-insensitive), or ID |
| API calls |
1 (CalendarList for resolution) + N (selected calendars only) |
| Backward compat |
100% — existing behavior unchanged |
| Precedent |
--cal follows --reminder pattern; --calendars follows freebusy/conflicts |
Thank you for building such a useful tool — it's been great for scripting Google Workspace workflows.
Feature Request: Multi-Calendar Selection for
gog calendar eventsProposal
Add support for querying a specific subset of calendars in
gog calendar events, via one or more of:Numeric index (
--calendars 1,2,5) — quick selection by position fromgog calendar calendarslist:Repeated flag (
--cal) — robust, handles calendar names containing commas:Comma-separated names/IDs (
--calendars) — concise for simple names:gog calendar events --calendars "Work,Family,Holidays" --todayAll approaches would support mixing: numeric indices, calendar names (case-insensitive), and full calendar IDs.
Rationale
API Efficiency
The Google Calendar API's
Events.listendpoint requires a singlecalendarIdper request — there's no native "query multiple calendars" parameter. Currently, users who need events from 2-3 specific calendars must either:--all— makes N+1 API calls (1 CalendarList + N Events calls for all calendars), then filter results client-sidegog calendar events cal1 --today && gog calendar events cal2 --todayFor users with many calendars (10-25 is common), option 1 wastes API quota and bandwidth fetching unwanted data. A
--calendarsflag would make only the necessary calls.Ergonomics
gog calendar calendars, users can quickly select by number:--calendars 1,2,5instead of copying long IDsv9c8pfp6n57g49cacq96r8vlh4@group.calendar.google.comare not user-friendly; names like "Work" or "Family" areCurrent Implementation Understanding
I traced through the codebase to understand how
--allworks:Flow (
internal/cmd/calendar.go+calendar_list.go)--allandcalendarIdare mutually exclusivelistAllCalendarsEvents()vslistCalendarEvents()svc.CalendarList.List()to get all calendarscalResp.Items, callingsvc.Events.List(cal.Id)for eacheventWithCalendarwrapper tracks source calendar for displayExisting Multi-Value Patterns
The codebase already has precedents:
freebusy<calendarIds>gog calendar freebusy cal1,cal2conflicts--calendars--calendars "primary,work"create--reminder--reminder popup:30m --reminder email:1dName Resolution Precedent
Gmail labels already implement name-to-ID resolution in
gmail_labels_utils.go:This pattern (case-insensitive lookup with fallback to treating input as ID) could be adapted for calendars.
Proposed Design
Command Structure
Two complementary approaches, following existing patterns in the codebase:
Option A: Repeated flag (
--cal) — handles names with commasOption B: Comma-separated (
--calendars) — concise for simple namesBoth options could coexist (merged into single list before resolution), or just one could be implemented — whichever fits the project's style better.
Mutual Exclusivity
(If both
--caland--calendarsare supported, they could be additive rather than exclusive.)Calendar Resolution (Optional Enhancement)
A utility similar to Gmail's label resolution, extended to support numeric indices:
Resolution order for each input:
"1","2"), resolve to Nth calendar fromCalendarList.List()(1-indexed to match display)cal.Summarycal.IdThis allows intuitive usage after running
gog calendar calendars:Corner Cases
1,2,3--calrepeated flag:--cal "Planning, Review & Sync"&, spaces, quotes)--cal "1"for literal nameCalendar names containing commas are the key reason to support the repeated
--calflag pattern. Examples of real-world calendar names that would break comma-separated parsing:"Meeting, Planning & Review""John, Jane & Family""Project X, Phase 2"With
--calrepeated flag, these work safely:Usage Examples
Implementation Suggestions
These are just gentle suggestions — you know the codebase best:
New utility file — Perhaps
calendar_names_utils.goforfetchCalendarNameToID()andresolveCalendarIDs(), mirroring the Gmail label resolution patternNew handler function — Something like
listSelectedCalendarsEvents()incalendar_list.go, similar tolistAllCalendarsEvents()but accepting a[]stringof resolved IDsFlag choice — The
--cal []stringrepeated flag (like--reminder) handles edge cases better;--calendarscomma-separated is more concise. Either or both would be valuableMerge inputs — If both flags are supported, they could be merged:
append(c.Cal, splitCSV(c.Calendars)...)Validation in
Run()— Add mutual exclusivity check for--cal/--calendarsalongside existing--all/calendarIdchecksReuse
splitCSV()— Already handles trimming and empty filtering (for--calendarsoption)Tests —
calendar_selected_events_test.gofollowing the pattern ofcalendar_all_events_test.goOf course, if there's a simpler approach or architectural considerations I've missed, I trust your judgment entirely.
Summary
--calendars 1,2,5(quick selection from list)--cal "cal1" --cal "cal2"(handles commas in names)--calendars "cal1,cal2"(concise alternative)--calfollows--reminderpattern;--calendarsfollowsfreebusy/conflictsThank you for building such a useful tool — it's been great for scripting Google Workspace workflows.