My Reps has one shared behavior layer and three thin adapters.
- Core: validation, runtime mode selection, source facades, canonical representative mapping, envelopes, schema, and source status.
- CLI: terminal adapter for agents, local demos, and contract checks.
- API: HTTP adapter for the web app and Cloudflare Pages Function.
- Web: React UI that renders the canonical envelope returned by the API.
The important design choice is that the API does not shell out to the CLI. Both the API and CLI import the same TypeScript core.
flowchart TD
cli[CLI] --> core[Shared core]
api[HTTP API] --> core
worker[Cloudflare Pages Function] --> api
web[React web UI] --> apiEndpoint[/POST /api/lookup/]
apiEndpoint --> worker
core --> validation[Validation]
core --> envelope[Schema-versioned envelope]
core --> mode[Runtime mode selector]
core --> sources[Representative sources]
sources --> mock[Mock fixture source]
sources --> live[OpenStates + Census live source]
CLI lookup:
georep lookup --address "530 S King St, Honolulu, HI 96813" --mock
-> shared validation
-> runtime mode selection
-> representative source
-> canonical envelope
-> stdout JSON
Web lookup:
Browser form
-> POST /api/lookup
-> shared validation
-> runtime mode selection
-> representative source
-> canonical envelope
-> React renders cards/status
mock: deterministic, fixture-backed, no live upstream calls.live: uses server-side live source configuration.auto: defaults to mock unless live behavior is explicitly configured.
Mock mode is the reliable public demo path. Its representative records are synthetic and intentionally fake.
Live mode currently uses Census Geocoder for address-to-point lookup and OpenStates/Plural Open for state legislative representatives. It reports partial coverage because county and local offices are not covered by that live path.
All commands and API lookup responses use a schema-versioned envelope:
{
"schema_version": "1.0",
"success": true,
"data": {
"representatives": []
},
"meta": {
"source": "mock",
"sources": ["mock"],
"is_mock": true,
"query": {
"type": "address"
},
"count": 0,
"latency_ms": 0,
"generated_at": "2026-05-23T00:00:00.000Z"
}
}The envelope lets the CLI, API, and UI share one contract. It also gives agents a stable shape to inspect with georep schema.
Validation strips behavior down to a safe query shape before the source layer is called. Failure envelopes do not echo raw addresses or exact coordinates.
The code should not store:
- exact addresses
- exact coordinates
- raw upstream bodies
- source credentials
Source credentials are read from environment variables or Cloudflare project secrets and stay server-side.
Running the CLI from the deployed API would add an unnecessary process boundary and would not fit Cloudflare Pages Functions well. Keeping both surfaces as adapters over shared core code makes behavior easier to test and keeps deploys simple.