Dependencies are managed with uv. Run everything from the repo root.
uv syncThis creates .venv/ with the Python version from .python-version (downloaded if needed) and the packages pinned in uv.lock. Add --group instagram if you want to run the Instagram poster.
./vilf buildor for automatically running modified files during development
ls | entr ./vilf buildpython3 -m http.server 8080 --directory buildOpen localhost:8080 (if you open 0.0.0.0:8080 then the map will not render).
uv run pytest# Requires roles/storage.objectAdmin on gs://vilf-org
gsutil -m rsync -R -d build gs://vilf-org# Requires roles/owner on projects/vilf-com
gcloud compute url-maps invalidate-cdn-cache vilf-lb --path /openssl s_client -showcerts -servername scripts.org -connect $(dig +short A vilf.org):443 -verify 99 -verify_return_errorWe use OpenTofu to deploy infrastructure as code primarily to GCP. Install the tool and running it in ./infra for more details. This will read the configuration from main.tf.json to determine what to administer. This file is generated from the configuration in default.nix, which requires the Nix package manager to interpret. Once installed and the development shell activated, run vilf tofu to manage further.
We use a Nix development shell to currently to manage infrastructure and autoformat code. It will likely accumulate more functionality too. After installing Nix: the package manager, you can enter the development shell by running nix develop. In this shell you can run pre-commit to run repository commit hooks (autoformatting, linting, etc.), as well as access repository tools through the vilf executable.
spatula, check and audit call the Google Places API. Put your key in a .env at the repo root (gitignored) or export it; no browser is needed:
echo 'GOOGLE_PLACES_API_KEY=...' > .env./vilf spatula
# Search Google Places (name and city, or a Google Maps URL): Lion Dance Cafe OaklandType a name and city, or paste a Google Maps URL. An unambiguous search goes straight through; otherwise up to five results are listed and you pick one (or 0 to search again):
# 0: Search again
# 1: Bongo Java at 2007 Belmont Blvd, Nashville
# 2: Bongo Java East at 107 S 11th St, Nashville
# Pick one [0-2]: 2./vilf spatula -s 'Lion Dance Cafe Oakland'skips the search prompt../vilf spatula --place-id ChIJ...skips the search entirely.- Phone and website are fetched too by default (one Enterprise-tier call per new review);
--no-detailsskips that. The website is only used as the Menu link's fallback when the review has no menu link.
It then asks for cuisine, area (defaults to the city, but use a neighborhood), drinks, taste, value and visited (defaults to today), writes places/<slug>.md with a <REVIEW> placeholder and prints what is still to do. Normally that is just "write the review and bold a dish":
./vilf spatula -s 'Lion Dance Cafe Oakland'
# Name = Lion Dance Cafe
# Address = 380 17th St
# City = Oakland
# State = CA
# Zip code = 94612
# Phone = +15105550199
# Website = https://example.com/lion-dance
# Status = OPERATIONAL
# Lat, lon = 37.806100, -122.268300
# Maps = https://maps.google.com/?cid=...
# cuisine: Chinese
# area (neighborhood, required) [Oakland]: Downtown Oakland
# drinks (serves alcohol) [y/n]: n
# taste: 0=DNR 1=SGFI 2=Good 3=Phenomenal
# taste: 3
# value: 0=Bad 1=Fine 2=Good 3=Phenomenal
# value: 2
# visited [2026-09-09]:
# To do before this file builds:
# - write the review (body is still the <REVIEW> placeholder) and bold at least one dish with **...**
# Wrote places/lion-dance-cafe.mdExtras:
--no-promptwrites the blank skeleton (emptycuisine,area,drinks, ...) for you to fill in by hand.--photo path-or-urlcopies (or downloads) the food photo toraw/food/<slug>.jpg, converting to JPEG if needed, and warns if it is wider than 16:9 (the build rejects that).- A place already in the output directory (same
place_id, or within 30 m) aborts with the existing filename;--forcewrites anyway and also overwrites an existing photo. Only the output directory is scanned. - Filenames are slugs of the name (
Lion Dance Café->lion-dance-cafe) with-0,-1, ... appended on collision.--street-in-filenameadds the street (useful for chains),--manual-filename path.mdsets it by hand,--directorychanges the output directory (default./places/). --city-as-areaprefillsareawith the city;--ask-firstconfirms before writing../vilf spatula --helplists everything.
Before committing new markdown files, compare them with what Google Places has:
./vilf check $(git diff --staged --name-only places/) # or just ./vilf check for every reviewFiles with a place_id are looked up directly; older files without one are matched by a text search on the name and address (the output says so). Name and address must match exactly, coordinates within 1e-4 degrees. Add --contact to also compare the phone number and show a website the file lacks. The command exits 1 on any mismatch or error, so it can gate a commit.
If everything looks as expected, you will see
./vilf check $(git diff --staged --name-only places/)
# Testing files:
# ✔ places/lion-dance-cafe.md
# ✔ places/maya-halal-taqueria.md
# All files look good.If anything is wrong, the metadata will be displayed:
./vilf check $(git diff --staged --name-only places/)
# Testing files:
# ✘ places/lion-dance-cafe.md
# ✔ places/maya-halal-taqueria.md
# The following files may need inspection:
# places/lion-dance-cafe.md
# Current address: 382 17th St | Determined address: 380 17th St
# Current latitude: 34.8060489 | Determined latitude: 37.8060489
# Current longitude: -120.267932 | Determined longitude: -122.267932./vilf auditChecks the Google business status of every place file that has a place_id (one Pro-tier call each; files without a place_id are only counted) and lists the ones that are permanently closed, temporarily closed, or have an unknown status. Informational: it always exits 0.
All of these need GOOGLE_PLACES_API_KEY in .env (see Setup). Every run of audit and check --fix appends a line to AUDIT_LOG.md, and audit prints when the last audit happened, so that file is the answer to "is it time to re-check everything?".
./vilf enrich # link reviews that lack a place_id (nearest Google match within 150 m); fills city
./vilf check --contact --fix places/*.md # pull Google's phone, website, coordinates and street into files with a place_id
./vilf audit --mark-closed # set closed: True on reviews Google marks permanently closedcheck --fix never changes a restaurant's name and keeps unit or suite details you recorded; audit --mark-closed only flags permanently closed places (their page stays online with a banner, out of the map and lists) and reports temporary closures. Two reviews are deliberately unlinked and will always show up as "without a place_id": fiji-airways (a joke entry) and boba-binge (the branch reviewed no longer exists on Maps).