Local-first, provider-agnostic new-car quote pipeline. AutoBroker discovers dealers, pulls real dealer email/web quotes, audits the numbers, and helps you negotiate — driven by 18 skills (9 LLM-backed for extraction and drafting, 9 deterministic for the load-bearing math and orchestration), with humans approving anything irreversible.
Shown with the built-in zero-config demo data (
AUTOBROKER_DEMO_SEED=1) — no API key, Gmail, or network required.
This repository is the full-TypeScript rebuild of AutoBroker. It is built
from the ground up, one skill at a time in dependency × risk order, against the
frozen legacy Python implementation (../AutoBroker-Python) as a read-only
parity oracle. Mastra 1.x owns orchestration and durable workflow state, AI
SDK 6 is the provider layer, and React/Vite is the local UI surface before the
optional Electron shell.
The default LLM provider is DeepSeek (api.deepseek.com). DeepSeek is also
the agent that runs AutoBroker's live test harness against the real corpus.
Per DeepSeek's privacy policy, your inputs, prompts, and uploaded files are stored on servers in the People's Republic of China (PRC) and may be used to train DeepSeek's models. AutoBroker's core job is to read your private Gmail content and dealer PII — dealer replies are passed verbatim to the model when extracting quotes. With the default provider, that sensitive content is routed to, stored in, and potentially trained on inside the PRC.
If you do not accept this, do not use the default. Instead set one of the other two first-class providers:
ANTHROPIC_API_KEY— route LLM calls to Anthropic (Claude) via the API key (per-token billed).OPENAI_API_KEY— route LLM calls to OpenAI.
All three providers (DeepSeek, Anthropic, OpenAI) are first-class and
switchable. Provider selection is policy-driven (useCase → ModelAlias); you do
not edit workflow code to change providers — set the corresponding key and the
provider registry routes there.
Switching at runtime — the chat-rail AgentBar. Above the chat input, a four-box selector (Provider · Method · Model · Thinking effort) lets you pick the lane per run; it only enables combinations whose credential is present. DeepSeek runs on its API key; Claude runs on either an API key OR a Pro/Max subscription (OAuth) — see below. Until you pick explicitly, the server's default lane is used, so existing behavior is unchanged.
Claude on your subscription (OAuth, lane B). Set CLAUDE_CODE_OAUTH_TOKEN
(from claude setup-token) to run LLM calls on your Claude Pro/Max
subscription instead of per-token billing, via Anthropic's official Agent SDK.
This is for personal, single-user use of your own subscription (Anthropic's
"ordinary individual usage of Claude Code and the Agent SDK") — AutoBroker is
local-first and you bring your own token; it does not route others'
subscription credentials. A multi-user deployment must use ANTHROPIC_API_KEY
instead. Subscription usage is flat-rate (the cost ledger records it as
subscription, not a per-call dollar amount). Privacy note: this routes your Gmail
content / dealer PII to Anthropic (US) rather than the PRC — generally a stronger
posture than the DeepSeek default.
Fastest path — see it work with zero keys. The built-in demo seeds sample
data into an isolated test-mode database; no API key, Gmail, or network needed:
pnpm install # requires Node >= 24.13.0 and pnpm 9
pnpm doctor # read-only env self-check (prints the fix for any problem)
pnpm demo # builds the server once, then boots the seeded demo (mode=test)
# in a second terminal:
pnpm --filter @autobroker/ui dev
# open http://localhost:5173 — a populated dashboard, all sample dataTo do real work, add at least one provider key (and a Google Maps key for dealer search):
cp .env.example .env
# .env ships AUTOBROKER_MODE=test, so copying it starts you in SAFE test mode
# (every send is the local fake mailbox). Delete/change that line to go buyer.
#
# Set keys in the app — Settings → API keys (paste → Test connection → Save) —
# or hand-edit .env. Read the Privacy section above before choosing a provider.Which keys do you actually need?
- One LLM provider key — DeepSeek or Anthropic or OpenAI. DeepSeek is the
default/keystone; the other two are switchable. Setting several keys does not by
itself pick a lane — the DeepSeek policy default wins until you choose one in the
chat-rail AgentBar (or set
AUTOBROKER_AGENT_PROVIDER). Claude can also run on a Pro/Max subscription (OAuth) instead of per-token billing — see the guide. GOOGLE_PLACES_API_KEY— required for dealer search; without it, intake suspends at the location step.- Gmail OAuth — required only for the email pipeline (reading dealer replies, sending follow-ups); optional for search-and-audit-only use.
Every credential has a step-by-step manual guide, including the Gmail Google-Cloud setup and the optional Claude subscription/OAuth lane: docs/onboarding/CREDENTIALS_SETUP.md.
The no-keys verification floor (all three run without any provider key):
pnpm typecheck # tsc --build across the workspace
pnpm test # vitest
pnpm ui:functional # deterministic Playwright UI lane against seeded fixturesParity-period data (cold-copied product SQLite DB, Mastra runtime DB, logs,
config) lives under ~/.autobroker-ts/, isolated from the legacy Python repo's
~/.autobroker/.
After pnpm install and setting at least one provider key, build and start the
backend server, then open the UI:
# 1. Build the server (required once; re-run after source changes)
pnpm --filter @autobroker/server build
# 2. Start the backend server (listens on 127.0.0.1:8100)
node apps/server/dist/index.js
# 3. In a second terminal: start the Vite dev server for the UI
pnpm --filter @autobroker/ui dev
# Open http://localhost:5173 in your browser.Optional — open as a desktop app (requires the bundled Electron shell):
pnpm desktop:bundle # build the self-contained server bundle
pnpm desktop:start # launch the Electron shellNote: Full pipeline operation (Gmail inbox reading, lead submission) also requires a Gmail OAuth credential — set it up with the one-time command-line consent flow in docs/onboarding/CREDENTIALS_SETUP.md §5 (the in-app Connect button is still a placeholder). Both
pnpm testandpnpm ui:functionalneed no provider keys (they are the no-keys verification floor shown above).
The pipeline is 18 skills, built one at a time in dependency × risk order (phase 1 → 5). Nine call an LLM (extraction, field-mapping, drafting); nine are fully deterministic (the load-bearing audit math, ranking, and orchestration). Read-only scans run freely; the two destructive and three irreversible-send skills never act without an explicit human approval.
Skill (/slash) |
Phase | Risk | Engine | What it does |
|---|---|---|---|---|
search_profile_intake |
1 | local write | LLM | Create a new-car search profile from a slash form or freeform prose. |
quote_audit |
1 | read-only | deterministic | Run the 10-check audit over a profile's recent dealer quotes and flag issues. |
quote_compare |
1 | read-only | deterministic | Rank and compare multiple dealer quotes side by side. |
inventory_compare |
1 | read-only | deterministic | Rank inventory listings against a search profile. |
dealer_geosearch |
2 | local write | LLM | Find dealers near the profile's location via the browser. |
inventory_site_scan |
2 | local write | LLM | Scan the profile's dealer sites for matching inventory, then auto-chain a shopping-site scan. |
inventory_aggregator_scan |
2 | local write | LLM | Search marketplace sites (Cars.com, visor.vin, Edmunds) for matching new-car listings nearby. |
inventory_link_scan |
2 | local write | LLM | Visit unscraped dealer inventory URLs and match listings against the profile. |
incentive_scrape |
2 | local write | LLM | Scrape current manufacturer incentives for each active profile's vehicle. |
dealer_inbox_check |
3 | local write | deterministic | Read dealer replies from the mailbox and surface new messages. |
dealer_reply_extract |
3 | local write | LLM | Extract a structured quote from a dealer's email reply. |
dealer_hygiene |
3 | destructive | deterministic | Classify CRM threads, suppress noisy senders, delete orphan records (three staged confirms). |
quote_pipeline |
4 | local write | deterministic | Orchestrate the post-reply chain (reply-extract → incentive → audit → compare). |
daily_digest |
4 | local write | deterministic | Build a daily digest of pipeline activity across active profiles. |
pipeline_reset |
4 | destructive | deterministic | Wipe the entire local pipeline DB and recreate the schema (typed-YES confirm). |
dealer_web_lead_submit |
5 | irreversible | LLM | Submit a lead to a dealer's web form (human approval). |
negotiation_followup |
5 | irreversible | LLM | Draft and send a negotiation follow-up email to a dealer (human approval). |
dealer_closeout_email |
5 | irreversible | deterministic | Send a close-out email to a dealer (human approval; templated draft). |
Two behaviors worth knowing up front: trim is a required field — intake asks
for it, and all three browser scans stop with trim_missing without it; and a
completed inventory_site_scan auto-chains an inventory_aggregator_scan for
the same profile (kill switch AUTOBROKER_SITESCAN_CHAIN=0). The authoritative
definition of every skill (id, slash command, risk class, profile-pin posture)
lives in packages/skills/src/registry.ts.
A pnpm monorepo with a strict one-way dependency wall, enforced by TS
project references. Frameworks stay in their owning layer: core remains pure
contracts, model owns provider adaptation, workflows owns Mastra
orchestration, tools owns all side effects, and app owns HTTP/UI shells.
| Layer | Package | Owns | May import frameworks? |
|---|---|---|---|
| 1. core | packages/core |
Pure TYPES + Zod schemas (DealerQuote, AuditFlag, SearchProfile, CapabilityFlags, ModelAlias, run-status projections) |
No — AI SDK / Mastra / Drizzle / Playwright are invisible here |
| 2. model | packages/model |
AI SDK 6 provider layer: registry (deepseek, anthropic, openai), policy(useCase->alias), resolveModel(alias), canonical-message translation, structured-output strategy helpers |
AI SDK 6 |
| 3. workflows | packages/workflows |
Mastra 1.x backbone: one flat createWorkflow per skill, Memory-thread/session integration, OM chat-lane compacting, durable suspend/resume projection, L2 gate orchestration |
Mastra |
| 4. tools | packages/tools |
Gmail, browser (Playwright-native), product DB writes, calc/validators. Only this layer touches the product DB or external APIs. Mutating actions wear a code-level approval wrapper | Playwright, @googleapis/gmail, google-auth-library, pdfjs-dist |
| 5. app | apps/server, apps/ui, apps/desktop |
Backend HTTP + SSE skill-run stream; React/Vite + AI SDK UI chat rail; Electron shell (optional; macOS install + auto-fresh) | Fastify, React (+ @ai-sdk/react for the chat rail), Electron |
Supporting packages: packages/db (Drizzle schema + better-sqlite3,
drizzle-kit pull baseline, test_run_records ledger; Mastra state lives in a
separate mastra.db), packages/skills (the 18 skill definitions).
ai is pinned to ^6 deliberately — v7's LanguageModelV4 spec would
break V3-spec community providers. On the backend, AI SDK 6 is the provider
adapter (createProviderRegistry -> LanguageModel -> Mastra agent), not the
workflow engine. On the frontend, AI SDK UI remains first-class for the chat
rail and message streaming.
Side effects can physically reach browser.submit / gmail.send only
through the L2 in-process gate handler, which fails closed. AUTOBROKER_MODE
is the single send-control variable: AUTOBROKER_MODE=test resolves every send
to the local fake mailbox, while buyer (the code default) enables real sends —
still one human-approved action at a time through the always-on L2 gate. The three
irreversible skills (dealer_web_lead_submit, negotiation_followup,
dealer_closeout_email) really send in buyer mode through that same gate and
fake-send in test mode — via the one AUTOBROKER_MODE switch, with no separate
per-skill flag — and their human approval is never hidden.
Am I about to send real email? A fresh clone is safe by construction: the
shipped .env.example sets AUTOBROKER_MODE=test, so copying it starts you in the
local fake-mailbox posture. The in-app TopBar toggle is authoritative and is read
fresh on every send, and even in buyer mode nothing leaves the machine without
your explicit per-action approval at the L2 gate. See CLAUDE.md for the full
12-point invariant set.
The legacy Python implementation is frozen at ../AutoBroker-Python and is
read-only until all 18 skills reach parity-GREEN, at which point AutoBroker (TS)
takes over and the Python repo retires.
- Agent setup guide — step-by-step instructions for an AI agent configuring a fresh install.
- User guide — plain-language setup for the owner.
- Credential setup (manual) — human, step-by-step procedures for every API key and the Gmail OAuth client.
AutoBroker is an independent, personal automation tool. It is not affiliated with, endorsed by, or otherwise connected to any automobile dealer, dealership group, or OEM (including Toyota, Honda, Hyundai, Ford, GM, Stellantis, or others); nor to DeepSeek, Anthropic, OpenAI, Google, or any other commercial entity whose services it automates or integrates with. All trademarks and service marks are the property of their respective owners.
AutoBroker is provided "as is" without warranty of any kind. The authors are not responsible for any unintended communications sent to dealers, data shared with third-party LLM providers, or consequences of automating car-buying workflows. All irreversible actions require explicit human confirmation before they execute.
MIT — see LICENSE.
For data handling details (Gmail API, LLM data routing, local storage) see docs/DATA_HANDLING.md.
For security and vulnerability reporting see SECURITY.md.
