RTX 5090 monitoring and auto-checkout tool for personal use on Windows 11.
This is an agent-safety study: an autonomous agent that can spend money, engineered so that it provably will not misbehave. The point is the constraint architecture, not the GPU.
- Config-proof hard caps. Quantity 1, plus per-transaction and 24-hour spend ceilings, enforced as pure functions. They cannot be raised by configuration. Raising them requires a source change a reviewer would see.
- An unforgeable confirmation gate. The one irreversible action (place-order) is reachable only through an HMAC capability token whose constructor is module-private, so no adapter can forge one.
- A security test suite (
tests/security/) whose design assumption is that every bypass must require a reviewable source change. Twelve invariants, each mapped to where it is enforced (table below). - No anti-bot evasion. It does not bypass CAPTCHAs or 2FA, and uses no stealth, proxy, or fingerprint-spoofing. On a challenge it stops and asks the human.
Honest status: detection ran for about 10 days in dry-run. The money-spending methods (add_to_cart, place_order) are deliberately left as NotImplementedError stubs marked TODO: USER in every adapter, so the agent never actually purchased anything. The full safety apparatus is real and tested against a fake adapter. The stub is deliberate, and the safety architecture is the deliverable.
This tool is provided for one person's legitimate, personal-use purchase of a single graphics card. Using it for scalping or resale is not permitted and is actively prevented by the safety invariants below.
- Personal use only. This automates your logged-in browser session against your saved card and shipping address to buy one GPU you want to own.
- Hard caps are enforced in code:
- Quantity per order: exactly
1. - Per-transaction price cap:
$2,800USD. - Per-24-hour rolling spend cap:
$3,000USD. - Any listing exceeding these caps is rejected pre-click.
- Quantity per order: exactly
- Vendor Terms of Service. Automated ordering may violate retailer ToS. You accept the risk of order cancellation, account suspension, or permanent ban. This tool does not attempt to evade anti-bot protection and does not use stealth / fingerprint-spoofing / UA-rotation / proxy infrastructure.
- No resale. Don't use this to stockpile cards. The one-unit / spend-cap combo makes that hard by design; please don't try to route around it.
- Taxes are your problem. Report any income from the ultimate use of what you buy.
- You are responsible for everything this tool does while it runs as you. Read the safety invariants below and make sure you understand them before enabling
--no-dry-run. - CAPTCHAs are not bypassed. When a retailer shows a CAPTCHA or 2FA challenge, the tool stops, notifies you, and asks you to complete it manually. It does not attempt to solve them, forward them to a solver service, or otherwise evade them.
If any of the above makes you uncomfortable, do not use this tool.
- Windows 11 (tested on 11 Pro, build 26200+).
- Python 3.12+ with uv installed.
- Chromium via Playwright (
uv run playwright install chromium). - A Telegram bot account (for interactive CONFIRM / CANCEL prompts) or an SMTP-capable email account (fallback).
- A vendor account at each retailer you enable, with:
- A saved payment method (the card whose last-4 matches
[payment]). - A saved shipping address (matching
[ship_to]). - "Remember me" / session-persistence enabled.
- A saved payment method (the card whose last-4 matches
The tool runs as a logged-in foreground user. It cannot run under LocalService or unattended on a locked machine (Playwright needs an interactive session).
git clone <this-repo> gpuwatch
cd gpuwatch
uv sync --all-extras
uv run playwright install chromiumWalk through the secret-setup prompt. Secrets are stored in the Windows Credential Manager via keyring, not in any file:
uv run gpuwatch setup-secretsYou'll be prompted for:
- Telegram bot token + chat ID (optional — skip to use email-only).
- SMTP host, port, username, password, from-address, to-address (the fallback channel; strongly recommended).
- SQLCipher database passphrase (can be any non-empty string — it encrypts
data/gpuwatch.db).
Sanity-check everything is wired:
uv run gpuwatch test-notifyExpected output:
telegram: OK
email: OK
If either side reports FAIL/unconfigured, re-run setup-secrets for that channel.
Non-secret configuration lives at ./config.toml (project root). The defaults ship safe: dry_run_default = true, all vendors enabled = false.
Required edits before anything useful happens:
[general]
timezone = "America/Los_Angeles"
dry_run_default = true # stays here; CLI flag overrides
[ship_to]
name = "Firstname Lastname"
street_number = "1234" # just the number; used for equality check
zip = "94107"
[payment]
card_last4 = "4242"
card_exp_month = 4
card_exp_year = 2028
card_brand = "visa" # visa / mastercard / amex / discover
[scheduler]
min_poll_seconds = 45
max_poll_seconds = 180
absolute_min_seconds = 30 # hard floor — do not lower
[notifications]
primary = "telegram"
fallback = "email"
[observability]
prometheus_enabled = false
prometheus_port = 9090
daily_summary_hour = 9
[vendors.bestbuy]
enabled = true
full_auto_after_first = false # stays false until you explicitly flip it
product_urls = [
"https://www.bestbuy.com/site/nvidia-geforce-rtx-5090-founders-edition/XXXXXXX.p",
]
# ... repeat for each vendor you want to watchEvery vendor block takes enabled, full_auto_after_first, and product_urls (a list). Other per-vendor knobs may be added in future phases. Leave a vendor disabled if you don't have a working first-party login for it.
Before gpuwatch run can cart anything for a given vendor, you must log in once by hand so the persistent browser context has a session cookie:
- Launch a headful browser session pointing at the vendor's home page using the gpuwatch-managed profile directory (under
sessions/<vendor>/). A helper will be added in a follow-up phase; for now:uv run python -c "from playwright.sync_api import sync_playwright; p=sync_playwright().start(); b=p.chromium.launch_persistent_context(user_data_dir='sessions/bestbuy', headless=False); b.new_page().goto('https://www.bestbuy.com/'); input('press enter after you have logged in and saved your card...')" - Log in with your real account.
- Add your payment card to "saved cards" and confirm "set as default".
- Add / confirm your shipping address.
- Tick "Remember me" / "Keep me signed in".
- Close the browser (or hit enter in the terminal helper above).
Repeat for each vendor you enable. The cookies / local storage live in sessions/<vendor>/ and are required for the cart flow.
Default mode is dry run. This exercises the entire pipeline — detection, condition verification, matching, notification, confirmation gate, pre-click verification, screenshot capture — but stops just short of clicking "Place order". Instead it records an abandoned purchase attempt with failure_reason = "dry-run".
uv run gpuwatch run # dry-run
uv run gpuwatch run --dry-run # explicit dry-runExercise the notification pipeline without any live vendor traffic:
uv run gpuwatch simulate-instock --vendor bestbuyThis uses tests/fixtures/bestbuy/in_stock_5090_fe.html (or the fixture you pass via --fixture PATH) to synthesize an in-stock event, then runs it through the confirmation gate. Expect a Telegram / email with CONFIRM / CANCEL buttons.
To actually spend money you must pass both flags:
uv run gpuwatch run --no-dry-run --i-understand-this-will-spend-real-moneyPassing only --no-dry-run exits with code 2 and a reminder. This is intentional. Do not alias it away.
Recommended progression:
- Run for 24–48h in default dry-run mode. Watch the logs + daily summary under
logs/daily-YYYY-MM-DD.md. simulate-instockonce per enabled vendor. Confirm you receive the CONFIRM/CANCEL prompt and the confirmation coordinator reacts correctly.- Once you trust it, flip
--no-dry-run --i-understand-this-will-spend-real-money. - The first purchase per vendor always pauses for human confirmation regardless of
full_auto_after_first. Only after a successful first order with that vendor (and you manually flippingfull_auto_after_first = truein config) will subsequent checkouts skip the human gate.
A template is included at deploy/gpuwatch.xml. It fires on boot (30s delay) and user logon, runs as the interactive user at least-privilege, and restarts on failure up to 3x per minute.
Import it via:
schtasks /Create /TN "gpuwatch" /XML "C:\path\to\gpuwatch\deploy\gpuwatch.xml"Edit the <Command> and <WorkingDirectory> paths first if your install lives elsewhere. The task XML intentionally does not pass --no-dry-run; add that only once you trust the setup.
Every vendor is a small module under src/gpuwatch/vendors/<name>/:
__init__.py— empty.selectors.yaml— five keys:title,price,availability,sold_by,breadcrumb. CSS selectors, one per key.adapter.py— subclass ofVendorAdapter:nameclass attribute (string slug).check_stock()(Phase 2 pattern: fetch each URL, parse viaparse_product_page, returnCheckResult).add_to_cart,proceed_to_review,place_order,watch_order— start asNotImplementedErrorwithTODO: USERmarkers; fill in once you have a working manual login.
- Fixtures under
tests/fixtures/<name>/:in_stock.html+out_of_stock.htmlminimum. Addopen_box.html/refurbished.htmletc. as the retailer has them.
- Unit test at
tests/unit/vendors/test_<name>.py— copy an existing vendor's test file and swap the paths.
Finally, add your vendor to register_all_adapters() in src/gpuwatch/vendors/registry.py and add a [vendors.<name>] block to config.toml.
The Micro Center adapter (src/gpuwatch/vendors/microcenter/) is the simplest complete example.
These are enforced in code and verified by the tests/security/ suite. Attempting to bypass any of them is considered a bug.
| # | Invariant | Where enforced |
|---|---|---|
| 1 | Quantity per order is exactly 1 | core.pre_click_verifier + core.matcher |
| 2 | Per-transaction price cap: $2,800 | core.pre_click_verifier |
| 3 | Per-24h rolling spend: $3,000 | core.pre_click_verifier (DB sum) |
| 4 | Only NEW, first-party listings accepted |
core.condition_verifier |
| 5 | Marketplace / warehouse / renewed / open-box rejected | core.condition_verifier (deny substrings + allowlist) |
| 6 | Card last-4 + brand + expiry must match [payment] before place-order |
core.pre_click_verifier |
| 7 | Shipping name + street number + ZIP must match [ship_to] |
core.pre_click_verifier |
| 8 | Confirmation token (CONFIRM/CANCEL) required on first purchase per vendor | core.confirmation_coordinator |
| 9 | --no-dry-run requires --i-understand-this-will-spend-real-money |
cli.run |
| 10 | Per-attempt screenshot captured before and after place-order click | core.screenshot |
| 11 | UNKNOWN-state attempts cannot auto-resume; must be cleared via reset-hold |
core.attempt_state_machine + cli.reset-hold |
| 12 | Secrets never on disk or in env vars — keyring only | gpuwatch.secrets |
CAPTCHA appears mid-checkout. The tool detects cart/checkout pages that look like a CAPTCHA challenge, notifies you via the primary channel, and halts that attempt with failure_reason = "captcha". Complete the challenge by hand in the paused browser window, then gpuwatch reset-hold <attempt_id> to clear the hold.
Order stuck in UNKNOWN. Happens when the place-order click was submitted but the response parse failed (network hiccup, unexpected DOM). The attempt stays in UNKNOWN forever until a human decides. Use:
uv run gpuwatch reset-hold <attempt_id> --confirm-no-duplicateOnly pass --confirm-no-duplicate after logging in and visually verifying no order was actually placed.
Notifications aren't arriving. Run uv run gpuwatch test-notify. If Telegram fails, verify your bot token and chat ID (chat ID is the numeric id of the direct-message chat with the bot, not your username). If email fails, most SMTP providers require an app-specific password; Gmail needs an "App Password" under 2FA.
Prometheus metrics. Set [observability] prometheus_enabled = true + prometheus_port = 9090 in config.toml. Scrape http://localhost:9090/metrics from your Prometheus server. Metrics: gpuwatch_checks_total, gpuwatch_check_latency_seconds, gpuwatch_in_stock_events_total, gpuwatch_near_misses_total, gpuwatch_purchase_attempts_total, gpuwatch_errors_total.
Daily summary. Written to logs/daily-YYYY-MM-DD.md at daily_summary_hour local time. Covers observations (with in-stock count), attempts grouped by vendor / state, and per-attempt detail with failure reasons.
Order-status watcher. After a SUCCEEDED attempt, an entry is inserted in order_watches and the watcher polls that vendor's order page every 30 min. On cancelled / unable_to_fulfill, detection resumes automatically for that listing. On delivered, the watch is retired.
uv sync --all-extras
make test # unit + integration
make security # invariant-bypass tests + bandit + pip-audit
uv run pytest -v # full runDesign spec: docs/superpowers/specs/2026-04-19-gpuwatch-design.md.
Phase plans: docs/superpowers/plans/.
- Phase 1 — foundation, settings, secrets, DB schema — complete.
- Phase 2 — HTTP detection, three vendor adapters, condition verifier — complete.
- Phase 3 — scheduler, notifications, confirmation coordinator, simulate — complete.
- Phase 4 — pre-click verifier, screenshot, checkout orchestrator, security suite — complete.
- Phase 5 — remaining vendors, order watcher, Prometheus, daily summary, deploy — complete.
Post-Phase-5 TODO for the user:
- Paste real product-page HTML into each
tests/fixtures/<vendor>/*.html(current fixtures are minimal placeholders). - Run
gpuwatch setup-secretsto populate the Windows Credential Manager. - Edit
config.toml—[ship_to],[payment], each[vendors.<name>]product_urlsandenabled. - Do one-time manual login per vendor (launch headful browser, log in, save card + address, check "remember me").
- Run
gpuwatch test-notify. - Run
gpuwatch simulate-instock --vendor bestbuyand confirm the CONFIRM/CANCEL prompt arrives. - Let
gpuwatch run(dry-run) soak for 24–48h. - Flip to
--no-dry-run --i-understand-this-will-spend-real-moneyonce everything is boring.
