@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 +333 -95
- package/gcal.js.map +1 -1
- package/gcal.ts +344 -88
- package/glib/gutils.d.ts.map +1 -1
- package/glib/gutils.js +13 -0
- package/glib/gutils.js.map +1 -1
- package/glib/gutils.ts +13 -0
- package/gtask.js +12 -3
- package/gtask.js.map +1 -1
- package/gtask.ts +12 -3
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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:
|