npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

open-local-audit

v0.95.0

Published

Open-source website and local presence auditor for small businesses.

Readme

Open Local Audit

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.

Find prospective clients without a Google API key or billing account, from a source checkout:

Requires Node.js 22.19 or newer.

npm install
npm run build
node dist/cli.js start

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 for source attribution, limits, and the four-country benchmark.

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.

Current stage

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.

Business purpose

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.

Product principles

  • Evidence first: every recommendation should point to a concrete finding.
  • Ethical scanning: audit public URLs supplied by the user or found in an explicitly requested open-data search; avoid Google Maps scraping.
  • Small-business language: reports should be readable by non-technical owners.
  • Developer quality: deterministic checks, clear CLI output, tests, and release notes.
  • Commercial clarity: the open-source scanner is useful; paid work is execution and support.

Proposed first stack

  • Runtime: Node.js with TypeScript.
  • CLI framework: commander or cac.
  • Validation: zod.
  • HTML parsing: cheerio.
  • Browser rendering: Playwright where static parsing is not enough.
  • Storage: no persistence by default; optional JSON/Markdown outputs.
  • Test runner: Vitest.
  • Package manager: npm unless implementation chooses pnpm before first commit.

Documentation

Local development

Install dependencies and run the checks:

npm install
npm test
npm run lint
npm run build
npm run release-check

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.

Run the CLI locally:

npm start -- https://example.com --format markdown

After building, run the compiled CLI:

node dist/cli.js https://example.com --format json --pretty

Run the published package:

npx open-local-audit https://example.com --format markdown

Run with an industry profile:

open-local-audit https://example.com --profile dental --format markdown

Supported profiles are generic, dental, beauty, restaurant, contractor, lawyer, clinic, gym, hotel, and auto-service.

Render the page before auditing when static HTML is not enough:

open-local-audit https://example.com --render --format markdown

Capture a rendered homepage screenshot and add it as visual evidence:

open-local-audit https://example.com --screenshot --format all --out-dir reports

--render loads Playwright from the current project. Install it alongside the CLI when needed:

npm install -D playwright

Run Lighthouse category scoring:

open-local-audit https://example.com --lighthouse --format html --out report.html

--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.

Write standard report formats to a directory:

open-local-audit https://example.com --format all --out-dir reports

Write an HTML report:

open-local-audit https://example.com --format html --out report.html

Write a branded PDF report:

open-local-audit https://example.com --format pdf --out report.pdf

Apply report branding:

open-local-audit https://example.com --brand-config brand.json --format pdf --out report.pdf

Minimal brand.json:

{
  "name": "Example Audit Studio",
  "primaryColor": "#123456",
  "accentColor": "#2f7d5f",
  "footerText": "Prepared for outreach review",
  "contact": "[email protected]",
  "signature": "Best regards,\nYour Name\nExample Audit Studio Ltd, company no. 01234567"
}

Brand colors must use six-digit hex values. Branding applies to Markdown, HTML, and PDF reports. signature is optional: when set, monitor ends each client email draft with it, trimmed of leading and trailing blank space (use \n for line breaks), instead of the brand name and contact.

Run a batch audit from a text file:

open-local-audit --input sites.txt --format all --out-dir reports

Run a labeled CSV batch audit:

open-local-audit --input sites.csv --format all --out-dir reports

Run a profile-aware batch audit and write a prospect CSV export:

open-local-audit --input sites.csv --profile dental --export-csv prospects.csv --format all --out-dir reports

Run a focused batch triage index:

open-local-audit --input sites.csv --format all --out-dir reports --segment dental --sort score-asc --top 25

Write the selected batch index to an explicit automation JSON path:

open-local-audit --input sites.csv --format markdown --out-dir reports --summary-json automation/batch-summary.json

Run a controlled parallel batch audit:

open-local-audit --input sites.csv --format all --out-dir reports --concurrency 3

Capture screenshots during batch audits:

open-local-audit --input sites.csv --screenshot --format all --out-dir reports

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.

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.

Supported CSV columns:

url,label,segment,profile
example.com,Example Clinic,dental,dental
https://example.org/path,Example Salon,beauty,beauty

Manual CSV lead discovery:

open-local-audit discover --input places.csv --provider manual-csv --profile dental --out-dir reports/dental --export-csv leads.csv

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.

Config-driven local workflow:

open-local-audit workflow --config workflow.json

Preflight a workflow without running it:

open-local-audit workflow --config workflow.json --check
open-local-audit workflow --config workflow.json --check --format json

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.

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.

Plan a workflow without running it:

open-local-audit workflow --config workflow.json --plan
open-local-audit workflow --config workflow.json --plan --format json

--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.

Resume an interrupted workflow from its last verified stage:

open-local-audit workflow --config workflow.json --resume

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.

Minimal workflow.json for a manual CSV run:

{
  "version": 1,
  "outDir": "./workflow-output",
  "discovery": {
    "provider": "manual-csv",
    "input": "./places.csv",
    "profile": "dental"
  },
  "shortlist": {
    "top": 10
  }
}

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.

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.

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.

Check workflow status

Inspect the latest persisted state without running the workflow:

open-local-audit workflow --config workflow.json --status

Use JSON for scripts and integrations:

open-local-audit workflow --config workflow.json --status --format json

The status is not-started when no state or checkpoint exists, running-or-interrupted when the last recorded run was not finished and its process liveness is unknown, failed after a controlled failure whose failure state was persisted, completed when every enabled stage succeeds, or invalid when local state, its configuration identity, a checkpoint, or required managed artifacts cannot be trusted.

Status inspection is read-only: it makes no network calls, resolves no API keys, and does not create or change output files. not-started, running-or-interrupted, and completed exit 0; failed and invalid exit 1. Before following resume advice from a running-or-interrupted result, confirm that no workflow process is still active. If a controlled failure cannot persist its failure transition, status reports the last durable state, which may be running-or-interrupted; the workflow command still exits 1 for the underlying failure. Checkpoint correlation gates status resume advice only; direct workflow --resume validates its v1 checkpoint independently.

Google Places lead discovery:

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

Control discovery cost and audit volume:

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

Skip previously reviewed leads and keep only stronger opportunities:

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

Maintain a review queue and duplicate review report across reruns:

open-local-audit discover --input places.csv --provider manual-csv --review-csv review.csv --duplicates-json duplicates.json --dry-run --export-csv leads.csv

Write a CRM-ready local import CSV:

open-local-audit discover --input places.csv --provider manual-csv --dry-run --export-csv crm-leads.csv --export-preset crm

Validate a CRM-ready local import CSV:

open-local-audit validate-export --input crm-leads.csv --preset crm
open-local-audit validate-export --input crm-leads.csv --preset crm --format json

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.

Create a local lead shortlist from a discovery or CRM CSV export:

open-local-audit shortlist --input leads.csv --out shortlist.md --top 20
open-local-audit shortlist --input leads.csv --out shortlist.md --min-opportunity-score 80
open-local-audit shortlist --input leads.csv --sort score-desc --out shortlist.md
open-local-audit shortlist --input leads.csv --out shortlist.md --summary-json shortlist-summary.json
open-local-audit shortlist --input leads.csv --segment dental --priority high --contact-confidence High --out shortlist.csv --format csv
open-local-audit shortlist --input leads.csv --preferred-contact-channel email --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --exclude-review-status deferred --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --require-website --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --missing-website --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --unreviewed --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --reviewed-before 2026-06-20 --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --require-contact --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --missing-contact --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --require-report --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --missing-report --out shortlist.json --format json
open-local-audit shortlist --input leads.csv --review-csv review.csv --review-status pending --out shortlist.json --format json
open-local-audit shortlist --input crm-leads.csv --out shortlist.json --format json
open-local-audit shortlist --input crm-leads.csv --out shortlist.csv --format csv
open-local-audit shortlist --input leads.csv --review-csv review.csv --out shortlist.md
open-local-audit shortlist --input leads.csv --out shortlist.md --top 10 --pitch-brief pitch-briefs.md
open-local-audit shortlist --input leads.csv --out shortlist.md --top 10 --email-drafts drafts --outreach outreach.json --no-links

--pitch-brief also writes Markdown pitch notes for every shortlisted lead, each with a pitch strength: strong when the site has one problem an owner notices at once (its domain does not resolve, a placeholder or error page, a certificate or domain about to lapse, a page hidden from Google, no mobile viewport, a phone number that cannot be tapped, or no page title), medium when it has at least two smaller owner-visible problems (such as no meta description, no visible phone number, an http final URL, or an outdated copyright year), and weak when only minor points remain (headings, opening hours, map links, images). --min-pitch-strength strong|medium|weak keeps only leads at or above that strength, read from each lead's JSON report in --reports-dir, before --top is applied, so a batch skips leads not worth a cold email. Leads without a readable report (no website, a failed audit, or a wrong --reports-dir) rate none and are left out, except dead-domain leads, which are strong. 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.

--email-drafts <dir> with --outreach <file> writes an unsent .eml cold email draft for each shortlisted lead rated strong or medium, addressed to the first plain address in the lead's publicEmail column when there is one, plus an index.md listing every lead with its draft or the reason it was skipped. Weak or unrated leads are skipped as not worth a cold email, and dead-domain leads are skipped because email at that domain may bounce. Each draft names the strongest point in its subject and lists up to three owner-visible points in plain words, followed by your offer, your signature, an opt-out line, and your company details. Mail programs such as Outlook open the files ready to edit and send; nothing is sent. The outreach file holds your own wording:

{
  "senderName": "Sam Sender",
  "company": "EXAMPLE STUDIO LTD",
  "registeredIn": "England and Wales",
  "companyNumber": "01234567",
  "registeredOffice": "1 Example Street, London E1 1AA",
  "opener": "I came across {name} while looking at accountants in Leeds.",
  "offer": "I run a fixed-price 48-hour fix for up to 3 issues like these.",
  "callToAction": "If useful, I can send the exact changes first, free."
}

senderName, company, and offer are required; registeredIn, companyNumber, and registeredOffice complete the company details line, which UK companies are expected to give. {name} in opener becomes the business name; signature replaces the default sign-off (senderName and company), and optOut replaces the default opt-out line. --no-links keeps URLs, email addresses, and domains (names ending in a country code or a common generic top-level domain such as .com, .ltd, or .dental) out of drafts, since some mail tools rewrite links into tracking redirects and links in a first cold email hurt delivery: the run fails when the outreach file contains one, and a lead whose draft would contain one, such as through its business name, is skipped. Each run replaces the drafts an earlier run wrote to the same folder (files named like 03-business-name.eml), so drafts for leads since marked contacted or do-not-contact do not linger; other files there are left alone. Before sending, re-check each point on the live site and confirm the business may receive cold email; in the UK (PECR) that means a corporate body, such as a limited company or LLP.

follow-ups writes the one follow-up to leads you have marked contacted with the review command, after --after-days days (default 5):

open-local-audit follow-ups --input leads.csv --review-csv review.csv --outreach outreach.json --out-dir follow-ups --after-days 5 --no-links

It fetches each due lead's website again, with the same robots and public-address checks as discovery, and audits it. A draft says a problem is still there only when it was among the up to three points a first email from this tool would have named, read from the first audit (--reports-dir, as above), and the fresh check found it again. index.md lists the points that are no longer found, whether the site changed or a newer version of a rule no longer reports them. Only a lead's own review row counts, matched by leadKey. A lead gets a reason instead of a draft when another contacted lead has the same address or website, it is not due, it has no valid contact date or no first audit, it fails --legal-status, the site cannot be fetched, none of those points remain, only minor points remain, or it fails --no-links. Use a folder of its own for --out-dir, because each run replaces the drafts an earlier run wrote there. An optional followUp in the outreach file replaces callToAction in these drafts. Each draft promises it is the only follow-up, so send it as a reply in the first email's thread (the file copies the subject the tool would have written but is not linked to that email), then mark the lead followed-up with review. Nothing is sent.

--legal-status <file> adds your Companies House results to that decision. The CSV has a row per checked lead, matched by leadKey or else by label (a row can have both), with companyType and companyStatus columns (Companies House codes or labels, such as ltd, llp, private-limited-guarant-nsc, or Private limited company, and active or dissolved) and an optional companyNumber:

leadKey,label,companyNumber,companyType,companyStatus
overture:1,Smith Accountants,01234567,ltd,active
,Jones & Partners,,partnership,active

With it, only active companies, LLPs, CICs, CIOs, and registered societies get a draft. A lead without a row, a sole trader, partnership, or limited partnership, and a company that is not active (dissolved, in liquidation, or in administration) are skipped with the reason, and index.md shows each lead's company number, type, and status. The file only repeats what you found; check that the Companies House record's name and address match the business before sending.

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, followed-up, 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.

Update local review state by lead key:

open-local-audit review --review-csv review.csv --lead-key url:https://lead.test --status contacted --reason "Email sent"
open-local-audit review --review-csv review.csv --lead-key url:https://lead.test --status in-review --reviewed-at 2026-06-24T09:00:00Z
open-local-audit review --review-csv review.csv --input shortlist.csv --status in-review --reason "Selected from shortlist"
open-local-audit review --review-csv review.csv --input shortlist.json --status qualified --dry-run
open-local-audit review --review-csv review.csv --summary --summary-json review-summary.json
open-local-audit review --review-csv review.csv --summary --stale-before 2026-06-20

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, followed-up, 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.

Package an existing single-site report folder for local customer sharing:

open-local-audit package-report --input reports/example-com --out packages/example-com

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.

Compare a later audit of the same site with an earlier one to show progress:

open-local-audit compare reports/before/example-com reports/after/example-com --format html --out progress.html

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.

Turn an audit into a monthly care proposal for the business:

open-local-audit proposal reports/riverside-dental --offer offer.json --name "Riverside Dental" --brand-config brand.json --format pdf --out proposal.pdf

proposal reads a JSON report (a file or report directory) and writes a short proposal as HTML (default) or PDF (--out is required for PDF). A typical offer fits on one PDF page; when a long offer runs to more, the command prints a note so you can shorten its includes or plan features. It lists up to three problems a business owner would notice, picked and worded the same way as the shortlist pitch briefs, with the overall score and how many more points the full report has; what monthly care includes; your plans; and the next step. --name sets the business name (default: the site's host), and --brand-config adds your business name, colors, and footer. Plans, prices, and wording come only from your offer file:

{
  "plans": [
    { "name": "Care Lite", "price": "£29 / month", "features": ["Daily check", "Monthly report"] },
    { "name": "Care", "price": "£49 / month", "features": ["Everything in Care Lite", "30 minutes of changes each month"] }
  ],
  "nextStep": "Reply to this email and I will set up the first check this week.",
  "validDays": 14
}

The offer file needs one to four plans, each with a name, a price written as you want it shown, and optional features. Optional fields are title, intro, includes (what monthly care includes; by default the monthly report, certificate and domain warnings, and history page that a monthly monitor run provides; list the daily check here if you schedule --quick for the client), nextStep, and validDays (prints a "Valid until" date). Unknown fields are rejected so a typo does not silently drop text. Every proposal says it is based on an automated check and that each point is confirmed on the live site before work starts. Proposals are local files: nothing is sent.

Monitor client sites every month with one command:

open-local-audit monitor --input clients.csv --out-dir monitoring --fail-on-alerts

monitor reads a URL list or a CSV with url and optional label, profile, included_minutes, and client_email columns, audits each site, and keeps every run under <out-dir>/<site>/<run time>/ with JSON, Markdown, HTML, and PDF reports. Run folders are named by UTC time to the millisecond, so a retried run never reuses a folder. The site folder is the host without www., such as clinic.co.uk, plus a short hash when the URL has a path or query, so different sites never share a history; the same site listed twice (for example with and without www.) is rejected. Reuse the same --out-dir each time: a site that has an earlier run is compared with the latest one that still has a readable report, and the run folder gets comparison.md, comparison.html, and a comparison.pdf progress report to send the client, branded with --brand-config; a first run, or a site whose profile or final host changed, records a new baseline. monitor-summary-<run time>.md and .json list every site's score, score change, new and fixed findings, the client PDF (the comparison, or the report on a first run), and the sites that need attention: new high-severity findings since the last run, an HTTPS certificate that fails tls-certificate-valid, a domain that fails domain-expiry, or an audit that failed. One site that fails to audit or save does not stop the others. --fail-on-alerts exits with code 1 when any site needs attention, so a scheduled task can notify you; --profile, --brand-config, and --timeout work as in single audits, and --no-check-domain skips the domain lookup. Each site folder also keeps history.html, rebuilt on every run that audits the site: a branded, self-contained page with a chart of the overall score (and page speed, with --lighthouse) for every check, and a table of each check's score, speed, response time, open issues, fixed and new issues, and certificate and domain problems. It works offline and on a phone, so you can send it to the client or keep it open during a renewal call. The summary links it in a History column (historyPath in JSON), and a run that cannot be compared with the one before it, such as after a profile or host change, shows as a new baseline there too. Static audits now also record how long the page HTML took to load (responseTimeMs in the JSON report and a "Response time" evidence row), and the monitoring summary shows it in a Response column with the change since the last run (responseTimeMs and previousResponseTimeMs in JSON).

Every run folder also gets client-email.md, a plain-text draft for the client with a subject line, the score change, fixed and new issues, certificate and domain status, the top three next steps, the name of the PDF to attach, and a sign-off from --brand-config (signature when set, otherwise name and contact). Edit it, check each point on the live site, and send it from your own mailbox. The run folder also gets client-email.eml, the same email as an unsent draft with the client PDF attached: open it in your mail program, check it, and send it yourself. It is marked unsent (X-Unsent: 1), so Outlook for Windows opens it as a new message with the recipients, subject, text, and PDF filled in, ready to send. If double-clicking the file only brings Outlook to the front, make Outlook the default app for .eml files in Windows settings. Other mail programs may show it as a received message, so use their "edit as new" or forward option, or attach the PDF to a new message and paste the text from client-email.md. It has no sender, so your mail program uses its own account. With a client_email column in the client list (several addresses separated by semicolons; plain addresses only, such as [email protected]), the draft is addressed to the client. --lighthouse also runs Lighthouse for each site, so the summary gets a Speed column with the change since the last run and the email reports the page speed; if Lighthouse cannot run, the site is still monitored and the summary notes why. Apart from the site audits, the domain lookups, and Lighthouse, monitoring makes no network calls: reports and drafts stay in local files and nothing is uploaded or sent to anyone.

Between monthly runs, check every day that each site still loads:

open-local-audit monitor --input clients.csv --out-dir monitoring --quick --fail-on-alerts

--quick fetches only each homepage, then reads its HTTPS certificate and, unless --no-check-domain is set, its domain registration. It prints one line per site (OK with the HTTP status and response time, or ALERT with the reason) and writes no reports, comparisons, or history, so the monthly runs stay clean. It keeps the latest result in quick-status.json, adds one line per check to quick-history.jsonl for the monthly availability count, and rebuilds the client overview. A site needs attention when it does not load, returns a non-2xx status, or fails the certificate or domain checks. A maintenance, parked, or for-sale page also counts as not loading, even though it answers with HTTP 200. Sites behind a firewall or bot protection that blocks the tool's requests will alert every day. With --fail-on-alerts it exits with code 1, and a scheduled run then shows a desktop notification (see below); nothing is sent anywhere. --lighthouse cannot be combined with --quick.

Every monthly run and every quick check also rebuilds index.html in --out-dir, a client overview for you rather than for clients: one row per client with the latest score and its change, the latest daily check, open alerts (for a client with a daily result, its certificate, domain, and loading alerts come from that newer check), and links to the history page, client PDF, email draft, and .eml draft. Clients that need attention come first.

The monthly reports also show what the service did since the last run (or in the last 30 days on a first run). From the daily checks in quick-history.jsonl, the email draft and the comparison reports get an "Availability" section, such as "The site loaded on 29 of 30 daily checks since 1 September 2026" and the days it did not load; a site counts as down when it did not load, returned an error status, or showed a placeholder page, while certificate and domain warnings do not count. To report your own work, keep work-log.csv in --out-dir:

date,site,minutes,description
2026-09-15,clinic.co.uk,25,"Fixed the contact form, tested on mobile"

site is the client's website, with or without https:// and www.; dates are local calendar days. Each monthly run lists the work dated from the previous run's day up to the day before its own run under "Work done", so work logged on a run day goes into the next month's report and nothing is counted twice. With included_minutes in the client list (for example 30 for a plan with half an hour of changes), it reports the time used against it, such as "Time used: 20 of 30 included minutes", or how many minutes went over. A row that cannot be read (a date that is not YYYY-MM-DD, a missing site, or minutes that are not a whole number above 0) stops the monthly run before any site is audited, with the line number. The summary gains Uptime and Work columns, and the overview shows each client's uptime over the 30 days up to the latest daily check, the work logged since the latest monthly run, an alert when it goes over the included minutes, and a warning when work-log.csv has rows it cannot read. The client's report PDF includes them too, so a first month's report (before there is a comparison) lists the onboarding work.

Schedule monitoring once a month:

open-local-audit monitor --input clients.csv --out-dir monitoring --fail-on-alerts --print-schedule

--print-schedule does not monitor anything. It writes a run script into --out-dir with the same options, absolute paths, and the installed Open Local Audit version pinned, so monthly comparisons keep using the same rules until you regenerate the script after an upgrade. On Windows that is run-monitor.ps1, which the task starts with PowerShell, so paths with spaces, &, %, apostrophes, or non-English letters work; on macOS and Linux it is run-monitor.sh. It then prints the schtasks command (one line for Command Prompt, one for PowerShell, and a warning if the task command would pass Task Scheduler's 261-character limit) or the crontab line that runs the script on the 1st of every month at 09:00, appending its output to monitor.log. It does not create the scheduled task itself. When a scheduled run ends with a non-zero exit code (alerts with --fail-on-alerts, or an error such as npx not being found), the script shows a desktop notification: a Windows toast, or osascript on macOS and notify-send on Linux when available (Linux notifications need a graphical session, which cron jobs often lack, and macOS may hold back notifications from cron until they are allowed in System Settings). The scripts write each run's output to the log when the run finishes. Add --quick to schedule the daily check instead: it writes run-monitor-quick.ps1 or run-monitor-quick.sh, logs to quick.log, and prints a task named "Open Local Audit quick check" that runs every day at 08:00. Each scheduled run downloads the pinned package from the npm registry with npx if it is not cached. The Windows task runs while you are signed in and opens a console window; if the computer is off or you are signed out at the scheduled time, the run waits until the next scheduled time (the next month, or the next day for --quick) unless you start it by hand with schtasks /Run. If npx cannot be found when the task runs, the script writes that to its log (monitor.log or quick.log) and exits with code 1.

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.

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.

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.

See Google Maps API key setup for local environment setup.

Check same-origin links and fail CI when high-severity issues are found:

open-local-audit https://example.com --check-links --max-pages 10 --fail-on high --format all --out-dir reports

First implementation milestone

The first implementation milestone is a CLI that accepts one URL and outputs:

  • JSON report.
  • Markdown report.
  • HTML report.
  • PDF report.
  • Executive Summary section in customer-facing reports.
  • Contact Readiness section in customer-facing reports.
  • Optional report branding with --brand-config.
  • Combined JSON, Markdown, and HTML report output with --format all --out-dir.
  • Batch input files with per-site report folders.
  • CSV batch input with optional labels and segments.
  • Aggregate batch index reports for prospect triage.
  • Batch index filtering, sorting, and top-N triage controls.
  • Explicit batch automation JSON output with --summary-json.
  • Controlled parallel batch audits with --concurrency.
  • Batch index insights for average score, profile breakdown, segment breakdown, and frequent findings.
  • Batch contact and outreach rollups for audited public website contact readiness.
  • CRM-ready local CSV export preset for batch and discovery exports.
  • Local CRM export validation with Markdown and JSON issue reports.
  • Local lead shortlist reports from discovery and CRM CSV exports.
  • Local shortlist sort modes with --sort.
  • Local shortlist automation summaries with --summary-json.
  • Local shortlist website-present filtering with --require-website.
  • Local shortlist missing-website filtering with --missing-website.
  • Local shortlist unreviewed filtering with --unreviewed.
  • Local shortlist re-review filtering with --reviewed-before.
  • Local shortlist contact-ready filtering with --require-contact.
  • Local shortlist missing-contact filtering with --missing-contact.
  • Local shortlist report-ready filtering with --require-report.
  • Local shortlist missing-report filtering with --missing-report.
  • Local shortlist preferred-contact-channel filtering with --preferred-contact-channel.
  • Local shortlist review-state suppression with --review-csv.
  • Local shortlist opportunity-score filtering with --min-opportunity-score.
  • Local shortlist focus filtering by segment, profile, priority, and contact confidence.
  • Local shortlist active review-status filtering with --review-status.
  • Local shortlist active review-status exclusion with --exclude-review-status.
  • Spreadsheet-ready local shortlist CSV output with --format csv.
  • Local review CSV status upserts with review.
  • Bulk local review CSV status upserts from shortlist CSV and JSON files.
  • Local review CSV queue summaries with review --summary.
  • Local stale review counts with review --summary --stale-before.
  • Local report packaging with shareable summaries and copied report artifacts.
  • Industry profiles for generic, dental, beauty, restaurant, contractor, lawyer, clinic, gym, hotel, and auto-service audits.
  • Profile-specific findings for dental, beauty, restaurant, and contractor conversion/trust signals.
  • Prospect CSV export with profile, score, top finding, contact handoff, report path, and error columns.
  • Discovery CSV contact enrichment from audited public website HTML.
  • Discovery CSV outreach handoff fields for manual next-channel selection.
  • Advisory fuzzy duplicate lead review candidates for discovery reruns.
  • Duplicate review output remains local operator guidance and does not auto-suppress, send outreach, or sync to CRM systems.
  • Optional rendered DOM audits with --render.
  • Optional rendered screenshot evidence with --screenshot.
  • Optional Lighthouse category scoring with --lighthouse.
  • Score summary.
  • Evidence table.
  • Optional same-origin link checks.
  • Terminal summary when reports are written to files.
  • CI-friendly exit codes with --fail-on.
  • Clear owner-readable recommendations.
  • Structured-data quality, address, opening-hours, service-location, CTA, and placeholder-copy checks.
  • Monthly care proposals from an audit with proposal, using your own plans and prices.
  • Monthly client monitoring with monitor: re-audits, progress comparisons with the previous run, client PDFs and email drafts with your signature, per-site history pages, a daily quick check, response-time and optional Lighthouse speed tracking, a summary of sites that need attention, monthly availability from the daily checks, a work log with included minutes, a client overview page, desktop notifications for scheduled runs, and a ready-made monthly schedule.
  • Optional domain registration expiry checks with --check-domain.
  • robots.txt crawl checks for the audited page.
  • Trust and conversion checks for current date signals, review cues, service detail depth, brand icons, and placeholder social links.
  • Privacy and consent checks for a privacy policy link and for trackers that load without a cookie consent tool.
  • Accessibility checks for the page language, form field labels, and descriptive link text.

Example target command:

open-local-audit https://example.com --format markdown --out report.md

Example report artifacts are available under examples/reports.

Known limits

  • --render requires Playwright in the calling project and a working browser runtime.
  • --screenshot uses the rendered audit path, requires --out-dir, and also requires Playwright.
  • --lighthouse requires a local Chrome or Chromium runtime that Lighthouse can launch.
  • --check-domain (on by default in monitor) reads registration data from the registry's RDAP server listed by IANA. Registries without RDAP, such as .tr and .de, and registries that do not publish an expiry date are skipped.
  • --format pdf is supported for single URL audits and requires --out or --out-dir.
  • --brand-config reads local JSON only; it does not fetch remote assets or upload report data.
  • Batch triage options apply to the aggregate batch index, not individual per-site report contents.
  • Batch --summary-json writes the selected local aggregate index only and requires --input.
  • Batch contact and outreach rollups are advisory local triage metadata; they do not send outreach or sync to a CRM.
  • --export-preset crm changes CSV columns only; it does not create, update, or sync CRM records.
  • validate-export checks local CSV files only; it does not create, update, or sync CRM records.
  • shortlist ranks local CSV files only; it does not call APIs, send outreach, or sync CRM records.
  • shortlist --sort changes local report ordering only; it does not update source or review CSV files.
  • shortlist --summary-json writes local automation metadata only; it does not update source or review CSV files.
  • shortlist --require-website filters local report output only; it does not update source or review CSV files.
  • shortlist --missing-website filters local report output only; it does not update source or review CSV files.
  • shortlist --unreviewed filters local report output only; it does not update source or review CSV files.
  • 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.
  • shortlist --require-contact filters local report output only; it does not update source or review CSV files.
  • shortlist --missing-contact filters local report output only; it does not update source or review CSV files.
  • shortlist --require-report filters local report output only; it does not update source or review CSV files.
  • shortlist --missing-report filters local report output only; it does not update source or review CSV files.
  • shortlist --preferred-contact-channel filters local report output only; it does not update source or review CSV files.
  • shortlist --review-csv reads review state for suppression and report context only; it does not mutate the review CSV.
  • shortlist --min-opportunity-score filters local report output only; it does not update source lead files.
  • Shortlist focus filters use case-insensitive exact matching and do not update source lead files.
  • shortlist --review-status filters local report output only; it does not update source or review CSV files.
  • shortlist --exclude-review-status filters local report output only; it does not update source or review CSV files.
  • shortlist --format csv writes a local spreadsheet review file only; it does not import or sync records.
  • 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.
  • package-report packages local files only; it does not upload reports, send outreach, or sync CRM records.
  • --export-csv is supported for batch audits and discovery exports.
  • discover --provider google-places requires GOOGLE_MAPS_API_KEY and may incur Google Maps Platform billing.
  • --limit caps Google Places candidates at 50; it does not paginate beyond one Text Search request.
  • --max-audits limits website audits only; all discovered candidates still appear in leads.csv.
  • 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.
  • --suppression-list uses exact lead identity matching; source IDs are preferred, then normalized website URLs, then normalized labels.
  • --review-csv is local operator state only; it does not send outreach or sync to a CRM.
  • --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.
  • Batch input requires --out-dir and cannot be combined with a positional URL.
  • Industry profiles are deterministic vertical heuristics, not a replacement for a human review of each business model.
  • Higher --concurrency values can increase network load against audited sites; use conservative values for prospect batches.
  • Rule checks are deterministic heuristics, so they can miss or over-flag site-specific markup.

Releases

Open Local Audit is published under the MIT license on GitHub and npm. Release history is available in CHANGELOG.md and GitHub Releases. Every release runs lint, tests, build, audit, package-content checks, and a fresh consumer install audit through GitHub Actions.