@mcptoolshop/accessibility-suite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +63 -0
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/docs/prov-spec/.github/workflows/ci.yml +68 -0
- package/docs/prov-spec/CHANGELOG.md +69 -0
- package/docs/prov-spec/CODE_OF_CONDUCT.md +129 -0
- package/docs/prov-spec/CONFORMANCE_LEVELS.md +223 -0
- package/docs/prov-spec/CONTRIBUTING.md +145 -0
- package/docs/prov-spec/IMPLEMENTER_CHECKLIST.md +137 -0
- package/docs/prov-spec/LICENSE +21 -0
- package/docs/prov-spec/PRESS_RELEASE.md +74 -0
- package/docs/prov-spec/README.md +182 -0
- package/docs/prov-spec/SETUP.md +135 -0
- package/docs/prov-spec/WHY.md +86 -0
- package/docs/prov-spec/examples/artifact.example.json +14 -0
- package/docs/prov-spec/examples/artifact.ref.example.json +9 -0
- package/docs/prov-spec/examples/evidence.example.json +6 -0
- package/docs/prov-spec/examples/mcp.envelope.example.json +97 -0
- package/docs/prov-spec/examples/mcp.request.example.json +28 -0
- package/docs/prov-spec/examples/prov.record.example.json +35 -0
- package/docs/prov-spec/interop/PROOF_NODE_ENGINE.md +114 -0
- package/docs/prov-spec/spec/MCP_COMPATIBILITY.md +241 -0
- package/docs/prov-spec/spec/PROV_METHODS_CATALOG.md +142 -0
- package/docs/prov-spec/spec/PROV_METHODS_SPEC.md +397 -0
- package/docs/prov-spec/spec/methods.json +213 -0
- package/docs/prov-spec/spec/schemas/artifact.ref.schema.v0.1.json +58 -0
- package/docs/prov-spec/spec/schemas/artifact.schema.v0.1.json +61 -0
- package/docs/prov-spec/spec/schemas/assist.request.schema.v0.1.json +52 -0
- package/docs/prov-spec/spec/schemas/assist.response.schema.v0.1.json +70 -0
- package/docs/prov-spec/spec/schemas/cli.error.schema.v0.1.json +78 -0
- package/docs/prov-spec/spec/schemas/evidence.schema.v0.1.json +37 -0
- package/docs/prov-spec/spec/schemas/mcp.envelope.schema.v0.1.json +141 -0
- package/docs/prov-spec/spec/schemas/mcp.request.schema.v0.1.json +79 -0
- package/docs/prov-spec/spec/schemas/methods.schema.json +93 -0
- package/docs/prov-spec/spec/schemas/prov-capabilities.schema.json +122 -0
- package/docs/prov-spec/spec/schemas/prov.record.schema.v0.1.json +133 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/expected.json +4 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/input.json +1 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/double_wrapped.json +14 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/wrong_schema_version.json +11 -0
- package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/expected.json +24 -0
- package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/input.json +8 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/expected.json +7 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/input.json +1 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/non_hex_chars.json +16 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/uppercase_hex.json +16 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/wrong_length.json +16 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/hyphen_separator.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/reserved_namespace.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/starts_with_digit.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/uppercase.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/positive/valid_ids.json +18 -0
- package/docs/prov-spec/tools/python/prov_validator.py +428 -0
- package/examples/a11y-demo-site/.github/workflows/a11y-artifacts.yml +81 -0
- package/examples/a11y-demo-site/.github/workflows/a11y.yml +34 -0
- package/examples/a11y-demo-site/CODE_OF_CONDUCT.md +129 -0
- package/examples/a11y-demo-site/CONTRIBUTING.md +83 -0
- package/examples/a11y-demo-site/LICENSE +21 -0
- package/examples/a11y-demo-site/README.md +155 -0
- package/examples/a11y-demo-site/html/contact.html +15 -0
- package/examples/a11y-demo-site/html/index.html +20 -0
- package/examples/a11y-demo-site/scripts/a11y.sh +20 -0
- package/package.json +26 -0
- package/src/a11y-assist/.github/workflows/publish.yml +52 -0
- package/src/a11y-assist/.github/workflows/test.yml +30 -0
- package/src/a11y-assist/A11Y_ASSIST_TEST_COVERAGE_REQUIREMENTS.md +104 -0
- package/src/a11y-assist/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-assist/CONTRIBUTING.md +98 -0
- package/src/a11y-assist/ENGINE.md +363 -0
- package/src/a11y-assist/LICENSE +21 -0
- package/src/a11y-assist/PRESS_RELEASE.md +71 -0
- package/src/a11y-assist/QUICKSTART.md +101 -0
- package/src/a11y-assist/README.md +192 -0
- package/src/a11y-assist/RELEASE_NOTES.md +319 -0
- package/src/a11y-assist/a11y_assist/__init__.py +3 -0
- package/src/a11y-assist/a11y_assist/cli.py +599 -0
- package/src/a11y-assist/a11y_assist/from_cli_error.py +149 -0
- package/src/a11y-assist/a11y_assist/guard.py +444 -0
- package/src/a11y-assist/a11y_assist/ingest.py +407 -0
- package/src/a11y-assist/a11y_assist/methods.py +137 -0
- package/src/a11y-assist/a11y_assist/parse_raw.py +71 -0
- package/src/a11y-assist/a11y_assist/profiles/__init__.py +29 -0
- package/src/a11y-assist/a11y_assist/profiles/cognitive_load.py +245 -0
- package/src/a11y-assist/a11y_assist/profiles/cognitive_load_render.py +86 -0
- package/src/a11y-assist/a11y_assist/profiles/dyslexia.py +144 -0
- package/src/a11y-assist/a11y_assist/profiles/dyslexia_render.py +77 -0
- package/src/a11y-assist/a11y_assist/profiles/plain_language.py +119 -0
- package/src/a11y-assist/a11y_assist/profiles/plain_language_render.py +66 -0
- package/src/a11y-assist/a11y_assist/profiles/screen_reader.py +348 -0
- package/src/a11y-assist/a11y_assist/profiles/screen_reader_render.py +89 -0
- package/src/a11y-assist/a11y_assist/render.py +95 -0
- package/src/a11y-assist/a11y_assist/schemas/assist.request.schema.v0.1.json +52 -0
- package/src/a11y-assist/a11y_assist/schemas/assist.response.schema.v0.1.json +70 -0
- package/src/a11y-assist/a11y_assist/schemas/cli.error.schema.v0.1.json +78 -0
- package/src/a11y-assist/a11y_assist/storage.py +31 -0
- package/src/a11y-assist/pyproject.toml +60 -0
- package/src/a11y-assist/tests/__init__.py +1 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_high.json +18 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_medium.json +16 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/raw_text_low.txt +3 -0
- package/src/a11y-assist/tests/fixtures/cli_error_good.json +9 -0
- package/src/a11y-assist/tests/fixtures/cli_error_missing_id.json +7 -0
- package/src/a11y-assist/tests/fixtures/cli_error_string_format.json +7 -0
- package/src/a11y-assist/tests/fixtures/expected/cognitive_load_high.txt +20 -0
- package/src/a11y-assist/tests/fixtures/expected/dyslexia_high.txt +20 -0
- package/src/a11y-assist/tests/fixtures/expected/lowvision_high.txt +18 -0
- package/src/a11y-assist/tests/fixtures/expected/plain_language_high.txt +14 -0
- package/src/a11y-assist/tests/fixtures/expected/screen_reader_high.txt +19 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_cli_error.txt +16 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_no_id.txt +14 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_with_id.txt +14 -0
- package/src/a11y-assist/tests/fixtures/raw_good.txt +11 -0
- package/src/a11y-assist/tests/fixtures/raw_no_id.txt +2 -0
- package/src/a11y-assist/tests/test_cognitive_load.py +469 -0
- package/src/a11y-assist/tests/test_dyslexia.py +337 -0
- package/src/a11y-assist/tests/test_explain.py +74 -0
- package/src/a11y-assist/tests/test_golden.py +127 -0
- package/src/a11y-assist/tests/test_guard.py +819 -0
- package/src/a11y-assist/tests/test_guard_integration.py +457 -0
- package/src/a11y-assist/tests/test_ingest.py +311 -0
- package/src/a11y-assist/tests/test_methods_metadata.py +236 -0
- package/src/a11y-assist/tests/test_plain_language.py +348 -0
- package/src/a11y-assist/tests/test_render.py +117 -0
- package/src/a11y-assist/tests/test_screen_reader.py +703 -0
- package/src/a11y-assist/tests/test_storage_last.py +61 -0
- package/src/a11y-assist/tests/test_triage.py +86 -0
- package/src/a11y-ci/.github/workflows/ci.yml +43 -0
- package/src/a11y-ci/.github/workflows/test.yml +30 -0
- package/src/a11y-ci/A11Y_CI_TEST_COVERAGE_REQUIREMENTS.md +94 -0
- package/src/a11y-ci/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-ci/CONTRIBUTING.md +142 -0
- package/src/a11y-ci/LICENSE +21 -0
- package/src/a11y-ci/README.md +105 -0
- package/src/a11y-ci/a11y_ci/__init__.py +3 -0
- package/src/a11y-ci/a11y_ci/allowlist.py +83 -0
- package/src/a11y-ci/a11y_ci/cli.py +145 -0
- package/src/a11y-ci/a11y_ci/gate.py +131 -0
- package/src/a11y-ci/a11y_ci/render.py +48 -0
- package/src/a11y-ci/a11y_ci/schemas/allowlist.schema.json +24 -0
- package/src/a11y-ci/a11y_ci/scorecard.py +99 -0
- package/src/a11y-ci/npm/package.json +35 -0
- package/src/a11y-ci/pyproject.toml +64 -0
- package/src/a11y-ci/tests/__init__.py +1 -0
- package/src/a11y-ci/tests/fixtures/allowlist_expired.json +10 -0
- package/src/a11y-ci/tests/fixtures/allowlist_ok.json +10 -0
- package/src/a11y-ci/tests/fixtures/baseline_ok.json +7 -0
- package/src/a11y-ci/tests/fixtures/current_fail.json +6 -0
- package/src/a11y-ci/tests/fixtures/current_ok.json +6 -0
- package/src/a11y-ci/tests/fixtures/current_regresses.json +7 -0
- package/src/a11y-ci/tests/test_gate.py +134 -0
- package/src/a11y-evidence-engine/.github/workflows/ci.yml +53 -0
- package/src/a11y-evidence-engine/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-evidence-engine/CONTRIBUTING.md +128 -0
- package/src/a11y-evidence-engine/LICENSE +21 -0
- package/src/a11y-evidence-engine/README.md +71 -0
- package/src/a11y-evidence-engine/bin/a11y-engine.js +11 -0
- package/src/a11y-evidence-engine/fixtures/bad/button-no-name.html +30 -0
- package/src/a11y-evidence-engine/fixtures/bad/img-missing-alt.html +19 -0
- package/src/a11y-evidence-engine/fixtures/bad/input-missing-label.html +26 -0
- package/src/a11y-evidence-engine/fixtures/bad/missing-lang.html +11 -0
- package/src/a11y-evidence-engine/fixtures/good/index.html +29 -0
- package/src/a11y-evidence-engine/package-lock.json +109 -0
- package/src/a11y-evidence-engine/package.json +45 -0
- package/src/a11y-evidence-engine/src/cli.js +74 -0
- package/src/a11y-evidence-engine/src/evidence/canonicalize.js +52 -0
- package/src/a11y-evidence-engine/src/evidence/json_pointer.js +34 -0
- package/src/a11y-evidence-engine/src/evidence/prov_emit.js +153 -0
- package/src/a11y-evidence-engine/src/fswalk.js +56 -0
- package/src/a11y-evidence-engine/src/html_parse.js +117 -0
- package/src/a11y-evidence-engine/src/ids.js +53 -0
- package/src/a11y-evidence-engine/src/rules/document_missing_lang.js +50 -0
- package/src/a11y-evidence-engine/src/rules/form_control_missing_label.js +105 -0
- package/src/a11y-evidence-engine/src/rules/img_missing_alt.js +77 -0
- package/src/a11y-evidence-engine/src/rules/index.js +37 -0
- package/src/a11y-evidence-engine/src/rules/interactive_missing_name.js +129 -0
- package/src/a11y-evidence-engine/src/scan.js +128 -0
- package/src/a11y-evidence-engine/test/scan.test.js +149 -0
- package/src/a11y-evidence-engine/test/vectors.test.js +200 -0
- package/src/a11y-lint/.github/workflows/ci.yml +46 -0
- package/src/a11y-lint/.github/workflows/test.yml +34 -0
- package/src/a11y-lint/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-lint/CONTRIBUTING.md +70 -0
- package/src/a11y-lint/GOVERNANCE.md +57 -0
- package/src/a11y-lint/LICENSE +21 -0
- package/src/a11y-lint/PRESS_RELEASE.md +50 -0
- package/src/a11y-lint/README.md +276 -0
- package/src/a11y-lint/RELEASE_NOTES.md +57 -0
- package/src/a11y-lint/RELEASING.md +57 -0
- package/src/a11y-lint/a11y_lint/__init__.py +64 -0
- package/src/a11y-lint/a11y_lint/cli.py +319 -0
- package/src/a11y-lint/a11y_lint/errors.py +252 -0
- package/src/a11y-lint/a11y_lint/render.py +293 -0
- package/src/a11y-lint/a11y_lint/report_md.py +289 -0
- package/src/a11y-lint/a11y_lint/scan_cli_text.py +434 -0
- package/src/a11y-lint/a11y_lint/schemas/cli.error.schema.v0.1.json +83 -0
- package/src/a11y-lint/a11y_lint/scorecard.py +244 -0
- package/src/a11y-lint/a11y_lint/validate.py +225 -0
- package/src/a11y-lint/pyproject.toml +75 -0
- package/src/a11y-lint/tests/__init__.py +1 -0
- package/src/a11y-lint/tests/test_cli.py +200 -0
- package/src/a11y-lint/tests/test_errors.py +188 -0
- package/src/a11y-lint/tests/test_render.py +202 -0
- package/src/a11y-lint/tests/test_report_md.py +188 -0
- package/src/a11y-lint/tests/test_scan_cli_text.py +290 -0
- package/src/a11y-lint/tests/test_scorecard.py +195 -0
- package/src/a11y-lint/tests/test_validate.py +257 -0
- package/src/a11y-mcp-tools/.github/workflows/ci.yml +53 -0
- package/src/a11y-mcp-tools/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-mcp-tools/CONTRIBUTING.md +136 -0
- package/src/a11y-mcp-tools/LICENSE +21 -0
- package/src/a11y-mcp-tools/PROV_METHODS_CATALOG.md +104 -0
- package/src/a11y-mcp-tools/README.md +168 -0
- package/src/a11y-mcp-tools/bin/cli.js +452 -0
- package/src/a11y-mcp-tools/bin/server.js +244 -0
- package/src/a11y-mcp-tools/fixtures/requests/a11y.diagnose.ok.json +27 -0
- package/src/a11y-mcp-tools/fixtures/requests/a11y.evidence.ok.json +25 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.ok.json +139 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.provenance_fail.json +13 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.evidence.ok.json +88 -0
- package/src/a11y-mcp-tools/package-lock.json +189 -0
- package/src/a11y-mcp-tools/package.json +49 -0
- package/src/a11y-mcp-tools/src/envelope.js +197 -0
- package/src/a11y-mcp-tools/src/index.js +9 -0
- package/src/a11y-mcp-tools/src/schemas/artifact.js +85 -0
- package/src/a11y-mcp-tools/src/schemas/diagnosis.schema.v0.1.json +137 -0
- package/src/a11y-mcp-tools/src/schemas/envelope.schema.v0.1.json +108 -0
- package/src/a11y-mcp-tools/src/schemas/evidence.bundle.schema.v0.1.json +129 -0
- package/src/a11y-mcp-tools/src/schemas/evidence.js +97 -0
- package/src/a11y-mcp-tools/src/schemas/index.js +11 -0
- package/src/a11y-mcp-tools/src/schemas/provenance.js +140 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.request.schema.v0.1.json +77 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.response.schema.v0.1.json +50 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.request.schema.v0.1.json +120 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.response.schema.v0.1.json +50 -0
- package/src/a11y-mcp-tools/src/tools/diagnose.js +597 -0
- package/src/a11y-mcp-tools/src/tools/evidence.js +481 -0
- package/src/a11y-mcp-tools/src/tools/index.js +10 -0
- package/src/a11y-mcp-tools/test/contract.test.mjs +154 -0
- package/src/a11y-mcp-tools/test/diagnose.test.js +485 -0
- package/src/a11y-mcp-tools/test/evidence.test.js +183 -0
- package/src/a11y-mcp-tools/test/schema.test.js +327 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# a11y-mcp-tools
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) tools for accessibility evidence capture and diagnosis.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### `a11y.evidence`
|
|
8
|
+
|
|
9
|
+
Capture tamper-evident evidence bundles from HTML files, CLI logs, or other inputs.
|
|
10
|
+
|
|
11
|
+
**Capabilities:**
|
|
12
|
+
- Canonical HTML normalization
|
|
13
|
+
- DOM snapshot extraction
|
|
14
|
+
- SHA-256 integrity digests
|
|
15
|
+
- prov-spec provenance records
|
|
16
|
+
|
|
17
|
+
### `a11y.diagnose`
|
|
18
|
+
|
|
19
|
+
Run deterministic accessibility checks over evidence bundles.
|
|
20
|
+
|
|
21
|
+
**Capabilities:**
|
|
22
|
+
- WCAG 2.2 AA rule checking
|
|
23
|
+
- Evidence-anchored findings (JSON Pointer, CSS selector, line spans)
|
|
24
|
+
- SAFE-only fix guidance (intent patches, not direct writes)
|
|
25
|
+
- Provenance verification
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g a11y-mcp-tools
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### CLI (Recommended)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Capture evidence from HTML file
|
|
39
|
+
a11y evidence --target index.html --dom-snapshot --out evidence.json
|
|
40
|
+
|
|
41
|
+
# Diagnose captured evidence
|
|
42
|
+
a11y diagnose --bundle evidence.json --fix
|
|
43
|
+
|
|
44
|
+
# With provenance verification
|
|
45
|
+
a11y diagnose --bundle evidence.json --verify-provenance --fix
|
|
46
|
+
|
|
47
|
+
# Output with MCP envelope
|
|
48
|
+
a11y evidence --target page.html --dom-snapshot --envelope
|
|
49
|
+
|
|
50
|
+
# One-liner capture and diagnose
|
|
51
|
+
a11y evidence --target page.html --dom-snapshot | a11y diagnose --fix
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Exit Codes (CI-native):**
|
|
55
|
+
- `0` - Success (no findings at/above `--fail-on`)
|
|
56
|
+
- `2` - Findings exist (tool succeeded, but issues found)
|
|
57
|
+
- `3` - Capture/validation failure (bad input, schema error)
|
|
58
|
+
- `4` - Provenance verification failed (digest mismatch)
|
|
59
|
+
|
|
60
|
+
### As MCP Server
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
a11y-mcp
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### MCP Envelope Format (v0.1)
|
|
67
|
+
|
|
68
|
+
Requests and responses use a standard envelope:
|
|
69
|
+
|
|
70
|
+
**Request:**
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcp": {
|
|
74
|
+
"envelope": "mcp.envelope_v0_1",
|
|
75
|
+
"request_id": "req_01HR9Y6GQ7V8WQ0K8N9K",
|
|
76
|
+
"tool": "a11y.evidence",
|
|
77
|
+
"client": { "name": "a11y-cli", "version": "0.2.0" }
|
|
78
|
+
},
|
|
79
|
+
"input": {
|
|
80
|
+
"targets": [{ "kind": "file", "path": "html/index.html" }],
|
|
81
|
+
"capture": { "html": { "canonicalize": true }, "dom": { "snapshot": true } }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Response:**
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcp": {
|
|
90
|
+
"envelope": "mcp.envelope_v0_1",
|
|
91
|
+
"request_id": "req_01HR9Y6GQ7V8WQ0K8N9K",
|
|
92
|
+
"tool": "a11y.evidence",
|
|
93
|
+
"ok": true
|
|
94
|
+
},
|
|
95
|
+
"result": {
|
|
96
|
+
"bundle": { ... }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Error:**
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcp": {
|
|
105
|
+
"envelope": "mcp.envelope_v0_1",
|
|
106
|
+
"request_id": "req_01HR9Y6GQ7V8WQ0K8N9K",
|
|
107
|
+
"tool": "a11y.diagnose",
|
|
108
|
+
"ok": false
|
|
109
|
+
},
|
|
110
|
+
"error": {
|
|
111
|
+
"code": "PROVENANCE_VERIFICATION_FAILED",
|
|
112
|
+
"message": "Evidence digest mismatch for artifact:dom:index.",
|
|
113
|
+
"fix": "Re-run a11y.evidence to recapture evidence."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Schemas
|
|
119
|
+
|
|
120
|
+
JSON Schemas are provided for validation:
|
|
121
|
+
|
|
122
|
+
- [`envelope.schema.v0.1.json`](src/schemas/envelope.schema.v0.1.json) - MCP envelope format
|
|
123
|
+
- [`evidence.bundle.schema.v0.1.json`](src/schemas/evidence.bundle.schema.v0.1.json) - Evidence bundle format
|
|
124
|
+
- [`diagnosis.schema.v0.1.json`](src/schemas/diagnosis.schema.v0.1.json) - Diagnosis output format
|
|
125
|
+
|
|
126
|
+
## Method ID Catalog (v0.1)
|
|
127
|
+
|
|
128
|
+
Stable method IDs for provenance tracking. See [PROV_METHODS_CATALOG.md](PROV_METHODS_CATALOG.md) for full documentation.
|
|
129
|
+
|
|
130
|
+
| Method ID | Description |
|
|
131
|
+
|-----------|-------------|
|
|
132
|
+
| `adapter.wrap.envelope_v0_1` | Wrap in MCP envelope |
|
|
133
|
+
| `adapter.provenance.record_v0_1` | Provenance record creation |
|
|
134
|
+
| `adapter.integrity.sha256_v0_1` | SHA-256 integrity verification |
|
|
135
|
+
| `engine.capture.html_canonicalize_v0_1` | HTML capture with canonicalization |
|
|
136
|
+
| `engine.capture.dom_snapshot_v0_1` | DOM snapshot extraction |
|
|
137
|
+
| `engine.diagnose.wcag_rules_v0_1` | WCAG rule evaluation |
|
|
138
|
+
| `engine.extract.evidence.json_pointer_v0_1` | JSON Pointer evidence extraction |
|
|
139
|
+
| `engine.extract.evidence.selector_v0_1` | CSS selector evidence extraction |
|
|
140
|
+
|
|
141
|
+
## Shared Artifact Model
|
|
142
|
+
|
|
143
|
+
Both tools work with a shared artifact/provenance model:
|
|
144
|
+
|
|
145
|
+
- **Artifacts**: Captured content with digests and metadata
|
|
146
|
+
- **Evidence Anchors**: Pointers back to artifact locations (JSON Pointer, selector, line span)
|
|
147
|
+
- **Provenance**: prov-spec records documenting capture and analysis
|
|
148
|
+
|
|
149
|
+
## WCAG Rules (v0.1)
|
|
150
|
+
|
|
151
|
+
| Rule | Finding ID | WCAG | Description |
|
|
152
|
+
|------|-----------|------|-------------|
|
|
153
|
+
| `lang` | `a11y.lang.missing` | 3.1.1 | Missing lang attribute on html element |
|
|
154
|
+
| `alt` | `a11y.img.missing_alt` | 1.1.1 | Missing alt attribute on img element |
|
|
155
|
+
| `button-name` | `a11y.button.missing_name` | 4.1.2 | Button without accessible name |
|
|
156
|
+
| `link-name` | `a11y.link.missing_name` | 4.1.2 | Link without accessible name |
|
|
157
|
+
| `label` | `a11y.input.missing_label` | 1.3.1 | Form input without label |
|
|
158
|
+
|
|
159
|
+
## Related
|
|
160
|
+
|
|
161
|
+
- [prov-spec](https://github.com/mcp-tool-shop-org/prov-spec) - Provenance specification
|
|
162
|
+
- [a11y-evidence-engine](https://github.com/mcp-tool-shop-org/a11y-evidence-engine) - CLI scanner
|
|
163
|
+
- [a11y-assist](https://github.com/mcp-tool-shop-org/a11y-assist) - Fix advisor
|
|
164
|
+
- [a11y-demo-site](https://github.com/mcp-tool-shop-org/a11y-demo-site) - Demo with CI workflows
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* a11y CLI - Command-line interface for a11y MCP tools.
|
|
6
|
+
*
|
|
7
|
+
* Maps directly to MCP tool calls with standard exit codes:
|
|
8
|
+
* 0 = success, no findings at/above --fail-on
|
|
9
|
+
* 2 = success, findings exist (but not a tool failure)
|
|
10
|
+
* 3 = capture/validation failure (bad input, schema fail, etc.)
|
|
11
|
+
* 4 = provenance verification failed (digest mismatch)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const { evidence, diagnose } = require("../src/tools/index.js");
|
|
17
|
+
const {
|
|
18
|
+
createRequestEnvelope,
|
|
19
|
+
createResponseEnvelope,
|
|
20
|
+
createErrorEnvelope,
|
|
21
|
+
ERROR_CODES,
|
|
22
|
+
} = require("../src/envelope.js");
|
|
23
|
+
|
|
24
|
+
const VERSION = "0.2.0";
|
|
25
|
+
|
|
26
|
+
// Exit codes (CI-native)
|
|
27
|
+
const EXIT_OK = 0; // Success, no findings
|
|
28
|
+
const EXIT_FINDINGS = 2; // Success, findings exist
|
|
29
|
+
const EXIT_VALIDATION = 3; // Capture/validation failure
|
|
30
|
+
const EXIT_PROVENANCE = 4; // Provenance verification failed
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse command-line arguments.
|
|
34
|
+
*/
|
|
35
|
+
function parseArgs(args) {
|
|
36
|
+
const result = {
|
|
37
|
+
command: null,
|
|
38
|
+
flags: {},
|
|
39
|
+
positional: [],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let i = 0;
|
|
43
|
+
while (i < args.length) {
|
|
44
|
+
const arg = args[i];
|
|
45
|
+
|
|
46
|
+
if (!result.command && !arg.startsWith("-")) {
|
|
47
|
+
result.command = arg;
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (arg.startsWith("--")) {
|
|
53
|
+
const key = arg.slice(2);
|
|
54
|
+
const nextArg = args[i + 1];
|
|
55
|
+
|
|
56
|
+
// Boolean flags vs value flags
|
|
57
|
+
if (
|
|
58
|
+
!nextArg ||
|
|
59
|
+
nextArg.startsWith("-") ||
|
|
60
|
+
key === "dom-snapshot" ||
|
|
61
|
+
key === "canonicalize" ||
|
|
62
|
+
key === "strip-dynamic" ||
|
|
63
|
+
key === "fix" ||
|
|
64
|
+
key === "json" ||
|
|
65
|
+
key === "envelope" ||
|
|
66
|
+
key === "verify-provenance" ||
|
|
67
|
+
key === "help" ||
|
|
68
|
+
key === "version"
|
|
69
|
+
) {
|
|
70
|
+
result.flags[key] = true;
|
|
71
|
+
i++;
|
|
72
|
+
} else {
|
|
73
|
+
result.flags[key] = nextArg;
|
|
74
|
+
i += 2;
|
|
75
|
+
}
|
|
76
|
+
} else if (arg.startsWith("-")) {
|
|
77
|
+
// Short flags
|
|
78
|
+
const key = arg.slice(1);
|
|
79
|
+
result.flags[key] = true;
|
|
80
|
+
i++;
|
|
81
|
+
} else {
|
|
82
|
+
result.positional.push(arg);
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Print help message.
|
|
92
|
+
*/
|
|
93
|
+
function printHelp() {
|
|
94
|
+
console.log(`
|
|
95
|
+
a11y - Accessibility evidence capture and diagnosis
|
|
96
|
+
|
|
97
|
+
USAGE:
|
|
98
|
+
a11y <command> [options]
|
|
99
|
+
|
|
100
|
+
COMMANDS:
|
|
101
|
+
evidence Capture tamper-evident evidence bundles
|
|
102
|
+
diagnose Run accessibility diagnosis on evidence
|
|
103
|
+
|
|
104
|
+
EVIDENCE OPTIONS:
|
|
105
|
+
--target <path> File to capture (can be repeated)
|
|
106
|
+
--dom-snapshot Include DOM snapshot artifact
|
|
107
|
+
--canonicalize Canonicalize HTML content
|
|
108
|
+
--strip-dynamic Strip dynamic attributes (React, Vue, Angular)
|
|
109
|
+
--label <label> Add label to artifacts (can be repeated)
|
|
110
|
+
--out <path> Output bundle to file (default: stdout)
|
|
111
|
+
--envelope Wrap output in MCP envelope
|
|
112
|
+
|
|
113
|
+
DIAGNOSE OPTIONS:
|
|
114
|
+
--bundle <path> Path to evidence bundle JSON
|
|
115
|
+
--bundle-id <id> Bundle ID (when using MCP server)
|
|
116
|
+
--profile <profile> WCAG profile (wcag-2.0-a, wcag-2.0-aa, wcag-2.1-a,
|
|
117
|
+
wcag-2.1-aa, wcag-2.2-a, wcag-2.2-aa) (default: wcag-2.2-aa)
|
|
118
|
+
--rules <rules> Comma-separated rule names to run
|
|
119
|
+
--exclude <rules> Comma-separated rules to exclude
|
|
120
|
+
--fix Include fix guidance in findings
|
|
121
|
+
--verify-provenance Verify artifact digests before diagnosis
|
|
122
|
+
--fail-on <severity> Exit 2 if findings at/above severity (default: low)
|
|
123
|
+
--out <path> Output diagnosis to file (default: stdout)
|
|
124
|
+
--envelope Wrap output in MCP envelope
|
|
125
|
+
|
|
126
|
+
GLOBAL OPTIONS:
|
|
127
|
+
--json Output as JSON (default)
|
|
128
|
+
--envelope Wrap output in MCP envelope
|
|
129
|
+
--help, -h Show this help message
|
|
130
|
+
--version, -v Show version
|
|
131
|
+
|
|
132
|
+
EXIT CODES:
|
|
133
|
+
0 Success (no findings at/above --fail-on)
|
|
134
|
+
2 Findings exist (tool succeeded, but issues found)
|
|
135
|
+
3 Capture/validation failure (bad input, schema error)
|
|
136
|
+
4 Provenance verification failed (digest mismatch)
|
|
137
|
+
|
|
138
|
+
EXAMPLES:
|
|
139
|
+
# Capture evidence from HTML file
|
|
140
|
+
a11y evidence --target index.html --dom-snapshot --out evidence.json
|
|
141
|
+
|
|
142
|
+
# Diagnose captured evidence
|
|
143
|
+
a11y diagnose --bundle evidence.json --fix
|
|
144
|
+
|
|
145
|
+
# Diagnose with specific WCAG profile
|
|
146
|
+
a11y diagnose --bundle evidence.json --profile wcag-2.1-aa --fix
|
|
147
|
+
|
|
148
|
+
# With provenance verification
|
|
149
|
+
a11y diagnose --bundle evidence.json --verify-provenance --fix
|
|
150
|
+
|
|
151
|
+
# Output with MCP envelope
|
|
152
|
+
a11y evidence --target page.html --envelope
|
|
153
|
+
|
|
154
|
+
# One-liner capture and diagnose
|
|
155
|
+
a11y evidence --target page.html --dom-snapshot | a11y diagnose --fix
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Evidence command handler.
|
|
161
|
+
*/
|
|
162
|
+
async function handleEvidence(flags, positional) {
|
|
163
|
+
// Collect targets
|
|
164
|
+
const targets = [];
|
|
165
|
+
|
|
166
|
+
// From --target flags (may be repeated)
|
|
167
|
+
if (flags.target) {
|
|
168
|
+
const targetPaths = Array.isArray(flags.target)
|
|
169
|
+
? flags.target
|
|
170
|
+
: [flags.target];
|
|
171
|
+
for (const p of targetPaths) {
|
|
172
|
+
targets.push({ kind: "file", path: p });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// From positional args
|
|
177
|
+
for (const p of positional) {
|
|
178
|
+
targets.push({ kind: "file", path: p });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (targets.length === 0) {
|
|
182
|
+
console.error("Error: No targets specified. Use --target <path>");
|
|
183
|
+
process.exit(EXIT_VALIDATION);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Build capture options
|
|
187
|
+
const capture = {
|
|
188
|
+
html: {},
|
|
189
|
+
dom: {},
|
|
190
|
+
environment: { include: ["os", "node", "tool_versions"] },
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
if (flags["dom-snapshot"]) {
|
|
194
|
+
capture.dom.snapshot = true;
|
|
195
|
+
capture.dom.include_css_selectors = true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (flags.canonicalize) {
|
|
199
|
+
capture.html.canonicalize = true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (flags["strip-dynamic"]) {
|
|
203
|
+
capture.html.strip_dynamic = true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Collect labels
|
|
207
|
+
const labels = [];
|
|
208
|
+
if (flags.label) {
|
|
209
|
+
const labelList = Array.isArray(flags.label) ? flags.label : [flags.label];
|
|
210
|
+
labels.push(...labelList);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Build input
|
|
214
|
+
const input = { targets, capture, labels };
|
|
215
|
+
|
|
216
|
+
// Execute
|
|
217
|
+
const result = await evidence.execute(input);
|
|
218
|
+
|
|
219
|
+
if (!result.ok) {
|
|
220
|
+
if (flags.envelope) {
|
|
221
|
+
const errorEnvelope = createErrorEnvelope(
|
|
222
|
+
`req_cli_${Date.now()}`,
|
|
223
|
+
"a11y.evidence",
|
|
224
|
+
result.error?.code || ERROR_CODES.CAPTURE_FAILED,
|
|
225
|
+
result.error?.message || "Evidence capture failed"
|
|
226
|
+
);
|
|
227
|
+
console.log(JSON.stringify(errorEnvelope, null, 2));
|
|
228
|
+
} else {
|
|
229
|
+
console.error(`Error: ${result.error.message}`);
|
|
230
|
+
}
|
|
231
|
+
process.exit(EXIT_VALIDATION);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Output
|
|
235
|
+
let output;
|
|
236
|
+
if (flags.envelope) {
|
|
237
|
+
const envelope = createResponseEnvelope(
|
|
238
|
+
`req_cli_${Date.now()}`,
|
|
239
|
+
"a11y.evidence",
|
|
240
|
+
{ bundle: result.bundle }
|
|
241
|
+
);
|
|
242
|
+
output = JSON.stringify(envelope, null, 2);
|
|
243
|
+
} else {
|
|
244
|
+
output = JSON.stringify(result.bundle, null, 2);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (flags.out) {
|
|
248
|
+
fs.writeFileSync(flags.out, output + "\n");
|
|
249
|
+
console.error(`Bundle written to: ${flags.out}`);
|
|
250
|
+
} else {
|
|
251
|
+
console.log(output);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
process.exit(EXIT_OK);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Diagnose command handler.
|
|
259
|
+
*/
|
|
260
|
+
async function handleDiagnose(flags, positional) {
|
|
261
|
+
let bundle = null;
|
|
262
|
+
|
|
263
|
+
// Load bundle from file
|
|
264
|
+
if (flags.bundle) {
|
|
265
|
+
const bundlePath = path.resolve(flags.bundle);
|
|
266
|
+
if (!fs.existsSync(bundlePath)) {
|
|
267
|
+
console.error(`Error: Bundle file not found: ${bundlePath}`);
|
|
268
|
+
process.exit(EXIT_VALIDATION);
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
bundle = JSON.parse(fs.readFileSync(bundlePath, "utf8"));
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error(`Error: Failed to parse bundle: ${err.message}`);
|
|
274
|
+
process.exit(EXIT_VALIDATION);
|
|
275
|
+
}
|
|
276
|
+
} else if (positional.length > 0) {
|
|
277
|
+
// First positional as bundle path
|
|
278
|
+
const bundlePath = path.resolve(positional[0]);
|
|
279
|
+
if (!fs.existsSync(bundlePath)) {
|
|
280
|
+
console.error(`Error: Bundle file not found: ${bundlePath}`);
|
|
281
|
+
process.exit(EXIT_VALIDATION);
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
bundle = JSON.parse(fs.readFileSync(bundlePath, "utf8"));
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error(`Error: Failed to parse bundle: ${err.message}`);
|
|
287
|
+
process.exit(EXIT_VALIDATION);
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
// Read from stdin
|
|
291
|
+
const stdin = fs.readFileSync(0, "utf8");
|
|
292
|
+
try {
|
|
293
|
+
bundle = JSON.parse(stdin);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
console.error("Error: Failed to parse bundle from stdin");
|
|
296
|
+
process.exit(EXIT_VALIDATION);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Build rules filter
|
|
301
|
+
const rules = {};
|
|
302
|
+
if (flags.rules) {
|
|
303
|
+
rules.include = flags.rules.split(",").map((r) => r.trim());
|
|
304
|
+
}
|
|
305
|
+
if (flags.exclude) {
|
|
306
|
+
rules.exclude = flags.exclude.split(",").map((r) => r.trim());
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Build output options
|
|
310
|
+
const outputOptions = {};
|
|
311
|
+
if (flags.fix) {
|
|
312
|
+
outputOptions.include_fix_guidance = true;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Build integrity options
|
|
316
|
+
const integrity = {};
|
|
317
|
+
if (flags["verify-provenance"]) {
|
|
318
|
+
integrity.verify_provenance = true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Get profile (default wcag-2.2-aa)
|
|
322
|
+
const profile = flags.profile || "wcag-2.2-aa";
|
|
323
|
+
const validProfiles = [
|
|
324
|
+
"wcag-2.0-a",
|
|
325
|
+
"wcag-2.0-aa",
|
|
326
|
+
"wcag-2.1-a",
|
|
327
|
+
"wcag-2.1-aa",
|
|
328
|
+
"wcag-2.2-a",
|
|
329
|
+
"wcag-2.2-aa",
|
|
330
|
+
];
|
|
331
|
+
if (!validProfiles.includes(profile)) {
|
|
332
|
+
console.error(`Error: Invalid profile '${profile}'. Valid profiles: ${validProfiles.join(", ")}`);
|
|
333
|
+
process.exit(EXIT_VALIDATION);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Execute
|
|
337
|
+
const input = { bundle, rules, output: outputOptions, integrity, profile };
|
|
338
|
+
const result = await diagnose.execute(input);
|
|
339
|
+
|
|
340
|
+
if (!result.ok) {
|
|
341
|
+
// Check for provenance verification failure
|
|
342
|
+
const isProvenanceError =
|
|
343
|
+
result.error?.code === ERROR_CODES.PROVENANCE_VERIFICATION_FAILED ||
|
|
344
|
+
result.error?.code === ERROR_CODES.DIGEST_MISMATCH;
|
|
345
|
+
|
|
346
|
+
if (flags.envelope) {
|
|
347
|
+
const errorEnvelope = createErrorEnvelope(
|
|
348
|
+
`req_cli_${Date.now()}`,
|
|
349
|
+
"a11y.diagnose",
|
|
350
|
+
result.error?.code || ERROR_CODES.INTERNAL_ERROR,
|
|
351
|
+
result.error?.message || "Diagnosis failed",
|
|
352
|
+
result.error?.fix
|
|
353
|
+
);
|
|
354
|
+
console.log(JSON.stringify(errorEnvelope, null, 2));
|
|
355
|
+
} else {
|
|
356
|
+
console.error(`Error: ${result.error.message}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
process.exit(isProvenanceError ? EXIT_PROVENANCE : EXIT_VALIDATION);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Output
|
|
363
|
+
let output;
|
|
364
|
+
if (flags.envelope) {
|
|
365
|
+
const envelope = createResponseEnvelope(
|
|
366
|
+
`req_cli_${Date.now()}`,
|
|
367
|
+
"a11y.diagnose",
|
|
368
|
+
{ diagnosis: result.diagnosis }
|
|
369
|
+
);
|
|
370
|
+
output = JSON.stringify(envelope, null, 2);
|
|
371
|
+
} else {
|
|
372
|
+
output = JSON.stringify(result.diagnosis, null, 2);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (flags.out) {
|
|
376
|
+
fs.writeFileSync(flags.out, output + "\n");
|
|
377
|
+
console.error(`Diagnosis written to: ${flags.out}`);
|
|
378
|
+
} else {
|
|
379
|
+
console.log(output);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Exit based on findings
|
|
383
|
+
const findingsCount = result.diagnosis.summary.findings_total;
|
|
384
|
+
if (findingsCount > 0) {
|
|
385
|
+
// Check --fail-on threshold
|
|
386
|
+
const failOn = flags["fail-on"] || "low";
|
|
387
|
+
const severityOrder = ["low", "medium", "high", "critical"];
|
|
388
|
+
const failIndex = severityOrder.indexOf(failOn);
|
|
389
|
+
|
|
390
|
+
// Count findings at or above threshold
|
|
391
|
+
const counts = result.diagnosis.summary.severity_counts;
|
|
392
|
+
let failingCount = 0;
|
|
393
|
+
for (let i = failIndex; i < severityOrder.length; i++) {
|
|
394
|
+
failingCount += counts[severityOrder[i]] || 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (failingCount > 0) {
|
|
398
|
+
console.error(`Found ${failingCount} issue(s) at/above '${failOn}' severity`);
|
|
399
|
+
process.exit(EXIT_FINDINGS);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
process.exit(EXIT_OK);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Main entry point.
|
|
408
|
+
*/
|
|
409
|
+
async function main() {
|
|
410
|
+
const args = process.argv.slice(2);
|
|
411
|
+
|
|
412
|
+
if (args.length === 0) {
|
|
413
|
+
printHelp();
|
|
414
|
+
process.exit(EXIT_OK);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const { command, flags, positional } = parseArgs(args);
|
|
418
|
+
|
|
419
|
+
// Global flags
|
|
420
|
+
if (flags.help || flags.h) {
|
|
421
|
+
printHelp();
|
|
422
|
+
process.exit(EXIT_OK);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (flags.version || flags.v) {
|
|
426
|
+
console.log(`a11y v${VERSION}`);
|
|
427
|
+
process.exit(EXIT_OK);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Route to command handler
|
|
431
|
+
switch (command) {
|
|
432
|
+
case "evidence":
|
|
433
|
+
await handleEvidence(flags, positional);
|
|
434
|
+
break;
|
|
435
|
+
|
|
436
|
+
case "diagnose":
|
|
437
|
+
await handleDiagnose(flags, positional);
|
|
438
|
+
break;
|
|
439
|
+
|
|
440
|
+
default:
|
|
441
|
+
if (command) {
|
|
442
|
+
console.error(`Unknown command: ${command}`);
|
|
443
|
+
}
|
|
444
|
+
printHelp();
|
|
445
|
+
process.exit(EXIT_VALIDATION);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
main().catch((err) => {
|
|
450
|
+
console.error(`Fatal error: ${err.message}`);
|
|
451
|
+
process.exit(EXIT_VALIDATION);
|
|
452
|
+
});
|