Private, single-operator Pokemon product monitoring with price-guarded alerts and manual retailer handoff.
Spotter watches an explicit SKU allowlist, records normalized stock observations and monitor health in Postgres, and emits durable events when availability or price changes. Pokemon Center US is the primary retailer integration. Target US is a secondary monitor.
When a product is available at or below the operator's configured maximum price, Spotter can send an alert with the retailer's official product link. The operator can click that link or explicitly run handoff --open, then completes every retailer interaction manually in the normal visible browser.
Important
Spotter does not add to cart, sign in, fill forms, enter payment data, solve CAPTCHAs, bypass queues, or submit orders. Retailer challenges and queues remain manual steps in the retailer's own interface.
| Surface | State | Notes |
|---|---|---|
| Pokemon Center US | Primary monitor | Public catalog session, exact-variant parsing, aggregate pacing, Retry-After, and defense-response backoff |
| Target US | Secondary monitor | Read-only Redsky adapter; unavailable Target configuration does not stop Pokemon Center monitoring |
| Monitor health | Implemented | Per-SKU health plus retailer-wide request deadlines, challenge state, stable failure codes, and freshness are persisted |
| Process health | Implemented | Tracker and alert relay starts, heartbeats, clean stops, and unclean restart history are persisted |
| SKU operations | Implemented | Audited add, list, enable, and disable commands with retailer-scoped identity and a required maximum price |
| Alerts | Implemented | Durable stdout or Telegram delivery for actionable stock and price events, with watch-only alerts for unsafe price states |
| Handoff | Implemented | Prints or explicitly opens an official retailer link only after health, freshness, availability, and price checks pass |
| Deployment packaging | Implemented | Digest-pinned nonroot Docker image, fail-closed runtime arming, and intended Render Blueprint; no live deployment is claimed |
| macOS service management | Implemented | Hardened user LaunchAgents with persistent enable and disable state, versioned releases, rollback, private logs, and persisted-health startup checks |
| Purchase automation | Not present | Acquisition remains operator-controlled in the normal retailer UI |
The Pokemon Center adapter is not guaranteed to be reachable from every network. A 403, challenge response, malformed payload, or unknown availability is recorded as degraded monitor health or an unknown observation. It is never converted into an out-of-stock result.
flowchart LR
PC[Pokemon Center US] --> T[Go tracker]
TG[Target US] --> T
T --> O[(observations)]
T --> H[(monitor_state)]
T --> E[(event_log outbox)]
T --> P[(runtime_process_runs)]
E --> A[alert relay]
A --> P
A --> N[stdout or Telegram]
N --> U[operator]
U --> C[spotter-cli handoff]
C --> R[official retailer page]
Each enabled SKU gets a scheduler loop. The tracker reconciles allowlist changes while it is running, so newly added, enabled, or disabled products take effect without a process restart. Pokemon Center requests share one PostgreSQL-backed retailer pacing gate, so adding SKUs or restarting the process does not multiply aggregate request rate. A session advisory lock permits only one tracker process at a time.
Successful observations and derived events commit in one transaction. Poll failures update monitor_state with a stable redacted failure code and the next scheduled attempt. Consumers resume from durable offsets; Postgres notifications only reduce latency.
The tracker and alert relay each append a process run after acquiring singleton ownership, then update a database heartbeat every 15 seconds. A replacement run fences an unfinished prior run, and process history remains available across migration rollback and reapply.
The first healthy in_stock or preorder observation for a newly enabled SKU intentionally emits an unknown -> available transition. This makes an already-available product visible immediately; initial out-of-stock and unknown observations remain silent.
- Explicit allowlist: only enabled retailer and SKU pairs produce outbound monitoring traffic.
- Required price ceiling: every SKU added through the CLI has a maximum acceptable USD price.
- Fail-closed parsing: malformed JSON, SKU mismatches, missing availability, HTML challenges, and incident responses do not become stock signals.
- Fresh health state: the operator can distinguish a recent out-of-stock observation from a stale or failing monitor.
- Price-guarded links: alerts and handoff omit the official product link when price is missing, above the ceiling, or monitor state is not ready.
- Manual acquisition: only an explicit
handoff --opencommand launches the validated page; Spotter never interacts with retailer UI. - Durable delivery: alert consumers use persisted offsets, retries, and dead letters.
- Single-owner runtime: a second tracker exits before starting monitor work, and ownership loss stops the active runtime.
- Persisted process health: required runtime starts, heartbeats, stops, and unclean replacements are visible without reconstructing state from logs.
- Restart-safe pacing: Pokemon Center request deadlines and challenge cooldowns survive process restarts.
- Audited mutations: SKU add, enable, and disable operations write redacted audit records.
- Go 1.26.4
- Node.js 22 or newer
- PostgreSQL 17
- Docker Desktop or another compatible Docker daemon
make
On macOS with Homebrew:
brew install go node postgresql@17
brew services start postgresql@17
createdb spotter_devInstall and start Docker Desktop, or start another compatible Docker daemon, before running the validation gate.
Install dependencies, configure the local database, apply migrations, and run the complete gate:
cp .env.example .env
make setup
make migrate
make verifyMake targets load .env automatically. Before running go run CLI commands directly, load the same local configuration into the current shell:
set -a
. ./.env
set +amake verify is the single retailer-offline validation gate. It validates Go and TypeScript, checks generated event contracts and migrations, parses the Render Blueprint against a compact pinned snapshot of Render's official JSON schema, performs deployment policy checks, builds and exercises the packaged linux/amd64 image, creates a disposable Postgres database, exercises event delivery, process lifecycle state, and tracker startup, then tears everything down. It requires a running Docker daemon but does not contact a retailer, Telegram, or Render.
For accounts, subscriptions, Telegram, PostgreSQL, and shared recovery procedures, see Production setup. For a local Mac service, see macOS launchd deployment. For the staged hosted procedure, see Render deployment.
Every product needs a retailer, public SKU, operator label, and maximum acceptable price:
go run ./cmd/spotter-cli sku add \
--retailer pokemoncenter_us \
--sku 10-10377-109 \
--label "Pokemon Center product" \
--max-price 59.99The command validates the retailer-specific SKU, forces purchase automation off, enables the monitor, and writes an audit record. A running tracker reconciles the change within five seconds.
List or change the allowlist with the full retailer and SKU identity:
go run ./cmd/spotter-cli sku list
go run ./cmd/spotter-cli sku list --retailer pokemoncenter_us --json
go run ./cmd/spotter-cli sku disable --retailer pokemoncenter_us --sku 10-10377-109
go run ./cmd/spotter-cli sku enable --retailer pokemoncenter_us --sku 10-10377-109Start the tracker and durable alert relay together. Stdout is the default alert driver:
make devRun only the durable alert relay when the tracker is managed separately:
make alertsUse Ctrl-C to stop the foreground process. make dev is a local supervisor, not an operating-system service manager.
For a user service on a dedicated Mac, follow the macOS launchd deployment guide. It runs only in the operator's gui/<uid> login domain, so it is not a pre-login or logged-out daemon. For an intended hosted deployment using the checked-in Dockerfile and Blueprint, follow the Render deployment guide. Repository configuration and local verification do not prove that any live Render resource exists.
go run ./cmd/spotter-cli status
go run ./cmd/spotter-cli status --jsonStatus includes enabled SKUs, latest availability and price, monitor failures and schedules, retailer request state, required process health, consumer lag and heartbeat, and dead letters. Process rows report starts, observed restarts, unclean restart history, the latest start and heartbeat, and a stable stop code. JSON also includes the latest instance ID, stop state and time, and whether the latest start replaced an unfinished run. A restart count is the number of starts after the first recorded start; it does not prove that an operating system service manager performed the restart.
The tracker and alert relay process heartbeats are separate from the spotter.alert-relay.v1 consumer heartbeat. When any SKU is enabled, a required process that never started, stopped, or has a heartbeat older than one minute makes status degraded. A missing or stale required consumer also degrades status. With no enabled SKUs, top-level status remains idle, but the process rows still expose missing, stale, stopped, and historical state. A dead letter degrades status for 15 minutes while remaining visible in the historical count afterward. The command prints the report and returns a nonzero exit status for degraded state.
Request a handoff only after reviewing monitor status:
go run ./cmd/spotter-cli handoff \
--retailer pokemoncenter_us \
--sku 10-10377-109The command prints the official retailer URL only when the SKU is enabled, monitor health is current, the latest state is in_stock or preorder, and the observed price is known and no higher than the configured ceiling. Otherwise it prints a stable reason and returns a nonzero exit status.
Add --open to launch that validated HTTPS URL through the operating system's normal visible browser:
go run ./cmd/spotter-cli handoff \
--retailer pokemoncenter_us \
--sku 10-10377-109 \
--openThe --open flag is an explicit operator action and accepts only the supported official retailer hosts. Login, queue participation, CAPTCHA completion, cart changes, quantity selection, checkout, and payment remain manual retailer-UI steps.
The alert relay consumes stock-transition events to in_stock or preorder, all price-drop events, and all price-available events under the durable consumer name spotter.alert-relay.v1.
- A READY alert includes the official product link only when the SKU is still enabled, monitor health certifies the latest observation, current availability remains actionable, and current price is within the configured ceiling.
- A WATCH alert omits the link when current availability remains actionable but the SKU is disabled, monitor health is degraded or stale, or price is missing, above the ceiling, or otherwise unusable.
- Delayed events are enriched from the latest certified observation. Events superseded by unavailable state, out-of-stock transitions, and unrelated events do not notify the operator.
- Delivery is at-least-once. Failed deliveries retry through the event consumer and eventually appear in dead-letter status; a process failure after sending but before acknowledgement can produce a duplicate with the same event ID.
Copy .env.example to .env. The runtime settings are:
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string required by every database-backed command |
SPOTTER_ACTOR |
For SKU mutations | Redacted actor label written to audit_log |
POKEMONCENTER_US_USER_AGENT |
No | Overrides the monitor's default user agent |
TARGET_REDSKY_KEY |
For enabled Target SKUs | Target Redsky request key |
TARGET_STORE_ID |
No | Target store context; defaults to 2069 |
TARGET_USER_AGENT |
No | Overrides the Target request user agent |
SPOTTER_ALERT_DRIVER |
No | stdout by default; also accepts telegram |
TELEGRAM_BOT_TOKEN_FILE |
For Telegram | Path to a private file containing only the bot token |
TELEGRAM_CHAT_ID |
For Telegram | Nonzero numeric destination chat ID |
Do not put the Telegram token itself in .env, command arguments, logs, or documentation. Store it in a private file outside the repository and set TELEGRAM_BOT_TOKEN_FILE to that path. Telegram requires the token in its HTTPS Bot API request path; Spotter constructs that URL only during send, disables redirects and custom transports, and never stores or logs a printable token-bearing endpoint.
| Command | Purpose |
|---|---|
make migrate |
Apply all Goose migrations |
make migrate-status |
Show database migration state |
make verify |
Run the complete retailer-offline validation gate, including deployment and container checks |
make dev |
Supervise the tracker and durable operator alert relay |
make alerts |
Run only the durable operator alert relay |
POKEMONCENTER_US_SKU=<sku> go run ./scripts/canaries/tracker-pokemoncenter-us |
Probe one Pokemon Center SKU through the real adapter |
go run ./scripts/canaries/tracker-target |
Probe enabled Target SKUs through Redsky |
- Single-operator product availability and price monitoring, durable alerts, and manual official-link handoff.
- No executable cart, checkout, account-login, form-fill, payment, CAPTCHA-solving, queue-bypass, stealth, fingerprint-spoofing, or order-submission path.
- Retailer challenges stop automated monitoring traffic and surface as degraded health. Any challenge or queue in the retailer UI is handled manually by the operator.
- Live retailer canaries and Telegram delivery checks are explicit operator actions and are excluded from
make verify. - A supported retailer needs a reviewed posture record under
docs/retailers/.
cmd/spotter-cli/ Operator status, SKU lifecycle, and handoff commands
cmd/tracker/ Dynamic retailer monitor runtime
cmd/alert-relay/ Durable stdout and Telegram alert delivery
internal/tracker/ Schedulers, observations, transitions, monitor health
internal/operator/ Audited operator state and price-guarded handoff
internal/alert/ Alert enrichment, price guards, and notifiers
internal/eventbus/ Transactional outbox, offsets, retries, dead letters
internal/processhealth/ Persisted runtime starts, heartbeats, and stop state
internal/retailer/ Official product URL generation
pkg/pokemoncenter/ Pokemon Center catalog client and parser
pkg/target/ Target Redsky client and parser
ts/events/ Source Zod event contract
migrations/ Goose migrations and policy seeds
deploy/render/ Render runtime arming and relay secret entrypoints
ops/launchd/ Hardened macOS user LaunchAgent templates
Dockerfile, render.yaml Pinned image and intended Render Blueprint
scripts/canaries/ Explicit read-only retailer probes
scripts/launchd/ macOS install, lifecycle, rollback, and verification tools
docs/ Runbook, roadmap, and retailer posture records
- Production setup
- macOS launchd deployment
- Render deployment
- Operator runbook
- Monitoring roadmap
- Pokemon Center US posture
- Target US posture
- Failure taxonomy
- Legal and operating posture
- Private-repository sharing checklist
No license is granted. This repository is private.