a11y-devkit-deploy 0.6.5 → 0.7.1

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
@@ -1,6 +1,8 @@
1
1
  # A11y Devkit Deploy
2
2
 
3
- A cross-platform CLI for deploying accessibility skills and MCP servers across Claude Code, Cursor, Codex, and VSCode.
3
+ A **fully config-driven**, cross-platform CLI for deploying accessibility skills and MCP servers across Claude Code, Cursor, Codex, and VSCode.
4
+
5
+ **Add new skills or MCP servers without writing code** - just edit the JSON config and re-run.
4
6
 
5
7
  ## Install
6
8
 
@@ -61,17 +63,23 @@ All MCP servers are configured to run via `npx`, which means:
61
63
 
62
64
  This CLI automates the setup of accessibility tooling by:
63
65
 
64
- 1. **Installing skills from npm** - Downloads and installs 7 accessibility skill packages
65
- 2. **Configuring MCP servers** - Updates each IDE's MCP config to enable 5 accessibility-focused MCP servers:
66
- - **wcag** - WCAG 2.2 guidelines, success criteria, and techniques
67
- - **aria** - WAI-ARIA roles, states, properties, and patterns
68
- - **magentaa11y** - Component accessibility acceptance criteria
69
- - **a11y-personas** - Accessibility personas for diverse user needs
70
- - **arc-issues** - Format AxeCore violations into standardized issue templates
66
+ 1. **Installing skills from npm** - Downloads and installs accessibility skill packages (configurable in `config/a11y.json`)
67
+ 2. **Configuring MCP servers** - Updates each IDE's MCP config to enable accessibility-focused MCP servers (also configurable)
68
+
69
+ **Default configuration includes:**
70
+ - **7 accessibility skills** - Testing, remediation, validation, documentation, and orchestration
71
+ - **5 MCP servers**:
72
+ - **wcag** - WCAG 2.2 guidelines, success criteria, and techniques
73
+ - **aria** - WAI-ARIA roles, states, properties, and patterns
74
+ - **magentaa11y** - Component accessibility acceptance criteria
75
+ - **a11y-personas** - Accessibility personas for diverse user needs
76
+ - **arc-issues** - Format AxeCore violations into standardized issue templates
71
77
 
72
- ### Skills Installed
78
+ **Fully customizable** - add/remove skills or MCP servers by editing the config file.
73
79
 
74
- The following skill packages are installed from npm:
80
+ ### Skills Installed (Default)
81
+
82
+ The following skill packages are installed from npm by default. **Add your own by editing `config/a11y.json`**:
75
83
 
76
84
  | Skill | Package | Description |
77
85
  |-------|---------|-------------|
@@ -110,11 +118,58 @@ The generated MCP config looks like this:
110
118
 
111
119
  ## Configuration
112
120
 
113
- Edit `config/a11y.json` to customize the deployment:
121
+ The entire tool is **fully config-driven**. Edit `config/a11y.json` to customize everything without touching code:
122
+
123
+ ### Example: Adding a New Skill
124
+
125
+ Simply add an object to the `skills` array with a `name` (npm package) and `description`:
126
+
127
+ ```json
128
+ {
129
+ "skills": [
130
+ {
131
+ "name": "a11y-tester-skill",
132
+ "description": "Run accessibility tests"
133
+ },
134
+ {
135
+ "name": "your-custom-skill",
136
+ "description": "Your custom skill description"
137
+ }
138
+ ]
139
+ }
140
+ ```
141
+
142
+ ### Example: Adding a New MCP Server
143
+
144
+ Add an object to the `mcpServers` array with name, description, command, and args:
114
145
 
115
- - `skills` - Array of npm package names to install as skills
146
+ ```json
147
+ {
148
+ "mcpServers": [
149
+ {
150
+ "name": "wcag",
151
+ "description": "WCAG guidelines reference",
152
+ "command": "npx",
153
+ "args": ["-y", "wcag-mcp"]
154
+ },
155
+ {
156
+ "name": "your-custom-mcp",
157
+ "description": "Your custom MCP server",
158
+ "command": "npx",
159
+ "args": ["-y", "your-mcp-package"]
160
+ }
161
+ ]
162
+ }
163
+ ```
164
+
165
+ ### Config Structure
166
+
167
+ - `skills` - Array of skill objects with `name` (npm package) and `description`
116
168
  - `ideSkillsPaths` - IDE-specific skills directories (configurable per IDE)
117
- - `mcpServers` - MCP server definitions using npx
169
+ - `ideMcpPaths` - IDE-specific MCP config file paths
170
+ - `mcpServers` - MCP server definitions with name, description, command, and args
171
+
172
+ All changes take effect immediately - just re-run the CLI to deploy your updated config.
118
173
 
119
174
  ## Directory Structure
120
175
 
@@ -142,7 +197,9 @@ MCP configurations are written to each IDE's OS-specific config path:
142
197
  - **Windows**: `%APPDATA%\{IDE}\mcp.json`
143
198
  - **Linux**: `~/.config/{IDE}/mcp.json`
144
199
 
145
- ## MCP Servers Included
200
+ ## MCP Servers Included (Default)
201
+
202
+ **Add your own by editing `config/a11y.json`**:
146
203
 
147
204
  | Server | Package | Description |
148
205
  |--------|---------|-------------|
package/config/a11y.json CHANGED
@@ -1,66 +1,110 @@
1
1
  {
2
+ "skillsFolder": "a11y",
3
+ "readmeTemplate": "deploy-README.md",
2
4
  "skills": [
3
- "a11y-base-web-skill",
4
- "a11y-issue-writer-skill",
5
- "a11y-tester-skill",
6
- "a11y-remediator-skill",
7
- "a11y-validator-skill",
8
- "web-standards-skill",
9
- "a11y-audit-fix-agent-orchestrator-skill"
5
+ {
6
+ "name": "a11y-base-web-skill",
7
+ "description": "Core accessibility testing utilities"
8
+ },
9
+ {
10
+ "name": "a11y-issue-writer-skill",
11
+ "description": "Document accessibility issues"
12
+ },
13
+ {
14
+ "name": "a11y-tester-skill",
15
+ "description": "Run accessibility tests"
16
+ },
17
+ {
18
+ "name": "a11y-remediator-skill",
19
+ "description": "Fix accessibility issues"
20
+ },
21
+ {
22
+ "name": "a11y-validator-skill",
23
+ "description": "Validate accessibility compliance"
24
+ },
25
+ {
26
+ "name": "web-standards-skill",
27
+ "description": "Web standards reference"
28
+ },
29
+ {
30
+ "name": "a11y-audit-fix-agent-orchestrator-skill",
31
+ "description": "Orchestrate accessibility audits"
32
+ }
33
+ ],
34
+ "ides": [
35
+ {
36
+ "id": "claude",
37
+ "displayName": "Claude Code",
38
+ "mcpServerKey": "servers",
39
+ "skillsFolder": ".claude/skills",
40
+ "mcpConfigFile": ".claude/mcp.json"
41
+ },
42
+ {
43
+ "id": "cursor",
44
+ "displayName": "Cursor",
45
+ "mcpServerKey": "mcpServers",
46
+ "skillsFolder": ".cursor/skills",
47
+ "mcpConfigFile": ".cursor/mcp.json"
48
+ },
49
+ {
50
+ "id": "codex",
51
+ "displayName": "Codex",
52
+ "mcpServerKey": "servers",
53
+ "skillsFolder": ".codex/skills",
54
+ "mcpConfigFile": ".codex/mcp.json"
55
+ },
56
+ {
57
+ "id": "vscode",
58
+ "displayName": "VSCode",
59
+ "mcpServerKey": "servers",
60
+ "skillsFolder": ".github/skills",
61
+ "mcpConfigFile": ".github/mcp.json"
62
+ },
63
+ {
64
+ "id": "windsurf",
65
+ "displayName": "Windsurf",
66
+ "mcpServerKey": "servers",
67
+ "skillsFolder": ".codeium/windsurf/skills",
68
+ "mcpConfigFile": ".codeium/windsurf/mcp_config.json"
69
+ },
70
+ {
71
+ "id": "factory",
72
+ "displayName": "Factory",
73
+ "mcpServerKey": "mcpServers",
74
+ "skillsFolder": ".factory/skills",
75
+ "mcpConfigFile": ".factory/mcp.json"
76
+ }
10
77
  ],
11
- "ideSkillsPaths": {
12
- "claude": ".claude/skills",
13
- "cursor": ".cursor/skills",
14
- "codex": ".codex/skills",
15
- "vscode": ".github/skills",
16
- "local": ".github/skills"
17
- },
18
- "ideMcpPaths": {
19
- "claude": ".claude/mcp.json",
20
- "cursor": ".cursor/mcp.json",
21
- "codex": ".codex/mcp.json",
22
- "vscode": ".github/mcp.json"
23
- },
24
78
  "mcpServers": [
25
79
  {
26
80
  "name": "wcag",
81
+ "description": "WCAG guidelines reference",
27
82
  "command": "npx",
28
- "args": [
29
- "-y",
30
- "wcag-mcp"
31
- ]
83
+ "args": ["-y", "wcag-mcp"]
32
84
  },
33
85
  {
34
86
  "name": "aria",
87
+ "description": "ARIA specification reference",
35
88
  "command": "npx",
36
- "args": [
37
- "-y",
38
- "aria-mcp"
39
- ]
89
+ "args": ["-y", "aria-mcp"]
40
90
  },
41
91
  {
42
92
  "name": "magentaa11y",
93
+ "description": "MagentaA11y accessibility acceptance criteria tool",
43
94
  "command": "npx",
44
- "args": [
45
- "-y",
46
- "magentaa11y-mcp"
47
- ]
95
+ "args": ["-y", "magentaa11y-mcp"]
48
96
  },
49
97
  {
50
98
  "name": "a11y-personas",
99
+ "description": "Accessibility personas and user scenarios",
51
100
  "command": "npx",
52
- "args": [
53
- "-y",
54
- "a11y-personas-mcp"
55
- ]
101
+ "args": ["-y", "a11y-personas-mcp"]
56
102
  },
57
103
  {
58
104
  "name": "arc-issues",
105
+ "description": "Pre-formatted a11y issue templates",
59
106
  "command": "npx",
60
- "args": [
61
- "-y",
62
- "arc-issues-mcp"
63
- ]
107
+ "args": ["-y", "arc-issues-mcp"]
64
108
  }
65
109
  ]
66
- }
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a11y-devkit-deploy",
3
- "version": "0.6.5",
3
+ "version": "0.7.1",
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
@@ -23,29 +23,15 @@ async function loadPackageJson() {
23
23
  return JSON.parse(raw);
24
24
  }
25
25
 
26
- const skillDescriptions = {
27
- "a11y-base-web-skill": "Core accessibility testing utilities",
28
- "a11y-issue-writer-skill": "Document accessibility issues",
29
- "a11y-tester-skill": "Run accessibility tests",
30
- "a11y-remediator-skill": "Fix accessibility issues",
31
- "a11y-validator-skill": "Validate accessibility compliance",
32
- "web-standards-skill": "Web standards reference",
33
- "a11y-audit-fix-agent-orchestrator-skill": "Orchestrate accessibility audits"
34
- };
35
-
36
- const mcpDescriptions = {
37
- "wcag": "WCAG guidelines reference",
38
- "aria": "ARIA specification reference",
39
- "magentaa11y": "MagentaA11y accessibility acceptance criteria tool",
40
- "a11y-personas": "Accessibility personas and user scenarios",
41
- "arc-issues": "Pre-formatted a11y issue templates"
42
- };
43
-
44
26
  function parseArgs(argv) {
45
27
  const args = new Set(argv.slice(2));
46
28
  return {
47
29
  autoYes: args.has("--yes") || args.has("-y"),
48
- scope: args.has("--global") ? "global" : args.has("--local") ? "local" : null
30
+ scope: args.has("--global")
31
+ ? "global"
32
+ : args.has("--local")
33
+ ? "local"
34
+ : null,
49
35
  };
50
36
  }
51
37
 
@@ -61,35 +47,40 @@ async function run() {
61
47
  const platformInfo = getPlatform();
62
48
  const config = await loadConfig();
63
49
  const pkg = await loadPackageJson();
64
- const idePaths = getIdePaths(projectRoot, platformInfo, config.ideSkillsPaths, config.ideMcpPaths);
50
+ const idePaths = getIdePaths(projectRoot, platformInfo, config.ides);
65
51
  const args = parseArgs(process.argv);
66
52
 
67
- header(`A11y Devkit Deploy v${pkg.version}`, "Install skills + MCP servers across IDEs");
53
+ header(
54
+ `A11y Devkit Deploy v${pkg.version}`,
55
+ "Install skills + MCP servers across IDEs",
56
+ );
68
57
  info(`Detected OS: ${formatOs(platformInfo)}`);
69
58
 
70
59
  console.log("\nSkills to install:");
71
60
  config.skills.forEach((skill) => {
72
- const description = skillDescriptions[skill] || "No description";
73
- console.log(`${skill} - ${description}`);
61
+ const name = typeof skill === "string" ? skill : skill.name;
62
+ const description =
63
+ typeof skill === "string"
64
+ ? "No description"
65
+ : skill.description || "No description";
66
+ console.log(`- ${name}: ${description}`);
74
67
  });
75
68
 
76
69
  console.log("\nMCP Servers to install:");
77
70
  config.mcpServers.forEach((server) => {
78
- const description = mcpDescriptions[server.name] || "No description";
71
+ const description = server.description || "No description";
79
72
  console.log(`${server.name} - ${description}`);
80
73
  });
81
74
  console.log("");
82
75
 
83
- const ideChoices = [
84
- { title: "Claude Code", value: "claude" },
85
- { title: "Cursor", value: "cursor" },
86
- { title: "Codex", value: "codex" },
87
- { title: "VSCode", value: "vscode" }
88
- ];
76
+ const ideChoices = config.ides.map((ide) => ({
77
+ title: ide.displayName,
78
+ value: ide.id,
79
+ }));
89
80
 
90
81
  let scope = args.scope;
91
82
  let mcpScope = null;
92
- let ideSelection = ["claude", "cursor", "codex", "vscode"];
83
+ let ideSelection = config.ides.map((ide) => ide.id);
93
84
  let installSkills = true;
94
85
 
95
86
  if (!args.autoYes) {
@@ -100,10 +91,13 @@ async function run() {
100
91
  name: "scope",
101
92
  message: "Install skills + repo locally or globally?",
102
93
  choices: [
103
- { title: `Local to this project (${formatPath(projectRoot)})`, value: "local" },
104
- { title: "Global for this user", value: "global" }
94
+ {
95
+ title: `Local to this project (${formatPath(projectRoot)})`,
96
+ value: "local",
97
+ },
98
+ { title: "Global for this user", value: "global" },
105
99
  ],
106
- initial: 0
100
+ initial: 0,
107
101
  },
108
102
  {
109
103
  type: "select",
@@ -113,22 +107,23 @@ async function run() {
113
107
  {
114
108
  title: `Local to this project (${formatPath(projectRoot)})`,
115
109
  value: "local",
116
- description: "Write to project-level IDE config folders (version-controllable)"
110
+ description:
111
+ "Write to project-level IDE config folders (version-controllable)",
117
112
  },
118
113
  {
119
114
  title: "Global for this user",
120
115
  value: "global",
121
- description: "Write to user-level IDE config folders"
122
- }
116
+ description: "Write to user-level IDE config folders",
117
+ },
123
118
  ],
124
- initial: 0
119
+ initial: 0,
125
120
  },
126
121
  {
127
122
  type: "multiselect",
128
123
  name: "ides",
129
124
  message: "Configure MCP for which IDEs?",
130
125
  choices: ideChoices,
131
- initial: ideChoices.map((_, index) => index)
126
+ initial: ideChoices.map((_, index) => index),
132
127
  },
133
128
  {
134
129
  type: "toggle",
@@ -136,15 +131,15 @@ async function run() {
136
131
  message: "Install skills into IDE skills folders?",
137
132
  active: "yes",
138
133
  inactive: "no",
139
- initial: true
140
- }
134
+ initial: true,
135
+ },
141
136
  ],
142
137
  {
143
138
  onCancel: () => {
144
139
  warn("Setup cancelled.");
145
140
  process.exit(0);
146
- }
147
- }
141
+ },
142
+ },
148
143
  );
149
144
 
150
145
  scope = scope || response.scope;
@@ -175,12 +170,24 @@ async function run() {
175
170
  const skillsSpinner = startSpinner("Installing skills from npm...");
176
171
 
177
172
  try {
178
- const skillTargets = scope === "local"
179
- ? ideSelection.map((ide) => idePaths[ide].localSkillsDir)
180
- : ideSelection.map((ide) => idePaths[ide].skillsDir);
181
-
182
- const result = await installSkillsFromNpm(config.skills, skillTargets, tempDir);
183
- skillsSpinner.succeed(`${result.installed} skills installed to ${skillTargets.length} IDE location(s).`);
173
+ const skillTargets =
174
+ scope === "local"
175
+ ? ideSelection.map((ide) => idePaths[ide].localSkillsDir)
176
+ : ideSelection.map((ide) => idePaths[ide].skillsDir);
177
+
178
+ const skillNames = config.skills.map((skill) =>
179
+ typeof skill === "string" ? skill : skill.name,
180
+ );
181
+ const result = await installSkillsFromNpm(
182
+ skillNames,
183
+ skillTargets,
184
+ tempDir,
185
+ config.skillsFolder,
186
+ config.readmeTemplate,
187
+ );
188
+ skillsSpinner.succeed(
189
+ `${result.installed} skills installed to ${skillTargets.length} IDE location(s).`,
190
+ );
184
191
  } catch (error) {
185
192
  skillsSpinner.fail(`Failed to install skills: ${error.message}`);
186
193
  }
@@ -190,19 +197,22 @@ async function run() {
190
197
 
191
198
  // Configure MCP servers using npx (no local installation needed!)
192
199
  const mcpSpinner = startSpinner("Updating MCP configurations...");
193
- const mcpConfigPaths = mcpScope === "local"
194
- ? ideSelection.map((ide) => idePaths[ide].localMcpConfig)
195
- : ideSelection.map((ide) => idePaths[ide].mcpConfig);
200
+ const mcpConfigPaths =
201
+ mcpScope === "local"
202
+ ? ideSelection.map((ide) => idePaths[ide].localMcpConfig)
203
+ : ideSelection.map((ide) => idePaths[ide].mcpConfig);
196
204
 
197
205
  for (let i = 0; i < ideSelection.length; i++) {
198
206
  const ide = ideSelection[i];
199
207
  await installMcpConfig(
200
208
  mcpConfigPaths[i],
201
209
  config.mcpServers,
202
- idePaths[ide].mcpServerKey
210
+ idePaths[ide].mcpServerKey,
203
211
  );
204
212
  }
205
- mcpSpinner.succeed(`MCP configs updated for ${ideSelection.length} IDE(s) (${mcpScope} scope).`);
213
+ mcpSpinner.succeed(
214
+ `MCP configs updated for ${ideSelection.length} IDE(s) (${mcpScope} scope).`,
215
+ );
206
216
 
207
217
  // Clean up temporary directory
208
218
  const cleanupSpinner = startSpinner("Cleaning up temporary files...");
@@ -214,17 +224,19 @@ async function run() {
214
224
  info("MCP servers use npx - no local installation needed!");
215
225
  console.log("");
216
226
  success("Next Steps:");
217
- const skillsPath = scope === "local"
218
- ? `.claude/skills/README.md (or your IDE's equivalent)`
219
- : `~/.claude/skills/README.md (or your IDE's global skills directory)`;
227
+ const skillsFolderPath = config.skillsFolder ? `${config.skillsFolder}/` : "";
228
+ const skillsPath =
229
+ scope === "local"
230
+ ? `.claude/skills/${skillsFolderPath}README.md (or your IDE's equivalent)`
231
+ : `~/.claude/skills/${skillsFolderPath}README.md (or your IDE's global skills directory)`;
220
232
  info(`📖 Check ${skillsPath} for comprehensive usage guide`);
221
233
  info("✨ Includes 70+ example prompts for all skills and MCP servers");
222
- info("🚀 Start with the 'Getting Started' section for your first accessibility check");
234
+ info(
235
+ "🚀 Start with the 'Getting Started' section for your first accessibility check",
236
+ );
223
237
  console.log("");
224
238
  info("You can re-run this CLI any time to update skills and configs.");
225
239
  info("Documentation: https://github.com/joe-watkins/a11y-devkit#readme");
226
240
  }
227
241
 
228
- export {
229
- run
230
- };
242
+ export { run };
@@ -17,20 +17,32 @@ async function pathExists(target) {
17
17
 
18
18
  function run(command, args, options = {}) {
19
19
  return new Promise((resolve, reject) => {
20
- const child = spawn(command, args, { stdio: "pipe", shell: true, ...options });
20
+ const child = spawn(command, args, {
21
+ stdio: "pipe",
22
+ shell: true,
23
+ ...options,
24
+ });
21
25
  let stdout = "";
22
26
  let stderr = "";
23
-
24
- child.stdout?.on("data", (data) => { stdout += data; });
25
- child.stderr?.on("data", (data) => { stderr += data; });
26
-
27
+
28
+ child.stdout?.on("data", (data) => {
29
+ stdout += data;
30
+ });
31
+ child.stderr?.on("data", (data) => {
32
+ stderr += data;
33
+ });
34
+
27
35
  child.on("error", reject);
28
36
  child.on("close", (code) => {
29
37
  if (code === 0) {
30
38
  resolve({ stdout, stderr });
31
39
  return;
32
40
  }
33
- reject(new Error(`${command} ${args.join(" ")} failed with code ${code}: ${stderr}`));
41
+ reject(
42
+ new Error(
43
+ `${command} ${args.join(" ")} failed with code ${code}: ${stderr}`,
44
+ ),
45
+ );
34
46
  });
35
47
  });
36
48
  }
@@ -43,18 +55,26 @@ async function cleanupTemp(tempDir) {
43
55
 
44
56
  /**
45
57
  * Install skills from npm packages into IDE skills directories.
46
- *
58
+ *
47
59
  * 1. Creates temp directory with package.json listing skills as dependencies
48
60
  * 2. Runs npm install in temp directory
49
61
  * 3. Copies installed skill packages (SKILL.md files) to target directories
50
62
  * 4. Returns temp directory path for cleanup
51
- *
63
+ *
52
64
  * @param {string[]} skills - Array of npm package names
53
65
  * @param {string[]} targetDirs - Array of target directories to install skills to
54
66
  * @param {string} tempDir - Temporary directory for npm install
67
+ * @param {string} skillsFolder - Optional subfolder name to bundle skills (e.g., "a11y")
68
+ * @param {string} readmeTemplate - README template filename from templates folder
55
69
  * @returns {Promise<{installed: number, tempDir: string}>}
56
70
  */
57
- async function installSkillsFromNpm(skills, targetDirs, tempDir) {
71
+ async function installSkillsFromNpm(
72
+ skills,
73
+ targetDirs,
74
+ tempDir,
75
+ skillsFolder = null,
76
+ readmeTemplate = "deploy-README.md",
77
+ ) {
58
78
  // Create temp directory
59
79
  await fs.mkdir(tempDir, { recursive: true });
60
80
 
@@ -63,7 +83,7 @@ async function installSkillsFromNpm(skills, targetDirs, tempDir) {
63
83
  name: "a11y-skills-temp",
64
84
  version: "1.0.0",
65
85
  private: true,
66
- dependencies: {}
86
+ dependencies: {},
67
87
  };
68
88
 
69
89
  for (const skill of skills) {
@@ -72,7 +92,7 @@ async function installSkillsFromNpm(skills, targetDirs, tempDir) {
72
92
 
73
93
  await fs.writeFile(
74
94
  path.join(tempDir, "package.json"),
75
- JSON.stringify(packageJson, null, 2)
95
+ JSON.stringify(packageJson, null, 2),
76
96
  );
77
97
 
78
98
  // Run npm install
@@ -83,7 +103,12 @@ async function installSkillsFromNpm(skills, targetDirs, tempDir) {
83
103
  let installedCount = 0;
84
104
 
85
105
  for (const targetDir of targetDirs) {
86
- await fs.mkdir(targetDir, { recursive: true });
106
+ // Determine the actual skills directory (with or without bundle folder)
107
+ const skillsDir = skillsFolder
108
+ ? path.join(targetDir, skillsFolder)
109
+ : targetDir;
110
+
111
+ await fs.mkdir(skillsDir, { recursive: true });
87
112
 
88
113
  for (const skill of skills) {
89
114
  const skillPackageDir = path.join(nodeModulesDir, skill);
@@ -92,7 +117,7 @@ async function installSkillsFromNpm(skills, targetDirs, tempDir) {
92
117
  if (await pathExists(skillMdPath)) {
93
118
  // Create skill directory in target (use package name without -skill suffix)
94
119
  const skillDirName = skill.replace(/-skill$/, "");
95
- const targetSkillDir = path.join(targetDir, skillDirName);
120
+ const targetSkillDir = path.join(skillsDir, skillDirName);
96
121
  await fs.mkdir(targetSkillDir, { recursive: true });
97
122
 
98
123
  // Copy SKILL.md
@@ -102,20 +127,23 @@ async function installSkillsFromNpm(skills, targetDirs, tempDir) {
102
127
  }
103
128
 
104
129
  // Copy the comprehensive README template to the skills directory
105
- const readmeTemplatePath = path.join(__dirname, "..", "templates", "skills-README.md");
106
- const targetReadmePath = path.join(targetDir, "README.md");
130
+ const readmeTemplatePath = path.join(
131
+ __dirname,
132
+ "..",
133
+ "..",
134
+ "templates",
135
+ readmeTemplate,
136
+ );
137
+ const targetReadmePath = path.join(skillsDir, "a11y-devkit-README.md");
107
138
  if (await pathExists(readmeTemplatePath)) {
108
139
  await fs.copyFile(readmeTemplatePath, targetReadmePath);
109
140
  }
110
141
  }
111
142
 
112
- return {
113
- installed: installedCount / targetDirs.length,
114
- tempDir
143
+ return {
144
+ installed: installedCount / targetDirs.length,
145
+ tempDir,
115
146
  };
116
147
  }
117
148
 
118
- export {
119
- installSkillsFromNpm,
120
- cleanupTemp
121
- };
149
+ export { installSkillsFromNpm, cleanupTemp };
package/src/paths.js CHANGED
@@ -30,60 +30,26 @@ function getAppSupportDir(platformInfo = getPlatform()) {
30
30
  return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
31
31
  }
32
32
 
33
- function getIdePaths(projectRoot, platformInfo = getPlatform(), ideSkillsPaths = null, ideMcpPaths = null) {
34
- const appSupport = getAppSupportDir(platformInfo);
33
+ function getIdePaths(projectRoot, platformInfo = getPlatform(), ideConfigs = []) {
35
34
  const home = os.homedir();
35
+ const paths = {};
36
+
37
+ for (const ide of ideConfigs) {
38
+ // Use custom paths from config, or fall back to default pattern: ~/.{id}/
39
+ const skillsFolder = ide.skillsFolder || `.${ide.id}/skills`;
40
+ const mcpConfigFile = ide.mcpConfigFile || `.${ide.id}/mcp.json`;
41
+
42
+ paths[ide.id] = {
43
+ name: ide.displayName,
44
+ mcpConfig: path.join(home, mcpConfigFile),
45
+ localMcpConfig: path.join(projectRoot, mcpConfigFile),
46
+ mcpServerKey: ide.mcpServerKey,
47
+ skillsDir: path.join(home, skillsFolder),
48
+ localSkillsDir: path.join(projectRoot, skillsFolder)
49
+ };
50
+ }
36
51
 
37
- // Default paths if not provided via config
38
- const skillsPaths = ideSkillsPaths || {
39
- claude: ".claude/skills",
40
- cursor: ".cursor/skills",
41
- codex: ".codex/skills",
42
- vscode: ".vscode/skills",
43
- local: ".github/skills"
44
- };
45
-
46
- const mcpPaths = ideMcpPaths || {
47
- claude: ".claude/mcp.json",
48
- cursor: ".cursor/mcp.json",
49
- codex: ".codex/mcp.json",
50
- vscode: ".vscode/mcp.json"
51
- };
52
-
53
- return {
54
- claude: {
55
- name: "Claude Code",
56
- mcpConfig: path.join(appSupport, "Claude", "mcp.json"),
57
- localMcpConfig: path.join(projectRoot, mcpPaths.claude),
58
- mcpServerKey: "servers",
59
- skillsDir: path.join(home, skillsPaths.claude),
60
- localSkillsDir: path.join(projectRoot, skillsPaths.claude)
61
- },
62
- cursor: {
63
- name: "Cursor",
64
- mcpConfig: path.join(appSupport, "Cursor", "mcp.json"),
65
- localMcpConfig: path.join(projectRoot, mcpPaths.cursor),
66
- mcpServerKey: "mcpServers",
67
- skillsDir: path.join(home, skillsPaths.cursor),
68
- localSkillsDir: path.join(projectRoot, skillsPaths.cursor)
69
- },
70
- codex: {
71
- name: "Codex",
72
- mcpConfig: path.join(home, ".codex", "mcp.json"),
73
- localMcpConfig: path.join(projectRoot, mcpPaths.codex),
74
- mcpServerKey: "servers",
75
- skillsDir: path.join(home, skillsPaths.codex),
76
- localSkillsDir: path.join(projectRoot, skillsPaths.codex)
77
- },
78
- vscode: {
79
- name: "VSCode",
80
- mcpConfig: path.join(appSupport, "Code", "User", "mcp.json"),
81
- localMcpConfig: path.join(projectRoot, mcpPaths.vscode),
82
- mcpServerKey: "servers",
83
- skillsDir: path.join(home, skillsPaths.vscode),
84
- localSkillsDir: path.join(projectRoot, skillsPaths.vscode)
85
- }
86
- };
52
+ return paths;
87
53
  }
88
54
 
89
55
  export {