@bobfrankston/gcal 0.1.26 → 0.1.30

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.d.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  * Manage Google Calendar events with ICS import support
5
5
  *
6
6
  * Can be associated with .ics files for direct import:
7
- * gcal assoc
7
+ * assoc .ics=icsfile
8
+ * ftype icsfile=gcal "%1"
8
9
  */
9
10
  export {};
10
11
  //# sourceMappingURL=gcal.d.ts.map
package/gcal.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gcal.d.ts","sourceRoot":"","sources":["gcal.ts"],"names":[],"mappings":";AACA;;;;;;GAMG"}
1
+ {"version":3,"file":"gcal.d.ts","sourceRoot":"","sources":["gcal.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG"}
package/gcal.js CHANGED
@@ -4,22 +4,30 @@
4
4
  * Manage Google Calendar events with ICS import support
5
5
  *
6
6
  * Can be associated with .ics files for direct import:
7
- * gcal assoc
7
+ * assoc .ics=icsfile
8
+ * ftype icsfile=gcal "%1"
8
9
  */
9
10
  import fs from 'fs';
10
11
  import path from 'path';
11
- import { execSync } from 'child_process';
12
12
  import { authenticateOAuth } from '@bobfrankston/oauthsupport';
13
13
  import { CREDENTIALS_FILE, loadConfig, saveConfig, getUserPaths, ensureUserDir, formatDateTime, formatDuration, parseDuration, parseDateTime, ts, normalizeUser } from './glib/gutils.js';
14
+ import pkg from './package.json' with { type: 'json' };
15
+ const VERSION = pkg.version;
14
16
  const CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3';
15
17
  const CALENDAR_SCOPE_READ = 'https://www.googleapis.com/auth/calendar.readonly';
16
18
  const CALENDAR_SCOPE_WRITE = 'https://www.googleapis.com/auth/calendar';
17
19
  let abortController = null;
18
20
  function setupAbortHandler() {
19
21
  abortController = new AbortController();
22
+ let ctrlCCount = 0;
20
23
  process.on('SIGINT', () => {
24
+ ctrlCCount++;
21
25
  abortController?.abort();
22
- console.log('\n\nCtrl+C pressed - aborting...');
26
+ if (ctrlCCount >= 2) {
27
+ console.log('\n\nForce exit.');
28
+ process.exit(1);
29
+ }
30
+ console.log('\n\nCtrl+C pressed - aborting... (press again to force exit)');
23
31
  });
24
32
  }
25
33
  async function getAccessToken(user, writeAccess = false, forceRefresh = false) {
@@ -43,7 +51,7 @@ async function getAccessToken(user, writeAccess = false, forceRefresh = false) {
43
51
  scope,
44
52
  tokenDirectory: paths.userDir,
45
53
  tokenFileName,
46
- credentialsKey: 'web',
54
+ credentialsKey: 'installed',
47
55
  signal: abortController?.signal
48
56
  });
49
57
  if (!token) {
@@ -68,15 +76,13 @@ async function listCalendars(accessToken) {
68
76
  const data = await res.json();
69
77
  return data.items || [];
70
78
  }
71
- async function listEvents(accessToken, calendarId = 'primary', maxResults = 10, timeMin, timeMax) {
79
+ async function listEvents(accessToken, calendarId = 'primary', maxResults = 10, timeMin) {
72
80
  const params = new URLSearchParams({
73
81
  maxResults: maxResults.toString(),
74
82
  singleEvents: 'true',
75
83
  orderBy: 'startTime',
76
84
  timeMin: timeMin || new Date().toISOString()
77
85
  });
78
- if (timeMax)
79
- params.set('timeMax', timeMax);
80
86
  const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events?${params}`;
81
87
  const res = await apiFetch(url, accessToken);
82
88
  if (!res.ok) {
@@ -105,18 +111,6 @@ async function deleteEvent(accessToken, eventId, calendarId = 'primary') {
105
111
  throw new Error(`Failed to delete event: ${res.status} ${errText}`);
106
112
  }
107
113
  }
108
- async function updateEvent(accessToken, eventId, body, calendarId = 'primary') {
109
- const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
110
- const res = await apiFetch(url, accessToken, {
111
- method: 'PATCH',
112
- body: JSON.stringify(body)
113
- });
114
- if (!res.ok) {
115
- const errText = await res.text();
116
- throw new Error(`Failed to update event: ${res.status} ${errText}`);
117
- }
118
- return await res.json();
119
- }
120
114
  async function importIcsFile(filePath, accessToken, calendarId = 'primary') {
121
115
  const ICAL = await import('ical.js');
122
116
  const result = { imported: 0, errors: [] };
@@ -186,56 +180,9 @@ async function importIcsFile(filePath, accessToken, calendarId = 'primary') {
186
180
  }
187
181
  return result;
188
182
  }
189
- function setupFileAssociation() {
190
- if (process.platform !== 'win32') {
191
- console.error('File association is only supported on Windows.');
192
- process.exit(1);
193
- }
194
- const classKey = 'HKCU\\Software\\Classes';
195
- const command = `cmd.exe /c gcal "%1"`;
196
- try {
197
- execSync(`reg add "${classKey}\\.ics" /ve /d "gcalFile" /f`, { stdio: 'pipe' });
198
- execSync(`reg add "${classKey}\\gcalFile\\shell\\open\\command" /ve /d "${command}" /f`, { stdio: 'pipe' });
199
- console.log('.ics file association set — double-click any .ics to import via gcal');
200
- }
201
- catch (e) {
202
- console.error(`Failed to set file association: ${e.message}`);
203
- process.exit(1);
204
- }
205
- }
206
- /** Format reminders compactly: "30m", "1h:email", "30m,1h:email", "def", "" */
207
- function formatReminders(reminders) {
208
- if (!reminders)
209
- return '';
210
- if (reminders.useDefault)
211
- return 'def';
212
- if (!reminders.overrides || reminders.overrides.length === 0)
213
- return '';
214
- return reminders.overrides.map(r => {
215
- const mins = r.minutes || 0;
216
- const label = mins >= 1440 ? `${mins / 1440}d` : mins >= 60 ? `${mins / 60}h` : `${mins}m`;
217
- return `${label}${r.method === 'email' ? ':email' : ''}`;
218
- }).join(',');
219
- }
220
- /** Clean up URLs — replace https URLs with [sitename] labels */
221
- function cleanUrls(text) {
222
- if (!text)
223
- return text;
224
- return text.replace(/https?:\/\/([^\/\s]+)\S*/gi, (_match, host) => {
225
- // Extract meaningful site name from hostname
226
- const parts = host.split('.');
227
- // Drop common prefixes (www, us02web, events, etc.) and TLD suffixes
228
- // Keep the main domain name: "us02web.zoom.us" → "Zoom", "events.vtools.ieee.org" → "ieee"
229
- if (parts.length >= 2) {
230
- const domain = parts.length > 2 ? parts[parts.length - 2] : parts[0];
231
- return `[${domain.charAt(0).toUpperCase() + domain.slice(1)}]`;
232
- }
233
- return '[link]';
234
- });
235
- }
236
183
  function showUsage() {
237
184
  console.log(`
238
- gcal - Google Calendar CLI
185
+ gcal v${VERSION} - Google Calendar CLI
239
186
 
240
187
  Usage:
241
188
  gcal <file.ics> Import ICS file (file association)
@@ -245,10 +192,8 @@ Commands:
245
192
  list [n] List upcoming n events (default: 10)
246
193
  add <title> <when> [duration] Add event
247
194
  del|delete <id> [id2...] Delete event(s) by ID (prefix match)
248
- update <id> [flags] Update event by ID (prefix match)
249
195
  import <file.ics> Import events from ICS file
250
196
  calendars List available calendars
251
- assoc Associate .ics files with gcal (Windows)
252
197
  help Show this help
253
198
 
254
199
  Options:
@@ -256,89 +201,23 @@ Options:
256
201
  -defaultUser <email> Set default user for future use
257
202
  -c, -calendar <id> Calendar ID (default: primary)
258
203
  -n <count> Number of events to list
259
- -limit <span> Time horizon for list: #d, #w, #m, #y (default: 3m)
260
204
  -v, -verbose Show event IDs and links
261
205
  -b, -birthdays Include birthday events (hidden by default)
262
- -title <text> New title (update command)
263
- -loc <text> New location (update command)
264
- -start <when> New start time (update command)
265
- -dur <duration> New duration (update command)
266
- -r <spec> Reminders: #m, #h, #d with optional :email/:popup
267
- -r 0 No reminders
268
- -r 30m 30 min popup (default kind)
269
- -r 1h:email 1 hour email
270
- -r 15m,1h Multiple: comma-separated
271
206
 
272
207
  Examples:
273
208
  gcal meeting.ics Import ICS file
274
209
  gcal list List next 10 events
275
- gcal add "Dentist" "fri 3pm" "1h"
210
+ gcal add "Dentist" "Friday 3pm" "1h"
276
211
  gcal add "Lunch" "1/14/2026 12:00" "1h"
277
212
  gcal add "Meeting" "tomorrow 10:00"
278
213
  gcal add "Appointment" "jan 15 2pm"
279
- gcal add "Lunch" "tomorrow noon" "1h"
280
- gcal add "Call" "tomorrow 3pm" "30m" -r 15m,1h:email
281
- gcal update abc1 -title "New Title" -loc "Room 5"
282
- gcal update abc1 -start "friday 3pm" -dur 2h
283
- gcal update abc1 -r 15m,1h:email
284
214
  gcal -defaultUser bob@gmail.com Set default user
285
215
 
286
216
  File Association (Windows):
287
- gcal assoc Set up .ics file association
217
+ assoc .ics=icsfile
218
+ ftype icsfile=gcal "%1"
288
219
  `);
289
220
  }
290
- /** Parse a reminder spec like "30m", "1h", "2d", "30m:email", "1h:popup" */
291
- function parseReminder(spec) {
292
- // Split on colon for method: "30m:email" or just "30m"
293
- const [timePart, methodPart] = spec.split(':');
294
- const method = methodPart === 'email' ? 'email' : 'popup';
295
- const match = timePart.match(/^(\d+)\s*([mhd]?)$/i);
296
- if (!match) {
297
- console.error(`Invalid reminder: "${spec}" — use #m, #h, or #d (e.g. 30m, 1h, 2d)`);
298
- process.exit(1);
299
- }
300
- const num = parseInt(match[1]);
301
- const unit = (match[2] || 'm').toLowerCase();
302
- let minutes;
303
- switch (unit) {
304
- case 'h':
305
- minutes = num * 60;
306
- break;
307
- case 'd':
308
- minutes = num * 60 * 24;
309
- break;
310
- default:
311
- minutes = num;
312
- break;
313
- }
314
- return { method, minutes };
315
- }
316
- /** Parse a time limit spec like "3m", "1y", "2w", "90d" into a future Date */
317
- function parseTimeLimit(spec) {
318
- const match = spec.match(/^(\d+)\s*([dwmy]?)$/i);
319
- if (!match) {
320
- console.error(`Invalid time limit: "${spec}" — use #d, #w, #m, or #y (e.g. 3m, 90d, 1y)`);
321
- process.exit(1);
322
- }
323
- const num = parseInt(match[1]);
324
- const unit = (match[2] || 'm').toLowerCase();
325
- const now = new Date();
326
- switch (unit) {
327
- case 'd':
328
- now.setDate(now.getDate() + num);
329
- break;
330
- case 'w':
331
- now.setDate(now.getDate() + num * 7);
332
- break;
333
- case 'y':
334
- now.setFullYear(now.getFullYear() + num);
335
- break;
336
- default:
337
- now.setMonth(now.getMonth() + num);
338
- break; // 'm' = months
339
- }
340
- return now;
341
- }
342
221
  function parseArgs(argv) {
343
222
  const result = {
344
223
  command: '',
@@ -350,14 +229,7 @@ function parseArgs(argv) {
350
229
  help: false,
351
230
  verbose: false,
352
231
  icsFile: '',
353
- birthdays: false,
354
- reminders: [],
355
- noReminders: false,
356
- timeLimit: '3m',
357
- title: '',
358
- location: '',
359
- startTime: '',
360
- duration: ''
232
+ birthdays: false
361
233
  };
362
234
  const unknown = [];
363
235
  let i = 0;
@@ -391,50 +263,17 @@ function parseArgs(argv) {
391
263
  case '--birthdays':
392
264
  result.birthdays = true;
393
265
  break;
394
- case '-limit':
395
- case '--limit':
396
- result.timeLimit = argv[++i] || '3m';
397
- break;
398
- case '-title':
399
- case '--title':
400
- result.title = argv[++i] || '';
401
- break;
402
- case '-loc':
403
- case '-location':
404
- case '--location':
405
- result.location = argv[++i] || '';
406
- break;
407
- case '-start':
408
- case '--start':
409
- result.startTime = argv[++i] || '';
410
- break;
411
- case '-dur':
412
- case '-duration':
413
- case '--duration':
414
- result.duration = argv[++i] || '';
415
- break;
416
- case '-r':
417
- case '-reminder':
418
- case '--reminder': {
419
- const rval = argv[++i] || '';
420
- if (rval === '0' || rval === 'none') {
421
- result.noReminders = true;
422
- }
423
- else {
424
- for (const part of rval.split(',')) {
425
- const reminder = parseReminder(part.trim());
426
- if (reminder)
427
- result.reminders.push(reminder);
428
- }
429
- }
430
- break;
431
- }
432
266
  case '-h':
433
267
  case '-help':
434
268
  case '--help':
435
269
  case 'help':
436
270
  result.help = true;
437
271
  break;
272
+ case '-V':
273
+ case '-version':
274
+ case '--version':
275
+ console.log(`gcal v${VERSION}`);
276
+ process.exit(0);
438
277
  default:
439
278
  if (arg.startsWith('-')) {
440
279
  unknown.push(arg);
@@ -456,8 +295,7 @@ function parseArgs(argv) {
456
295
  i++;
457
296
  }
458
297
  if (unknown.length > 0) {
459
- console.error(`Unknown options: ${unknown.join(', ')}\n`);
460
- showUsage();
298
+ console.error(`Unknown options: ${unknown.join(', ')}`);
461
299
  process.exit(1);
462
300
  }
463
301
  return result;
@@ -501,11 +339,6 @@ async function main() {
501
339
  showUsage();
502
340
  process.exit(1);
503
341
  }
504
- // Commands that don't need a user
505
- if (parsed.command === 'assoc') {
506
- setupFileAssociation();
507
- process.exit(0);
508
- }
509
342
  // Resolve user
510
343
  const user = resolveUser(parsed.user, false);
511
344
  if (!user) {
@@ -538,9 +371,8 @@ async function main() {
538
371
  }
539
372
  case 'list': {
540
373
  const count = parsed.args[0] ? parseInt(parsed.args[0]) : parsed.count;
541
- const timeMax = parseTimeLimit(parsed.timeLimit).toISOString();
542
374
  const token = await getAccessToken(user, false);
543
- let events = await listEvents(token, parsed.calendar, count, undefined, timeMax);
375
+ let events = await listEvents(token, parsed.calendar, count);
544
376
  const birthdayCount = events.filter(e => e.eventType === 'birthday').length;
545
377
  if (!parsed.birthdays) {
546
378
  events = events.filter(e => e.eventType !== 'birthday');
@@ -556,29 +388,27 @@ async function main() {
556
388
  const shortId = (event.id || '').slice(0, 8);
557
389
  const start = event.start ? formatDateTime(event.start) : '?';
558
390
  const duration = (event.start && event.end) ? formatDuration(event.start, event.end) : '';
559
- const summary = cleanUrls(event.summary || '(no title)') + (event.eventType === 'birthday' ? ' [from contact]' : '');
560
- const loc = cleanUrls(event.location || '');
561
- const rem = formatReminders(event.reminders);
391
+ const summary = (event.summary || '(no title)') + (event.eventType === 'birthday' ? ' [from contact]' : '');
392
+ const loc = event.location || '';
562
393
  if (parsed.verbose) {
563
- rows.push([shortId, start, duration, summary, rem, loc, event.htmlLink || '']);
394
+ rows.push([shortId, start, duration, summary, loc, event.htmlLink || '']);
564
395
  }
565
396
  else {
566
- rows.push([shortId, start, duration, summary, rem, loc]);
397
+ rows.push([shortId, start, duration, summary, loc]);
567
398
  }
568
399
  }
569
400
  // Calculate column widths
570
401
  const headers = parsed.verbose
571
- ? ['ID', 'When', 'Dur', 'Event', 'Rem', 'Location', 'Link']
572
- : ['ID', 'When', 'Dur', 'Event', 'Rem', 'Location'];
402
+ ? ['ID', 'When', 'Dur', 'Event', 'Location', 'Link']
403
+ : ['ID', 'When', 'Dur', 'Event', 'Location'];
573
404
  const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map(r => (r[i] || '').length)));
574
405
  // Print header
575
- const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(' ');
406
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(' ');
576
407
  console.log(headerLine);
577
- console.log(colWidths.map(w => '-'.repeat(w)).join(' '));
578
- // Print rows — last column not padded to avoid trailing whitespace
408
+ console.log(colWidths.map(w => '-'.repeat(w)).join(' '));
409
+ // Print rows
579
410
  for (const row of rows) {
580
- const lastIdx = row.length - 1;
581
- const line = row.map((cell, i) => i < lastIdx ? (cell || '').padEnd(colWidths[i]) : (cell || '')).join(' ');
411
+ const line = row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(' ');
582
412
  console.log(line);
583
413
  }
584
414
  }
@@ -608,27 +438,10 @@ async function main() {
608
438
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
609
439
  }
610
440
  };
611
- if (parsed.noReminders) {
612
- event.reminders = { useDefault: false, overrides: [] };
613
- }
614
- else if (parsed.reminders.length > 0) {
615
- event.reminders = { useDefault: false, overrides: parsed.reminders };
616
- }
617
441
  const token = await getAccessToken(user, true);
618
442
  const created = await createEvent(token, event, parsed.calendar);
619
443
  console.log(`\nEvent created: ${created.summary}`);
620
444
  console.log(` When: ${formatDateTime(created.start)} - ${formatDateTime(created.end)}`);
621
- if (parsed.noReminders) {
622
- console.log(` Reminders: none`);
623
- }
624
- else if (parsed.reminders.length > 0) {
625
- const rlist = parsed.reminders.map(r => {
626
- const mins = r.minutes;
627
- const label = mins >= 1440 ? `${mins / 1440}d` : mins >= 60 ? `${mins / 60}h` : `${mins}m`;
628
- return `${label}${r.method === 'email' ? ':email' : ''}`;
629
- }).join(', ');
630
- console.log(` Reminders: ${rlist}`);
631
- }
632
445
  if (created.htmlLink) {
633
446
  console.log(` Link: ${created.htmlLink}`);
634
447
  }
@@ -652,12 +465,7 @@ async function main() {
652
465
  if (matches.length > 1) {
653
466
  console.error(`${idPrefix}: ambiguous (${matches.length} matches)`);
654
467
  for (const e of matches) {
655
- // Show enough ID to distinguish recurring instances (base_date)
656
- const displayId = (e.id || '').length > 12 ? e.id.slice(0, 16) : e.id?.slice(0, 8);
657
- const cleaned = cleanUrls(e.summary || '');
658
- const summary = cleaned.length > 60 ? cleaned.slice(0, 57) + '...' : cleaned;
659
- const when = e.start ? formatDateTime(e.start) : '';
660
- console.error(` ${displayId} ${when} ${summary}`);
468
+ console.error(` ${e.id?.slice(0, 8)} - ${e.summary}`);
661
469
  }
662
470
  continue;
663
471
  }
@@ -667,7 +475,7 @@ async function main() {
667
475
  continue;
668
476
  }
669
477
  await deleteEvent(token, event.id, parsed.calendar);
670
- console.log(`Deleted: ${cleanUrls(event.summary || '')}`);
478
+ console.log(`Deleted: ${event.summary}`);
671
479
  }
672
480
  break;
673
481
  }
@@ -683,99 +491,6 @@ async function main() {
683
491
  }
684
492
  break;
685
493
  }
686
- case 'update': {
687
- if (parsed.args.length === 0) {
688
- console.error('Usage: gcal update <id> [-title "..."] [-loc "..."] [-start "..."] [-dur "..."] [-r ...]');
689
- console.error('Use "gcal list -v" to see event IDs');
690
- process.exit(1);
691
- }
692
- const idPrefix = parsed.args[0];
693
- const token = await getAccessToken(user, true);
694
- const events = await listEvents(token, parsed.calendar, 50);
695
- const matches = events.filter(e => e.id?.startsWith(idPrefix));
696
- if (matches.length === 0) {
697
- console.error(`${idPrefix}: not found`);
698
- process.exit(1);
699
- }
700
- if (matches.length > 1) {
701
- console.error(`${idPrefix}: ambiguous (${matches.length} matches)`);
702
- for (const e of matches) {
703
- const displayId = (e.id || '').length > 12 ? e.id.slice(0, 16) : e.id?.slice(0, 8);
704
- const cleaned = cleanUrls(e.summary || '');
705
- const summary = cleaned.length > 60 ? cleaned.slice(0, 57) + '...' : cleaned;
706
- const when = e.start ? formatDateTime(e.start) : '';
707
- console.error(` ${displayId} ${when} ${summary}`);
708
- }
709
- process.exit(1);
710
- }
711
- const target = matches[0];
712
- const body = {};
713
- const changes = [];
714
- if (parsed.title) {
715
- body.summary = parsed.title;
716
- changes.push(`title → "${parsed.title}"`);
717
- }
718
- if (parsed.location) {
719
- body.location = parsed.location;
720
- changes.push(`location → "${parsed.location}"`);
721
- }
722
- if (parsed.startTime) {
723
- const newStart = parseDateTime(parsed.startTime);
724
- const durationMs = parsed.duration
725
- ? parseDuration(parsed.duration) * 60 * 1000
726
- : (target.end?.dateTime && target.start?.dateTime)
727
- ? new Date(target.end.dateTime).getTime() - new Date(target.start.dateTime).getTime()
728
- : 60 * 60 * 1000;
729
- const newEnd = new Date(newStart.getTime() + durationMs);
730
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
731
- body.start = { dateTime: newStart.toISOString(), timeZone: tz };
732
- body.end = { dateTime: newEnd.toISOString(), timeZone: tz };
733
- changes.push(`start → ${formatDateTime(body.start)}`);
734
- if (parsed.duration)
735
- changes.push(`duration → ${parsed.duration}`);
736
- }
737
- else if (parsed.duration) {
738
- // Duration change without start change — shift end time
739
- if (target.start?.dateTime) {
740
- const startMs = new Date(target.start.dateTime).getTime();
741
- const newEnd = new Date(startMs + parseDuration(parsed.duration) * 60 * 1000);
742
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
743
- body.end = { dateTime: newEnd.toISOString(), timeZone: tz };
744
- changes.push(`duration → ${parsed.duration}`);
745
- }
746
- else {
747
- console.error('Cannot change duration of all-day event');
748
- process.exit(1);
749
- }
750
- }
751
- if (parsed.noReminders) {
752
- body.reminders = { useDefault: false, overrides: [] };
753
- changes.push('reminders → none');
754
- }
755
- else if (parsed.reminders.length > 0) {
756
- body.reminders = { useDefault: false, overrides: parsed.reminders };
757
- const rlist = parsed.reminders.map(r => {
758
- const mins = r.minutes;
759
- const label = mins >= 1440 ? `${mins / 1440}d` : mins >= 60 ? `${mins / 60}h` : `${mins}m`;
760
- return `${label}${r.method === 'email' ? ':email' : ''}`;
761
- }).join(', ');
762
- changes.push(`reminders → ${rlist}`);
763
- }
764
- if (changes.length === 0) {
765
- console.error('No update flags provided. Use -title, -loc, -start, -dur, or -r');
766
- process.exit(1);
767
- }
768
- const updated = await updateEvent(token, target.id, body, parsed.calendar);
769
- console.log(`\nUpdated: ${cleanUrls(updated.summary || '')}`);
770
- for (const c of changes) {
771
- console.log(` ${c}`);
772
- }
773
- break;
774
- }
775
- case 'assoc': {
776
- setupFileAssociation();
777
- break;
778
- }
779
494
  default:
780
495
  console.error(`Unknown command: ${parsed.command}`);
781
496
  showUsage();
@@ -783,11 +498,9 @@ async function main() {
783
498
  }
784
499
  }
785
500
  if (import.meta.main) {
786
- main().then(() => {
787
- process.exitCode = 0;
788
- }).catch(e => {
501
+ main().catch(e => {
789
502
  console.error(`Error: ${e.message}`);
790
- process.exitCode = 1;
503
+ process.exit(1);
791
504
  });
792
505
  }
793
506
  //# sourceMappingURL=gcal.js.map