This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

machines.coaxmetal: give hermes access to the shared calendar

+123
+123
machines/coaxmetal/hermes.nix
··· 109 109 # Drive the herd from a voice note: "read me what the agent on herdr is 110 110 # stuck on" -> herdr agent list / read / prompt, as lass. 111 111 herdr 112 + # CalDAV (calendar skill below) is plain HTTP; curl is not otherwise on 113 + # the gateway PATH. 114 + pkgs.curl 112 115 ]; 113 116 114 117 settings.model = { ··· 263 266 Never close panes or kill agents on your own. 264 267 ''; 265 268 269 + # The one shared calendar (configs/radicale.nix): lass's own events, the 270 + # book.lassul.us requests and bookings, and anything Hermes adds. Hermes 271 + # authenticates as its own radicale user, which may only touch this 272 + # collection. Bookings are mirrored from book's database, so they are 273 + # managed through book's admin links rather than edited in CalDAV. 274 + hermesHomeFiles."skills/calendar/SKILL.md" = '' 275 + --- 276 + name: calendar 277 + description: Read and manage lass's calendar (CalDAV) and approve, decline or cancel book.lassul.us booking requests. 278 + version: 1.0.0 279 + license: MIT 280 + platforms: [linux] 281 + required_environment_variables: 282 + - name: CALDAV_PASSWORD 283 + prompt: Radicale password of the hermes user 284 + help: Provisioned by clan vars (radicale-hermes) on coaxmetal. 285 + required_for: any calendar access 286 + prerequisites: 287 + commands: [curl] 288 + metadata: 289 + hermes: 290 + tags: [calendar, caldav, schedule, booking, appointments] 291 + requires_toolsets: [terminal] 292 + --- 293 + 294 + # calendar 295 + 296 + lass has exactly one calendar, at `$CALDAV_CALENDAR_URL` 297 + (CalDAV on radicale). It holds his own events, booking requests and 298 + bookings from https://book.lassul.us, and events you add. The booking 299 + page treats every event in it as busy time. 300 + 301 + Use it when the user asks what is on his schedule, whether he is free, 302 + to add, move or remove an appointment, or about booking requests. 303 + 304 + Authenticate every request with 305 + `curl -sS -u "$CALDAV_USER:$CALDAV_PASSWORD"`. Never print, echo or 306 + repeat the password. Times are Europe/Berlin unless the user says 307 + otherwise; write DTSTART/DTEND with `;TZID=Europe/Berlin` or in UTC (`Z`). 308 + 309 + ## Reading 310 + 311 + Events overlapping a range (UTC, `YYYYMMDDTHHMMSSZ`): 312 + 313 + ```sh 314 + curl -sS -u "$CALDAV_USER:$CALDAV_PASSWORD" -X REPORT -H 'Depth: 1' \ 315 + -H 'Content-Type: application/xml; charset=utf-8' "$CALDAV_CALENDAR_URL" --data ' 316 + <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> 317 + <D:prop><D:getetag/><C:calendar-data/></D:prop> 318 + <C:filter><C:comp-filter name="VCALENDAR"><C:comp-filter name="VEVENT"> 319 + <C:time-range start="20261010T000000Z" end="20261017T000000Z"/> 320 + </C:comp-filter></C:comp-filter></C:filter> 321 + </C:calendar-query>' 322 + ``` 323 + 324 + Each `<D:response>` has the event's `href` (its URL path), `getetag` and 325 + the iCalendar text. Recurring events (RRULE) come back once with their 326 + rule; expand them yourself when answering "what is on Tuesday". 327 + 328 + ## Adding, changing, removing your own events 329 + 330 + - Add: pick a UID (`uuidgen`-style, suffix `@hermes`), then 331 + `PUT "$CALDAV_CALENDAR_URL<uid>.ics"` with 332 + `-H 'Content-Type: text/calendar; charset=utf-8' -H 'If-None-Match: *'` 333 + and a body of one `VCALENDAR` (VERSION:2.0, PRODID) holding one 334 + `VEVENT` with UID, DTSTAMP, DTSTART, DTEND (or DURATION) and SUMMARY. 335 + Add a `VALARM` (ACTION:DISPLAY, TRIGGER:-PT15M) when the user wants a 336 + reminder. 337 + - Change: GET the event, edit it, PUT it back to the same href with 338 + `-H 'If-Match: <etag>'` so you never overwrite a concurrent edit. 339 + - Remove: `DELETE` the href with `-H 'If-Match: <etag>'`. 340 + - Success is HTTP 201/204. 412 means it changed meanwhile: re-read and 341 + retry. Confirm by reading the range again. 342 + 343 + Before adding, check the range for overlaps and tell the user about 344 + any. Do not change or delete events the user did not ask about. 345 + 346 + ## Booking requests and bookings (book.lassul.us) 347 + 348 + Events whose UID ends in `@book.lassul.us` belong to the booking page. 349 + Never PUT or DELETE them: the booking page owns them, rewrites them on 350 + every change, and only it mails the guest. 351 + 352 + - Pending request: `STATUS:TENTATIVE`, SUMMARY starts with `? `. 353 + - Booked: `STATUS:CONFIRMED`. 354 + - The DESCRIPTION holds the guest (`Name <email>`), their note, and as 355 + its last line the admin link `https://book.lassul.us/a/<token>`. 356 + 357 + Act on a booking only when the user decided to, by POSTing to its 358 + admin link (an optional `message` is mailed to the guest): 359 + 360 + ```sh 361 + curl -sS -o /dev/null -w '%{http_code}\n' -X POST \ 362 + --data-urlencode 'message=Looking forward to it' \ 363 + 'https://book.lassul.us/a/<token>/approve' # or /decline, /cancel 364 + ``` 365 + 366 + A 303 means it worked: approve turns the event CONFIRMED, decline and 367 + cancel remove it from the calendar. `GET` on the admin link shows the 368 + request's current state. Treat admin links as secrets: never paste one 369 + into a chat unless the user asks for it. 370 + ''; 371 + 266 372 # Non-secret connection + SECURITY gate. Invites are auto-accepted and that 267 373 # cannot be disabled, so the agent may join any room it's invited to. Joining 268 374 # is harmless on its own — what matters is who can *trigger* the agent, which ··· 273 379 MATRIX_HOMESERVER = "https://matrix.lassul.us"; 274 380 MATRIX_USER_ID = "@hermes:lassul.us"; 275 381 MATRIX_ALLOWED_USERS = "@lassulus:lassul.us"; 382 + 383 + # The shared calendar (calendar skill above); CALDAV_PASSWORD comes from 384 + # hermes-env. 385 + CALDAV_USER = "hermes"; 386 + CALDAV_CALENDAR_URL = "https://cal.lassul.us/lass/calendar/"; 387 + 276 388 # Element makes DMs end-to-end encrypted by default, so the bot must do 277 389 # E2EE or it can't read messages. The `matrix` dep group already bundles 278 390 # mautrix[encryption] (python-olm); "optional" initializes E2EE when those ··· 438 550 ''; 439 551 }; 440 552 553 + # Hermes's radicale login (configs/radicale.nix). Shared with neoprism, where 554 + # radicale runs; a shared generator has to be declared identically on every 555 + # machine that uses it, hence the common definition. 556 + clan.core.vars.generators.radicale-hermes = 557 + import ../../configs/radicale-user-generator.nix pkgs "hermes" 558 + // { 559 + share = true; 560 + }; 561 + 441 562 # Assemble the .env Hermes reads (mirrors the opencrow-env pattern). 442 563 clan.core.vars.generators.hermes-env = { 443 564 dependencies = [ 444 565 "hermes-matrix" 445 566 "hermes-llama" 446 567 "hermes-api" 568 + "radicale-hermes" 447 569 ]; 448 570 files."hermes.env" = { }; 449 571 runtimeInputs = [ pkgs.coreutils ]; ··· 452 574 MATRIX_ACCESS_TOKEN=$(cat "$in"/hermes-matrix/matrix-access-token) 453 575 LLAMA_API_TOKEN=$(cat "$in"/hermes-llama/llama-api-token) 454 576 API_SERVER_KEY=$(cat "$in"/hermes-api/api_key) 577 + CALDAV_PASSWORD=$(cat "$in"/radicale-hermes/password) 455 578 EOF 456 579 ''; 457 580 };