Commit Graph
7 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 4adb925bc7 release 2026.08.27.01
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>
2026-08-27 07:36:16 +00:00
pastilhas f5d5a75314 AGENTS.md, so both harnesses find the same guidance
opencode discovers AGENTS.md and nothing else; claude code discovers CLAUDE.md.
this repo had neither, so an agent sent to change something here arrived with no
guidance at all under either.

it deliberately does NOT repeat README.md. that file says what the plugin is,
for someone deciding whether to install it. this one answers the other question:
i have to change something in here, what will i break.

the facts in it are read from the repository rather than recalled — the panel
keys from web/panels.ts, the tables from db/schema.ts, the permission keys, the
host binaries and the route claims from manifest.ts. presence is the declaration
everywhere in this system, so the file says what the platform will actually
conclude from these files existing.

CLAUDE.md beside it is a pointer, not a copy: two copies drift and the stale one
is the one somebody reads.
2026-08-27 07:24:07 +00:00
pastilhas 2bfdb9c284 say what this plugin is for, in the store's words
tags are how the marketplace is browsed rather than scrolled: a list of them on
the left, the plugins that carry one on the right. a closed vocabulary
(PLUGIN_TAGS in the platform) because free text becomes homelab, home-lab, Home
Lab and selfhosted inside a month, and a filter list of synonyms is worse than
no filter at all.

declared here rather than curated in the catalogue, so a plugin can say what it
is and nobody edits a table when one ships. the marketplace caches it on sync,
exactly as it already does for label and summary.
2026-08-27 06:49:29 +00:00
pastilhasandClaude Opus 5 a161e2ac5f agents can manage the calendar and the address book
the owner's scenario: any chat, "I'm having dinner Saturday with my wife, add
that", "cancel it", "add Peter Griffin, this number" — and it happens.

reading already worked through the JSON api. writing is the DAV door, and the
credential question is what took the thinking: every option meant a NEW secret
and then somewhere to keep it — a file, the secret store, one per commander —
each a password to distribute, rotate and eventually leak.

$OFFICER_AGENT_KEY is already in every agent's environment, already bound to one
user, already revoked when the session ends. so the DAV door accepts it as the
basic-auth password, and no new secret exists anywhere.

it cannot reach another account, and that is structural rather than a check:
resolveApiKey says whose the key is and that id becomes X-Remote-User. the URL
is never consulted, radicale refuses any other principal, and the username in
the header must AGREE with the key rather than select the account. verified —
own principal 207/201, another account 403, spoofed username 401.

the permission check is isApiRequestAllowed against this plugin's own prefix, so
the answer is exactly what the gate would give the same credential at
/api/caldav. a second copy of that rule is the copy that stops being updated.
DAV methods are mapped to read/write with PROPFIND and REPORT as reads —
unmapped they counted as writes and a member with read could not list their own
calendar. anything unrecognised defaults to write, which is the safe direction.

the skill is written from recipes that were RUN, not recalled: create 201, move
204 (same UID, same href, SEQUENCE bumped), delete 200, contact create and
delete, and the json read for lookups.

the trap it exists to prevent, found while testing rather than by reading:
`date -u -d 'saturday 20:00'` does not convert. -u parses AND prints as UTC, so
8pm becomes 20:00Z, which is 21:00 in lisbon. every event an hour out, nothing
errors, and it is invisible until someone is stood up. the two-step through
epoch is the only form that works and the skill leads with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 06:08:25 +00:00
pastilhasandClaude Opus 5 622dfa3602 the config comment stops terminating its own template literal
my previous commit put backticks inside the generated radicale.conf, which is a
template literal — so the string ended mid-comment and the file stopped parsing.
caught by tsgo, not by a test, because nothing imports this at test time.

plain words instead. the point of the comment survives; the backticks were
decoration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 04:55:40 +00:00
pastilhasandClaude Opus 5 f12e56aa80 the radicale config stops claiming caldav is single-user
the setting was right and the sentence beside it was not, which is the worse of
the two: anyone reading this config would conclude caldav is single-user and
either build around a limit that is not there or "fix" a line that was never
broken. it had been false since multi-user landed on 2026-08-07.

owner_only is what makes it per-user, together with the sync door resolving an
app password to one account and sending that id as X-Remote-User. verified on
alpha: collections/collection-root/1/{personal,contacts,tasks}.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 04:53:58 +00:00
pastilhasandClaude Opus 5 dcbc9b102c calendar and contacts, extracted from the officer platform
one sidecar, one permission, one install — and two screens, because Calendar and
Contacts are two places you go rather than two sections of one page. the first
plugin to declare `screens`, which the platform gained for this.

what moved: the radicale sidecar, the four routers, the panel apps, the
app-password table and the settings form. the form became a modal on the
calendar screen — a plugin ships panels and cannot contribute a settings
section, which is the same wall the email plugin hit and the same answer.

the routes are /calendar and /contacts, not /caldav/calendar. they were core
routes and keeping them is not cosmetic: a bookmark, a dock tile someone
arranged, and a persisted layout all address them. only a first-party plugin may
choose its own segment; a third-party one is confined to /p/<publisher>/.

four paths outside /api come with it, above the account gate, because phones are
not browsers: DAVx5, iOS and thunderbird send HTTP basic on every request and
have nowhere to put a JWT. that is not "open" — the sync door demands a scoped
app password and resolves it to one user before radicale sees anything.

two things deliberately unchanged. the table is still dav_app_passwords, not
caldav_app_passwords: installs hold real rows and a naming convention is not
worth a data migration on a live machine. and the panel names still resolve
through the platform's catalogs, so the six locales they had as core screens
survive rather than dropping to english to make the refactor tidier.

per-user was already true and is worth writing down because the config said
otherwise: the sync door resolves an app password to an account, tells radicale
that id is the principal, and owner_only does the rest. the comment claiming
"the owner is the only principal that exists" had been stale since multi-user
landed on 2026-08-07.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 04:53:45 +00:00