How it fits together
Two maps hold the watch list and the last text seen for each page; each
entry is small and keyed by watch, so a map fits better than a volume. The
OPENAI_API_KEY and SLACK_WEBHOOK_URL secrets reach only check-page.
Between sweeps nothing runs, and the model call happens only when a page’s
text changed.
Run it
1
Download
The CLI installs once with Playwright, trafilatura and the OpenAI client sit in the
uv tool install lazycloud-client. The commands
below download a standalone project and install its dependencies; no
repository checkout is needed. The guide then runs from that directory through
uv run, which uses the project’s pinned SDK.check
dependency group, which only the remote image installs.2
Configure
The script asks for an OpenAI API key and a Slack
incoming webhook URL and
stores both as secrets.
3
Deploy
watches URL. The browser image takes longer the first time because it
installs Chromium.4
Use
Requests to the API need an access token
for the workspace. With The first sweep stores a baseline for a new page. To check now instead
of waiting for the next quarter hour:The result lists each page’s outcome, one of
LAZYCLOUD_TOKEN set and the printed URL in
place of <watches-url>:baseline, unchanged,
minor_change and alerted, and the pages that failed.
uv run lazycloud task list --app site_monitor shows each page’s logs
and retries.A schedule that fans out with .map
A schedule is a function with cron; .map yields
None for a page whose retries ran out, and a sweep still running at the
next quarter hour delays that run rather than overlapping it.
site_monitor/app.py
A browser function with an autoscaler
The autoscaler adds a container per four queued pages, up to five, so at most 20 browsers run at once, andmax_pending_tasks leaves room for a retried sweep that submits every page
again.
site_monitor/app.py
Maps for watches and snapshots
A map key expires after seven days unless set withttl=0, so watches get ttl=0 and snapshots keep the default, which
lets a snapshot left by a removed watch expire on its own.
site_monitor/api.py
One alert per run across retries
A retried call reruns from the top, so the snapshot carries the run’s ID; a retry in the same run finds its own snapshot, resends an alert that failed to deliver and skips one already sent.site_monitor/checks.py
An ASGI app behind platform auth
An ASGI app is authorized by default, so a plain FastAPI app bound to a module attribute needs no auth code of its own.site_monitor/app.py
Adapt it
- A
selectorsuch as#pricing-tablewatches one element, andfocustells the judge what counts, for example “new job postings in Europe”. cronsets the interval;"0 * * * *"checks hourly and cuts the cost fourfold.MODELinjudge.pypicks the OpenAI model.notifycan be any function that takes a watch and a summary, such as an email or a ticket in your issue tracker.
Clean up
deployment stop sweep pauses only the schedule and keeps the API and the
stored pages.