From 27d8f16313ff734209b7a962b80b458fe6c8ded6 Mon Sep 17 00:00:00 2001 From: Hoid Date: Wed, 18 Feb 2026 07:38:57 +0000 Subject: [PATCH] Enhance calendar CLI: add search, date, range, month commands --- TOOLS.md | 6 +++ bin/calendar | 106 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/TOOLS.md b/TOOLS.md index 9cc5ad4..19b1dab 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -141,6 +141,12 @@ calendar today # Today's events (default) calendar tomorrow # Tomorrow's events calendar week # Next 7 days calendar next # Next 14 days +calendar month # Next 30 days +calendar date 2026-02-20 # Events on a specific date +calendar 2026-02-20 # Shorthand for date +calendar range 2026-02-18 2026-02-25 # Date range +calendar search dentist # Search by name/location (next 90 days) +calendar search rpg 180 # Search with custom day range ``` - Credentials: `.credentials/services.env` (NEXTCLOUD_URL, NEXTCLOUD_USER, NEXTCLOUD_PASS, CALDAV_CALENDAR) diff --git a/bin/calendar b/bin/calendar index 551aecd..9294aa9 100755 --- a/bin/calendar +++ b/bin/calendar @@ -137,7 +137,29 @@ async function main() { const args = process.argv.slice(2); const cmd = args[0] || 'today'; + // Parse date string (YYYY-MM-DD or natural offsets) + function parseDate(str) { + const m = str.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (m) { + const d = new Date(Date.UTC(+m[1], +m[2]-1, +m[3])); + d.setUTCHours(0, 0, 0, 0); + return d; + } + // Try as number of days offset + const n = parseInt(str); + if (!isNaN(n)) { + const d = new Date(); + d.setUTCHours(0, 0, 0, 0); + d.setUTCDate(d.getUTCDate() + n); + return d; + } + return null; + } + let range; + let label = cmd; + const searchQuery = args.slice(1).join(' ').toLowerCase(); + switch (cmd) { case 'today': range = getDateRange(0, 1); @@ -151,12 +173,71 @@ async function main() { case 'next': range = getDateRange(0, 14); break; + case 'month': + range = getDateRange(0, 30); + break; + case 'range': { + // calendar range 2026-02-18 2026-02-25 + const from = args[1] && parseDate(args[1]); + const to = args[2] && parseDate(args[2]); + if (!from || !to) { + console.error('Usage: calendar range '); + console.error(' Dates: YYYY-MM-DD or offset in days (e.g., 0 = today, 7 = in 7 days)'); + process.exit(1); + } + const fmt = d => d.toISOString().replace(/[-:]/g, '').replace(/\.\d+/, ''); + range = { start: fmt(from), end: fmt(to) }; + label = `${args[1]} to ${args[2]}`; + break; + } + case 'search': { + // calendar search [days] — search events by name, default 30 days + if (!searchQuery) { + console.error('Usage: calendar search [days]'); + console.error(' Searches event summaries/locations. Default: next 30 days.'); + process.exit(1); + } + const days = parseInt(args[args.length - 1]); + const searchDays = (!isNaN(days) && args.length > 2) ? days : 90; + range = getDateRange(0, searchDays); + label = `search "${searchQuery}" (${searchDays} days)`; + break; + } + case 'date': { + // calendar date 2026-02-20 + const d = args[1] && parseDate(args[1]); + if (!d) { + console.error('Usage: calendar date '); + process.exit(1); + } + const next = new Date(d); + next.setUTCDate(next.getUTCDate() + 1); + const fmt = d => d.toISOString().replace(/[-:]/g, '').replace(/\.\d+/, ''); + range = { start: fmt(d), end: fmt(next) }; + label = args[1]; + break; + } default: - console.log('Usage: calendar [today|tomorrow|week|next]'); - console.log(' today — Events for today (default)'); - console.log(' tomorrow — Events for tomorrow'); - console.log(' week — Events for the next 7 days'); - console.log(' next — Events for the next 14 days'); + // Try as date: calendar 2026-02-20 + const asDate = parseDate(cmd); + if (asDate) { + const next = new Date(asDate); + next.setUTCDate(next.getUTCDate() + 1); + const fmt = d => d.toISOString().replace(/[-:]/g, '').replace(/\.\d+/, ''); + range = { start: fmt(asDate), end: fmt(next) }; + label = cmd; + break; + } + console.log('Usage: calendar '); + console.log(' today — Events for today (default)'); + console.log(' tomorrow — Events for tomorrow'); + console.log(' week — Next 7 days'); + console.log(' next — Next 14 days'); + console.log(' month — Next 30 days'); + console.log(' date — Events on a specific date'); + console.log(' — Shorthand for date'); + console.log(' range — Events in date range (YYYY-MM-DD or day offset)'); + console.log(' search [days] — Search events by name/location (default: 90 days)'); process.exit(0); } @@ -167,9 +248,20 @@ async function main() { process.exit(1); } - const events = parseEvents(res.data); + let events = parseEvents(res.data); + + // Filter by search query if searching + if (cmd === 'search' && searchQuery) { + const q = searchQuery.replace(/\s+\d+$/, ''); // strip trailing days number + events = events.filter(e => + (e.summary || '').toLowerCase().includes(q) || + (e.location || '').toLowerCase().includes(q) || + (e.description || '').toLowerCase().includes(q) + ); + } + if (events.length === 0) { - console.log(`No events (${cmd}).`); + console.log(`No events (${label}).`); return; }