Package: @pbpyrojust/universal-accessibility-audit
CLI commands: universal-a11y-audit, uaaudit
Version: 0.2.7
A CLI toolkit for accessibility and browser-native AI readiness audits with:
- sitemap discovery for WordPress, Yoast, Drupal, and standard sitemap.xml setups
- Playwright + axe-core WCAG scanning
- Agentic Lighthouse-style scoring for browser-native AI readiness
- manual browser-saved sitemap XML fallback for protected sites
- CSV, JSON, Markdown, and backlog/ticket-ready outputs with importance and likely out-of-control flags
- image alt text inventory reporting
- WebMCP Protocol, Accessibility Trees, Semantic Data Formatting, and Layout Stability scoring
This repo works in two ways:
- Clone and run directly from source
- Install and run as an npm CLI package
git clone https://github.com/pbpyrojust/universal-accessibility-audit.git
cd universal-accessibility-audit
npm install
npx playwright install --with-deps chromium
node scripts/run-audit.mjs --site https://www.example.comnpm install -g @pbpyrojust/universal-accessibility-audit
npx playwright install --with-deps chromium
universal-a11y-audit audit --site https://www.example.comYou can also use the shorter alias:
uaaudit audit --site https://www.example.comThe tool can also run against protected sites in two common ways:
For environments protected by browser-native username/password auth:
node scripts/run-audit.mjs \
--site https://staging.example.com \
--http-username your-user \
--http-password your-passYou can combine that with other flags:
node scripts/run-audit.mjs \
--site https://staging.example.com \
--http-username your-user \
--http-password your-pass \
--slow \
--respect-robots \
--cloudflare-awareFor sites that require a login form, use a local auth config file:
node scripts/run-audit.mjs \
--site https://staging.example.com \
--auth-config ./auth.local.jsonExample auth.local.json:
{
"loginUrl": "https://staging.example.com/login",
"username": "your-username",
"password": "your-password",
"usernameSelector": "input[name='username']",
"passwordSelector": "input[name='password']",
"submitSelector": "button[type='submit']",
"readySelector": "body",
"postLoginWaitMs": 2000
}You can also override parts of the config with flags:
--login-url--username--password--username-selector--password-selector--submit-selector--ready-selector--post-login-wait-ms
Instead of putting secrets directly on the command line, you can use environment variables:
export A11Y_HTTP_USERNAME=your-user
export A11Y_HTTP_PASSWORD=your-passor for form login:
export A11Y_LOGIN_USERNAME=your-user
export A11Y_LOGIN_PASSWORD=your-passDo not commit real auth config files, credentials, or environment files.
The project now ignores local auth files such as:
*.auth.jsonauth.local.json.auth.local.json.a11y-auth.local.json
Use the committed auth-config.example.json file only as a template.
- Node.js 20+ recommended for local use
- Chromium installed for Playwright scans
Install dependencies:
npm install
npx playwright install --with-deps chromiumIf Playwright later reports a missing browser, run:
npx playwright installnode scripts/run-audit.mjs --site https://www.example.comInstalled CLI equivalent:
universal-a11y-audit audit --site https://www.example.comnode scripts/run-audit.mjs \
--site https://www.example.com \
--slow \
--respect-robots \
--cloudflare-awarenode scripts/run-audit.mjs \
--site https://www.example.com \
--urls-file ./reports/manual-urls.txt \
--slow \
--respect-robots \
--cloudflare-aware \
--batch-size 10The audit workflow runs four steps:
- Discover URLs from the sitemap, unless
--urls-fileis provided. - Scan pages with Playwright, axe-core, image-alt inventory, and Agentic Lighthouse scoring.
- Generate a Google Docs-ready Markdown summary.
- Generate a GitHub/backlog-ready ticket CSV, unless
--no-ticketsis used.
The scan is intentionally two-layered:
- WCAG/axe layer: finds accessibility violations and affected DOM nodes.
- Agentic Lighthouse layer: scores how well AI/browser agents can understand, call, and safely operate functional page surfaces.
Violation output now includes an importance field in addition to impact/priority. This is intended to help triage what should be handled first in a ticketing or project-management system.
The scan also attempts to flag issues that are likely out of direct control, especially when they appear to be inside:
- iframes
- embedded media players
- third-party widgets
- externally hosted embeds
importancelikely_out_of_controlcontrol_notes
importancelikely_out_of_controlcontrol_notesticket_notes
The out-of-control detection is a best-effort heuristic. It is designed to surface likely iframe/embed issues for manual review, not to make a perfect ownership decision automatically.
Each scan now includes an additional agent-readiness score modeled after Lighthouse-style category scoring. This score is separate from axe/WCAG violations and is designed to show how well browser-native AI agents and software test harnesses can understand and safely operate the site.
- WebMCP Protocol: checks for WebMCP-style manifests, page-level registration signals, and named functional surfaces such as cart, checkout, search, filter, sort, login, and booking controls.
- Accessibility Trees: checks whether form controls and clickable components expose precise accessible names through labels, text, ARIA, titles, or placeholders.
- Semantic Data Formatting: checks machine-readable discovery files including
/llms.txt,/robots.txt,/sitemap.xml, and/.well-known/ai-plugin.json. - Layout Stability: checks observed cumulative layout shift and whether interactive controls move after page load.
agentic-lighthouse-scores.csvagentic-lighthouse-report.json
The summary report includes overall and per-category averages, and the ticket backlog includes agent-readiness tickets for category scores below 90.
Each run writes to a site-name + timestamp folder so reports are easy to identify later:
reports/
example.com-20260122-141010/
urls.txt
a11y-violations.csv
a11y-report.json
a11y-run-metadata.json
a11y-summary-google-doc.md
a11y-github-tickets.csv
a11y-image-alts.csv
agentic-lighthouse-scores.csv
agentic-lighthouse-report.json
latest
urls.txt: URL list used for the run.a11y-violations.csv: one row per violating axe node, with impact, priority, importance, WCAG refs, ownership hints, and recommendations.a11y-report.json: full per-page JSON scan results, including axe results and Agentic Lighthouse page details.a11y-run-metadata.json: run timing, page counts, axe rollups, and agentic scoring rollups.a11y-summary-google-doc.md: paste-ready executive summary with WCAG findings and Agentic Lighthouse averages.a11y-github-tickets.csv: backlog-ready tickets for global accessibility issues, page-level accessibility issues, and low agentic category scores.a11y-image-alts.csv: image alt-text inventory with readability ratings and suggested review notes.agentic-lighthouse-scores.csv: per-page, per-category agent-readiness scores with findings, evidence, priority, importance, and recommendations.agentic-lighthouse-report.json: structured Agentic Lighthouse scoring details for every scored page.latest: text pointer containing the latest run ID.
90-100: pass70-89: needs review0-69: fail
Agentic tickets are generated for category scores below 90.
This repo is intended to stay public.
Do not commit:
.npmrcwith real tokens- npm access tokens
- GitHub personal access tokens
.envfiles with secrets- generated real-world reports under
reports/ - saved sitemap XML files from client sites
- internal or staging URLs
- browser/session files
This repo already ignores the common risky files in .gitignore, but you should still review what is staged before pushing.
git status
git ls-files
git grep -n "_authToken"
git grep -n "BEGIN PRIVATE KEY"
git grep -n "github_pat_"
git grep -n "ghp_"This project is set up to:
- stay a normal GitHub repo
- publish as a public npm CLI package
- optionally publish to GitHub Packages later
- auto-publish from GitHub Actions on version tags
See PUBLISHING.md for the exact release steps.
universal-a11y-audit help
universal-a11y-audit version
universal-a11y-audit audit --site https://www.example.com
universal-a11y-audit build-urls --site https://www.example.com --out ./reports/urls.txt
universal-a11y-audit scan --urls-file ./reports/urls.txt --out-dir ./reports
universal-a11y-audit report --run-dir ./reports/<run-id>
universal-a11y-audit tickets --run-dir ./reports/<run-id>
universal-a11y-audit sitemap-xml-to-urls --input ./saved-sitemap.xml --out ./reports/urls.txtThe shorter alias works the same way:
uaaudit audit --site https://www.example.comFull audit:
node scripts/run-audit.mjs --site https://www.example.comFull audit with explicit output folder:
node scripts/run-audit.mjs \
--site https://www.example.com \
--out-dir ./reportsProtected-site slow mode:
node scripts/run-audit.mjs \
--site https://www.example.com \
--slow \
--respect-robots \
--cloudflare-awareSmall-batch helper:
node scripts/run-audit.mjs \
--site https://www.example.com \
--urls-file ./reports/manual-urls.txt \
--slow \
--respect-robots \
--cloudflare-aware \
--batch-size 10Build URLs only:
node scripts/build-urls-from-sitemap.mjs \
--site https://www.example.com \
--out ./reports/<run-id>/urls.txtScan an existing URL list:
node scripts/a11y-audit.mjs \
--urls-file ./reports/<run-id>/urls.txt \
--out-dir ./reports \
--run-id <run-id>Scan by crawling from a start URL:
node scripts/a11y-audit.mjs \
--crawl \
--start https://www.example.com \
--max-pages 50 \
--out-dir ./reportsGenerate only the Markdown summary:
node scripts/generate-google-doc-report.mjs \
--run-dir ./reports/<run-id> \
--site https://www.example.comGenerate only the ticket/backlog CSV:
node scripts/generate-ticket-csv.mjs \
--run-dir ./reports/<run-id>Convert browser-saved sitemap XML into urls.txt:
node scripts/convert-sitemap-xml-to-urls.mjs \
--input ./saved-sitemap.xml \
--out ./reports/<run-id>/urls.txt--site: site origin to discover and audit.--sitemap-url: explicit sitemap URL when auto-discovery is not enough.--urls-file: use an existing URL list instead of building one from a sitemap.--out-dir: base output folder, defaults toreports.--run-id: explicit run folder name.--batch-size: cap the number of URLs scanned in one run.--slow: use more conservative navigation timing and retries.--respect-robots: respectrobots.txtdisallow rules during URL filtering/crawling.--cloudflare-aware: detect likely Cloudflare/WAF challenge pages and back off.--retries: override navigation retry count.--backoff-ms: override retry backoff timing.--crawl-delay-ms: add delay between scanned pages.--no-tickets: skip ticket CSV generation in the fullauditworkflow.--http-username/--http-password: HTTP Basic Auth credentials.--auth-config: local form-login config file.
npm run audit
npm run audit:crawl
npm run audit:urls
npm run audit:slow
npm run build:urls
npm run report
npm run sitemap:xml-to-urls
npm run pack:checkSome scans can take a while, especially when:
- the site has many pages
- you are using
--slow - the site has Cloudflare / WAF / bot protection
- pages are very large or have many violations
- axe analysis takes longer on complex pages
To make this clearer, the tool prints startup advisories, ETA hints, and heartbeat lines.
At the start of a scan, the tool may print notices such as:
- large scan detected
- small-batch mode enabled
- slow/protected-site mode enabled
- crawl delay in use
- retry/backoff policy in use
- Cloudflare-aware detection enabled
These are informational and help set expectations before the scan begins.
Each page starts with an ETA hint, for example:
[3/25] Scanning: https://example.com/page | ETA remaining: 7.5m
If a navigation or analysis step takes a while, the tool also prints heartbeat lines such as:
… still working on https://example.com/some-page (axe analysis) | elapsed 22.1s | ETA remaining: 6.3m
This means the process is still running and has not stalled.
A page with many violations, lots of DOM nodes, or heavy client-side rendering may legitimately take longer to analyze. The heartbeat output is there to make long pages easier to trust.
Protected-site small-batch run:
node scripts/run-audit.mjs \
--site https://www.example.com \
--urls-file ./reports/manual-urls.txt \
--slow \
--respect-robots \
--cloudflare-aware \
--batch-size 10Fix note: run folders now consistently use
site-name + timestamp, for exampleexample.com-20260307-094546.
MIT