wcag-a11y 0.3.6 → 0.4.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/README.md +74 -26
- package/dist/ai/anthropic.js +59 -0
- package/dist/ai/azure-openai.js +17 -0
- package/dist/ai/base.js +39 -0
- package/dist/ai/cohere.js +58 -0
- package/dist/ai/deepseek.js +6 -0
- package/dist/ai/gemini.js +8 -39
- package/dist/ai/groq.js +6 -0
- package/dist/ai/index.js +78 -14
- package/dist/ai/mistral.js +6 -0
- package/dist/ai/ollama.js +3 -23
- package/dist/ai/openai-compat.js +65 -0
- package/dist/ai/openai.js +3 -84
- package/dist/ai/perplexity.js +6 -0
- package/dist/ai/together.js +6 -0
- package/dist/ai/xai.js +6 -0
- package/dist/cli.js +4 -4
- package/dist/config.js +51 -0
- package/package.json +20 -2
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Generating AI fixes for 7 violations...
|
|
|
56
56
|
|
|
57
57
|
The **prompt** at the end of each fix is what you copy into Cursor, Copilot, or Claude. It includes the affected selectors, the WCAG rule, and exactly what needs to change — no rewriting needed.
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
The full output is saved to `a11y-report.md` by default. Pass `--no-report` to skip.
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
@@ -69,17 +69,22 @@ npm install -g wcag-a11y
|
|
|
69
69
|
## Setup
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
wcag-a11y init
|
|
73
|
-
wcag-a11y init --provider openai
|
|
74
|
-
wcag-a11y init --provider
|
|
72
|
+
wcag-a11y init # Gemini (free, default)
|
|
73
|
+
wcag-a11y init --provider openai # OpenAI
|
|
74
|
+
wcag-a11y init --provider anthropic # Anthropic Claude
|
|
75
|
+
wcag-a11y init --provider mistral # Mistral
|
|
76
|
+
wcag-a11y init --provider groq # Groq (fast inference)
|
|
77
|
+
wcag-a11y init --provider cohere # Cohere
|
|
78
|
+
wcag-a11y init --provider xai # xAI Grok
|
|
79
|
+
wcag-a11y init --provider deepseek # DeepSeek
|
|
80
|
+
wcag-a11y init --provider together # Together AI (open-source models)
|
|
81
|
+
wcag-a11y init --provider perplexity # Perplexity
|
|
82
|
+
wcag-a11y init --provider azure-openai # Azure OpenAI
|
|
83
|
+
wcag-a11y init --provider ollama # Local — no API key needed
|
|
75
84
|
```
|
|
76
85
|
|
|
77
86
|
Each command creates an `a11y.config.json` pre-wired for that provider. Fill in your API key, then scan.
|
|
78
87
|
|
|
79
|
-
**Get a free Gemini key:** https://aistudio.google.com
|
|
80
|
-
**Get an OpenAI key:** https://platform.openai.com/api-keys
|
|
81
|
-
**Ollama (local):** install from https://ollama.com, then run `ollama serve`
|
|
82
|
-
|
|
83
88
|
---
|
|
84
89
|
|
|
85
90
|
## Commands
|
|
@@ -91,13 +96,13 @@ Scan a built-in page with 10 intentional violations. No dev server or config req
|
|
|
91
96
|
```bash
|
|
92
97
|
wcag-a11y demo # violations + AI fixes (default, requires config)
|
|
93
98
|
wcag-a11y demo --no-ai # violations only, no AI — faster
|
|
94
|
-
wcag-a11y demo --report
|
|
99
|
+
wcag-a11y demo --no-report # skip saving a11y-report.md
|
|
95
100
|
```
|
|
96
101
|
|
|
97
102
|
| Flag | Description |
|
|
98
103
|
|---|---|
|
|
99
104
|
| `--no-ai` | Skip AI fix generation — prints violations only, no prompts |
|
|
100
|
-
|
|
|
105
|
+
| `--no-report` | Skip saving report to `a11y-report.md` (report is saved by default) |
|
|
101
106
|
|
|
102
107
|
---
|
|
103
108
|
|
|
@@ -113,7 +118,7 @@ wcag-a11y init --provider ollama # Ollama (local)
|
|
|
113
118
|
|
|
114
119
|
| Flag | Description |
|
|
115
120
|
|---|---|
|
|
116
|
-
| `--provider <name>` | Which provider to configure: `gemini` (default), `openai`,
|
|
121
|
+
| `--provider <name>` | Which provider to configure. Valid values: `gemini` (default), `openai`, `anthropic`, `mistral`, `groq`, `cohere`, `xai`, `deepseek`, `together`, `perplexity`, `azure-openai`, `ollama`. Determines which fields are written to the config file. |
|
|
117
122
|
|
|
118
123
|
---
|
|
119
124
|
|
|
@@ -124,8 +129,9 @@ Scan a running dev server for accessibility violations.
|
|
|
124
129
|
```bash
|
|
125
130
|
wcag-a11y scan -u http://localhost:3000
|
|
126
131
|
wcag-a11y scan -u http://localhost:3000 --pages / /about /contact
|
|
127
|
-
wcag-a11y scan -u http://localhost:3000 --crawl
|
|
132
|
+
wcag-a11y scan -u http://localhost:3000 --crawl
|
|
128
133
|
wcag-a11y scan -u http://localhost:3000 --no-ai --ci
|
|
134
|
+
wcag-a11y scan -u http://localhost:3000 --terminal --fast-mode
|
|
129
135
|
```
|
|
130
136
|
|
|
131
137
|
| Flag | Default | Description |
|
|
@@ -133,12 +139,14 @@ wcag-a11y scan -u http://localhost:3000 --no-ai --ci
|
|
|
133
139
|
| `-u, --url <url>` | required | Base URL of your running dev server |
|
|
134
140
|
| `-p, --pages <pages...>` | `/` | One or more paths to scan. Separate with spaces: `--pages / /about /contact` |
|
|
135
141
|
| `-c, --crawl` | off | Follow same-origin links and scan all reachable pages automatically |
|
|
136
|
-
|
|
|
142
|
+
| `--no-report` | on | Skip saving scan output to `a11y-report.md` (report is saved by default) |
|
|
137
143
|
| `--no-ai` | on | Skip AI fix generation — scan runs faster and prints violations only |
|
|
138
|
-
| `--no-explain` |
|
|
144
|
+
| `--no-explain` | on | Print only the ready-to-paste prompt for each fix, without the AI explanation |
|
|
145
|
+
| `--terminal` | off | Print violations summary and AI fix prompts to terminal |
|
|
146
|
+
| `--fast-mode` | off | Output only AI fix prompts — no summaries, explanations, or progress messages |
|
|
139
147
|
| `--group <strategy>` | `rule` | `rule` (default) groups all violations of the same type into one fix prompt. `none` produces a separate prompt per element. Use `none` when violations of the same rule need different fixes |
|
|
140
148
|
| `--ci` | off | Exit with code `1` if any violations are found. Use this to fail a CI pipeline |
|
|
141
|
-
| `--provider <name>` | from config | Override the AI provider for this run
|
|
149
|
+
| `--provider <name>` | from config | Override the AI provider for this run. See [AI Providers](#ai-providers) for valid names. Does not modify the config file |
|
|
142
150
|
|
|
143
151
|
---
|
|
144
152
|
|
|
@@ -195,33 +203,46 @@ src/components/Navbar.jsx — 2 violation(s)
|
|
|
195
203
|
| `-c, --crawl` | off | Auto-discover pages by following same-origin links |
|
|
196
204
|
| `--from-report [path]` | `a11y-report.md` | Load violations from an existing report instead of scanning. Useful when you already ran `scan --report` and just want to apply fixes |
|
|
197
205
|
| `--apply` | off | Write patched files to disk (dry-run without this flag) |
|
|
198
|
-
| `--provider <name>` | from config | Override AI provider for this run
|
|
206
|
+
| `--provider <name>` | from config | Override AI provider for this run. See [AI Providers](#ai-providers) for valid names |
|
|
199
207
|
|
|
200
208
|
> **Tip:** Always run without `--apply` first to review the diff. The dry-run is safe — nothing is written to disk.
|
|
201
209
|
|
|
202
210
|
**Common workflow:** run `scan --report` to generate a report for review, then run `fix --from-report --apply` to patch the files — no second browser crawl needed.
|
|
203
211
|
|
|
204
212
|
```bash
|
|
205
|
-
wcag-a11y scan -u http://localhost:3000
|
|
206
|
-
wcag-a11y fix --from-report --apply
|
|
213
|
+
wcag-a11y scan -u http://localhost:3000 # generates a11y-report.md automatically
|
|
214
|
+
wcag-a11y fix --from-report --apply # patch files from that report
|
|
207
215
|
```
|
|
208
216
|
|
|
209
217
|
---
|
|
210
218
|
|
|
211
219
|
## AI Providers
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
|---|---|---|---|
|
|
215
|
-
| `gemini` (default) | `gemini-2.5-flash` | Free tier | [aistudio.google.com](https://aistudio.google.com) |
|
|
216
|
-
| `openai` | `gpt-4o-mini` | Pay-per-use | [platform.openai.com](https://platform.openai.com/api-keys) |
|
|
217
|
-
| `ollama` | `llama3` | Free (local) | None — run `ollama serve` |
|
|
221
|
+
12 providers are supported. Set your provider in `a11y.config.json` or override per-run with `--provider <name>`.
|
|
218
222
|
|
|
219
|
-
|
|
223
|
+
| Provider | `--provider` name | Default model | API key source |
|
|
224
|
+
|---|---|---|---|
|
|
225
|
+
| Google Gemini | `gemini` *(default)* | `gemini-2.5-flash` | [aistudio.google.com](https://aistudio.google.com) — free tier |
|
|
226
|
+
| OpenAI | `openai` | `gpt-4o-mini` | [platform.openai.com/api-keys](https://platform.openai.com/api-keys) |
|
|
227
|
+
| Anthropic | `anthropic` | `claude-sonnet-4-6` | [console.anthropic.com](https://console.anthropic.com) |
|
|
228
|
+
| Mistral | `mistral` | `mistral-large-latest` | [console.mistral.ai](https://console.mistral.ai) |
|
|
229
|
+
| Groq | `groq` | `llama-3.3-70b-versatile` | [console.groq.com](https://console.groq.com) |
|
|
230
|
+
| Cohere | `cohere` | `command-r-plus` | [dashboard.cohere.com](https://dashboard.cohere.com) |
|
|
231
|
+
| xAI | `xai` | `grok-2` | [console.x.ai](https://console.x.ai) |
|
|
232
|
+
| DeepSeek | `deepseek` | `deepseek-chat` | [platform.deepseek.com](https://platform.deepseek.com) |
|
|
233
|
+
| Together AI | `together` | `meta-llama/Llama-3-70b-chat-hf` | [api.together.xyz](https://api.together.xyz) |
|
|
234
|
+
| Perplexity | `perplexity` | `llama-3.1-sonar-large-128k-online` | [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api) |
|
|
235
|
+
| Azure OpenAI | `azure-openai` | *(your deployment)* | [portal.azure.com](https://portal.azure.com) |
|
|
236
|
+
| Ollama | `ollama` | `llama3` | None — run `ollama serve` locally |
|
|
237
|
+
|
|
238
|
+
All models are configurable. Change the model field in `a11y.config.json` to use any model your API key has access to. If the AI response is unparseable, the tool generates a fix prompt directly from the violation data so you always get something actionable.
|
|
220
239
|
|
|
221
240
|
---
|
|
222
241
|
|
|
223
242
|
## Config (`a11y.config.json`)
|
|
224
243
|
|
|
244
|
+
Only the fields for your active `provider` are required. This file is gitignored by default.
|
|
245
|
+
|
|
225
246
|
```json
|
|
226
247
|
{
|
|
227
248
|
"provider": "gemini",
|
|
@@ -232,13 +253,40 @@ Set your provider in `a11y.config.json` or override it per-run with `--provider`
|
|
|
232
253
|
"openaiApiKey": "YOUR_OPENAI_API_KEY",
|
|
233
254
|
"openaiModel": "gpt-4o-mini",
|
|
234
255
|
|
|
256
|
+
"anthropicApiKey": "YOUR_ANTHROPIC_API_KEY",
|
|
257
|
+
"anthropicModel": "claude-sonnet-4-6",
|
|
258
|
+
|
|
259
|
+
"mistralApiKey": "YOUR_MISTRAL_API_KEY",
|
|
260
|
+
"mistralModel": "mistral-large-latest",
|
|
261
|
+
|
|
262
|
+
"groqApiKey": "YOUR_GROQ_API_KEY",
|
|
263
|
+
"groqModel": "llama-3.3-70b-versatile",
|
|
264
|
+
|
|
265
|
+
"cohereApiKey": "YOUR_COHERE_API_KEY",
|
|
266
|
+
"cohereModel": "command-r-plus",
|
|
267
|
+
|
|
268
|
+
"xaiApiKey": "YOUR_XAI_API_KEY",
|
|
269
|
+
"xaiModel": "grok-2",
|
|
270
|
+
|
|
271
|
+
"deepseekApiKey": "YOUR_DEEPSEEK_API_KEY",
|
|
272
|
+
"deepseekModel": "deepseek-chat",
|
|
273
|
+
|
|
274
|
+
"togetherApiKey": "YOUR_TOGETHER_API_KEY",
|
|
275
|
+
"togetherModel": "meta-llama/Llama-3-70b-chat-hf",
|
|
276
|
+
|
|
277
|
+
"perplexityApiKey": "YOUR_PERPLEXITY_API_KEY",
|
|
278
|
+
"perplexityModel": "llama-3.1-sonar-large-128k-online",
|
|
279
|
+
|
|
280
|
+
"azureOpenaiApiKey": "YOUR_AZURE_KEY",
|
|
281
|
+
"azureOpenaiEndpoint": "https://YOUR_RESOURCE.openai.azure.com",
|
|
282
|
+
"azureOpenaiDeployment": "YOUR_DEPLOYMENT_NAME",
|
|
283
|
+
"azureOpenaiApiVersion": "2024-10-01-preview",
|
|
284
|
+
|
|
235
285
|
"ollamaBaseUrl": "http://localhost:11434",
|
|
236
286
|
"ollamaModel": "llama3"
|
|
237
287
|
}
|
|
238
288
|
```
|
|
239
289
|
|
|
240
|
-
Only the fields for your active provider are required. This file is gitignored by default.
|
|
241
|
-
|
|
242
290
|
---
|
|
243
291
|
|
|
244
292
|
## What it checks
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { buildPrompt } from './prompt.js';
|
|
2
|
+
import { buildPatchPrompt } from './patch-prompt.js';
|
|
3
|
+
import { groupViolations } from './group.js';
|
|
4
|
+
import { BaseAIProvider } from './base.js';
|
|
5
|
+
export class AnthropicProvider extends BaseAIProvider {
|
|
6
|
+
apiKey;
|
|
7
|
+
model;
|
|
8
|
+
constructor(apiKey, model = 'claude-sonnet-4-6') {
|
|
9
|
+
super();
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.model = model;
|
|
12
|
+
}
|
|
13
|
+
get headers() {
|
|
14
|
+
return {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
'x-api-key': this.apiKey,
|
|
17
|
+
'anthropic-version': '2023-06-01',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async generateFixes(violations, strategy = 'rule', framework) {
|
|
21
|
+
if (violations.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
const groups = groupViolations(violations, strategy);
|
|
24
|
+
const prompt = buildPrompt(groups, framework);
|
|
25
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: this.headers,
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model: this.model,
|
|
30
|
+
max_tokens: 8192,
|
|
31
|
+
temperature: 0.2,
|
|
32
|
+
messages: [{ role: 'user', content: prompt }],
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
|
|
37
|
+
}
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
const text = data.content?.[0]?.text ?? '[]';
|
|
40
|
+
return this.parse(text, groups, framework);
|
|
41
|
+
}
|
|
42
|
+
async generateFilePatch(fileContent, violations, filePath, framework) {
|
|
43
|
+
const prompt = buildPatchPrompt(fileContent, violations, filePath, framework);
|
|
44
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: this.headers,
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
model: this.model,
|
|
49
|
+
max_tokens: 16384,
|
|
50
|
+
temperature: 0.1,
|
|
51
|
+
messages: [{ role: 'user', content: prompt }],
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok)
|
|
55
|
+
throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
return data.content?.[0]?.text ?? '';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OpenAICompatProvider } from './openai-compat.js';
|
|
2
|
+
export class AzureOpenAIProvider extends OpenAICompatProvider {
|
|
3
|
+
apiVersion;
|
|
4
|
+
constructor(endpoint, apiKey, deployment, apiVersion = '2024-10-01-preview') {
|
|
5
|
+
super(`${endpoint}/openai/deployments/${deployment}`, apiKey, deployment, 'Azure OpenAI');
|
|
6
|
+
this.apiVersion = apiVersion;
|
|
7
|
+
}
|
|
8
|
+
buildUrl(path) {
|
|
9
|
+
return `${this.baseUrl}${path}?api-version=${this.apiVersion}`;
|
|
10
|
+
}
|
|
11
|
+
buildHeaders() {
|
|
12
|
+
return {
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
'api-key': this.apiKey,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/ai/base.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { fallbackExplanation } from './fallback-explanation.js';
|
|
2
|
+
export class BaseAIProvider {
|
|
3
|
+
parse(text, groups, framework) {
|
|
4
|
+
try {
|
|
5
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
6
|
+
const fixes = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
|
|
7
|
+
return groups.map((g) => {
|
|
8
|
+
const found = fixes.find((f) => f.ruleId === g.ruleId);
|
|
9
|
+
return found
|
|
10
|
+
? { ...found, selectors: g.selectors, instanceCount: g.count }
|
|
11
|
+
: this.fallbackFix(g, framework);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return groups.map((g) => this.fallbackFix(g, framework));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
fallback(groups, framework) {
|
|
19
|
+
return groups.map((g) => this.fallbackFix(g, framework));
|
|
20
|
+
}
|
|
21
|
+
fallbackFix(g, framework) {
|
|
22
|
+
const v = g.representative;
|
|
23
|
+
const selectorList = g.selectors.map((s) => `- ${s}`).join('\n');
|
|
24
|
+
const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
|
|
25
|
+
const fwNote = framework ? `This project uses ${framework}. ` : '';
|
|
26
|
+
const prompt = g.count > 1
|
|
27
|
+
? `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`${v.html.slice(0, 300)}\`\n\nApply the fix to all ${g.count} instances in the codebase to comply with WCAG 2.1 SC ${g.wcag}.`
|
|
28
|
+
: `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n- HTML: \`${v.html.slice(0, 300)}\`\n\nApply the fix to comply with WCAG 2.1 SC ${g.wcag}.`;
|
|
29
|
+
return {
|
|
30
|
+
ruleId: g.ruleId,
|
|
31
|
+
selectors: g.selectors,
|
|
32
|
+
instanceCount: g.count,
|
|
33
|
+
explanation,
|
|
34
|
+
fixedCode: v.html,
|
|
35
|
+
wcagReference: `WCAG 2.1 SC ${g.wcag}`,
|
|
36
|
+
optimalPrompt: prompt,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { buildPrompt } from './prompt.js';
|
|
2
|
+
import { buildPatchPrompt } from './patch-prompt.js';
|
|
3
|
+
import { groupViolations } from './group.js';
|
|
4
|
+
import { BaseAIProvider } from './base.js';
|
|
5
|
+
export class CohereProvider extends BaseAIProvider {
|
|
6
|
+
apiKey;
|
|
7
|
+
model;
|
|
8
|
+
constructor(apiKey, model = 'command-r-plus') {
|
|
9
|
+
super();
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.model = model;
|
|
12
|
+
}
|
|
13
|
+
get headers() {
|
|
14
|
+
return {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async generateFixes(violations, strategy = 'rule', framework) {
|
|
20
|
+
if (violations.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
const groups = groupViolations(violations, strategy);
|
|
23
|
+
const prompt = buildPrompt(groups, framework);
|
|
24
|
+
const response = await fetch('https://api.cohere.com/v2/chat', {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: this.headers,
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
model: this.model,
|
|
29
|
+
messages: [{ role: 'user', content: prompt }],
|
|
30
|
+
max_tokens: 8192,
|
|
31
|
+
temperature: 0.2,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Cohere API error: ${response.status} ${await response.text()}`);
|
|
36
|
+
}
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
const text = data.message?.content?.[0]?.text ?? '[]';
|
|
39
|
+
return this.parse(text, groups, framework);
|
|
40
|
+
}
|
|
41
|
+
async generateFilePatch(fileContent, violations, filePath, framework) {
|
|
42
|
+
const prompt = buildPatchPrompt(fileContent, violations, filePath, framework);
|
|
43
|
+
const response = await fetch('https://api.cohere.com/v2/chat', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: this.headers,
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
model: this.model,
|
|
48
|
+
messages: [{ role: 'user', content: prompt }],
|
|
49
|
+
max_tokens: 16384,
|
|
50
|
+
temperature: 0.1,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
throw new Error(`Cohere API error: ${response.status} ${await response.text()}`);
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return data.message?.content?.[0]?.text ?? '';
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/ai/gemini.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { buildPrompt } from './prompt.js';
|
|
2
2
|
import { buildPatchPrompt } from './patch-prompt.js';
|
|
3
3
|
import { groupViolations } from './group.js';
|
|
4
|
-
import {
|
|
5
|
-
export class GeminiProvider {
|
|
4
|
+
import { BaseAIProvider } from './base.js';
|
|
5
|
+
export class GeminiProvider extends BaseAIProvider {
|
|
6
6
|
apiKey;
|
|
7
7
|
model;
|
|
8
8
|
constructor(apiKey, model = 'gemini-2.5-flash') {
|
|
9
|
+
super();
|
|
9
10
|
this.apiKey = apiKey;
|
|
10
11
|
this.model = model;
|
|
11
12
|
}
|
|
13
|
+
buildUrl(model) {
|
|
14
|
+
return `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${this.apiKey}`;
|
|
15
|
+
}
|
|
12
16
|
async generateFixes(violations, strategy = 'rule', framework) {
|
|
13
17
|
if (violations.length === 0)
|
|
14
18
|
return [];
|
|
15
19
|
const groups = groupViolations(violations, strategy);
|
|
16
20
|
const prompt = buildPrompt(groups, framework);
|
|
17
|
-
const
|
|
18
|
-
const response = await fetch(url, {
|
|
21
|
+
const response = await fetch(this.buildUrl(this.model), {
|
|
19
22
|
method: 'POST',
|
|
20
23
|
headers: { 'Content-Type': 'application/json' },
|
|
21
24
|
body: JSON.stringify({
|
|
@@ -32,8 +35,7 @@ export class GeminiProvider {
|
|
|
32
35
|
}
|
|
33
36
|
async generateFilePatch(fileContent, violations, filePath, framework) {
|
|
34
37
|
const prompt = buildPatchPrompt(fileContent, violations, filePath, framework);
|
|
35
|
-
const
|
|
36
|
-
const response = await fetch(url, {
|
|
38
|
+
const response = await fetch(this.buildUrl(this.model), {
|
|
37
39
|
method: 'POST',
|
|
38
40
|
headers: { 'Content-Type': 'application/json' },
|
|
39
41
|
body: JSON.stringify({
|
|
@@ -46,37 +48,4 @@ export class GeminiProvider {
|
|
|
46
48
|
const data = await response.json();
|
|
47
49
|
return data.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
|
|
48
50
|
}
|
|
49
|
-
parse(text, groups, framework) {
|
|
50
|
-
try {
|
|
51
|
-
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
52
|
-
const fixes = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
|
|
53
|
-
return groups.map((g) => {
|
|
54
|
-
const found = fixes.find((f) => f.ruleId === g.ruleId);
|
|
55
|
-
return found
|
|
56
|
-
? { ...found, selectors: g.selectors, instanceCount: g.count }
|
|
57
|
-
: this.fallbackFix(g, framework);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return groups.map((g) => this.fallbackFix(g, framework));
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
fallbackFix(g, framework) {
|
|
65
|
-
const v = g.representative;
|
|
66
|
-
const selectorList = g.selectors.map((s) => `- ${s}`).join('\n');
|
|
67
|
-
const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
|
|
68
|
-
const fwNote = framework ? `This project uses ${framework}. ` : '';
|
|
69
|
-
const prompt = g.count > 1
|
|
70
|
-
? `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`${v.html.slice(0, 300)}\`\n\nApply the fix to all ${g.count} instances in the codebase to comply with WCAG 2.1 SC ${g.wcag}.`
|
|
71
|
-
: `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n- HTML: \`${v.html.slice(0, 300)}\`\n\nApply the fix to comply with WCAG 2.1 SC ${g.wcag}.`;
|
|
72
|
-
return {
|
|
73
|
-
ruleId: g.ruleId,
|
|
74
|
-
selectors: g.selectors,
|
|
75
|
-
instanceCount: g.count,
|
|
76
|
-
explanation,
|
|
77
|
-
fixedCode: v.html,
|
|
78
|
-
wcagReference: `WCAG 2.1 SC ${g.wcag}`,
|
|
79
|
-
optimalPrompt: prompt,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
51
|
}
|
package/dist/ai/groq.js
ADDED
package/dist/ai/index.js
CHANGED
|
@@ -1,20 +1,84 @@
|
|
|
1
1
|
import { GeminiProvider } from './gemini.js';
|
|
2
2
|
import { OllamaProvider } from './ollama.js';
|
|
3
3
|
import { OpenAIProvider } from './openai.js';
|
|
4
|
+
import { AnthropicProvider } from './anthropic.js';
|
|
5
|
+
import { MistralProvider } from './mistral.js';
|
|
6
|
+
import { GroqProvider } from './groq.js';
|
|
7
|
+
import { CohereProvider } from './cohere.js';
|
|
8
|
+
import { XAIProvider } from './xai.js';
|
|
9
|
+
import { DeepSeekProvider } from './deepseek.js';
|
|
10
|
+
import { TogetherProvider } from './together.js';
|
|
11
|
+
import { PerplexityProvider } from './perplexity.js';
|
|
12
|
+
import { AzureOpenAIProvider } from './azure-openai.js';
|
|
4
13
|
export function createAIProvider(config) {
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
switch (config.provider) {
|
|
15
|
+
case 'ollama':
|
|
16
|
+
return new OllamaProvider(config.ollamaBaseUrl, config.ollamaModel);
|
|
17
|
+
case 'openai':
|
|
18
|
+
if (!config.openaiApiKey) {
|
|
19
|
+
console.error('No OpenAI API key found. Add "openaiApiKey" to a11y.config.json.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
return new OpenAIProvider(config.openaiApiKey, config.openaiModel);
|
|
23
|
+
case 'anthropic':
|
|
24
|
+
if (!config.anthropicApiKey) {
|
|
25
|
+
console.error('No Anthropic API key found. Add "anthropicApiKey" to a11y.config.json.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return new AnthropicProvider(config.anthropicApiKey, config.anthropicModel);
|
|
29
|
+
case 'mistral':
|
|
30
|
+
if (!config.mistralApiKey) {
|
|
31
|
+
console.error('No Mistral API key found. Add "mistralApiKey" to a11y.config.json.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
return new MistralProvider(config.mistralApiKey, config.mistralModel);
|
|
35
|
+
case 'groq':
|
|
36
|
+
if (!config.groqApiKey) {
|
|
37
|
+
console.error('No Groq API key found. Add "groqApiKey" to a11y.config.json.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
return new GroqProvider(config.groqApiKey, config.groqModel);
|
|
41
|
+
case 'cohere':
|
|
42
|
+
if (!config.cohereApiKey) {
|
|
43
|
+
console.error('No Cohere API key found. Add "cohereApiKey" to a11y.config.json.');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
return new CohereProvider(config.cohereApiKey, config.cohereModel);
|
|
47
|
+
case 'xai':
|
|
48
|
+
if (!config.xaiApiKey) {
|
|
49
|
+
console.error('No xAI API key found. Add "xaiApiKey" to a11y.config.json.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
return new XAIProvider(config.xaiApiKey, config.xaiModel);
|
|
53
|
+
case 'deepseek':
|
|
54
|
+
if (!config.deepseekApiKey) {
|
|
55
|
+
console.error('No DeepSeek API key found. Add "deepseekApiKey" to a11y.config.json.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
return new DeepSeekProvider(config.deepseekApiKey, config.deepseekModel);
|
|
59
|
+
case 'together':
|
|
60
|
+
if (!config.togetherApiKey) {
|
|
61
|
+
console.error('No Together AI API key found. Add "togetherApiKey" to a11y.config.json.');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
return new TogetherProvider(config.togetherApiKey, config.togetherModel);
|
|
65
|
+
case 'perplexity':
|
|
66
|
+
if (!config.perplexityApiKey) {
|
|
67
|
+
console.error('No Perplexity API key found. Add "perplexityApiKey" to a11y.config.json.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
return new PerplexityProvider(config.perplexityApiKey, config.perplexityModel);
|
|
71
|
+
case 'azure-openai':
|
|
72
|
+
if (!config.azureOpenaiApiKey || !config.azureOpenaiEndpoint || !config.azureOpenaiDeployment) {
|
|
73
|
+
console.error('Azure OpenAI requires "azureOpenaiApiKey", "azureOpenaiEndpoint", and "azureOpenaiDeployment" in a11y.config.json.');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
return new AzureOpenAIProvider(config.azureOpenaiEndpoint, config.azureOpenaiApiKey, config.azureOpenaiDeployment, config.azureOpenaiApiVersion);
|
|
77
|
+
default:
|
|
78
|
+
if (!config.apiKey) {
|
|
79
|
+
console.error('No API key found. Run: wcag-a11y init');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
return new GeminiProvider(config.apiKey, config.model);
|
|
7
83
|
}
|
|
8
|
-
if (config.provider === 'openai') {
|
|
9
|
-
if (!config.openaiApiKey) {
|
|
10
|
-
console.error('No OpenAI API key found. Add "openaiApiKey" to a11y.config.json.');
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
return new OpenAIProvider(config.openaiApiKey, config.openaiModel);
|
|
14
|
-
}
|
|
15
|
-
if (!config.apiKey) {
|
|
16
|
-
console.error('No API key found. Run: wcag-a11y init');
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
return new GeminiProvider(config.apiKey, config.model);
|
|
20
84
|
}
|
package/dist/ai/ollama.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { buildPrompt } from './prompt.js';
|
|
2
2
|
import { buildPatchPrompt } from './patch-prompt.js';
|
|
3
3
|
import { groupViolations } from './group.js';
|
|
4
|
-
import {
|
|
5
|
-
export class OllamaProvider {
|
|
4
|
+
import { BaseAIProvider } from './base.js';
|
|
5
|
+
export class OllamaProvider extends BaseAIProvider {
|
|
6
6
|
baseUrl;
|
|
7
7
|
model;
|
|
8
8
|
constructor(baseUrl = 'http://localhost:11434', model = 'llama3') {
|
|
9
|
+
super();
|
|
9
10
|
this.baseUrl = baseUrl;
|
|
10
11
|
this.model = model;
|
|
11
12
|
}
|
|
@@ -52,25 +53,4 @@ export class OllamaProvider {
|
|
|
52
53
|
const data = await response.json();
|
|
53
54
|
return data.response ?? '';
|
|
54
55
|
}
|
|
55
|
-
fallback(groups, framework) {
|
|
56
|
-
return groups.map((g) => this.fallbackFix(g, framework));
|
|
57
|
-
}
|
|
58
|
-
fallbackFix(g, framework) {
|
|
59
|
-
const v = g.representative;
|
|
60
|
-
const selectorList = g.selectors.map((s) => `- ${s}`).join('\n');
|
|
61
|
-
const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
|
|
62
|
-
const fwNote = framework ? `This project uses ${framework}. ` : '';
|
|
63
|
-
const prompt = g.count > 1
|
|
64
|
-
? `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`${v.html.slice(0, 300)}\`\n\nApply the fix to all ${g.count} instances in the codebase to comply with WCAG 2.1 SC ${g.wcag}.`
|
|
65
|
-
: `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n- HTML: \`${v.html.slice(0, 300)}\`\n\nApply the fix to comply with WCAG 2.1 SC ${g.wcag}.`;
|
|
66
|
-
return {
|
|
67
|
-
ruleId: g.ruleId,
|
|
68
|
-
selectors: g.selectors,
|
|
69
|
-
instanceCount: g.count,
|
|
70
|
-
explanation,
|
|
71
|
-
fixedCode: v.html,
|
|
72
|
-
wcagReference: `WCAG 2.1 SC ${g.wcag}`,
|
|
73
|
-
optimalPrompt: prompt,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
56
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { buildPrompt } from './prompt.js';
|
|
2
|
+
import { buildPatchPrompt } from './patch-prompt.js';
|
|
3
|
+
import { groupViolations } from './group.js';
|
|
4
|
+
import { BaseAIProvider } from './base.js';
|
|
5
|
+
export class OpenAICompatProvider extends BaseAIProvider {
|
|
6
|
+
baseUrl;
|
|
7
|
+
apiKey;
|
|
8
|
+
model;
|
|
9
|
+
providerName;
|
|
10
|
+
constructor(baseUrl, apiKey, model, providerName = 'API') {
|
|
11
|
+
super();
|
|
12
|
+
this.baseUrl = baseUrl;
|
|
13
|
+
this.apiKey = apiKey;
|
|
14
|
+
this.model = model;
|
|
15
|
+
this.providerName = providerName;
|
|
16
|
+
}
|
|
17
|
+
buildUrl(path) {
|
|
18
|
+
return `${this.baseUrl}${path}`;
|
|
19
|
+
}
|
|
20
|
+
buildHeaders() {
|
|
21
|
+
return {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async generateFixes(violations, strategy = 'rule', framework) {
|
|
27
|
+
if (violations.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
const groups = groupViolations(violations, strategy);
|
|
30
|
+
const prompt = buildPrompt(groups, framework);
|
|
31
|
+
const response = await fetch(this.buildUrl('/chat/completions'), {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: this.buildHeaders(),
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
model: this.model,
|
|
36
|
+
messages: [{ role: 'user', content: prompt }],
|
|
37
|
+
temperature: 0.2,
|
|
38
|
+
max_tokens: 8192,
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`${this.providerName} API error: ${response.status} ${await response.text()}`);
|
|
43
|
+
}
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
const text = data.choices?.[0]?.message?.content ?? '[]';
|
|
46
|
+
return this.parse(text, groups, framework);
|
|
47
|
+
}
|
|
48
|
+
async generateFilePatch(fileContent, violations, filePath, framework) {
|
|
49
|
+
const prompt = buildPatchPrompt(fileContent, violations, filePath, framework);
|
|
50
|
+
const response = await fetch(this.buildUrl('/chat/completions'), {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: this.buildHeaders(),
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
model: this.model,
|
|
55
|
+
messages: [{ role: 'user', content: prompt }],
|
|
56
|
+
temperature: 0.1,
|
|
57
|
+
max_tokens: 16384,
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok)
|
|
61
|
+
throw new Error(`${this.providerName} API error: ${response.status} ${await response.text()}`);
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
return data.choices?.[0]?.message?.content ?? '';
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/ai/openai.js
CHANGED
|
@@ -1,87 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { groupViolations } from './group.js';
|
|
4
|
-
import { fallbackExplanation } from './fallback-explanation.js';
|
|
5
|
-
export class OpenAIProvider {
|
|
6
|
-
apiKey;
|
|
7
|
-
model;
|
|
1
|
+
import { OpenAICompatProvider } from './openai-compat.js';
|
|
2
|
+
export class OpenAIProvider extends OpenAICompatProvider {
|
|
8
3
|
constructor(apiKey, model = 'gpt-4o-mini') {
|
|
9
|
-
|
|
10
|
-
this.model = model;
|
|
11
|
-
}
|
|
12
|
-
async generateFixes(violations, strategy = 'rule', framework) {
|
|
13
|
-
if (violations.length === 0)
|
|
14
|
-
return [];
|
|
15
|
-
const groups = groupViolations(violations, strategy);
|
|
16
|
-
const prompt = buildPrompt(groups, framework);
|
|
17
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
18
|
-
method: 'POST',
|
|
19
|
-
headers: {
|
|
20
|
-
'Content-Type': 'application/json',
|
|
21
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
22
|
-
},
|
|
23
|
-
body: JSON.stringify({
|
|
24
|
-
model: this.model,
|
|
25
|
-
messages: [{ role: 'user', content: prompt }],
|
|
26
|
-
temperature: 0.2,
|
|
27
|
-
max_tokens: 8192,
|
|
28
|
-
}),
|
|
29
|
-
});
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
throw new Error(`OpenAI API error: ${response.status} ${await response.text()}`);
|
|
32
|
-
}
|
|
33
|
-
const data = await response.json();
|
|
34
|
-
const text = data.choices?.[0]?.message?.content ?? '[]';
|
|
35
|
-
return this.parse(text, groups, framework);
|
|
36
|
-
}
|
|
37
|
-
async generateFilePatch(fileContent, violations, filePath, framework) {
|
|
38
|
-
const prompt = buildPatchPrompt(fileContent, violations, filePath, framework);
|
|
39
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}` },
|
|
42
|
-
body: JSON.stringify({
|
|
43
|
-
model: this.model,
|
|
44
|
-
messages: [{ role: 'user', content: prompt }],
|
|
45
|
-
temperature: 0.1,
|
|
46
|
-
max_tokens: 16384,
|
|
47
|
-
}),
|
|
48
|
-
});
|
|
49
|
-
if (!response.ok)
|
|
50
|
-
throw new Error(`OpenAI API error: ${response.status} ${await response.text()}`);
|
|
51
|
-
const data = await response.json();
|
|
52
|
-
return data.choices?.[0]?.message?.content ?? '';
|
|
53
|
-
}
|
|
54
|
-
parse(text, groups, framework) {
|
|
55
|
-
try {
|
|
56
|
-
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
57
|
-
const fixes = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
|
|
58
|
-
return groups.map((g) => {
|
|
59
|
-
const found = fixes.find((f) => f.ruleId === g.ruleId);
|
|
60
|
-
return found
|
|
61
|
-
? { ...found, selectors: g.selectors, instanceCount: g.count }
|
|
62
|
-
: this.fallbackFix(g, framework);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return groups.map((g) => this.fallbackFix(g, framework));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
fallbackFix(g, framework) {
|
|
70
|
-
const v = g.representative;
|
|
71
|
-
const selectorList = g.selectors.map((s) => `- ${s}`).join('\n');
|
|
72
|
-
const explanation = fallbackExplanation(g.ruleId, g.description, g.wcag, g.level);
|
|
73
|
-
const fwNote = framework ? `This project uses ${framework}. ` : '';
|
|
74
|
-
const prompt = g.count > 1
|
|
75
|
-
? `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected elements (${g.count} instances):\n${selectorList}\n\nRepresentative HTML:\n\`${v.html.slice(0, 300)}\`\n\nApply the fix to all ${g.count} instances in the codebase to comply with WCAG 2.1 SC ${g.wcag}.`
|
|
76
|
-
: `${fwNote}Fix WCAG 2.1 SC ${g.wcag} (Level ${g.level}) — ${g.description}\n\nAffected element:\n- Selector: \`${g.selectors[0]}\`\n- HTML: \`${v.html.slice(0, 300)}\`\n\nApply the fix to comply with WCAG 2.1 SC ${g.wcag}.`;
|
|
77
|
-
return {
|
|
78
|
-
ruleId: g.ruleId,
|
|
79
|
-
selectors: g.selectors,
|
|
80
|
-
instanceCount: g.count,
|
|
81
|
-
explanation,
|
|
82
|
-
fixedCode: v.html,
|
|
83
|
-
wcagReference: `WCAG 2.1 SC ${g.wcag}`,
|
|
84
|
-
optimalPrompt: prompt,
|
|
85
|
-
};
|
|
4
|
+
super('https://api.openai.com/v1', apiKey, model, 'OpenAI');
|
|
86
5
|
}
|
|
87
6
|
}
|
package/dist/ai/xai.js
ADDED
package/dist/cli.js
CHANGED
|
@@ -13,11 +13,11 @@ const program = new Command();
|
|
|
13
13
|
program
|
|
14
14
|
.name('wcag-a11y')
|
|
15
15
|
.description('WCAG 2.1/2.2 accessibility auditor with AI-powered fixes')
|
|
16
|
-
.version('0.
|
|
16
|
+
.version('0.4.0');
|
|
17
17
|
program
|
|
18
18
|
.command('init')
|
|
19
19
|
.description('Create a11y.config.json in the current directory')
|
|
20
|
-
.option('--provider <name>', 'AI provider to configure (gemini|openai|ollama)', 'gemini')
|
|
20
|
+
.option('--provider <name>', 'AI provider to configure (gemini|openai|ollama|anthropic|mistral|groq|cohere|xai|deepseek|together|perplexity|azure-openai)', 'gemini')
|
|
21
21
|
.action((opts) => {
|
|
22
22
|
initConfig(opts.provider);
|
|
23
23
|
});
|
|
@@ -34,7 +34,7 @@ program
|
|
|
34
34
|
.option('--fast-mode', 'Output only AI fix prompts — no summaries or explanations', false)
|
|
35
35
|
.option('--group <strategy>', 'Group violations by rule or show individually (rule|none)', 'rule')
|
|
36
36
|
.option('--ci', 'Exit with code 1 if any violations are found (for CI/CD pipelines)', false)
|
|
37
|
-
.option('--provider <name>', 'Override the AI provider from config (gemini|openai|ollama)')
|
|
37
|
+
.option('--provider <name>', 'Override the AI provider from config (gemini|openai|ollama|anthropic|mistral|groq|cohere|xai|deepseek|together|perplexity|azure-openai)')
|
|
38
38
|
.action(async (opts) => {
|
|
39
39
|
try {
|
|
40
40
|
console.log(`\nScanning ${opts.url}...`);
|
|
@@ -82,7 +82,7 @@ program
|
|
|
82
82
|
.option('-c, --crawl', 'Auto-discover pages by following same-origin links', false)
|
|
83
83
|
.option('--from-report [path]', 'Use an existing report instead of scanning (default: a11y-report.md)')
|
|
84
84
|
.option('--apply', 'Write fixes to source files (default: dry-run, shows diff only)', false)
|
|
85
|
-
.option('--provider <name>', 'Override the AI provider from config (gemini|openai|ollama)')
|
|
85
|
+
.option('--provider <name>', 'Override the AI provider from config (gemini|openai|ollama|anthropic|mistral|groq|cohere|xai|deepseek|together|perplexity|azure-openai)')
|
|
86
86
|
.action(async (opts) => {
|
|
87
87
|
if (!opts.url && !opts.fromReport) {
|
|
88
88
|
console.error('\nError: provide --url <url> to scan, or --from-report [path] to load an existing report.');
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,15 @@ const DEFAULTS = {
|
|
|
7
7
|
ollamaBaseUrl: 'http://localhost:11434',
|
|
8
8
|
ollamaModel: 'llama3',
|
|
9
9
|
openaiModel: 'gpt-4o-mini',
|
|
10
|
+
anthropicModel: 'claude-sonnet-4-6',
|
|
11
|
+
mistralModel: 'mistral-large-latest',
|
|
12
|
+
groqModel: 'llama-3.3-70b-versatile',
|
|
13
|
+
cohereModel: 'command-r-plus',
|
|
14
|
+
xaiModel: 'grok-2',
|
|
15
|
+
deepseekModel: 'deepseek-chat',
|
|
16
|
+
togetherModel: 'meta-llama/Llama-3-70b-chat-hf',
|
|
17
|
+
perplexityModel: 'llama-3.1-sonar-large-128k-online',
|
|
18
|
+
azureOpenaiApiVersion: '2024-10-01-preview',
|
|
10
19
|
};
|
|
11
20
|
export function loadConfig() {
|
|
12
21
|
const configPath = join(process.cwd(), CONFIG_FILE);
|
|
@@ -30,6 +39,48 @@ const STARTER_CONFIGS = {
|
|
|
30
39
|
{ provider: 'ollama', ollamaBaseUrl: 'http://localhost:11434', ollamaModel: 'llama3' },
|
|
31
40
|
`Created ${CONFIG_FILE} — run \`ollama serve\` to start the local model server`,
|
|
32
41
|
],
|
|
42
|
+
anthropic: [
|
|
43
|
+
{ provider: 'anthropic', anthropicApiKey: 'YOUR_ANTHROPIC_API_KEY', anthropicModel: 'claude-sonnet-4-6' },
|
|
44
|
+
`Created ${CONFIG_FILE} — add your Anthropic API key from https://console.anthropic.com`,
|
|
45
|
+
],
|
|
46
|
+
mistral: [
|
|
47
|
+
{ provider: 'mistral', mistralApiKey: 'YOUR_MISTRAL_API_KEY', mistralModel: 'mistral-large-latest' },
|
|
48
|
+
`Created ${CONFIG_FILE} — add your Mistral API key from https://console.mistral.ai`,
|
|
49
|
+
],
|
|
50
|
+
groq: [
|
|
51
|
+
{ provider: 'groq', groqApiKey: 'YOUR_GROQ_API_KEY', groqModel: 'llama-3.3-70b-versatile' },
|
|
52
|
+
`Created ${CONFIG_FILE} — add your Groq API key from https://console.groq.com`,
|
|
53
|
+
],
|
|
54
|
+
cohere: [
|
|
55
|
+
{ provider: 'cohere', cohereApiKey: 'YOUR_COHERE_API_KEY', cohereModel: 'command-r-plus' },
|
|
56
|
+
`Created ${CONFIG_FILE} — add your Cohere API key from https://dashboard.cohere.com`,
|
|
57
|
+
],
|
|
58
|
+
xai: [
|
|
59
|
+
{ provider: 'xai', xaiApiKey: 'YOUR_XAI_API_KEY', xaiModel: 'grok-2' },
|
|
60
|
+
`Created ${CONFIG_FILE} — add your xAI API key from https://console.x.ai`,
|
|
61
|
+
],
|
|
62
|
+
deepseek: [
|
|
63
|
+
{ provider: 'deepseek', deepseekApiKey: 'YOUR_DEEPSEEK_API_KEY', deepseekModel: 'deepseek-chat' },
|
|
64
|
+
`Created ${CONFIG_FILE} — add your DeepSeek API key from https://platform.deepseek.com`,
|
|
65
|
+
],
|
|
66
|
+
together: [
|
|
67
|
+
{ provider: 'together', togetherApiKey: 'YOUR_TOGETHER_API_KEY', togetherModel: 'meta-llama/Llama-3-70b-chat-hf' },
|
|
68
|
+
`Created ${CONFIG_FILE} — add your Together AI API key from https://api.together.xyz`,
|
|
69
|
+
],
|
|
70
|
+
perplexity: [
|
|
71
|
+
{ provider: 'perplexity', perplexityApiKey: 'YOUR_PERPLEXITY_API_KEY', perplexityModel: 'llama-3.1-sonar-large-128k-online' },
|
|
72
|
+
`Created ${CONFIG_FILE} — add your Perplexity API key from https://www.perplexity.ai/settings/api`,
|
|
73
|
+
],
|
|
74
|
+
'azure-openai': [
|
|
75
|
+
{
|
|
76
|
+
provider: 'azure-openai',
|
|
77
|
+
azureOpenaiApiKey: 'YOUR_AZURE_OPENAI_API_KEY',
|
|
78
|
+
azureOpenaiEndpoint: 'https://YOUR_RESOURCE.openai.azure.com',
|
|
79
|
+
azureOpenaiDeployment: 'YOUR_DEPLOYMENT_NAME',
|
|
80
|
+
azureOpenaiApiVersion: '2024-10-01-preview',
|
|
81
|
+
},
|
|
82
|
+
`Created ${CONFIG_FILE} — fill in your Azure OpenAI endpoint, deployment, and API key from https://portal.azure.com`,
|
|
83
|
+
],
|
|
33
84
|
};
|
|
34
85
|
export function initConfig(provider = 'gemini') {
|
|
35
86
|
const configPath = join(process.cwd(), CONFIG_FILE);
|
package/package.json
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wcag-a11y",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "WCAG 2.1/2.2 accessibility auditor with AI-powered fixes",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "WCAG 2.1/2.2 accessibility auditor with AI-powered fixes. Crawls your dev server with Playwright, runs 40+ checks, and uses AI (12 providers) to generate fix prompts or patch source files directly.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wcag",
|
|
7
|
+
"accessibility",
|
|
8
|
+
"a11y",
|
|
9
|
+
"wcag2",
|
|
10
|
+
"wcag21",
|
|
11
|
+
"wcag22",
|
|
12
|
+
"aria",
|
|
13
|
+
"playwright",
|
|
14
|
+
"ai",
|
|
15
|
+
"audit",
|
|
16
|
+
"fix",
|
|
17
|
+
"cli",
|
|
18
|
+
"gemini",
|
|
19
|
+
"openai",
|
|
20
|
+
"anthropic",
|
|
21
|
+
"screen-reader"
|
|
22
|
+
],
|
|
5
23
|
"type": "module",
|
|
6
24
|
"bin": {
|
|
7
25
|
"wcag-a11y": "dist/cli.js"
|