@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.
- package/gcal.ts +67 -6
- package/glib/gutils.ts +23 -0
- 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
|
-
-
|
|
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
|
|
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
|
-
|
|
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 '-
|
|
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
|
|
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())) {
|