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 +117 -101
- package/bin/a11y-skills.js +8 -8
- package/config/a11y.json +41 -54
- package/package.json +48 -48
- package/src/cli.js +17 -62
- package/src/installers/mcp.js +2 -17
- package/src/installers/skills.js +109 -30
- package/src/paths.js +0 -10
- package/src/ui.js +56 -56
- package/src/installers/repo.js +0 -103
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.
|
|
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. **
|
|
29
|
-
2. **
|
|
30
|
-
- **wcag
|
|
31
|
-
- **aria
|
|
32
|
-
- **magentaa11y** - Component accessibility acceptance criteria
|
|
33
|
-
- **a11y-personas
|
|
34
|
-
- **
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- `
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 |
|
package/bin/a11y-skills.js
CHANGED
|
@@ -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
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
49
|
-
"command": "
|
|
50
|
-
"args": [
|
|
20
|
+
"name": "wcag",
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": [
|
|
23
|
+
"-y",
|
|
24
|
+
"wcag-mcp"
|
|
25
|
+
]
|
|
51
26
|
},
|
|
52
27
|
{
|
|
53
|
-
"name": "aria
|
|
54
|
-
"command": "
|
|
55
|
-
"args": [
|
|
28
|
+
"name": "aria",
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": [
|
|
31
|
+
"-y",
|
|
32
|
+
"aria-mcp"
|
|
33
|
+
]
|
|
56
34
|
},
|
|
57
35
|
{
|
|
58
36
|
"name": "magentaa11y",
|
|
59
|
-
"command": "
|
|
60
|
-
"args": [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
"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,
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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,
|
|
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("
|
|
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
|
+
};
|
package/src/installers/mcp.js
CHANGED
|
@@ -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
|
+
};
|
package/src/installers/skills.js
CHANGED
|
@@ -1,31 +1,110 @@
|
|
|
1
|
-
import fs from "fs/promises";
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
};
|
package/src/installers/repo.js
DELETED
|
@@ -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
|
-
};
|