@bobfrankston/gcal 0.1.62 → 0.1.63

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/gcal.js CHANGED
@@ -240,6 +240,7 @@ Commands:
240
240
  show Show full details for an event (-json for JSON)
241
241
  open Open event in browser
242
242
  add Add event (explicit, AI, or interactive)
243
+ update | edit | set Change an event's time/title/location/busy/...
243
244
  del | delete Delete event(s) by ID
244
245
  remind Add reminder(s) to existing event
245
246
  resched Reschedule event
@@ -287,20 +288,55 @@ const USAGE = {
287
288
  Examples:
288
289
  gcal open abc12345
289
290
  `,
290
- add: `gcal add <title> <when> [duration] Explicit
291
+ add: `gcal add <title> <when> [duration] Explicit (timed)
292
+ gcal add <title> <date> [days] -allday Explicit (all-day)
291
293
  gcal add "<free text>" AI-parsed single arg
292
294
  gcal add -clip AI-parsed from clipboard
293
295
  gcal add Interactive (type description)
294
296
  Add a calendar event. Default duration 1h. Use -r <dur> to add reminder(s).
295
297
 
298
+ -allday Create an all-day event. The third arg is a day count
299
+ (default 1); the event spans that many days.
300
+ -free Mark the event as Free (does not block time / not busy).
301
+ -busy Mark the event as Busy (the default).
302
+ -open Open the event in the browser after creating it.
303
+
296
304
  Examples:
297
305
  gcal add "Dentist" "Friday 3pm" "1h"
298
306
  gcal add "Lunch" "1/14/2026 12:00" "1h"
299
307
  gcal add "Meeting" "tomorrow 10:00"
300
308
  gcal add "Appointment" "jan 15 2pm"
309
+ gcal add "Vacation" "jul 1" -allday (1 day, all-day)
310
+ gcal add "Conference" "jul 1" 3 -allday (3-day all-day event)
311
+ gcal add "Out of office" "jul 1" -allday -free (all-day, not busy)
301
312
  gcal add "Dentist appointment Friday 3pm for 1 hour"
302
313
  gcal add -clip
303
314
  gcal add "Dentist" "Friday 3pm" -r 30m
315
+ `,
316
+ update: `gcal update <id> [options]
317
+ gcal edit <id> ... (aliases: edit, set)
318
+ Change settings on an existing event. Only the options you give are
319
+ changed. Searches up to 30 days back; widen with -since.
320
+
321
+ -when <when> New start time/date (accepts +Nd / +Nh advances too).
322
+ -dur <dur> New duration ("1h30m"); for all-day events, a day count.
323
+ -title <text> New event title.
324
+ -loc <text> New location ("" to clear).
325
+ -note <text> New description ("" to clear).
326
+ -free Mark as Free (not busy).
327
+ -busy Mark as Busy.
328
+ -r <dur> Add a popup reminder (repeatable).
329
+ -rx <dur> Remove a reminder matching that duration (repeatable).
330
+ -nr Remove all reminders.
331
+ -open Open the event in the browser afterward.
332
+
333
+ Examples:
334
+ gcal update abc12345 -free
335
+ gcal update abc12345 -busy -title "Team sync"
336
+ gcal update abc12345 -when "wed 2pm" -dur 90m
337
+ gcal update abc12345 -r 30m -r 1h (add two reminders)
338
+ gcal update abc12345 -rx 30m (remove the 30m reminder)
339
+ gcal update abc12345 -nr (clear all reminders)
304
340
  `,
305
341
  del: `gcal del <id> [id2...] [-all] [-b]
306
342
  gcal delete <id> [id2...]
@@ -359,7 +395,9 @@ const USAGE = {
359
395
  const HELP_ALIASES = {
360
396
  'listc': 'calendars',
361
397
  'list-calendars': 'calendars',
362
- 'list-recurring': 'listr'
398
+ 'list-recurring': 'listr',
399
+ 'set': 'update',
400
+ 'edit': 'update'
363
401
  };
364
402
  function showUsage(cmd) {
365
403
  if (cmd)
@@ -387,6 +425,11 @@ function parseArgs(argv) {
387
425
  clip: false,
388
426
  all: false,
389
427
  json: false,
428
+ allDay: false,
429
+ transparency: '',
430
+ removeReminders: [],
431
+ clearReminders: false,
432
+ open: false,
390
433
  reminders: [],
391
434
  rrule: '',
392
435
  helpCmd: ''
@@ -432,6 +475,57 @@ function parseArgs(argv) {
432
475
  case '--json':
433
476
  result.json = true;
434
477
  break;
478
+ case '-allday':
479
+ case '-all-day':
480
+ case '--allday':
481
+ result.allDay = true;
482
+ break;
483
+ case '-free':
484
+ case '--free':
485
+ result.transparency = 'transparent';
486
+ break;
487
+ case '-busy':
488
+ case '--busy':
489
+ result.transparency = 'opaque';
490
+ break;
491
+ case '-title':
492
+ case '--title':
493
+ result.setTitle = argv[++i] || '';
494
+ break;
495
+ case '-loc':
496
+ case '-location':
497
+ case '--location':
498
+ result.setLoc = argv[++i] || '';
499
+ break;
500
+ case '-note':
501
+ case '-desc':
502
+ case '-description':
503
+ case '--note':
504
+ result.setNote = argv[++i] || '';
505
+ break;
506
+ case '-when':
507
+ case '--when':
508
+ result.setWhen = argv[++i] || '';
509
+ break;
510
+ case '-dur':
511
+ case '-duration':
512
+ case '--duration':
513
+ result.setDur = argv[++i] || '';
514
+ break;
515
+ case '-rx':
516
+ case '-rmr':
517
+ case '--remove-reminder':
518
+ result.removeReminders.push(parseDuration(argv[++i] || ''));
519
+ break;
520
+ case '-nr':
521
+ case '-noreminders':
522
+ case '--no-reminders':
523
+ result.clearReminders = true;
524
+ break;
525
+ case '-open':
526
+ case '--open':
527
+ result.open = true;
528
+ break;
435
529
  case '-r':
436
530
  case '-reminder':
437
531
  case '--reminder': {
@@ -520,6 +614,104 @@ function buildReminders(minutes) {
520
614
  overrides: minutes.map(m => ({ method: 'popup', minutes: m }))
521
615
  };
522
616
  }
617
+ /** Compute a start/end patch for moving and/or resizing an event.
618
+ * whenArg undefined => keep original start; durationArg undefined => keep original length.
619
+ * For all-day events durationArg is a day count; for timed events a duration string. */
620
+ function reschedulePatch(event, whenArg, durationArg) {
621
+ const origIsAllDay = !!event.start?.date;
622
+ if (origIsAllDay) {
623
+ const origStart = parseAllDay(event.start.date);
624
+ const origEnd = parseAllDay(event.end.date);
625
+ const origDurDays = Math.max(1, Math.round((origEnd.getTime() - origStart.getTime()) / 86400_000));
626
+ let newStart;
627
+ if (whenArg) {
628
+ const adv = whenArg.match(/^\+(\d+)([dw])$/i);
629
+ if (adv) {
630
+ const [, n, unit] = adv;
631
+ const amt = parseInt(n);
632
+ newStart = new Date(origStart);
633
+ newStart.setDate(newStart.getDate() + (unit.toLowerCase() === 'w' ? amt * 7 : amt));
634
+ }
635
+ else {
636
+ newStart = parseDateTime(whenArg);
637
+ newStart.setHours(0, 0, 0, 0);
638
+ }
639
+ }
640
+ else {
641
+ newStart = new Date(origStart);
642
+ }
643
+ const durDays = durationArg ? (parseInt(durationArg, 10) || origDurDays) : origDurDays;
644
+ const newEnd = new Date(newStart);
645
+ newEnd.setDate(newEnd.getDate() + durDays);
646
+ const patch = { start: { date: formatYMD(newStart) }, end: { date: formatYMD(newEnd) } };
647
+ return { patch, startDisplay: patch.start, endDisplay: patch.end };
648
+ }
649
+ const origStart = new Date(event.start.dateTime);
650
+ const origEnd = new Date(event.end.dateTime);
651
+ const origDurMs = origEnd.getTime() - origStart.getTime();
652
+ let newStart;
653
+ if (whenArg) {
654
+ const adv = whenArg.match(/^\+(\d+)([dwhm])$/i);
655
+ if (adv) {
656
+ const [, n, unit] = adv;
657
+ const amt = parseInt(n);
658
+ newStart = new Date(origStart);
659
+ switch (unit.toLowerCase()) {
660
+ case 'd':
661
+ newStart.setDate(newStart.getDate() + amt);
662
+ break;
663
+ case 'w':
664
+ newStart.setDate(newStart.getDate() + amt * 7);
665
+ break;
666
+ case 'h':
667
+ newStart.setHours(newStart.getHours() + amt);
668
+ break;
669
+ case 'm':
670
+ newStart.setMinutes(newStart.getMinutes() + amt);
671
+ break;
672
+ }
673
+ }
674
+ else {
675
+ newStart = parseDateTime(whenArg);
676
+ if (!hasTimeComponent(whenArg)) {
677
+ newStart.setHours(origStart.getHours(), origStart.getMinutes(), 0, 0);
678
+ }
679
+ }
680
+ }
681
+ else {
682
+ newStart = new Date(origStart);
683
+ }
684
+ const durMs = durationArg ? parseDuration(durationArg) * 60_000 : origDurMs;
685
+ const newEnd = new Date(newStart.getTime() + durMs);
686
+ const tz = event.start.timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone;
687
+ const patch = {
688
+ start: { dateTime: newStart.toISOString(), timeZone: tz },
689
+ end: { dateTime: newEnd.toISOString(), timeZone: tz }
690
+ };
691
+ return { patch, startDisplay: patch.start, endDisplay: patch.end };
692
+ }
693
+ /** Format a reminder override list for display, e.g. "10m, 1h" or "(none)". */
694
+ function formatReminders(overrides) {
695
+ if (!overrides || overrides.length === 0)
696
+ return '(none)';
697
+ return overrides
698
+ .map(r => r.minutes ?? 0)
699
+ .sort((a, b) => a - b)
700
+ .map(m => (m >= 60 && m % 60 === 0 ? `${m / 60}h` : `${m}m`))
701
+ .join(', ');
702
+ }
703
+ /** Open a URL in the platform's default browser. */
704
+ function openUrl(url) {
705
+ if (process.platform === 'win32') {
706
+ execSync(`start "" "${url}"`, { stdio: 'ignore', shell: 'cmd.exe' });
707
+ }
708
+ else if (process.platform === 'darwin') {
709
+ execSync(`open "${url}"`, { stdio: 'ignore' });
710
+ }
711
+ else {
712
+ execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
713
+ }
714
+ }
523
715
  /** Match events by ID prefix and dedup recurring instances to the earliest.
524
716
  * `events` must be ordered by startTime (as returned by listEvents). */
525
717
  function findByPrefix(events, prefix, includeBirthdays) {
@@ -771,32 +963,50 @@ async function main() {
771
963
  case 'add': {
772
964
  // Explicit mode: gcal add "title" "when" [duration]
773
965
  if (parsed.args.length >= 2 && !parsed.clip) {
774
- const [title, when, duration = '1h'] = parsed.args;
966
+ const [title, when, third] = parsed.args;
775
967
  const startTime = parseDateTime(when);
776
- const durationMins = parseDuration(duration);
777
- const endTime = new Date(startTime.getTime() + durationMins * 60 * 1000);
778
968
  const event = {
779
969
  summary: title,
780
- start: {
781
- dateTime: startTime.toISOString(),
782
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
783
- },
784
- end: {
785
- dateTime: endTime.toISOString(),
786
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
787
- },
970
+ start: {},
971
+ end: {},
788
972
  reminders: buildReminders(parsed.reminders)
789
973
  };
974
+ if (parsed.allDay) {
975
+ // All-day: [duration] is a day count (default 1). Google's
976
+ // end.date is exclusive, so a 1-day event ends the next day.
977
+ const days = third ? (parseInt(third, 10) || 1) : 1;
978
+ const startD = new Date(startTime);
979
+ startD.setHours(0, 0, 0, 0);
980
+ const endD = new Date(startD);
981
+ endD.setDate(endD.getDate() + days);
982
+ event.start = { date: formatYMD(startD) };
983
+ event.end = { date: formatYMD(endD) };
984
+ }
985
+ else {
986
+ const durationMins = parseDuration(third || '1h');
987
+ const endTime = new Date(startTime.getTime() + durationMins * 60 * 1000);
988
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
989
+ event.start = { dateTime: startTime.toISOString(), timeZone: tz };
990
+ event.end = { dateTime: endTime.toISOString(), timeZone: tz };
991
+ }
992
+ if (parsed.transparency)
993
+ event.transparency = parsed.transparency;
790
994
  if (parsed.rrule)
791
995
  event.recurrence = [`RRULE:${parsed.rrule}`];
792
996
  const token = await getAccessToken(user, true);
793
- await checkProximity(token, parsed.calendar, startTime, endTime);
997
+ if (!parsed.allDay) {
998
+ await checkProximity(token, parsed.calendar, new Date(event.start.dateTime), new Date(event.end.dateTime));
999
+ }
794
1000
  const created = await createEvent(token, event, parsed.calendar);
795
1001
  console.log(`\nEvent created: ${created.summary}`);
796
1002
  console.log(` When: ${formatDateTime(created.start)} - ${formatDateTime(created.end)}`);
1003
+ if (created.transparency === 'transparent')
1004
+ console.log(` Free (not busy)`);
797
1005
  if (created.htmlLink) {
798
1006
  console.log(` Link: ${created.htmlLink}`);
799
1007
  }
1008
+ if (parsed.open && created.htmlLink)
1009
+ openUrl(created.htmlLink);
800
1010
  break;
801
1011
  }
802
1012
  // AI mode: freeform text from clipboard, keyboard, or single arg
@@ -854,6 +1064,8 @@ async function main() {
854
1064
  description: extracted.description,
855
1065
  reminders: buildReminders(parsed.reminders)
856
1066
  };
1067
+ if (parsed.transparency)
1068
+ event.transparency = parsed.transparency;
857
1069
  events.push(event);
858
1070
  console.log(`\n Event: ${extracted.summary}`);
859
1071
  console.log(` When: ${formatDateTime(event.start)} - ${formatDateTime(event.end)} (${extracted.duration || '1h'})${tz !== localTz ? ` [${tz}]` : ''}`);
@@ -895,6 +1107,8 @@ async function main() {
895
1107
  if (created.htmlLink) {
896
1108
  console.log(` Link: ${created.htmlLink}`);
897
1109
  }
1110
+ if (parsed.open && created.htmlLink)
1111
+ openUrl(created.htmlLink);
898
1112
  }
899
1113
  break;
900
1114
  }
@@ -1012,6 +1226,104 @@ async function main() {
1012
1226
  }
1013
1227
  break;
1014
1228
  }
1229
+ case 'update':
1230
+ case 'edit':
1231
+ case 'set': {
1232
+ if (parsed.args.length < 1) {
1233
+ console.error('Usage: gcal update <id> [-when <when>] [-dur <dur>] [-title <text>]');
1234
+ console.error(' [-loc <text>] [-note <text>] [-free|-busy]');
1235
+ console.error(' [-r <dur>] [-rx <dur>] [-nr] [-open]');
1236
+ console.error('Use "gcal list" to see event IDs');
1237
+ process.exit(1);
1238
+ }
1239
+ const patch = {};
1240
+ if (parsed.setTitle !== undefined)
1241
+ patch.summary = parsed.setTitle;
1242
+ if (parsed.setLoc !== undefined)
1243
+ patch.location = parsed.setLoc;
1244
+ if (parsed.setNote !== undefined)
1245
+ patch.description = parsed.setNote;
1246
+ if (parsed.transparency)
1247
+ patch.transparency = parsed.transparency;
1248
+ const changingTime = parsed.setWhen !== undefined || parsed.setDur !== undefined;
1249
+ const changingReminders = parsed.clearReminders
1250
+ || parsed.reminders.length > 0 || parsed.removeReminders.length > 0;
1251
+ if (Object.keys(patch).length === 0 && !changingTime && !changingReminders) {
1252
+ console.error('Nothing to change. Specify -when, -dur, -title, -loc, -note,');
1253
+ console.error(' -free, -busy, -r (add reminder), -rx (remove reminder), or -nr.');
1254
+ process.exit(1);
1255
+ }
1256
+ const idPrefix = parsed.args[0];
1257
+ const lookback = parsed.since
1258
+ ? parsed.since.toISOString()
1259
+ : new Date(Date.now() - 30 * 86400_000).toISOString();
1260
+ const timeMax = parsed.till ? parsed.till.toISOString() : undefined;
1261
+ const token = await getAccessToken(user, true);
1262
+ const events = await listEvents(token, parsed.calendar, 250, lookback, timeMax);
1263
+ const unique = findByPrefix(events, idPrefix, parsed.birthdays);
1264
+ if (unique.length === 0) {
1265
+ console.error(`${idPrefix}: not found (searched from ${lookback.slice(0, 10)})`);
1266
+ process.exit(1);
1267
+ }
1268
+ if (unique.length > 1) {
1269
+ console.error(`${idPrefix}: ambiguous (${unique.length} matches)`);
1270
+ for (const e of unique) {
1271
+ console.error(` ${e.id?.slice(0, 8)} - ${e.summary}`);
1272
+ }
1273
+ process.exit(1);
1274
+ }
1275
+ const event = unique[0];
1276
+ // Time / duration change (reuses the resched logic)
1277
+ let timeFrom = '';
1278
+ let timeTo = '';
1279
+ if (changingTime) {
1280
+ const r = reschedulePatch(event, parsed.setWhen, parsed.setDur);
1281
+ patch.start = r.patch.start;
1282
+ patch.end = r.patch.end;
1283
+ timeFrom = `${formatDateTime(event.start)} - ${formatDateTime(event.end)}`;
1284
+ timeTo = `${formatDateTime(r.startDisplay)} - ${formatDateTime(r.endDisplay)}`;
1285
+ }
1286
+ // Reminders: -nr clears all; otherwise merge existing with -r adds and -rx removes
1287
+ if (parsed.clearReminders) {
1288
+ patch.reminders = { useDefault: false, overrides: [] };
1289
+ }
1290
+ else if (changingReminders) {
1291
+ const mins = new Set((event.reminders?.overrides ?? []).map(r => r.minutes ?? 0));
1292
+ for (const m of parsed.removeReminders)
1293
+ mins.delete(m);
1294
+ for (const m of parsed.reminders)
1295
+ mins.add(m);
1296
+ patch.reminders = {
1297
+ useDefault: false,
1298
+ overrides: [...mins].sort((a, b) => a - b).map(m => ({ method: 'popup', minutes: m }))
1299
+ };
1300
+ }
1301
+ // Proximity check when the timed slot moved
1302
+ if (patch.start?.dateTime && patch.end?.dateTime) {
1303
+ await checkProximity(token, parsed.calendar, new Date(patch.start.dateTime), new Date(patch.end.dateTime), (event.id || '').split('_')[0]);
1304
+ }
1305
+ const updated = await patchEvent(token, event.id, patch, parsed.calendar);
1306
+ console.log(`Updated: ${updated.summary}`);
1307
+ if (patch.summary !== undefined)
1308
+ console.log(` Title: ${updated.summary}`);
1309
+ if (changingTime) {
1310
+ console.log(` When: ${timeFrom}`);
1311
+ console.log(` -> ${timeTo}`);
1312
+ }
1313
+ if (patch.location !== undefined)
1314
+ console.log(` Where: ${updated.location || '(cleared)'}`);
1315
+ if (patch.description !== undefined)
1316
+ console.log(` Note: ${updated.description || '(cleared)'}`);
1317
+ if (patch.transparency !== undefined) {
1318
+ console.log(` Shows as: ${updated.transparency === 'transparent' ? 'free' : 'busy'}`);
1319
+ }
1320
+ if (patch.reminders !== undefined) {
1321
+ console.log(` Reminders: ${formatReminders(updated.reminders?.overrides)}`);
1322
+ }
1323
+ if (parsed.open && updated.htmlLink)
1324
+ openUrl(updated.htmlLink);
1325
+ break;
1326
+ }
1015
1327
  case 'resched':
1016
1328
  case 'reschedule':
1017
1329
  case 'snooze': {
@@ -1055,84 +1367,17 @@ async function main() {
1055
1367
  process.exit(1);
1056
1368
  }
1057
1369
  }
1058
- const origIsAllDay = !!event.start?.date;
1059
- let patch;
1060
- let newStartDisplay;
1061
- let newEndDisplay;
1062
- if (origIsAllDay) {
1063
- const origStart = parseAllDay(event.start.date);
1064
- const origEnd = parseAllDay(event.end.date);
1065
- const origDurDays = Math.max(1, Math.round((origEnd.getTime() - origStart.getTime()) / 86400_000));
1066
- let newStart;
1067
- const adv = whenArg.match(/^\+(\d+)([dw])$/i);
1068
- if (adv) {
1069
- const [, n, unit] = adv;
1070
- const amt = parseInt(n);
1071
- newStart = new Date(origStart);
1072
- newStart.setDate(newStart.getDate() + (unit.toLowerCase() === 'w' ? amt * 7 : amt));
1073
- }
1074
- else {
1075
- newStart = parseDateTime(whenArg);
1076
- newStart.setHours(0, 0, 0, 0);
1077
- }
1078
- const newEnd = new Date(newStart);
1079
- newEnd.setDate(newEnd.getDate() + origDurDays);
1080
- patch = {
1081
- start: { date: formatYMD(newStart) },
1082
- end: { date: formatYMD(newEnd) }
1083
- };
1084
- newStartDisplay = { date: formatYMD(newStart) };
1085
- newEndDisplay = { date: formatYMD(newEnd) };
1086
- }
1087
- else {
1088
- const origStart = new Date(event.start.dateTime);
1089
- const origEnd = new Date(event.end.dateTime);
1090
- const origDurMs = origEnd.getTime() - origStart.getTime();
1091
- let newStart;
1092
- const adv = whenArg.match(/^\+(\d+)([dwhm])$/i);
1093
- if (adv) {
1094
- const [, n, unit] = adv;
1095
- const amt = parseInt(n);
1096
- newStart = new Date(origStart);
1097
- switch (unit.toLowerCase()) {
1098
- case 'd':
1099
- newStart.setDate(newStart.getDate() + amt);
1100
- break;
1101
- case 'w':
1102
- newStart.setDate(newStart.getDate() + amt * 7);
1103
- break;
1104
- case 'h':
1105
- newStart.setHours(newStart.getHours() + amt);
1106
- break;
1107
- case 'm':
1108
- newStart.setMinutes(newStart.getMinutes() + amt);
1109
- break;
1110
- }
1111
- }
1112
- else {
1113
- newStart = parseDateTime(whenArg);
1114
- if (!hasTimeComponent(whenArg)) {
1115
- newStart.setHours(origStart.getHours(), origStart.getMinutes(), 0, 0);
1116
- }
1117
- }
1118
- const durMs = durationArg ? parseDuration(durationArg) * 60_000 : origDurMs;
1119
- const newEnd = new Date(newStart.getTime() + durMs);
1120
- const tz = event.start.timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone;
1121
- patch = {
1122
- start: { dateTime: newStart.toISOString(), timeZone: tz },
1123
- end: { dateTime: newEnd.toISOString(), timeZone: tz }
1124
- };
1125
- newStartDisplay = patch.start;
1126
- newEndDisplay = patch.end;
1127
- }
1370
+ const { patch, startDisplay, endDisplay } = reschedulePatch(event, whenArg, durationArg);
1128
1371
  // Proximity check for timed events (skip all-day)
1129
- if (!origIsAllDay && patch.start?.dateTime && patch.end?.dateTime) {
1372
+ if (patch.start?.dateTime && patch.end?.dateTime) {
1130
1373
  await checkProximity(token, parsed.calendar, new Date(patch.start.dateTime), new Date(patch.end.dateTime), (event.id || '').split('_')[0]);
1131
1374
  }
1132
1375
  const updated = await patchEvent(token, event.id, patch, parsed.calendar);
1133
1376
  console.log(`Rescheduled: ${updated.summary}`);
1134
1377
  console.log(` From: ${formatDateTime(event.start)} - ${formatDateTime(event.end)}`);
1135
- console.log(` To: ${formatDateTime(newStartDisplay)} - ${formatDateTime(newEndDisplay)}`);
1378
+ console.log(` To: ${formatDateTime(startDisplay)} - ${formatDateTime(endDisplay)}`);
1379
+ if (parsed.open && updated.htmlLink)
1380
+ openUrl(updated.htmlLink);
1136
1381
  break;
1137
1382
  }
1138
1383
  case 'show': {
@@ -1217,6 +1462,7 @@ async function main() {
1217
1462
  if (event.htmlLink)
1218
1463
  console.log(` Link: ${event.htmlLink}`);
1219
1464
  console.log(` Status: ${event.status || 'confirmed'}`);
1465
+ console.log(` Shows as: ${event.transparency === 'transparent' ? 'free' : 'busy'}`);
1220
1466
  if (event.created)
1221
1467
  console.log(` Created: ${formatDateTime({ dateTime: event.created })}`);
1222
1468
  if (event.updated)
@@ -1256,15 +1502,7 @@ async function main() {
1256
1502
  }
1257
1503
  console.log(`Opening: ${event.summary || '(no title)'}`);
1258
1504
  console.log(` ${event.htmlLink}`);
1259
- if (process.platform === 'win32') {
1260
- execSync(`start "" "${event.htmlLink}"`, { stdio: 'ignore', shell: 'cmd.exe' });
1261
- }
1262
- else if (process.platform === 'darwin') {
1263
- execSync(`open "${event.htmlLink}"`, { stdio: 'ignore' });
1264
- }
1265
- else {
1266
- execSync(`xdg-open "${event.htmlLink}"`, { stdio: 'ignore' });
1267
- }
1505
+ openUrl(event.htmlLink);
1268
1506
  break;
1269
1507
  }
1270
1508
  default: