Remove all Swift/legacy artifacts — no native app, no swift-react UI, no tech debt. The Chrome PWA (web/) on the NestJS backend (src/) + MCP server is the sole way forward. Removed: - @packages/prospector-client, @packages/prospector-ui (Swift packages) - Sources/ (QuinnProspector, QuinnProspectorCore), Config/, project.yml, QuinnProspector.xcodeproj, Resources/ - PLAN.md (stale my/-port plan, superseded) Rewired: - .forgejo/workflows/ci.yml → Node CI (npm ci → typecheck/test/build for backend + web + MCP) instead of Swift build/test - .gitignore → drop .build/, *.xcodeproj/ Unified definition: - docs/PROSPECTOR.md — new single source of truth (architecture, surfaces, invariants, dependencies, build/deploy) - README.md, CLAUDE.md, docs/README.md, src/README.md → PWA-forward, point at docs/PROSPECTOR.md, no Swift/legacy references designs/ kept intact as the authoritative UI/behavior spec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.3 KiB
Quinn Prospector — Unified Definition
The single source of truth for what this application is. Read this first;
everything else (src/README.md, per-module READMEs, designs/) drills down from here.
One repo, one app. Prospector is a self-contained NestJS backend + React PWA + MCP server, with its own Postgres database. There is no Swift app, no native macOS target, and no platform
my/round-trip in the build. The PWA is the way forward.
1. What it is
Prospector is Quinn's AFK auto-send engine + operator console for inbound prospecting and outbound campaigns. It replaces the old Claude Desktop "coworker/executor" with a focused, installable tool:
- Backend (
src/) — a NestJS service (Node 20) that classifies inbound messages, decides whether to auto-send or hold, drafts replies from canon templates, dispatches through the macsync outbox, and exposes everything under/prospector/*. Owns its Postgres database. - Operator PWA (
web/) — a Vite + React app served same-origin by the backend. Installs as a standalone Chrome/macOS window (PWA manifest + app-shell service worker). This is the operator's daily workhorse. - MCP server (
@packages/mcp-prospector/) — a thin adapter over the REST surface so agent coworkers can drive the same flows.
The 8 interactive prototypes in designs/ are the authoritative visual +
behavior contract. Open them in a browser; the PWA ports and wires them to real
data. Treat them as the spec — they win UI/behavior disputes.
2. Architecture (three pieces, one origin)
┌─────────────────────────────────────────────┐
│ Operator (Chrome PWA, installed window) │
│ web/ — React, hash-routed, 8 views │
└───────────────┬─────────────────────────────┘
│ same-origin /prospector/*
▼
agents ──MCP──▶ ┌──────────────────────────────────────────┐
@packages/ │ Backend src/ (NestJS, Node 20) │
mcp-prospector │ feature-sliced modules → pure logic │
│ serves web/dist via useStaticAssets │
└───┬───────────────┬──────────────┬────────┘
│ │ │
▼ ▼ ▼
Postgres on-demand GPU HTTP services
(own DB) (LLM classify/ people · mac-sync
draft) mr-number
- Same-origin in prod:
main.tsservesweb/dist(useStaticAssets); the API stays under/prospector.web/src/api.tstargets/prospectorin prod and the/apiVite proxy in dev. No SPA-fallback route — the app hash-routes. - Auth: a service-token guard protects all
/prospector+/internalroutes (injected by the fronting proxy in prod; the Vite proxy injectsPROSPECTOR_SERVICE_TOKENin dev).health/is public.
Layering rule (enforced, see docs/STANDARDS.md)
controller (HTTP only) → service (I/O + orchestration) → pure modules
(domain logic, no DB, unit-tested). Cross-module access goes through a feature's
index.ts / *Module — never a deep internal import. src/markets/ is the
reference module.
3. The operator surfaces (PWA views ↔ backend modules)
Eight nav-rail views, each backed by real /prospector/* endpoints:
| View | What it does | Backend module |
|---|---|---|
| Triage (home) | Life/Dates/Digital segmented roster + search → detail; toolbar (classify/MR/pastebin refresh) | prospects/ |
| Detail | thread, Mr. Number panel, draft-from-🌹 + send-via-outbox, teach-loop correction | prospects/, corrections/ |
| Queue | held-for-review backlog → open / release (auto-drafts on release) | audit/ |
| Campaigns | tag + market + msg-age filter → audience preview → confirmed launch + history | campaigns/ |
| Reports | auto-qualify funnel, 7d volume, band/market breakdown, backlog | reports/ |
| Markets | tour-stop selector + per-market peak hours/days, conversion, locality | markets/ |
| Pastebin | live 🌹 canon templates (from macsync Notes) | pastebin/ |
| Control | kill-switch (GO/PAUSE/AWAY) · digest · activity feed · held | settings/, audit/ |
Supporting backend modules: inbound/ (macsync webhook → classify → decide),
classify/ + engine/ (pure classifier/gate/scam/state/booking/send-guard),
runner/ + scheduler/ (AFK decision + heartbeat), auth/, health/,
clients/ (people, mr-number, macsync), entities/ + migrations/.
Two distinct "market" notions — never conflate
- Campaign-targeting market — a coarse E.164 calling-code bucket
(
src/prospects/segment.ts, e.g. US/CA, MX). Used by Campaigns + Reports. - Tour-stop market — a metro Quinn physically works for a date window, with
a timezone (
src/markets/registry.ts, currently NYC). Drives Markets stats.
4. Invariants (the safety floor)
- Every send — manual, AFK runner, or campaign — goes through the macsync
outbox, keeps the
human_ownedhard floor, and writes one sharedprospect_draftsaudit row. - Campaigns never blast Life-band contacts unless
lifeis explicitly selected. - The kill-switch (
settings/) gates all auto-send. AWAY/PAUSE halts the runner.
5. Dependencies
Internal (npm workspaces): @prospector/app (backend, src/) ·
@prospector/web (PWA) · @prospector/mcp-prospector (MCP).
Backend stack: NestJS 11 (common/core/platform-express, config,
schedule, swagger, typeorm), TypeORM + pg (Postgres), class-validator,
class-transformer, rxjs, reflect-metadata. Tests: Vitest.
Web stack: React 18 + Vite 5. MCP stack: @modelcontextprotocol/sdk + zod.
External services (HTTP, declared in .infra.yaml): people (signals) ·
mac-sync (Notes pastebin, outbox, messages, calendar — the only mesh-dependent
path) · mr-number (number reputation). Plus shared managed Postgres
(lilith-store-pg, own logical DB/role) and an on-demand GPU droplet for
LLM classify/draft.
6. Build, run, deploy
# Dev
cd web && npm run dev # PWA on Vite, proxies /api → backend
npm run start:dev # backend (watch)
# Verify
npm test # backend (Vitest)
npm run typecheck && npm run build
npm run build --workspace web # → web/dist/
# Local app window
./run # launches the local stack
./run tray # macOS menu-bar launcher (AppleScript, no Swift)
- Prod is a single NestJS process serving
web/distsame-origin behind the auth-injecting proxy on the DO droplet (.infra.yaml: hostlime, port 3210, systemd unitprospector). Override the dist path withPROSPECTOR_WEB_DIST. - CI (
.forgejo/workflows/ci.yml) runs on ct-forge Linux runners:npm ci→ typecheck → test → build backend → build web → build MCP. - Mesh dependency is limited to
mac-sync(Apple integrations over the WG mesh). Core prospector (stats, classify, drafts, sends, reports, queue) is pure DO + Postgres.
Future goal (not the current host): a platform my/ SSO surface mirroring
this PWA for unified quinn.my deploy. Build against web/ here; the my/ port
is a later integration, not a dependency.
7. Where to look
src/README.md— backend module index (routes, consumers, ground truth).- per-module
README.md— what/why + file table + HTTP surface. docs/STANDARDS.md— house rules (feature-sliced modules, pure/IO split, reuse, co-located Vitest, 300/500 LOC caps).docs/features/—deploy.md,draft-engine.md,mcp.md.docs/MIGRATION_FROM_LP.md— context for the move off lilith-platform.designs/*.html— the visual + behavior spec (open in a browser).