first dated version. plugins were all sitting on 1.0.0, which said nothing about when a build was made or which of two copies is newer — the platform already uses YYYY.MM.DD.NN and this follows it. the version lives in manifest.ts because a plugin has no package.json; that is the field the marketplace publishes and the installer compares. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
92 lines
4.2 KiB
TypeScript
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.01',
|
|
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' },
|
|
},
|
|
],
|
|
};
|