a11y-devkit-deploy 0.4.0 → 0.5.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,6 +1,6 @@
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. Automatically clones the a11y-skills repo and all required MCP server repositories.
3
+ A cross-platform CLI for deploying accessibility skills and MCP servers across Claude Code, Cursor, Codex, and VSCode.
4
4
 
5
5
  ## Install
6
6
 
@@ -26,29 +26,47 @@ a11y-devkit-deploy
26
26
  This CLI automates the setup of accessibility tooling by:
27
27
 
28
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
29
+ 2. **Installing skills** - Copies skills to IDE-specific directories based on scope (local/global)
30
+ 3. **Configuring MCP servers** - Updates each IDE's MCP config to enable 5 accessibility-focused MCP servers:
31
+ - **wcag** - WCAG 2.2 guidelines, success criteria, and techniques
32
+ - **aria** - WAI-ARIA roles, states, properties, and patterns
32
33
  - **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
34
+ - **a11y-personas** - Accessibility personas for diverse user needs
35
+ - **arc-issues** - Format AxeCore violations into standardized issue templates
36
+
37
+ ### No Local MCP Installation Required!
38
+
39
+ MCP servers are configured to use `npx`, which means:
40
+ - **No cloning** of MCP server repositories
41
+ - **No building** or `npm install` steps
42
+ - **No disk space** used for local copies
43
+ - **Always up-to-date** - npx fetches the latest version automatically
44
+
45
+ The generated MCP config looks like this:
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "wcag": {
51
+ "command": "npx",
52
+ "args": ["-y", "wcag-mcp"]
53
+ },
54
+ "aria": {
55
+ "command": "npx",
56
+ "args": ["-y", "aria-mcp"]
57
+ }
58
+ }
59
+ }
60
+ ```
41
61
 
42
62
  ## Configuration
43
63
 
44
64
  Edit `config/a11y.json` to customize the deployment:
45
65
 
46
66
  - `repo.url` - Main skills repository to clone
47
- - `mcpRepos` - Array of MCP repositories to clone and build
48
67
  - `skillsSearchPaths` - Directories to search for skills in the cloned repo
49
68
  - `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/`)
69
+ - `mcpServers` - MCP server definitions using npx
52
70
 
53
71
  ## Directory Structure
54
72
 
@@ -69,33 +87,19 @@ your-project/
69
87
  ~/.vscode/skills/ # VSCode skills
70
88
  ```
71
89
 
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
- ```
90
+ ### MCP Configuration Locations
94
91
 
95
92
  MCP configurations are written to each IDE's OS-specific config path:
96
93
  - **macOS**: `~/Library/Application Support/{IDE}/mcp.json`
97
94
  - **Windows**: `%APPDATA%\{IDE}\mcp.json`
98
95
  - **Linux**: `~/.config/{IDE}/mcp.json`
99
96
 
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.
97
+ ## MCP Servers Included
98
+
99
+ | Server | Package | Description |
100
+ |--------|---------|-------------|
101
+ | wcag | `wcag-mcp` | WCAG 2.2 guidelines, success criteria, techniques |
102
+ | aria | `aria-mcp` | WAI-ARIA roles, states, properties |
103
+ | magentaa11y | `magentaa11y-mcp` | Component accessibility acceptance criteria |
104
+ | a11y-personas | `a11y-personas-mcp` | Accessibility personas for diverse users |
105
+ | arc-issues | `arc-issues-mcp` | AxeCore violation formatting |
package/config/a11y.json CHANGED
@@ -1,73 +1,46 @@
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"
38
- ],
39
- "ideSkillsPaths": {
40
- "claude": ".claude/skills",
41
- "cursor": ".cursor/skills",
42
- "codex": ".codex/skills",
43
- "vscode": ".github/skills",
44
- "local": ".github/skills"
45
- },
46
- "mcpServers": [
47
- {
48
- "name": "wcag-mcp",
49
- "command": "node",
50
- "args": ["{mcpRepoDir}/wcag-mcp/src/index.js"]
51
- },
52
- {
53
- "name": "aria-mcp",
54
- "command": "node",
55
- "args": ["{mcpRepoDir}/aria-mcp/src/index.js"]
56
- },
57
- {
58
- "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"]
71
- }
72
- ]
73
- }
1
+ {
2
+ "repo": {
3
+ "url": "https://github.com/joe-watkins/a11y-skills",
4
+ "dirName": "a11y-skills"
5
+ },
6
+ "skillsSearchPaths": [
7
+ ".",
8
+ "skills",
9
+ ".github/skills",
10
+ ".codex/skills"
11
+ ],
12
+ "ideSkillsPaths": {
13
+ "claude": ".claude/skills",
14
+ "cursor": ".cursor/skills",
15
+ "codex": ".codex/skills",
16
+ "vscode": ".github/skills",
17
+ "local": ".github/skills"
18
+ },
19
+ "mcpServers": [
20
+ {
21
+ "name": "wcag",
22
+ "command": "npx",
23
+ "args": ["-y", "wcag-mcp"]
24
+ },
25
+ {
26
+ "name": "aria",
27
+ "command": "npx",
28
+ "args": ["-y", "aria-mcp"]
29
+ },
30
+ {
31
+ "name": "magentaa11y",
32
+ "command": "npx",
33
+ "args": ["-y", "magentaa11y-mcp"]
34
+ },
35
+ {
36
+ "name": "a11y-personas",
37
+ "command": "npx",
38
+ "args": ["-y", "a11y-personas-mcp"]
39
+ },
40
+ {
41
+ "name": "arc-issues",
42
+ "command": "npx",
43
+ "args": ["-y", "arc-issues-mcp"]
44
+ }
45
+ ]
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a11y-devkit-deploy",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI to deploy a11y skills and MCP servers across IDEs",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,12 +27,12 @@
27
27
  ],
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "https://github.com/joe-watkins/a11y-skills-npm.git"
30
+ "url": "https://github.com/joe-watkins/a11y-devkit.git"
31
31
  },
32
32
  "bugs": {
33
- "url": "https://github.com/joe-watkins/a11y-skills-npm/issues"
33
+ "url": "https://github.com/joe-watkins/a11y-devkit/issues"
34
34
  },
35
- "homepage": "https://github.com/joe-watkins/a11y-skills-npm#readme",
35
+ "homepage": "https://github.com/joe-watkins/a11y-devkit#readme",
36
36
  "author": "Joe Watkins",
37
37
  "engines": {
38
38
  "node": ">=18"
package/src/cli.js CHANGED
@@ -1,194 +1,162 @@
1
- import fs from "fs/promises";
2
- import os from "os";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
- import prompts from "prompts";
6
-
7
- 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";
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = path.dirname(__filename);
15
-
16
- async function loadConfig() {
17
- const configPath = path.join(__dirname, "..", "config", "a11y.json");
18
- const raw = await fs.readFile(configPath, "utf8");
19
- return JSON.parse(raw);
20
- }
21
-
22
- function parseArgs(argv) {
23
- const args = new Set(argv.slice(2));
24
- return {
25
- autoYes: args.has("--yes") || args.has("-y"),
26
- scope: args.has("--global") ? "global" : args.has("--local") ? "local" : null
27
- };
28
- }
29
-
30
- function formatOs(platformInfo) {
31
- if (platformInfo.isWindows) return "Windows";
32
- if (platformInfo.isMac) return "macOS";
33
- if (platformInfo.isLinux) return "Linux";
34
- return platformInfo.platform;
35
- }
36
-
37
- async function run() {
38
- const projectRoot = process.cwd();
39
- const platformInfo = getPlatform();
40
- const config = await loadConfig();
41
- const idePaths = getIdePaths(projectRoot, platformInfo, config.ideSkillsPaths);
42
- const args = parseArgs(process.argv);
43
- const homeDir = os.homedir();
44
-
45
- header("A11y Devkit Deploy", "Install skills + MCP servers across IDEs");
46
- info(`Detected OS: ${formatOs(platformInfo)}`);
47
-
48
- const ideChoices = [
49
- { title: "Claude Code", value: "claude" },
50
- { title: "Cursor", value: "cursor" },
51
- { title: "Codex", value: "codex" },
52
- { title: "VSCode", value: "vscode" }
53
- ];
54
-
55
- let scope = args.scope;
56
- let ideSelection = ["claude", "cursor", "codex", "vscode"];
57
- let installSkills = true;
58
-
59
- if (!args.autoYes) {
60
- const response = await prompts(
61
- [
62
- {
63
- type: scope ? null : "select",
64
- name: "scope",
65
- message: "Install skills + repo locally or globally?",
66
- choices: [
67
- { title: "Local to this project", value: "local" },
68
- { title: "Global for this user", value: "global" }
69
- ],
70
- initial: 0
71
- },
72
- {
73
- type: "multiselect",
74
- name: "ides",
75
- message: "Configure MCP for which IDEs?",
76
- choices: ideChoices,
77
- initial: ideChoices.map((_, index) => index)
78
- },
79
- {
80
- type: "toggle",
81
- name: "installSkills",
82
- message: "Install skills into IDE skills folders?",
83
- active: "yes",
84
- inactive: "no",
85
- initial: true
86
- }
87
- ],
88
- {
89
- onCancel: () => {
90
- warn("Setup cancelled.");
91
- process.exit(0);
92
- }
93
- }
94
- );
95
-
96
- scope = scope || response.scope;
97
- ideSelection = response.ides || ideSelection;
98
- installSkills = response.installSkills;
99
- }
100
-
101
- if (!scope) {
102
- scope = "local";
103
- }
104
-
105
- if (!ideSelection.length) {
106
- warn("No IDEs selected. MCP installation requires at least one IDE.");
107
- process.exit(1);
108
- }
109
-
110
- info(`Install scope: ${scope === "local" ? "Local" : "Global"}`);
111
-
112
- // Create temp directory for cloning and building
113
- 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
-
156
- 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 {
162
- const skillTargets = scope === "local"
163
- ? ideSelection.map((ide) => idePaths[ide].localSkillsDir)
164
- : ideSelection.map((ide) => idePaths[ide].skillsDir);
165
-
166
- for (const target of skillTargets) {
167
- await copySkills(sourceDir, target);
168
- }
169
-
170
- skillsSpinner.succeed(`Skills installed to ${skillTargets.length} IDE location(s).`);
171
- }
172
- } else {
173
- warn("Skipping skills install to IDE folders.");
174
- }
175
-
176
- const serverDefs = resolveServers(config.mcpServers, mcpServerDir, mcpServerDir);
177
- const mcpSpinner = startSpinner("Updating MCP configurations...");
178
- for (const ide of ideSelection) {
179
- await installMcpConfig(idePaths[ide].mcpConfig, serverDefs, idePaths[ide].mcpServerKey);
180
- }
181
- mcpSpinner.succeed(`MCP configs updated for ${ideSelection.length} IDE(s).`);
182
-
183
- // Clean up temporary directory
184
- const cleanupSpinner = startSpinner("Cleaning up temporary files...");
185
- await cleanupTemp(tempDir);
186
- cleanupSpinner.succeed("Temporary files removed");
187
-
188
- 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.");
190
- }
191
-
192
- export {
193
- run
194
- };
1
+ import fs from "fs/promises";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import prompts from "prompts";
6
+
7
+ import { header, info, warn, success, startSpinner, formatPath } from "./ui.js";
8
+ import { getPlatform, getIdePaths, getTempDir } from "./paths.js";
9
+ import { ensureRepo, cleanupTemp } from "./installers/repo.js";
10
+ import { findSkillsDir, copySkills } from "./installers/skills.js";
11
+ import { installMcpConfig } from "./installers/mcp.js";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ async function loadConfig() {
17
+ const configPath = path.join(__dirname, "..", "config", "a11y.json");
18
+ const raw = await fs.readFile(configPath, "utf8");
19
+ return JSON.parse(raw);
20
+ }
21
+
22
+ function parseArgs(argv) {
23
+ const args = new Set(argv.slice(2));
24
+ return {
25
+ autoYes: args.has("--yes") || args.has("-y"),
26
+ scope: args.has("--global") ? "global" : args.has("--local") ? "local" : null
27
+ };
28
+ }
29
+
30
+ function formatOs(platformInfo) {
31
+ if (platformInfo.isWindows) return "Windows";
32
+ if (platformInfo.isMac) return "macOS";
33
+ if (platformInfo.isLinux) return "Linux";
34
+ return platformInfo.platform;
35
+ }
36
+
37
+ async function run() {
38
+ const projectRoot = process.cwd();
39
+ const platformInfo = getPlatform();
40
+ const config = await loadConfig();
41
+ const idePaths = getIdePaths(projectRoot, platformInfo, config.ideSkillsPaths);
42
+ const args = parseArgs(process.argv);
43
+
44
+ header("A11y Devkit Deploy", "Install skills + MCP servers across IDEs");
45
+ info(`Detected OS: ${formatOs(platformInfo)}`);
46
+
47
+ const ideChoices = [
48
+ { title: "Claude Code", value: "claude" },
49
+ { title: "Cursor", value: "cursor" },
50
+ { title: "Codex", value: "codex" },
51
+ { title: "VSCode", value: "vscode" }
52
+ ];
53
+
54
+ let scope = args.scope;
55
+ let ideSelection = ["claude", "cursor", "codex", "vscode"];
56
+ let installSkills = true;
57
+
58
+ if (!args.autoYes) {
59
+ const response = await prompts(
60
+ [
61
+ {
62
+ type: scope ? null : "select",
63
+ name: "scope",
64
+ message: "Install skills + repo locally or globally?",
65
+ choices: [
66
+ { title: "Local to this project", value: "local" },
67
+ { title: "Global for this user", value: "global" }
68
+ ],
69
+ initial: 0
70
+ },
71
+ {
72
+ type: "multiselect",
73
+ name: "ides",
74
+ message: "Configure MCP for which IDEs?",
75
+ choices: ideChoices,
76
+ initial: ideChoices.map((_, index) => index)
77
+ },
78
+ {
79
+ type: "toggle",
80
+ name: "installSkills",
81
+ message: "Install skills into IDE skills folders?",
82
+ active: "yes",
83
+ inactive: "no",
84
+ initial: true
85
+ }
86
+ ],
87
+ {
88
+ onCancel: () => {
89
+ warn("Setup cancelled.");
90
+ process.exit(0);
91
+ }
92
+ }
93
+ );
94
+
95
+ scope = scope || response.scope;
96
+ ideSelection = response.ides || ideSelection;
97
+ installSkills = response.installSkills;
98
+ }
99
+
100
+ if (!scope) {
101
+ scope = "local";
102
+ }
103
+
104
+ if (!ideSelection.length) {
105
+ warn("No IDEs selected. MCP installation requires at least one IDE.");
106
+ process.exit(1);
107
+ }
108
+
109
+ info(`Install scope: ${scope === "local" ? "Local" : "Global"}`);
110
+
111
+ // Create temp directory for cloning skills repo
112
+ const tempDir = path.join(getTempDir(), `.a11y-devkit-${Date.now()}`);
113
+ const tempSkillsDir = path.join(tempDir, "skills");
114
+
115
+ // Clone skills repo into temp
116
+ const repoSpinner = startSpinner("Syncing a11y-skills repo...");
117
+ const repoResult = await ensureRepo({
118
+ url: config.repo.url,
119
+ dir: tempSkillsDir
120
+ });
121
+ repoSpinner.succeed(`Repo ${repoResult.action}: ${formatPath(repoResult.dir)}`);
122
+
123
+ if (installSkills) {
124
+ const skillsSpinner = startSpinner("Installing skills to IDE folders...");
125
+ const sourceDir = await findSkillsDir(tempSkillsDir, config.skillsSearchPaths);
126
+ if (!sourceDir) {
127
+ skillsSpinner.fail("No skills directory found in repo.");
128
+ } else {
129
+ const skillTargets = scope === "local"
130
+ ? ideSelection.map((ide) => idePaths[ide].localSkillsDir)
131
+ : ideSelection.map((ide) => idePaths[ide].skillsDir);
132
+
133
+ for (const target of skillTargets) {
134
+ await copySkills(sourceDir, target);
135
+ }
136
+
137
+ skillsSpinner.succeed(`Skills installed to ${skillTargets.length} IDE location(s).`);
138
+ }
139
+ } else {
140
+ warn("Skipping skills install to IDE folders.");
141
+ }
142
+
143
+ // Configure MCP servers using npx (no local installation needed!)
144
+ const mcpSpinner = startSpinner("Updating MCP configurations...");
145
+ for (const ide of ideSelection) {
146
+ await installMcpConfig(idePaths[ide].mcpConfig, config.mcpServers, idePaths[ide].mcpServerKey);
147
+ }
148
+ mcpSpinner.succeed(`MCP configs updated for ${ideSelection.length} IDE(s).`);
149
+
150
+ // Clean up temporary directory
151
+ const cleanupSpinner = startSpinner("Cleaning up temporary files...");
152
+ await cleanupTemp(tempDir);
153
+ cleanupSpinner.succeed("Temporary files removed");
154
+
155
+ success("All done. Your skills and MCP servers are ready.");
156
+ info("MCP servers use npx - no local installation needed!");
157
+ info("You can re-run this CLI any time to update the repo and configs.");
158
+ }
159
+
160
+ export {
161
+ run
162
+ };
@@ -1,69 +1,54 @@
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 loadJson(filePath) {
14
- if (!(await pathExists(filePath))) {
15
- return {};
16
- }
17
-
18
- try {
19
- const raw = await fs.readFile(filePath, "utf8");
20
- return raw.trim() ? JSON.parse(raw) : {};
21
- } catch (error) {
22
- const backupPath = `${filePath}.bak`;
23
- await fs.copyFile(filePath, backupPath);
24
- return {};
25
- }
26
- }
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
- function mergeServers(existing, incoming, serverKey = "servers") {
41
- const existingServers = existing[serverKey] && typeof existing[serverKey] === "object"
42
- ? existing[serverKey]
43
- : {};
44
-
45
- const merged = { ...existing, [serverKey]: { ...existingServers } };
46
-
47
- for (const server of incoming) {
48
- merged[serverKey][server.name] = {
49
- command: server.command,
50
- args: server.args || [],
51
- env: server.env || {},
52
- cwd: server.cwd
53
- };
54
- }
55
-
56
- return merged;
57
- }
58
-
59
- async function installMcpConfig(configPath, servers, serverKey = "servers") {
60
- await fs.mkdir(path.dirname(configPath), { recursive: true });
61
- const existing = await loadJson(configPath);
62
- const updated = mergeServers(existing, servers, serverKey);
63
- await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
64
- }
65
-
66
- export {
67
- resolveServers,
68
- installMcpConfig
69
- };
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 loadJson(filePath) {
14
+ if (!(await pathExists(filePath))) {
15
+ return {};
16
+ }
17
+
18
+ try {
19
+ const raw = await fs.readFile(filePath, "utf8");
20
+ return raw.trim() ? JSON.parse(raw) : {};
21
+ } catch (error) {
22
+ const backupPath = `${filePath}.bak`;
23
+ await fs.copyFile(filePath, backupPath);
24
+ return {};
25
+ }
26
+ }
27
+
28
+ function mergeServers(existing, incoming, serverKey = "servers") {
29
+ const existingServers = existing[serverKey] && typeof existing[serverKey] === "object"
30
+ ? existing[serverKey]
31
+ : {};
32
+
33
+ const merged = { ...existing, [serverKey]: { ...existingServers } };
34
+
35
+ for (const server of incoming) {
36
+ merged[serverKey][server.name] = {
37
+ command: server.command,
38
+ args: server.args || []
39
+ };
40
+ }
41
+
42
+ return merged;
43
+ }
44
+
45
+ async function installMcpConfig(configPath, servers, serverKey = "servers") {
46
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
47
+ const existing = await loadJson(configPath);
48
+ const updated = mergeServers(existing, servers, serverKey);
49
+ await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
50
+ }
51
+
52
+ export {
53
+ installMcpConfig
54
+ };
@@ -1,103 +1,55 @@
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
- };
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 cleanupTemp(tempDir) {
47
+ if (await pathExists(tempDir)) {
48
+ await fs.rm(tempDir, { recursive: true, force: true });
49
+ }
50
+ }
51
+
52
+ export {
53
+ ensureRepo,
54
+ cleanupTemp
55
+ };
package/src/paths.js CHANGED
@@ -1,93 +1,83 @@
1
- import os from "os";
2
- import path from "path";
3
-
4
- function getPlatform() {
5
- const platform = os.platform();
6
- return {
7
- platform,
8
- isWindows: platform === "win32",
9
- isMac: platform === "darwin",
10
- isLinux: platform === "linux"
11
- };
12
- }
13
-
14
- function getTempDir() {
15
- return os.tmpdir();
16
- }
17
-
18
- function getAppSupportDir(platformInfo = getPlatform()) {
19
- if (platformInfo.isWindows) {
20
- return (
21
- process.env.APPDATA ||
22
- path.join(os.homedir(), "AppData", "Roaming")
23
- );
24
- }
25
-
26
- if (platformInfo.isMac) {
27
- return path.join(os.homedir(), "Library", "Application Support");
28
- }
29
-
30
- return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
31
- }
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
- function getIdePaths(projectRoot, platformInfo = getPlatform(), ideSkillsPaths = null) {
43
- const appSupport = getAppSupportDir(platformInfo);
44
- const home = os.homedir();
45
-
46
- // Default paths if not provided via config
47
- const skillsPaths = ideSkillsPaths || {
48
- claude: ".claude/skills",
49
- cursor: ".cursor/skills",
50
- codex: ".codex/skills",
51
- vscode: ".vscode/skills",
52
- local: ".github/skills"
53
- };
54
-
55
- return {
56
- claude: {
57
- name: "Claude Code",
58
- mcpConfig: path.join(appSupport, "Claude", "mcp.json"),
59
- mcpServerKey: "servers",
60
- skillsDir: path.join(home, skillsPaths.claude),
61
- localSkillsDir: path.join(projectRoot, skillsPaths.claude)
62
- },
63
- cursor: {
64
- name: "Cursor",
65
- mcpConfig: path.join(appSupport, "Cursor", "mcp.json"),
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
- mcpServerKey: "servers",
74
- skillsDir: path.join(home, skillsPaths.codex),
75
- localSkillsDir: path.join(projectRoot, skillsPaths.codex)
76
- },
77
- vscode: {
78
- name: "VSCode",
79
- mcpConfig: path.join(appSupport, "Code", "User", "mcp.json"),
80
- mcpServerKey: "servers",
81
- skillsDir: path.join(home, skillsPaths.vscode),
82
- localSkillsDir: path.join(projectRoot, skillsPaths.vscode)
83
- }
84
- };
85
- }
86
-
87
- export {
88
- getPlatform,
89
- getAppSupportDir,
90
- getIdePaths,
91
- getMcpServerDir,
92
- getTempDir
1
+ import os from "os";
2
+ import path from "path";
3
+
4
+ function getPlatform() {
5
+ const platform = os.platform();
6
+ return {
7
+ platform,
8
+ isWindows: platform === "win32",
9
+ isMac: platform === "darwin",
10
+ isLinux: platform === "linux"
11
+ };
12
+ }
13
+
14
+ function getTempDir() {
15
+ return os.tmpdir();
16
+ }
17
+
18
+ function getAppSupportDir(platformInfo = getPlatform()) {
19
+ if (platformInfo.isWindows) {
20
+ return (
21
+ process.env.APPDATA ||
22
+ path.join(os.homedir(), "AppData", "Roaming")
23
+ );
24
+ }
25
+
26
+ if (platformInfo.isMac) {
27
+ return path.join(os.homedir(), "Library", "Application Support");
28
+ }
29
+
30
+ return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
31
+ }
32
+
33
+ function getIdePaths(projectRoot, platformInfo = getPlatform(), ideSkillsPaths = null) {
34
+ const appSupport = getAppSupportDir(platformInfo);
35
+ const home = os.homedir();
36
+
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
+ return {
47
+ claude: {
48
+ name: "Claude Code",
49
+ mcpConfig: path.join(appSupport, "Claude", "mcp.json"),
50
+ mcpServerKey: "servers",
51
+ skillsDir: path.join(home, skillsPaths.claude),
52
+ localSkillsDir: path.join(projectRoot, skillsPaths.claude)
53
+ },
54
+ cursor: {
55
+ name: "Cursor",
56
+ mcpConfig: path.join(appSupport, "Cursor", "mcp.json"),
57
+ mcpServerKey: "mcpServers",
58
+ skillsDir: path.join(home, skillsPaths.cursor),
59
+ localSkillsDir: path.join(projectRoot, skillsPaths.cursor)
60
+ },
61
+ codex: {
62
+ name: "Codex",
63
+ mcpConfig: path.join(home, ".codex", "mcp.json"),
64
+ mcpServerKey: "servers",
65
+ skillsDir: path.join(home, skillsPaths.codex),
66
+ localSkillsDir: path.join(projectRoot, skillsPaths.codex)
67
+ },
68
+ vscode: {
69
+ name: "VSCode",
70
+ mcpConfig: path.join(appSupport, "Code", "User", "mcp.json"),
71
+ mcpServerKey: "servers",
72
+ skillsDir: path.join(home, skillsPaths.vscode),
73
+ localSkillsDir: path.join(projectRoot, skillsPaths.vscode)
74
+ }
75
+ };
76
+ }
77
+
78
+ export {
79
+ getPlatform,
80
+ getAppSupportDir,
81
+ getIdePaths,
82
+ getTempDir
93
83
  };