Skip to content

Repository files navigation

Spotter

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.

Current Status

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.

Architecture

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]
Loading

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.

Safety Properties

  • 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 --open command 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.

Quick Start

Prerequisites

  • 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_dev

Install 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 verify

Make targets load .env automatically. Before running go run CLI commands directly, load the same local configuration into the current shell:

set -a
. ./.env
set +a

make 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.

Add A Product

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.99

The 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-109

Run

Start the tracker and durable alert relay together. Stdout is the default alert driver:

make dev

Run only the durable alert relay when the tracker is managed separately:

make alerts

Use 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.

Inspect Health

go run ./cmd/spotter-cli status
go run ./cmd/spotter-cli status --json

Status 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.

Operator Handoff

Request a handoff only after reviewing monitor status:

go run ./cmd/spotter-cli handoff \
  --retailer pokemoncenter_us \
  --sku 10-10377-109

The 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 \
  --open

The --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.

Alert Semantics

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.

Configuration

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.

Useful Commands

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

Operating Boundaries

  • 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/.

Repository Layout

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

Documentation

License

No license is granted. This repository is private.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages