@wholisphere.ai/scanner-runner
v0.1.0
Published
Headless scan-mode runner for Wholisphere. Fetches pages, runs capability evaluators, submits findings to /v1/scans/:id/findings. Used standalone (CLI), in CI (GitHub Action), or as a library.
Readme
@wholisphere.ai/scanner-runner
Headless scan-mode runner for Wholisphere. Fetches pages, runs the agent's WCAG capability evaluators against each page, submits findings to /v1/scans/:id/findings, and completes the scan.
Use it three ways:
- CLI —
wholisphere-scanbinary - GitHub Action —
action.yml(composite). Onpull_requestevents the action also posts a one-line summary as a PR comment with a diff vs the previous scan; subsequent pushes update the same comment via a hidden marker. - Library — programmatic API for any Node 20+ runtime
Install
npm install --save-dev @wholisphere.ai/scanner-runner
# or
pnpm add -D @wholisphere.ai/scanner-runnerCLI
wholisphere-scan \
--api-key $WHOLISPHERE_API_KEY \
--product-name "Acme App" \
--product-version 1.4.2 \
--sitemap https://acme.test/sitemap.xmlOr with a fixed URL list:
wholisphere-scan \
--scan-id scan_xyz \
--urls https://acme.test/pricing,https://acme.test/aboutFlags
| Flag | Description |
|---|---|
| --api-key <key> | API key (or WHOLISPHERE_API_KEY env var) |
| --base-url <url> | Backend base URL (default https://api.wholisphere.ai, or WHOLISPHERE_BASE_URL) |
| --scan-id <id> | Append findings to an existing scan |
| --product-name <name> | (with --product-version) auto-creates a scan |
| --product-version <ver> | (with --product-name) auto-creates a scan |
| --build-ref <sha> | Commit SHA / build identifier (recommended in CI) |
| --urls <a,b,c> | Comma-separated URLs |
| --sitemap <url> | Fetch + parse a sitemap.xml |
| --max-concurrency <n> | Parallel page count (default 3) |
| --gemini-api-key <key> | Optional. Activates the vision-LLM upgrade so SC 1.1.1 alt-text suspects are verified against the page (also reads GEMINI_API_KEY). Has the most effect with the browser runner — the static runner does not capture screenshots. |
| --verbose | Log each page result + API call |
| --help / -h | Show usage |
Exit codes
| Code | Meaning | |---|---| | 0 | Scan completed cleanly | | 1 | Invocation error (bad args, missing api key, sitemap fetch failed) | | 2 | Scan failed (every page errored OR backend rejected) |
Library
import {
ApiClient,
PageFetcher,
defaultCapabilities,
parseSitemapXml,
runScan,
} from '@wholisphere.ai/scanner-runner';
const api = new ApiClient({
baseUrl: 'https://api.wholisphere.ai',
apiKey: process.env.WHOLISPHERE_API_KEY!,
});
const sitemapXml = await fetch('https://acme.test/sitemap.xml').then((r) => r.text());
const urls = parseSitemapXml(sitemapXml);
const result = await runScan({
api,
productName: 'Acme App',
productVersion: '1.4.2',
buildRef: process.env.GITHUB_SHA,
urls,
capabilities: defaultCapabilities(),
fetcher: new PageFetcher({ timeoutMs: 60_000 }),
maxConcurrency: 5,
onPage: (e) => {
if (e.status === 'completed') {
console.log(` ${e.url} → ${e.findingCount} findings`);
}
},
});
console.log(`scan ${result.scanId}: ${result.pageCount} pages, ${result.findingCount} findings`);GitHub Action — PR comment
When the action runs in a pull_request context it posts a one-comment summary back to the PR with: scan ID, pages scanned, findings emitted, and (when a previous completed scan exists for the same product) a diff table showing regressed / improved / unchanged / newly evaluated counts plus the top regressions and improvements. Subsequent pushes to the same PR update the same comment via a hidden <!-- wholisphere-pr-comment --> marker so the thread stays clean.
The action posts via the default GITHUB_TOKEN, so workflows must grant write access to issues:
permissions:
contents: read
pull-requests: writeSet pr-comment: false to disable, or pr-comment: true to force a post even outside pull_request events.
How it works
For each URL:
PageFetcher.fetchPage(url)— fetches the HTML via Node 22+ nativefetchand parses it through happy-dom into anEvaluationDocument.evaluatePage()— runs every capability'sevaluate()against the document. Per-capability errors are isolated; a thrown evaluator does not crash the page.api.appendFindings()— POSTs the resulting findings to/v1/scans/:id/findings, where the backend's aggregator collapses them into per-criterion verdicts ready for the VPAT generator.
After all URLs finish, api.completeScan() marks the scan complete (or failed if every page errored). The backend emits a scan.completed webhook through the existing outbox.
Limitations
- No real browser rendering. happy-dom parses HTML statically; client-side JS that mutates the DOM is not executed. For SPA / auth-walled / dynamic-content scans, the headless Playwright driver (ADR-0003, separate package) is the right tool. The runner is pre-built for static + server-rendered pages, which covers ~70% of the typical site.
- Sequential sitemap walking. Sitemap-index files (sitemaps that link to other sitemaps) are not yet recursively expanded — a follow-up.
- No login replay. Auth-walled pages need cookie/header injection or a login-flow recorder; both are deferred to the Playwright driver.
Build
pnpm --filter @wholisphere.ai/scanner-runner buildProduces dist/cli.js (the executable for the wholisphere-scan binary) plus the library entry points.
License
MIT.
