lacspace-leads 0.3.0 → 1.3.0
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 +50 -8
- package/dist/cli.js +383 -100
- package/dist/lib.cjs +323 -91
- package/dist/lib.d.cts +134 -8
- package/dist/lib.d.ts +134 -8
- package/dist/lib.js +307 -86
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -12,13 +12,16 @@ 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
|
+
- **Search by radius** — centre on a coordinate and keep only what's within range: `--near "27.72,85.32" --radius 2km`. Each lead gets a `distanceKm`, sorted nearest-first.
|
|
16
|
+
- **Accumulate a master list** — `--append` merges each run into your existing file and de-duplicates, so daily runs build one clean database.
|
|
15
17
|
- **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.
|
|
18
|
+
- **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
19
|
- **CRM-ready data** — normalise phones to **E.164** (`--country NP` → `+9779…`), and tidy website URLs (unwrap Google redirects, strip `utm_*`/`fbclid`).
|
|
17
20
|
- **Any format** — JSON, NDJSON, CSV or Excel; write to a file or pipe to `stdout` with `-o -`.
|
|
18
21
|
- **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
|
-
- **
|
|
21
|
-
- **Library too** — `import { searchLeads, searchLeadsBatch } from "lacspace-leads"`.
|
|
22
|
+
- **Sort & filter** — `--sort reviews --desc`, `--min-rating`, `--has-valid-email`, and more.
|
|
23
|
+
- **Robust & polite** — `--proxy`, `--retries`, `--jitter`, per-listing delays and a permission-first prompt before it opens a browser.
|
|
24
|
+
- **Library too** — `import { searchLeads, searchLeadsBatch, searchLeadsDetailed } from "lacspace-leads"`.
|
|
22
25
|
|
|
23
26
|
## Install
|
|
24
27
|
|
|
@@ -40,18 +43,24 @@ npx lacspace-leads [type] [options]
|
|
|
40
43
|
| `--city <text>` | City, e.g. `Kathmandu`. Comma-separate for several. |
|
|
41
44
|
| `--area <text>` | Area / neighbourhood, e.g. `Baneshwor`. Comma-separate to **sweep a whole city**. |
|
|
42
45
|
| `-q, --query <text>` | Raw query, used verbatim (overrides city/area/type) |
|
|
43
|
-
| `--
|
|
46
|
+
| `--near <lat,lng>` | Centre the search on a coordinate (radius search) |
|
|
47
|
+
| `--radius <dist>` | Keep only leads within this of `--near`, e.g. `2km`, `500m`, `1mi` |
|
|
48
|
+
| `--fields <list>` | Columns: `name,category,rating,reviews,priceLevel,address,phone,website,email,facebook,instagram,whatsapp,linkedin,twitter,youtube,tiktok,telegram,emailStatus,plusCode,latitude,longitude,distanceKm,hours,mapsUrl` |
|
|
44
49
|
| `--preset <name>` | Field bundle: `minimal` · `outreach` · `contact` · `geo` · `full` · `everything` |
|
|
45
50
|
| `-f, --format <fmt>` | `json` · `ndjson` · `csv` · `xlsx` (default `json`) |
|
|
46
51
|
| `-o, --out <file>` | Output file, or `-` for **stdout** (default: a slug + date) |
|
|
52
|
+
| `--append` | Merge into an existing output file — **accumulate + dedupe** across runs |
|
|
47
53
|
| `--sheet <name>` | Excel sheet name (default `Leads`) |
|
|
48
54
|
| `-n, --limit <n>` | Max listings **per search** (default `60`) |
|
|
49
55
|
| `--total <n>` | Cap the merged result when sweeping several searches |
|
|
50
56
|
| `--no-details` | Skip opening each listing — names + Maps URLs only, much faster |
|
|
51
|
-
| `--sort <key>` | `rating` · `reviews` · `name` · `priceLevel` (missing values last) |
|
|
52
|
-
| `--desc` / `--asc` | Sort direction (default: `desc` for
|
|
57
|
+
| `--sort <key>` | `rating` · `reviews` · `name` · `priceLevel` · `distance` (missing values last) |
|
|
58
|
+
| `--desc` / `--asc` | Sort direction (default: `desc` for quality keys, `asc` for name/distance) |
|
|
53
59
|
| `--delay <ms>` | Pause between listings (default `700`) |
|
|
60
|
+
| `--jitter` | Randomise the delay ±40% (more human) |
|
|
61
|
+
| `--retries <n>` | Retry a listing that fails to open (default `1`) |
|
|
54
62
|
| `--max-time <s>` | Stop collecting after n seconds |
|
|
63
|
+
| `--proxy <url>` | Route the browser via a proxy (`http://user:pass@host:port`) |
|
|
55
64
|
| `--lang <locale>` | Browser locale, e.g. `en-US`, `ne-NP` (default `en-US`) |
|
|
56
65
|
| `--region <cc>` | Region bias for results, e.g. `np`, `us` |
|
|
57
66
|
| `--headless` | Run the browser without a visible window |
|
|
@@ -64,6 +73,7 @@ npx lacspace-leads [type] [options]
|
|
|
64
73
|
| `--emails` | Also find an email from each website |
|
|
65
74
|
| `--socials` | Also find Facebook / Instagram / WhatsApp / LinkedIn / X / YouTube / TikTok / Telegram |
|
|
66
75
|
| `--enrich` | Both of the above |
|
|
76
|
+
| `--verify-emails` | Check each email domain has **MX records** (implies `--emails`); adds an `emailStatus` column |
|
|
67
77
|
| `--concurrency <n>` | How many websites to enrich in parallel (default `3`) |
|
|
68
78
|
|
|
69
79
|
**Clean-up** (data quality):
|
|
@@ -82,7 +92,8 @@ npx lacspace-leads [type] [options]
|
|
|
82
92
|
| `--has-phone` | Only leads with a phone |
|
|
83
93
|
| `--has-website` | Only leads with a website |
|
|
84
94
|
| `--has-email` | Only leads with an email (implies `--emails`) |
|
|
85
|
-
| `--
|
|
95
|
+
| `--has-valid-email` | Only leads whose email passed **MX verification** (implies `--verify-emails`) |
|
|
96
|
+
| `--dedupe <key>` | `website` · `phone` · `name` · `smart` · `none` (default `website`; `--append` uses `smart` = website→phone→name) |
|
|
86
97
|
|
|
87
98
|
Run with no arguments for an interactive walkthrough.
|
|
88
99
|
|
|
@@ -100,6 +111,17 @@ npx lacspace-leads "coffee shop" \
|
|
|
100
111
|
npx lacspace-leads --type "gym,fitness studio" --city Pokhara --total 100 -f csv
|
|
101
112
|
```
|
|
102
113
|
|
|
114
|
+
## Search by radius
|
|
115
|
+
|
|
116
|
+
Centre the search on a coordinate and keep only what's within range — precise catchment-area targeting for delivery zones, field sales or store-radius research:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Restaurants within 2 km of a point, nearest first, with a distance column
|
|
120
|
+
npx lacspace-leads restaurants --near "27.7172,85.3240" --radius 2km -f csv
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Every kept lead gains a `distanceKm` column and results are sorted **nearest-first** by default (override with `--sort`). `--radius` accepts `km`, `m` or `mi` (a bare number is metres); omit it to just centre the search without a hard cutoff. Under the hood it points Google Maps at the coordinate, then filters by real great-circle (haversine) distance.
|
|
124
|
+
|
|
103
125
|
## Field presets
|
|
104
126
|
|
|
105
127
|
Skip spelling out `--fields` with a ready-made bundle:
|
|
@@ -117,6 +139,21 @@ Skip spelling out `--fields` with a ready-made bundle:
|
|
|
117
139
|
npx lacspace-leads salons --city Pokhara --preset outreach --country NP -f csv
|
|
118
140
|
```
|
|
119
141
|
|
|
142
|
+
## Verify emails & build a master list
|
|
143
|
+
|
|
144
|
+
Chase down deliverable contacts and accumulate them over time:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Only businesses with an MX-verified email, as CSV
|
|
148
|
+
npx lacspace-leads dentists --city Pokhara --verify-emails --has-valid-email -f csv
|
|
149
|
+
|
|
150
|
+
# Run this daily — each run merges into master.csv and de-duplicates (smart key)
|
|
151
|
+
npx lacspace-leads cafes --city Kathmandu --area "Thamel,Baneshwor,Patan" \
|
|
152
|
+
-o master.csv --append
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`--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.
|
|
156
|
+
|
|
120
157
|
## Convert anything (JSON ↔ CSV ↔ Excel)
|
|
121
158
|
|
|
122
159
|
A general converter is built in — it works on any tabular file, not just leads:
|
|
@@ -180,17 +217,22 @@ const leads = await searchLeadsBatch(
|
|
|
180
217
|
| Export | Purpose |
|
|
181
218
|
| --- | --- |
|
|
182
219
|
| `searchLeads(options)` | Run one search, resolve to `Lead[]`. |
|
|
220
|
+
| `searchLeadsDetailed(options)` | Same, plus aggregate `stats` and `elapsedMs`. |
|
|
183
221
|
| `searchLeadsBatch(queries, options)` | Run several searches and merge/dedupe/sort into one `Lead[]`. |
|
|
184
222
|
| `searchLeadsMulti(options)` | Expand comma-separated `type`/`city`/`area` and run as a batch. |
|
|
185
223
|
| `serialize(leads, format, fields?)` | Serialize to `{ data, binary }` for `json` / `ndjson` / `csv` / `xlsx`. |
|
|
224
|
+
| `computeStats(leads)` | Aggregate counts (`withPhone`, `withValidEmail`, `avgRating` …). |
|
|
225
|
+
| `verifyEmails(leads)` / `verifyEmail(email)` | MX-verify emails; also `emailFormatValid` / `emailDomain`. |
|
|
226
|
+
| `rowsToLeads(rows)` | Turn read-back rows (CSV/Excel/JSON) into `Lead[]` — powers `--append`. |
|
|
186
227
|
| `toRows(leads, fields?)` | Header-keyed rows, for your own exporter. |
|
|
187
228
|
| `enrichContacts(website)` / `extractEmails` / `extractSocials` | Website enrichment, on tap. |
|
|
188
229
|
| `cleanWebsite` / `normalizePhone` / `sortLeads` | Pure data-cleaning helpers (unit-tested). |
|
|
230
|
+
| `haversineMeters` / `parseLatLngPair` / `parseDistance` | Pure geo helpers for radius search. |
|
|
189
231
|
| `filterLeads` / `dedupeLeads` | Pure post-processing over any `Lead[]`. |
|
|
190
232
|
| `expandQueries` / `resolvePreset` / `FIELD_PRESETS` | Batch expansion + field presets. |
|
|
191
233
|
| `composeQuery` / `mapsSearchUrl` / `normalizeFields` / `defaultFilename` | Query + helper utilities. |
|
|
192
234
|
|
|
193
|
-
Everything is fully typed (`Lead`, `LeadField`, `SearchOptions`, `SortKey`, `OutputFormat`, `Contacts` …) and ships dual **ESM + CJS**.
|
|
235
|
+
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
236
|
|
|
195
237
|
## Please use it responsibly
|
|
196
238
|
|