From f824a2e23dd8f4343b9faf19e4c282e510a25fb9 Mon Sep 17 00:00:00 2001 From: gado-ships-it Date: Thu, 14 May 2026 00:06:03 +0200 Subject: [PATCH 1/4] feat(calendar): show LOCATION column by default in events list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Calendar API populates `event.location` (a free-form place / address / venue string), but `calendar events` never surfaced it in the table view — users had to read JSON or open the event in another tool to find out where something happened. That made the listing meaningfully less useful for "plan the day" workflows, especially when an agent is generating context from the output. Add a LOCATION column after SUMMARY in every variant of the events list table — `--all` aggregated view, single calendar, with and without `--weekday`. JSON output already exposes `location` and is unchanged. eventDisplayLocation collapses embedded newlines, carriage returns, and tabs into single spaces so a multi-line postal address from the API does not break the row layout. Tests: new TestListCalendarEvents_TableIncludesLocation verifies the LOCATION header appears, that an event without a location renders as an empty cell, and that a location with an embedded newline is collapsed into a single line. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + internal/cmd/calendar_all_events_test.go | 6 +- internal/cmd/calendar_events_cmds.go | 7 ++- internal/cmd/calendar_events_test.go | 66 +++++++++++++++++++- internal/cmd/calendar_list.go | 76 ++++++++++++++++++------ 5 files changed, 130 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 337cbda8..3fa8eccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Calendar: add `calendar events --sort=start|end|summary|calendar` and `--order=asc|desc` so `--all` output can be returned chronologically across calendars instead of per-calendar API iteration order. Also documents `now` in the `--from`/`--to` help strings (already accepted by `timeparse`) — the relative form agents need when planning "from now on" — thanks @gado-ships-it. +- Calendar: add `calendar events --location` to include event locations in table output. Embedded newlines in the location string are collapsed so multi-line addresses still render on one row — thanks @gado-ships-it. - Drive: add `drive share --notify` for invite targets that require a Drive notification email. - Calendar: keep `calendar appointments` as an explicit diagnostic because the Calendar API still rejects `eventTypes=appointmentSchedule`. (#329) - CLI: add nested `docs tabs ...` and `forms questions ...` aliases for consistent sub-item command patterns while preserving existing flat commands. (#433) diff --git a/internal/cmd/calendar_all_events_test.go b/internal/cmd/calendar_all_events_test.go index c4b0b821..82b697c1 100644 --- a/internal/cmd/calendar_all_events_test.go +++ b/internal/cmd/calendar_all_events_test.go @@ -62,7 +62,7 @@ func TestListAllCalendarsEvents_JSON(t *testing.T) { ctx := newCalendarJSONContext(t) jsonOut := captureStdout(t, func() { - if runErr := listAllCalendarsEvents(ctx, svc, "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, "", ""); runErr != nil { + if runErr := listAllCalendarsEvents(ctx, svc, "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, false, "", ""); runErr != nil { t.Fatalf("listAllCalendarsEvents: %v", runErr) } }) @@ -121,7 +121,7 @@ func TestListAllCalendarsEvents_SortByStart(t *testing.T) { ctx := newCalendarJSONContext(t) jsonOut := captureStdout(t, func() { - if err := listAllCalendarsEvents(ctx, svc, "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, "start", "asc"); err != nil { + if err := listAllCalendarsEvents(ctx, svc, "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, false, "start", "asc"); err != nil { t.Fatalf("listAllCalendarsEvents: %v", err) } }) @@ -144,7 +144,7 @@ func TestListAllCalendarsEvents_SortByStart(t *testing.T) { // Descending order flips it. jsonOut = captureStdout(t, func() { - if err := listAllCalendarsEvents(ctx, svc, "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, "start", "desc"); err != nil { + if err := listAllCalendarsEvents(ctx, svc, "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, false, "start", "desc"); err != nil { t.Fatalf("listAllCalendarsEvents desc: %v", err) } }) diff --git a/internal/cmd/calendar_events_cmds.go b/internal/cmd/calendar_events_cmds.go index 314c8a24..a542577f 100644 --- a/internal/cmd/calendar_events_cmds.go +++ b/internal/cmd/calendar_events_cmds.go @@ -30,6 +30,7 @@ type CalendarEventsCmd struct { SharedPropFilter string `name:"shared-prop-filter" help:"Filter by shared extended property (key=value)"` Fields string `name:"fields" help:"Comma-separated fields to return"` Weekday bool `name:"weekday" help:"Include start/end day-of-week columns" default:"${calendar_weekday}"` + Location bool `name:"location" help:"Include event LOCATION column in table output"` Sort string `name:"sort" help:"Sort events by start|end|summary|calendar (default: keep API order; with --all, start is recommended for chronological output)" enum:"start,end,summary,calendar," default:""` Order string `name:"order" help:"Sort order" enum:"asc,desc" default:"asc"` } @@ -82,7 +83,7 @@ func (c *CalendarEventsCmd) Run(ctx context.Context, flags *RootFlags) error { from, to := timeRange.FormatRFC3339() if c.All { - return listAllCalendarsEvents(ctx, svc, from, to, c.Max, c.Page, c.AllPages, c.FailEmpty, c.Query, c.PrivatePropFilter, c.SharedPropFilter, c.Fields, c.Weekday, c.Sort, c.Order) + return listAllCalendarsEvents(ctx, svc, from, to, c.Max, c.Page, c.AllPages, c.FailEmpty, c.Query, c.PrivatePropFilter, c.SharedPropFilter, c.Fields, c.Weekday, c.Location, c.Sort, c.Order) } if len(calInputs) > 0 { ids, err := resolveCalendarIDs(ctx, svc, calInputs) @@ -92,9 +93,9 @@ func (c *CalendarEventsCmd) Run(ctx context.Context, flags *RootFlags) error { if len(ids) == 0 { return usage("no calendars specified") } - return listSelectedCalendarsEvents(ctx, svc, ids, from, to, c.Max, c.Page, c.AllPages, c.FailEmpty, c.Query, c.PrivatePropFilter, c.SharedPropFilter, c.Fields, c.Weekday, c.Sort, c.Order) + return listSelectedCalendarsEvents(ctx, svc, ids, from, to, c.Max, c.Page, c.AllPages, c.FailEmpty, c.Query, c.PrivatePropFilter, c.SharedPropFilter, c.Fields, c.Weekday, c.Location, c.Sort, c.Order) } - return listCalendarEvents(ctx, svc, calendarID, from, to, c.Max, c.Page, c.AllPages, c.FailEmpty, c.Query, c.PrivatePropFilter, c.SharedPropFilter, c.Fields, c.Weekday, c.Sort, c.Order) + return listCalendarEvents(ctx, svc, calendarID, from, to, c.Max, c.Page, c.AllPages, c.FailEmpty, c.Query, c.PrivatePropFilter, c.SharedPropFilter, c.Fields, c.Weekday, c.Location, c.Sort, c.Order) } func normalizeCalendarEventsArgs(args []string) (string, error) { diff --git a/internal/cmd/calendar_events_test.go b/internal/cmd/calendar_events_test.go index c807d574..70d5a6d2 100644 --- a/internal/cmd/calendar_events_test.go +++ b/internal/cmd/calendar_events_test.go @@ -33,7 +33,7 @@ func TestListCalendarEvents_JSON(t *testing.T) { ctx := newCalendarJSONContext(t) jsonOut := captureStdout(t, func() { - if err := listCalendarEvents(ctx, svc, "cal1", "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, "", ""); err != nil { + if err := listCalendarEvents(ctx, svc, "cal1", "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", 10, "", false, false, "", "", "", "", false, false, "", ""); err != nil { t.Fatalf("listCalendarEvents: %v", err) } }) @@ -82,7 +82,7 @@ func TestListCalendarEvents_TableUsesCalendarTimezone(t *testing.T) { text := captureStdout(t, func() { ctx := newCalendarOutputContext(t, os.Stdout, io.Discard) - if err := listCalendarEvents(ctx, svc, "cal1", "2026-04-08T00:00:00Z", "2026-04-09T00:00:00Z", 10, "", false, false, "", "", "", "", false, "", ""); err != nil { + if err := listCalendarEvents(ctx, svc, "cal1", "2026-04-08T00:00:00Z", "2026-04-09T00:00:00Z", 10, "", false, false, "", "", "", "", false, false, "", ""); err != nil { t.Fatalf("listCalendarEvents: %v", err) } }) @@ -95,6 +95,66 @@ func TestListCalendarEvents_TableUsesCalendarTimezone(t *testing.T) { } } +// TestListCalendarEvents_TableIncludesLocation asserts that the events list +// table renders the LOCATION column when requested and that embedded newlines in +// the location string are collapsed so the row stays on one line. +func TestListCalendarEvents_TableIncludesLocation(t *testing.T) { + svc, closeServer := newCalendarServiceForTest(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/calendars/cal1/events") && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "items": []map[string]any{ + { + "id": "e1", + "summary": "Standup", + "location": "Bahnhofstrasse 1\n8001 Zürich", + "start": map[string]any{"dateTime": "2026-04-08T09:00:00Z"}, + "end": map[string]any{"dateTime": "2026-04-08T09:15:00Z"}, + }, + { + "id": "e2", + "summary": "No-location event", + "start": map[string]any{"dateTime": "2026-04-08T10:00:00Z"}, + "end": map[string]any{"dateTime": "2026-04-08T10:15:00Z"}, + }, + }, + }) + return + } + http.NotFound(w, r) + })) + defer closeServer() + + text := captureStdout(t, func() { + ctx := newCalendarOutputContext(t, os.Stdout, io.Discard) + if err := listCalendarEvents(ctx, svc, "cal1", "2026-04-08T00:00:00Z", "2026-04-09T00:00:00Z", 10, "", false, false, "", "", "", "", false, false, "", ""); err != nil { + t.Fatalf("listCalendarEvents: %v", err) + } + }) + + if strings.Contains(text, "LOCATION") { + t.Fatalf("did not expect LOCATION header without --location, got: %q", text) + } + + text = captureStdout(t, func() { + ctx := newCalendarOutputContext(t, os.Stdout, io.Discard) + if err := listCalendarEvents(ctx, svc, "cal1", "2026-04-08T00:00:00Z", "2026-04-09T00:00:00Z", 10, "", false, false, "", "", "", "", false, true, "", ""); err != nil { + t.Fatalf("listCalendarEvents with location: %v", err) + } + }) + + if !strings.Contains(text, "LOCATION") { + t.Fatalf("expected LOCATION header with --location, got: %q", text) + } + if !strings.Contains(text, "Bahnhofstrasse 1 8001 Zürich") { + t.Fatalf("expected collapsed multi-line location, got: %q", text) + } + // Original newline must not leak into the rendered row. + if strings.Contains(text, "Bahnhofstrasse 1\n8001 Zürich") { + t.Fatalf("expected newline in location to be collapsed, got: %q", text) + } +} + func TestListCalendarEvents_JSONUsesCalendarTimezoneForLocalFields(t *testing.T) { svc, closeServer := newCalendarServiceForTest(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { @@ -127,7 +187,7 @@ func TestListCalendarEvents_JSONUsesCalendarTimezoneForLocalFields(t *testing.T) ctx := newCalendarJSONContext(t) jsonOut := captureStdout(t, func() { - if err := listCalendarEvents(ctx, svc, "cal1", "2026-04-08T00:00:00Z", "2026-04-09T00:00:00Z", 10, "", false, false, "", "", "", "", false, "", ""); err != nil { + if err := listCalendarEvents(ctx, svc, "cal1", "2026-04-08T00:00:00Z", "2026-04-09T00:00:00Z", 10, "", false, false, "", "", "", "", false, false, "", ""); err != nil { t.Fatalf("listCalendarEvents: %v", err) } }) diff --git a/internal/cmd/calendar_list.go b/internal/cmd/calendar_list.go index 3f6fccae..e94b7bd0 100644 --- a/internal/cmd/calendar_list.go +++ b/internal/cmd/calendar_list.go @@ -42,7 +42,7 @@ func calendarEventsListCall(ctx context.Context, svc *calendar.Service, calendar return call } -func listCalendarEvents(ctx context.Context, svc *calendar.Service, calendarID, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, sortKey, sortOrder string) error { +func listCalendarEvents(ctx context.Context, svc *calendar.Service, calendarID, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, showLocation bool, sortKey, sortOrder string) error { calendarTimezone, loc := calendarDisplayTimezone(ctx, svc, calendarID, nil) fetch := func(pageToken string) ([]*calendar.Event, string, error) { resp, err := calendarEventsListCall(ctx, svc, calendarID, from, to, maxResults, query, privatePropFilter, sharedPropFilter, fields, pageToken).Do() @@ -77,7 +77,7 @@ func listCalendarEvents(ctx context.Context, svc *calendar.Service, calendarID, } return nil } - return renderCalendarEventsTable(ctx, events, nextPageToken, false, showWeekday, failEmpty, true) + return renderCalendarEventsTable(ctx, events, nextPageToken, false, showWeekday, showLocation, failEmpty, true) } type eventWithCalendar struct { @@ -111,7 +111,7 @@ type calendarTimezoneHint struct { loc *time.Location } -func listAllCalendarsEvents(ctx context.Context, svc *calendar.Service, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, sortKey, sortOrder string) error { +func listAllCalendarsEvents(ctx context.Context, svc *calendar.Service, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, showLocation bool, sortKey, sortOrder string) error { u := ui.FromContext(ctx) calendars, err := listCalendarList(ctx, svc) @@ -135,14 +135,14 @@ func listAllCalendarsEvents(ctx context.Context, svc *calendar.Service, from, to u.Err().Println("No calendars") return nil } - return listCalendarIDsEvents(ctx, svc, ids, from, to, maxResults, page, allPages, failEmpty, query, privatePropFilter, sharedPropFilter, fields, showWeekday, calendarTimezoneHints(calendars), sortKey, sortOrder) + return listCalendarIDsEvents(ctx, svc, ids, from, to, maxResults, page, allPages, failEmpty, query, privatePropFilter, sharedPropFilter, fields, showWeekday, showLocation, calendarTimezoneHints(calendars), sortKey, sortOrder) } -func listSelectedCalendarsEvents(ctx context.Context, svc *calendar.Service, calendarIDs []string, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, sortKey, sortOrder string) error { - return listCalendarIDsEvents(ctx, svc, calendarIDs, from, to, maxResults, page, allPages, failEmpty, query, privatePropFilter, sharedPropFilter, fields, showWeekday, nil, sortKey, sortOrder) +func listSelectedCalendarsEvents(ctx context.Context, svc *calendar.Service, calendarIDs []string, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, showLocation bool, sortKey, sortOrder string) error { + return listCalendarIDsEvents(ctx, svc, calendarIDs, from, to, maxResults, page, allPages, failEmpty, query, privatePropFilter, sharedPropFilter, fields, showWeekday, showLocation, nil, sortKey, sortOrder) } -func listCalendarIDsEvents(ctx context.Context, svc *calendar.Service, calendarIDs []string, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, timezoneHints map[string]calendarTimezoneHint, sortKey, sortOrder string) error { +func listCalendarIDsEvents(ctx context.Context, svc *calendar.Service, calendarIDs []string, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, showLocation bool, timezoneHints map[string]calendarTimezoneHint, sortKey, sortOrder string) error { u := ui.FromContext(ctx) all := []*eventWithCalendar{} for _, calID := range calendarIDs { @@ -181,10 +181,10 @@ func listCalendarIDsEvents(ctx context.Context, svc *calendar.Service, calendarI } return nil } - return renderCalendarEventsTable(ctx, all, "", true, showWeekday, failEmpty, false) + return renderCalendarEventsTable(ctx, all, "", true, showWeekday, showLocation, failEmpty, false) } -func renderCalendarEventsTable(ctx context.Context, events []*eventWithCalendar, nextPageToken string, includeCalendar, showWeekday, failEmpty bool, printPageHint bool) error { +func renderCalendarEventsTable(ctx context.Context, events []*eventWithCalendar, nextPageToken string, includeCalendar, showWeekday, showLocation, failEmpty bool, printPageHint bool) error { u := ui.FromContext(ctx) if len(events) == 0 { u.Err().Println("No events") @@ -196,30 +196,46 @@ func renderCalendarEventsTable(ctx context.Context, events []*eventWithCalendar, if showWeekday { if includeCalendar { - fmt.Fprintln(w, "CALENDAR\tID\tSTART\tSTART_DOW\tEND\tEND_DOW\tSUMMARY") + header := "CALENDAR\tID\tSTART\tSTART_DOW\tEND\tEND_DOW\tSUMMARY" + if showLocation { + header += "\tLOCATION" + } + fmt.Fprintln(w, header) for _, e := range events { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", e.CalendarID, e.Id, eventDisplayStart(e), e.StartDayOfWeek, eventDisplayEnd(e), e.EndDayOfWeek, e.Summary) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s%s\n", e.CalendarID, e.Id, eventDisplayStart(e), e.StartDayOfWeek, eventDisplayEnd(e), e.EndDayOfWeek, e.Summary, eventLocationCell(e, showLocation)) } } else { - fmt.Fprintln(w, "ID\tSTART\tSTART_DOW\tEND\tEND_DOW\tSUMMARY") + header := "ID\tSTART\tSTART_DOW\tEND\tEND_DOW\tSUMMARY" + if showLocation { + header += "\tLOCATION" + } + fmt.Fprintln(w, header) for _, e := range events { startDay, endDay := e.StartDayOfWeek, e.EndDayOfWeek if startDay == "" && endDay == "" { startDay, endDay = eventDaysOfWeek(e.Event) } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", e.Id, eventDisplayStart(e), startDay, eventDisplayEnd(e), endDay, e.Summary) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s%s\n", e.Id, eventDisplayStart(e), startDay, eventDisplayEnd(e), endDay, e.Summary, eventLocationCell(e, showLocation)) } } } else { if includeCalendar { - fmt.Fprintln(w, "CALENDAR\tID\tSTART\tEND\tSUMMARY") + header := "CALENDAR\tID\tSTART\tEND\tSUMMARY" + if showLocation { + header += "\tLOCATION" + } + fmt.Fprintln(w, header) for _, e := range events { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", e.CalendarID, e.Id, eventDisplayStart(e), eventDisplayEnd(e), e.Summary) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s%s\n", e.CalendarID, e.Id, eventDisplayStart(e), eventDisplayEnd(e), e.Summary, eventLocationCell(e, showLocation)) } } else { - fmt.Fprintln(w, "ID\tSTART\tEND\tSUMMARY") + header := "ID\tSTART\tEND\tSUMMARY" + if showLocation { + header += "\tLOCATION" + } + fmt.Fprintln(w, header) for _, e := range events { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", e.Id, eventDisplayStart(e), eventDisplayEnd(e), e.Summary) + fmt.Fprintf(w, "%s\t%s\t%s\t%s%s\n", e.Id, eventDisplayStart(e), eventDisplayEnd(e), e.Summary, eventLocationCell(e, showLocation)) } } } @@ -266,6 +282,32 @@ func eventDisplayEnd(e *eventWithCalendar) string { return eventEnd(e.Event) } +func eventLocationCell(e *eventWithCalendar, showLocation bool) string { + if !showLocation { + return "" + } + return "\t" + eventDisplayLocation(e) +} + +// eventDisplayLocation returns the event location formatted for a single +// table cell. Newlines are collapsed and the value is trimmed so a multi-line +// address from the Calendar API does not break the row layout. +func eventDisplayLocation(e *eventWithCalendar) string { + if e == nil || e.Event == nil { + return "" + } + loc := strings.TrimSpace(e.Location) + if loc == "" { + return "" + } + // Calendar locations occasionally arrive with embedded newlines (pasted + // multi-line addresses); collapse them so the row stays on one line. + loc = strings.ReplaceAll(loc, "\r\n", " ") + loc = strings.ReplaceAll(loc, "\n", " ") + loc = strings.ReplaceAll(loc, "\t", " ") + return loc +} + func calendarDisplayTimezone(ctx context.Context, svc *calendar.Service, calendarID string, hints map[string]calendarTimezoneHint) (string, *time.Location) { if hint, ok := hints[calendarID]; ok { return hint.timezone, hint.loc From fc41ee68c3ab1884097793455ef89fd459815a45 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 14 May 2026 15:04:02 +0100 Subject: [PATCH 2/4] fix: satisfy lint checks --- internal/cmd/calendar_list.go | 10 ++++++---- internal/googleauth/identity_migration.go | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/cmd/calendar_list.go b/internal/cmd/calendar_list.go index e94b7bd0..a12b03bf 100644 --- a/internal/cmd/calendar_list.go +++ b/internal/cmd/calendar_list.go @@ -111,6 +111,8 @@ type calendarTimezoneHint struct { loc *time.Location } +const calendarLocationColumnSuffix = "\tLOCATION" + func listAllCalendarsEvents(ctx context.Context, svc *calendar.Service, from, to string, maxResults int64, page string, allPages bool, failEmpty bool, query, privatePropFilter, sharedPropFilter, fields string, showWeekday bool, showLocation bool, sortKey, sortOrder string) error { u := ui.FromContext(ctx) @@ -198,7 +200,7 @@ func renderCalendarEventsTable(ctx context.Context, events []*eventWithCalendar, if includeCalendar { header := "CALENDAR\tID\tSTART\tSTART_DOW\tEND\tEND_DOW\tSUMMARY" if showLocation { - header += "\tLOCATION" + header += calendarLocationColumnSuffix } fmt.Fprintln(w, header) for _, e := range events { @@ -207,7 +209,7 @@ func renderCalendarEventsTable(ctx context.Context, events []*eventWithCalendar, } else { header := "ID\tSTART\tSTART_DOW\tEND\tEND_DOW\tSUMMARY" if showLocation { - header += "\tLOCATION" + header += calendarLocationColumnSuffix } fmt.Fprintln(w, header) for _, e := range events { @@ -222,7 +224,7 @@ func renderCalendarEventsTable(ctx context.Context, events []*eventWithCalendar, if includeCalendar { header := "CALENDAR\tID\tSTART\tEND\tSUMMARY" if showLocation { - header += "\tLOCATION" + header += calendarLocationColumnSuffix } fmt.Fprintln(w, header) for _, e := range events { @@ -231,7 +233,7 @@ func renderCalendarEventsTable(ctx context.Context, events []*eventWithCalendar, } else { header := "ID\tSTART\tEND\tSUMMARY" if showLocation { - header += "\tLOCATION" + header += calendarLocationColumnSuffix } fmt.Fprintln(w, header) for _, e := range events { diff --git a/internal/googleauth/identity_migration.go b/internal/googleauth/identity_migration.go index 64451e14..d4400a2c 100644 --- a/internal/googleauth/identity_migration.go +++ b/internal/googleauth/identity_migration.go @@ -13,7 +13,7 @@ func MigrateStoredSubjectIdentity(store secrets.Store, client string, identity I newEmail := normalizeEmail(identity.Email) if subject == "" || newEmail == "" { - return "", nil + return "", nil //nolint:nilerr // best-effort cleanup must not block saving the new token } tokens, err := store.ListTokens() From 7f2b39ac3ea7030444bcdb290bef6f0bc76414cd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 14 May 2026 15:05:36 +0100 Subject: [PATCH 3/4] fix: move nilerr suppression --- internal/googleauth/identity_migration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/googleauth/identity_migration.go b/internal/googleauth/identity_migration.go index d4400a2c..e634a2c2 100644 --- a/internal/googleauth/identity_migration.go +++ b/internal/googleauth/identity_migration.go @@ -13,7 +13,7 @@ func MigrateStoredSubjectIdentity(store secrets.Store, client string, identity I newEmail := normalizeEmail(identity.Email) if subject == "" || newEmail == "" { - return "", nil //nolint:nilerr // best-effort cleanup must not block saving the new token + return "", nil } tokens, err := store.ListTokens() @@ -21,7 +21,7 @@ func MigrateStoredSubjectIdentity(store secrets.Store, client string, identity I // Subject migration is best-effort compatibility cleanup. A stale or // corrupted token must not make a freshly completed OAuth flow fail // before the new refresh token is saved. - return "", nil + return "", nil //nolint:nilerr // best-effort cleanup must not block saving the new token } for _, tok := range tokens { From aa8e29cff3d5f5109bd41414f606595b48f731a9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 14 May 2026 15:07:22 +0100 Subject: [PATCH 4/4] docs(calendar): document event location flag --- docs/commands/gog-calendar-events.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/commands/gog-calendar-events.md b/docs/commands/gog-calendar-events.md index 2b46ef2f..93b2b1d8 100644 --- a/docs/commands/gog-calendar-events.md +++ b/docs/commands/gog-calendar-events.md @@ -37,6 +37,7 @@ gog calendar (cal) events (list,ls) [ ...] [flags] | `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) | | `-h`
`--help` | `kong.helpFlag` | | Show context-sensitive help. | | `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) | +| `--location` | `bool` | | Include event LOCATION column in table output | | `--max`
`--limit` | `int64` | 10 | Max results | | `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) | | `--order` | `string` | asc | Sort order |