Skip to main content
Glama
firelever

firelever-mcp

Official
by firelever
README.md
# FireLever Growth Engine

Lead-gen agent pipeline for [firelever.com](https://firelever.com). Four agents — Prospector, Enricher, Scorer, Drafter — feed a human review queue. **Nothing is ever sent automatically**; approved emails are sent manually from Gmail (free-tier phase).

See [PLAN.md](PLAN.md) for the full growth strategy. The pipeline is growing into
**FireLever Copilot** (RAG + MCP + fine-tuned model for ops-heavy SMBs) — see
[docs/PROCESS.md](docs/PROCESS.md) for the delivery process and
[docs/01-BRD.md](docs/01-BRD.md) / [docs/02-PRD.md](docs/02-PRD.md) for requirements.

## Setup

```sh
npm install
export ANTHROPIC_API_KEY=sk-ant-...   # or `ant auth login`
```

## Daily loop

```sh
npm run pipeline   # prospect, enrich, score, draft (~10 new leads/run; needs API credits)
npm run review     # approve / reject each draft interactively
npm run set-email  # attach a manually sourced recipient email to a lead
npm run send       # send due emails + follow-ups, stop sequences on reply (Gmail)
npm run dashboard  # regenerate dashboard.html from leads.db
npm run digest     # pipeline status snapshot in the terminal
```

## RAG: ingestion, retrieval, evals

The copilot's retrieval layer (slice 2) lives in [src/rag/](src/rag/): per-tenant
document ingestion into SQLite (sqlite-vec for vectors, FTS5 for keywords) with local
on-device embeddings (bge-small, no API key) and hybrid search fused via tuned RRF.
See [ADR-002](docs/adr/ADR-002-rag-storage-and-embeddings.md).

```sh
npm run ingest -- --tenant firelever PLAN.md README.md docs   # extract → chunk → embed → store
npm run search -- --tenant firelever "how many emails per day" # poke retrieval manually
npm run eval                                                   # golden-set evals + regression gate
```

Retrieval quality is measured, not vibed: [evals/retrieval.jsonl](evals/retrieval.jsonl)
holds golden queries; `npm run eval` reports recall@5 and MRR for keyword-only,
vector-only, and hybrid, records history, and fails on regression. Current: hybrid
86.4% recall@5 / 0.714 MRR vs the keyword baseline.

Grounded Q&A (slice 3) sits on top: `npm run ask -- --tenant firelever "question"`
answers with a citation after every claim and refuses when the corpus lacks the answer
([ADR-003](docs/adr/ADR-003-grounded-answering.md)). `npm run eval:qa` grades a
24-question golden set (16 answerable, 8 unanswerable) with an LLM judge plus
programmatic citation checks. Current (2026-07-08): refusal 100%, faithfulness 100%,
citation accuracy 100%, correctness 93.3%; 1 of 16 wrongly refused, traced to a known
retrieval miss on the PRD latency table, not an answering bug.

## Fine-tuned triage model

Slice 6 ([ADR-006](docs/adr/ADR-006-finetune-distillation.md), results in
[docs/04-finetune-benchmark.md](docs/04-finetune-benchmark.md)): Qwen2.5-7B
LoRA-distilled from the production Opus classifier on 300 synthetic emails, trained
on a $0.16/hr RunPod A5000, served locally via Ollama. On the human-labeled golden
set: **student 91.7% · teacher 100% · keyword baseline 83.3%**, at ~$0 vs ~$5 per
1,000 emails. Notable finding: the student fell for a prompt-injection email the
teacher resisted — distillation transfers the task, not the robustness.

```sh
npm run corpus            # generate + teacher-label training data
npm run gpu -- launch     # unattended RunPod training run
npm run eval:student      # benchmark the local model on the golden set
```

## Copilot server + web UI

Slice 5a ([ADR-005](docs/adr/ADR-005-hosted-api-and-ui.md)): a tenant-authenticated
HTTP API (Hono) over the RAG and triage stack, plus a customer-facing web interface —
grounded Q&A with expandable citations, document upload, and the triage review queue —
styled to the firelever.com brand.

```sh
npm run tenant -- create acme "Acme Freight"   # mints a flv_ API key (shown once)
npm run serve                                  # http://localhost:8787
```

Auth: per-tenant bearer keys, SHA-256 at rest, constant-time compare. Every route is
tenant-scoped; the growth pipeline (leads.db) is not exposed. Nothing sends email —
approving a triage draft only marks it approved.

## OCR for scanned PDFs

Scanned documents (no text layer) are handled by an OCR fallback
([ADR-007](docs/adr/ADR-007-ocr.md)): when a PDF's text layer is sparse, pages are
rasterized with mupdf (WASM, no native deps) and transcribed by Claude vision on
Haiku 4.5. Text-based PDFs skip this path entirely, so native documents stay fast and
free. Page cap via `OCR_MAX_PAGES` (default 30), with a logged warning on truncation.

## Email triage

Slice 4 ([ADR-004](docs/adr/ADR-004-email-triage.md)): inbound email is classified
(new business / support / vendor / recruiting / spam / other) and anything needing a
response gets a reply drafted from the knowledge base — grounded in retrieved sources,
with a low-confidence flag when the corpus lacks an answer. Nothing sends
automatically; approved drafts are copy-pasted into Gmail.

```sh
npm run triage -- --demo        # synthetic emails, no credentials needed
npm run triage -- --imap        # unseen Gmail messages (GMAIL_* in .env)
npm run triage:review           # approve / reject / ignore each draft
npm run eval:triage             # classification accuracy vs keyword baseline
```

Current eval (synthetic 24-email golden set, to be replaced with real labeled
traffic): 100% classification accuracy vs 83.3% keyword baseline, including a
prompt-injection email correctly filed as spam.

## MCP server

`npm run mcp` starts a read-only [MCP](https://modelcontextprotocol.io) server over
`leads.db` (tools: `search_leads`, `get_lead`, `pipeline_stats`). [.mcp.json](.mcp.json)
registers it for Claude Code automatically — open this repo and ask things like
"which approved leads scored above 80?". All writes (approve/reject/send) stay in the
human review CLI by design; see
[ADR-001](docs/adr/ADR-001-mcp-server-over-leads-db.md).

## Sending (Gmail free tier)

`npm run send` delivers approved sequences and manages day 0/3/7 follow-ups. Before a reply
check and every follow-up it searches the Gmail inbox via IMAP; a reply stops the sequence
and flags the lead. Setup:

1. Google Account > Security > 2-Step Verification > App passwords: create one.
2. Add to `.env`: `GMAIL_USER=you@gmail.com` and `GMAIL_APP_PASSWORD=...`
3. Put your business mailing address in `EMAIL_FOOTER` (src/config.ts). The sender refuses
   to run until it's set (CAN-SPAM).
4. Optional daily automation: `cp scripts/com.firelever.sender.plist ~/Library/LaunchAgents/ && launchctl load ~/Library/LaunchAgents/com.firelever.sender.plist`

## Dashboard

`npm run dashboard` writes `dashboard.html` (a self-contained snapshot of the pipeline),
which is published as a shareable web page for the team after each batch.

Leads live in `leads.db` (SQLite). Statuses: `new → enriched → scored → drafted → approved/rejected`, with `parked` for leads scoring below the threshold in [src/config.ts](src/config.ts).

## Guardrails

- Human approves every outbound message; approved emails are sent manually.
- All personalization must come from Enricher research — agents are instructed never to invent facts.
- ICP and scoring threshold are configured in [src/config.ts](src/config.ts).