@bobfrankston/gcal 0.1.11 → 0.1.12

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.ts CHANGED
@@ -90,14 +90,17 @@ 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(),
97
98
  singleEvents: 'true',
98
- orderBy: 'startTime',
99
- timeMin: timeMin || new Date().toISOString()
99
+ orderBy: 'startTime'
100
100
  });
101
+ if (timeMin) params.set('timeMin', timeMin);
102
+ if (timeMax) params.set('timeMax', timeMax);
103
+ if (!timeMin && !timeMax) params.set('timeMin', new Date().toISOString());
101
104
 
102
105
  const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events?${params}`;
103
106
  const res = await apiFetch(url, accessToken);
@@ -228,6 +231,7 @@ Usage:
228
231
 
229
232
  Commands:
230
233
  list [n] List upcoming n events (default: 10)
234
+ past [n] List past n events (default: 10)
231
235
  add <title> <when> [duration] Add event
232
236
  del|delete <id> [id2...] Delete event(s) by ID (prefix match)
233
237
  import <file.ics> Import events from ICS file
@@ -472,6 +476,57 @@ async function main(): Promise<void> {
472
476
  break;
473
477
  }
474
478
 
479
+ case 'past': {
480
+ const count = parsed.args[0] ? parseInt(parsed.args[0]) : parsed.count;
481
+ const token = await getAccessToken(user, false);
482
+ // Get events before now, then reverse to show most recent first
483
+ const now = new Date();
484
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
485
+ const events = await listEvents(token, parsed.calendar, count, thirtyDaysAgo.toISOString(), now.toISOString());
486
+ events.reverse(); // Most recent first
487
+
488
+ if (events.length === 0) {
489
+ console.log('No past events found in last 30 days.');
490
+ } else {
491
+ console.log(`\nPast events (${events.length}):\n`);
492
+
493
+ // Build table data (same format as list)
494
+ const rows: string[][] = [];
495
+ for (const event of events) {
496
+ const shortId = (event.id || '').slice(0, 8);
497
+ const start = event.start ? formatDateTime(event.start) : '?';
498
+ const duration = (event.start && event.end) ? formatDuration(event.start, event.end) : '';
499
+ const summary = event.summary || '(no title)';
500
+ const loc = event.location || '';
501
+ if (parsed.verbose) {
502
+ rows.push([shortId, start, duration, summary, loc, event.htmlLink || '']);
503
+ } else {
504
+ rows.push([shortId, start, duration, summary, loc]);
505
+ }
506
+ }
507
+
508
+ // Calculate column widths
509
+ const headers = parsed.verbose
510
+ ? ['ID', 'When', 'Dur', 'Event', 'Location', 'Link']
511
+ : ['ID', 'When', 'Dur', 'Event', 'Location'];
512
+ const colWidths = headers.map((h, i) =>
513
+ Math.max(h.length, ...rows.map(r => (r[i] || '').length))
514
+ );
515
+
516
+ // Print header
517
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(' ');
518
+ console.log(headerLine);
519
+ console.log(colWidths.map(w => '-'.repeat(w)).join(' '));
520
+
521
+ // Print rows
522
+ for (const row of rows) {
523
+ const line = row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(' ');
524
+ console.log(line);
525
+ }
526
+ }
527
+ break;
528
+ }
529
+
475
530
  case 'add': {
476
531
  if (parsed.args.length < 2) {
477
532
  console.error('Usage: gcal add <title> <when> [duration]');
package/glib/gutils.ts CHANGED
@@ -298,16 +298,19 @@ export function parseDateTime(input: string): Date {
298
298
  }
299
299
  }
300
300
 
301
- // Handle time only (HH:mm) - assume today
301
+ // Handle time only (HH:mm) - assume today, or tomorrow if time already passed
302
302
  const timeMatch = input.match(/^(\d{1,2}):(\d{2})$/);
303
303
  if (timeMatch) {
304
304
  const [, hour, min] = timeMatch;
305
305
  const d = new Date(now);
306
306
  d.setHours(parseInt(hour), parseInt(min), 0, 0);
307
+ if (d <= now) {
308
+ d.setDate(d.getDate() + 1); // Time passed, use tomorrow
309
+ }
307
310
  return d;
308
311
  }
309
312
 
310
- // Handle time with am/pm (2pm, 10am, 2:30pm) - assume today
313
+ // Handle time with am/pm (2pm, 10am, 2:30pm) - assume today, or tomorrow if time already passed
311
314
  const ampmMatch = lower.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)$/);
312
315
  if (ampmMatch) {
313
316
  const [, hour, min, ampm] = ampmMatch;
@@ -316,6 +319,9 @@ export function parseDateTime(input: string): Date {
316
319
  if (ampm === 'am' && h === 12) h = 0;
317
320
  const d = new Date(now);
318
321
  d.setHours(h, parseInt(min || '0'), 0, 0);
322
+ if (d <= now) {
323
+ d.setDate(d.getDate() + 1); // Time passed, use tomorrow
324
+ }
319
325
  return d;
320
326
  }
321
327
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/gcal",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Google Calendar CLI tool with ICS import support",
5
5
  "type": "module",
6
6
  "main": "gcal.ts",