a11y-devkit-deploy 0.7.1 → 0.8.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,8 +1,8 @@
1
1
  # A11y Devkit Deploy
2
2
 
3
- A **fully config-driven**, cross-platform CLI for deploying accessibility skills and MCP servers across Claude Code, Cursor, Codex, and VSCode.
3
+ A **fully config-driven**, cross-platform CLI for deploying accessibility skills and MCP servers across multiple IDEs: Claude Code, Cursor, Codex, VSCode, Windsurf, and Factory.
4
4
 
5
- **Add new skills or MCP servers without writing code** - just edit the JSON config and re-run.
5
+ **Add new skills, MCP servers, or entire IDEs without writing code** - just edit the JSON config and re-run.
6
6
 
7
7
  ## Install
8
8
 
@@ -27,8 +27,8 @@ a11y-devkit-deploy
27
27
 
28
28
  Once installation completes, you'll find a comprehensive usage guide in your IDE's skills directory:
29
29
 
30
- - **Local install**: `.claude/skills/README.md` (or `.cursor/skills/`, `.codex/skills/` depending on your IDE)
31
- - **Global install**: `~/.claude/skills/README.md` (or your IDE's global skills directory)
30
+ - **Local install**: `.claude/skills/a11y/a11y-devkit-README.md` (or `.cursor/skills/a11y/`, `.codex/skills/a11y/` depending on your IDE)
31
+ - **Global install**: `~/.claude/skills/a11y/a11y-devkit-README.md` (or your IDE's global skills directory)
32
32
 
33
33
  ### What's in the Guide
34
34
 
@@ -42,7 +42,7 @@ The bundled README includes:
42
42
 
43
43
  ### Preview the Guide
44
44
 
45
- You can preview the guide here: [templates/skills-README.md](templates/skills-README.md)
45
+ You can preview the guide here: [templates/deploy-README.md](templates/deploy-README.md)
46
46
 
47
47
  ### Next Steps
48
48
 
@@ -75,7 +75,31 @@ This CLI automates the setup of accessibility tooling by:
75
75
  - **a11y-personas** - Accessibility personas for diverse user needs
76
76
  - **arc-issues** - Format AxeCore violations into standardized issue templates
77
77
 
78
- **Fully customizable** - add/remove skills or MCP servers by editing the config file.
78
+ **Fully customizable** - add/remove skills, MCP servers, or entire IDEs by editing the config file.
79
+
80
+ ## Why This Tool?
81
+
82
+ **Zero Hardcoded Values** - Every aspect of the tool is driven by `config/a11y.json`:
83
+ - IDE paths and configuration files
84
+ - Skills to install
85
+ - MCP servers to configure
86
+ - Even the IDE list itself
87
+
88
+ **Adding Support for a New IDE** takes just 5 lines of JSON:
89
+ ```json
90
+ {
91
+ "id": "new-ide",
92
+ "displayName": "New IDE",
93
+ "mcpServerKey": "servers",
94
+ "skillsFolder": ".new-ide/skills",
95
+ "mcpConfigFile": ".new-ide/mcp.json"
96
+ }
97
+ ```
98
+
99
+ **Safe by Default** - Won't overwrite your existing:
100
+ - Custom MCP servers in your IDE configs
101
+ - Other skills in your skills directories
102
+ - Creates backups if it encounters JSON parsing errors
79
103
 
80
104
  ### Skills Installed (Default)
81
105
 
@@ -118,9 +142,9 @@ The generated MCP config looks like this:
118
142
 
119
143
  ## Configuration
120
144
 
121
- The entire tool is **fully config-driven**. Edit `config/a11y.json` to customize everything without touching code:
145
+ The entire tool is **fully config-driven**. Edit `config/a11y.json` to customize everything without touching code.
122
146
 
123
- ### Example: Adding a New Skill
147
+ ### Adding a New Skill
124
148
 
125
149
  Simply add an object to the `skills` array with a `name` (npm package) and `description`:
126
150
 
@@ -139,7 +163,7 @@ Simply add an object to the `skills` array with a `name` (npm package) and `desc
139
163
  }
140
164
  ```
141
165
 
142
- ### Example: Adding a New MCP Server
166
+ ### Adding a New MCP Server
143
167
 
144
168
  Add an object to the `mcpServers` array with name, description, command, and args:
145
169
 
@@ -162,40 +186,96 @@ Add an object to the `mcpServers` array with name, description, command, and arg
162
186
  }
163
187
  ```
164
188
 
189
+ ### Adding a New IDE
190
+
191
+ Add an object to the `ides` array with the IDE's configuration:
192
+
193
+ ```json
194
+ {
195
+ "ides": [
196
+ {
197
+ "id": "windsurf",
198
+ "displayName": "Windsurf",
199
+ "mcpServerKey": "servers",
200
+ "skillsFolder": ".codeium/windsurf/skills",
201
+ "mcpConfigFile": ".codeium/windsurf/mcp_config.json"
202
+ }
203
+ ]
204
+ }
205
+ ```
206
+
207
+ **IDE Configuration Properties:**
208
+ - `id` - Unique identifier for the IDE
209
+ - `displayName` - Human-readable name shown in prompts
210
+ - `mcpServerKey` - MCP config key name (`"servers"` or `"mcpServers"`)
211
+ - `skillsFolder` - Path to skills directory (relative to home/project root)
212
+ - `mcpConfigFile` - Path to MCP config file (relative to home/project root)
213
+
165
214
  ### Config Structure
166
215
 
216
+ - `skillsFolder` - Subfolder name to bundle skills under (e.g., "a11y")
217
+ - `readmeTemplate` - README template file to copy into skills directories
167
218
  - `skills` - Array of skill objects with `name` (npm package) and `description`
168
- - `ideSkillsPaths` - IDE-specific skills directories (configurable per IDE)
169
- - `ideMcpPaths` - IDE-specific MCP config file paths
219
+ - `ides` - Array of IDE configuration objects
170
220
  - `mcpServers` - MCP server definitions with name, description, command, and args
171
221
 
172
222
  All changes take effect immediately - just re-run the CLI to deploy your updated config.
173
223
 
224
+ ### Safe Merging
225
+
226
+ The CLI **safely merges** with existing configurations:
227
+ - **MCP configs** - Adds/updates only the specified servers, preserves others
228
+ - **Skills** - Installs only the configured skills, preserves other skills in the directory
229
+ - **Backups** - Creates `.bak` files if JSON parsing fails
230
+
174
231
  ## Directory Structure
175
232
 
176
233
  ### Local Install (Project-Specific)
177
234
  ```
178
235
  your-project/
179
- ├── .claude/skills/ # Skills copied to Claude Code (if selected)
180
- ├── .cursor/skills/ # Skills copied to Cursor (if selected)
181
- ├── .codex/skills/ # Skills copied to Codex (if selected)
182
- └── .github/skills/ # Skills copied here for version control
236
+ ├── .claude/
237
+ ├── mcp.json # Claude Code MCP config
238
+ │ └── skills/ # Claude Code skills
239
+ ├── .cursor/
240
+ │ ├── mcp.json # Cursor MCP config
241
+ │ └── skills/ # Cursor skills
242
+ ├── .codex/
243
+ │ ├── mcp.json # Codex MCP config
244
+ │ └── skills/ # Codex skills
245
+ ├── .github/
246
+ │ ├── mcp.json # VSCode MCP config
247
+ │ └── skills/ # VSCode skills
248
+ ├── .codeium/windsurf/
249
+ │ ├── mcp_config.json # Windsurf MCP config
250
+ │ └── skills/ # Windsurf skills
251
+ └── .factory/
252
+ ├── mcp.json # Factory MCP config
253
+ └── skills/ # Factory skills
183
254
  ```
184
255
 
185
256
  ### Global Install (User-Wide)
186
257
  ```
187
- ~/.claude/skills/ # Claude Code skills
188
- ~/.cursor/skills/ # Cursor skills
189
- ~/.codex/skills/ # Codex skills
190
- ~/.vscode/skills/ # VSCode skills
258
+ ~/.claude/
259
+ ├── mcp.json # Claude Code global MCP config
260
+ └── skills/ # Claude Code global skills
261
+ ~/.cursor/
262
+ ├── mcp.json # Cursor global MCP config
263
+ └── skills/ # Cursor global skills
264
+ ~/.codex/
265
+ ├── mcp.json # Codex global MCP config
266
+ └── skills/ # Codex global skills
267
+ ~/.github/
268
+ ├── mcp.json # VSCode global MCP config
269
+ └── skills/ # VSCode global skills
270
+ ~/.codeium/windsurf/
271
+ ├── mcp_config.json # Windsurf global MCP config
272
+ └── skills/ # Windsurf global skills
273
+ ~/.factory/
274
+ ├── mcp.json # Factory global MCP config
275
+ └── skills/ # Factory global skills
191
276
  ```
192
277
 
193
- ### MCP Configuration Locations
194
-
195
- MCP configurations are written to each IDE's OS-specific config path:
196
- - **macOS**: `~/Library/Application Support/{IDE}/mcp.json`
197
- - **Windows**: `%APPDATA%\{IDE}\mcp.json`
198
- - **Linux**: `~/.config/{IDE}/mcp.json`
278
+ **Note:** Paths are fully customizable per IDE in `config/a11y.json`
199
279
 
200
280
  ## MCP Servers Included (Default)
201
281
 
@@ -208,3 +288,63 @@ MCP configurations are written to each IDE's OS-specific config path:
208
288
  | magentaa11y | `magentaa11y-mcp` | Component accessibility acceptance criteria |
209
289
  | a11y-personas | `a11y-personas-mcp` | Accessibility personas for diverse users |
210
290
  | arc-issues | `arc-issues-mcp` | AxeCore violation formatting |
291
+
292
+ ## Complete Config Example
293
+
294
+ Here's what a complete `config/a11y.json` looks like:
295
+
296
+ ```json
297
+ {
298
+ "skillsFolder": "a11y",
299
+ "readmeTemplate": "deploy-README.md",
300
+ "skills": [
301
+ {
302
+ "name": "a11y-base-web-skill",
303
+ "description": "Core accessibility testing utilities"
304
+ },
305
+ {
306
+ "name": "a11y-tester-skill",
307
+ "description": "Run accessibility tests"
308
+ }
309
+ ],
310
+ "ides": [
311
+ {
312
+ "id": "claude",
313
+ "displayName": "Claude Code",
314
+ "mcpServerKey": "servers",
315
+ "skillsFolder": ".claude/skills",
316
+ "mcpConfigFile": ".claude/mcp.json"
317
+ },
318
+ {
319
+ "id": "cursor",
320
+ "displayName": "Cursor",
321
+ "mcpServerKey": "mcpServers",
322
+ "skillsFolder": ".cursor/skills",
323
+ "mcpConfigFile": ".cursor/mcp.json"
324
+ },
325
+ {
326
+ "id": "windsurf",
327
+ "displayName": "Windsurf",
328
+ "mcpServerKey": "servers",
329
+ "skillsFolder": ".codeium/windsurf/skills",
330
+ "mcpConfigFile": ".codeium/windsurf/mcp_config.json"
331
+ }
332
+ ],
333
+ "mcpServers": [
334
+ {
335
+ "name": "wcag",
336
+ "description": "WCAG guidelines reference",
337
+ "command": "npx",
338
+ "args": ["-y", "wcag-mcp"]
339
+ },
340
+ {
341
+ "name": "aria",
342
+ "description": "ARIA specification reference",
343
+ "command": "npx",
344
+ "args": ["-y", "aria-mcp"]
345
+ }
346
+ ]
347
+ }
348
+ ```
349
+
350
+ Everything is customizable - add, remove, or modify any section to match your needs.
package/config/a11y.json CHANGED
@@ -3,31 +3,45 @@
3
3
  "readmeTemplate": "deploy-README.md",
4
4
  "skills": [
5
5
  {
6
- "name": "a11y-base-web-skill",
6
+ "name": "A11y Base Web Skill",
7
+ "npmName": "a11y-base-web-skill",
8
+ "npmURL": "https://www.npmjs.com/package/a11y-base-web-skill",
7
9
  "description": "Core accessibility testing utilities"
8
10
  },
9
11
  {
10
- "name": "a11y-issue-writer-skill",
12
+ "name": "A11y Issue Writer Skill",
13
+ "npmName": "a11y-issue-writer-skill",
14
+ "npmURL": "https://www.npmjs.com/package/a11y-issue-writer-skill",
11
15
  "description": "Document accessibility issues"
12
16
  },
13
17
  {
14
- "name": "a11y-tester-skill",
18
+ "name": "A11y Tester Skill",
19
+ "npmName": "a11y-tester-skill",
20
+ "npmURL": "https://www.npmjs.com/package/a11y-tester-skill",
15
21
  "description": "Run accessibility tests"
16
22
  },
17
23
  {
18
- "name": "a11y-remediator-skill",
24
+ "name": "A11y Remediator Skill",
25
+ "npmName": "a11y-remediator-skill",
26
+ "npmURL": "https://www.npmjs.com/package/a11y-remediator-skill",
19
27
  "description": "Fix accessibility issues"
20
28
  },
21
29
  {
22
- "name": "a11y-validator-skill",
30
+ "name": "A11y Validator Skill",
31
+ "npmName": "a11y-validator-skill",
32
+ "npmURL": "https://www.npmjs.com/package/a11y-validator-skill",
23
33
  "description": "Validate accessibility compliance"
24
34
  },
25
35
  {
26
- "name": "web-standards-skill",
36
+ "name": "Web Standards Skill",
37
+ "npmName": "web-standards-skill",
38
+ "npmURL": "https://www.npmjs.com/package/web-standards-skill",
27
39
  "description": "Web standards reference"
28
40
  },
29
41
  {
30
- "name": "a11y-audit-fix-agent-orchestrator-skill",
42
+ "name": "A11y Audit Fix Agent Orchestrator Skill",
43
+ "npmName": "a11y-audit-fix-agent-orchestrator-skill",
44
+ "npmURL": "https://www.npmjs.com/package/a11y-audit-fix-agent-orchestrator-skill",
31
45
  "description": "Orchestrate accessibility audits"
32
46
  }
33
47
  ],
@@ -58,7 +72,17 @@
58
72
  "displayName": "VSCode",
59
73
  "mcpServerKey": "servers",
60
74
  "skillsFolder": ".github/skills",
61
- "mcpConfigFile": ".github/mcp.json"
75
+ "mcpConfigFile": ".github/mcp.json",
76
+ "globalPaths": {
77
+ "Win": {
78
+ "mcpConfigFile": "Code/User/mcp.json",
79
+ "skillsFolder": "Code/User/skills"
80
+ },
81
+ "macOS": {
82
+ "mcpConfigFile": "Code/User/mcp.json",
83
+ "skillsFolder": "Code/User/skills"
84
+ }
85
+ }
62
86
  },
63
87
  {
64
88
  "id": "windsurf",
@@ -106,5 +130,63 @@
106
130
  "command": "npx",
107
131
  "args": ["-y", "arc-issues-mcp"]
108
132
  }
133
+ ],
134
+ "profiles": [
135
+ {
136
+ "id": "developer",
137
+ "displayName": "Developer",
138
+ "description": "For developers building accessible applications",
139
+ "skills": ["a11y-base-web-skill"],
140
+ "mcpServers": ["magentaa11y"]
141
+ },
142
+ {
143
+ "id": "tester",
144
+ "displayName": "Tester/QA",
145
+ "description": "For QA engineers testing accessibility",
146
+ "skills": ["a11y-tester-skill"],
147
+ "mcpServers": ["arc-issues"]
148
+ },
149
+ {
150
+ "id": "a11y-sme",
151
+ "displayName": "Accessibility SME",
152
+ "description": "For accessibility subject matter experts",
153
+ "skills": ["web-standards-skill"],
154
+ "mcpServers": ["magentaa11y"]
155
+ },
156
+ {
157
+ "id": "product",
158
+ "displayName": "Product",
159
+ "description": "For product managers and owners",
160
+ "skills": ["a11y-base-web-skill"],
161
+ "mcpServers": ["magentaa11y"]
162
+ },
163
+ {
164
+ "id": "design",
165
+ "displayName": "Design",
166
+ "description": "For designers creating accessible experiences",
167
+ "skills": ["web-standards-skill"],
168
+ "mcpServers": ["aria"]
169
+ },
170
+ {
171
+ "id": "hybrid",
172
+ "displayName": "Hybrid (I want all the things)",
173
+ "description": "All skills and MCP servers for comprehensive accessibility support",
174
+ "skills": [
175
+ "a11y-base-web-skill",
176
+ "a11y-issue-writer-skill",
177
+ "a11y-tester-skill",
178
+ "a11y-remediator-skill",
179
+ "a11y-validator-skill",
180
+ "web-standards-skill",
181
+ "a11y-audit-fix-agent-orchestrator-skill"
182
+ ],
183
+ "mcpServers": [
184
+ "wcag",
185
+ "aria",
186
+ "magentaa11y",
187
+ "a11y-personas",
188
+ "arc-issues"
189
+ ]
190
+ }
109
191
  ]
110
192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a11y-devkit-deploy",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "CLI to deploy a11y skills and MCP servers across IDEs",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -4,9 +4,11 @@ import { fileURLToPath } from "url";
4
4
  import prompts from "prompts";
5
5
 
6
6
  import { header, info, warn, success, startSpinner, formatPath } from "./ui.js";
7
- import { getPlatform, getIdePaths, getTempDir } from "./paths.js";
7
+ import { getPlatform, getIdePaths, getTempDir, getMcpRepoDir } from "./paths.js";
8
8
  import { installSkillsFromNpm, cleanupTemp } from "./installers/skills.js";
9
9
  import { installMcpConfig } from "./installers/mcp.js";
10
+ import { getGitMcpPrompts, parseArgsString } from "./prompts/git-mcp.js";
11
+ import { installGitMcp } from "./installers/git-mcp.js";
10
12
 
11
13
  const __filename = fileURLToPath(import.meta.url);
12
14
  const __dirname = path.dirname(__filename);
@@ -32,6 +34,7 @@ function parseArgs(argv) {
32
34
  : args.has("--local")
33
35
  ? "local"
34
36
  : null,
37
+ gitMcp: args.has("--git-mcp"),
35
38
  };
36
39
  }
37
40
 
@@ -52,26 +55,115 @@ async function run() {
52
55
 
53
56
  header(
54
57
  `A11y Devkit Deploy v${pkg.version}`,
55
- "Install skills + MCP servers across IDEs",
58
+ args.gitMcp
59
+ ? "Install MCP server from Git repository"
60
+ : "Install skills + MCP servers across IDEs",
56
61
  );
57
62
  info(`Detected OS: ${formatOs(platformInfo)}`);
58
63
 
59
- console.log("\nSkills to install:");
60
- config.skills.forEach((skill) => {
61
- const name = typeof skill === "string" ? skill : skill.name;
62
- const description =
63
- typeof skill === "string"
64
- ? "No description"
65
- : skill.description || "No description";
66
- console.log(`- ${name}: ${description}`);
67
- });
64
+ // Branch to Git MCP installation flow
65
+ if (args.gitMcp) {
66
+ await runGitMcpInstallation(projectRoot, platformInfo, config, idePaths, args);
67
+ return;
68
+ }
68
69
 
69
- console.log("\nMCP Servers to install:");
70
- config.mcpServers.forEach((server) => {
71
- const description = server.description || "No description";
72
- console.log(`${server.name} - ${description}`);
73
- });
74
- console.log("");
70
+ // Prompt for profile selection
71
+ let selectedProfile = null;
72
+ let skillsToInstall = [];
73
+ let mcpServersToInstall = [];
74
+ let profileConfirmed = false;
75
+
76
+ while (!profileConfirmed) {
77
+ if (!args.autoYes && config.profiles) {
78
+ const profileChoices = config.profiles.map((profile) => ({
79
+ title: profile.displayName,
80
+ description: profile.description,
81
+ value: profile.id,
82
+ }));
83
+
84
+ const profileResponse = await prompts(
85
+ {
86
+ type: "select",
87
+ name: "profile",
88
+ message: "Select your profile:",
89
+ choices: profileChoices,
90
+ },
91
+ {
92
+ onCancel: () => {
93
+ warn("Setup cancelled.");
94
+ process.exit(0);
95
+ },
96
+ },
97
+ );
98
+
99
+ selectedProfile = config.profiles.find((p) => p.id === profileResponse.profile);
100
+
101
+ if (selectedProfile) {
102
+ // Filter skills based on profile
103
+ skillsToInstall = config.skills.filter((skill) => {
104
+ const skillNpmName = typeof skill === "string" ? skill : skill.npmName;
105
+ return selectedProfile.skills.includes(skillNpmName);
106
+ });
107
+
108
+ // Filter MCP servers based on profile
109
+ mcpServersToInstall = config.mcpServers.filter((server) =>
110
+ selectedProfile.mcpServers.includes(server.name),
111
+ );
112
+
113
+ console.log(`\n${selectedProfile.displayName} profile selected`);
114
+ }
115
+ } else {
116
+ // If no profiles or auto-yes, use all skills and servers
117
+ skillsToInstall = config.skills;
118
+ mcpServersToInstall = config.mcpServers;
119
+ profileConfirmed = true; // Skip confirmation for auto-yes
120
+ }
121
+
122
+ // Show what will be installed
123
+ if (!args.autoYes) {
124
+ console.log("\nSkills to install:");
125
+ skillsToInstall.forEach((skill) => {
126
+ const name = typeof skill === "string" ? skill : skill.name;
127
+ const description =
128
+ typeof skill === "string"
129
+ ? "No description"
130
+ : skill.description || "No description";
131
+ console.log(` • ${name}`);
132
+ console.log(` ${description}`);
133
+ });
134
+
135
+ console.log("\nMCP Servers to install:");
136
+ mcpServersToInstall.forEach((server) => {
137
+ const description = server.description || "No description";
138
+ console.log(` • ${server.name}`);
139
+ console.log(` ${description}`);
140
+ });
141
+ console.log("");
142
+
143
+ // Confirmation prompt
144
+ const confirmResponse = await prompts(
145
+ {
146
+ type: "confirm",
147
+ name: "continue",
148
+ message: "Continue with this configuration?",
149
+ initial: true,
150
+ },
151
+ {
152
+ onCancel: () => {
153
+ warn("Setup cancelled.");
154
+ process.exit(0);
155
+ },
156
+ },
157
+ );
158
+
159
+ if (confirmResponse.continue) {
160
+ profileConfirmed = true;
161
+ } else {
162
+ // User wants to go back - loop will restart profile selection
163
+ console.log("");
164
+ }
165
+ }
166
+ }
75
167
 
76
168
  const ideChoices = config.ides.map((ide) => ({
77
169
  title: ide.displayName,
@@ -81,7 +173,6 @@ async function run() {
81
173
  let scope = args.scope;
82
174
  let mcpScope = null;
83
175
  let ideSelection = config.ides.map((ide) => ide.id);
84
- let installSkills = true;
85
176
 
86
177
  if (!args.autoYes) {
87
178
  const response = await prompts(
@@ -89,7 +180,7 @@ async function run() {
89
180
  {
90
181
  type: scope ? null : "select",
91
182
  name: "scope",
92
- message: "Install skills + repo locally or globally?",
183
+ message: "Install skills locally or globally?",
93
184
  choices: [
94
185
  {
95
186
  title: `Local to this project (${formatPath(projectRoot)})`,
@@ -121,18 +212,10 @@ async function run() {
121
212
  {
122
213
  type: "multiselect",
123
214
  name: "ides",
124
- message: "Configure MCP for which IDEs?",
215
+ message: "Configure for which IDEs?",
125
216
  choices: ideChoices,
126
217
  initial: ideChoices.map((_, index) => index),
127
218
  },
128
- {
129
- type: "toggle",
130
- name: "installSkills",
131
- message: "Install skills into IDE skills folders?",
132
- active: "yes",
133
- inactive: "no",
134
- initial: true,
135
- },
136
219
  ],
137
220
  {
138
221
  onCancel: () => {
@@ -145,7 +228,6 @@ async function run() {
145
228
  scope = scope || response.scope;
146
229
  mcpScope = response.mcpScope || "local";
147
230
  ideSelection = response.ides || ideSelection;
148
- installSkills = response.installSkills;
149
231
  }
150
232
 
151
233
  if (!scope) {
@@ -166,33 +248,29 @@ async function run() {
166
248
  // Create temp directory for npm install
167
249
  const tempDir = path.join(getTempDir(), `.a11y-devkit-${Date.now()}`);
168
250
 
169
- if (installSkills) {
170
- const skillsSpinner = startSpinner("Installing skills from npm...");
251
+ const skillsSpinner = startSpinner("Installing skills from npm...");
171
252
 
172
- try {
173
- const skillTargets =
174
- scope === "local"
175
- ? ideSelection.map((ide) => idePaths[ide].localSkillsDir)
176
- : ideSelection.map((ide) => idePaths[ide].skillsDir);
253
+ try {
254
+ const skillTargets =
255
+ scope === "local"
256
+ ? ideSelection.map((ide) => idePaths[ide].localSkillsDir)
257
+ : ideSelection.map((ide) => idePaths[ide].skillsDir);
177
258
 
178
- const skillNames = config.skills.map((skill) =>
179
- typeof skill === "string" ? skill : skill.name,
180
- );
181
- const result = await installSkillsFromNpm(
182
- skillNames,
183
- skillTargets,
184
- tempDir,
185
- config.skillsFolder,
186
- config.readmeTemplate,
187
- );
188
- skillsSpinner.succeed(
189
- `${result.installed} skills installed to ${skillTargets.length} IDE location(s).`,
190
- );
191
- } catch (error) {
192
- skillsSpinner.fail(`Failed to install skills: ${error.message}`);
193
- }
194
- } else {
195
- warn("Skipping skills install to IDE folders.");
259
+ const skillNames = skillsToInstall.map((skill) =>
260
+ typeof skill === "string" ? skill : skill.npmName,
261
+ );
262
+ const result = await installSkillsFromNpm(
263
+ skillNames,
264
+ skillTargets,
265
+ tempDir,
266
+ config.skillsFolder,
267
+ config.readmeTemplate,
268
+ );
269
+ skillsSpinner.succeed(
270
+ `${result.installed} skills installed to ${skillTargets.length} IDE location(s).`,
271
+ );
272
+ } catch (error) {
273
+ skillsSpinner.fail(`Failed to install skills: ${error.message}`);
196
274
  }
197
275
 
198
276
  // Configure MCP servers using npx (no local installation needed!)
@@ -206,7 +284,7 @@ async function run() {
206
284
  const ide = ideSelection[i];
207
285
  await installMcpConfig(
208
286
  mcpConfigPaths[i],
209
- config.mcpServers,
287
+ mcpServersToInstall,
210
288
  idePaths[ide].mcpServerKey,
211
289
  );
212
290
  }
@@ -239,4 +317,198 @@ async function run() {
239
317
  info("Documentation: https://github.com/joe-watkins/a11y-devkit#readme");
240
318
  }
241
319
 
320
+ async function runGitMcpInstallation(projectRoot, platformInfo, config, idePaths, args) {
321
+ // Check if --yes flag is used with --git-mcp
322
+ if (args.autoYes) {
323
+ warn("--yes flag not supported for Git MCP installation");
324
+ info("Interactive prompts required for Git MCP configuration");
325
+ process.exit(1);
326
+ }
327
+
328
+ console.log("\n");
329
+ info("Installing MCP server from Git repository");
330
+ console.log("");
331
+
332
+ // Collect Git MCP information
333
+ const gitMcpPrompts = getGitMcpPrompts();
334
+ const mcpInfo = await prompts(gitMcpPrompts, {
335
+ onCancel: () => {
336
+ warn("Git MCP installation cancelled.");
337
+ process.exit(0);
338
+ },
339
+ });
340
+
341
+ // Parse args string into array
342
+ const argsArray = parseArgsString(mcpInfo.args);
343
+
344
+ // Prompt for Repo Clone Scope (where to clone the Git repository)
345
+ const repoScopeResponse = await prompts(
346
+ {
347
+ type: "select",
348
+ name: "repoScope",
349
+ message: "Where to clone the Git repository?",
350
+ choices: [
351
+ {
352
+ title: `Local to this project (${formatPath(projectRoot)})`,
353
+ value: "local",
354
+ description: "Clone to .a11y-devkit/mcp-repos/ in project",
355
+ },
356
+ {
357
+ title: "Global for this user",
358
+ value: "global",
359
+ description: "Clone to user-level app support directory",
360
+ },
361
+ ],
362
+ initial: 0,
363
+ },
364
+ {
365
+ onCancel: () => {
366
+ warn("Git MCP installation cancelled.");
367
+ process.exit(0);
368
+ },
369
+ },
370
+ );
371
+
372
+ // Prompt for MCP Config Scope (where to write MCP configurations)
373
+ const mcpScopeResponse = await prompts(
374
+ {
375
+ type: "select",
376
+ name: "mcpScope",
377
+ message: "Where to write MCP configurations?",
378
+ choices: [
379
+ {
380
+ title: `Local to this project (${formatPath(projectRoot)})`,
381
+ value: "local",
382
+ description: "Write to project-level IDE config folders",
383
+ },
384
+ {
385
+ title: "Global for this user",
386
+ value: "global",
387
+ description: "Write to user-level IDE config folders",
388
+ },
389
+ ],
390
+ initial: 0,
391
+ },
392
+ {
393
+ onCancel: () => {
394
+ warn("Git MCP installation cancelled.");
395
+ process.exit(0);
396
+ },
397
+ },
398
+ );
399
+
400
+ // Prompt for IDE selection
401
+ const ideChoices = config.ides.map((ide) => ({
402
+ title: ide.displayName,
403
+ value: ide.id,
404
+ }));
405
+
406
+ const ideResponse = await prompts(
407
+ {
408
+ type: "multiselect",
409
+ name: "ides",
410
+ message: "Configure MCP for which IDEs?",
411
+ choices: ideChoices,
412
+ initial: ideChoices.map((_, index) => index),
413
+ },
414
+ {
415
+ onCancel: () => {
416
+ warn("Git MCP installation cancelled.");
417
+ process.exit(0);
418
+ },
419
+ },
420
+ );
421
+
422
+ const ideSelection = ideResponse.ides || [];
423
+
424
+ if (!ideSelection.length) {
425
+ warn("No IDEs selected. MCP installation requires at least one IDE.");
426
+ process.exit(1);
427
+ }
428
+
429
+ const repoScope = repoScopeResponse.repoScope;
430
+ const mcpScope = mcpScopeResponse.mcpScope;
431
+
432
+ info(`Repository clone scope: ${repoScope === "local" ? "Local" : "Global"}`);
433
+ info(`MCP config scope: ${mcpScope === "local" ? "Local" : "Global"}`);
434
+
435
+ // Install Git MCP
436
+ const gitSpinner = startSpinner("Cloning Git repository...");
437
+
438
+ let mcpServer;
439
+ try {
440
+ mcpServer = await installGitMcp(
441
+ {
442
+ name: mcpInfo.name,
443
+ repoUrl: mcpInfo.repoUrl,
444
+ type: mcpInfo.type,
445
+ command: mcpInfo.command,
446
+ args: argsArray,
447
+ buildCommand: mcpInfo.buildCommand,
448
+ },
449
+ repoScope,
450
+ projectRoot,
451
+ platformInfo,
452
+ getMcpRepoDir,
453
+ );
454
+ gitSpinner.succeed(`Repository cloned to ${mcpServer.repoPath}`);
455
+ } catch (error) {
456
+ gitSpinner.fail(`Failed to install Git MCP: ${error.message}`);
457
+ process.exit(1);
458
+ }
459
+
460
+ // Install MCP configurations to selected IDEs
461
+ const mcpConfigSpinner = startSpinner("Updating MCP configurations...");
462
+
463
+ const mcpConfigPaths =
464
+ mcpScope === "local"
465
+ ? ideSelection.map((ide) => idePaths[ide].localMcpConfig)
466
+ : ideSelection.map((ide) => idePaths[ide].mcpConfig);
467
+
468
+ // Construct the MCP server configuration with absolute path
469
+ const mcpServerConfig = {
470
+ name: mcpServer.name,
471
+ type: mcpServer.type,
472
+ command: mcpServer.command,
473
+ args: mcpServer.args.length > 0 ? mcpServer.args : undefined,
474
+ };
475
+
476
+ // If args are provided, prepend the repo path to the first argument
477
+ // Otherwise, use the repo path as the only argument
478
+ if (mcpServerConfig.args && mcpServerConfig.args.length > 0) {
479
+ // Assume first arg is a relative path within the repo
480
+ mcpServerConfig.args = [
481
+ path.join(mcpServer.repoPath, mcpServerConfig.args[0]),
482
+ ...mcpServerConfig.args.slice(1),
483
+ ];
484
+ } else {
485
+ // No args provided - this might need to be handled differently
486
+ // For now, don't add any args
487
+ delete mcpServerConfig.args;
488
+ }
489
+
490
+ for (let i = 0; i < ideSelection.length; i++) {
491
+ const ide = ideSelection[i];
492
+ await installMcpConfig(
493
+ mcpConfigPaths[i],
494
+ [mcpServerConfig],
495
+ idePaths[ide].mcpServerKey,
496
+ );
497
+ }
498
+
499
+ mcpConfigSpinner.succeed(
500
+ `MCP configs updated for ${ideSelection.length} IDE(s) (${mcpScope} scope).`,
501
+ );
502
+
503
+ // Display success message
504
+ success("Git MCP installation complete!");
505
+ info(`Repository location: ${mcpServer.repoPath}`);
506
+ info(`MCP server '${mcpServer.name}' configured in ${ideSelection.length} IDE(s)`);
507
+ console.log("");
508
+ success("Next Steps:");
509
+ info("Restart your IDE to load the new MCP server");
510
+ info(`Repository cloned to: ${mcpServer.repoPath}`);
511
+ info("You can manually edit the MCP configuration files if needed");
512
+ }
513
+
242
514
  export { run };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Git repository operations and MCP installation orchestration
3
+ */
4
+
5
+ import fs from "fs/promises";
6
+ import path from "path";
7
+ import { spawn } from "child_process";
8
+ import prompts from "prompts";
9
+ import { warn, error as errorMsg } from "../ui.js";
10
+
11
+ /**
12
+ * Helper function to check if a path exists
13
+ * @param {string} target - Path to check
14
+ * @returns {Promise<boolean>}
15
+ */
16
+ async function pathExists(target) {
17
+ try {
18
+ await fs.access(target);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Runs a command using spawn
27
+ * @param {string} command - Command to run
28
+ * @param {string[]} args - Command arguments
29
+ * @param {object} options - Spawn options
30
+ * @returns {Promise<{stdout: string, stderr: string}>}
31
+ */
32
+ function run(command, args, options = {}) {
33
+ return new Promise((resolve, reject) => {
34
+ const child = spawn(command, args, {
35
+ stdio: "pipe",
36
+ shell: true,
37
+ ...options,
38
+ });
39
+ let stdout = "";
40
+ let stderr = "";
41
+
42
+ child.stdout?.on("data", (data) => {
43
+ stdout += data;
44
+ });
45
+ child.stderr?.on("data", (data) => {
46
+ stderr += data;
47
+ });
48
+
49
+ child.on("error", reject);
50
+ child.on("close", (code) => {
51
+ if (code === 0) {
52
+ resolve({ stdout, stderr });
53
+ return;
54
+ }
55
+ reject(
56
+ new Error(
57
+ `${command} ${args.join(" ")} failed with code ${code}: ${stderr}`,
58
+ ),
59
+ );
60
+ });
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Clones a Git repository to the target directory
66
+ * @param {string} repoUrl - Git repository URL
67
+ * @param {string} targetDir - Directory to clone into
68
+ * @returns {Promise<{success: boolean, stdout: string, stderr: string}>}
69
+ */
70
+ export async function cloneGitRepo(repoUrl, targetDir) {
71
+ try {
72
+ const result = await run("git", ["clone", repoUrl, targetDir]);
73
+ return {
74
+ success: true,
75
+ stdout: result.stdout,
76
+ stderr: result.stderr,
77
+ };
78
+ } catch (err) {
79
+ // Provide helpful error messages based on error type
80
+ const errorMessage = err.message.toLowerCase();
81
+
82
+ if (errorMessage.includes("authentication") || errorMessage.includes("fatal: could not read")) {
83
+ throw new Error(
84
+ `Authentication required for ${repoUrl}. Please ensure the repository is public or configure Git credentials.`,
85
+ );
86
+ }
87
+
88
+ if (errorMessage.includes("repository not found") || errorMessage.includes("not found")) {
89
+ throw new Error(
90
+ `Repository not found: ${repoUrl}. Please verify the URL is correct.`,
91
+ );
92
+ }
93
+
94
+ if (errorMessage.includes("git: command not found") || errorMessage.includes("'git' is not recognized")) {
95
+ throw new Error(
96
+ "Git is not installed. Please install Git and try again.",
97
+ );
98
+ }
99
+
100
+ // Generic error
101
+ throw new Error(`Failed to clone repository: ${err.message}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Executes build command in the repository directory
107
+ * @param {string} repoDir - Repository directory path
108
+ * @param {string} buildCommand - Build command to execute
109
+ * @returns {Promise<{success: boolean, stdout: string, stderr: string}>}
110
+ */
111
+ export async function buildGitRepo(repoDir, buildCommand) {
112
+ try {
113
+ // Parse build command (handle multi-command strings like "npm install && npm run build")
114
+ // We'll execute the entire command string via shell
115
+ const result = await run(buildCommand, [], { cwd: repoDir });
116
+ return {
117
+ success: true,
118
+ stdout: result.stdout,
119
+ stderr: result.stderr,
120
+ };
121
+ } catch (err) {
122
+ return {
123
+ success: false,
124
+ stdout: err.stdout || "",
125
+ stderr: err.stderr || err.message,
126
+ };
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Removes a cloned repository directory
132
+ * @param {string} repoPath - Path to repository to remove
133
+ * @returns {Promise<void>}
134
+ */
135
+ export async function cleanupGitRepo(repoPath) {
136
+ if (await pathExists(repoPath)) {
137
+ await fs.rm(repoPath, { recursive: true, force: true });
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Main orchestrator function for Git MCP installation
143
+ * @param {object} mcpConfig - MCP configuration {name, repoUrl, type, command, args, buildCommand}
144
+ * @param {string} repoScope - Where to clone repo ('local' | 'global')
145
+ * @param {string} projectRoot - Project root directory
146
+ * @param {object} platformInfo - Platform info object
147
+ * @param {Function} getMcpRepoDir - Function to get MCP repo directory
148
+ * @returns {Promise<{name: string, type: string, command: string, args: string[], repoPath: string}>}
149
+ */
150
+ export async function installGitMcp(
151
+ mcpConfig,
152
+ repoScope,
153
+ projectRoot,
154
+ platformInfo,
155
+ getMcpRepoDir,
156
+ ) {
157
+ const { name, repoUrl, type, command, args, buildCommand } = mcpConfig;
158
+
159
+ // Determine repo storage location
160
+ const repoPath = getMcpRepoDir(repoScope, projectRoot, platformInfo, name);
161
+
162
+ // Check for existing MCP with same name
163
+ if (await pathExists(repoPath)) {
164
+ warn(`MCP server '${name}' already exists at ${repoPath}`);
165
+
166
+ const overwriteResponse = await prompts(
167
+ {
168
+ type: "confirm",
169
+ name: "overwrite",
170
+ message: "Overwrite existing installation?",
171
+ initial: false,
172
+ },
173
+ {
174
+ onCancel: () => {
175
+ throw new Error("Installation cancelled by user");
176
+ },
177
+ },
178
+ );
179
+
180
+ if (!overwriteResponse.overwrite) {
181
+ throw new Error("Installation cancelled - existing MCP not overwritten");
182
+ }
183
+
184
+ // Remove old directory
185
+ await cleanupGitRepo(repoPath);
186
+ }
187
+
188
+ // Clone repository
189
+ await cloneGitRepo(repoUrl, repoPath);
190
+
191
+ // Run build command (continue with warning if fails)
192
+ const buildResult = await buildGitRepo(repoPath, buildCommand);
193
+ if (!buildResult.success) {
194
+ warn("Build command failed but continuing with installation");
195
+ if (buildResult.stderr) {
196
+ console.error("Build error output:");
197
+ console.error(buildResult.stderr);
198
+ }
199
+ }
200
+
201
+ // Return MCP server object for config installation
202
+ return {
203
+ name,
204
+ type,
205
+ command,
206
+ args,
207
+ repoPath,
208
+ };
209
+ }
@@ -33,10 +33,17 @@ function mergeServers(existing, incoming, serverKey = "servers") {
33
33
  const merged = { ...existing, [serverKey]: { ...existingServers } };
34
34
 
35
35
  for (const server of incoming) {
36
- merged[serverKey][server.name] = {
36
+ const serverConfig = {
37
37
  command: server.command,
38
38
  args: server.args || []
39
39
  };
40
+
41
+ // Include type if provided
42
+ if (server.type) {
43
+ serverConfig.type = server.type;
44
+ }
45
+
46
+ merged[serverKey][server.name] = serverConfig;
40
47
  }
41
48
 
42
49
  return merged;
package/src/paths.js CHANGED
@@ -32,19 +32,45 @@ function getAppSupportDir(platformInfo = getPlatform()) {
32
32
 
33
33
  function getIdePaths(projectRoot, platformInfo = getPlatform(), ideConfigs = []) {
34
34
  const home = os.homedir();
35
+ const appSupport = getAppSupportDir(platformInfo);
35
36
  const paths = {};
36
37
 
37
38
  for (const ide of ideConfigs) {
38
- // Use custom paths from config, or fall back to default pattern: ~/.{id}/
39
- const skillsFolder = ide.skillsFolder || `.${ide.id}/skills`;
40
- const mcpConfigFile = ide.mcpConfigFile || `.${ide.id}/mcp.json`;
39
+ // Default paths (used for local scope, and global scope if no overrides)
40
+ let skillsFolder = ide.skillsFolder || `.${ide.id}/skills`;
41
+ let mcpConfigFile = ide.mcpConfigFile || `.${ide.id}/mcp.json`;
42
+
43
+ // Check for platform-specific global path overrides
44
+ let globalSkillsFolder = skillsFolder;
45
+ let globalMcpConfigFile = mcpConfigFile;
46
+
47
+ if (ide.globalPaths) {
48
+ const platformKey = platformInfo.isWindows ? 'Win' : platformInfo.isMac ? 'macOS' : null;
49
+
50
+ if (platformKey && ide.globalPaths[platformKey]) {
51
+ const globalOverrides = ide.globalPaths[platformKey];
52
+
53
+ // Use app support directory + override path for global
54
+ if (globalOverrides.skillsFolder) {
55
+ globalSkillsFolder = globalOverrides.skillsFolder;
56
+ }
57
+ if (globalOverrides.mcpConfigFile) {
58
+ globalMcpConfigFile = globalOverrides.mcpConfigFile;
59
+ }
60
+ }
61
+ }
62
+
63
+ // Determine base directory for global paths
64
+ const useAppSupport = ide.globalPaths &&
65
+ (platformInfo.isWindows || platformInfo.isMac);
66
+ const globalBase = useAppSupport ? appSupport : home;
41
67
 
42
68
  paths[ide.id] = {
43
69
  name: ide.displayName,
44
- mcpConfig: path.join(home, mcpConfigFile),
70
+ mcpConfig: path.join(globalBase, globalMcpConfigFile),
45
71
  localMcpConfig: path.join(projectRoot, mcpConfigFile),
46
72
  mcpServerKey: ide.mcpServerKey,
47
- skillsDir: path.join(home, skillsFolder),
73
+ skillsDir: path.join(globalBase, globalSkillsFolder),
48
74
  localSkillsDir: path.join(projectRoot, skillsFolder)
49
75
  };
50
76
  }
@@ -52,9 +78,19 @@ function getIdePaths(projectRoot, platformInfo = getPlatform(), ideConfigs = [])
52
78
  return paths;
53
79
  }
54
80
 
81
+ function getMcpRepoDir(scope, projectRoot, platformInfo, mcpName) {
82
+ if (scope === 'local') {
83
+ return path.join(projectRoot, '.a11y-devkit', 'mcp-repos', mcpName);
84
+ }
85
+
86
+ const appSupport = getAppSupportDir(platformInfo);
87
+ return path.join(appSupport, 'a11y-devkit', 'mcp-repos', mcpName);
88
+ }
89
+
55
90
  export {
56
91
  getPlatform,
57
92
  getAppSupportDir,
58
93
  getIdePaths,
59
- getTempDir
94
+ getTempDir,
95
+ getMcpRepoDir
60
96
  };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Git MCP-specific prompt definitions and validation logic
3
+ */
4
+
5
+ /**
6
+ * Validates MCP name according to rules:
7
+ * - Not empty
8
+ * - No spaces
9
+ * - Only alphanumeric characters and hyphens
10
+ * @param {string} name - MCP name to validate
11
+ * @returns {true | string} - true if valid, error message if invalid
12
+ */
13
+ export function validateMcpName(name) {
14
+ if (!name || name.trim() === "") {
15
+ return "MCP name is required";
16
+ }
17
+
18
+ if (name.includes(" ")) {
19
+ return "MCP name cannot contain spaces";
20
+ }
21
+
22
+ const validPattern = /^[a-z0-9-]+$/i;
23
+ if (!validPattern.test(name)) {
24
+ return "MCP name can only contain letters, numbers, and hyphens";
25
+ }
26
+
27
+ return true;
28
+ }
29
+
30
+ /**
31
+ * Validates Git URL format (GitHub or GitLab)
32
+ * @param {string} url - Git repository URL to validate
33
+ * @returns {{ valid: boolean, provider: 'github'|'gitlab'|null, error: string|null }}
34
+ */
35
+ export function validateGitUrl(url) {
36
+ if (!url || url.trim() === "") {
37
+ return {
38
+ valid: false,
39
+ provider: null,
40
+ error: "Git repository URL is required",
41
+ };
42
+ }
43
+
44
+ const gitUrlPattern =
45
+ /^https:\/\/(github\.com|gitlab\.com)\/[\w-]+\/[\w-]+(\.git)?$/;
46
+ const match = url.match(gitUrlPattern);
47
+
48
+ if (!match) {
49
+ if (url.includes("github.com") || url.includes("gitlab.com")) {
50
+ return {
51
+ valid: false,
52
+ provider: null,
53
+ error:
54
+ "Invalid Git URL format. Must be: https://github.com/user/repo.git or https://gitlab.com/user/repo.git",
55
+ };
56
+ }
57
+ return {
58
+ valid: false,
59
+ provider: null,
60
+ error: "Only GitHub and GitLab repositories are supported",
61
+ };
62
+ }
63
+
64
+ const provider = match[1].includes("github") ? "github" : "gitlab";
65
+
66
+ return {
67
+ valid: true,
68
+ provider,
69
+ error: null,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Parses comma-separated arguments string into array
75
+ * @param {string} argsString - Comma-separated arguments
76
+ * @returns {string[]} - Array of trimmed argument strings
77
+ */
78
+ export function parseArgsString(argsString) {
79
+ if (!argsString || argsString.trim() === "") {
80
+ return [];
81
+ }
82
+
83
+ return argsString
84
+ .split(",")
85
+ .map((arg) => arg.trim())
86
+ .filter((arg) => arg !== "");
87
+ }
88
+
89
+ /**
90
+ * Returns prompts array for Git MCP configuration
91
+ * @returns {Array} - Array of prompt objects for use with prompts library
92
+ */
93
+ export function getGitMcpPrompts() {
94
+ return [
95
+ {
96
+ type: "text",
97
+ name: "name",
98
+ message: "MCP Name (no spaces, alphanumeric with hyphens):",
99
+ validate: (value) => validateMcpName(value),
100
+ },
101
+ {
102
+ type: "text",
103
+ name: "repoUrl",
104
+ message: "Git Repository URL (GitHub or GitLab):",
105
+ validate: (value) => {
106
+ const result = validateGitUrl(value);
107
+ return result.valid ? true : result.error;
108
+ },
109
+ },
110
+ {
111
+ type: "select",
112
+ name: "type",
113
+ message: "MCP server transport type:",
114
+ choices: [
115
+ { title: "stdio", value: "stdio" },
116
+ { title: "http", value: "http" },
117
+ { title: "sse", value: "sse" },
118
+ ],
119
+ initial: 0,
120
+ },
121
+ {
122
+ type: "text",
123
+ name: "command",
124
+ message: "Command to run the MCP server:",
125
+ initial: "node",
126
+ },
127
+ {
128
+ type: "text",
129
+ name: "args",
130
+ message: "Arguments for the command (typically relative path to mcp .js example: js/index.js):",
131
+ initial: "",
132
+ },
133
+ {
134
+ type: "text",
135
+ name: "buildCommand",
136
+ message: "Build command:",
137
+ initial: "npm install",
138
+ },
139
+ ];
140
+ }