a11y-devkit-deploy 0.9.2 → 0.9.4

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 CHANGED
@@ -64,7 +64,7 @@ All MCP servers are configured to run via `npx`, which means:
64
64
 
65
65
  This CLI automates the setup of accessibility tooling by:
66
66
 
67
- 1. **Installing skills from npm** - Downloads and installs accessibility skill packages (configurable in `config/a11y.json`)
67
+ 1. **Installing skills from npm** - Downloads and installs accessibility skill packages (configurable in `config/settings.json`)
68
68
  2. **Configuring MCP servers** - Updates each IDE's MCP config to enable accessibility-focused MCP servers (also configurable)
69
69
 
70
70
  **Default configuration includes:**
@@ -80,7 +80,7 @@ This CLI automates the setup of accessibility tooling by:
80
80
 
81
81
  ## Why This Tool?
82
82
 
83
- **Zero Hardcoded Values** - Every aspect of the tool is driven by `config/a11y.json`:
83
+ **Zero Hardcoded Values** - Every aspect of the tool is driven by `config/settings.json`:
84
84
  - IDE paths and configuration files
85
85
  - Skills to install
86
86
  - MCP servers to configure
@@ -104,7 +104,7 @@ This CLI automates the setup of accessibility tooling by:
104
104
 
105
105
  ### Skills Installed (Default)
106
106
 
107
- The following skill packages are installed from npm by default. **Add your own by editing `config/a11y.json`**:
107
+ The following skill packages are installed from npm by default. **Add your own by editing `config/settings.json`**:
108
108
 
109
109
  | Skill | Package | Description |
110
110
  |-------|---------|-------------|
@@ -143,7 +143,7 @@ The generated MCP config looks like this:
143
143
 
144
144
  ## Configuration
145
145
 
146
- The entire tool is **fully config-driven**. Edit `config/a11y.json` to customize everything without touching code.
146
+ The entire tool is **fully config-driven**. Edit `config/settings.json` to customize everything without touching code.
147
147
 
148
148
  ### Adding a New Skill
149
149
 
@@ -218,14 +218,21 @@ Add an object to the `hostApplications` array with the host application's config
218
218
  **Host Application Configuration Properties:**
219
219
  - `id` - Unique identifier for the host application
220
220
  - `displayName` - Human-readable name shown in prompts
221
- - `mcpServerKey` - MCP config key name (`"servers"` or `"mcpServers"`)
221
+ - `mcpServerKey` - MCP config key name (`"servers"`, `"mcpServers"`, or `"mcp_servers"` for TOML)
222
222
  - `skillsFolder` - Path to skills directory (relative to home/project root)
223
- - `mcpConfigFile` - Path to MCP config file (relative to home/project root)
223
+ - `mcpConfigFile` - Path to MCP config file (relative to home/project root). Supports both JSON (`.json`) and TOML (`.toml`) formats. TOML format is auto-detected by file extension (used by Codex).
224
224
  - `globalMcpConfigFile` - (Optional) Path to global MCP config relative to AppData/Application Support instead of home directory. Used for hosts like VSCode that store configs in platform-specific app directories:
225
225
  - Windows: `%APPDATA%` (e.g., `C:\Users\name\AppData\Roaming`)
226
226
  - macOS: `~/Library/Application Support`
227
227
  - Linux: `$XDG_CONFIG_HOME` or `~/.config`
228
228
 
229
+ **Note:** Codex uses TOML format for its MCP configuration (`~/.codex/config.toml`), which requires the `mcpServerKey` to be `"mcp_servers"` and generates config entries like:
230
+ ```toml
231
+ [mcp_servers.magentaa11y]
232
+ command = "npx"
233
+ args = ["-y", "magentaa11y-mcp"]
234
+ ```
235
+
229
236
  ### Config Structure
230
237
 
231
238
  - `skillsFolder` - Subfolder name to bundle skills under (e.g., "a11y")
@@ -255,7 +262,7 @@ your-project/
255
262
  │ ├── mcp.json # Cursor MCP config
256
263
  │ └── skills/ # Cursor skills
257
264
  ├── .codex/
258
- │ ├── mcp.json # Codex MCP config
265
+ │ ├── config.toml # Codex MCP config
259
266
  │ └── skills/ # Codex skills
260
267
  ├── .github/
261
268
  │ ├── mcp.json # VSCode MCP config
@@ -277,7 +284,7 @@ your-project/
277
284
  ├── mcp.json # Cursor global MCP config
278
285
  └── skills/ # Cursor global skills
279
286
  ~/.codex/
280
- ├── mcp.json # Codex global MCP config
287
+ ├── config.toml # Codex global MCP config
281
288
  └── skills/ # Codex global skills
282
289
  ~/.github/
283
290
  └── skills/ # VSCode global skills
@@ -293,11 +300,11 @@ your-project/
293
300
  # macOS: ~/Library/Application Support/Code/User/mcp.json
294
301
  ```
295
302
 
296
- **Note:** Paths are fully customizable per IDE in `config/a11y.json`
303
+ **Note:** Paths are fully customizable per IDE in `config/settings.json`
297
304
 
298
305
  ## MCP Servers Included (Default)
299
306
 
300
- **Add your own by editing `config/a11y.json`**:
307
+ **Add your own by editing `config/settings.json`**:
301
308
 
302
309
  | Server | Package | Description |
303
310
  |--------|---------|-------------|
@@ -309,7 +316,7 @@ your-project/
309
316
 
310
317
  ## Complete Config Example
311
318
 
312
- Here's what a complete `config/a11y.json` looks like:
319
+ Here's what a complete `config/settings.json` looks like:
313
320
 
314
321
  ```json
315
322
  {
@@ -366,3 +373,4 @@ Here's what a complete `config/a11y.json` looks like:
366
373
  ```
367
374
 
368
375
  Everything is customizable - add, remove, or modify any section to match your needs.
376
+
@@ -63,9 +63,9 @@
63
63
  {
64
64
  "id": "codex",
65
65
  "displayName": "Codex",
66
- "mcpServerKey": "servers",
66
+ "mcpServerKey": "mcp_servers",
67
67
  "skillsFolder": ".codex/skills",
68
- "mcpConfigFile": ".codex/mcp.json"
68
+ "mcpConfigFile": ".codex/config.toml"
69
69
  },
70
70
  {
71
71
  "id": "vscode",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a11y-devkit-deploy",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "CLI to deploy a11y skills and MCP servers across IDEs",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -34,7 +34,7 @@ const __filename = fileURLToPath(import.meta.url);
34
34
  const __dirname = path.dirname(__filename);
35
35
 
36
36
  async function loadConfig() {
37
- const configPath = path.join(__dirname, "..", "config", "a11y.json");
37
+ const configPath = path.join(__dirname, "..", "config", "settings.json");
38
38
  const raw = await fs.readFile(configPath, "utf8");
39
39
  return JSON.parse(raw);
40
40
  }
@@ -733,3 +733,4 @@ async function runGitMcpInstallation(projectRoot, platformInfo, config, hostPath
733
733
  }
734
734
 
735
735
  export { run };
736
+
@@ -10,6 +10,10 @@ async function pathExists(target) {
10
10
  }
11
11
  }
12
12
 
13
+ function isTomlFile(filePath) {
14
+ return filePath.endsWith('.toml');
15
+ }
16
+
13
17
  async function loadJson(filePath) {
14
18
  if (!(await pathExists(filePath))) {
15
19
  return {};
@@ -25,6 +29,120 @@ async function loadJson(filePath) {
25
29
  }
26
30
  }
27
31
 
32
+ // Simple TOML parser for MCP server configs
33
+ function parseSimpleToml(content) {
34
+ const result = {};
35
+ const lines = content.split('\n');
36
+ let currentSection = null;
37
+ let currentTable = null;
38
+
39
+ for (const line of lines) {
40
+ const trimmed = line.trim();
41
+
42
+ // Skip empty lines and comments
43
+ if (!trimmed || trimmed.startsWith('#')) continue;
44
+
45
+ // Parse table headers like [mcp_servers.name]
46
+ const tableMatch = trimmed.match(/^\[([^\]]+)\]$/);
47
+ if (tableMatch) {
48
+ const parts = tableMatch[1].split('.');
49
+ if (parts.length === 2) {
50
+ const [section, table] = parts;
51
+ if (!result[section]) result[section] = {};
52
+ if (!result[section][table]) result[section][table] = {};
53
+ currentSection = section;
54
+ currentTable = table;
55
+ }
56
+ continue;
57
+ }
58
+
59
+ // Parse key-value pairs
60
+ const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
61
+ if (kvMatch && currentSection && currentTable) {
62
+ const [, key, value] = kvMatch;
63
+
64
+ // Parse arrays like ["a", "b"]
65
+ if (value.startsWith('[')) {
66
+ const arrayMatch = value.match(/\[(.*)\]/);
67
+ if (arrayMatch) {
68
+ const items = arrayMatch[1].split(',').map(item => {
69
+ const trimmedItem = item.trim();
70
+ // Remove quotes
71
+ if (trimmedItem.startsWith('"') && trimmedItem.endsWith('"')) {
72
+ return trimmedItem.slice(1, -1);
73
+ }
74
+ return trimmedItem;
75
+ });
76
+ result[currentSection][currentTable][key] = items;
77
+ }
78
+ }
79
+ // Parse strings
80
+ else if (value.startsWith('"') && value.endsWith('"')) {
81
+ result[currentSection][currentTable][key] = value.slice(1, -1);
82
+ }
83
+ // Parse other values
84
+ else {
85
+ result[currentSection][currentTable][key] = value;
86
+ }
87
+ }
88
+ }
89
+
90
+ return result;
91
+ }
92
+
93
+ // Simple TOML stringifier for MCP server configs
94
+ function stringifySimpleToml(obj) {
95
+ const lines = [];
96
+
97
+ for (const [section, tables] of Object.entries(obj)) {
98
+ if (typeof tables !== 'object' || tables === null) continue;
99
+
100
+ for (const [tableName, config] of Object.entries(tables)) {
101
+ if (typeof config !== 'object' || config === null) continue;
102
+
103
+ // Write table header [section.tableName]
104
+ lines.push(`[${section}.${tableName}]`);
105
+
106
+ // Write key-value pairs
107
+ for (const [key, value] of Object.entries(config)) {
108
+ if (Array.isArray(value)) {
109
+ // Format arrays
110
+ const arrayStr = value.map(v => `"${v}"`).join(', ');
111
+ lines.push(`${key} = [${arrayStr}]`);
112
+ } else if (typeof value === 'string') {
113
+ lines.push(`${key} = "${value}"`);
114
+ } else {
115
+ lines.push(`${key} = ${value}`);
116
+ }
117
+ }
118
+
119
+ // Add blank line between tables
120
+ lines.push('');
121
+ }
122
+ }
123
+
124
+ return lines.join('\n');
125
+ }
126
+
127
+ async function loadToml(filePath) {
128
+ if (!(await pathExists(filePath))) {
129
+ return {};
130
+ }
131
+
132
+ try {
133
+ const raw = await fs.readFile(filePath, "utf8");
134
+ return raw.trim() ? parseSimpleToml(raw) : {};
135
+ } catch (error) {
136
+ const backupPath = `${filePath}.bak`;
137
+ await fs.copyFile(filePath, backupPath);
138
+ return {};
139
+ }
140
+ }
141
+
142
+ async function loadConfig(filePath) {
143
+ return isTomlFile(filePath) ? loadToml(filePath) : loadJson(filePath);
144
+ }
145
+
28
146
  function mergeServers(existing, incoming, serverKey = "servers") {
29
147
  const existingServers = existing[serverKey] && typeof existing[serverKey] === "object"
30
148
  ? existing[serverKey]
@@ -84,9 +202,14 @@ function removeServers(existing, removeNames, serverKey = "servers") {
84
202
 
85
203
  async function installMcpConfig(configPath, servers, serverKey = "servers") {
86
204
  await fs.mkdir(path.dirname(configPath), { recursive: true });
87
- const existing = await loadJson(configPath);
205
+ const existing = await loadConfig(configPath);
88
206
  const updated = mergeServers(existing, servers, serverKey);
89
- await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
207
+
208
+ if (isTomlFile(configPath)) {
209
+ await fs.writeFile(configPath, stringifySimpleToml(updated), "utf8");
210
+ } else {
211
+ await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
212
+ }
90
213
  }
91
214
 
92
215
  async function removeMcpConfig(configPath, serverNames, serverKey = "servers") {
@@ -94,14 +217,18 @@ async function removeMcpConfig(configPath, serverNames, serverKey = "servers") {
94
217
  return { removed: 0, changed: false };
95
218
  }
96
219
 
97
- const existing = await loadJson(configPath);
220
+ const existing = await loadConfig(configPath);
98
221
  const { updated, removed } = removeServers(existing, serverNames, serverKey);
99
222
 
100
223
  if (removed === 0) {
101
224
  return { removed: 0, changed: false };
102
225
  }
103
226
 
104
- await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
227
+ if (isTomlFile(configPath)) {
228
+ await fs.writeFile(configPath, stringifySimpleToml(updated), "utf8");
229
+ } else {
230
+ await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
231
+ }
105
232
  return { removed, changed: true };
106
233
  }
107
234