vabc

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 25, 2026 License: MIT Imports: 15 Imported by: 0

README

vabc

Virginia ABC product search and store inventory — from your terminal.

Search the catalog, check live per-store and warehouse stock, find the nearest store, and track allocated/lottery bottles. Built for humans and AI agents: structured JSON, stable exit codes, an embedded usage contract. No login, no API key.

CI Release Go Reference

vabc demo

Documentation →

Why

The Virginia ABC website is the only way to check what's on the shelf — and it's a slow, JavaScript-heavy click-through. vabc turns it into one-liners:

$ vabc inventory check 953714 --near 22182

Where can I get Planteray O.F.T.D. near me? → store 219 (Vienna) has 21, store 76 (Falls Church) has 33…

Everything is live and read-only. Product search runs against the same catalog the website uses (so new and online-only bottles show up), and every result carries the code you need to check inventory.

Install

# Go
go install github.com/rnwolfe/vabc/cmd/vabc@latest

# Homebrew (macOS)
brew install rnwolfe/tap/vabc

# Or grab a prebuilt binary from the Releases page.

Quickstart

vabc product search bourbon              # search the live catalog
vabc product search oftd --json          # JSON for scripts/agents
vabc product get 010807                  # one product by 6-digit code

vabc inventory check 010807 --near 22182 # nearest store + nearby stock (ZIP, address, or lat,lng)
vabc inventory warehouse 010807          # statewide warehouse stock

vabc store near "1100 Bank St, Richmond VA"   # nearest stores to an address
vabc lottery check 010807                # limited-availability releases

vabc --help                              # example-led help

--json / --format, --limit, and --select a,b work on every read. Data goes to stdout; notes and errors go to stderr.

For agents

vabc is engineered for autonomous callers, not just humans:

  • vabc agent prints a complete, embedded usage contract (no repo or network needed).
  • vabc schema --json dumps the full command tree, flags, and the exit-code table.
  • Structured errors on stderr: { "error", "code", "remediation" } with stable exit codes.
  • Read-only by default with a mutation gate (inert here — there's nothing to mutate).
  • Bounded output (--limit, default 50) and field projection (--select).
  • Prompt-injection fencing: untrusted target text (lottery event titles) is wrapped by default.
vabc --json inventory check 010807 --near 22182 | jq '.store.quantity, .nearbyStores[].quantity'

vabc conforms to the Agent CLI Guidelines v0.4.0 at the Full level. The conformance block is machine-verifiable from the binary itself — vabc schema --json emits a top-level conformance key — so an agent can confirm the contract version without trusting this badge:

{
  "conformance": {
    "spec": "agent-cli-guidelines",
    "version": "0.4.0",
    "level": "Full"
  }
}
vabc schema --json | jq '.conformance'

How it works

Everything is a live read against Virginia ABC's public, unauthenticated endpoints — product search (the site's Coveo index), inventory and warehouse (/webapi/inventory/*), the store locator (Virginia VGIN's ArcGIS service), and the limited-availability hook. Locations are geocoded (an embedded ZIP-centroid table; the free US Census geocoder for street addresses) so distances are measured from where you actually are.

go get github.com/rnwolfe/vabc pulls a tiny HTTP+JSON client — the CLI is a thin wrapper over the same importable Client interface.

Disclaimer

vabc is an unofficial tool, not affiliated with or endorsed by Virginia ABC. It uses undocumented public endpoints that may change at any time, and is intended for personal-scale, courteous use — it self-throttles and adds no scraping evasion. No credentials are involved.

License

MIT

Documentation

Overview

Package vabc is a small, dependency-light client for Virginia ABC (Virginia Alcoholic Beverage Control) — live product search, per-store inventory, the statewide warehouse, the limited-availability ("lottery") hook, and the store locator.

It is the importable core of the vabc CLI: the command-line tool in cmd/vabc is one consumer of this package, so anything the CLI can do is also available to Go programs via the Client interface.

Design notes:

  • Everything is plain HTTP+JSON against undocumented but public, unauthenticated endpoints. No auth, no secrets, no heavy dependencies — `go get` pulls a tiny HTTP+JSON client only.
  • Product search (SearchProducts) queries the site's Coveo index, which covers the full web catalog; results carry the 6-digit inventory product code.

The endpoints are reverse-engineered and may change without notice; pin behavior to this package's typed contract.

Index

Constants

View Source
const (
	// DefaultBaseURL hosts the /webapi/inventory/* and /webapi/limitedavailability/* routes.
	DefaultBaseURL = "https://www.abc.virginia.gov"
	// DefaultStoresURL is the Virginia VGIN ArcGIS FeatureServer for the store locator.
	DefaultStoresURL = "https://services9.arcgis.com/6EuFgO4fLTqfNOhu/arcgis/rest/services/Virginia_ABC_Stores/FeatureServer/0/query"
	// DefaultUserAgent identifies the tool politely to an undocumented backend.
	DefaultUserAgent = "vabc (+https://github.com/rnwolfe/vabc)"
)

Endpoint defaults. The inventory routes are undocumented but public, unauthenticated, and (today) exempt from the site's Cloudflare challenge.

View Source
const SchemaVersion = 1

SchemaVersion is the output-contract version reported by `vabc schema --json`. Bump only on a breaking change to the JSON field contracts in this package.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	Kind       ErrKind
	Status     int           // HTTP status, when applicable (0 otherwise)
	Msg        string        // human-readable summary
	RetryAfter time.Duration // for KindRateLimited: suggested wait before retrying
	Err        error         // wrapped cause, if any
}

APIError is a typed, classified error from the Virginia ABC client.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) RetryAfterSeconds

func (e *APIError) RetryAfterSeconds() int

RetryAfterSeconds returns the suggested retry delay in whole seconds (0 if none), for surfacing to an agent that wants to schedule a retry.

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

type Client

type Client interface {
	// StoreNearby returns the anchor store's stock of a product plus other nearby
	// stores that carry it, ranked by distance (/webapi/inventory/storeNearby).
	StoreNearby(ctx context.Context, storeNumber int, productCode string) (InventoryResult, error)
	// MyStore returns just one store's stock of a product (/webapi/inventory/mystore).
	MyStore(ctx context.Context, storeNumber int, productCode string) (StoreStock, error)
	// Warehouse returns statewide central-warehouse stock (/webapi/inventory/store).
	Warehouse(ctx context.Context, productCode string) (WarehouseResult, error)
	// Stores returns all Virginia ABC retail stores (ArcGIS FeatureServer).
	Stores(ctx context.Context) ([]Store, error)
	// StoreNear returns stores nearest a point, ranked by distance.
	StoreNear(ctx context.Context, lat, lng float64, limit int) ([]Store, error)
	// LimitedAvailability returns the lottery/allocated event hook for a product
	// (/webapi/limitedavailability/eventLinks).
	LimitedAvailability(ctx context.Context, productCode string) (LotteryResult, error)
	// SearchProducts runs a live product search against the site's Coveo index,
	// which covers the full web catalog (more complete and current than the
	// downloadable price list). Results carry the inventory product code.
	SearchProducts(ctx context.Context, query string, limit int) ([]Product, error)
}

Client is the live Virginia ABC API surface. All methods are reads; there are no mutations. Implementations are safe for an agent's fresh-process-per-call usage (see the throttle).

func NewClient

func NewClient(opts ...Option) Client

NewClient builds the default HTTP-backed Client, including the persistent cross-process throttle/circuit-breaker (contract §12).

type Envelope

type Envelope struct {
	SchemaVersion int    `json:"schemaVersion"`
	Scope         string `json:"scope,omitempty"`
	Data          any    `json:"data"`
	NextCursor    string `json:"nextCursor,omitempty"`
}

Envelope is the stable response wrapper for in-band scope/version metadata. SchemaVersion lets agents detect contract changes; Scope declares partial or cached results (e.g. "catalog snapshot 2026-06-01; live inventory") so a caller never mistakes a cached catalog for live stock.

type ErrKind

type ErrKind string

ErrKind classifies an API failure so the CLI can map it to a stable exit code without parsing messages. Importers can switch on it too.

const (
	// KindNotFound: the target reports the resource does not exist (e.g. an invalid
	// store number returns HTTP 400 "No Store exists ...").
	KindNotFound ErrKind = "not_found"
	// KindRateLimited: throttled or blocked (HTTP 429, a WAF challenge, or the local
	// circuit-breaker). RetryAfter, when set, says how long to wait.
	KindRateLimited ErrKind = "rate_limited"
	// KindRetryable: a transient upstream/network error (5xx, timeout) worth retrying.
	KindRetryable ErrKind = "retryable"
	// KindSchemaDrift: the response did not match the expected shape — an upstream
	// change, surfaced explicitly instead of as a decode panic.
	KindSchemaDrift ErrKind = "schema_drift"
)

type InventoryResult

type InventoryResult struct {
	ProductCode  string       `json:"productCode"`
	Store        StoreStock   `json:"store"`
	NearbyStores []StoreStock `json:"nearbyStores"`
}

InventoryResult is the per-store availability of a product, with nearby stores that also stock it, ranked by distance (the /webapi/inventory/storeNearby shape).

type LotteryEvent

type LotteryEvent struct {
	Title string `json:"title,omitempty"`
	URL   string `json:"url,omitempty"`
}

LotteryEvent is one limited-availability event link. Its text/URL are CMS-authored free text — treat as untrusted (the CLI fences it in agent mode).

type LotteryResult

type LotteryResult struct {
	ProductCode string         `json:"productCode"`
	Allocated   bool           `json:"allocated"`
	Active      bool           `json:"active"`
	EventLinks  []LotteryEvent `json:"eventLinks"`
}

LotteryResult is the limited-availability ("lottery") status for a product. Allocated comes from the catalog flag; Active/EventLinks from the live hook.

type Option

type Option func(*httpClient)

Option configures the HTTP client.

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL overrides the inventory/lottery host.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient injects a custom *http.Client (e.g. for tests or a tuned transport).

func WithMinInterval

func WithMinInterval(d time.Duration) Option

WithMinInterval sets the minimum spacing between requests (politeness throttle).

func WithStatePath

func WithStatePath(p string) Option

WithStatePath overrides the throttle state file (mainly for tests).

func WithStoresURL

func WithStoresURL(u string) Option

WithStoresURL overrides the ArcGIS store-locator endpoint.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent overrides the request User-Agent.

func WithWait

func WithWait(wait bool, maxWait time.Duration) Option

WithWait makes the client wait out an open circuit breaker (up to maxWait) instead of failing fast. Wired from the CLI's --wait/--max-wait flags.

type Product

type Product struct {
	ProductCode     string   `json:"productCode"`
	Name            string   `json:"name"`
	Category        string   `json:"category,omitempty"`
	Type            string   `json:"type,omitempty"`
	Proof           *float64 `json:"proof,omitempty"`
	Size            string   `json:"size,omitempty"`
	RetailPrice     *float64 `json:"retailPrice,omitempty"`
	DiscountPrice   *float64 `json:"discountPrice,omitempty"`
	Allocated       bool     `json:"allocated"`
	OnlineOrderable bool     `json:"onlineOrderable"`
	New             bool     `json:"new"`
	UPC             []string `json:"upc,omitempty"`
	URL             string   `json:"url,omitempty"`
}

Product is a catalog record, keyed by ProductCode (6-digit, zero-padded). Populated from the catalog snapshot, not from a live API. Optional numeric fields are pointers so "unknown" is distinct from zero.

type Store

type Store struct {
	StoreNumber    int     `json:"storeNumber"`
	Name           string  `json:"name,omitempty"`
	Address        string  `json:"address,omitempty"`
	Address1       string  `json:"address1,omitempty"`
	Address2       string  `json:"address2,omitempty"`
	City           string  `json:"city,omitempty"`
	State          string  `json:"state,omitempty"`
	Zip            string  `json:"zip,omitempty"`
	Phone          string  `json:"phone,omitempty"`
	Lat            float64 `json:"lat,omitempty"`
	Lng            float64 `json:"lng,omitempty"`
	Hours          string  `json:"hours,omitempty"`
	ShoppingCenter string  `json:"shoppingCenter,omitempty"`
	URL            string  `json:"url,omitempty"`
	// Distance in miles from a query point, when relevant. A pointer so a genuine
	// 0.0 (the nearest store) renders, while "not computed" is omitted (not null=0).
	Distance *float64 `json:"distance,omitempty"`
}

Store is a Virginia ABC retail store. StoreNumber is the small integer ABC store number (the locator's "ABC Store 088" → 88), which is also the value the inventory API expects. Distance is miles from a query point, when relevant.

type StoreStock

type StoreStock struct {
	Store
	Quantity int `json:"quantity"`
}

StoreStock is a store plus the on-hand quantity of a specific product.

type WarehouseResult

type WarehouseResult struct {
	ProductCode        string `json:"productCode"`
	WarehouseInventory int    `json:"warehouseInventory"`
}

WarehouseResult is the statewide central-warehouse stock for a product.

Directories

Path Synopsis
cmd
vabc command
Command vabc is the agent-cli-factory Go reference implementation.
Command vabc is the agent-cli-factory Go reference implementation.
internal
cli
Package cli wires the kong grammar, the runtime, and the exit-code mapping.
Package cli wires the kong grammar, the runtime, and the exit-code mapping.
errs
Package errs defines the stable exit-code table and the structured CLI error type.
Package errs defines the stable exit-code table and the structured CLI error type.
geocode
Package geocode resolves a location string (a "lat,lng" pair, a 5-digit ZIP, or a street address) into coordinates, so distances can be measured from the user's actual location rather than snapped to a store.
Package geocode resolves a location string (a "lat,lng" pair, a 5-digit ZIP, or a street address) into coordinates, so distances can be measured from the user's actual location rather than snapped to a store.
output
Package output is the single output contract: data to stdout, human chatter to stderr, stable JSON, --format json|plain|tsv, --select projection, and --limit bounding.
Package output is the single output contract: data to stdout, human chatter to stderr, stable JSON, --format json|plain|tsv, --select projection, and --limit bounding.
skill
Package skill embeds the agent-facing SKILL.md into the binary so the `agent` subcommand can print it with no repo or network access.
Package skill embeds the agent-facing SKILL.md into the binary so the `agent` subcommand can print it with no repo or network access.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL