parish-create-event
Add a new event (an "aktualita") to the new farakurim.cz site from a poster, invitation or announcement given as a PDF, PNG or JPG, or from the details alone - extract the event, confirm it with the u
SKILL.md.claude/skills/parish-create-event/SKILL.md
GitHubCreate an event
Read the source, extract one event, confirm it with the user, add it (with its files, if it has any, staged by
pnpm stage event), then finish with parish-publish-content.
The fields and how they are shown are specified in design/DESIGN.md §11.7 and §13.4.
1. Read the source
The source is usually a file: the user gives at least its name; without a folder, look in ~/Downloads/. Read it
with the Read tool (images render visually; for a PDF also run pdftotext -layout <file> - so no small print is
missed). The user may give several files of one event (a poster and a programme): read them all, they become its
attachments. If a file announces several unrelated events, ask which to add (or add each separately).
Without a file, warn the user before going on: the event gets no poster (the cards and the detail page show the placeholder) and no attachment, so ask whether they have a poster or invitation after all. If not, take the event from what they wrote, follow steps 3b and 5b instead of 3a and 5a.
2. Extract the event
Write the text from the source, in Czech; do not invent facts. Leave out what the source does not say.
| Field | How to fill it |
|---|---|
id |
ASCII kebab-case title and start year (hody-ceska-2026): the URL /aktuality/<id>/ and the files' prefix. |
title |
The headline, sentence case (not the poster's capitals). Required. |
start, end |
ISO dates. end only for multi-day events. A date without a year is the next such date from today. |
startTime |
"18:00" (H:MM). Omit for all-day events. |
endTime |
"20:30", when the source gives an end; only with startTime, and after it. |
place |
"Kuřim, kostel sv. Maří Magdalény", "Kuřim, fara", "Tišnov". Required. Add mapQuery when the place alone would not find it on Mapy.cz. |
text |
1–2 sentences for the cards. Required. No URLs. |
lead |
Optional: one inviting sentence under the detail title (also the page description). Defaults to text. |
body |
Optional HTML for "O akci" (<p>, <strong>, <ul><li>) when the source says more than text: a few short paragraphs, no links (links). |
program |
Rows { time, title, note? } when the source has a programme. |
links |
{ label, href } for every web address and e-mail (mailto:) in the source, with a readable label. |
price |
"300 Kč", "zdarma", when stated. |
registrationDeadline |
ISO date of "přihlášky do …". |
sessions |
Listed meeting dates of a series, in order from start to end: ["2026-10-11", …], or { date, startTime?, endTime?, end? } with its own time or days. |
longTerm |
{ weeklyAt: "18:30" } for a weekly series (weekday of start), true for other long-running events; needs end. |
highlights |
Only for real key facts or numbers (anniversaries); usually omit. |
tags |
Free tags when nothing above fits: [{ label: "Pro rodiny", color? }] ("orange", "magenta", "grey"; blue). Never "Zrušeno": change. |
pinned |
true only when the user asks for "Doporučujeme". The script moves the pin: it unpins the event pinned before and names it. |
published |
Leave out: the script sets today's date in Prague (the shared link's publication date). Keep it when editing a record later. |
3a. Pick the labels and the order (only with files)
Each file is attached under a label, shown on the page as written (with its accents). Prefer one of these; another short Czech label is fine when none fits
(Mapka trasy, Záznam koncertu):
| Label | When | Poster |
|---|---|---|
Plakát |
Poster or visual flyer | yes |
Pozvánka |
Invitation to a specific event | yes |
Leták |
Handout-style flyer | yes |
Program |
Schedule or order of proceedings | no |
Informace |
General information | no |
Oznámení |
Plain announcement without strong visuals | no |
The files are attached in the order you list them, and the first one is the one "Doporučujeme" and the archive
show. Only the first file can be the poster: list the poster first. It becomes the poster when it is an image or
PDF, unless you pass --no-poster: pass it when the first file has a "no" label or is another text-only document
(an MP3 never becomes one; it refuses any file but a PDF, PNG, JPG, WebP or MP3). Check the names before confirming (it refuses a taken name, a bad or taken id, a label
without letters or digits and two labels giving the same file name):
pnpm stage event <id> "<source>" "<label>" ["<source>" "<label>" …] [--no-poster] --check
3b. Check the record (only without a file)
Write the event to record.json as in step 5a and check it (its fields, a free id, and the Would unpin … line
for a pinned event):
pnpm add-event <scratchpad>/record.json --check
4. Confirm with the user
Show the extracted fields and, with files, each file with its label as written, in their order, and which one becomes
the poster in a short list (the script takes any label, so a typo like Plakat would go on the page). Point out guesses: the year, an
inferred end date, longTerm. For a pinned event, say which event loses "Doporučujeme" (the Would unpin … line of
the step 3a command run with --record, or of step 3b).
Wait for the user's yes or corrections to these before staging or adding anything.
5a. Stage the files and add the record (only with files)
Write the confirmed event as JSON to record.json in the session's scratchpad (never in the repo): the fields
from step 2, without published, poster and attachments. A poster.alt you write (e.g. with the date) is
kept; otherwise it is "<label of the first file>: <title>".
{
"id": "hody-ceska-2026",
"title": "Hody v České",
"start": "2026-10-02",
"end": "2026-10-04",
"place": "Česká",
"text": "Srdečně zveme na tradiční hody v České – stavění máje, průvod a koncert."
}
Then run the step 3a command without --check, with the record:
pnpm stage event <id> "<source>" "<label>" ["<source>" "<label>" …] [--no-poster] --record <scratchpad>/record.json
5b. Add the record (only without a file)
Update record.json from step 3b with the confirmed event, then add it:
pnpm add-event <scratchpad>/record.json
The output of step 5a or 5b means (5b stages nothing):
- An error (a bad field, a taken
id, a pin on an event that has already ended): nothing was staged. Fixrecord.jsonand run the same command again. - The
Události …lines (the event in the Google Calendar the Kalendář reads): unless they saylinked, show them to the user as printed; they say what to do.
6. Publish
Follow parish-publish-content (commit, upload and push). Until the calendar check in step 5 says
"linked", remind the user of what it asked for, so the event shows in the Kalendář with its link.
Unattended run
Only when the prompt says the run is unattended (the events workflow gives a mailed event, a known-events file and a report file); a local run follows steps 1 to 6 as written. An unattended run follows them too, with these changes:
- The source is the prompt's mail folder:
subject.txt,body.txt(the mail's new text) andattachments/. One mail may hold several events. Group the attachments into events, each attachment in exactly one group or dropped: the body says which belongs where, else what the files show (the same title, date or place), else their names. One attachment is one event; unrelated attachments are one event each; related ones are one event. In each group the visual one (Plakát/Pozvánka) is the poster, first; the others are its attachments with the labels of step 3a. Drop an attachment no event can be made from (a signature, a logo, a file without event information), but keep one that belongs to an event (a programme, a form, a map). An unclear grouping is a blocking question showing your best grouping. - Reading a PDF: run
pdftotext -layout <file> -first andReadonly its first page (pages: "1"), to see the poster; read further pages only when the text is empty or garbled (a scan). Each page read is an image. - Title: with one event, the subject, corrected (sentence case, typos). With several events, each from its own poster; the subject may confirm or correct the one it clearly names. Text: from the body, corrected, formatted and filled in from the attachments; never invent a fact. Without attachments, the event is the subject (its title) and the body (its text), with no poster and no warning (step 1's question is not asked).
- Pin:
pinnedonly when the mail asks for "Doporučujeme" in so many words. - Refuse corrections. A subject or body saying an event changes is a correction, whatever the form, with or
without diacritics:
oprav-(oprava, opraveno),změn-/mění(změna, změněno, mění se),upřesn-,aktualiz-, "nový termín/čas",posun-,přesun-/přesouvá,odlož-/odkládá,zruš-/ruší(zrušeno, zrušena),nekoná,odpadá. Decide which event the word refers to (with one event, a word anywhere applies to it); that event is refused, the others go ahead. - Refuse duplicates. Compare each event with the known-events file (
where:mainor an open PR's number): the same event under any title (same date, time and place, or the same poster) is refused. An uncertain match is a blocking question, not a refusal. A different event whose id is taken gets the date id the refusal gives. - It never asks and never waits: every "confirm with the user" or "ask the user" step writes into the report
instead. The scratchpad is the prompt's (
record.json; write nothing in the repo); the files are staged inuploads/as usual (step 5a or 5b, one event after another). Skip the--checkruns of steps 3a and 3b: step 5a or 5b checks everything first and stages nothing when it refuses, so fixrecord.jsonand run it again. - Write the report first, then stage, then fill in each event's
Outcome:line from what the script did, and add any line it printed for the user (theUdálostilines, an unpinned event) to the report. A script refusal isrefused: <its message>. - Skip step 6: no
parish-publish-content, no commit, no release. The workflow commits and opens the PR.
The report is English, with site content quoted in Czech, and starts with exactly these two lines:
blocking: yes|no
commit: <message>
blocking: yes when any question is blocking (its answer changes what is published). commit: is the PR's title
and the squash commit on main: feat(content): add the <title> event for one event created,
feat(content): add <n> events for several.
Then, per event, in the mail's order (a mail with no event in it, such as a thank-you with a logo, has no event section: the workflow refuses it):
## <title>(as published);- one line
Outcome: createdorOutcome: refused: <reason>(a correction:refused: a correction of <id>; make it with parish-correct-event; a duplicate:refused: already in <main or PR #n> as <id>); - for a created event, only what the PR's diff does not show: where the title came from (the subject, the poster) and the guesses (step 4), not the fields or the files;
### Questions, one per line:- **q1** (blocking|note) <where>: <question> — proposal: <proposal>.
Then ## Dropped attachments (each as "attachment 3, an image without event information": never its file name) and
## Mirror in the calendar (the Události lines as printed). The PR is public: quote only what is published (the
fields, the staged files' names and labels); describe the rest of the mail without quoting it.
Common mistakes
- Copying the poster's capitals into
title, or its whole text intobody: keeptextshort andbodybrief. - Putting URLs or e-mails into
text/bodyinstead oflinks. - Guessing a year or end date silently: say it in the confirmation.
- Going on without a file and without the warning in step 1: the user may have a poster to add.
- Staging (without
--check) before the user confirms: an abandoned file would go out with the next release. - Adding a new event by hand instead of
--record(orpnpm add-event): the order andpublishedare then up to you. - Editing
pinnedby hand: a pinned--recordmoves the pin itself. - Listing the poster after another file: only the first file becomes the poster (afterwards, only
pnpm event-posterchanges it). - Forgetting
--no-posterwhen the first file is a text-only PDF (Program,Informace,Oznámení): its first page becomes the poster.