A Feed for Tiny Tools: Following, Updating and Trusting AI-Built Web Apps A developer built tool_feed.py, a single Python script that turns any AI-built tools index page into a subscribable, change-tracked JSON Feed. The script detects the tool list by scoring parents on their largest group of similarly-signed linked children while skipping nav, header and footer, then fingerprints each tool page using conditional ETag/Last-Modified requests, noise stripping and SHA-256 hashing to distinguish listing changes from content updates. Updates are flagged needs_review when a page contacts a new external domain or changes size by more than 50%, addressing Matt Webb's concern about malicious updates to small web tools. AI coding assistants have made small, single-purpose web tools almost free to build. A JSON differ, an image resizer or a token counter now takes one prompt and a few minutes, so people like Simon Willison publish dozens of them on a single page. The trouble starts after publishing. In his April 2026 post Syndicating vibes https://interconnected.org/home/2026/04/29/syndicating-vibes , Matt Webb asks a set of plain questions nobody has answered yet: Blogs had this problem twenty years ago, and RSS fixed it. Tools have no equivalent. Matt sketches the answer as an RSS-style feed of tools, each item with an Install button. I wanted to see how much of that one person could build in a weekend. tool feed.py is a single Python script that turns any tools index page into a subscribable, change-tracked feed. You point it at a page, and it writes two files: feed.json , a standard JSON Feed anyone can subscribe to, and state.json , which remembers what it saw last time. Each run goes through five stages: python tool feed.py https://tools.simonwillison.net --title "Simon's tools" Run it daily on a schedule and the feed stays current. The rest of this post walks through how each stage works. The script finds the tool list by looking for the element with the largest group of similar children that each contain a link. No site-specific rules are needed for a typical tools page. It parses the page into a tree and gives every element a signature: its tag plus its sorted CSS classes, like li.tool . Then it walks the tree children-first, so each element already knows whether anything below it contains a link. For every parent, it groups the linked children by signature and scores the parent by its biggest group. Navigation menus are the main trap, because they are also lists of links. Anything inside nav , header or footer is skipped, so the real list wins even when the menu has more links. The whole pass touches each element once, so it costs O N in the size of the page and takes milliseconds. A tool counts as updated only when its own page changes in a meaningful way. The index page rarely changes when a tool is edited, so the script fetches every tool page and fingerprints it. Conditional requests first. Each fetch sends the ETag and Last-Modified values from the previous run. If the server answers 304 Not Modified , the tool is skipped without downloading anything. Most days, most tools cost one tiny request. Strip the noise. Pages contain things that change on every load: HTML comments, nonce attributes on scripts, embedded timestamps. Hashing those would report an update every single day. So the script removes them and collapses whitespace before going further. Hash what remains. The cleaned page goes through SHA-256. If the hash matches the stored one, nothing changed. If it differs, the tool gets a new version the first 12 characters of the hash and a changelog entry with the date. The result is two kinds of change: listing changes from the index a tool added or removed and content changes from each tool page a tool updated . An update gets flagged needs review when the page starts talking to a domain it never talked to before. That is the cheapest strong signal I could find for Matt's malicious-update worry. For every version, the script collects the set of external hosts the page touches: