Skip to content

0.26.0: cal, the calendar from the terminal over CalDAV - #50

Merged
ralyodio merged 1 commit into
masterfrom
feat/cal
Sep 6, 2026
Merged

0.26.0: cal, the calendar from the terminal over CalDAV#50
ralyodio merged 1 commit into
masterfrom
feat/cal

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

What

A cal command: the calendar from the terminal, over CalDAV. Follows mail (#48, #49) and shares its shape.

  • cal ls [--today|--tomorrow|--week|--days N|--from W --to W] [-c CALENDAR] — the agenda, grouped by local day, all-day events first; cal show <uid>; cal calendars.
  • cal add <title> --at W [--end W | --for D] [--all-day] [-c C] [--where P] [--notes T] [--link U]--at takes 2026-09-06 14:00, tomorrow 9:30, friday 2pm, 14:00, or a bare day (all-day). cal rm <uid> shows the event and asks first; --yes skips it.
  • cal login <provider|address> [address] [--as NAME] [--like MAIL-ACCOUNT] — says which kind of password the host wants before asking, discovers the calendars, stores nothing on a refusal. --like borrows a mail account's address and password, since iCloud, Fastmail and Forward Email use one for both.
  • cal providers — nine built in (Forward Email, iCloud, Fastmail, Zoho, Yahoo, AOL, GMX, mailbox.org, Posteo) plus custom --url for Nextcloud, Radicale, Baïkal, Stalwart; and the unreachable ones with the reason: Google Calendar (CalDAV takes only OAuth2; a Gmail app password opens the mailbox, not the calendar), Outlook.com / Microsoft 365 (no CalDAV), Proton (no CalDAV on any plan).
  • Accounts in ~/.config/cli-tools/cal.json (0600) or the new cli-tools-cal--prod vault as CAL_<NAME>_EMAIL / _PROVIDER / _PASSWORD (optional _USER, _URL), CAL_DEFAULT; cal accounts pull imports them.
  • Plugin cal (/cal:login, /cal:agenda, /cal:add) in the marketplace; README section; registry summary.

Decisions worth a look

  • No dependency. CalDAV here is three requests: PROPFIND (principal → home → calendars), REPORT (a window, or one UID), PUT/DELETE. The XML that comes back is shallow enough for a prefix-agnostic extractor; the iCalendar parser unfolds, splits parameters (quoted values included) and normalises dates.
  • The server expands recurrences. The REPORT carries <C:expand start end/>, so a weekly standup lists on every day it happens and this code implements no RRULE. cal rm on a recurring event removes the series and says so before asking.
  • Zoned times without a tz database. DTSTART;TZID=America/New_York:… is converted with Intl.DateTimeFormat, DST resolved by two-step offset lookup. Output is in the machine's zone; what goes out is UTC.
  • Basic auth over HTTPS only. A http:// URL (other than localhost) is refused before any request; a 401 becomes the provider's fix (app password, per-alias password) rather than "Unauthorized".
  • Redirects are followed by hand (redirect: 'manual'), so a discovery root that 301s to the real DAV path works and the Authorization header is re-sent only to where we chose to go.
  • Google is listed, not attempted, for the same reason mail lists Outlook: the endpoint takes only OAuth2, and a login attempt would fail in a way that reads like a typo.

Verified

  • 700 tests pass (27 new in test/cal.test.ts: providers, config and vault round-trips, XML extraction, iCalendar parse/build/fold, zoned-time conversion, typed times and windows, the CalDAV client against an in-memory server, output); typecheck clean apart from the two pre-existing free-names errors. (test/torrent.test.ts talks to a live tracker and timed out once, unrelated.)
  • Live against Forward Email's CalDAV, 2026-09-06: cal login forwardemail --like work discovered the account's calendar; cal add created an event (PUT 201), cal ls --days 3 listed it grouped by day, cal show <uid> printed it with location and notes, cal rm <uid> --yes deleted it, cal ls then showed nothing. CAL_WORK_* pushed to the new cli-tools-cal--prod vault and cal accounts pull reports it matching.
  • cal login google, a Gmail address, a Proton address, an unknown domain, a bad --at, and a missing title each produce the intended message.
  • Not exercised live: iCloud, Fastmail, Zoho, Yahoo (no account on the dev box). Their discovery URLs are the documented ones; iCloud's numbered pNN host is found through the principal, which the client handles.

🤖 Generated with Claude Code

https://claude.ai/code/session_013h8jopY81BGQ4Pn22NfZTu

`cal ls` for today, the week or a range, `cal show <uid>`, `cal add`,
`cal rm`, and `cal calendars`, against any CalDAV host that still takes a
password. No library: PROPFIND finds the principal and the calendars,
REPORT reads a window with the server expanding recurrences, PUT and DELETE
write. Nine providers are built in (Forward Email, iCloud, Fastmail, Zoho,
Yahoo, AOL, GMX, mailbox.org, Posteo) plus `custom` by URL for Nextcloud,
Radicale, Baïkal and the like; Google, Outlook and Proton are listed as
unreachable with the reason, since Google's CalDAV takes only OAuth2 and
the other two have none.

`cal login` mirrors `mail login`: it says which kind of password the host
wants before asking, discovers the calendars, and stores nothing on a
refusal. `--like <mail-account>` borrows the address and password of a mail
account, because iCloud, Fastmail and Forward Email use one password for
both. Accounts live in ~/.config/cli-tools/cal.json (0600) or the
cli-tools-cal vault as CAL_<NAME>_EMAIL / _PROVIDER / _PASSWORD.

Times as people type them: 2026-09-06 14:00, tomorrow 9:30, friday 2pm, or
a bare day for all-day. Zoned iCalendar times are converted with Intl, so
a TZID needs no tz database. Plugin `cal` (/cal:login, /cal:agenda,
/cal:add).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013h8jopY81BGQ4Pn22NfZTu
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

10 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 4 | LOW: 4

Severity Rule Location
HIGH sh-remote-script-execution root-ubuntu.sh:2816
HIGH sh-remote-script-execution root-ubuntu.sh:2820
MEDIUM sh-remote-script-execution root-ubuntu.sh:2969
MEDIUM sh-remote-script-execution root-ubuntu.sh:3998
MEDIUM redos-nested-quantifier src/domain-free.ts:56
MEDIUM redos-nested-quantifier src/mail.ts:1042
LOW secret-generic-credential src/credentials.ts:36
LOW secret-generic-api-key test/credentials.test.ts:208
LOW secret-generic-credential test/mail.test.ts:135
LOW secret-generic-credential test/shorten.test.ts:36

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit e34589e into master Sep 6, 2026
5 checks passed
@ralyodio
ralyodio deleted the feat/cal branch September 6, 2026 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant