@weaaare/mcp-nvda-auditor 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.
@@ -0,0 +1,428 @@
1
+ import { getPromptMessages, listPrompts } from '@weaaare/mcp-auditor-core';
2
+ // ─── Shared lifecycle note ────────────────────────────────────────────────────
3
+ // Every NVDA audit follows this discipline:
4
+ // 1. check_setup → nvda_start → navigate to URL → start_audit
5
+ // 2. … audit work using nvda_perform, nvda_press, nvda_next, nvda_last_spoken_phrase …
6
+ // 3. end_audit → generate_report → nvda_stop
7
+ const fullAccessibilityAudit = {
8
+ prompt: {
9
+ name: 'full_accessibility_audit',
10
+ description: 'Comprehensive NVDA screen reader accessibility audit of a web page. Covers headings, landmarks, links, forms, images, tables, and keyboard navigation.',
11
+ arguments: [
12
+ {
13
+ name: 'url',
14
+ description: 'URL of the page to audit',
15
+ required: true,
16
+ },
17
+ {
18
+ name: 'browser',
19
+ description: 'Browser to use (default: Chrome)',
20
+ required: false,
21
+ },
22
+ {
23
+ name: 'wcagLevel',
24
+ description: 'WCAG conformance level: A, AA, or AAA (default: AA)',
25
+ required: false,
26
+ },
27
+ ],
28
+ },
29
+ getMessage: (args) => {
30
+ const url = args.url ?? 'UNKNOWN_URL';
31
+ const browser = args.browser ?? 'Chrome';
32
+ const wcagLevel = args.wcagLevel ?? 'AA';
33
+ return [
34
+ {
35
+ role: 'user',
36
+ content: {
37
+ type: 'text',
38
+ text: `Perform a comprehensive NVDA accessibility audit of ${url}. Follow every step in order.
39
+
40
+ ## Phase 1 — Environment & NVDA Startup
41
+ 1. \`check_setup\` — verify Windows environment (NVDA is Windows-only)
42
+ 2. \`nvda_start\` — start NVDA
43
+ 3. Navigate to ${url} in ${browser} (use Playwright MCP, browser dev tools, or instruct the user)
44
+ 4. \`start_audit\` with url "${url}", screenReader "nvda", wcagLevel "${wcagLevel}"
45
+
46
+ **Browse mode note:** NVDA uses browse mode by default for web content. Single-letter commands (H, K, F, D…) navigate by element type. To interact with a specific control (e.g. type in a field), toggle to focus mode with \`nvda_perform\` → toggleBetweenBrowseAndFocusMode, interact, then press Escape to return to browse mode.
47
+
48
+ ## Phase 2 — Audit Workflow
49
+
50
+ ### 2.1 Page Title & Language
51
+ - \`nvda_perform\` → reportTitle — check what NVDA announces as the page title
52
+ - Verify the title is descriptive and unique (WCAG 2.4.2 Page Titled)
53
+ - \`nvda_last_spoken_phrase\` — capture the announcement
54
+ - \`log_finding\` — log pass or violation
55
+
56
+ ### 2.2 Heading Structure
57
+ - \`nvda_perform\` → moveToNextHeading (repeat until NVDA announces "no next heading" or similar)
58
+ - For each heading: \`nvda_last_spoken_phrase\` and \`nvda_item_text\` — record level and text
59
+ - Also audit specific levels: \`nvda_perform\` → moveToNextHeadingLevel1 (expect exactly one h1)
60
+ - Use \`nvda_perform\` → browseModeElementsList to open the Elements List dialog (NVDA+F7) — it shows all headings in a navigable list for a complete overview
61
+ - Verify:
62
+ - Exactly one h1 on the page (WCAG 2.4.6)
63
+ - No skipped levels (e.g., h1 → h3 without h2 is a violation)
64
+ - Heading text is descriptive
65
+ - \`log_finding\` for each issue (WCAG 1.3.1, 2.4.6)
66
+
67
+ ### 2.3 Landmark Regions
68
+ - \`nvda_perform\` → moveToNextLandmark (repeat until exhausted)
69
+ - For each landmark: \`nvda_last_spoken_phrase\` — identify its type (banner, navigation, main, contentinfo, complementary)
70
+ - Required: \`main\` landmark must exist. Recommended: \`navigation\` and \`contentinfo\`.
71
+ - \`log_finding\` for missing required landmarks (WCAG 1.3.1, 2.4.1)
72
+
73
+ ### 2.4 Links
74
+ - \`nvda_perform\` → moveToNextLink (repeat until exhausted)
75
+ - For each link: \`nvda_last_spoken_phrase\` — check announced text
76
+ - Watch for vague link text: "click here", "read more", "here", "more", URLs as link text
77
+ - Also audit visited vs unvisited: \`nvda_perform\` → moveToNextUnvisitedLink
78
+ - \`log_finding\` for vague links (WCAG 2.4.4 Link Purpose in Context)
79
+
80
+ ### 2.5 Forms
81
+ Navigate by specific control type for thorough coverage:
82
+ - **Text inputs / selects:** \`nvda_perform\` → moveToNextEditField / moveToNextFormField (repeat)
83
+ - **Buttons:** \`nvda_perform\` → moveToNextButton (repeat)
84
+ - **Checkboxes:** \`nvda_perform\` → moveToNextCheckbox (repeat)
85
+ - **Radio buttons:** \`nvda_perform\` → moveToNextRadioButton (repeat)
86
+ - **Combo boxes:** \`nvda_perform\` → moveToNextComboBox (repeat)
87
+
88
+ For each control:
89
+ a. \`nvda_last_spoken_phrase\` — check label, role, required state, current value
90
+ b. Verify label is announced (tab to control with \`nvda_press\` 'Tab')
91
+ c. Toggle to focus mode (\`nvda_perform\` → toggleBetweenBrowseAndFocusMode) to interact
92
+ d. For text fields: type invalid data, check error messages are announced
93
+ e. Return to browse mode (Escape key via \`nvda_press\` 'Escape')
94
+ - \`log_finding\` per issue (WCAG 1.3.1, 3.3.1, 3.3.2, 4.1.2)
95
+
96
+ ### 2.6 Images and Graphics
97
+ - \`nvda_perform\` → moveToNextGraphic (repeat until exhausted)
98
+ - For each image: \`nvda_last_spoken_phrase\` — check alt text announcement
99
+ - Informative images: must have descriptive alt text
100
+ - Decorative images: must be hidden (NVDA should skip them or announce "graphic" with empty alt)
101
+ - Complex images (charts/diagrams): check for long description (\`nvda_perform\` → openLongDescription)
102
+ - \`log_finding\` per issue (WCAG 1.1.1 Non-text Content)
103
+
104
+ ### 2.7 Tables
105
+ - \`nvda_perform\` → moveToNextTable (repeat)
106
+ - For each table: navigate cells with moveToNextColumn/moveToNextRow
107
+ - \`nvda_last_spoken_phrase\` — NVDA should announce column/row header context with each cell
108
+ - Verify headers are associated with data cells
109
+ - Check table caption or summary if present
110
+ - \`log_finding\` per issue (WCAG 1.3.1)
111
+
112
+ ### 2.8 Keyboard Navigation
113
+ - Press Tab from the start of the page (\`nvda_press\` 'Tab' repeatedly)
114
+ - Verify logical focus order — matches reading/visual order
115
+ - Verify no keyboard traps — you can always Tab out of any component
116
+ - Verify all interactive elements are reachable with keyboard
117
+ - Pay particular attention to modals, menus, and custom widgets
118
+ - \`log_finding\` per issue (WCAG 2.1.1, 2.1.2, 2.4.3)
119
+
120
+ ### 2.9 Dynamic Content & Live Regions
121
+ - Trigger dynamic updates (form submissions, AJAX, notifications)
122
+ - \`nvda_last_spoken_phrase\` — verify NVDA announces changes automatically
123
+ - Check that status messages are announced without moving focus (WCAG 4.1.3)
124
+ - \`log_finding\` per issue
125
+
126
+ ## Handling Accessibility Failures
127
+ - If NVDA cannot navigate to an element at all → that IS the finding — log it as a violation
128
+ - If toggling browse/focus mode is needed → use \`nvda_perform\` → toggleBetweenBrowseAndFocusMode
129
+ - If the Elements List dialog is useful → \`nvda_perform\` → browseModeElementsList
130
+
131
+ ## Phase 3 — Report & Shutdown
132
+ 1. \`end_audit\`
133
+ 2. \`generate_report\` with format "markdown"
134
+ 3. Present findings — critical violations first, then warnings, then passes
135
+ 4. \`nvda_stop\` — stop NVDA`,
136
+ },
137
+ },
138
+ ];
139
+ },
140
+ };
141
+ const headingStructureAudit = {
142
+ prompt: {
143
+ name: 'heading_structure_audit',
144
+ description: 'Audit heading structure using NVDA. Checks h1 uniqueness, hierarchy, and descriptive content.',
145
+ arguments: [
146
+ {
147
+ name: 'url',
148
+ description: 'URL of the page to audit',
149
+ required: true,
150
+ },
151
+ ],
152
+ },
153
+ getMessage: (args) => {
154
+ const url = args.url ?? 'UNKNOWN_URL';
155
+ return [
156
+ {
157
+ role: 'user',
158
+ content: {
159
+ type: 'text',
160
+ text: `Audit the heading structure of ${url} using NVDA.
161
+
162
+ ## Setup
163
+ 1. \`check_setup\` — verify Windows environment
164
+ 2. \`nvda_start\` — start NVDA
165
+ 3. Navigate to ${url} in browser
166
+ 4. \`start_audit\` with url "${url}"
167
+
168
+ ## Overview with Elements List
169
+ 5. \`nvda_perform\` → browseModeElementsList
170
+ Opens the NVDA Elements List dialog (NVDA+F7). Select "Headings" from the dropdown.
171
+ This shows all headings with their levels — ideal for a complete structural overview.
172
+ Press Escape to close the dialog after reviewing.
173
+
174
+ ## Heading Traversal
175
+ 6. \`nvda_perform\` → moveToNextHeading — repeat until exhausted
176
+ For each heading: \`nvda_last_spoken_phrase\` and \`nvda_item_text\`
177
+ Record: heading level (h1–h6) and text content.
178
+
179
+ 7. Verify level 1 specifically:
180
+ \`nvda_perform\` → moveToNextHeadingLevel1 (should find exactly ONE h1)
181
+ \`nvda_perform\` → moveToNextHeadingLevel1 again (should find no more)
182
+
183
+ ## Verification
184
+ 8. Check:
185
+ - Exactly one h1 on the page (WCAG 2.4.6)
186
+ - No skipped heading levels (h1 → h3 without h2 is a violation)
187
+ - All headings are descriptive (not empty, not generic like "Section")
188
+ - Heading hierarchy reflects the logical document structure
189
+
190
+ 9. \`log_finding\` for each issue found
191
+
192
+ ## Wrap-up
193
+ 10. \`end_audit\`
194
+ 11. \`nvda_stop\` — stop NVDA
195
+ 12. \`generate_report\` with format "markdown"`,
196
+ },
197
+ },
198
+ ];
199
+ },
200
+ };
201
+ const formAccessibilityAudit = {
202
+ prompt: {
203
+ name: 'form_accessibility_audit',
204
+ description: 'Audit form accessibility with NVDA: labels, error messages, required fields, and keyboard navigation.',
205
+ arguments: [
206
+ {
207
+ name: 'url',
208
+ description: 'URL of the page with forms to audit',
209
+ required: true,
210
+ },
211
+ ],
212
+ },
213
+ getMessage: (args) => {
214
+ const url = args.url ?? 'UNKNOWN_URL';
215
+ return [
216
+ {
217
+ role: 'user',
218
+ content: {
219
+ type: 'text',
220
+ text: `Audit form accessibility on ${url} using NVDA.
221
+
222
+ ## Setup
223
+ 1. \`check_setup\` — verify Windows environment
224
+ 2. \`nvda_start\` — start NVDA
225
+ 3. Navigate to ${url} in browser
226
+ 4. \`start_audit\` with url "${url}"
227
+
228
+ ## Form Controls
229
+ Navigate by control type for thorough coverage:
230
+
231
+ a. **Edit fields & text inputs:**
232
+ \`nvda_perform\` → moveToNextEditField (repeat)
233
+ \`nvda_last_spoken_phrase\` — check label text, role, required state
234
+
235
+ b. **General form fields (includes selects, textareas):**
236
+ \`nvda_perform\` → moveToNextFormField (repeat)
237
+ \`nvda_last_spoken_phrase\` — check complete announcement
238
+
239
+ c. **Buttons:**
240
+ \`nvda_perform\` → moveToNextButton (repeat)
241
+ \`nvda_last_spoken_phrase\` — check button label
242
+
243
+ d. **Checkboxes:**
244
+ \`nvda_perform\` → moveToNextCheckbox (repeat)
245
+ \`nvda_last_spoken_phrase\` — check label and checked/unchecked state
246
+
247
+ e. **Radio buttons:**
248
+ \`nvda_perform\` → moveToNextRadioButton (repeat)
249
+ \`nvda_last_spoken_phrase\` — check label, group name, selected state
250
+
251
+ f. **Combo boxes / dropdowns:**
252
+ \`nvda_perform\` → moveToNextComboBox (repeat)
253
+ \`nvda_last_spoken_phrase\` — check label and current value
254
+
255
+ ## Interaction Testing (for each control that accepts input)
256
+ 5. Tab to the control (\`nvda_press\` 'Tab')
257
+ 6. \`nvda_perform\` → toggleBetweenBrowseAndFocusMode — enter focus mode
258
+ 7. Interact: type invalid data, select options, check/uncheck
259
+ 8. Submit or trigger validation
260
+ 9. \`nvda_last_spoken_phrase\` — check if error messages are announced
261
+ 10. Press Escape (\`nvda_press\` 'Escape') to return to browse mode
262
+
263
+ ## Verification Checklist
264
+ - All inputs have associated labels (WCAG 1.3.1, 3.3.2)
265
+ - Required fields are announced as required (WCAG 3.3.2)
266
+ - Error messages are announced — either live region or associated with field (WCAG 3.3.1)
267
+ - Form is completable entirely by keyboard (WCAG 2.1.1)
268
+ - Submit button is reachable by Tab (WCAG 2.1.1)
269
+
270
+ 11. \`log_finding\` for each issue
271
+
272
+ ## Wrap-up
273
+ 12. \`end_audit\`
274
+ 13. \`nvda_stop\` — stop NVDA
275
+ 14. \`generate_report\` with format "markdown"`,
276
+ },
277
+ },
278
+ ];
279
+ },
280
+ };
281
+ const navigationAudit = {
282
+ prompt: {
283
+ name: 'navigation_audit',
284
+ description: 'Audit keyboard navigation and landmark regions with NVDA. Checks tab order, landmarks, skip links, and focus management.',
285
+ arguments: [
286
+ {
287
+ name: 'url',
288
+ description: 'URL of the page to audit',
289
+ required: true,
290
+ },
291
+ ],
292
+ },
293
+ getMessage: (args) => {
294
+ const url = args.url ?? 'UNKNOWN_URL';
295
+ return [
296
+ {
297
+ role: 'user',
298
+ content: {
299
+ type: 'text',
300
+ text: `Audit keyboard navigation and landmark regions on ${url} using NVDA.
301
+
302
+ ## Setup
303
+ 1. \`check_setup\` — verify Windows environment
304
+ 2. \`nvda_start\` — start NVDA
305
+ 3. Navigate to ${url} in browser
306
+ 4. \`start_audit\` with url "${url}"
307
+
308
+ ## Skip Links
309
+ 5. Press Tab once from the top of the page (\`nvda_press\` 'Tab')
310
+ - Check if a "Skip to main content" or "Skip navigation" link appears
311
+ - \`nvda_last_spoken_phrase\` — verify the link text is descriptive
312
+ - Activate it (\`nvda_act\`) and verify focus moves to the main content area
313
+
314
+ ## Landmark Regions
315
+ 6. \`nvda_perform\` → moveToNextLandmark (repeat until exhausted)
316
+ For each landmark: \`nvda_last_spoken_phrase\` — note the type announced
317
+ Expected landmark types: banner, navigation, main, contentinfo, complementary, search, form, region
318
+ - \`main\` landmark is required (WCAG 1.3.1)
319
+ - \`navigation\` (nav) is strongly recommended
320
+ - \`contentinfo\` (footer) is recommended
321
+ 7. \`log_finding\` for missing required or expected landmarks
322
+
323
+ ## Tab Order
324
+ 8. From page start, press Tab repeatedly (\`nvda_press\` 'Tab')
325
+ - \`nvda_last_spoken_phrase\` after each Tab to track focus position
326
+ - Verify order follows logical/visual reading order (WCAG 2.4.3)
327
+ - Verify all interactive elements are reachable (links, buttons, inputs, selects)
328
+ - Verify there are no keyboard traps — you can always Tab out (WCAG 2.1.2)
329
+ - Watch for invisible or off-screen focused elements
330
+
331
+ ## Focus Management
332
+ 9. If the page has modals/dialogs:
333
+ - Trigger the modal (click/Enter)
334
+ - Verify focus moves into the modal automatically
335
+ - Verify focus is trapped within modal while open (Tab should cycle within modal)
336
+ - Close modal and verify focus returns to trigger element
337
+ 10. If the page has expandable sections (accordions, disclosures):
338
+ - Toggle open — verify appropriate focus or announcement
339
+
340
+ ## Findings
341
+ 11. \`log_finding\` per issue with applicable WCAG criteria:
342
+ - 2.4.1 Bypass Blocks (skip link)
343
+ - 2.4.3 Focus Order
344
+ - 2.1.1 Keyboard
345
+ - 2.1.2 No Keyboard Trap
346
+ - 1.3.1 Info and Relationships (landmarks)
347
+
348
+ ## Wrap-up
349
+ 12. \`end_audit\`
350
+ 13. \`nvda_stop\` — stop NVDA
351
+ 14. \`generate_report\` with format "markdown"`,
352
+ },
353
+ },
354
+ ];
355
+ },
356
+ };
357
+ const imageAudit = {
358
+ prompt: {
359
+ name: 'image_accessibility_audit',
360
+ description: 'Audit image accessibility with NVDA. Checks alt text, decorative image handling, and complex image descriptions.',
361
+ arguments: [
362
+ {
363
+ name: 'url',
364
+ description: 'URL of the page to audit',
365
+ required: true,
366
+ },
367
+ ],
368
+ },
369
+ getMessage: (args) => {
370
+ const url = args.url ?? 'UNKNOWN_URL';
371
+ return [
372
+ {
373
+ role: 'user',
374
+ content: {
375
+ type: 'text',
376
+ text: `Audit image accessibility on ${url} using NVDA.
377
+
378
+ ## Setup
379
+ 1. \`check_setup\` — verify Windows environment
380
+ 2. \`nvda_start\` — start NVDA
381
+ 3. Navigate to ${url} in browser
382
+ 4. \`start_audit\` with url "${url}"
383
+
384
+ ## Image Traversal
385
+ 5. \`nvda_perform\` → moveToNextGraphic (repeat until exhausted)
386
+ For each graphic:
387
+ a. \`nvda_last_spoken_phrase\` — capture what NVDA announces
388
+ b. \`nvda_item_text\` — get item text
389
+
390
+ ## Per-Image Analysis
391
+ Classify each image based on NVDA's announcement:
392
+ - **Has descriptive alt text** → "graphic, [description]" → PASS
393
+ - **Empty alt (decorative)** → NVDA skips entirely or announces just role → PASS (verify intentional)
394
+ - **Missing alt** → NVDA announces filename or "graphic" with no description → VIOLATION
395
+ - **Alt text is filename** → e.g. "image001.jpg" → VIOLATION
396
+ - **Alt text is redundant** → same as surrounding text → WARNING
397
+ - **Alt text says "image of"/"picture of"** → redundant prefix → WARNING
398
+
399
+ For complex images (charts, graphs, infographics):
400
+ \`nvda_perform\` → openLongDescription — check if long description exists and is comprehensive
401
+ Verify the image has an aria-describedby, figure/figcaption, or linked long description
402
+
403
+ ## Verification Checklist
404
+ - All informative images have meaningful alt text (WCAG 1.1.1)
405
+ - Decorative images are hidden from the accessibility tree (alt="" or role="presentation")
406
+ - Complex images have complete long descriptions
407
+
408
+ 6. \`log_finding\` per image with issue
409
+
410
+ ## Wrap-up
411
+ 7. \`end_audit\`
412
+ 8. \`nvda_stop\` — stop NVDA
413
+ 9. \`generate_report\` with format "markdown"`,
414
+ },
415
+ },
416
+ ];
417
+ },
418
+ };
419
+ const PROMPT_DEFINITIONS = new Map([
420
+ ['full_accessibility_audit', fullAccessibilityAudit],
421
+ ['heading_structure_audit', headingStructureAudit],
422
+ ['form_accessibility_audit', formAccessibilityAudit],
423
+ ['navigation_audit', navigationAudit],
424
+ ['image_accessibility_audit', imageAudit],
425
+ ]);
426
+ export const listNvdaPrompts = () => listPrompts(PROMPT_DEFINITIONS);
427
+ export const getNvdaPromptMessages = (name, args) => getPromptMessages(name, args, PROMPT_DEFINITIONS);
428
+ //# sourceMappingURL=audit-prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-prompts.js","sourceRoot":"","sources":["../../src/prompts/audit-prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE3E,iFAAiF;AACjF,4CAA4C;AAC5C,sEAAsE;AACtE,yFAAyF;AACzF,mDAAmD;AAEnD,MAAM,sBAAsB,GAAqB;IAC/C,MAAM,EAAE;QACN,IAAI,EAAE,0BAA0B;QAChC,WAAW,EACT,wJAAwJ;QAC1J,SAAS,EAAE;YACT;gBACE,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,0BAA0B;gBACvC,QAAQ,EAAE,IAAI;aACf;YACD;gBACE,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,kCAAkC;gBAC/C,QAAQ,EAAE,KAAK;aAChB;YACD;gBACE,IAAI,EAAE,WAAW;gBACjB,WAAW,EAAE,qDAAqD;gBAClE,QAAQ,EAAE,KAAK;aAChB;SACF;KACF;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;QACzC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;QAEzC,OAAO;YACL;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,uDAAuD,GAAG;;;;;iBAKzD,GAAG,OAAO,OAAO;+BACH,GAAG,sCAAsC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA2FpD;iBACpB;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,MAAM,qBAAqB,GAAqB;IAC9C,MAAM,EAAE;QACN,IAAI,EAAE,yBAAyB;QAC/B,WAAW,EACT,+FAA+F;QACjG,SAAS,EAAE;YACT;gBACE,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,0BAA0B;gBACvC,QAAQ,EAAE,IAAI;aACf;SACF;KACF;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO;YACL;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,kCAAkC,GAAG;;;;;iBAKpC,GAAG;+BACW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CA6Ba;iBACtC;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,MAAM,sBAAsB,GAAqB;IAC/C,MAAM,EAAE;QACN,IAAI,EAAE,0BAA0B;QAChC,WAAW,EACT,uGAAuG;QACzG,SAAS,EAAE;YACT;gBACE,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,qCAAqC;gBAClD,QAAQ,EAAE,IAAI;aACf;SACF;KACF;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO;YACL;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,+BAA+B,GAAG;;;;;iBAKjC,GAAG;+BACW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CAiDa;iBACtC;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,MAAM,eAAe,GAAqB;IACxC,MAAM,EAAE;QACN,IAAI,EAAE,kBAAkB;QACxB,WAAW,EACT,0HAA0H;QAC5H,SAAS,EAAE;YACT;gBACE,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,0BAA0B;gBACvC,QAAQ,EAAE,IAAI;aACf;SACF;KACF;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO;YACL;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,qDAAqD,GAAG;;;;;iBAKvD,GAAG;+BACW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CA6Ca;iBACtC;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,MAAM,UAAU,GAAqB;IACnC,MAAM,EAAE;QACN,IAAI,EAAE,2BAA2B;QACjC,WAAW,EACT,kHAAkH;QACpH,SAAS,EAAE;YACT;gBACE,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,0BAA0B;gBACvC,QAAQ,EAAE,IAAI;aACf;SACF;KACF;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO;YACL;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,gCAAgC,GAAG;;;;;iBAKlC,GAAG;+BACW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8CA+BY;iBACrC;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,MAAM,kBAAkB,GAA0C,IAAI,GAAG,CAAC;IACxE,CAAC,0BAA0B,EAAE,sBAAsB,CAAC;IACpD,CAAC,yBAAyB,EAAE,qBAAqB,CAAC;IAClD,CAAC,0BAA0B,EAAE,sBAAsB,CAAC;IACpD,CAAC,kBAAkB,EAAE,eAAe,CAAC;IACrC,CAAC,2BAA2B,EAAE,UAAU,CAAC;CAC1C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAmC,EAAE,CAClE,WAAW,CAAC,kBAAkB,CAAC,CAAC;AAElC,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,IAAY,EACZ,IAAwC,EACF,EAAE,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { ClickOptions, ScreenReaderCommandResult, ScreenReaderPort } from '@weaaare/mcp-auditor-core';
2
+ export declare class NvdaAdapter implements ScreenReaderPort {
3
+ start(): Promise<ScreenReaderCommandResult>;
4
+ stop(): Promise<ScreenReaderCommandResult>;
5
+ next(): Promise<ScreenReaderCommandResult>;
6
+ previous(): Promise<ScreenReaderCommandResult>;
7
+ act(): Promise<ScreenReaderCommandResult>;
8
+ interact(): Promise<ScreenReaderCommandResult>;
9
+ stopInteracting(): Promise<ScreenReaderCommandResult>;
10
+ press(key: string): Promise<ScreenReaderCommandResult>;
11
+ type(text: string): Promise<ScreenReaderCommandResult>;
12
+ perform(command: string): Promise<ScreenReaderCommandResult>;
13
+ performCommander(command: string): Promise<ScreenReaderCommandResult>;
14
+ getCommanderCommands(): string[];
15
+ itemText(): Promise<string>;
16
+ lastSpokenPhrase(): Promise<string>;
17
+ spokenPhraseLog(): Promise<string[]>;
18
+ itemTextLog(): Promise<string[]>;
19
+ clearSpokenPhraseLog(): Promise<void>;
20
+ clearItemTextLog(): Promise<void>;
21
+ click(options?: ClickOptions): Promise<ScreenReaderCommandResult>;
22
+ detect(): Promise<boolean>;
23
+ isDefault(): Promise<boolean>;
24
+ }
25
+ //# sourceMappingURL=nvda.adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nvda.adapter.d.ts","sourceRoot":"","sources":["../../../src/screen-readers/nvda/nvda.adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,yBAAyB,EACzB,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AAuHnC,qBAAa,WAAY,YAAW,gBAAgB;IAC5C,KAAK,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAK3C,IAAI,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAK1C,IAAI,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAO1C,QAAQ,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAO9C,GAAG,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAMzC,QAAQ,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAM9C,eAAe,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAMrD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAMtD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAKtD,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAgB5D,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAM3E,oBAAoB,IAAI,MAAM,EAAE;IAI1B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAI3B,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIhC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAejE,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
@@ -0,0 +1,208 @@
1
+ import { nvda } from '@guidepup/guidepup';
2
+ const buildCommandMap = () => {
3
+ const kc = nvda.keyboardCommands;
4
+ return {
5
+ // ── Basic navigation ──────────────────────────────────────────────────────
6
+ moveToNext: kc.moveToNext,
7
+ moveToPrevious: kc.moveToPrevious,
8
+ readNextFocusableItem: kc.readNextFocusableItem,
9
+ performDefaultActionForItem: kc.performDefaultActionForItem,
10
+ activate: kc.activate,
11
+ // ── Browse mode — Headings ────────────────────────────────────────────────
12
+ moveToNextHeading: kc.moveToNextHeading,
13
+ moveToPreviousHeading: kc.moveToPreviousHeading,
14
+ moveToNextHeadingLevel1: kc.moveToNextHeadingLevel1,
15
+ moveToPreviousHeadingLevel1: kc.moveToPreviousHeadingLevel1,
16
+ moveToNextHeadingLevel2: kc.moveToNextHeadingLevel2,
17
+ moveToPreviousHeadingLevel2: kc.moveToPreviousHeadingLevel2,
18
+ moveToNextHeadingLevel3: kc.moveToNextHeadingLevel3,
19
+ moveToPreviousHeadingLevel3: kc.moveToPreviousHeadingLevel3,
20
+ moveToNextHeadingLevel4: kc.moveToNextHeadingLevel4,
21
+ moveToPreviousHeadingLevel4: kc.moveToPreviousHeadingLevel4,
22
+ moveToNextHeadingLevel5: kc.moveToNextHeadingLevel5,
23
+ moveToPreviousHeadingLevel5: kc.moveToPreviousHeadingLevel5,
24
+ moveToNextHeadingLevel6: kc.moveToNextHeadingLevel6,
25
+ moveToPreviousHeadingLevel6: kc.moveToPreviousHeadingLevel6,
26
+ // ── Browse mode — Links ───────────────────────────────────────────────────
27
+ moveToNextLink: kc.moveToNextLink,
28
+ moveToPreviousLink: kc.moveToPreviousLink,
29
+ moveToNextUnvisitedLink: kc.moveToNextUnvisitedLink,
30
+ moveToPreviousUnvisitedLink: kc.moveToPreviousUnvisitedLink,
31
+ moveToNextVisitedLink: kc.moveToNextVisitedLink,
32
+ moveToPreviousVisitedLink: kc.moveToPreviousVisitedLink,
33
+ // ── Browse mode — Forms ───────────────────────────────────────────────────
34
+ moveToNextFormField: kc.moveToNextFormField,
35
+ moveToPreviousFormField: kc.moveToPreviousFormField,
36
+ moveToNextButton: kc.moveToNextButton,
37
+ moveToPreviousButton: kc.moveToPreviousButton,
38
+ moveToNextCheckbox: kc.moveToNextCheckbox,
39
+ moveToPreviousCheckbox: kc.moveToPreviousCheckbox,
40
+ moveToNextComboBox: kc.moveToNextComboBox,
41
+ moveToPreviousComboBox: kc.moveToPreviousComboBox,
42
+ moveToNextRadioButton: kc.moveToNextRadioButton,
43
+ moveToPreviousRadioButton: kc.moveToPreviousRadioButton,
44
+ moveToNextEditField: kc.moveToNextEditField,
45
+ moveToPreviousEditField: kc.moveToPreviousEditField,
46
+ // ── Browse mode — Landmarks ───────────────────────────────────────────────
47
+ moveToNextLandmark: kc.moveToNextLandmark,
48
+ moveToPreviousLandmark: kc.moveToPreviousLandmark,
49
+ // ── Browse mode — Tables ──────────────────────────────────────────────────
50
+ moveToNextTable: kc.moveToNextTable,
51
+ moveToPreviousTable: kc.moveToPreviousTable,
52
+ moveToNextColumn: kc.moveToNextColumn,
53
+ moveToPreviousColumn: kc.moveToPreviousColumn,
54
+ moveToNextRow: kc.moveToNextRow,
55
+ moveToPreviousRow: kc.moveToPreviousRow,
56
+ // ── Browse mode — Lists ───────────────────────────────────────────────────
57
+ moveToNextList: kc.moveToNextList,
58
+ moveToPreviousList: kc.moveToPreviousList,
59
+ moveToNextListItem: kc.moveToNextListItem,
60
+ moveToPreviousListItem: kc.moveToPreviousListItem,
61
+ // ── Browse mode — Other elements ──────────────────────────────────────────
62
+ moveToNextGraphic: kc.moveToNextGraphic,
63
+ moveToPreviousGraphic: kc.moveToPreviousGraphic,
64
+ moveToNextBlockQuote: kc.moveToNextBlockQuote,
65
+ moveToPreviousBlockQuote: kc.moveToPreviousBlockQuote,
66
+ moveToNextNonLinkedText: kc.moveToNextNonLinkedText,
67
+ moveToPreviousNonLinkedText: kc.moveToPreviousNonLinkedText,
68
+ moveToNextSeparator: kc.moveToNextSeparator,
69
+ moveToPreviousSeparator: kc.moveToPreviousSeparator,
70
+ moveToNextFrame: kc.moveToNextFrame,
71
+ moveToPreviousFrame: kc.moveToPreviousFrame,
72
+ moveToNextAnnotation: kc.moveToNextAnnotation,
73
+ moveToPreviousAnnotation: kc.moveToPreviousAnnotation,
74
+ moveToNextSpellingError: kc.moveToNextSpellingError,
75
+ moveToPreviousSpellingError: kc.moveToPreviousSpellingError,
76
+ moveToNextEmbeddedObject: kc.moveToNextEmbeddedObject,
77
+ moveToPreviousEmbeddedObject: kc.moveToPreviousEmbeddedObject,
78
+ moveToStartOfContainer: kc.moveToStartOfContainer,
79
+ movePastEndOfContainer: kc.movePastEndOfContainer,
80
+ // ── Browse mode — Mode control ────────────────────────────────────────────
81
+ toggleBetweenBrowseAndFocusMode: kc.toggleBetweenBrowseAndFocusMode,
82
+ exitFocusMode: kc.exitFocusMode,
83
+ browseModeElementsList: kc.browseModeElementsList,
84
+ toggleSingleLetterNavigation: kc.toggleSingleLetterNavigation,
85
+ refreshBrowseDocument: kc.refreshBrowseDocument,
86
+ // ── Reading and reporting ─────────────────────────────────────────────────
87
+ sayAll: kc.sayAll,
88
+ readLine: kc.readLine,
89
+ readCurrentSelection: kc.readCurrentSelection,
90
+ reportTextFormatting: kc.reportTextFormatting,
91
+ reportTitle: kc.reportTitle,
92
+ reportCurrentFocus: kc.reportCurrentFocus,
93
+ readActiveWindow: kc.readActiveWindow,
94
+ reportStatusBar: kc.reportStatusBar,
95
+ reportDateTime: kc.reportDateTime,
96
+ reportClipboardText: kc.reportClipboardText,
97
+ // ── Find ──────────────────────────────────────────────────────────────────
98
+ find: kc.find,
99
+ findNext: kc.findNext,
100
+ findPrevious: kc.findPrevious,
101
+ openLongDescription: kc.openLongDescription,
102
+ // ── Speech control ────────────────────────────────────────────────────────
103
+ stopSpeech: kc.stopSpeech,
104
+ pauseSpeech: kc.pauseSpeech,
105
+ };
106
+ };
107
+ export class NvdaAdapter {
108
+ async start() {
109
+ await nvda.start();
110
+ return { action: 'NVDA started successfully' };
111
+ }
112
+ async stop() {
113
+ await nvda.stop();
114
+ return { action: 'NVDA stopped successfully' };
115
+ }
116
+ async next() {
117
+ await nvda.next();
118
+ const itemText = await nvda.itemText();
119
+ const spokenPhrase = await nvda.lastSpokenPhrase();
120
+ return { action: 'moved to next item', itemText, spokenPhrase };
121
+ }
122
+ async previous() {
123
+ await nvda.previous();
124
+ const itemText = await nvda.itemText();
125
+ const spokenPhrase = await nvda.lastSpokenPhrase();
126
+ return { action: 'moved to previous item', itemText, spokenPhrase };
127
+ }
128
+ async act() {
129
+ await nvda.act();
130
+ const spokenPhrase = await nvda.lastSpokenPhrase();
131
+ return { action: 'performed default action', spokenPhrase };
132
+ }
133
+ async interact() {
134
+ await nvda.interact();
135
+ const spokenPhrase = await nvda.lastSpokenPhrase();
136
+ return { action: 'interact (no-op on NVDA)', spokenPhrase };
137
+ }
138
+ async stopInteracting() {
139
+ await nvda.stopInteracting();
140
+ const spokenPhrase = await nvda.lastSpokenPhrase();
141
+ return { action: 'stopInteracting (no-op on NVDA)', spokenPhrase };
142
+ }
143
+ async press(key) {
144
+ await nvda.press(key);
145
+ const spokenPhrase = await nvda.lastSpokenPhrase();
146
+ return { action: `pressed key: ${key}`, spokenPhrase };
147
+ }
148
+ async type(text) {
149
+ await nvda.type(text);
150
+ return { action: `typed: ${text}` };
151
+ }
152
+ async perform(command) {
153
+ const commandMap = buildCommandMap();
154
+ const keyboardCommand = commandMap[command];
155
+ if (!keyboardCommand) {
156
+ throw new Error(`Unknown command: ${command}. Available: ${Object.keys(commandMap).join(', ')}`);
157
+ }
158
+ await nvda.perform(keyboardCommand);
159
+ const itemText = await nvda.itemText();
160
+ const spokenPhrase = await nvda.lastSpokenPhrase();
161
+ return { action: `performed command: ${command}`, itemText, spokenPhrase };
162
+ }
163
+ async performCommander(command) {
164
+ // NVDA has no separate "commander" channel — keyboard commands are the only API.
165
+ // This method exists for ScreenReaderPort compatibility.
166
+ return this.perform(command);
167
+ }
168
+ getCommanderCommands() {
169
+ return Object.keys(buildCommandMap());
170
+ }
171
+ async itemText() {
172
+ return nvda.itemText();
173
+ }
174
+ async lastSpokenPhrase() {
175
+ return nvda.lastSpokenPhrase();
176
+ }
177
+ async spokenPhraseLog() {
178
+ return nvda.spokenPhraseLog();
179
+ }
180
+ async itemTextLog() {
181
+ return nvda.itemTextLog();
182
+ }
183
+ async clearSpokenPhraseLog() {
184
+ await nvda.clearSpokenPhraseLog();
185
+ }
186
+ async clearItemTextLog() {
187
+ await nvda.clearItemTextLog();
188
+ }
189
+ async click(options) {
190
+ const button = options?.button ?? 'left';
191
+ const clickCount = options?.clickCount ?? 1;
192
+ if (![1, 2, 3].includes(clickCount)) {
193
+ throw new Error('clickCount must be 1, 2, or 3.');
194
+ }
195
+ await nvda.click({ button, clickCount: clickCount });
196
+ return {
197
+ action: 'clicked mouse',
198
+ spokenPhrase: `${button} click x${clickCount}`,
199
+ };
200
+ }
201
+ async detect() {
202
+ return nvda.detect();
203
+ }
204
+ async isDefault() {
205
+ return nvda.default();
206
+ }
207
+ }
208
+ //# sourceMappingURL=nvda.adapter.js.map