a11y-devkit-deploy 0.4.1 → 0.6.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 CHANGED
@@ -1,101 +1,117 @@
1
- # A11y Devkit Deploy
2
-
3
- A cross-platform CLI for deploying accessibility skills and MCP servers across Claude Code, Cursor, Codex, and VSCode. Automatically clones the a11y-skills repo and all required MCP server repositories.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install -g a11y-devkit-deploy
9
- # or
10
- npx a11y-devkit-deploy
11
- ```
12
-
13
- ## Usage
14
-
15
- ```bash
16
- a11y-devkit-deploy
17
- ```
18
-
19
- ### Flags
20
-
21
- - `--local` / `--global`: Skip the scope prompt.
22
- - `--yes`: Use defaults (local scope, all IDEs, install skills).
23
-
24
- ## What It Does
25
-
26
- This CLI automates the setup of accessibility tooling by:
27
-
28
- 1. **Cloning the a11y-skills repository** - Contains IDE skills for accessibility workflows
29
- 2. **Cloning and building MCP server repositories** - Installs 5 accessibility-focused MCP servers:
30
- - **wcag-mcp** - WCAG 2.2 guidelines, success criteria, and techniques
31
- - **aria-mcp** - WAI-ARIA roles, states, properties, and patterns
32
- - **magentaa11y** - Component accessibility acceptance criteria
33
- - **a11y-personas-mcp** - Accessibility personas for diverse user needs
34
- - **a11y-issues-template-mcp** - Format AxeCore violations into standardized issue templates
35
- 3. **Installing skills** - Copies skills to IDE-specific directories based on scope (local/global)
36
- 4. **Installing MCP servers** - Copies built MCP servers to home directory:
37
- - Single IDE: `~/.{ide}/mcp/servers/` (e.g., `~/.claude/mcp/servers/`)
38
- - Multiple IDEs: `~/.mcp/servers/` (shared location)
39
- 5. **Configuring MCP servers** - Updates each IDE's MCP config to enable the accessibility tools
40
- 6. **Cleanup** - Removes temporary build directory after installation
41
-
42
- ## Configuration
43
-
44
- Edit `config/a11y.json` to customize the deployment:
45
-
46
- - `repo.url` - Main skills repository to clone
47
- - `mcpRepos` - Array of MCP repositories to clone and build
48
- - `skillsSearchPaths` - Directories to search for skills in the cloned repo
49
- - `ideSkillsPaths` - IDE-specific skills directories (configurable per IDE)
50
- - `mcpServers` - MCP server definitions with placeholders:
51
- - `{mcpRepoDir}` - Path to the MCP servers directory (e.g., `~/.mcp/servers/` or `~/.claude/mcp/servers/`)
52
-
53
- ## Directory Structure
54
-
55
- ### Local Install (Project-Specific)
56
- ```
57
- your-project/
58
- ├── .claude/skills/ # Skills copied to Claude Code (if selected)
59
- ├── .cursor/skills/ # Skills copied to Cursor (if selected)
60
- ├── .codex/skills/ # Skills copied to Codex (if selected)
61
- └── .github/skills/ # Skills copied here for version control
62
- ```
63
-
64
- ### Global Install (User-Wide)
65
- ```
66
- ~/.claude/skills/ # Claude Code skills
67
- ~/.cursor/skills/ # Cursor skills
68
- ~/.codex/skills/ # Codex skills
69
- ~/.vscode/skills/ # VSCode skills
70
- ```
71
-
72
- ### MCP Server Locations
73
- MCP servers are always installed to the home directory:
74
-
75
- **Single IDE Selection:**
76
- ```
77
- ~/.claude/mcp/servers/ # If only Claude Code is selected
78
- │ ├── wcag-mcp/
79
- │ ├── aria-mcp/
80
- │ ├── magentaa11y-mcp/
81
- │ ├── a11y-personas-mcp/
82
- │ └── a11y-issues-template-mcp/
83
- ```
84
-
85
- **Multiple IDE Selection:**
86
- ```
87
- ~/.mcp/servers/ # Shared location for all selected IDEs
88
- ├── wcag-mcp/
89
- ├── aria-mcp/
90
- ├── magentaa11y-mcp/
91
- │ ├── a11y-personas-mcp/
92
- │ └── a11y-issues-template-mcp/
93
- ```
94
-
95
- MCP configurations are written to each IDE's OS-specific config path:
96
- - **macOS**: `~/Library/Application Support/{IDE}/mcp.json`
97
- - **Windows**: `%APPDATA%\{IDE}\mcp.json`
98
- - **Linux**: `~/.config/{IDE}/mcp.json`
99
-
100
- ### Temporary Build Directory
101
- During installation, repos are cloned and built in a temporary directory (OS temp folder) which is automatically cleaned up after completion.
1
+ # A11y Devkit Deploy
2
+
3
+ A cross-platform CLI for deploying accessibility skills and MCP servers across Claude Code, Cursor, Codex, and VSCode.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g a11y-devkit-deploy
9
+ # or
10
+ npx a11y-devkit-deploy
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```bash
16
+ a11y-devkit-deploy
17
+ ```
18
+
19
+ ### Flags
20
+
21
+ - `--local` / `--global`: Skip the scope prompt.
22
+ - `--yes`: Use defaults (local scope, all IDEs, install skills).
23
+
24
+ ## What It Does
25
+
26
+ This CLI automates the setup of accessibility tooling by:
27
+
28
+ 1. **Installing skills from npm** - Downloads and installs 7 accessibility skill packages
29
+ 2. **Configuring MCP servers** - Updates each IDE's MCP config to enable 5 accessibility-focused MCP servers:
30
+ - **wcag** - WCAG 2.2 guidelines, success criteria, and techniques
31
+ - **aria** - WAI-ARIA roles, states, properties, and patterns
32
+ - **magentaa11y** - Component accessibility acceptance criteria
33
+ - **a11y-personas** - Accessibility personas for diverse user needs
34
+ - **arc-issues** - Format AxeCore violations into standardized issue templates
35
+
36
+ ### Skills Installed
37
+
38
+ The following skill packages are installed from npm:
39
+
40
+ | Skill | Package | Description |
41
+ |-------|---------|-------------|
42
+ | a11y-base-web | `a11y-base-web-skill` | Foundational accessibility patterns for web code |
43
+ | a11y-issue-writer | `a11y-issue-writer-skill` | Write clear accessibility issue reports |
44
+ | a11y-tester | `a11y-tester-skill` | Automated testing with axe-core and Playwright |
45
+ | a11y-remediator | `a11y-remediator-skill` | Fix accessibility issues in code |
46
+ | a11y-validator | `a11y-validator-skill` | Validate accessibility compliance |
47
+ | web-standards | `web-standards-skill` | Web standards and best practices |
48
+ | a11y-audit-fix-agent-orchestrator | `a11y-audit-fix-agent-orchestrator-skill` | Orchestrate full audit and fix workflows |
49
+
50
+ ### No Local MCP Installation Required!
51
+
52
+ MCP servers are configured to use `npx`, which means:
53
+ - **No cloning** of MCP server repositories
54
+ - **No building** or `npm install` steps
55
+ - **No disk space** used for local copies
56
+ - **Always up-to-date** - npx fetches the latest version automatically
57
+
58
+ The generated MCP config looks like this:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "wcag": {
64
+ "command": "npx",
65
+ "args": ["-y", "wcag-mcp"]
66
+ },
67
+ "aria": {
68
+ "command": "npx",
69
+ "args": ["-y", "aria-mcp"]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ Edit `config/a11y.json` to customize the deployment:
78
+
79
+ - `skills` - Array of npm package names to install as skills
80
+ - `ideSkillsPaths` - IDE-specific skills directories (configurable per IDE)
81
+ - `mcpServers` - MCP server definitions using npx
82
+
83
+ ## Directory Structure
84
+
85
+ ### Local Install (Project-Specific)
86
+ ```
87
+ your-project/
88
+ ├── .claude/skills/ # Skills copied to Claude Code (if selected)
89
+ ├── .cursor/skills/ # Skills copied to Cursor (if selected)
90
+ ├── .codex/skills/ # Skills copied to Codex (if selected)
91
+ └── .github/skills/ # Skills copied here for version control
92
+ ```
93
+
94
+ ### Global Install (User-Wide)
95
+ ```
96
+ ~/.claude/skills/ # Claude Code skills
97
+ ~/.cursor/skills/ # Cursor skills
98
+ ~/.codex/skills/ # Codex skills
99
+ ~/.vscode/skills/ # VSCode skills
100
+ ```
101
+
102
+ ### MCP Configuration Locations
103
+
104
+ MCP configurations are written to each IDE's OS-specific config path:
105
+ - **macOS**: `~/Library/Application Support/{IDE}/mcp.json`
106
+ - **Windows**: `%APPDATA%\{IDE}\mcp.json`
107
+ - **Linux**: `~/.config/{IDE}/mcp.json`
108
+
109
+ ## MCP Servers Included
110
+
111
+ | Server | Package | Description |
112
+ |--------|---------|-------------|
113
+ | wcag | `wcag-mcp` | WCAG 2.2 guidelines, success criteria, techniques |
114
+ | aria | `aria-mcp` | WAI-ARIA roles, states, properties |
115
+ | magentaa11y | `magentaa11y-mcp` | Component accessibility acceptance criteria |
116
+ | a11y-personas | `a11y-personas-mcp` | Accessibility personas for diverse users |
117
+ | arc-issues | `arc-issues-mcp` | AxeCore violation formatting |
@@ -1,9 +1,9 @@
1
- #!/usr/bin/env node
2
-
3
- import { run } from "../src/cli.js";
4
-
5
- run().catch((error) => {
6
- const message = error instanceof Error ? error.message : String(error);
7
- console.error(`\n[Error] ${message}`);
8
- process.exitCode = 1;
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from "../src/cli.js";
4
+
5
+ run().catch((error) => {
6
+ const message = error instanceof Error ? error.message : String(error);
7
+ console.error(`\n[Error] ${message}`);
8
+ process.exitCode = 1;
9
9
  });
package/config/a11y.json CHANGED
@@ -1,40 +1,12 @@
1
1
  {
2
- "repo": {
3
- "url": "https://github.com/joe-watkins/a11y-skills",
4
- "dirName": "a11y-skills"
5
- },
6
- "mcpRepos": [
7
- {
8
- "url": "https://github.com/joe-watkins/wcag-mcp",
9
- "dirName": "wcag-mcp",
10
- "buildCommands": ["npm install", "npm run build"]
11
- },
12
- {
13
- "url": "https://github.com/joe-watkins/aria-mcp",
14
- "dirName": "aria-mcp",
15
- "buildCommands": ["npm install"]
16
- },
17
- {
18
- "url": "https://github.com/joe-watkins/magentaa11y-mcp",
19
- "dirName": "magentaa11y-mcp",
20
- "buildCommands": ["npm install", "npm run update-content"]
21
- },
22
- {
23
- "url": "https://github.com/joe-watkins/a11y-personas-mcp",
24
- "dirName": "a11y-personas-mcp",
25
- "buildCommands": ["npm install", "npm run build"]
26
- },
27
- {
28
- "url": "https://github.com/joe-watkins/accessibility-issues-template-mcp",
29
- "dirName": "a11y-issues-template-mcp",
30
- "buildCommands": ["npm install", "npm run build"]
31
- }
32
- ],
33
- "skillsSearchPaths": [
34
- ".",
35
- "skills",
36
- ".github/skills",
37
- ".codex/skills"
2
+ "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"
38
10
  ],
39
11
  "ideSkillsPaths": {
40
12
  "claude": ".claude/skills",
@@ -45,29 +17,44 @@
45
17
  },
46
18
  "mcpServers": [
47
19
  {
48
- "name": "wcag-mcp",
49
- "command": "node",
50
- "args": ["{mcpRepoDir}/wcag-mcp/src/index.js"]
20
+ "name": "wcag",
21
+ "command": "npx",
22
+ "args": [
23
+ "-y",
24
+ "wcag-mcp"
25
+ ]
51
26
  },
52
27
  {
53
- "name": "aria-mcp",
54
- "command": "node",
55
- "args": ["{mcpRepoDir}/aria-mcp/src/index.js"]
28
+ "name": "aria",
29
+ "command": "npx",
30
+ "args": [
31
+ "-y",
32
+ "aria-mcp"
33
+ ]
56
34
  },
57
35
  {
58
36
  "name": "magentaa11y",
59
- "command": "node",
60
- "args": ["{mcpRepoDir}/magentaa11y-mcp/src/index.js"]
61
- },
62
- {
63
- "name": "a11y-personas-mcp",
64
- "command": "node",
65
- "args": ["{mcpRepoDir}/a11y-personas-mcp/src/index.js"]
66
- },
67
- {
68
- "name": "a11y-issues-template-mcp",
69
- "command": "node",
70
- "args": ["{mcpRepoDir}/accessibility-issues-template-mcp/build/index.js"]
37
+ "command": "npx",
38
+ "args": [
39
+ "-y",
40
+ "magentaa11y-mcp"
41
+ ]
42
+ },
43
+ {
44
+ "name": "a11y-personas",
45
+ "command": "npx",
46
+ "args": [
47
+ "-y",
48
+ "a11y-personas-mcp"
49
+ ]
50
+ },
51
+ {
52
+ "name": "arc-issues",
53
+ "command": "npx",
54
+ "args": [
55
+ "-y",
56
+ "arc-issues-mcp"
57
+ ]
71
58
  }
72
59
  ]
73
60
  }
package/package.json CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "a11y-devkit-deploy",
3
- "version": "0.4.1",
4
- "description": "CLI to deploy a11y skills and MCP servers across IDEs",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "src/cli.js",
8
- "bin": {
9
- "a11y-devkit-deploy": "bin/a11y-skills.js"
10
- },
11
- "files": [
12
- "bin",
13
- "src",
14
- "config",
15
- "README.md"
16
- ],
17
- "keywords": [
18
- "accessibility",
19
- "a11y",
20
- "mcp",
21
- "model-context-protocol",
22
- "copilot",
23
- "skills",
24
- "wcag",
25
- "aria",
26
- "cli"
27
- ],
28
- "repository": {
29
- "type": "git",
30
- "url": "https://github.com/joe-watkins/a11y-devkit.git"
31
- },
32
- "bugs": {
33
- "url": "https://github.com/joe-watkins/a11y-devkit/issues"
34
- },
35
- "homepage": "https://github.com/joe-watkins/a11y-devkit#readme",
36
- "author": "Joe Watkins",
37
- "engines": {
38
- "node": ">=18"
39
- },
40
- "scripts": {
41
- "start": "node bin/a11y-skills.js"
42
- },
43
- "dependencies": {
44
- "boxen": "^5.1.2",
45
- "ora": "^6.3.1",
46
- "picocolors": "^1.1.0",
47
- "prompts": "^2.4.2"
48
- }
1
+ {
2
+ "name": "a11y-devkit-deploy",
3
+ "version": "0.6.0",
4
+ "description": "CLI to deploy a11y skills and MCP servers across IDEs",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "src/cli.js",
8
+ "bin": {
9
+ "a11y-devkit-deploy": "bin/a11y-skills.js"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "src",
14
+ "config",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "accessibility",
19
+ "a11y",
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "copilot",
23
+ "skills",
24
+ "wcag",
25
+ "aria",
26
+ "cli"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/joe-watkins/a11y-devkit.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/joe-watkins/a11y-devkit/issues"
34
+ },
35
+ "homepage": "https://github.com/joe-watkins/a11y-devkit#readme",
36
+ "author": "Joe Watkins",
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "scripts": {
41
+ "start": "node bin/a11y-skills.js"
42
+ },
43
+ "dependencies": {
44
+ "boxen": "^5.1.2",
45
+ "ora": "^6.3.1",
46
+ "picocolors": "^1.1.0",
47
+ "prompts": "^2.4.2"
48
+ }
49
49
  }
package/src/cli.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import fs from "fs/promises";
2
- import os from "os";
3
2
  import path from "path";
4
3
  import { fileURLToPath } from "url";
5
4
  import prompts from "prompts";
6
5
 
7
6
  import { header, info, warn, success, startSpinner, formatPath } from "./ui.js";
8
- import { getPlatform, getIdePaths, getMcpServerDir, getTempDir } from "./paths.js";
9
- import { ensureRepo, buildMcp, copyMcpServers, cleanupTemp } from "./installers/repo.js";
10
- import { findSkillsDir, copySkills } from "./installers/skills.js";
11
- import { resolveServers, installMcpConfig } from "./installers/mcp.js";
7
+ import { getPlatform, getIdePaths, getTempDir } from "./paths.js";
8
+ import { installSkillsFromNpm, cleanupTemp } from "./installers/skills.js";
9
+ import { installMcpConfig } from "./installers/mcp.js";
12
10
 
13
11
  const __filename = fileURLToPath(import.meta.url);
14
12
  const __dirname = path.dirname(__filename);
@@ -40,7 +38,6 @@ async function run() {
40
38
  const config = await loadConfig();
41
39
  const idePaths = getIdePaths(projectRoot, platformInfo, config.ideSkillsPaths);
42
40
  const args = parseArgs(process.argv);
43
- const homeDir = os.homedir();
44
41
 
45
42
  header("A11y Devkit Deploy", "Install skills + MCP servers across IDEs");
46
43
  info(`Detected OS: ${formatOs(platformInfo)}`);
@@ -109,74 +106,30 @@ async function run() {
109
106
 
110
107
  info(`Install scope: ${scope === "local" ? "Local" : "Global"}`);
111
108
 
112
- // Create temp directory for cloning and building
109
+ // Create temp directory for npm install
113
110
  const tempDir = path.join(getTempDir(), `.a11y-devkit-${Date.now()}`);
114
- const tempSkillsDir = path.join(tempDir, "skills");
115
- const tempMcpDir = path.join(tempDir, "mcp");
116
-
117
- // Determine MCP server destination based on IDE selection
118
- const mcpServerDir = getMcpServerDir(homeDir, ideSelection);
119
- info(`MCP servers: ${formatPath(mcpServerDir)}`);
120
-
121
- // Clone skills repo into temp
122
- const repoSpinner = startSpinner("Syncing a11y-skills repo...");
123
- const repoResult = await ensureRepo({
124
- url: config.repo.url,
125
- dir: tempSkillsDir
126
- });
127
- repoSpinner.succeed(`Repo ${repoResult.action}: ${formatPath(repoResult.dir)}`);
128
-
129
- // Clone and build MCP repos in temp
130
- if (config.mcpRepos && config.mcpRepos.length > 0) {
131
- const mcpSpinner = startSpinner(`Syncing ${config.mcpRepos.length} MCP repos...`);
132
- for (const mcpRepo of config.mcpRepos) {
133
- const mcpDir = path.join(tempMcpDir, mcpRepo.dirName);
134
- await ensureRepo({
135
- url: mcpRepo.url,
136
- dir: mcpDir
137
- });
138
-
139
- // Build MCP if build commands are specified
140
- if (mcpRepo.buildCommands) {
141
- mcpSpinner.text = `Building ${mcpRepo.dirName}...`;
142
- await buildMcp({
143
- dir: mcpDir,
144
- buildCommands: mcpRepo.buildCommands
145
- });
146
- }
147
- }
148
- mcpSpinner.succeed(`MCP repos synced and built in temp directory`);
149
- }
150
-
151
- // Copy MCP servers from temp to final location
152
- const copySpinner = startSpinner("Installing MCP servers...");
153
- await copyMcpServers(tempMcpDir, mcpServerDir);
154
- copySpinner.succeed(`MCP servers installed to ${formatPath(mcpServerDir)}`);
155
111
 
156
112
  if (installSkills) {
157
- const skillsSpinner = startSpinner("Installing skills to IDE folders...");
158
- const sourceDir = await findSkillsDir(tempSkillsDir, config.skillsSearchPaths);
159
- if (!sourceDir) {
160
- skillsSpinner.fail("No skills directory found in repo.");
161
- } else {
113
+ const skillsSpinner = startSpinner("Installing skills from npm...");
114
+
115
+ try {
162
116
  const skillTargets = scope === "local"
163
117
  ? ideSelection.map((ide) => idePaths[ide].localSkillsDir)
164
118
  : ideSelection.map((ide) => idePaths[ide].skillsDir);
165
119
 
166
- for (const target of skillTargets) {
167
- await copySkills(sourceDir, target);
168
- }
169
-
170
- skillsSpinner.succeed(`Skills installed to ${skillTargets.length} IDE location(s).`);
120
+ const result = await installSkillsFromNpm(config.skills, skillTargets, tempDir);
121
+ skillsSpinner.succeed(`${result.installed} skills installed to ${skillTargets.length} IDE location(s).`);
122
+ } catch (error) {
123
+ skillsSpinner.fail(`Failed to install skills: ${error.message}`);
171
124
  }
172
125
  } else {
173
126
  warn("Skipping skills install to IDE folders.");
174
127
  }
175
128
 
176
- const serverDefs = resolveServers(config.mcpServers, mcpServerDir, mcpServerDir);
129
+ // Configure MCP servers using npx (no local installation needed!)
177
130
  const mcpSpinner = startSpinner("Updating MCP configurations...");
178
131
  for (const ide of ideSelection) {
179
- await installMcpConfig(idePaths[ide].mcpConfig, serverDefs, idePaths[ide].mcpServerKey);
132
+ await installMcpConfig(idePaths[ide].mcpConfig, config.mcpServers, idePaths[ide].mcpServerKey);
180
133
  }
181
134
  mcpSpinner.succeed(`MCP configs updated for ${ideSelection.length} IDE(s).`);
182
135
 
@@ -186,9 +139,11 @@ async function run() {
186
139
  cleanupSpinner.succeed("Temporary files removed");
187
140
 
188
141
  success("All done. Your skills and MCP servers are ready.");
189
- info("You can re-run this CLI any time to update the repo and configs.");
142
+ info("Skills installed from npm packages.");
143
+ info("MCP servers use npx - no local installation needed!");
144
+ info("You can re-run this CLI any time to update skills and configs.");
190
145
  }
191
146
 
192
147
  export {
193
148
  run
194
- };
149
+ };
@@ -25,18 +25,6 @@ async function loadJson(filePath) {
25
25
  }
26
26
  }
27
27
 
28
- function resolveServers(servers, repoDir, mcpRepoDir) {
29
- return servers.map((server) => {
30
- const args = Array.isArray(server.args) ? server.args : [];
31
- return {
32
- ...server,
33
- args: args.map((value) =>
34
- value.replace("{repoDir}", repoDir).replace("{mcpRepoDir}", mcpRepoDir)
35
- )
36
- };
37
- });
38
- }
39
-
40
28
  function mergeServers(existing, incoming, serverKey = "servers") {
41
29
  const existingServers = existing[serverKey] && typeof existing[serverKey] === "object"
42
30
  ? existing[serverKey]
@@ -47,9 +35,7 @@ function mergeServers(existing, incoming, serverKey = "servers") {
47
35
  for (const server of incoming) {
48
36
  merged[serverKey][server.name] = {
49
37
  command: server.command,
50
- args: server.args || [],
51
- env: server.env || {},
52
- cwd: server.cwd
38
+ args: server.args || []
53
39
  };
54
40
  }
55
41
 
@@ -64,6 +50,5 @@ async function installMcpConfig(configPath, servers, serverKey = "servers") {
64
50
  }
65
51
 
66
52
  export {
67
- resolveServers,
68
53
  installMcpConfig
69
- };
54
+ };
@@ -1,31 +1,110 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
-
4
- async function pathExists(target) {
5
- try {
6
- await fs.access(target);
7
- return true;
8
- } catch {
9
- return false;
10
- }
11
- }
12
-
13
- async function findSkillsDir(repoDir, candidates) {
14
- for (const candidate of candidates) {
15
- const fullPath = path.join(repoDir, candidate);
16
- if (await pathExists(fullPath)) {
17
- return fullPath;
18
- }
19
- }
20
- return null;
21
- }
22
-
23
- async function copySkills(sourceDir, targetDir) {
24
- await fs.mkdir(targetDir, { recursive: true });
25
- await fs.cp(sourceDir, targetDir, { recursive: true, force: true });
26
- }
27
-
28
- export {
29
- findSkillsDir,
30
- copySkills
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { spawn } from "child_process";
4
+
5
+ async function pathExists(target) {
6
+ try {
7
+ await fs.access(target);
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ function run(command, args, options = {}) {
15
+ return new Promise((resolve, reject) => {
16
+ const child = spawn(command, args, { stdio: "pipe", ...options });
17
+ let stdout = "";
18
+ let stderr = "";
19
+
20
+ child.stdout?.on("data", (data) => { stdout += data; });
21
+ child.stderr?.on("data", (data) => { stderr += data; });
22
+
23
+ child.on("error", reject);
24
+ child.on("close", (code) => {
25
+ if (code === 0) {
26
+ resolve({ stdout, stderr });
27
+ return;
28
+ }
29
+ reject(new Error(`${command} ${args.join(" ")} failed with code ${code}: ${stderr}`));
30
+ });
31
+ });
32
+ }
33
+
34
+ async function cleanupTemp(tempDir) {
35
+ if (await pathExists(tempDir)) {
36
+ await fs.rm(tempDir, { recursive: true, force: true });
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Install skills from npm packages into IDE skills directories.
42
+ *
43
+ * 1. Creates temp directory with package.json listing skills as dependencies
44
+ * 2. Runs npm install in temp directory
45
+ * 3. Copies installed skill packages (SKILL.md files) to target directories
46
+ * 4. Returns temp directory path for cleanup
47
+ *
48
+ * @param {string[]} skills - Array of npm package names
49
+ * @param {string[]} targetDirs - Array of target directories to install skills to
50
+ * @param {string} tempDir - Temporary directory for npm install
51
+ * @returns {Promise<{installed: number, tempDir: string}>}
52
+ */
53
+ async function installSkillsFromNpm(skills, targetDirs, tempDir) {
54
+ // Create temp directory
55
+ await fs.mkdir(tempDir, { recursive: true });
56
+
57
+ // Create package.json with skills as dependencies
58
+ const packageJson = {
59
+ name: "a11y-skills-temp",
60
+ version: "1.0.0",
61
+ private: true,
62
+ dependencies: {}
63
+ };
64
+
65
+ for (const skill of skills) {
66
+ packageJson.dependencies[skill] = "latest";
67
+ }
68
+
69
+ await fs.writeFile(
70
+ path.join(tempDir, "package.json"),
71
+ JSON.stringify(packageJson, null, 2)
72
+ );
73
+
74
+ // Run npm install
75
+ await run("npm", ["install", "--production"], { cwd: tempDir });
76
+
77
+ // Copy SKILL.md files from installed packages to target directories
78
+ const nodeModulesDir = path.join(tempDir, "node_modules");
79
+ let installedCount = 0;
80
+
81
+ for (const targetDir of targetDirs) {
82
+ await fs.mkdir(targetDir, { recursive: true });
83
+
84
+ for (const skill of skills) {
85
+ const skillPackageDir = path.join(nodeModulesDir, skill);
86
+ const skillMdPath = path.join(skillPackageDir, "SKILL.md");
87
+
88
+ if (await pathExists(skillMdPath)) {
89
+ // Create skill directory in target (use package name without -skill suffix)
90
+ const skillDirName = skill.replace(/-skill$/, "");
91
+ const targetSkillDir = path.join(targetDir, skillDirName);
92
+ await fs.mkdir(targetSkillDir, { recursive: true });
93
+
94
+ // Copy SKILL.md
95
+ await fs.copyFile(skillMdPath, path.join(targetSkillDir, "SKILL.md"));
96
+ installedCount++;
97
+ }
98
+ }
99
+ }
100
+
101
+ return {
102
+ installed: installedCount / targetDirs.length,
103
+ tempDir
104
+ };
105
+ }
106
+
107
+ export {
108
+ installSkillsFromNpm,
109
+ cleanupTemp
31
110
  };
package/src/paths.js CHANGED
@@ -30,15 +30,6 @@ function getAppSupportDir(platformInfo = getPlatform()) {
30
30
  return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
31
31
  }
32
32
 
33
- function getMcpServerDir(home, ideSelection) {
34
- // Single IDE: use IDE-specific path (~/.claude/mcp/servers/)
35
- // Multiple IDEs: use shared path (~/.mcp/servers/)
36
- if (ideSelection.length === 1) {
37
- return path.join(home, `.${ideSelection[0]}`, "mcp", "servers");
38
- }
39
- return path.join(home, ".mcp", "servers");
40
- }
41
-
42
33
  function getIdePaths(projectRoot, platformInfo = getPlatform(), ideSkillsPaths = null) {
43
34
  const appSupport = getAppSupportDir(platformInfo);
44
35
  const home = os.homedir();
@@ -88,6 +79,5 @@ export {
88
79
  getPlatform,
89
80
  getAppSupportDir,
90
81
  getIdePaths,
91
- getMcpServerDir,
92
82
  getTempDir
93
83
  };
package/src/ui.js CHANGED
@@ -1,57 +1,57 @@
1
- import boxen from "boxen";
2
- import pc from "picocolors";
3
- import ora from "ora";
4
-
5
- const bullets = {
6
- info: pc.cyan("i"),
7
- warn: pc.yellow("!"),
8
- error: pc.red("x"),
9
- success: pc.green("ok")
10
- };
11
-
12
- function header(title, subtitle) {
13
- const line = subtitle ? `${pc.dim(subtitle)}` : "";
14
- const content = [pc.bold(title), line].filter(Boolean).join("\n");
15
- console.log(
16
- boxen(content, {
17
- padding: 1,
18
- margin: { top: 1, bottom: 1 },
19
- borderStyle: "round",
20
- borderColor: "cyan"
21
- })
22
- );
23
- }
24
-
25
- function info(message) {
26
- console.log(`${bullets.info} ${message}`);
27
- }
28
-
29
- function warn(message) {
30
- console.log(`${bullets.warn} ${message}`);
31
- }
32
-
33
- function success(message) {
34
- console.log(`${bullets.success} ${message}`);
35
- }
36
-
37
- function error(message) {
38
- console.log(`${bullets.error} ${message}`);
39
- }
40
-
41
- function startSpinner(text) {
42
- return ora({ text, spinner: "dots" }).start();
43
- }
44
-
45
- function formatPath(value) {
46
- return pc.dim(value);
47
- }
48
-
49
- export {
50
- header,
51
- info,
52
- warn,
53
- success,
54
- error,
55
- startSpinner,
56
- formatPath
1
+ import boxen from "boxen";
2
+ import pc from "picocolors";
3
+ import ora from "ora";
4
+
5
+ const bullets = {
6
+ info: pc.cyan("i"),
7
+ warn: pc.yellow("!"),
8
+ error: pc.red("x"),
9
+ success: pc.green("ok")
10
+ };
11
+
12
+ function header(title, subtitle) {
13
+ const line = subtitle ? `${pc.dim(subtitle)}` : "";
14
+ const content = [pc.bold(title), line].filter(Boolean).join("\n");
15
+ console.log(
16
+ boxen(content, {
17
+ padding: 1,
18
+ margin: { top: 1, bottom: 1 },
19
+ borderStyle: "round",
20
+ borderColor: "cyan"
21
+ })
22
+ );
23
+ }
24
+
25
+ function info(message) {
26
+ console.log(`${bullets.info} ${message}`);
27
+ }
28
+
29
+ function warn(message) {
30
+ console.log(`${bullets.warn} ${message}`);
31
+ }
32
+
33
+ function success(message) {
34
+ console.log(`${bullets.success} ${message}`);
35
+ }
36
+
37
+ function error(message) {
38
+ console.log(`${bullets.error} ${message}`);
39
+ }
40
+
41
+ function startSpinner(text) {
42
+ return ora({ text, spinner: "dots" }).start();
43
+ }
44
+
45
+ function formatPath(value) {
46
+ return pc.dim(value);
47
+ }
48
+
49
+ export {
50
+ header,
51
+ info,
52
+ warn,
53
+ success,
54
+ error,
55
+ startSpinner,
56
+ formatPath
57
57
  };
@@ -1,103 +0,0 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
- import { spawn } from "child_process";
4
-
5
- async function pathExists(target) {
6
- try {
7
- await fs.access(target);
8
- return true;
9
- } catch {
10
- return false;
11
- }
12
- }
13
-
14
- function run(command, args, options = {}) {
15
- return new Promise((resolve, reject) => {
16
- const child = spawn(command, args, { stdio: "inherit", ...options });
17
- child.on("error", reject);
18
- child.on("close", (code) => {
19
- if (code === 0) {
20
- resolve();
21
- return;
22
- }
23
- reject(new Error(`${command} ${args.join(" ")} failed with code ${code}`));
24
- });
25
- });
26
- }
27
-
28
- async function ensureRepo({ url, dir }) {
29
- const hasDir = await pathExists(dir);
30
- const gitDir = path.join(dir, ".git");
31
-
32
- if (hasDir) {
33
- const isGitRepo = await pathExists(gitDir);
34
- if (!isGitRepo) {
35
- throw new Error(`Target exists but is not a git repo: ${dir}`);
36
- }
37
-
38
- await run("git", ["-C", dir, "pull", "--ff-only"]);
39
- return { action: "updated", dir };
40
- }
41
-
42
- await run("git", ["clone", "--depth", "1", url, dir]);
43
- return { action: "cloned", dir };
44
- }
45
-
46
- async function buildMcp({ dir, buildCommands }) {
47
- if (!buildCommands || buildCommands.length === 0) {
48
- return;
49
- }
50
-
51
- for (const command of buildCommands) {
52
- const parts = command.split(" ");
53
- const cmd = parts[0];
54
- const args = parts.slice(1);
55
- await run(cmd, args, { cwd: dir, shell: true });
56
- }
57
- }
58
-
59
- async function copyDirectory(source, dest) {
60
- await fs.mkdir(dest, { recursive: true });
61
- const entries = await fs.readdir(source, { withFileTypes: true });
62
-
63
- for (const entry of entries) {
64
- const srcPath = path.join(source, entry.name);
65
- const destPath = path.join(dest, entry.name);
66
-
67
- if (entry.isDirectory()) {
68
- await copyDirectory(srcPath, destPath);
69
- } else {
70
- await fs.copyFile(srcPath, destPath);
71
- }
72
- }
73
- }
74
-
75
- async function copyMcpServers(tempMcpDir, finalMcpDir) {
76
- if (!(await pathExists(tempMcpDir))) {
77
- return;
78
- }
79
-
80
- await fs.mkdir(finalMcpDir, { recursive: true });
81
- const entries = await fs.readdir(tempMcpDir, { withFileTypes: true });
82
-
83
- for (const entry of entries) {
84
- if (entry.isDirectory()) {
85
- const srcPath = path.join(tempMcpDir, entry.name);
86
- const destPath = path.join(finalMcpDir, entry.name);
87
- await copyDirectory(srcPath, destPath);
88
- }
89
- }
90
- }
91
-
92
- async function cleanupTemp(tempDir) {
93
- if (await pathExists(tempDir)) {
94
- await fs.rm(tempDir, { recursive: true, force: true });
95
- }
96
- }
97
-
98
- export {
99
- ensureRepo,
100
- buildMcp,
101
- copyMcpServers,
102
- cleanupTemp
103
- };