@bobfrankston/gcal 0.1.8 → 0.1.10

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.
Files changed (3) hide show
  1. package/gcal.ts +67 -6
  2. package/glib/gutils.ts +23 -0
  3. package/package.json +1 -1
package/gcal.ts CHANGED
@@ -90,7 +90,8 @@ async function listEvents(
90
90
  accessToken: string,
91
91
  calendarId = 'primary',
92
92
  maxResults = 10,
93
- timeMin?: string
93
+ timeMin?: string,
94
+ timeMax?: string
94
95
  ): Promise<GoogleEvent[]> {
95
96
  const params = new URLSearchParams({
96
97
  maxResults: maxResults.toString(),
@@ -98,6 +99,9 @@ async function listEvents(
98
99
  orderBy: 'startTime',
99
100
  timeMin: timeMin || new Date().toISOString()
100
101
  });
102
+ if (timeMax) {
103
+ params.set('timeMax', timeMax);
104
+ }
101
105
 
102
106
  const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events?${params}`;
103
107
  const res = await apiFetch(url, accessToken);
@@ -239,14 +243,19 @@ Options:
239
243
  -defaultUser <email> Set default user for future use
240
244
  -c, -calendar <id> Calendar ID (default: primary)
241
245
  -n <count> Number of events to list
242
- -v, -verbose Show event IDs and links
246
+ -after <when> List events after this date/time
247
+ -before <when> List events before this date/time
248
+ -verbose Show event IDs and links
249
+ -v, --version Show version
243
250
 
244
251
  Examples:
245
252
  gcal meeting.ics Import ICS file
246
253
  gcal list List next 10 events
254
+ gcal list -after tomorrow -before "jan 30"
247
255
  gcal add "Dentist" "Friday 3pm" "1h"
248
256
  gcal add "Lunch" "1/14/2026 12:00" "1h"
249
- gcal add "Meeting" "tomorrow 10:00"
257
+ gcal add "Meeting" "tomorrow 10am"
258
+ gcal add "Call" "3pm" "30m"
250
259
  gcal add "Appointment" "jan 15 2pm"
251
260
  gcal -defaultUser bob@gmail.com Set default user
252
261
 
@@ -265,7 +274,10 @@ interface ParsedArgs {
265
274
  count: number;
266
275
  help: boolean;
267
276
  verbose: boolean;
277
+ version: boolean;
268
278
  icsFile: string; /** Direct .ics file path */
279
+ after: string; /** Filter: events after this date/time */
280
+ before: string; /** Filter: events before this date/time */
269
281
  }
270
282
 
271
283
  function parseArgs(argv: string[]): ParsedArgs {
@@ -278,7 +290,10 @@ function parseArgs(argv: string[]): ParsedArgs {
278
290
  count: 10,
279
291
  help: false,
280
292
  verbose: false,
281
- icsFile: ''
293
+ version: false,
294
+ icsFile: '',
295
+ after: '',
296
+ before: ''
282
297
  };
283
298
 
284
299
  const unknown: string[] = [];
@@ -303,11 +318,23 @@ function parseArgs(argv: string[]): ParsedArgs {
303
318
  case '-n':
304
319
  result.count = parseInt(argv[++i]) || 10;
305
320
  break;
306
- case '-v':
321
+ case '-after':
322
+ case '--after':
323
+ result.after = argv[++i] || '';
324
+ break;
325
+ case '-before':
326
+ case '--before':
327
+ result.before = argv[++i] || '';
328
+ break;
307
329
  case '-verbose':
308
330
  case '--verbose':
309
331
  result.verbose = true;
310
332
  break;
333
+ case '-v':
334
+ case '-V':
335
+ case '--version':
336
+ result.version = true;
337
+ break;
311
338
  case '-h':
312
339
  case '-help':
313
340
  case '--help':
@@ -378,6 +405,12 @@ async function main(): Promise<void> {
378
405
  }
379
406
  }
380
407
 
408
+ if (parsed.version) {
409
+ const pkg = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, 'package.json'), 'utf-8'));
410
+ console.log(`gcal ${pkg.version}`);
411
+ process.exit(0);
412
+ }
413
+
381
414
  if (parsed.help) {
382
415
  showUsage();
383
416
  process.exit(0);
@@ -428,7 +461,9 @@ async function main(): Promise<void> {
428
461
  case 'list': {
429
462
  const count = parsed.args[0] ? parseInt(parsed.args[0]) : parsed.count;
430
463
  const token = await getAccessToken(user, false);
431
- const events = await listEvents(token, parsed.calendar, count);
464
+ const timeMin = parsed.after ? parseDateTime(parsed.after).toISOString() : undefined;
465
+ const timeMax = parsed.before ? parseDateTime(parsed.before).toISOString() : undefined;
466
+ const events = await listEvents(token, parsed.calendar, count, timeMin, timeMax);
432
467
 
433
468
  if (events.length === 0) {
434
469
  console.log('No upcoming events found.');
@@ -484,6 +519,32 @@ async function main(): Promise<void> {
484
519
  const durationMins = parseDuration(duration);
485
520
  const endTime = new Date(startTime.getTime() + durationMins * 60 * 1000);
486
521
 
522
+ // Check for suspicious dates (likely parsing errors)
523
+ const now = new Date();
524
+ const twoYearsFromNow = new Date(now);
525
+ twoYearsFromNow.setFullYear(twoYearsFromNow.getFullYear() + 2);
526
+
527
+ let warning = '';
528
+ if (startTime < now) {
529
+ warning = `Date is in the past: ${formatDateTime({ dateTime: startTime.toISOString() })}`;
530
+ } else if (startTime > twoYearsFromNow) {
531
+ warning = `Date is more than 2 years away: ${formatDateTime({ dateTime: startTime.toISOString() })}`;
532
+ }
533
+
534
+ if (warning) {
535
+ console.log(`\nWarning: ${warning}`);
536
+ console.log(`Input was: "${when}"`);
537
+ const readline = await import('readline');
538
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
539
+ const response = await new Promise<string>(resolve => {
540
+ rl.question('Continue? (y/N) ', answer => { rl.close(); resolve(answer); });
541
+ });
542
+ if (response.toLowerCase() !== 'y') {
543
+ console.log('Cancelled.');
544
+ process.exit(0);
545
+ }
546
+ }
547
+
487
548
  const event: GoogleEvent = {
488
549
  summary: title,
489
550
  start: {
package/glib/gutils.ts CHANGED
@@ -307,6 +307,29 @@ export function parseDateTime(input: string): Date {
307
307
  return d;
308
308
  }
309
309
 
310
+ // Handle time with am/pm: "10am", "3pm", "10:30am", "3:45pm" - assume today
311
+ const timeAmPmMatch = lower.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)$/);
312
+ if (timeAmPmMatch) {
313
+ const [, hour, min, ampm] = timeAmPmMatch;
314
+ const d = new Date(now);
315
+ let h = parseInt(hour);
316
+ if (ampm === 'pm' && h < 12) h += 12;
317
+ if (ampm === 'am' && h === 12) h = 0;
318
+ d.setHours(h, parseInt(min || '0'), 0, 0);
319
+ return d;
320
+ }
321
+
322
+ // Handle bare hour: "10", "14" - interpret as today at that hour (not a month)
323
+ const bareHourMatch = input.match(/^(\d{1,2})$/);
324
+ if (bareHourMatch) {
325
+ const hour = parseInt(bareHourMatch[1]);
326
+ if (hour >= 0 && hour <= 23) {
327
+ const d = new Date(now);
328
+ d.setHours(hour, 0, 0, 0);
329
+ return d;
330
+ }
331
+ }
332
+
310
333
  // Try native Date parsing
311
334
  const parsed = new Date(input);
312
335
  if (!isNaN(parsed.getTime())) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/gcal",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Google Calendar CLI tool with ICS import support",
5
5
  "type": "module",
6
6
  "main": "gcal.ts",