@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 +58 -3
- package/glib/gutils.ts +8 -2
- package/package.json +1 -1
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
|
|