open-local-audit 0.76.0 → 0.77.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +512 -497
  2. package/LICENSE +21 -21
  3. package/README.md +543 -538
  4. package/dist/audit.d.ts +2 -0
  5. package/dist/audit.js +13 -0
  6. package/dist/audit.js.map +1 -1
  7. package/dist/cli.js +37 -12
  8. package/dist/cli.js.map +1 -1
  9. package/dist/compare.d.ts +17 -1
  10. package/dist/compare.js +170 -6
  11. package/dist/compare.js.map +1 -1
  12. package/dist/contact.d.ts +7 -0
  13. package/dist/contact.js +106 -2
  14. package/dist/contact.js.map +1 -1
  15. package/dist/discovery.d.ts +1 -0
  16. package/dist/discovery.js +92 -6
  17. package/dist/discovery.js.map +1 -1
  18. package/dist/index.d.ts +4 -2
  19. package/dist/index.js +2 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/overture.js +28 -28
  22. package/dist/pdf.js +2 -1
  23. package/dist/pdf.js.map +1 -1
  24. package/dist/pitch-brief.d.ts +33 -0
  25. package/dist/pitch-brief.js +160 -0
  26. package/dist/pitch-brief.js.map +1 -0
  27. package/dist/report-pack.js +2 -1
  28. package/dist/report-pack.js.map +1 -1
  29. package/dist/reporters.js +4 -2
  30. package/dist/reporters.js.map +1 -1
  31. package/dist/rules.js +34 -0
  32. package/dist/rules.js.map +1 -1
  33. package/dist/shortlist-runner.d.ts +4 -0
  34. package/dist/shortlist-runner.js +10 -0
  35. package/dist/shortlist-runner.js.map +1 -1
  36. package/dist/tls-probe.d.ts +2 -0
  37. package/dist/tls-probe.js +46 -0
  38. package/dist/tls-probe.js.map +1 -0
  39. package/dist/types.d.ts +9 -0
  40. package/dist/website-enrichment.d.ts +3 -0
  41. package/dist/website-enrichment.js +27 -1
  42. package/dist/website-enrichment.js.map +1 -1
  43. package/dist/workflow.d.ts +1 -1
  44. package/dist/workflow.js +2 -2
  45. package/dist/workflow.js.map +1 -1
  46. package/docs/guides/free-discovery.md +151 -151
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -1,538 +1,543 @@
1
- # Open Local Audit
2
-
3
- Open Local Audit is an open-source website and local presence auditor for small businesses. It turns public website signals into practical reports for outreach, customer education, and implementation work.
4
-
5
- Find prospective clients without a Google API key or billing account, from a source checkout:
6
-
7
- Requires Node.js 22.19 or newer.
8
-
9
- ```bash
10
- npm install
11
- npm run build
12
- node dist/cli.js start
13
- ```
14
-
15
- Keyless discovery uses Overture Places open data and GeoNames city lookup, then reads public business websites for contact details and audit reports. Coverage varies by location; missing website data means **unknown**, not proof that a business has no website. See the [free discovery guide](./docs/guides/free-discovery.md) for source attribution, limits, and the four-country benchmark.
16
-
17
- `start` asks for a country, city, category, candidate count, website-audit cap, and a new output directory before running. Repeated `discover` and `start` searches can reuse a local business-result cache; the CLI shows its state, source release, and original retrieval time. Use `--refresh-cache` for a fresh lookup or `--no-cache` to bypass that cache.
18
-
19
- ## Current stage
20
-
21
- Published CLI. The project can run a single URL audit, an opt-in Playwright-rendered audit with screenshot evidence, optional Lighthouse category scoring, branded customer-facing reports, public contact readiness extraction, a plain-text batch URL list, or a profile-aware CSV batch file, optionally check same-origin links, and produce JSON, Markdown, HTML, PDF, or all standard report formats. Batch runs can use controlled concurrency, write per-site reports, add aggregate insight sections and contact/outreach rollups to the top-level index, and optionally export prospect CSV data for triage. Discovery runs can control Google Places result counts, cap website audits, write summary JSON, merge local review CSVs, explain opportunity scores, enrich outreach and public-contact columns, select a preferred manual outreach channel, and report exact duplicate lead groups plus advisory fuzzy duplicate review candidates. CRM-ready CSV exports can be validated locally before import, lead CSVs can be ranked into local shortlist reports with optional local review-state suppression, local review CSV state can be updated by lead key or shortlist input, and single-site report folders can be packaged for local customer sharing.
22
-
23
- ## Business purpose
24
-
25
- Open Local Audit produces evidence-backed mini audits for local businesses. The tool should be useful as open-source software while keeping paid value in implementation, redesign, maintenance, and custom reporting.
26
-
27
- ## Product principles
28
-
29
- - Evidence first: every recommendation should point to a concrete finding.
30
- - Ethical scanning: audit public URLs supplied by the user or found in an explicitly requested open-data search; avoid Google Maps scraping.
31
- - Small-business language: reports should be readable by non-technical owners.
32
- - Developer quality: deterministic checks, clear CLI output, tests, and release notes.
33
- - Commercial clarity: the open-source scanner is useful; paid work is execution and support.
34
-
35
- ## Proposed first stack
36
-
37
- - Runtime: Node.js with TypeScript.
38
- - CLI framework: `commander` or `cac`.
39
- - Validation: `zod`.
40
- - HTML parsing: `cheerio`.
41
- - Browser rendering: Playwright where static parsing is not enough.
42
- - Storage: no persistence by default; optional JSON/Markdown outputs.
43
- - Test runner: Vitest.
44
- - Package manager: npm unless implementation chooses pnpm before first commit.
45
-
46
- ## Documentation
47
-
48
- - [Technical architecture](./docs/technical-architecture.md)
49
- - [Security and ethics](./docs/security-and-ethics.md)
50
- - [Audit checklist](./docs/audit-checklist.md)
51
- - [Google Maps API key setup](./docs/google-maps-api-key.md)
52
- - [Workflow command architecture](./docs/architecture/workflow-command.md)
53
- - [Workflow preflight contract](./docs/architecture/workflow-preflight.md)
54
- - [Workflow plan contract](./docs/architecture/workflow-plan.md)
55
- - [Workflow resume contract](./docs/architecture/workflow-resume.md)
56
- - [Workflow status contract](./docs/architecture/workflow-status.md)
57
-
58
- ## Local development
59
-
60
- Install dependencies and run the checks:
61
-
62
- ```bash
63
- npm install
64
- npm test
65
- npm run lint
66
- npm run build
67
- npm run release-check
68
- ```
69
-
70
- `release-check` also packs the package, installs it into a temporary consumer project, verifies the published package version can be resolved, and runs `npm audit --omit=dev` in that consumer project.
71
-
72
- Run the CLI locally:
73
-
74
- ```bash
75
- npm start -- https://example.com --format markdown
76
- ```
77
-
78
- After building, run the compiled CLI:
79
-
80
- ```bash
81
- node dist/cli.js https://example.com --format json --pretty
82
- ```
83
-
84
- Run the published package:
85
-
86
- ```bash
87
- npx open-local-audit https://example.com --format markdown
88
- ```
89
-
90
- Run with an industry profile:
91
-
92
- ```bash
93
- open-local-audit https://example.com --profile dental --format markdown
94
- ```
95
-
96
- Supported profiles are `generic`, `dental`, `beauty`, `restaurant`, `contractor`, `lawyer`, `clinic`, `gym`, `hotel`, and `auto-service`.
97
-
98
- Render the page before auditing when static HTML is not enough:
99
-
100
- ```bash
101
- open-local-audit https://example.com --render --format markdown
102
- ```
103
-
104
- Capture a rendered homepage screenshot and add it as visual evidence:
105
-
106
- ```bash
107
- open-local-audit https://example.com --screenshot --format all --out-dir reports
108
- ```
109
-
110
- `--render` loads Playwright from the current project. Install it alongside the CLI when needed:
111
-
112
- ```bash
113
- npm install -D playwright
114
- ```
115
-
116
- Run Lighthouse category scoring:
117
-
118
- ```bash
119
- open-local-audit https://example.com --lighthouse --format html --out report.html
120
- ```
121
-
122
- `--lighthouse` runs Chrome through Lighthouse and adds performance, accessibility, best-practices, and SEO category scores to the report. It requires a local Chrome or Chromium runtime that Lighthouse can launch.
123
-
124
- Write standard report formats to a directory:
125
-
126
- ```bash
127
- open-local-audit https://example.com --format all --out-dir reports
128
- ```
129
-
130
- Write an HTML report:
131
-
132
- ```bash
133
- open-local-audit https://example.com --format html --out report.html
134
- ```
135
-
136
- Write a branded PDF report:
137
-
138
- ```bash
139
- open-local-audit https://example.com --format pdf --out report.pdf
140
- ```
141
-
142
- Apply report branding:
143
-
144
- ```bash
145
- open-local-audit https://example.com --brand-config brand.json --format pdf --out report.pdf
146
- ```
147
-
148
- Minimal `brand.json`:
149
-
150
- ```json
151
- {
152
- "name": "Example Audit Studio",
153
- "primaryColor": "#123456",
154
- "accentColor": "#2f7d5f",
155
- "footerText": "Prepared for outreach review",
156
- "contact": "hello@example.com"
157
- }
158
- ```
159
-
160
- Brand colors must use six-digit hex values. Branding applies to Markdown, HTML, and PDF reports.
161
-
162
- Run a batch audit from a text file:
163
-
164
- ```bash
165
- open-local-audit --input sites.txt --format all --out-dir reports
166
- ```
167
-
168
- Run a labeled CSV batch audit:
169
-
170
- ```bash
171
- open-local-audit --input sites.csv --format all --out-dir reports
172
- ```
173
-
174
- Run a profile-aware batch audit and write a prospect CSV export:
175
-
176
- ```bash
177
- open-local-audit --input sites.csv --profile dental --export-csv prospects.csv --format all --out-dir reports
178
- ```
179
-
180
- Run a focused batch triage index:
181
-
182
- ```bash
183
- open-local-audit --input sites.csv --format all --out-dir reports --segment dental --sort score-asc --top 25
184
- ```
185
-
186
- Write the selected batch index to an explicit automation JSON path:
187
-
188
- ```bash
189
- open-local-audit --input sites.csv --format markdown --out-dir reports --summary-json automation/batch-summary.json
190
- ```
191
-
192
- Run a controlled parallel batch audit:
193
-
194
- ```bash
195
- open-local-audit --input sites.csv --format all --out-dir reports --concurrency 3
196
- ```
197
-
198
- Capture screenshots during batch audits:
199
-
200
- ```bash
201
- open-local-audit --input sites.csv --screenshot --format all --out-dir reports
202
- ```
203
-
204
- Batch triage supports `--segment <segment>`, `--min-score <score>`, `--top <count>`, `--sort score-asc|severity-desc`, and `--concurrency <count>`. Use `--summary-json <path>` to write the same selected aggregate batch index as JSON independently of the report format. Batch runs can also write `--export-csv <path>` for prospect triage. Batch index reports include aggregate average score, profile breakdown, segment breakdown, frequent finding sections, contact rollups, and advisory outreach channel rollups. Batch CSV exports include contact confidence, preferred contact channel, and contactability reason when reports were audited successfully. Use `--export-preset crm` with `--export-csv` to write a CRM-ready local import CSV instead of the standard operator CSV.
205
-
206
- Every JSON audit report also carries an operator-only `dotnetStack` object that fingerprints ASP.NET from data the audit already fetched: the final response headers (`X-AspNet-Version`, `X-AspNetMvc-Version`, `X-Powered-By`, `Server`), `Set-Cookie` names (never values), and Web Forms, Blazor, and `.aspx` markers in the page HTML. It reports `stack` (`aspnet-webforms`, `aspnet-mvc`, `aspnet-framework`, `aspnet-core`, `blazor`, `aspnet-unknown`, or `none`), `legacyFramework` (true only for .NET Framework stacks), `confidence`, an optional `frameworkVersion` from `X-AspNet-Version` (the `X-AspNetMvc-Version` value stays in evidence only), and the matched evidence. IIS or `X-Powered-By: ASP.NET` alone is reported as `aspnet-unknown` with low confidence. Standard batch and discovery CSV exports add `dotnetStack` and `dotnetLegacyFramework` (`yes`/`no`) columns for filtering. The signal makes no extra requests, is not a finding, and does not change scores or the Markdown, HTML, or PDF reports.
207
-
208
- Supported CSV columns:
209
-
210
- ```csv
211
- url,label,segment,profile
212
- example.com,Example Clinic,dental,dental
213
- https://example.org/path,Example Salon,beauty,beauty
214
- ```
215
-
216
- Manual CSV lead discovery:
217
-
218
- ```bash
219
- open-local-audit discover --input places.csv --provider manual-csv --profile dental --out-dir reports/dental --export-csv leads.csv
220
- ```
221
-
222
- The `manual-csv` provider reads an operator-prepared CSV, resolves supplied website URLs, audits website-present rows through the existing batch pipeline, and writes `leads.csv` with `hasWebsite`, `websiteUrl`, `priority`, and `nextAction`. Use `--dry-run` to create the prospect CSV without auditing websites.
223
-
224
- Config-driven local workflow:
225
-
226
- ```bash
227
- open-local-audit workflow --config workflow.json
228
- ```
229
-
230
- Preflight a workflow without running it:
231
-
232
- ```bash
233
- open-local-audit workflow --config workflow.json --check
234
- open-local-audit workflow --config workflow.json --check --format json
235
- ```
236
-
237
- Terminal output is the default. A ready preflight exits `0`, including when it reports warnings; a blocked preflight exits `1`. Use `--format json` for automation, where standard output is one versioned JSON report.
238
-
239
- Preflight is read-only: it makes no API or website calls, creates or modifies no outputs, and performs no outreach, uploads, or CRM synchronization. For Google Places workflows it checks only that an API key is present, so it does not display a billing warning because it makes no API call. Filesystem access checks are advisory; workflow execution keeps its write-time containment checks as the authoritative safety boundary.
240
-
241
- Plan a workflow without running it:
242
-
243
- ```bash
244
- open-local-audit workflow --config workflow.json --plan
245
- open-local-audit workflow --config workflow.json --plan --format json
246
- ```
247
-
248
- `--check` answers whether a workflow can run. `--plan` includes that readiness result and explains the successful-path steps and states, artifact paths, declared network capability, and configuration-derived upper bounds. Planning performs no network calls and creates or modifies no files. It does not use a Google query or API key, estimate currency cost, or replace the workflow's authoritative write-time safety checks.
249
-
250
- Resume an interrupted workflow from its last verified stage:
251
-
252
- ```bash
253
- open-local-audit workflow --config workflow.json --resume
254
- ```
255
-
256
- Resume is explicit and validates `workflow-checkpoint.json`, the effective configuration, and required managed artifacts before invoking a stage. Completed stages are reused only when their artifacts are unchanged. An incomplete packaging stage reruns packaging for every selected lead.
257
-
258
- Minimal `workflow.json` for a manual CSV run:
259
-
260
- ```json
261
- {
262
- "version": 1,
263
- "outDir": "./workflow-output",
264
- "discovery": {
265
- "provider": "manual-csv",
266
- "input": "./places.csv",
267
- "profile": "dental"
268
- },
269
- "shortlist": {
270
- "top": 10
271
- }
272
- }
273
- ```
274
-
275
- The workflow runs discovery and then shortlisting from one strict JSON configuration. Version `1` is the only supported configuration version. Relative `outDir`, manual `input`, and optional review CSV paths resolve from the configuration file directory, so this example reads `places.csv` beside `workflow.json` and writes below `workflow-output/` beside it.
276
-
277
- The workflow owns predictable output paths below `outDir`: `reports/`, `leads.csv`, `discovery-summary.json`, `shortlist.csv`, `shortlist-summary.json`, `workflow-checkpoint.json`, `workflow-summary.json`, and `workflow-state.json`; it also writes `review-summary.json` when `review` is configured and `packages/` when `packageReports` is enabled. `workflow-state.json` is current lifecycle metadata for status inspection, not part of `WorkflowManagedPaths` or checkpoint outputs. Rerunning the same configuration replaces those managed outputs without deleting unrelated files.
278
-
279
- Configuration validation happens before output creation. Invalid configuration or a failed workflow stage returns exit code `1`; when execution reaches a managed stage, inspect `workflow-summary.json` for stage status and output paths. The workflow writes local files only: it does not send outreach, upload reports, or synchronize a CRM. A `google-places` discovery configuration requires `GOOGLE_MAPS_API_KEY` and can incur Google Maps Platform billing; the API key is not stored in the configuration.
280
-
281
- ### Check workflow status
282
-
283
- Inspect the latest persisted state without running the workflow:
284
-
285
- ```bash
286
- open-local-audit workflow --config workflow.json --status
287
- ```
288
-
289
- Use JSON for scripts and integrations:
290
-
291
- ```bash
292
- open-local-audit workflow --config workflow.json --status --format json
293
- ```
294
-
295
- The status is `not-started` when no state or checkpoint exists,
296
- `running-or-interrupted` when the last recorded run was not finished and its
297
- process liveness is unknown, `failed` after a controlled failure whose failure
298
- state was persisted, `completed` when every enabled stage succeeds, or
299
- `invalid` when local state, its
300
- configuration identity, a checkpoint, or required managed artifacts cannot be
301
- trusted.
302
-
303
- Status inspection is read-only: it makes no network calls, resolves no API
304
- keys, and does not create or change output files. `not-started`,
305
- `running-or-interrupted`, and `completed` exit `0`; `failed` and `invalid` exit
306
- `1`. Before following resume advice from a `running-or-interrupted` result,
307
- confirm that no workflow process is still active. If a controlled failure cannot
308
- persist its failure transition, status reports the last durable state, which may
309
- be `running-or-interrupted`; the workflow command still exits `1` for the
310
- underlying failure. Checkpoint correlation gates status resume advice only;
311
- direct `workflow --resume` validates its v1 checkpoint independently.
312
-
313
- Google Places lead discovery:
314
-
315
- ```bash
316
- GOOGLE_MAPS_API_KEY=your-key open-local-audit discover "guzellik salonu Umraniye" --provider google-places --profile beauty --out-dir reports/umraniye-beauty --export-csv leads.csv
317
- ```
318
-
319
- Control discovery cost and audit volume:
320
-
321
- ```bash
322
- GOOGLE_MAPS_API_KEY=your-key open-local-audit discover "dis klinigi Kadikoy" --provider google-places --limit 20 --max-audits 5 --summary-json discovery-summary.json --out-dir reports/kadikoy-dental --export-csv leads.csv
323
- ```
324
-
325
- Skip previously reviewed leads and keep only stronger opportunities:
326
-
327
- ```bash
328
- open-local-audit discover --input places.csv --provider manual-csv --suppression-list reviewed-leads.csv --min-opportunity-score 90 --dry-run --export-csv leads.csv
329
- ```
330
-
331
- Maintain a review queue and duplicate review report across reruns:
332
-
333
- ```bash
334
- open-local-audit discover --input places.csv --provider manual-csv --review-csv review.csv --duplicates-json duplicates.json --dry-run --export-csv leads.csv
335
- ```
336
-
337
- Write a CRM-ready local import CSV:
338
-
339
- ```bash
340
- open-local-audit discover --input places.csv --provider manual-csv --dry-run --export-csv crm-leads.csv --export-preset crm
341
- ```
342
-
343
- Validate a CRM-ready local import CSV:
344
-
345
- ```bash
346
- open-local-audit validate-export --input crm-leads.csv --preset crm
347
- open-local-audit validate-export --input crm-leads.csv --preset crm --format json
348
- ```
349
-
350
- `validate-export` checks the local CSV shape before a manual import. It reports missing CRM columns, missing `companyName`, missing `website`, missing `leadKey`, duplicate `leadKey` values, low or empty contact confidence, and rows that still need manual contact review. Markdown output is the default; JSON is available for automation. Any issue returns exit code `1`, while a clean file returns exit code `0`.
351
-
352
- Create a local lead shortlist from a discovery or CRM CSV export:
353
-
354
- ```bash
355
- open-local-audit shortlist --input leads.csv --out shortlist.md --top 20
356
- open-local-audit shortlist --input leads.csv --out shortlist.md --min-opportunity-score 80
357
- open-local-audit shortlist --input leads.csv --sort score-desc --out shortlist.md
358
- open-local-audit shortlist --input leads.csv --out shortlist.md --summary-json shortlist-summary.json
359
- open-local-audit shortlist --input leads.csv --segment dental --priority high --contact-confidence High --out shortlist.csv --format csv
360
- open-local-audit shortlist --input leads.csv --preferred-contact-channel email --out shortlist.json --format json
361
- open-local-audit shortlist --input leads.csv --exclude-review-status deferred --out shortlist.json --format json
362
- open-local-audit shortlist --input leads.csv --require-website --out shortlist.json --format json
363
- open-local-audit shortlist --input leads.csv --missing-website --out shortlist.json --format json
364
- open-local-audit shortlist --input leads.csv --unreviewed --out shortlist.json --format json
365
- open-local-audit shortlist --input leads.csv --reviewed-before 2026-06-20 --out shortlist.json --format json
366
- open-local-audit shortlist --input leads.csv --require-contact --out shortlist.json --format json
367
- open-local-audit shortlist --input leads.csv --missing-contact --out shortlist.json --format json
368
- open-local-audit shortlist --input leads.csv --require-report --out shortlist.json --format json
369
- open-local-audit shortlist --input leads.csv --missing-report --out shortlist.json --format json
370
- open-local-audit shortlist --input leads.csv --review-csv review.csv --review-status pending --out shortlist.json --format json
371
- open-local-audit shortlist --input crm-leads.csv --out shortlist.json --format json
372
- open-local-audit shortlist --input crm-leads.csv --out shortlist.csv --format csv
373
- open-local-audit shortlist --input leads.csv --review-csv review.csv --out shortlist.md
374
- ```
375
-
376
- `shortlist` ranks local CSV rows by `opportunityScore`, `priority`, `contactConfidence`, `score`, and company name. Use `--sort opportunity-desc|score-desc|company-asc|last-reviewed-asc` to change the ranking mode before top-N selection. Use `--summary-json` to write a separate automation summary with counts and selected lead identifiers. Use `--require-website` to keep only rows with a website, `--missing-website` to keep only rows without a website, `--unreviewed` to keep only rows whose normalized `lastReviewedAt` is empty, `--reviewed-before YYYY-MM-DD` to keep only valid date-only or explicit-zone ISO review timestamps strictly earlier than a valid calendar threshold, `--require-contact` to keep only rows with contact confidence above `None`, `--missing-contact` to keep only rows with contact confidence set to `None`, `--require-report` to keep only rows with a report path, and `--missing-report` to keep only rows without a report path. Equal, blank, invalid, and zone-less review timestamps do not match `--reviewed-before`. Use `--min-opportunity-score` to keep only stronger local opportunities. Use `--segment`, `--profile`, `--priority`, `--contact-confidence`, and `--preferred-contact-channel` for case-insensitive exact focus filters; supplied filters, including review-date filters, are combined with `AND` semantics. Use `--review-status` to keep only active leads with a matching review status after completed or suppressed review rows have been removed, or `--exclude-review-status` to remove a matching active review status from the local report. Filtering runs after review-state suppression and before sorting and top-N selection. With `--review-csv`, the command matches review rows by `leadKey`, normalized website, or company label; skips rows marked `rejected`, `contacted`, `not-fit`, `not_a_fit`, `do-not-contact`, or `suppressed`; and carries active `reviewStatus`, `reviewReason`, and `lastReviewedAt` values into Markdown, JSON, and CSV reports. Markdown output is the default; JSON is available for automation; CSV is available for spreadsheet review. The command only reads local CSV files and writes a local report. It does not mutate source files, call APIs, send outreach, or sync to a CRM.
377
-
378
- Update local review state by lead key:
379
-
380
- ```bash
381
- open-local-audit review --review-csv review.csv --lead-key url:https://lead.test --status contacted --reason "Email sent"
382
- open-local-audit review --review-csv review.csv --lead-key url:https://lead.test --status in-review --reviewed-at 2026-06-24T09:00:00Z
383
- open-local-audit review --review-csv review.csv --input shortlist.csv --status in-review --reason "Selected from shortlist"
384
- open-local-audit review --review-csv review.csv --input shortlist.json --status qualified --dry-run
385
- open-local-audit review --review-csv review.csv --summary --summary-json review-summary.json
386
- open-local-audit review --review-csv review.csv --summary --stale-before 2026-06-20
387
- ```
388
-
389
- `review` updates the exact `leadKey` row in a local review CSV, or appends a new row when the key is not present. Use `--input` with a shortlist CSV or JSON file to update every row that has a `leadKey`; duplicate and blank keys are skipped. Use `--dry-run` with `--input` to preview added and updated counts without writing the review CSV. Use `--summary` to print local queue counts by status, reviewed/unreviewed rows, invalid review dates, and oldest/newest review timestamps without updating the CSV. Use `--stale-before YYYY-MM-DD` with summary mode to count rows reviewed before a strict calendar date. Use `--summary-json` to write the same summary for automation, including `actionableLeadKeys` for the deduplicated queue and `actionableLeads` for each lead key with one or more `unreviewed`, `invalid-reviewed-at`, or `stale` reasons; `unreviewedLeadKeys` for rows without review dates; `invalidReviewedAtLeadKeys` for invalid review dates; and `staleLeadKeys` when stale rows have lead keys. The command preserves existing columns, adds missing `leadKey`, `reviewStatus`, `reviewReason`, and `lastReviewedAt` columns, and accepts `new`, `pending`, `in-review`, `qualified`, `contacted`, `rejected`, `not-fit`, `do-not-contact`, or `suppressed`. `--reviewed-at` defaults to the current time and is written as an ISO timestamp. The command only mutates the supplied local review CSV when summary mode is not used. It does not change lead source files, call APIs, send outreach, or sync to a CRM.
390
-
391
- Package an existing single-site report folder for local customer sharing:
392
-
393
- ```bash
394
- open-local-audit package-report --input reports/example-com --out packages/example-com
395
- ```
396
-
397
- `package-report` reads `open-local-audit-report.json` from the input folder, copies available JSON, Markdown, HTML, and PDF report artifacts into `reports/`, and writes `README.md`, `next-actions.md`, and `manifest.json`. It is local file packaging only; it does not upload reports, send outreach, or sync to a CRM.
398
-
399
- Compare a later audit of the same site with an earlier one to show progress:
400
-
401
- ```bash
402
- open-local-audit compare reports/before/example-com reports/after/example-com --format html --out progress.html
403
- ```
404
-
405
- `compare` reads two JSON reports, either as files or as report directories containing `open-local-audit-report.json`, and matches findings by rule id. It lists fixed issues, issues that are still open, and new issues, with category and overall score changes, as Markdown (default), JSON, or HTML; `--brand-config` applies report branding. Both reports must be for the same site (a leading `www.` is ignored) and the same profile, and the earlier report must come first. Rules added in a newer release appear as new issues, so compare reports produced by the same Open Local Audit version when the result goes to a customer. Run both audits with the same options too: a finding that only some options produce, such as broken links with `--check-links`, shows as fixed when the later audit skips that check. Comparison is local only and makes no network calls.
406
-
407
- The `google-places` provider is opt-in and requires `GOOGLE_MAPS_API_KEY`. It uses the official Places Text Search API and requests only `places.id`, `places.displayName`, and `places.websiteUri`. It does not scrape Google Maps, collect reviews/photos/ratings, send outreach, or store raw Places responses. Google Maps Platform billing and quota limits apply to API use, and the CLI prints a billing warning when this provider is selected.
408
-
409
- Discovery CSV exports include `leadKey`, `opportunityScore`, `opportunityReasons`, `pitchAngle`, `recommendedOffer`, `estimatedNeed`, `outreachPriorityReason`, website-derived public contact columns, manual outreach handoff columns, and review columns for local triage. Contact columns are populated only from audited public website HTML and include `publicEmail`, `publicPhone`, `whatsappUrl`, `contactPageUrl`, `socialProfiles`, `contactConfidence`, and `contactSource`. Handoff columns include `preferredContactChannel`, `outreachAction`, and `contactabilityReason`; they are advisory only and do not send outreach. Duplicate review JSON includes exact `duplicateGroups` and advisory `fuzzyDuplicateGroups` with confidence and matching reasons for local operator review. Fuzzy duplicate candidates do not auto-suppress leads, change review status, send outreach, or sync to a CRM. The CRM export preset writes `companyName`, `website`, `segment`, `profile`, `priority`, `score`, `opportunityScore`, `topFinding`, `contactConfidence`, `preferredContactChannel`, `contactabilityReason`, `publicEmail`, `publicPhone`, `contactPageUrl`, `source`, `leadKey`, and `reportPath`; it is a local import file only and does not call CRM APIs. A suppression list can reuse a prior discovery CSV or a smaller CSV with `leadKey`, `sourceId`, `websiteUrl`, or `label` plus optional `reviewStatus`, `reviewReason`, and `lastReviewedAt` columns. Rows marked `rejected`, `contacted`, `not-fit`, `do-not-contact`, or `suppressed` are skipped before audits run. `--review-csv` preserves prior operator decisions and adds new leads as `pending`. Spreadsheet formula-like cell values are neutralized before export, so values such as phone numbers starting with `+` may be prefixed for spreadsheet safety.
410
-
411
- See [Google Maps API key setup](./docs/google-maps-api-key.md) for local environment setup.
412
-
413
- Check same-origin links and fail CI when high-severity issues are found:
414
-
415
- ```bash
416
- open-local-audit https://example.com --check-links --max-pages 10 --fail-on high --format all --out-dir reports
417
- ```
418
-
419
- ## First implementation milestone
420
-
421
- The first implementation milestone is a CLI that accepts one URL and outputs:
422
-
423
- - JSON report.
424
- - Markdown report.
425
- - HTML report.
426
- - PDF report.
427
- - Executive Summary section in customer-facing reports.
428
- - Contact Readiness section in customer-facing reports.
429
- - Optional report branding with `--brand-config`.
430
- - Combined JSON, Markdown, and HTML report output with `--format all --out-dir`.
431
- - Batch input files with per-site report folders.
432
- - CSV batch input with optional labels and segments.
433
- - Aggregate batch index reports for prospect triage.
434
- - Batch index filtering, sorting, and top-N triage controls.
435
- - Explicit batch automation JSON output with `--summary-json`.
436
- - Controlled parallel batch audits with `--concurrency`.
437
- - Batch index insights for average score, profile breakdown, segment breakdown, and frequent findings.
438
- - Batch contact and outreach rollups for audited public website contact readiness.
439
- - CRM-ready local CSV export preset for batch and discovery exports.
440
- - Local CRM export validation with Markdown and JSON issue reports.
441
- - Local lead shortlist reports from discovery and CRM CSV exports.
442
- - Local shortlist sort modes with `--sort`.
443
- - Local shortlist automation summaries with `--summary-json`.
444
- - Local shortlist website-present filtering with `--require-website`.
445
- - Local shortlist missing-website filtering with `--missing-website`.
446
- - Local shortlist unreviewed filtering with `--unreviewed`.
447
- - Local shortlist re-review filtering with `--reviewed-before`.
448
- - Local shortlist contact-ready filtering with `--require-contact`.
449
- - Local shortlist missing-contact filtering with `--missing-contact`.
450
- - Local shortlist report-ready filtering with `--require-report`.
451
- - Local shortlist missing-report filtering with `--missing-report`.
452
- - Local shortlist preferred-contact-channel filtering with `--preferred-contact-channel`.
453
- - Local shortlist review-state suppression with `--review-csv`.
454
- - Local shortlist opportunity-score filtering with `--min-opportunity-score`.
455
- - Local shortlist focus filtering by segment, profile, priority, and contact confidence.
456
- - Local shortlist active review-status filtering with `--review-status`.
457
- - Local shortlist active review-status exclusion with `--exclude-review-status`.
458
- - Spreadsheet-ready local shortlist CSV output with `--format csv`.
459
- - Local review CSV status upserts with `review`.
460
- - Bulk local review CSV status upserts from shortlist CSV and JSON files.
461
- - Local review CSV queue summaries with `review --summary`.
462
- - Local stale review counts with `review --summary --stale-before`.
463
- - Local report packaging with shareable summaries and copied report artifacts.
464
- - Industry profiles for generic, dental, beauty, restaurant, contractor, lawyer, clinic, gym, hotel, and auto-service audits.
465
- - Profile-specific findings for dental, beauty, restaurant, and contractor conversion/trust signals.
466
- - Prospect CSV export with profile, score, top finding, contact handoff, report path, and error columns.
467
- - Discovery CSV contact enrichment from audited public website HTML.
468
- - Discovery CSV outreach handoff fields for manual next-channel selection.
469
- - Advisory fuzzy duplicate lead review candidates for discovery reruns.
470
- - Duplicate review output remains local operator guidance and does not auto-suppress, send outreach, or sync to CRM systems.
471
- - Optional rendered DOM audits with `--render`.
472
- - Optional rendered screenshot evidence with `--screenshot`.
473
- - Optional Lighthouse category scoring with `--lighthouse`.
474
- - Score summary.
475
- - Evidence table.
476
- - Optional same-origin link checks.
477
- - Terminal summary when reports are written to files.
478
- - CI-friendly exit codes with `--fail-on`.
479
- - Clear owner-readable recommendations.
480
- - Structured-data quality, address, opening-hours, service-location, CTA, and placeholder-copy checks.
481
- - Trust and conversion checks for current date signals, review cues, service detail depth, brand icons, and placeholder social links.
482
-
483
- Example target command:
484
-
485
- ```bash
486
- open-local-audit https://example.com --format markdown --out report.md
487
- ```
488
-
489
- Example report artifacts are available under [`examples/reports`](./examples/reports).
490
-
491
- ## Known limits
492
-
493
- - `--render` requires Playwright in the calling project and a working browser runtime.
494
- - `--screenshot` uses the rendered audit path, requires `--out-dir`, and also requires Playwright.
495
- - `--lighthouse` requires a local Chrome or Chromium runtime that Lighthouse can launch.
496
- - `--format pdf` is supported for single URL audits and requires `--out` or `--out-dir`.
497
- - `--brand-config` reads local JSON only; it does not fetch remote assets or upload report data.
498
- - Batch triage options apply to the aggregate batch index, not individual per-site report contents.
499
- - Batch `--summary-json` writes the selected local aggregate index only and requires `--input`.
500
- - Batch contact and outreach rollups are advisory local triage metadata; they do not send outreach or sync to a CRM.
501
- - `--export-preset crm` changes CSV columns only; it does not create, update, or sync CRM records.
502
- - `validate-export` checks local CSV files only; it does not create, update, or sync CRM records.
503
- - `shortlist` ranks local CSV files only; it does not call APIs, send outreach, or sync CRM records.
504
- - `shortlist --sort` changes local report ordering only; it does not update source or review CSV files.
505
- - `shortlist --summary-json` writes local automation metadata only; it does not update source or review CSV files.
506
- - `shortlist --require-website` filters local report output only; it does not update source or review CSV files.
507
- - `shortlist --missing-website` filters local report output only; it does not update source or review CSV files.
508
- - `shortlist --unreviewed` filters local report output only; it does not update source or review CSV files.
509
- - `shortlist --reviewed-before` requires an explicit date threshold and filters local report output only; it does not calculate relative review age or update source or review CSV files.
510
- - `shortlist --require-contact` filters local report output only; it does not update source or review CSV files.
511
- - `shortlist --missing-contact` filters local report output only; it does not update source or review CSV files.
512
- - `shortlist --require-report` filters local report output only; it does not update source or review CSV files.
513
- - `shortlist --missing-report` filters local report output only; it does not update source or review CSV files.
514
- - `shortlist --preferred-contact-channel` filters local report output only; it does not update source or review CSV files.
515
- - `shortlist --review-csv` reads review state for suppression and report context only; it does not mutate the review CSV.
516
- - `shortlist --min-opportunity-score` filters local report output only; it does not update source lead files.
517
- - Shortlist focus filters use case-insensitive exact matching and do not update source lead files.
518
- - `shortlist --review-status` filters local report output only; it does not update source or review CSV files.
519
- - `shortlist --exclude-review-status` filters local report output only; it does not update source or review CSV files.
520
- - `shortlist --format csv` writes a local spreadsheet review file only; it does not import or sync records.
521
- - `review` mutates only the supplied local review CSV by exact `leadKey`; bulk `--input` mode reads shortlist CSV or JSON files for lead keys only. `review --summary`, `--summary-json`, and `--stale-before` read local review state only. The command does not update source lead files, call APIs, send outreach, or sync CRM records.
522
- - `package-report` packages local files only; it does not upload reports, send outreach, or sync CRM records.
523
- - `--export-csv` is supported for batch audits and discovery exports.
524
- - `discover --provider google-places` requires `GOOGLE_MAPS_API_KEY` and may incur Google Maps Platform billing.
525
- - `--limit` caps Google Places candidates at 50; it does not paginate beyond one Text Search request.
526
- - `--max-audits` limits website audits only; all discovered candidates still appear in `leads.csv`.
527
- - For Overture, `--audit-priority missing-contact` prioritizes missing source email, then phone; the default `source-order` preserves existing selection. Standard exports explain each selection and include business–website identity evidence. Conflicting sites contribute no website contacts or audit scores; uncertain matches require manual review. See the [free discovery guide](./docs/guides/free-discovery.md#website-enrichment).
528
- - `--suppression-list` uses exact lead identity matching; source IDs are preferred, then normalized website URLs, then normalized labels.
529
- - `--review-csv` is local operator state only; it does not send outreach or sync to a CRM.
530
- - `--duplicates-json` reports exact duplicate lead groups and advisory fuzzy duplicate candidates for manual review; it does not auto-suppress rows or update review decisions.
531
- - Batch input requires `--out-dir` and cannot be combined with a positional URL.
532
- - Industry profiles are deterministic vertical heuristics, not a replacement for a human review of each business model.
533
- - Higher `--concurrency` values can increase network load against audited sites; use conservative values for prospect batches.
534
- - Rule checks are deterministic heuristics, so they can miss or over-flag site-specific markup.
535
-
536
- ## Releases
537
-
538
- Open Local Audit is published under the MIT license on GitHub and npm. Release history is available in [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases. Every release runs lint, tests, build, audit, package-content checks, and a fresh consumer install audit through GitHub Actions.
1
+ # Open Local Audit
2
+
3
+ Open Local Audit is an open-source website and local presence auditor for small businesses. It turns public website signals into practical reports for outreach, customer education, and implementation work.
4
+
5
+ Find prospective clients without a Google API key or billing account, from a source checkout:
6
+
7
+ Requires Node.js 22.19 or newer.
8
+
9
+ ```bash
10
+ npm install
11
+ npm run build
12
+ node dist/cli.js start
13
+ ```
14
+
15
+ Keyless discovery uses Overture Places open data and GeoNames city lookup, then reads public business websites for contact details and audit reports. Coverage varies by location; missing website data means **unknown**, not proof that a business has no website. See the [free discovery guide](./docs/guides/free-discovery.md) for source attribution, limits, and the four-country benchmark.
16
+
17
+ `start` asks for a country, city, category, candidate count, website-audit cap, and a new output directory before running. Repeated `discover` and `start` searches can reuse a local business-result cache; the CLI shows its state, source release, and original retrieval time. Use `--refresh-cache` for a fresh lookup or `--no-cache` to bypass that cache.
18
+
19
+ ## Current stage
20
+
21
+ Published CLI. The project can run a single URL audit, an opt-in Playwright-rendered audit with screenshot evidence, optional Lighthouse category scoring, branded customer-facing reports, public contact readiness extraction, a plain-text batch URL list, or a profile-aware CSV batch file, optionally check same-origin links, and produce JSON, Markdown, HTML, PDF, or all standard report formats. Batch runs can use controlled concurrency, write per-site reports, add aggregate insight sections and contact/outreach rollups to the top-level index, and optionally export prospect CSV data for triage. Discovery runs can control Google Places result counts, cap website audits, write summary JSON, merge local review CSVs, explain opportunity scores, enrich outreach and public-contact columns, select a preferred manual outreach channel, and report exact duplicate lead groups plus advisory fuzzy duplicate review candidates. CRM-ready CSV exports can be validated locally before import, lead CSVs can be ranked into local shortlist reports with optional local review-state suppression, local review CSV state can be updated by lead key or shortlist input, and single-site report folders can be packaged for local customer sharing.
22
+
23
+ ## Business purpose
24
+
25
+ Open Local Audit produces evidence-backed mini audits for local businesses. The tool should be useful as open-source software while keeping paid value in implementation, redesign, maintenance, and custom reporting.
26
+
27
+ ## Product principles
28
+
29
+ - Evidence first: every recommendation should point to a concrete finding.
30
+ - Ethical scanning: audit public URLs supplied by the user or found in an explicitly requested open-data search; avoid Google Maps scraping.
31
+ - Small-business language: reports should be readable by non-technical owners.
32
+ - Developer quality: deterministic checks, clear CLI output, tests, and release notes.
33
+ - Commercial clarity: the open-source scanner is useful; paid work is execution and support.
34
+
35
+ ## Proposed first stack
36
+
37
+ - Runtime: Node.js with TypeScript.
38
+ - CLI framework: `commander` or `cac`.
39
+ - Validation: `zod`.
40
+ - HTML parsing: `cheerio`.
41
+ - Browser rendering: Playwright where static parsing is not enough.
42
+ - Storage: no persistence by default; optional JSON/Markdown outputs.
43
+ - Test runner: Vitest.
44
+ - Package manager: npm unless implementation chooses pnpm before first commit.
45
+
46
+ ## Documentation
47
+
48
+ - [Technical architecture](./docs/technical-architecture.md)
49
+ - [Security and ethics](./docs/security-and-ethics.md)
50
+ - [Audit checklist](./docs/audit-checklist.md)
51
+ - [Google Maps API key setup](./docs/google-maps-api-key.md)
52
+ - [Workflow command architecture](./docs/architecture/workflow-command.md)
53
+ - [Workflow preflight contract](./docs/architecture/workflow-preflight.md)
54
+ - [Workflow plan contract](./docs/architecture/workflow-plan.md)
55
+ - [Workflow resume contract](./docs/architecture/workflow-resume.md)
56
+ - [Workflow status contract](./docs/architecture/workflow-status.md)
57
+
58
+ ## Local development
59
+
60
+ Install dependencies and run the checks:
61
+
62
+ ```bash
63
+ npm install
64
+ npm test
65
+ npm run lint
66
+ npm run build
67
+ npm run release-check
68
+ ```
69
+
70
+ `release-check` also packs the package, installs it into a temporary consumer project, verifies the published package version can be resolved, and runs `npm audit --omit=dev` in that consumer project.
71
+
72
+ Run the CLI locally:
73
+
74
+ ```bash
75
+ npm start -- https://example.com --format markdown
76
+ ```
77
+
78
+ After building, run the compiled CLI:
79
+
80
+ ```bash
81
+ node dist/cli.js https://example.com --format json --pretty
82
+ ```
83
+
84
+ Run the published package:
85
+
86
+ ```bash
87
+ npx open-local-audit https://example.com --format markdown
88
+ ```
89
+
90
+ Run with an industry profile:
91
+
92
+ ```bash
93
+ open-local-audit https://example.com --profile dental --format markdown
94
+ ```
95
+
96
+ Supported profiles are `generic`, `dental`, `beauty`, `restaurant`, `contractor`, `lawyer`, `clinic`, `gym`, `hotel`, and `auto-service`.
97
+
98
+ Render the page before auditing when static HTML is not enough:
99
+
100
+ ```bash
101
+ open-local-audit https://example.com --render --format markdown
102
+ ```
103
+
104
+ Capture a rendered homepage screenshot and add it as visual evidence:
105
+
106
+ ```bash
107
+ open-local-audit https://example.com --screenshot --format all --out-dir reports
108
+ ```
109
+
110
+ `--render` loads Playwright from the current project. Install it alongside the CLI when needed:
111
+
112
+ ```bash
113
+ npm install -D playwright
114
+ ```
115
+
116
+ Run Lighthouse category scoring:
117
+
118
+ ```bash
119
+ open-local-audit https://example.com --lighthouse --format html --out report.html
120
+ ```
121
+
122
+ `--lighthouse` runs Chrome through Lighthouse and adds performance, accessibility, best-practices, and SEO category scores to the report. It requires a local Chrome or Chromium runtime that Lighthouse can launch.
123
+
124
+ Write standard report formats to a directory:
125
+
126
+ ```bash
127
+ open-local-audit https://example.com --format all --out-dir reports
128
+ ```
129
+
130
+ Write an HTML report:
131
+
132
+ ```bash
133
+ open-local-audit https://example.com --format html --out report.html
134
+ ```
135
+
136
+ Write a branded PDF report:
137
+
138
+ ```bash
139
+ open-local-audit https://example.com --format pdf --out report.pdf
140
+ ```
141
+
142
+ Apply report branding:
143
+
144
+ ```bash
145
+ open-local-audit https://example.com --brand-config brand.json --format pdf --out report.pdf
146
+ ```
147
+
148
+ Minimal `brand.json`:
149
+
150
+ ```json
151
+ {
152
+ "name": "Example Audit Studio",
153
+ "primaryColor": "#123456",
154
+ "accentColor": "#2f7d5f",
155
+ "footerText": "Prepared for outreach review",
156
+ "contact": "hello@example.com"
157
+ }
158
+ ```
159
+
160
+ Brand colors must use six-digit hex values. Branding applies to Markdown, HTML, and PDF reports.
161
+
162
+ Run a batch audit from a text file:
163
+
164
+ ```bash
165
+ open-local-audit --input sites.txt --format all --out-dir reports
166
+ ```
167
+
168
+ Run a labeled CSV batch audit:
169
+
170
+ ```bash
171
+ open-local-audit --input sites.csv --format all --out-dir reports
172
+ ```
173
+
174
+ Run a profile-aware batch audit and write a prospect CSV export:
175
+
176
+ ```bash
177
+ open-local-audit --input sites.csv --profile dental --export-csv prospects.csv --format all --out-dir reports
178
+ ```
179
+
180
+ Run a focused batch triage index:
181
+
182
+ ```bash
183
+ open-local-audit --input sites.csv --format all --out-dir reports --segment dental --sort score-asc --top 25
184
+ ```
185
+
186
+ Write the selected batch index to an explicit automation JSON path:
187
+
188
+ ```bash
189
+ open-local-audit --input sites.csv --format markdown --out-dir reports --summary-json automation/batch-summary.json
190
+ ```
191
+
192
+ Run a controlled parallel batch audit:
193
+
194
+ ```bash
195
+ open-local-audit --input sites.csv --format all --out-dir reports --concurrency 3
196
+ ```
197
+
198
+ Capture screenshots during batch audits:
199
+
200
+ ```bash
201
+ open-local-audit --input sites.csv --screenshot --format all --out-dir reports
202
+ ```
203
+
204
+ Batch triage supports `--segment <segment>`, `--min-score <score>`, `--top <count>`, `--sort score-asc|severity-desc`, and `--concurrency <count>`. Use `--summary-json <path>` to write the same selected aggregate batch index as JSON independently of the report format. Batch runs can also write `--export-csv <path>` for prospect triage. Batch index reports include aggregate average score, profile breakdown, segment breakdown, frequent finding sections, contact rollups, and advisory outreach channel rollups. Batch CSV exports include contact confidence, preferred contact channel, and contactability reason when reports were audited successfully. Use `--export-preset crm` with `--export-csv` to write a CRM-ready local import CSV instead of the standard operator CSV.
205
+
206
+ Every JSON audit report also carries an operator-only `dotnetStack` object that fingerprints ASP.NET from data the audit already fetched: the final response headers (`X-AspNet-Version`, `X-AspNetMvc-Version`, `X-Powered-By`, `Server`), `Set-Cookie` names (never values), and Web Forms, Blazor, and `.aspx` markers in the page HTML. It reports `stack` (`aspnet-webforms`, `aspnet-mvc`, `aspnet-framework`, `aspnet-core`, `blazor`, `aspnet-unknown`, or `none`), `legacyFramework` (true only for .NET Framework stacks), `confidence`, an optional `frameworkVersion` from `X-AspNet-Version` (the `X-AspNetMvc-Version` value stays in evidence only), and the matched evidence. IIS or `X-Powered-By: ASP.NET` alone is reported as `aspnet-unknown` with low confidence. Standard batch and discovery CSV exports add `dotnetStack` and `dotnetLegacyFramework` (`yes`/`no`) columns for filtering. The signal makes no extra requests, is not a finding, and does not change scores or the Markdown, HTML, or PDF reports.
207
+
208
+ Supported CSV columns:
209
+
210
+ ```csv
211
+ url,label,segment,profile
212
+ example.com,Example Clinic,dental,dental
213
+ https://example.org/path,Example Salon,beauty,beauty
214
+ ```
215
+
216
+ Manual CSV lead discovery:
217
+
218
+ ```bash
219
+ open-local-audit discover --input places.csv --provider manual-csv --profile dental --out-dir reports/dental --export-csv leads.csv
220
+ ```
221
+
222
+ The `manual-csv` provider reads an operator-prepared CSV, resolves supplied website URLs, audits website-present rows through the existing batch pipeline, and writes `leads.csv` with `hasWebsite`, `websiteUrl`, `priority`, and `nextAction`. Use `--dry-run` to create the prospect CSV without auditing websites.
223
+
224
+ Config-driven local workflow:
225
+
226
+ ```bash
227
+ open-local-audit workflow --config workflow.json
228
+ ```
229
+
230
+ Preflight a workflow without running it:
231
+
232
+ ```bash
233
+ open-local-audit workflow --config workflow.json --check
234
+ open-local-audit workflow --config workflow.json --check --format json
235
+ ```
236
+
237
+ Terminal output is the default. A ready preflight exits `0`, including when it reports warnings; a blocked preflight exits `1`. Use `--format json` for automation, where standard output is one versioned JSON report.
238
+
239
+ Preflight is read-only: it makes no API or website calls, creates or modifies no outputs, and performs no outreach, uploads, or CRM synchronization. For Google Places workflows it checks only that an API key is present, so it does not display a billing warning because it makes no API call. Filesystem access checks are advisory; workflow execution keeps its write-time containment checks as the authoritative safety boundary.
240
+
241
+ Plan a workflow without running it:
242
+
243
+ ```bash
244
+ open-local-audit workflow --config workflow.json --plan
245
+ open-local-audit workflow --config workflow.json --plan --format json
246
+ ```
247
+
248
+ `--check` answers whether a workflow can run. `--plan` includes that readiness result and explains the successful-path steps and states, artifact paths, declared network capability, and configuration-derived upper bounds. Planning performs no network calls and creates or modifies no files. It does not use a Google query or API key, estimate currency cost, or replace the workflow's authoritative write-time safety checks.
249
+
250
+ Resume an interrupted workflow from its last verified stage:
251
+
252
+ ```bash
253
+ open-local-audit workflow --config workflow.json --resume
254
+ ```
255
+
256
+ Resume is explicit and validates `workflow-checkpoint.json`, the effective configuration, and required managed artifacts before invoking a stage. Completed stages are reused only when their artifacts are unchanged. An incomplete packaging stage reruns packaging for every selected lead.
257
+
258
+ Minimal `workflow.json` for a manual CSV run:
259
+
260
+ ```json
261
+ {
262
+ "version": 1,
263
+ "outDir": "./workflow-output",
264
+ "discovery": {
265
+ "provider": "manual-csv",
266
+ "input": "./places.csv",
267
+ "profile": "dental"
268
+ },
269
+ "shortlist": {
270
+ "top": 10
271
+ }
272
+ }
273
+ ```
274
+
275
+ The workflow runs discovery and then shortlisting from one strict JSON configuration. Version `1` is the only supported configuration version. Relative `outDir`, manual `input`, and optional review CSV paths resolve from the configuration file directory, so this example reads `places.csv` beside `workflow.json` and writes below `workflow-output/` beside it.
276
+
277
+ The workflow owns predictable output paths below `outDir`: `reports/`, `leads.csv`, `discovery-summary.json`, `shortlist.csv`, `shortlist-summary.json`, `workflow-checkpoint.json`, `workflow-summary.json`, and `workflow-state.json`; it also writes `review-summary.json` when `review` is configured and `packages/` when `packageReports` is enabled. `workflow-state.json` is current lifecycle metadata for status inspection, not part of `WorkflowManagedPaths` or checkpoint outputs. Rerunning the same configuration replaces those managed outputs without deleting unrelated files.
278
+
279
+ Configuration validation happens before output creation. Invalid configuration or a failed workflow stage returns exit code `1`; when execution reaches a managed stage, inspect `workflow-summary.json` for stage status and output paths. The workflow writes local files only: it does not send outreach, upload reports, or synchronize a CRM. A `google-places` discovery configuration requires `GOOGLE_MAPS_API_KEY` and can incur Google Maps Platform billing; the API key is not stored in the configuration.
280
+
281
+ ### Check workflow status
282
+
283
+ Inspect the latest persisted state without running the workflow:
284
+
285
+ ```bash
286
+ open-local-audit workflow --config workflow.json --status
287
+ ```
288
+
289
+ Use JSON for scripts and integrations:
290
+
291
+ ```bash
292
+ open-local-audit workflow --config workflow.json --status --format json
293
+ ```
294
+
295
+ The status is `not-started` when no state or checkpoint exists,
296
+ `running-or-interrupted` when the last recorded run was not finished and its
297
+ process liveness is unknown, `failed` after a controlled failure whose failure
298
+ state was persisted, `completed` when every enabled stage succeeds, or
299
+ `invalid` when local state, its
300
+ configuration identity, a checkpoint, or required managed artifacts cannot be
301
+ trusted.
302
+
303
+ Status inspection is read-only: it makes no network calls, resolves no API
304
+ keys, and does not create or change output files. `not-started`,
305
+ `running-or-interrupted`, and `completed` exit `0`; `failed` and `invalid` exit
306
+ `1`. Before following resume advice from a `running-or-interrupted` result,
307
+ confirm that no workflow process is still active. If a controlled failure cannot
308
+ persist its failure transition, status reports the last durable state, which may
309
+ be `running-or-interrupted`; the workflow command still exits `1` for the
310
+ underlying failure. Checkpoint correlation gates status resume advice only;
311
+ direct `workflow --resume` validates its v1 checkpoint independently.
312
+
313
+ Google Places lead discovery:
314
+
315
+ ```bash
316
+ GOOGLE_MAPS_API_KEY=your-key open-local-audit discover "guzellik salonu Umraniye" --provider google-places --profile beauty --out-dir reports/umraniye-beauty --export-csv leads.csv
317
+ ```
318
+
319
+ Control discovery cost and audit volume:
320
+
321
+ ```bash
322
+ GOOGLE_MAPS_API_KEY=your-key open-local-audit discover "dis klinigi Kadikoy" --provider google-places --limit 20 --max-audits 5 --summary-json discovery-summary.json --out-dir reports/kadikoy-dental --export-csv leads.csv
323
+ ```
324
+
325
+ Skip previously reviewed leads and keep only stronger opportunities:
326
+
327
+ ```bash
328
+ open-local-audit discover --input places.csv --provider manual-csv --suppression-list reviewed-leads.csv --min-opportunity-score 90 --dry-run --export-csv leads.csv
329
+ ```
330
+
331
+ Maintain a review queue and duplicate review report across reruns:
332
+
333
+ ```bash
334
+ open-local-audit discover --input places.csv --provider manual-csv --review-csv review.csv --duplicates-json duplicates.json --dry-run --export-csv leads.csv
335
+ ```
336
+
337
+ Write a CRM-ready local import CSV:
338
+
339
+ ```bash
340
+ open-local-audit discover --input places.csv --provider manual-csv --dry-run --export-csv crm-leads.csv --export-preset crm
341
+ ```
342
+
343
+ Validate a CRM-ready local import CSV:
344
+
345
+ ```bash
346
+ open-local-audit validate-export --input crm-leads.csv --preset crm
347
+ open-local-audit validate-export --input crm-leads.csv --preset crm --format json
348
+ ```
349
+
350
+ `validate-export` checks the local CSV shape before a manual import. It reports missing CRM columns, missing `companyName`, missing `website`, missing `leadKey`, duplicate `leadKey` values, low or empty contact confidence, and rows that still need manual contact review. Markdown output is the default; JSON is available for automation. Any issue returns exit code `1`, while a clean file returns exit code `0`.
351
+
352
+ Create a local lead shortlist from a discovery or CRM CSV export:
353
+
354
+ ```bash
355
+ open-local-audit shortlist --input leads.csv --out shortlist.md --top 20
356
+ open-local-audit shortlist --input leads.csv --out shortlist.md --min-opportunity-score 80
357
+ open-local-audit shortlist --input leads.csv --sort score-desc --out shortlist.md
358
+ open-local-audit shortlist --input leads.csv --out shortlist.md --summary-json shortlist-summary.json
359
+ open-local-audit shortlist --input leads.csv --segment dental --priority high --contact-confidence High --out shortlist.csv --format csv
360
+ open-local-audit shortlist --input leads.csv --preferred-contact-channel email --out shortlist.json --format json
361
+ open-local-audit shortlist --input leads.csv --exclude-review-status deferred --out shortlist.json --format json
362
+ open-local-audit shortlist --input leads.csv --require-website --out shortlist.json --format json
363
+ open-local-audit shortlist --input leads.csv --missing-website --out shortlist.json --format json
364
+ open-local-audit shortlist --input leads.csv --unreviewed --out shortlist.json --format json
365
+ open-local-audit shortlist --input leads.csv --reviewed-before 2026-06-20 --out shortlist.json --format json
366
+ open-local-audit shortlist --input leads.csv --require-contact --out shortlist.json --format json
367
+ open-local-audit shortlist --input leads.csv --missing-contact --out shortlist.json --format json
368
+ open-local-audit shortlist --input leads.csv --require-report --out shortlist.json --format json
369
+ open-local-audit shortlist --input leads.csv --missing-report --out shortlist.json --format json
370
+ open-local-audit shortlist --input leads.csv --review-csv review.csv --review-status pending --out shortlist.json --format json
371
+ open-local-audit shortlist --input crm-leads.csv --out shortlist.json --format json
372
+ open-local-audit shortlist --input crm-leads.csv --out shortlist.csv --format csv
373
+ open-local-audit shortlist --input leads.csv --review-csv review.csv --out shortlist.md
374
+ open-local-audit shortlist --input leads.csv --out shortlist.md --top 10 --pitch-brief pitch-briefs.md
375
+ ```
376
+
377
+ `--pitch-brief` also writes Markdown pitch notes for every shortlisted lead. Each brief reads the lead's JSON audit report (the `reportPath` column is resolved from `--reports-dir`, by default the `reports` folder next to `--input`, or the input's own folder when there is none, as in `start` output) and lists up to three findings a business owner would notice, in a fixed order that puts broken or placeholder sites, security warnings, search blockers, and missing mobile contact actions first. Each point has a plain-language reason, the audit evidence, and the fix. The brief also suggests an offer (starter website, quick-fix sprint, or no pitch), repeats the lead's shortlist reasons and contact channel, and ends with a checklist: re-check every point on the live site, confirm uncertain identities and chain or location flags, check the business's legal form and local email rules (for example UK PECR) before a cold email, and include an opt-out. Briefs are notes for writing your own outreach; nothing is sent. Chain and location checklist items rely on the `opportunityReasons` column of a standard discovery export, so a CRM-preset input does not get them.
378
+
379
+ `shortlist` ranks local CSV rows by `opportunityScore`, `priority`, `contactConfidence`, `score`, and company name. Use `--sort opportunity-desc|score-desc|company-asc|last-reviewed-asc` to change the ranking mode before top-N selection. Use `--summary-json` to write a separate automation summary with counts and selected lead identifiers. Use `--require-website` to keep only rows with a website, `--missing-website` to keep only rows without a website, `--unreviewed` to keep only rows whose normalized `lastReviewedAt` is empty, `--reviewed-before YYYY-MM-DD` to keep only valid date-only or explicit-zone ISO review timestamps strictly earlier than a valid calendar threshold, `--require-contact` to keep only rows with contact confidence above `None`, `--missing-contact` to keep only rows with contact confidence set to `None`, `--require-report` to keep only rows with a report path, and `--missing-report` to keep only rows without a report path. Equal, blank, invalid, and zone-less review timestamps do not match `--reviewed-before`. Use `--min-opportunity-score` to keep only stronger local opportunities. Use `--segment`, `--profile`, `--priority`, `--contact-confidence`, and `--preferred-contact-channel` for case-insensitive exact focus filters; supplied filters, including review-date filters, are combined with `AND` semantics. Use `--review-status` to keep only active leads with a matching review status after completed or suppressed review rows have been removed, or `--exclude-review-status` to remove a matching active review status from the local report. Filtering runs after review-state suppression and before sorting and top-N selection. With `--review-csv`, the command matches review rows by `leadKey`, normalized website, or company label; skips rows marked `rejected`, `contacted`, `not-fit`, `not_a_fit`, `do-not-contact`, or `suppressed`; and carries active `reviewStatus`, `reviewReason`, and `lastReviewedAt` values into Markdown, JSON, and CSV reports. Markdown output is the default; JSON is available for automation; CSV is available for spreadsheet review. The command only reads local CSV files and writes a local report. It does not mutate source files, call APIs, send outreach, or sync to a CRM.
380
+
381
+ Update local review state by lead key:
382
+
383
+ ```bash
384
+ open-local-audit review --review-csv review.csv --lead-key url:https://lead.test --status contacted --reason "Email sent"
385
+ open-local-audit review --review-csv review.csv --lead-key url:https://lead.test --status in-review --reviewed-at 2026-06-24T09:00:00Z
386
+ open-local-audit review --review-csv review.csv --input shortlist.csv --status in-review --reason "Selected from shortlist"
387
+ open-local-audit review --review-csv review.csv --input shortlist.json --status qualified --dry-run
388
+ open-local-audit review --review-csv review.csv --summary --summary-json review-summary.json
389
+ open-local-audit review --review-csv review.csv --summary --stale-before 2026-06-20
390
+ ```
391
+
392
+ `review` updates the exact `leadKey` row in a local review CSV, or appends a new row when the key is not present. Use `--input` with a shortlist CSV or JSON file to update every row that has a `leadKey`; duplicate and blank keys are skipped. Use `--dry-run` with `--input` to preview added and updated counts without writing the review CSV. Use `--summary` to print local queue counts by status, reviewed/unreviewed rows, invalid review dates, and oldest/newest review timestamps without updating the CSV. Use `--stale-before YYYY-MM-DD` with summary mode to count rows reviewed before a strict calendar date. Use `--summary-json` to write the same summary for automation, including `actionableLeadKeys` for the deduplicated queue and `actionableLeads` for each lead key with one or more `unreviewed`, `invalid-reviewed-at`, or `stale` reasons; `unreviewedLeadKeys` for rows without review dates; `invalidReviewedAtLeadKeys` for invalid review dates; and `staleLeadKeys` when stale rows have lead keys. The command preserves existing columns, adds missing `leadKey`, `reviewStatus`, `reviewReason`, and `lastReviewedAt` columns, and accepts `new`, `pending`, `in-review`, `qualified`, `contacted`, `rejected`, `not-fit`, `do-not-contact`, or `suppressed`. `--reviewed-at` defaults to the current time and is written as an ISO timestamp. The command only mutates the supplied local review CSV when summary mode is not used. It does not change lead source files, call APIs, send outreach, or sync to a CRM.
393
+
394
+ Package an existing single-site report folder for local customer sharing:
395
+
396
+ ```bash
397
+ open-local-audit package-report --input reports/example-com --out packages/example-com
398
+ ```
399
+
400
+ `package-report` reads `open-local-audit-report.json` from the input folder, copies available JSON, Markdown, HTML, and PDF report artifacts into `reports/`, and writes `README.md`, `next-actions.md`, and `manifest.json`. It is local file packaging only; it does not upload reports, send outreach, or sync to a CRM.
401
+
402
+ Compare a later audit of the same site with an earlier one to show progress:
403
+
404
+ ```bash
405
+ open-local-audit compare reports/before/example-com reports/after/example-com --format html --out progress.html
406
+ ```
407
+
408
+ `compare` reads two JSON reports, either as files or as report directories containing `open-local-audit-report.json`, and matches findings by rule id. It lists fixed issues, issues that are still open, and new issues, with category and overall score changes, as Markdown (default), JSON, HTML, or PDF (`--out` is required for PDF); `--brand-config` applies report branding. When both audits were run with `--screenshot`, the HTML and PDF outputs show the homepage screenshots side by side under "Before and After"; the HTML embeds them so the file can be shared on its own. Screenshot paths are read relative to each report's directory, and files outside that directory are ignored. Markdown only says whether each screenshot was captured. Both reports must be for the same site (a leading `www.` is ignored) and the same profile, and the earlier report must come first. Rules added in a newer release appear as new issues, so compare reports produced by the same Open Local Audit version when the result goes to a customer. Run both audits with the same options too: a finding that only some options produce, such as broken links with `--check-links`, shows as fixed when the later audit skips that check. Comparison is local only and makes no network calls.
409
+
410
+ The `google-places` provider is opt-in and requires `GOOGLE_MAPS_API_KEY`. It uses the official Places Text Search API and requests only `places.id`, `places.displayName`, and `places.websiteUri`. It does not scrape Google Maps, collect reviews/photos/ratings, send outreach, or store raw Places responses. Google Maps Platform billing and quota limits apply to API use, and the CLI prints a billing warning when this provider is selected.
411
+
412
+ Discovery CSV exports include `leadKey`, `opportunityScore`, `opportunityReasons`, `pitchAngle`, `recommendedOffer`, `estimatedNeed`, `outreachPriorityReason`, website-derived public contact columns, manual outreach handoff columns, and review columns for local triage. Contact columns are populated only from audited public website HTML and include `publicEmail`, `publicPhone`, `whatsappUrl`, `contactPageUrl`, `socialProfiles`, `contactConfidence`, and `contactSource`. Handoff columns include `preferredContactChannel`, `outreachAction`, and `contactabilityReason`; they are advisory only and do not send outreach. Duplicate review JSON includes exact `duplicateGroups` and advisory `fuzzyDuplicateGroups` with confidence and matching reasons for local operator review. Fuzzy duplicate candidates do not auto-suppress leads, change review status, send outreach, or sync to a CRM. The CRM export preset writes `companyName`, `website`, `segment`, `profile`, `priority`, `score`, `opportunityScore`, `topFinding`, `contactConfidence`, `preferredContactChannel`, `contactabilityReason`, `publicEmail`, `publicPhone`, `contactPageUrl`, `source`, `leadKey`, and `reportPath`; it is a local import file only and does not call CRM APIs. A suppression list can reuse a prior discovery CSV or a smaller CSV with `leadKey`, `sourceId`, `websiteUrl`, or `label` plus optional `reviewStatus`, `reviewReason`, and `lastReviewedAt` columns. Rows marked `rejected`, `contacted`, `not-fit`, `do-not-contact`, or `suppressed` are skipped before audits run. `--review-csv` preserves prior operator decisions and adds new leads as `pending`. Spreadsheet formula-like cell values are neutralized before export, so values such as phone numbers starting with `+` may be prefixed for spreadsheet safety.
413
+
414
+ Discovery also flags leads that look like a chain branch or public body: a website on a public-sector domain (`nhs.uk`, `gov.uk`, `.gov`, `police.uk`, `ac.uk`, `sch.uk`), a website host shared by two or more differently named leads in the same search, or a website URL that is a branch page with two or more path segments (a leading language segment such as `/en-gb/` and a trailing `index.html` are ignored). A flagged lead keeps its row but gets the reason in `opportunityReasons` and the standard CSV `chainReason` column, low priority, an opportunity score of at most 20, and a "Skip unless you are targeting the head office" next action. Leads without a website are never flagged, and neither are social, link-in-bio, directory, site-builder, or booking-platform pages such as `facebook.com`, `linktr.ee`, `sites.google.com`, Treatwell, or Fresha, because those hosts give every business its own page.
415
+
416
+ See [Google Maps API key setup](./docs/google-maps-api-key.md) for local environment setup.
417
+
418
+ Check same-origin links and fail CI when high-severity issues are found:
419
+
420
+ ```bash
421
+ open-local-audit https://example.com --check-links --max-pages 10 --fail-on high --format all --out-dir reports
422
+ ```
423
+
424
+ ## First implementation milestone
425
+
426
+ The first implementation milestone is a CLI that accepts one URL and outputs:
427
+
428
+ - JSON report.
429
+ - Markdown report.
430
+ - HTML report.
431
+ - PDF report.
432
+ - Executive Summary section in customer-facing reports.
433
+ - Contact Readiness section in customer-facing reports.
434
+ - Optional report branding with `--brand-config`.
435
+ - Combined JSON, Markdown, and HTML report output with `--format all --out-dir`.
436
+ - Batch input files with per-site report folders.
437
+ - CSV batch input with optional labels and segments.
438
+ - Aggregate batch index reports for prospect triage.
439
+ - Batch index filtering, sorting, and top-N triage controls.
440
+ - Explicit batch automation JSON output with `--summary-json`.
441
+ - Controlled parallel batch audits with `--concurrency`.
442
+ - Batch index insights for average score, profile breakdown, segment breakdown, and frequent findings.
443
+ - Batch contact and outreach rollups for audited public website contact readiness.
444
+ - CRM-ready local CSV export preset for batch and discovery exports.
445
+ - Local CRM export validation with Markdown and JSON issue reports.
446
+ - Local lead shortlist reports from discovery and CRM CSV exports.
447
+ - Local shortlist sort modes with `--sort`.
448
+ - Local shortlist automation summaries with `--summary-json`.
449
+ - Local shortlist website-present filtering with `--require-website`.
450
+ - Local shortlist missing-website filtering with `--missing-website`.
451
+ - Local shortlist unreviewed filtering with `--unreviewed`.
452
+ - Local shortlist re-review filtering with `--reviewed-before`.
453
+ - Local shortlist contact-ready filtering with `--require-contact`.
454
+ - Local shortlist missing-contact filtering with `--missing-contact`.
455
+ - Local shortlist report-ready filtering with `--require-report`.
456
+ - Local shortlist missing-report filtering with `--missing-report`.
457
+ - Local shortlist preferred-contact-channel filtering with `--preferred-contact-channel`.
458
+ - Local shortlist review-state suppression with `--review-csv`.
459
+ - Local shortlist opportunity-score filtering with `--min-opportunity-score`.
460
+ - Local shortlist focus filtering by segment, profile, priority, and contact confidence.
461
+ - Local shortlist active review-status filtering with `--review-status`.
462
+ - Local shortlist active review-status exclusion with `--exclude-review-status`.
463
+ - Spreadsheet-ready local shortlist CSV output with `--format csv`.
464
+ - Local review CSV status upserts with `review`.
465
+ - Bulk local review CSV status upserts from shortlist CSV and JSON files.
466
+ - Local review CSV queue summaries with `review --summary`.
467
+ - Local stale review counts with `review --summary --stale-before`.
468
+ - Local report packaging with shareable summaries and copied report artifacts.
469
+ - Industry profiles for generic, dental, beauty, restaurant, contractor, lawyer, clinic, gym, hotel, and auto-service audits.
470
+ - Profile-specific findings for dental, beauty, restaurant, and contractor conversion/trust signals.
471
+ - Prospect CSV export with profile, score, top finding, contact handoff, report path, and error columns.
472
+ - Discovery CSV contact enrichment from audited public website HTML.
473
+ - Discovery CSV outreach handoff fields for manual next-channel selection.
474
+ - Advisory fuzzy duplicate lead review candidates for discovery reruns.
475
+ - Duplicate review output remains local operator guidance and does not auto-suppress, send outreach, or sync to CRM systems.
476
+ - Optional rendered DOM audits with `--render`.
477
+ - Optional rendered screenshot evidence with `--screenshot`.
478
+ - Optional Lighthouse category scoring with `--lighthouse`.
479
+ - Score summary.
480
+ - Evidence table.
481
+ - Optional same-origin link checks.
482
+ - Terminal summary when reports are written to files.
483
+ - CI-friendly exit codes with `--fail-on`.
484
+ - Clear owner-readable recommendations.
485
+ - Structured-data quality, address, opening-hours, service-location, CTA, and placeholder-copy checks.
486
+ - Trust and conversion checks for current date signals, review cues, service detail depth, brand icons, and placeholder social links.
487
+
488
+ Example target command:
489
+
490
+ ```bash
491
+ open-local-audit https://example.com --format markdown --out report.md
492
+ ```
493
+
494
+ Example report artifacts are available under [`examples/reports`](./examples/reports).
495
+
496
+ ## Known limits
497
+
498
+ - `--render` requires Playwright in the calling project and a working browser runtime.
499
+ - `--screenshot` uses the rendered audit path, requires `--out-dir`, and also requires Playwright.
500
+ - `--lighthouse` requires a local Chrome or Chromium runtime that Lighthouse can launch.
501
+ - `--format pdf` is supported for single URL audits and requires `--out` or `--out-dir`.
502
+ - `--brand-config` reads local JSON only; it does not fetch remote assets or upload report data.
503
+ - Batch triage options apply to the aggregate batch index, not individual per-site report contents.
504
+ - Batch `--summary-json` writes the selected local aggregate index only and requires `--input`.
505
+ - Batch contact and outreach rollups are advisory local triage metadata; they do not send outreach or sync to a CRM.
506
+ - `--export-preset crm` changes CSV columns only; it does not create, update, or sync CRM records.
507
+ - `validate-export` checks local CSV files only; it does not create, update, or sync CRM records.
508
+ - `shortlist` ranks local CSV files only; it does not call APIs, send outreach, or sync CRM records.
509
+ - `shortlist --sort` changes local report ordering only; it does not update source or review CSV files.
510
+ - `shortlist --summary-json` writes local automation metadata only; it does not update source or review CSV files.
511
+ - `shortlist --require-website` filters local report output only; it does not update source or review CSV files.
512
+ - `shortlist --missing-website` filters local report output only; it does not update source or review CSV files.
513
+ - `shortlist --unreviewed` filters local report output only; it does not update source or review CSV files.
514
+ - `shortlist --reviewed-before` requires an explicit date threshold and filters local report output only; it does not calculate relative review age or update source or review CSV files.
515
+ - `shortlist --require-contact` filters local report output only; it does not update source or review CSV files.
516
+ - `shortlist --missing-contact` filters local report output only; it does not update source or review CSV files.
517
+ - `shortlist --require-report` filters local report output only; it does not update source or review CSV files.
518
+ - `shortlist --missing-report` filters local report output only; it does not update source or review CSV files.
519
+ - `shortlist --preferred-contact-channel` filters local report output only; it does not update source or review CSV files.
520
+ - `shortlist --review-csv` reads review state for suppression and report context only; it does not mutate the review CSV.
521
+ - `shortlist --min-opportunity-score` filters local report output only; it does not update source lead files.
522
+ - Shortlist focus filters use case-insensitive exact matching and do not update source lead files.
523
+ - `shortlist --review-status` filters local report output only; it does not update source or review CSV files.
524
+ - `shortlist --exclude-review-status` filters local report output only; it does not update source or review CSV files.
525
+ - `shortlist --format csv` writes a local spreadsheet review file only; it does not import or sync records.
526
+ - `review` mutates only the supplied local review CSV by exact `leadKey`; bulk `--input` mode reads shortlist CSV or JSON files for lead keys only. `review --summary`, `--summary-json`, and `--stale-before` read local review state only. The command does not update source lead files, call APIs, send outreach, or sync CRM records.
527
+ - `package-report` packages local files only; it does not upload reports, send outreach, or sync CRM records.
528
+ - `--export-csv` is supported for batch audits and discovery exports.
529
+ - `discover --provider google-places` requires `GOOGLE_MAPS_API_KEY` and may incur Google Maps Platform billing.
530
+ - `--limit` caps Google Places candidates at 50; it does not paginate beyond one Text Search request.
531
+ - `--max-audits` limits website audits only; all discovered candidates still appear in `leads.csv`.
532
+ - For Overture, `--audit-priority missing-contact` prioritizes missing source email, then phone; the default `source-order` preserves existing selection. Standard exports explain each selection and include business–website identity evidence. Conflicting sites contribute no website contacts or audit scores; uncertain matches require manual review. See the [free discovery guide](./docs/guides/free-discovery.md#website-enrichment).
533
+ - `--suppression-list` uses exact lead identity matching; source IDs are preferred, then normalized website URLs, then normalized labels.
534
+ - `--review-csv` is local operator state only; it does not send outreach or sync to a CRM.
535
+ - `--duplicates-json` reports exact duplicate lead groups and advisory fuzzy duplicate candidates for manual review; it does not auto-suppress rows or update review decisions.
536
+ - Batch input requires `--out-dir` and cannot be combined with a positional URL.
537
+ - Industry profiles are deterministic vertical heuristics, not a replacement for a human review of each business model.
538
+ - Higher `--concurrency` values can increase network load against audited sites; use conservative values for prospect batches.
539
+ - Rule checks are deterministic heuristics, so they can miss or over-flag site-specific markup.
540
+
541
+ ## Releases
542
+
543
+ Open Local Audit is published under the MIT license on GitHub and npm. Release history is available in [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases. Every release runs lint, tests, build, audit, package-content checks, and a fresh consumer install audit through GitHub Actions.