gogcli-mcp-calendar 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31074,7 +31074,7 @@ async function run(args, options = {}) {
31074
31074
 
31075
31075
  // ../gogcli-mcp/src/tools/utils.ts
31076
31076
  var accountParam = external_exports.string().optional().describe(
31077
- "Google account email to use (overrides GOG_ACCOUNT env var)"
31077
+ "Google account email to use, e.g. you@gmail.com \u2014 must be the full address, not a bare username. Overrides the GOG_ACCOUNT env var. Omit to use the single configured account."
31078
31078
  );
31079
31079
  var ids = {
31080
31080
  course: external_exports.string().describe("Course ID"),
@@ -31133,26 +31133,41 @@ function toError(err) {
31133
31133
  }
31134
31134
  var AUTH_ERROR_PATTERN = /\b(401|unauthorized|token.*(expired|revoked)|invalid_grant)\b/i;
31135
31135
  var TRANSIENT_ERROR_PATTERN = /\b429\b|\b5\d\d\b|\bquota\b|rateLimit|\bDEADLINE_EXCEEDED\b/i;
31136
+ var GRID_LIMIT_ERROR_PATTERN = /exceeds grid limits/i;
31136
31137
  var AUTH_HINT = "\n\nAuthentication may have expired. Use gog_auth_add to re-authorize the account. Ask the user if they would like to re-authenticate.";
31137
31138
  var TRANSIENT_HINT = "\n\nThis error is often transient. Retry the same call before trying a different approach (do not fall back to smaller writes or row-by-row operations).";
31139
+ var GRID_LIMIT_HINT = "\n\nThe target range is outside the sheet's current grid. Add the missing rows or columns first with gog_sheets_insert (dimension: rows or cols), then retry the write.";
31140
+ function formatAccountList(raw) {
31141
+ try {
31142
+ const parsed = JSON.parse(raw);
31143
+ if (Array.isArray(parsed?.accounts)) {
31144
+ return parsed.accounts.map((a) => a?.email).filter(Boolean).join("\n");
31145
+ }
31146
+ } catch {
31147
+ }
31148
+ return raw.trim();
31149
+ }
31150
+ async function diagnose(err) {
31151
+ const errText = toError(err).content[0].text;
31152
+ const isAuthError = AUTH_ERROR_PATTERN.test(errText);
31153
+ const isTransientError = !isAuthError && TRANSIENT_ERROR_PATTERN.test(errText);
31154
+ const isGridLimitError = GRID_LIMIT_ERROR_PATTERN.test(errText);
31155
+ const hint = isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : isGridLimitError ? GRID_LIMIT_HINT : "";
31156
+ try {
31157
+ const accounts = formatAccountList(await run(["auth", "list"]));
31158
+ return toText(`${errText}
31159
+
31160
+ Configured accounts:
31161
+ ${accounts || "(none)"}${hint}`);
31162
+ } catch {
31163
+ return toText(`${errText}${hint}`);
31164
+ }
31165
+ }
31138
31166
  async function runOrDiagnose(args, options) {
31139
31167
  try {
31140
31168
  return toText(await run(args, options));
31141
31169
  } catch (err) {
31142
- const base = toError(err);
31143
- const errText = base.content[0].text;
31144
- const isAuthError = AUTH_ERROR_PATTERN.test(errText);
31145
- const isTransientError = !isAuthError && TRANSIENT_ERROR_PATTERN.test(errText);
31146
- const hint = isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : "";
31147
- try {
31148
- const accounts = await run(["auth", "list"]);
31149
- return toText(`${errText}
31150
-
31151
- Configured accounts:
31152
- ${accounts}${hint}`);
31153
- } catch {
31154
- return toText(`${errText}${hint}`);
31155
- }
31170
+ return diagnose(err);
31156
31171
  }
31157
31172
  }
31158
31173
 
@@ -31338,9 +31353,15 @@ function registerCalendarTools(server2) {
31338
31353
 
31339
31354
  // ../gogcli-mcp/src/tools/sheets.ts
31340
31355
  var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
31356
+ var dryRunParam = external_exports.boolean().optional().describe(
31357
+ "Preview the operation without modifying the sheet (gog --dry-run): reports the intended actions and exits without writing."
31358
+ );
31359
+ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
31360
+ 'Safety guard against silent overwrites: before writing, read the target range and refuse the write if any target cell already holds data. Costs one extra read. Anchor ranges (e.g. "Sheet1!A1") are expanded to the full area your values will cover; explicit and named ranges are checked as-is.'
31361
+ );
31341
31362
 
31342
31363
  // ../gogcli-mcp/src/server.ts
31343
- var VERSION = true ? "2.1.0" : "0.0.0";
31364
+ var VERSION = true ? "2.3.0" : "0.0.0";
31344
31365
  function createServer(options) {
31345
31366
  return new McpServer({
31346
31367
  name: options?.name ?? "gogcli",
@@ -31444,6 +31465,132 @@ function registerExtraCalendarTools(server2) {
31444
31465
  if (alias) args.push(`--alias=${alias}`);
31445
31466
  return runOrDiagnose(args, {});
31446
31467
  });
31468
+ server2.registerTool("gog_calendar_calendars", {
31469
+ description: "List the calendars in your calendar list (id, summary, access role, primary flag).",
31470
+ annotations: { readOnlyHint: true },
31471
+ inputSchema: {
31472
+ max: external_exports.number().optional().describe("Max results (default: 100)"),
31473
+ page: external_exports.string().optional().describe("Page token"),
31474
+ all: external_exports.boolean().optional().describe("Fetch all pages"),
31475
+ account: accountParam
31476
+ }
31477
+ }, async ({ max, page, all, account }) => {
31478
+ const args = ["calendar", "calendars"];
31479
+ if (max !== void 0) args.push(`--max=${max}`);
31480
+ if (page) args.push(`--page=${page}`);
31481
+ if (all) args.push("--all");
31482
+ return runOrDiagnose(args, { account });
31483
+ });
31484
+ server2.registerTool("gog_calendar_search", {
31485
+ description: "Full-text search for events matching a query string, with optional time filters.",
31486
+ annotations: { readOnlyHint: true },
31487
+ inputSchema: {
31488
+ query: external_exports.string().describe("Search query"),
31489
+ from: external_exports.string().optional().describe("Start time (RFC3339, date, or relative: now, today, tomorrow, monday)"),
31490
+ to: external_exports.string().optional().describe("End time (RFC3339, date, or relative: now, today, tomorrow, monday)"),
31491
+ today: external_exports.boolean().optional().describe("Today only"),
31492
+ tomorrow: external_exports.boolean().optional().describe("Tomorrow only"),
31493
+ week: external_exports.boolean().optional().describe("This week (uses weekStart, default Mon)"),
31494
+ days: external_exports.number().optional().describe("Next N days"),
31495
+ weekStart: external_exports.string().optional().describe("Week start day for week (sun, mon, ...)"),
31496
+ calendar: external_exports.string().optional().describe("Calendar ID (default: primary)"),
31497
+ max: external_exports.number().optional().describe("Max results (default: 25)"),
31498
+ account: accountParam
31499
+ }
31500
+ }, async ({ query, from, to, today, tomorrow, week, days, weekStart, calendar, max, account }) => {
31501
+ const args = ["calendar", "search", query];
31502
+ if (from) args.push(`--from=${from}`);
31503
+ if (to) args.push(`--to=${to}`);
31504
+ if (today) args.push("--today");
31505
+ if (tomorrow) args.push("--tomorrow");
31506
+ if (week) args.push("--week");
31507
+ if (days !== void 0) args.push(`--days=${days}`);
31508
+ if (weekStart) args.push(`--week-start=${weekStart}`);
31509
+ if (calendar) args.push(`--calendar=${calendar}`);
31510
+ if (max !== void 0) args.push(`--max=${max}`);
31511
+ return runOrDiagnose(args, { account });
31512
+ });
31513
+ server2.registerTool("gog_calendar_freebusy", {
31514
+ description: "Query free/busy intervals for one or more calendars over a time window.",
31515
+ annotations: { readOnlyHint: true },
31516
+ inputSchema: {
31517
+ from: external_exports.string().describe("Start time (RFC3339, required)"),
31518
+ to: external_exports.string().describe("End time (RFC3339, required)"),
31519
+ calendarIds: external_exports.string().optional().describe("Comma-separated calendar IDs, names, or indices"),
31520
+ all: external_exports.boolean().optional().describe("Query all calendars"),
31521
+ account: accountParam
31522
+ }
31523
+ }, async ({ from, to, calendarIds, all, account }) => {
31524
+ const args = ["calendar", "freebusy"];
31525
+ if (calendarIds) args.push(calendarIds);
31526
+ args.push(`--from=${from}`);
31527
+ args.push(`--to=${to}`);
31528
+ if (all) args.push("--all");
31529
+ return runOrDiagnose(args, { account });
31530
+ });
31531
+ server2.registerTool("gog_calendar_colors", {
31532
+ description: "Show the available calendar and event color palette (color IDs to hex values).",
31533
+ annotations: { readOnlyHint: true },
31534
+ inputSchema: {
31535
+ account: accountParam
31536
+ }
31537
+ }, async ({ account }) => {
31538
+ return runOrDiagnose(["calendar", "colors"], { account });
31539
+ });
31540
+ server2.registerTool("gog_calendar_acl", {
31541
+ description: "List the access control list (sharing rules) for a calendar.",
31542
+ annotations: { readOnlyHint: true },
31543
+ inputSchema: {
31544
+ calendarId: external_exports.string().describe("Calendar ID"),
31545
+ max: external_exports.number().optional().describe("Max results (default: 100)"),
31546
+ page: external_exports.string().optional().describe("Page token"),
31547
+ all: external_exports.boolean().optional().describe("Fetch all pages"),
31548
+ account: accountParam
31549
+ }
31550
+ }, async ({ calendarId, max, page, all, account }) => {
31551
+ const args = ["calendar", "acl", calendarId];
31552
+ if (max !== void 0) args.push(`--max=${max}`);
31553
+ if (page) args.push(`--page=${page}`);
31554
+ if (all) args.push("--all");
31555
+ return runOrDiagnose(args, { account });
31556
+ });
31557
+ server2.registerTool("gog_calendar_move", {
31558
+ description: "Move an event from one calendar to another; the destination calendar becomes the organizer.",
31559
+ inputSchema: {
31560
+ calendarId: external_exports.string().describe("Source calendar ID"),
31561
+ eventId: external_exports.string().describe("Event ID"),
31562
+ destinationCalendarId: external_exports.string().describe("Destination calendar ID that becomes the event organizer"),
31563
+ sendUpdates: external_exports.enum(["all", "externalOnly", "none"]).optional().describe("Notification mode (default: none)"),
31564
+ account: accountParam
31565
+ }
31566
+ }, async ({ calendarId, eventId, destinationCalendarId, sendUpdates, account }) => {
31567
+ const args = ["calendar", "move", calendarId, eventId, destinationCalendarId];
31568
+ if (sendUpdates) args.push(`--send-updates=${sendUpdates}`);
31569
+ return runOrDiagnose(args, { account });
31570
+ });
31571
+ server2.registerTool("gog_calendar_out_of_office", {
31572
+ description: "Create an Out of Office event that auto-declines invitations during the block.",
31573
+ inputSchema: {
31574
+ from: external_exports.string().describe("Start date or datetime (RFC3339 or YYYY-MM-DD)"),
31575
+ to: external_exports.string().describe("End date or datetime (RFC3339 or YYYY-MM-DD)"),
31576
+ calendarId: external_exports.string().optional().describe("Calendar ID (default: primary)"),
31577
+ summary: external_exports.string().optional().describe('Out of office title (default: "Out of office")'),
31578
+ autoDecline: external_exports.enum(["none", "all", "new"]).optional().describe("Auto-decline mode (default: all)"),
31579
+ declineMessage: external_exports.string().optional().describe("Message for declined invitations"),
31580
+ allDay: external_exports.boolean().optional().describe("Create as an all-day event"),
31581
+ account: accountParam
31582
+ }
31583
+ }, async ({ from, to, calendarId, summary, autoDecline, declineMessage, allDay, account }) => {
31584
+ const args = ["calendar", "out-of-office"];
31585
+ if (calendarId) args.push(calendarId);
31586
+ args.push(`--from=${from}`);
31587
+ args.push(`--to=${to}`);
31588
+ if (summary) args.push(`--summary=${summary}`);
31589
+ if (autoDecline) args.push(`--auto-decline=${autoDecline}`);
31590
+ if (declineMessage) args.push(`--decline-message=${declineMessage}`);
31591
+ if (allDay) args.push("--all-day");
31592
+ return runOrDiagnose(args, { account });
31593
+ });
31447
31594
  server2.registerTool("gog_meet_participants", {
31448
31595
  description: "List participants from the latest (or a specific) Meet call.",
31449
31596
  annotations: { readOnlyHint: true },
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-calendar",
5
5
  "display_name": "gogcli (Calendar)",
6
- "version": "2.1.0",
6
+ "version": "2.3.0",
7
7
  "description": "Extended Google Calendar for Claude via gogcli — auth + Calendar events + Meet space management",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -102,6 +102,34 @@
102
102
  "name": "gog_calendar_run",
103
103
  "description": "Run any gog calendar subcommand (escape hatch)"
104
104
  },
105
+ {
106
+ "name": "gog_calendar_calendars",
107
+ "description": "List the calendars in your calendar list"
108
+ },
109
+ {
110
+ "name": "gog_calendar_search",
111
+ "description": "Full-text search for events with optional time filters"
112
+ },
113
+ {
114
+ "name": "gog_calendar_freebusy",
115
+ "description": "Query free/busy intervals for one or more calendars"
116
+ },
117
+ {
118
+ "name": "gog_calendar_colors",
119
+ "description": "Show the available calendar and event color palette"
120
+ },
121
+ {
122
+ "name": "gog_calendar_acl",
123
+ "description": "List the access control list (sharing rules) for a calendar"
124
+ },
125
+ {
126
+ "name": "gog_calendar_move",
127
+ "description": "Move an event to another calendar"
128
+ },
129
+ {
130
+ "name": "gog_calendar_out_of_office",
131
+ "description": "Create an Out of Office event that auto-declines invitations"
132
+ },
105
133
  {
106
134
  "name": "gog_meet_create",
107
135
  "description": "Create a Google Meet space"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-calendar",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-calendar",
5
5
  "description": "Extended Google Calendar + Meet MCP server via gogcli — auth + Calendar events + Meet space management",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -110,6 +110,141 @@ export function registerExtraCalendarTools(server: McpServer): void {
110
110
  return runOrDiagnose(args, {});
111
111
  });
112
112
 
113
+ // --- gog 0.19.0 calendar reads & CRUD ---
114
+
115
+ server.registerTool('gog_calendar_calendars', {
116
+ description: 'List the calendars in your calendar list (id, summary, access role, primary flag).',
117
+ annotations: { readOnlyHint: true },
118
+ inputSchema: {
119
+ max: z.number().optional().describe('Max results (default: 100)'),
120
+ page: z.string().optional().describe('Page token'),
121
+ all: z.boolean().optional().describe('Fetch all pages'),
122
+ account: accountParam,
123
+ },
124
+ }, async ({ max, page, all, account }) => {
125
+ const args = ['calendar', 'calendars'];
126
+ if (max !== undefined) args.push(`--max=${max}`);
127
+ if (page) args.push(`--page=${page}`);
128
+ if (all) args.push('--all');
129
+ return runOrDiagnose(args, { account });
130
+ });
131
+
132
+ server.registerTool('gog_calendar_search', {
133
+ description: 'Full-text search for events matching a query string, with optional time filters.',
134
+ annotations: { readOnlyHint: true },
135
+ inputSchema: {
136
+ query: z.string().describe('Search query'),
137
+ from: z.string().optional().describe('Start time (RFC3339, date, or relative: now, today, tomorrow, monday)'),
138
+ to: z.string().optional().describe('End time (RFC3339, date, or relative: now, today, tomorrow, monday)'),
139
+ today: z.boolean().optional().describe('Today only'),
140
+ tomorrow: z.boolean().optional().describe('Tomorrow only'),
141
+ week: z.boolean().optional().describe('This week (uses weekStart, default Mon)'),
142
+ days: z.number().optional().describe('Next N days'),
143
+ weekStart: z.string().optional().describe('Week start day for week (sun, mon, ...)'),
144
+ calendar: z.string().optional().describe('Calendar ID (default: primary)'),
145
+ max: z.number().optional().describe('Max results (default: 25)'),
146
+ account: accountParam,
147
+ },
148
+ }, async ({ query, from, to, today, tomorrow, week, days, weekStart, calendar, max, account }) => {
149
+ const args = ['calendar', 'search', query];
150
+ if (from) args.push(`--from=${from}`);
151
+ if (to) args.push(`--to=${to}`);
152
+ if (today) args.push('--today');
153
+ if (tomorrow) args.push('--tomorrow');
154
+ if (week) args.push('--week');
155
+ if (days !== undefined) args.push(`--days=${days}`);
156
+ if (weekStart) args.push(`--week-start=${weekStart}`);
157
+ if (calendar) args.push(`--calendar=${calendar}`);
158
+ if (max !== undefined) args.push(`--max=${max}`);
159
+ return runOrDiagnose(args, { account });
160
+ });
161
+
162
+ server.registerTool('gog_calendar_freebusy', {
163
+ description: 'Query free/busy intervals for one or more calendars over a time window.',
164
+ annotations: { readOnlyHint: true },
165
+ inputSchema: {
166
+ from: z.string().describe('Start time (RFC3339, required)'),
167
+ to: z.string().describe('End time (RFC3339, required)'),
168
+ calendarIds: z.string().optional().describe('Comma-separated calendar IDs, names, or indices'),
169
+ all: z.boolean().optional().describe('Query all calendars'),
170
+ account: accountParam,
171
+ },
172
+ }, async ({ from, to, calendarIds, all, account }) => {
173
+ const args = ['calendar', 'freebusy'];
174
+ if (calendarIds) args.push(calendarIds);
175
+ args.push(`--from=${from}`);
176
+ args.push(`--to=${to}`);
177
+ if (all) args.push('--all');
178
+ return runOrDiagnose(args, { account });
179
+ });
180
+
181
+ server.registerTool('gog_calendar_colors', {
182
+ description: 'Show the available calendar and event color palette (color IDs to hex values).',
183
+ annotations: { readOnlyHint: true },
184
+ inputSchema: {
185
+ account: accountParam,
186
+ },
187
+ }, async ({ account }) => {
188
+ return runOrDiagnose(['calendar', 'colors'], { account });
189
+ });
190
+
191
+ server.registerTool('gog_calendar_acl', {
192
+ description: 'List the access control list (sharing rules) for a calendar.',
193
+ annotations: { readOnlyHint: true },
194
+ inputSchema: {
195
+ calendarId: z.string().describe('Calendar ID'),
196
+ max: z.number().optional().describe('Max results (default: 100)'),
197
+ page: z.string().optional().describe('Page token'),
198
+ all: z.boolean().optional().describe('Fetch all pages'),
199
+ account: accountParam,
200
+ },
201
+ }, async ({ calendarId, max, page, all, account }) => {
202
+ const args = ['calendar', 'acl', calendarId];
203
+ if (max !== undefined) args.push(`--max=${max}`);
204
+ if (page) args.push(`--page=${page}`);
205
+ if (all) args.push('--all');
206
+ return runOrDiagnose(args, { account });
207
+ });
208
+
209
+ server.registerTool('gog_calendar_move', {
210
+ description: 'Move an event from one calendar to another; the destination calendar becomes the organizer.',
211
+ inputSchema: {
212
+ calendarId: z.string().describe('Source calendar ID'),
213
+ eventId: z.string().describe('Event ID'),
214
+ destinationCalendarId: z.string().describe('Destination calendar ID that becomes the event organizer'),
215
+ sendUpdates: z.enum(['all', 'externalOnly', 'none']).optional().describe('Notification mode (default: none)'),
216
+ account: accountParam,
217
+ },
218
+ }, async ({ calendarId, eventId, destinationCalendarId, sendUpdates, account }) => {
219
+ const args = ['calendar', 'move', calendarId, eventId, destinationCalendarId];
220
+ if (sendUpdates) args.push(`--send-updates=${sendUpdates}`);
221
+ return runOrDiagnose(args, { account });
222
+ });
223
+
224
+ server.registerTool('gog_calendar_out_of_office', {
225
+ description: 'Create an Out of Office event that auto-declines invitations during the block.',
226
+ inputSchema: {
227
+ from: z.string().describe('Start date or datetime (RFC3339 or YYYY-MM-DD)'),
228
+ to: z.string().describe('End date or datetime (RFC3339 or YYYY-MM-DD)'),
229
+ calendarId: z.string().optional().describe('Calendar ID (default: primary)'),
230
+ summary: z.string().optional().describe('Out of office title (default: "Out of office")'),
231
+ autoDecline: z.enum(['none', 'all', 'new']).optional().describe('Auto-decline mode (default: all)'),
232
+ declineMessage: z.string().optional().describe('Message for declined invitations'),
233
+ allDay: z.boolean().optional().describe('Create as an all-day event'),
234
+ account: accountParam,
235
+ },
236
+ }, async ({ from, to, calendarId, summary, autoDecline, declineMessage, allDay, account }) => {
237
+ const args = ['calendar', 'out-of-office'];
238
+ if (calendarId) args.push(calendarId);
239
+ args.push(`--from=${from}`);
240
+ args.push(`--to=${to}`);
241
+ if (summary) args.push(`--summary=${summary}`);
242
+ if (autoDecline) args.push(`--auto-decline=${autoDecline}`);
243
+ if (declineMessage) args.push(`--decline-message=${declineMessage}`);
244
+ if (allDay) args.push('--all-day');
245
+ return runOrDiagnose(args, { account });
246
+ });
247
+
113
248
  server.registerTool('gog_meet_participants', {
114
249
  description: 'List participants from the latest (or a specific) Meet call.',
115
250
  annotations: { readOnlyHint: true },
@@ -143,6 +143,183 @@ describe('gog_zoom_auth_doctor', () => {
143
143
  });
144
144
  });
145
145
 
146
+ // --- gog 0.19.0 calendar reads & CRUD ---
147
+
148
+ describe('gog_calendar_calendars', () => {
149
+ it('calls runOrDiagnose with no flags', async () => {
150
+ await handlers.get('gog_calendar_calendars')!({});
151
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'calendars'], { account: undefined });
152
+ });
153
+
154
+ it('passes pagination flags', async () => {
155
+ await handlers.get('gog_calendar_calendars')!({ max: 50, page: 'tok', all: true });
156
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
157
+ ['calendar', 'calendars', '--max=50', '--page=tok', '--all'],
158
+ { account: undefined },
159
+ );
160
+ });
161
+
162
+ it('omits --all when false', async () => {
163
+ await handlers.get('gog_calendar_calendars')!({ all: false });
164
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'calendars'], { account: undefined });
165
+ });
166
+ });
167
+
168
+ describe('gog_calendar_search', () => {
169
+ it('calls runOrDiagnose with just the query', async () => {
170
+ await handlers.get('gog_calendar_search')!({ query: 'standup' });
171
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'search', 'standup'], { account: undefined });
172
+ });
173
+
174
+ it('passes all filter flags when provided', async () => {
175
+ await handlers.get('gog_calendar_search')!({
176
+ query: 'standup',
177
+ from: 'today',
178
+ to: 'tomorrow',
179
+ today: true,
180
+ tomorrow: true,
181
+ week: true,
182
+ days: 7,
183
+ weekStart: 'sun',
184
+ calendar: 'primary',
185
+ max: 10,
186
+ });
187
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
188
+ [
189
+ 'calendar', 'search', 'standup',
190
+ '--from=today', '--to=tomorrow',
191
+ '--today', '--tomorrow', '--week',
192
+ '--days=7', '--week-start=sun',
193
+ '--calendar=primary', '--max=10',
194
+ ],
195
+ { account: undefined },
196
+ );
197
+ });
198
+
199
+ it('omits boolean flags when false', async () => {
200
+ await handlers.get('gog_calendar_search')!({
201
+ query: 'standup', today: false, tomorrow: false, week: false,
202
+ });
203
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'search', 'standup'], { account: undefined });
204
+ });
205
+ });
206
+
207
+ describe('gog_calendar_freebusy', () => {
208
+ it('calls runOrDiagnose with required from/to only', async () => {
209
+ await handlers.get('gog_calendar_freebusy')!({ from: 'A', to: 'B' });
210
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
211
+ ['calendar', 'freebusy', '--from=A', '--to=B'],
212
+ { account: undefined },
213
+ );
214
+ });
215
+
216
+ it('passes calendarIds and --all when provided', async () => {
217
+ await handlers.get('gog_calendar_freebusy')!({
218
+ from: 'A', to: 'B', calendarIds: 'primary,team@x.com', all: true,
219
+ });
220
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
221
+ ['calendar', 'freebusy', 'primary,team@x.com', '--from=A', '--to=B', '--all'],
222
+ { account: undefined },
223
+ );
224
+ });
225
+
226
+ it('omits --all when false', async () => {
227
+ await handlers.get('gog_calendar_freebusy')!({ from: 'A', to: 'B', all: false });
228
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
229
+ ['calendar', 'freebusy', '--from=A', '--to=B'],
230
+ { account: undefined },
231
+ );
232
+ });
233
+ });
234
+
235
+ describe('gog_calendar_colors', () => {
236
+ it('calls runOrDiagnose with the colors subcommand', async () => {
237
+ await handlers.get('gog_calendar_colors')!({});
238
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'colors'], { account: undefined });
239
+ });
240
+ });
241
+
242
+ describe('gog_calendar_acl', () => {
243
+ it('calls runOrDiagnose with calendarId only', async () => {
244
+ await handlers.get('gog_calendar_acl')!({ calendarId: 'primary' });
245
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'acl', 'primary'], { account: undefined });
246
+ });
247
+
248
+ it('passes pagination flags', async () => {
249
+ await handlers.get('gog_calendar_acl')!({ calendarId: 'primary', max: 25, page: 'tok', all: true });
250
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
251
+ ['calendar', 'acl', 'primary', '--max=25', '--page=tok', '--all'],
252
+ { account: undefined },
253
+ );
254
+ });
255
+
256
+ it('omits --all when false', async () => {
257
+ await handlers.get('gog_calendar_acl')!({ calendarId: 'primary', all: false });
258
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'acl', 'primary'], { account: undefined });
259
+ });
260
+ });
261
+
262
+ describe('gog_calendar_move', () => {
263
+ it('calls runOrDiagnose with the three positionals', async () => {
264
+ await handlers.get('gog_calendar_move')!({
265
+ calendarId: 'primary', eventId: 'ev1', destinationCalendarId: 'team@x.com',
266
+ });
267
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
268
+ ['calendar', 'move', 'primary', 'ev1', 'team@x.com'],
269
+ { account: undefined },
270
+ );
271
+ });
272
+
273
+ it('passes --send-updates when provided', async () => {
274
+ await handlers.get('gog_calendar_move')!({
275
+ calendarId: 'primary', eventId: 'ev1', destinationCalendarId: 'team@x.com',
276
+ sendUpdates: 'all',
277
+ });
278
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
279
+ ['calendar', 'move', 'primary', 'ev1', 'team@x.com', '--send-updates=all'],
280
+ { account: undefined },
281
+ );
282
+ });
283
+ });
284
+
285
+ describe('gog_calendar_out_of_office', () => {
286
+ it('calls runOrDiagnose with required from/to only', async () => {
287
+ await handlers.get('gog_calendar_out_of_office')!({ from: '2026-06-01', to: '2026-06-05' });
288
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
289
+ ['calendar', 'out-of-office', '--from=2026-06-01', '--to=2026-06-05'],
290
+ { account: undefined },
291
+ );
292
+ });
293
+
294
+ it('passes calendarId and all optional flags when provided', async () => {
295
+ await handlers.get('gog_calendar_out_of_office')!({
296
+ from: '2026-06-01', to: '2026-06-05',
297
+ calendarId: 'primary',
298
+ summary: 'Vacation',
299
+ autoDecline: 'new',
300
+ declineMessage: 'Away',
301
+ allDay: true,
302
+ });
303
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
304
+ [
305
+ 'calendar', 'out-of-office', 'primary',
306
+ '--from=2026-06-01', '--to=2026-06-05',
307
+ '--summary=Vacation', '--auto-decline=new',
308
+ '--decline-message=Away', '--all-day',
309
+ ],
310
+ { account: undefined },
311
+ );
312
+ });
313
+
314
+ it('omits --all-day when false', async () => {
315
+ await handlers.get('gog_calendar_out_of_office')!({ from: '2026-06-01', to: '2026-06-05', allDay: false });
316
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
317
+ ['calendar', 'out-of-office', '--from=2026-06-01', '--to=2026-06-05'],
318
+ { account: undefined },
319
+ );
320
+ });
321
+ });
322
+
146
323
  describe('gog_meet_participants', () => {
147
324
  it('calls runOrDiagnose with meetingCode', async () => {
148
325
  await handlers.get('gog_meet_participants')!({ meetingCode: 'abc-defg-hij' });