lacspace-leads 0.3.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,13 +12,15 @@ That opens a browser, searches Maps for *"restaurants in Baneshwor, Kathmandu"*,
12
12
 
13
13
  - **Free & keyless** — uses a real browser (via [Playwright](https://playwright.dev)), not a paid Places API.
14
14
  - **Sweep a whole city** — comma-separate areas and it runs each search, then **merges and de-duplicates** into one list: `--area "Thamel,Baneshwor,Patan"`.
15
+ - **Accumulate a master list** — `--append` merges each run into your existing file and de-duplicates, so daily runs build one clean database.
15
16
  - **Rich enrichment** — visit each website to pull an **email** and links for **Facebook, Instagram, WhatsApp, LinkedIn, X, YouTube, TikTok and Telegram** — a few sites in parallel.
17
+ - **Verified emails** — `--verify-emails` checks each address's domain has **MX records** (no message sent) and tags it `valid`/`no-mx`; `--has-valid-email` keeps only deliverable ones.
16
18
  - **CRM-ready data** — normalise phones to **E.164** (`--country NP` → `+9779…`), and tidy website URLs (unwrap Google redirects, strip `utm_*`/`fbclid`).
17
19
  - **Any format** — JSON, NDJSON, CSV or Excel; write to a file or pipe to `stdout` with `-o -`.
18
20
  - **Pick your fields** — choose exactly what you collect, or a ready-made **preset** (`--preset outreach`).
19
- - **Sort & filter** — `--sort reviews --desc`, `--min-rating`, `--has-email`, and more.
20
- - **Permission-first** — it tells you what it's about to do and asks before opening a browser.
21
- - **Library too** — `import { searchLeads, searchLeadsBatch } from "lacspace-leads"`.
21
+ - **Sort & filter** — `--sort reviews --desc`, `--min-rating`, `--has-valid-email`, and more.
22
+ - **Robust & polite** — `--proxy`, `--retries`, `--jitter`, per-listing delays and a permission-first prompt before it opens a browser.
23
+ - **Library too** — `import { searchLeads, searchLeadsBatch, searchLeadsDetailed } from "lacspace-leads"`.
22
24
 
23
25
  ## Install
24
26
 
@@ -40,10 +42,11 @@ npx lacspace-leads [type] [options]
40
42
  | `--city <text>` | City, e.g. `Kathmandu`. Comma-separate for several. |
41
43
  | `--area <text>` | Area / neighbourhood, e.g. `Baneshwor`. Comma-separate to **sweep a whole city**. |
42
44
  | `-q, --query <text>` | Raw query, used verbatim (overrides city/area/type) |
43
- | `--fields <list>` | Columns: `name,category,rating,reviews,priceLevel,address,phone,website,email,facebook,instagram,whatsapp,linkedin,twitter,youtube,tiktok,telegram,plusCode,latitude,longitude,hours,mapsUrl` |
45
+ | `--fields <list>` | Columns: `name,category,rating,reviews,priceLevel,address,phone,website,email,facebook,instagram,whatsapp,linkedin,twitter,youtube,tiktok,telegram,emailStatus,plusCode,latitude,longitude,hours,mapsUrl` |
44
46
  | `--preset <name>` | Field bundle: `minimal` · `outreach` · `contact` · `geo` · `full` · `everything` |
45
47
  | `-f, --format <fmt>` | `json` · `ndjson` · `csv` · `xlsx` (default `json`) |
46
48
  | `-o, --out <file>` | Output file, or `-` for **stdout** (default: a slug + date) |
49
+ | `--append` | Merge into an existing output file — **accumulate + dedupe** across runs |
47
50
  | `--sheet <name>` | Excel sheet name (default `Leads`) |
48
51
  | `-n, --limit <n>` | Max listings **per search** (default `60`) |
49
52
  | `--total <n>` | Cap the merged result when sweeping several searches |
@@ -51,7 +54,10 @@ npx lacspace-leads [type] [options]
51
54
  | `--sort <key>` | `rating` · `reviews` · `name` · `priceLevel` (missing values last) |
52
55
  | `--desc` / `--asc` | Sort direction (default: `desc` for numbers, `asc` for name) |
53
56
  | `--delay <ms>` | Pause between listings (default `700`) |
57
+ | `--jitter` | Randomise the delay ±40% (more human) |
58
+ | `--retries <n>` | Retry a listing that fails to open (default `1`) |
54
59
  | `--max-time <s>` | Stop collecting after n seconds |
60
+ | `--proxy <url>` | Route the browser via a proxy (`http://user:pass@host:port`) |
55
61
  | `--lang <locale>` | Browser locale, e.g. `en-US`, `ne-NP` (default `en-US`) |
56
62
  | `--region <cc>` | Region bias for results, e.g. `np`, `us` |
57
63
  | `--headless` | Run the browser without a visible window |
@@ -64,6 +70,7 @@ npx lacspace-leads [type] [options]
64
70
  | `--emails` | Also find an email from each website |
65
71
  | `--socials` | Also find Facebook / Instagram / WhatsApp / LinkedIn / X / YouTube / TikTok / Telegram |
66
72
  | `--enrich` | Both of the above |
73
+ | `--verify-emails` | Check each email domain has **MX records** (implies `--emails`); adds an `emailStatus` column |
67
74
  | `--concurrency <n>` | How many websites to enrich in parallel (default `3`) |
68
75
 
69
76
  **Clean-up** (data quality):
@@ -82,7 +89,8 @@ npx lacspace-leads [type] [options]
82
89
  | `--has-phone` | Only leads with a phone |
83
90
  | `--has-website` | Only leads with a website |
84
91
  | `--has-email` | Only leads with an email (implies `--emails`) |
85
- | `--dedupe <key>` | `website` · `phone` · `name` · `none` (default `website`) |
92
+ | `--has-valid-email` | Only leads whose email passed **MX verification** (implies `--verify-emails`) |
93
+ | `--dedupe <key>` | `website` · `phone` · `name` · `smart` · `none` (default `website`; `--append` uses `smart` = website→phone→name) |
86
94
 
87
95
  Run with no arguments for an interactive walkthrough.
88
96
 
@@ -117,6 +125,21 @@ Skip spelling out `--fields` with a ready-made bundle:
117
125
  npx lacspace-leads salons --city Pokhara --preset outreach --country NP -f csv
118
126
  ```
119
127
 
128
+ ## Verify emails & build a master list
129
+
130
+ Chase down deliverable contacts and accumulate them over time:
131
+
132
+ ```bash
133
+ # Only businesses with an MX-verified email, as CSV
134
+ npx lacspace-leads dentists --city Pokhara --verify-emails --has-valid-email -f csv
135
+
136
+ # Run this daily — each run merges into master.csv and de-duplicates (smart key)
137
+ npx lacspace-leads cafes --city Kathmandu --area "Thamel,Baneshwor,Patan" \
138
+ -o master.csv --append
139
+ ```
140
+
141
+ `--verify-emails` does a DNS **MX lookup** on each email's domain (no message is sent) and adds an `emailStatus` column of `valid` · `no-mx` · `invalid-format`. `--append` reads the existing file back, merges, and de-duplicates with the **`smart`** key (website → phone → name) so even website-less businesses don't pile up on re-runs.
142
+
120
143
  ## Convert anything (JSON ↔ CSV ↔ Excel)
121
144
 
122
145
  A general converter is built in — it works on any tabular file, not just leads:
@@ -180,9 +203,13 @@ const leads = await searchLeadsBatch(
180
203
  | Export | Purpose |
181
204
  | --- | --- |
182
205
  | `searchLeads(options)` | Run one search, resolve to `Lead[]`. |
206
+ | `searchLeadsDetailed(options)` | Same, plus aggregate `stats` and `elapsedMs`. |
183
207
  | `searchLeadsBatch(queries, options)` | Run several searches and merge/dedupe/sort into one `Lead[]`. |
184
208
  | `searchLeadsMulti(options)` | Expand comma-separated `type`/`city`/`area` and run as a batch. |
185
209
  | `serialize(leads, format, fields?)` | Serialize to `{ data, binary }` for `json` / `ndjson` / `csv` / `xlsx`. |
210
+ | `computeStats(leads)` | Aggregate counts (`withPhone`, `withValidEmail`, `avgRating` …). |
211
+ | `verifyEmails(leads)` / `verifyEmail(email)` | MX-verify emails; also `emailFormatValid` / `emailDomain`. |
212
+ | `rowsToLeads(rows)` | Turn read-back rows (CSV/Excel/JSON) into `Lead[]` — powers `--append`. |
186
213
  | `toRows(leads, fields?)` | Header-keyed rows, for your own exporter. |
187
214
  | `enrichContacts(website)` / `extractEmails` / `extractSocials` | Website enrichment, on tap. |
188
215
  | `cleanWebsite` / `normalizePhone` / `sortLeads` | Pure data-cleaning helpers (unit-tested). |
@@ -190,7 +217,7 @@ const leads = await searchLeadsBatch(
190
217
  | `expandQueries` / `resolvePreset` / `FIELD_PRESETS` | Batch expansion + field presets. |
191
218
  | `composeQuery` / `mapsSearchUrl` / `normalizeFields` / `defaultFilename` | Query + helper utilities. |
192
219
 
193
- Everything is fully typed (`Lead`, `LeadField`, `SearchOptions`, `SortKey`, `OutputFormat`, `Contacts` …) and ships dual **ESM + CJS**.
220
+ The `onProgress` and `onLead` callbacks stream progress and each lead as it's found — handy for live UIs or crash-safe writing. Everything is fully typed (`Lead`, `LeadField`, `SearchOptions`, `LeadStats`, `EmailStatus`, `SortKey`, `OutputFormat`, `Contacts` …) and ships dual **ESM + CJS**.
194
221
 
195
222
  ## Please use it responsibly
196
223