Files
caldav/manifest.ts
T
pastilhasandClaude Opus 5 76b4454cae 2026.08.27.02 — create, edit and delete, and a skill that speaks JSON
everything since .01: edit and delete on both screens, create an event and a
contact, PATCH one item's fields, ?collection=<name> instead of an escaped dav
path, and the agent skill rewritten off hand-written iCalendar onto the routes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:28:43 +00:00

92 lines
4.2 KiB
TypeScript

import type { PluginManifest } from '@@/plugins/manifest';
/**
* Calendar and contacts, over CalDAV and CardDAV.
*
* One sidecar (Radicale on loopback), one permission, one install — and TWO screens, because Calendar and
* Contacts are two places you go rather than two sections of one page. That is what `screens` below is
* for, and caldav is the first plugin to use it.
*/
export const manifest: PluginManifest = {
publisher: 'officerdev',
version: '2026.08.27.02',
platform: '>=1.0.0',
/**
* What it is called in the store, in words that assume nothing.
*
* Not "Calendar", which is one of its two screens and undersells it; not "CalDAV" or "caldav", which
* are protocol names a person browsing has no reason to know. The convention across the store is that
* a plugin is named for what you get, not for how it works.
*/
label: 'Contacts and Calendar',
summary: 'Calendars and contacts on your own machine, synced to your phone over CalDAV and CardDAV.',
color: '#ef4444',
/**
* Personal because that is what it holds; Degoogling because replacing Google Calendar and Google
* Contacts is the reason most people would install it.
*/
tags: ['Personal', 'Degoogling'],
/**
* Two screens at URLs this plugin chose — `/calendar` and `/contacts`, not `/caldav/calendar`.
*
* They were core routes until 2026-08-27 and keeping them is not cosmetic: a bookmark, a dock tile a
* user has arranged, and the layout persisted under `screens/plugin/caldav/...` all address them.
*
* Only a first-party plugin may choose its own segment — a third-party one is confined to
* `/p/<publisher>/` so it cannot shadow a core route. Each ships its own artwork as
* `assets/<segment>.png`, because two identical tiles side by side say nothing about which is which.
*/
screens: [
{ segment: 'calendar', label: 'Calendar', icon: 'CalendarDays' },
{ segment: 'contacts', label: 'Contacts', icon: 'Contact' },
],
permissions: [
{
key: 'calendar',
label: 'Calendar and contacts',
description: 'Calendars, contacts, and the app passwords that sync them to a phone',
// App passwords are minted for and revoked by their own owner, and are how a member's own phone
// reaches their own calendar. Nothing under here is shared, so a read grant may still write them.
personal: ['/passwords', '/provision'],
},
],
/**
* The top-level doors, above the account gate — and the reason is that phones are not browsers.
*
* DAVx5, iOS and Thunderbird send HTTP Basic on every request and have nowhere to put a platform JWT,
* so the account gate would 401 every real client. `sync-router` demands its own credential instead: a
* scoped DAV app password, resolved to one user, whose id is then what Radicale is told the principal
* is. "Unauthenticated" here means "not authenticated by the PLATFORM's session", not "open".
*
* The two `.well-known` names are RFC 6764 autodiscovery: the client is given a bare domain and probes
* them before it has any credential at all. Without them iOS degrades to demanding a full collection
* URL, which is exactly the sort of thing that makes self-hosting feel worse than the product it
* replaces.
*
* Every one of these was the platform's until this plugin existed, and each is in `RESERVED_ROOT` for
* that reason — claimable by a first-party plugin, refused to everyone else.
*/
unauthenticatedRootPaths: ['/dav', '/.well-known/caldav', '/.well-known/carddav'],
/**
* Radicale — a ~2MB Python package, spawned on loopback by this plugin's sidecar.
*
* Not a container: it was one only because the old app store made everything one. No Homebrew formula
* exists (confirmed live: "No available formula with the name radicale"), so a macOS dev install has
* nowhere for the sidecar to find it — which is why `resolveRadicale()` failing is handled rather than
* fatal.
*/
osDependencies: [
{
binary: 'radicale',
reason: 'Serves the CalDAV and CardDAV collections. Without it there are no calendars and no contacts.',
packages: { apt: 'radicale', pacman: 'radicale', dnf: 'radicale' },
},
],
};