lcbo-cli
Installable Python library, Pydantic models, FastAPI facade, and CLI for read-only LCBO product search, prices, and store inventory.
Unofficial and not affiliated with LCBO. Stock quantities are snapshots, not reservations.
License
The project code is licensed under the MIT License. This does not grant rights to LCBO data, trademarks, or other third-party content, which remain subject to their respective terms.
Run with uvx
With uv installed, run directly from GitHub without cloning or setting up an environment:
uvx --from git+https://github.com/mariomeyer/lcbo-cli lcbo search guinness --limit 5
uvx --from git+https://github.com/mariomeyer/lcbo-cli lcbo availability 33989
uvx --from git+https://github.com/mariomeyer/lcbo-cli lcbo nearby 33989 --location "Toronto, ON"
uvx --from git+https://github.com/mariomeyer/lcbo-cli lcbo nearby 33989 --location "M5V 3L9"
uvx --from git+https://github.com/mariomeyer/lcbo-cli lcbo search guinness --json
Python 3.11+ is required; uv can manage the interpreter. Until the first PyPI release, use the GitHub commands above. The PyPI distribution name is lcbo, the Python import is lcbo_cli, and the executable is lcbo.
Once published to PyPI, the short command will be:
uvx lcbo search guinness --limit 5
CI tests Python 3.11–3.14 and validates wheel/source builds. Releases use Conventional Commits and Python Semantic Release; PyPI publishing uses a separate Trusted Publishing job. See the release setup guide for the required one-time configuration.
For a persistent command:
uv tool install git+https://github.com/mariomeyer/lcbo-cli
lcbo search guinness
See the CLI, library, and API reference and development guide.
Console output
Actual CLI output from public product examples, rendered as SVG images. Prices and availability are snapshots, not guarantees.
Regenerate these images with uv run python scripts/readme_screenshots.py. This makes live public LCBO requests; no location lookup is used.
Run from a checkout
uv sync --extra dev
uv run lcbo search guinness --limit 3
uv run lcbo product guinness-0-33989
uv run lcbo availability 33989
uv run lcbo nearby 33989 --latitude 43.65 --longitude -79.38
uv run lcbo nearby 33989 --location "M5V 3L9"
uv run lcbo nearby 33989 --location "Kitchener, Ontario"
uv run lcbo serve
The local server listens on 127.0.0.1:8000; interactive API documentation is at /docs. Routes: /search?q=guinness, /products/{slug}, /availability/{sku}, /nearby/{sku}?latitude=...&longitude=..., /stores, and /discover.
CLI results use console tables by default, with CAD prices and distances formatted for reading. Add --json before or after the command for machine-readable output, for example uv run lcbo search guinness --limit 3 --json. JSON retains the existing response structure. FastAPI responses remain JSON.
Product and store names use plain text. Search, discovery, product details, availability, and nearby tables have a final Product link column with a clickable Open label. These use OSC 8 hyperlinks in supporting terminals; the terminal controls click gestures and may underline only the Open label. Inventory tables use the product URL observed on the inventory page, not the store URL. Redirected output shows the label without escape sequences; JSON retains full URLs and its existing structure.
nearby ranks all stores shown in the product inventory HTML using public coordinates from store detail pages and straight-line distance. It makes one detail request per store with four concurrent requests. --candidates N limits these requests and therefore coverage. Results explicitly report coverage. /stores currently returns only the first directory page, while product availability uses all observed inventory table rows. discover filters homepage features and is not catalog search. Prices are CAD decimals; quantities reflect the page snapshot, not reservations.
City/address/postal-code lookup works automatically with --location using Photon, without an API key, configuration, or extra consent flag. Locations are sent to https://photon.komoot.io/api/, filtered to Canada, and the first match is used. The result identifies the matched location and includes OpenStreetMap attribution. City/postal-code coordinates are approximate; distances are straight-line, not driving distances. The API equivalent is location=Kitchener, Ontario, and the library defaults to allowing geocoding. Geocoding inputs/responses are excluded from HAR recordings, and repeated lookups are cached for the lifetime of a client. Photon permits modest use but does not guarantee availability; see its service guidance and API documentation. For an alternative provider, LCBO_GEOCODER_URL still overrides the default with a chosen HTTPS Nominatim-compatible endpoint. The legacy --allow-geocoding flag remains accepted but is unnecessary.
Postal-code queries require an exact normalized match; fuzzy matches to different codes are rejected. If Photon lacks the complete code, the tool estimates the location from postcode points sharing the requested first three characters, labels it as a postal-area estimate, and states that the exact code was not found. This is an average of returned points, not an official postal-area centroid. If no matching area is available, lookup fails with a suggestion to use city/province. Custom geocoders must return the exact postcode and do not use this fallback. Documentation examples use public locations; test fixtures are synthetic and do not record users' locations or requests.
from lcbo_cli import LCBOClient
client = LCBOClient()
results = client.search("guinness", limit=3)
product = client.product("guinness-0-33989")
stock = client.availability(product.sku)
Capture and provenance
The adapter was derived from public LCBO HTML and its linked Coveo SDK, inspected starting 2026-09-29. Search uses the public query POST shown in LCBO's homepage code. Public search configuration is fetched per call and is not persisted. Product prices and inventory are parsed from actual server-rendered pages. The included evidence is HTTP-client traffic, not a browser HAR or an official LCBO REST API specification.
evidence/lcbo-http.har is a real HTTP-client HAR 1.2 capture, not browser traffic. Its seven records cover the homepage, Coveo search, product details, inventory, and two store-detail requests. HTML bodies are omitted because they embed session/configuration keys. Request headers, cookies and bodies are omitted. The JSON payload retains public product data. Generic field redaction is not a guarantee that arbitrary imported HAR files are free of personal data; review imports before sharing.
uv run lcbo --har captures/search.har search guinness
uv run lcbo import-har evidence/lcbo-http.har -o captures/search.json
uv run lcbo endpoints captures/search.json
uv run lcbo get captures/search.json e1
uv run lcbo serve captures/search.json
The importer accepts public LCBO GET JSON and the observed Coveo search POST JSON, including base64 responses. POSTs are offline-only; no authentication, cookies, or POST request body is retained. Live capture replay is limited to observed read-only page route families and explicitly enabled with get --live; HTML endpoints normally use the typed client. Other methods, third-party origins and non-JSON responses are skipped. HAR is an HTTP archive; HAL is a hypermedia JSON format and is not interchangeable.
--har must precede the command. Each CLI invocation starts a new recording and replaces its destination file. The recorder creates parent directories; the importer requires the output directory to exist. endpoints, get, and the capture argument to serve expect imported JSON, not a raw HAR.
For a true browser HAR, use Chrome DevTools → Network → Preserve log, browse/search/check inventory, and Export HAR (sanitized). Importing browser capture is supported independently of Computer Use runtime availability.
Verification
uv sync --extra dev
uv run pytest
uv build
Builds produce a wheel and source distribution under dist/. See CONTRIBUTING.md for architecture and maintenance notes. Website markup and third-party services can change; the API is intended for local use and has no authentication. Purchasing, checkout, account actions, and anti-bot bypass are outside the project's scope.
Tests use the real sanitized search capture, observed product/inventory fragments, and mock HTTP for invalid URLs, mutation replay rejection, redirects, duplicate inventory rows, parsing failures and distance ranking. Live public search returned 20 Guinness results; Guinness 0 SKU 33989 was CAD 11.95. Live availability returned store quantities successfully. No purchasing, checkout, account actions, or anti-bot bypass is implemented.
Full proximity ranking was also verified live for SKU 33989: 263 of 263 observed inventory stores were ranked from the public King West & Victoria store coordinates in Kitchener. The closest results were King West & Victoria (0 km), Highland & Westmount (1.86 km), and Victoria & Edna (1.95 km). Geocoding is covered by mock tests for default Photon lookup, Canadian filtering, coordinate order, caching, empty matches, and custom-provider overrides.
Metadata
Release files for lcbo 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lcbo-0.1.0.tar.gz | 52.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lcbo-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 72.3 kB
Release files / lcbo-0.1.0.tar.gz
| Download URL | lcbo-0.1.0.tar.gz |
|---|---|
| Size | 52.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cc7e46eef7152949ab824f72bf8f8e097d0d8911a1cf32653afda260e8ca92b8
|
|
BLAKE2b-256 checksum How to use checksums |
82d35a0819f35fdb3fecebfee24e99e83cd4b076cf0337c9a09771b8ade9b5bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logRelease files / lcbo-0.1.0-py3-none-any.whl
| Download URL | lcbo-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c469c88aff2f8dd20256b5c56350fa48d170b8aae6775b5b1ce45d707632d1c9
|
|
BLAKE2b-256 checksum How to use checksums |
a69804456b7fcf0769a67c28d29fff2c431ccba804dad3df519c36333b4a1917
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency log