@bobfrankston/gcal 0.1.7 → 0.1.9
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 +53 -4
- package/glib/gutils.ts +43 -0
- package/package.json +3 -2
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
|
|
246
|
+
-after <when> List events after this date/time
|
|
247
|
+
-before <when> List events before this date/time
|
|
242
248
|
-v, -verbose Show event IDs and links
|
|
243
249
|
|
|
244
250
|
Examples:
|
|
245
251
|
gcal meeting.ics Import ICS file
|
|
246
252
|
gcal list List next 10 events
|
|
253
|
+
gcal list -after tomorrow -before "jan 30"
|
|
247
254
|
gcal add "Dentist" "Friday 3pm" "1h"
|
|
248
255
|
gcal add "Lunch" "1/14/2026 12:00" "1h"
|
|
249
|
-
gcal add "Meeting" "tomorrow
|
|
256
|
+
gcal add "Meeting" "tomorrow 10am"
|
|
257
|
+
gcal add "Call" "3pm" "30m"
|
|
258
|
+
gcal add "Appointment" "jan 15 2pm"
|
|
250
259
|
gcal -defaultUser bob@gmail.com Set default user
|
|
251
260
|
|
|
252
261
|
File Association (Windows):
|
|
@@ -265,6 +274,8 @@ interface ParsedArgs {
|
|
|
265
274
|
help: boolean;
|
|
266
275
|
verbose: boolean;
|
|
267
276
|
icsFile: string; /** Direct .ics file path */
|
|
277
|
+
after: string; /** Filter: events after this date/time */
|
|
278
|
+
before: string; /** Filter: events before this date/time */
|
|
268
279
|
}
|
|
269
280
|
|
|
270
281
|
function parseArgs(argv: string[]): ParsedArgs {
|
|
@@ -277,7 +288,9 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
277
288
|
count: 10,
|
|
278
289
|
help: false,
|
|
279
290
|
verbose: false,
|
|
280
|
-
icsFile: ''
|
|
291
|
+
icsFile: '',
|
|
292
|
+
after: '',
|
|
293
|
+
before: ''
|
|
281
294
|
};
|
|
282
295
|
|
|
283
296
|
const unknown: string[] = [];
|
|
@@ -302,6 +315,14 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
302
315
|
case '-n':
|
|
303
316
|
result.count = parseInt(argv[++i]) || 10;
|
|
304
317
|
break;
|
|
318
|
+
case '-after':
|
|
319
|
+
case '--after':
|
|
320
|
+
result.after = argv[++i] || '';
|
|
321
|
+
break;
|
|
322
|
+
case '-before':
|
|
323
|
+
case '--before':
|
|
324
|
+
result.before = argv[++i] || '';
|
|
325
|
+
break;
|
|
305
326
|
case '-v':
|
|
306
327
|
case '-verbose':
|
|
307
328
|
case '--verbose':
|
|
@@ -427,7 +448,9 @@ async function main(): Promise<void> {
|
|
|
427
448
|
case 'list': {
|
|
428
449
|
const count = parsed.args[0] ? parseInt(parsed.args[0]) : parsed.count;
|
|
429
450
|
const token = await getAccessToken(user, false);
|
|
430
|
-
const
|
|
451
|
+
const timeMin = parsed.after ? parseDateTime(parsed.after).toISOString() : undefined;
|
|
452
|
+
const timeMax = parsed.before ? parseDateTime(parsed.before).toISOString() : undefined;
|
|
453
|
+
const events = await listEvents(token, parsed.calendar, count, timeMin, timeMax);
|
|
431
454
|
|
|
432
455
|
if (events.length === 0) {
|
|
433
456
|
console.log('No upcoming events found.');
|
|
@@ -483,6 +506,32 @@ async function main(): Promise<void> {
|
|
|
483
506
|
const durationMins = parseDuration(duration);
|
|
484
507
|
const endTime = new Date(startTime.getTime() + durationMins * 60 * 1000);
|
|
485
508
|
|
|
509
|
+
// Check for suspicious dates (likely parsing errors)
|
|
510
|
+
const now = new Date();
|
|
511
|
+
const twoYearsFromNow = new Date(now);
|
|
512
|
+
twoYearsFromNow.setFullYear(twoYearsFromNow.getFullYear() + 2);
|
|
513
|
+
|
|
514
|
+
let warning = '';
|
|
515
|
+
if (startTime < now) {
|
|
516
|
+
warning = `Date is in the past: ${formatDateTime({ dateTime: startTime.toISOString() })}`;
|
|
517
|
+
} else if (startTime > twoYearsFromNow) {
|
|
518
|
+
warning = `Date is more than 2 years away: ${formatDateTime({ dateTime: startTime.toISOString() })}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (warning) {
|
|
522
|
+
console.log(`\nWarning: ${warning}`);
|
|
523
|
+
console.log(`Input was: "${when}"`);
|
|
524
|
+
const readline = await import('readline');
|
|
525
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
526
|
+
const response = await new Promise<string>(resolve => {
|
|
527
|
+
rl.question('Continue? (y/N) ', answer => { rl.close(); resolve(answer); });
|
|
528
|
+
});
|
|
529
|
+
if (response.toLowerCase() !== 'y') {
|
|
530
|
+
console.log('Cancelled.');
|
|
531
|
+
process.exit(0);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
486
535
|
const event: GoogleEvent = {
|
|
487
536
|
summary: title,
|
|
488
537
|
start: {
|
package/glib/gutils.ts
CHANGED
|
@@ -259,6 +259,26 @@ export function parseDateTime(input: string): Date {
|
|
|
259
259
|
return d;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
// Handle month names: "jan 15", "jan 15 2026", "jan 15 3pm", "january 15 2026 3pm"
|
|
263
|
+
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
264
|
+
const monthMatch = lower.match(/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+(\d{1,2})(?:\s+(\d{4}))?(?:\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?)?$/i);
|
|
265
|
+
if (monthMatch) {
|
|
266
|
+
const [, month, day, year, hour, min, ampm] = monthMatch;
|
|
267
|
+
const monthIndex = months.findIndex(m => month.toLowerCase().startsWith(m));
|
|
268
|
+
const d = new Date(parseInt(year || now.getFullYear().toString()), monthIndex, parseInt(day));
|
|
269
|
+
if (hour) {
|
|
270
|
+
let h = parseInt(hour);
|
|
271
|
+
if (ampm?.toLowerCase() === 'pm' && h < 12) h += 12;
|
|
272
|
+
if (ampm?.toLowerCase() === 'am' && h === 12) h = 0;
|
|
273
|
+
d.setHours(h, parseInt(min || '0'), 0, 0);
|
|
274
|
+
} else {
|
|
275
|
+
d.setHours(0, 0, 0, 0);
|
|
276
|
+
}
|
|
277
|
+
if (!isNaN(d.getTime())) {
|
|
278
|
+
return d;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
262
282
|
// Handle explicit date/time formats: "MM/DD/YYYY HH:mm" or "YYYY-MM-DD HH:mm"
|
|
263
283
|
const dateTimeMatch = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{2})$/);
|
|
264
284
|
if (dateTimeMatch) {
|
|
@@ -287,6 +307,29 @@ export function parseDateTime(input: string): Date {
|
|
|
287
307
|
return d;
|
|
288
308
|
}
|
|
289
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
|
+
|
|
290
333
|
// Try native Date parsing
|
|
291
334
|
const parsed = new Date(input);
|
|
292
335
|
if (!isNaN(parsed.getTime())) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/gcal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Google Calendar CLI tool with ICS import support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "gcal.ts",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"preversion": "npm run check && git add -A",
|
|
24
24
|
"postversion": "git push && git push --tags",
|
|
25
25
|
"release": "npm run prerelease:local && npm version patch && npm publish --access public",
|
|
26
|
-
"release:local": "npm run prerelease:local && npm version patch && npm publish --access public --ignore-scripts"
|
|
26
|
+
"release:local": "npm run prerelease:local && npm version patch && npm publish --access public --ignore-scripts",
|
|
27
|
+
"installer": "npm run release && npm install -g ."
|
|
27
28
|
},
|
|
28
29
|
"keywords": [
|
|
29
30
|
"google",
|