@bobfrankston/gcal 0.1.11 → 0.1.13

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
@@ -239,6 +243,7 @@ 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
246
+ -r, -reminder <time> Reminder before event (10, 30m, 1h, 1d)
242
247
  -v, -verbose Show event IDs and links
243
248
 
244
249
  Examples:
@@ -247,7 +252,7 @@ Examples:
247
252
  gcal add "Dentist" "Friday 3pm" "1h"
248
253
  gcal add "Lunch" "1/14/2026 12:00" "1h"
249
254
  gcal add "Meeting" "tomorrow 10:00"
250
- gcal add "Appointment" "jan 15 2pm"
255
+ gcal add "Appointment" "jan 15 2pm" -r 1h
251
256
  gcal -defaultUser bob@gmail.com Set default user
252
257
 
253
258
  File Association (Windows):
@@ -266,6 +271,20 @@ interface ParsedArgs {
266
271
  help: boolean;
267
272
  verbose: boolean;
268
273
  icsFile: string; /** Direct .ics file path */
274
+ reminder: number; /** Reminder in minutes, -1 = not set */
275
+ }
276
+
277
+ /** Parse reminder string: number (minutes), or suffix m/h/d */
278
+ function parseReminder(val: string): number {
279
+ const match = val.match(/^(\d+)(m|h|d)?$/i);
280
+ if (!match) return -1;
281
+ const num = parseInt(match[1]);
282
+ const unit = (match[2] || 'm').toLowerCase();
283
+ switch (unit) {
284
+ case 'h': return num * 60;
285
+ case 'd': return num * 60 * 24;
286
+ default: return num;
287
+ }
269
288
  }
270
289
 
271
290
  function parseArgs(argv: string[]): ParsedArgs {
@@ -278,7 +297,8 @@ function parseArgs(argv: string[]): ParsedArgs {
278
297
  count: 10,
279
298
  help: false,
280
299
  verbose: false,
281
- icsFile: ''
300
+ icsFile: '',
301
+ reminder: -1
282
302
  };
283
303
 
284
304
  const unknown: string[] = [];
@@ -308,6 +328,11 @@ function parseArgs(argv: string[]): ParsedArgs {
308
328
  case '--verbose':
309
329
  result.verbose = true;
310
330
  break;
331
+ case '-r':
332
+ case '-reminder':
333
+ case '--reminder':
334
+ result.reminder = parseReminder(argv[++i] || '');
335
+ break;
311
336
  case '-h':
312
337
  case '-help':
313
338
  case '--help':
@@ -472,6 +497,57 @@ async function main(): Promise<void> {
472
497
  break;
473
498
  }
474
499
 
500
+ case 'past': {
501
+ const count = parsed.args[0] ? parseInt(parsed.args[0]) : parsed.count;
502
+ const token = await getAccessToken(user, false);
503
+ // Get events before now, then reverse to show most recent first
504
+ const now = new Date();
505
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
506
+ const events = await listEvents(token, parsed.calendar, count, thirtyDaysAgo.toISOString(), now.toISOString());
507
+ events.reverse(); // Most recent first
508
+
509
+ if (events.length === 0) {
510
+ console.log('No past events found in last 30 days.');
511
+ } else {
512
+ console.log(`\nPast events (${events.length}):\n`);
513
+
514
+ // Build table data (same format as list)
515
+ const rows: string[][] = [];
516
+ for (const event of events) {
517
+ const shortId = (event.id || '').slice(0, 8);
518
+ const start = event.start ? formatDateTime(event.start) : '?';
519
+ const duration = (event.start && event.end) ? formatDuration(event.start, event.end) : '';
520
+ const summary = event.summary || '(no title)';
521
+ const loc = event.location || '';
522
+ if (parsed.verbose) {
523
+ rows.push([shortId, start, duration, summary, loc, event.htmlLink || '']);
524
+ } else {
525
+ rows.push([shortId, start, duration, summary, loc]);
526
+ }
527
+ }
528
+
529
+ // Calculate column widths
530
+ const headers = parsed.verbose
531
+ ? ['ID', 'When', 'Dur', 'Event', 'Location', 'Link']
532
+ : ['ID', 'When', 'Dur', 'Event', 'Location'];
533
+ const colWidths = headers.map((h, i) =>
534
+ Math.max(h.length, ...rows.map(r => (r[i] || '').length))
535
+ );
536
+
537
+ // Print header
538
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(' ');
539
+ console.log(headerLine);
540
+ console.log(colWidths.map(w => '-'.repeat(w)).join(' '));
541
+
542
+ // Print rows
543
+ for (const row of rows) {
544
+ const line = row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(' ');
545
+ console.log(line);
546
+ }
547
+ }
548
+ break;
549
+ }
550
+
475
551
  case 'add': {
476
552
  if (parsed.args.length < 2) {
477
553
  console.error('Usage: gcal add <title> <when> [duration]');
@@ -496,10 +572,22 @@ async function main(): Promise<void> {
496
572
  }
497
573
  };
498
574
 
575
+ if (parsed.reminder >= 0) {
576
+ event.reminders = {
577
+ useDefault: false,
578
+ overrides: [{ method: 'popup', minutes: parsed.reminder }]
579
+ };
580
+ }
581
+
499
582
  const token = await getAccessToken(user, true);
500
583
  const created = await createEvent(token, event, parsed.calendar);
501
584
  console.log(`\nEvent created: ${created.summary}`);
502
585
  console.log(` When: ${formatDateTime(created.start)} - ${formatDateTime(created.end)}`);
586
+ if (parsed.reminder >= 0) {
587
+ const mins = parsed.reminder;
588
+ const reminderStr = mins >= 1440 ? `${mins / 1440}d` : mins >= 60 ? `${mins / 60}h` : `${mins}m`;
589
+ console.log(` Reminder: ${reminderStr} before`);
590
+ }
503
591
  if (created.htmlLink) {
504
592
  console.log(` Link: ${created.htmlLink}`);
505
593
  }
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.13",
4
4
  "description": "Google Calendar CLI tool with ICS import support",
5
5
  "type": "module",
6
6
  "main": "gcal.ts",
@@ -43,6 +43,9 @@
43
43
  "@bobfrankston/oauthsupport": "^1.0.5",
44
44
  "ical.js": "^2.1.0"
45
45
  },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
46
49
  ".dependencies": {
47
50
  "@bobfrankston/oauthsupport": "file:../../../projects/OAuth/OauthSupport",
48
51
  "ical.js": "^2.1.0"