ReadyForPickup — a macOS app that watches Apple Store pickup availability for iPhone and Mac and alerts you when your configuration is ready nearby.
Download ReadyForPickup.dmg (always the latest version), open it, and drag ReadyForPickup into Applications. Earlier versions and release notes are on the Releases page. Or use the project site: https://wehbs.github.io/ReadyForPickup/
A native SwiftUI companion for iPhone and iPad is available in ios/. It supports iOS 17 or later and builds locally in Xcode. See the iOS guide for setup, verification flows, and the limitations of background inventory checks.
This is a modernized fork of worthbak's InventoryWatch (thanks, Worth!). Since the 2023 upstream release it has been updated for macOS 13+, Apple's current pickup API, the current iPhone and Mac lineup in 15 countries, a 2.0-style three-pane interface with multiple saved searches, auto-refresh with throttle backoff, and Pushover phone notifications (bring your own Pushover app token, entered in Settings). It was renamed from InventoryWatch to ReadyForPickup in October 2026 and relaunched as a new app (bundle ID com.wehbs.ReadyForPickup), so nothing carries over from InventoryWatch: saved searches, preferences and Pushover keys start fresh.
Licensed under GPL-3.0 — see LICENSE.md.
Project site: https://wehbs.github.io/ReadyForPickup/
Each search watches one country's stores. Pick the country in the search's inspector, above the store picker: changing it picks a store there and keeps the search's preferred models and custom SKU only if that country's model list has them too (most part numbers differ by country). Settings → General → Default country sets where new searches start.
Supported: the United States, Australia, Austria, Canada, China mainland, France, Germany, India, Italy, Japan, Malaysia, South Korea, Spain, the United Arab Emirates and the United Kingdom. Every model watched in a country was found in that country's own pickup API. Outside the US, iPhone names are Apple's local ones (e.g. Japanese or French); Mac names are the US spec names. Not supported: Hong Kong (its pickup API rejects the store locations the app sends), Macao (no online store), and Apple's other store countries (Belgium, Brazil, Mexico, the Netherlands, Singapore, Sweden, Switzerland, Taiwan, Thailand and Türkiye) until their models are verified.
The app bundles two data files, each rebuilt by a script (Python 3.9+, standard library only; --dry-run prints the changes without writing):
ReadyForPickup/Stores_LocalBootstrap.json: every Apple Store, by country.Scripts/update-store-list.pyrebuilds it from apple.com/retail/storelist in one request.ReadyForPickup/ProductCatalog.json: the countries the app offers and the models it watches in each (part number and name).Scripts/update-catalog.pyrebuilds every country but the US and checks each part number against that country's pickup API. Apple's pickup API blocks a network for up to half an hour after roughly 40-60 calls from the script, or about 31 in an hour and a half from the app (seeScripts/probe-throttle.py), so turn the app's auto-refresh off and refresh a few countries per run, about an hour apart, all with one new cache directory for that refresh:Scripts/update-catalog.py --only GB,JP,FR,DE --cache-dir ~/rfp-cache/2026-10-19. Saved responses never expire, so a cache directory from an earlier refresh replays Apple's data from then. A country already in the file that can't be rebuilt keeps its current entry and the script exits with status 1 (--drop-failedremoves it instead). The US entry is edited by hand: the script checks it against Apple's live US pages and pickup API and reports drift (new or missing SKUs) but never rewrites it, and the other countries' Macs are derived from it.
Run update-store-list.py before every release (stores open and close), and before update-catalog.py, which checks models at real stores. Run update-catalog.py when Apple's lineup changes (new iPhones each September, new Macs) after updating the US entry, or to add a country (see COUNTRIES in the script). A new product line also needs a ProductType case and an entry in the script's page tables. After editing either file by hand, run Scripts/check-bundled-data.py: the app reads each file as a whole, so one bad value would drop every country or store.
The app icon, the project site's logo (docs/logo.png) and its favicon (docs/favicon.png) are drawn in code by Scripts/make-app-icon.swift: a white paper shopping bag with grey rope handles and a green check, on a blue macOS icon tile. It uses neither Apple's logo nor SF Symbols, which may not appear in app icons. To change the icon, edit the script and run swift Scripts/make-app-icon.swift; it rewrites every size in ReadyForPickup/Assets.xcassets/AppIcon.appiconset, the site logo, the favicon and the iOS app's icon (ios/ReadyForPickup/Assets.xcassets/AppIcon.appiconset/AppIcon.png).
The site's link-preview image (docs/social-preview.png, the picture X and other apps show for a link to the site) is drawn by Scripts/make-social-preview.swift from the logo and the app screenshot (docs/screenshot.png, also shown on the site and above). Run swift Scripts/make-social-preview.swift after changing either.
ReadyForPickup uses Sparkle 2.10.0 (MIT — see Sparkle-LICENSE.txt) to check for updates automatically (daily, on by default; toggle in Settings → Updates, or use ReadyForPickup → Check for Updates…).
- Update feed:
https://raw.githubusercontent.com/wehbs/ReadyForPickup/main/appcast.xml— a file in this repo (onmain), chosen so releases are version-controlled, reviewable, and don't depend on a separate hosting service. The DMG enclosures it points to are GitHub Release assets. - Signing key: updates are EdDSA-signed. The private key lives only in the maintainer's Mac login Keychain (created by Sparkle's
generate_keys; servicehttps://sparkle-project.org, accounted25519) — it is never in this repo. Only the public key (SUPublicEDKey) is embedded in the app'sInfo.plist.
First refresh the bundled data (see Store list and product catalog): run Scripts/update-store-list.py, plus Scripts/update-catalog.py if Apple's lineup changed, and commit any changes. Read the changes each script prints before committing: a closed store or a removed country shows up there.
Then run Scripts/build-notarized-release.sh (see its header). It checks the bundled data (Scripts/check-bundled-data.py), builds, signs Sparkle's nested code then the app, makes the DMG, notarizes (notarytool submit --wait — this can take hours; the submission ID is printed — if running detached, poll xcrun notarytool info <id> --key … --key-id … --issuer … every ~2 min, and on Accepted staple/validate and check spctl on the mounted app), staples, then generates/signs appcast.xml with Sparkle's generate_appcast (fails safe if the Sparkle tools are missing). Then, in order:
- Commit + push the version bump: both
MARKETING_VERSIONandCURRENT_PROJECT_VERSION(Sparkle compares the latter). Don't commitappcast.xmlyet. gh release create v<VERSION> /tmp/readyforpickup-release/ReadyForPickup-<VERSION>.dmg /tmp/readyforpickup-release/ReadyForPickup.dmg --repo wehbs/ReadyForPickup --target <bump commit SHA> --title "ReadyForPickup <VERSION>" --notes "…"— upload both files: Sparkle's feed points at the versioned DMG, and the site and the README link to the version-freeReadyForPickup.dmgthroughreleases/latest/download/ReadyForPickup.dmg, which always serves the latest release's copy.- Verify: the release asset downloads (HTTP 200), its SHA-256 matches the local DMG, and the mounted app passes
spctl -a -vvv(accepted source=Notarized Developer ID). Note the DMG itself may showspctl"no usable signature" (it isn't codesigned) — the app inside is what matters. - Commit + push the generated
appcast.xml. This is what makes the update live, so it goes last: pushed earlier, the feed would point at a DMG that doesn't exist yet. - Verify the live feed (
https://raw.githubusercontent.com/wehbs/ReadyForPickup/main/appcast.xml, cached for up to 5 minutes) lists the new version with the right enclosure URL and signature.
