parked_sketch_ebay_deal_watcher_mac_studio_2026-06-13

DARE.CO.UK · PARKED SKETCH · 2026-06-17

Mirrored from ~/.claude/.../memory/parked_sketch_ebay_deal_watcher_mac_studio_2026-06-13.md. This is a design sketch parked for future build — read for context, not as a current deliverable.

“PARKED SKETCH (build tmrw, 2026-06-14) — eBay deal-watcher Cloudflare Worker that polls for Mac Studio listings every 30 min, dedups/price-tracks in KV, notifies on good deals. Dan handed the wrangler.toml; concept + open questions captured. Not built yet.”


Dan, 2026-06-13, mid-Amazon-rebuild: handed a wrangler.toml for an eBay “Mac Studio look-up” deal-watcher and said “park/sketch for tmrw”. NOT built — this is the seed to resume from on 2026-06-14.

Surface home (Dan, 2026-06-13 FYI)

Lives under https://agent.gf.cx/ — surface/codename ebay-sniffer. i.e. agent.gf.cx is the umbrella “agent” surface; ebay-sniffer is the first agent on it (deal-watcher Worker is its engine). Future agents can hang off the same agent.gf.cx surface.

The idea

A Cloudflare Worker (deal-watcher) on a 30-min cron that hits the eBay API for a saved search (first target: Mac Studio), keeps per-listing state in KV, and fires a notification when a good deal appears (new listing under a price threshold / below rolling median / Buy-It-Now bargain). Generalises to any saved search — Mac Studio is just the first source.

wrangler.toml Dan handed (verbatim — the starting scaffold)

STASH4

Shape to build (deal-watcher.js)

✅ PHASE 2 SHIPPED 2026-06-14 — the agent is BUILT & LIVE

The deal-watcher is no longer parked — it’s merged into the single ebay-sniffer Worker and running on cron. One Worker, served on io.gf.cx/agent/ebay/*: - Faces: /agent/ebay + / holding page (og card) · /dashboard recent finds table · /status.json normalised status card (green/yellow/red, no-store) · /run manual scan (JSON) · /account-deletion compliance (intact). - Agent: scheduled() cron */30 * * * * → Browse API item_summary/search per WANT → dedup seen:${itemId} KV 30d → Pushover one-liner on fresh match. - Deltas from sketch ALL applied: marketplace EBAY_GB + GBP (Dan UK); notify = Pushover direct from edge (creds op://Code Shared/API Pushover portfolio-notify app_token/user_key — same as notify.py) NOT shell; status card emitted; agent.gf.cx-namespaced; KV created. - KV namespace DEALS id 675e09053dcf47b19e4893e24c2c9a36 in wrangler.toml. - Secrets set via wrangler secret put: EBAY_CLIENT_ID (app_id_production, …238d28c1 — Dan fixed the 1P clc1 typo, and the worker has a sed 's/238d28cl$/238d28c1/' guard regardless), EBAY_CLIENT_SECRET (cert_id_production PRD-…), PUSHOVER_TOKEN, PUSHOVER_USER. - Verified live 2026-06-14: /run{ran:true,newHits:0} (creds WORK, no token error; 0 = nothing matches Mac Studio M1 Max 32/1TB ≤£900 right now); status.json verdict green; account-deletion hash still matches. - WANTS (inline in worker.js): only Mac Studio M1 Max 32/1TB ≤£900 active; M4 mini template commented. Edit WANTS + redeploy to add hunts. - GOTCHA — reading the eBay 1P item: its giant user_token field has raw control chars → op item get … --format json on the WHOLE item breaks jq (“control characters must be escaped”). SCOPE the read: op item get hjefnq2mpn25sxpcgt7yob3jka --fields label=app_id_production,label=cert_id_production --reveal --format json. - GOTCHA — edge cache masked the redeploy: CDN, security layer, and DNS provider sitting in front of dare.co.uk." data-tip="Cloudflare — the CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF cached the old holding-only responses; status.json served stale HTML until purged. FIX baked in: JSON responses now cache-control: no-store, HTML max-age=30-60 must-revalidate. Purge after a redeploy: POST /zones/{gf.cx zone 9f9c927257fab0bc341ae80b3ecb8fa1}/purge_cache with the Cloudflare gf.cx full access token + the 4 agent URLs. - Still TODO (next pass): register /status.json in status.gf.cx registry.json so it surfaces on the board; rolling-median-in-KV threshold (currently fixed £900 ceiling); add the M4 mini WANT when wanted.

✅ PHASE 1 SHIPPED 2026-06-14 — eBay account-deletion compliance endpoint LIVE

eBay won’t ISSUE the production keyset until the app has a working Marketplace Account Deletion endpoint (the “Non Compliant” gate). Built + deployed it: - Worker ebay-sniffer at ~/Code/agent.gf.cx/ebay-sniffer/worker.js, route io.gf.cx/agent/ebay/* (zone gf.cx). Live endpoint: https://io.gf.cx/agent/ebay/account-deletion. - Why io.gf.cx not agent.gf.cx: agent.gf.cx is behind CDN, security layer, and DNS provider sitting in front of dare.co.uk." data-tip="Cloudflare — the CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF Access (email-gated) → eBay’s unauthenticated webhook can’t reach it. io.gf.cx is the OPEN-ACCESS public-surfaces host (Dan’s call); /agent/ = live-apps namespace. - Handshake = sha256(challenge_code + verificationToken + endpoint), endpoint derived from request URL so it matches whatever is registered. GET→hash, POST→200 (we store no eBay PII). Verified live: hash MATCHES, POST 200. - Verification token (also entered in eBay portal): sb3qQdHdNUhwHsSng-K7A8DW4oru5OYUuX87wQ_0TAc8YTrF (hardcoded in worker.js; move to a Worker secret later). - Deploy: cd ~/Code/agent.gf.cx/ebay-sniffer && env -u CLOUDFLARE_API_TOKEN npx wrangler deploy (OAuth, NOT the agent-gfcx scoped token — different worker). - wrangler.deal-watcher.toml preserves the full cron/KV config for Phase 2.

PROD KEYSET = WORKING ✅ (verified 2026-06-14). 1P item RENAMED eBay API (id hjefnq2mpn25sxpcgt7yob3jka, Code Shared) now has app_id_production + cert_id_production (PRD-…) and app_id_sandbox/cert_id_sandbox/dev_id_sandbox. Production client-credentials token issues fine; Browse API get_item_by_legacy_id confirmed live (recent item → title+price; ended → 404 errorId 11001/11003). app AUDREYDE-paportfo-PRD-3a93a6a15-238d28c1. ⚠️ TYPO IN 1P: app_id_production is stored ending …238d28cl (lowercase L) but the correct char is 1 (digit) → …238d28c1. Token 401s with the stored value, works with the fix. Dan to correct that one char in 1P (Dan-run write); until then read app_id_production and .replace(last 'cl'→'c1') or just hardcode the correct id. Token endpoint: https://api.ebay.com/identity/v1/oauth2/token, scope https://api.ebay.com/oauth/api_scope, marketplace header EBAY_US/EBAY_GB. Phase 2 (deal-watcher Browse polling) is now BUILDABLE — only the WANTS/cron/KV merge remains.

STARTER CODE ALREADY EXISTS (Dan handed ebay-sniffer.zip, 2026-06-13)

A prior Claude chat produced a near-complete Worker — staged at ~/Code/agent.gf.cx/ebay-sniffer/ (deal-watcher.js + wrangler.toml). Build tomorrow STARTS FROM THIS, not from scratch. What it already does well: - Full Browse API: client-credentials OAuth token cached in KV w/ TTL; item_summary/search with server-side price:[..max]+FIXED_PRICE filter, sort:price; spec-match (require/exclude title regex + maxPrice) in code. - WANTS array (first entry = Mac Studio M1 Max 32/1TB ≤$900; commented M4 mini template). Dedup via KV seen:${itemId} 30d TTL → alert-once. Hits ledger (hits, cap 50) + self-hosted dashboard at / + manual /run trigger. - Notify via Resend (email) + sms.to (SMS), both optional via env.

Deltas to close before it’s “ours” (do tomorrow)

  1. eBay creds = the blocker — PARTIALLY exists, SANDBOX ONLY (confirmed 2026-06-14): 1P “Code Shared” item eBay Sell API (id hjefnq2mpn25sxpcgt7yob3jka) holds app_id/cert_id/dev_id — but it’s a SANDBOX keyset (app_id env token = SBX, cert_id = SBX-…; a production client-credentials token request returns 401 invalid_client). Sandbox can’t see real production listings → useless for the watcher. TODO: generate a PRODUCTION keyset (eBay dev portal → same app → Production keyset → -PRD- App ID + Cert), stash as EBAY_CLIENT_ID/ EBAY_CLIENT_SECRET (Dan-run 1P write), then wrangler secret put. The Browse API needs only the client-credentials app token (no user consent).
  2. Notifications: swap/augment Resend+sms.to → the portfolio notify.py one-liner (Pushover ships). Worker is edge → can’t shell notify.py; add a Pushover branch in notify() OR POST to a tiny notify endpoint. (Resend MCP IS connected, so email path is viable too — but the standard is the sentence.)
  3. Marketplace hardcoded EBAY_US — Dan is UK (.co.uk); switch to EBAY_GB or query both (the EBAY object’s marketplace + the X-EBAY-C-MARKETPLACE-ID header).
  4. No status.gf.cx card — Worker self-hosts a dashboard instead of emitting the normalised status JSON + registry.json the other surfaces do. Add it.
  5. Domain: deploys as bare deal-watcher Worker → add the agent.gf.cx route (ebay-sniffer is its first agent).
  6. REPLACE_WITH_KV_IDwrangler kv namespace create DEALS.
  7. Price = fixed ceiling (fine MVP); rolling-median-in-KV is the v2.

Open questions to resolve first (2026-06-14)

  1. eBay API + creds. Need the Browse API (Buy) item_summary/search (keyword + price filter + buyingOptions), which needs an eBay developer App ID + OAuth application token (client-credentials, not the user token). The existing eBay evidence work used Fastmail/Gmail mailboxes, NOT the eBay API — so there may be NO eBay dev app yet. Check 1P “Code Shared” for an eBay app/client-id; if absent, register a dev app + stash App ID/Cert ID in 1P (Dan-run write per the 1P-SA-token rule).
  2. Price-threshold logic: fixed ceiling vs rolling median in KV. Rolling is nicer (self-calibrating) but needs a few polls to warm up.
  3. Search params for “Mac Studio” (model/RAM/SSD variants → maybe several watched queries, or one broad query + client-side filter).
  4. Where notify.py lives relative to the Worker (Worker is edge → can’t shell out; pick the POST-endpoint or direct-Pushover path above).

Surface baseline reminder

If this becomes a real surface with any HTML face, it owes the three-layer floor (inline link promotion + rollover thumb + social card) per ~/.claude/CLAUDE.md. A headless cron Worker with only a status card may be exempt, but flag it when scaffolding.

Source: parked_sketch_ebay_deal_watcher_mac_studio_2026-06-14.md · Rendered