a11y-devkit-deploy 0.9.7 → 1.0.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 +89 -96
- package/config/settings.json +13 -15
- package/package.json +1 -1
- package/src/cli.js +45 -131
- package/src/installers/mcp.js +14 -5
- package/src/installers/skills.js +16 -10
- package/src/paths.js +14 -18
package/README.md
CHANGED
|
@@ -18,13 +18,15 @@ npx a11y-devkit-deploy
|
|
|
18
18
|
a11y-devkit-deploy
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
### Flags
|
|
22
|
-
|
|
23
|
-
- `--local` / `--global`: Skip the scope prompt.
|
|
24
|
-
- `--yes`: Use defaults (local scope, all IDEs, install skills).
|
|
25
|
-
- `--uninstall`: Remove skills and MCP entries installed by this tool.
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
### Flags
|
|
22
|
+
|
|
23
|
+
- `--local` / `--global`: Skip the scope prompt.
|
|
24
|
+
- `--yes`: Use defaults (local scope, all IDEs, install skills).
|
|
25
|
+
- `--uninstall`: Remove skills and MCP entries installed by this tool.
|
|
26
|
+
|
|
27
|
+
**Note:** MCP configs are always written globally (AppData/Application Support). The scope flag only affects skills.
|
|
28
|
+
|
|
29
|
+
## After Installation
|
|
28
30
|
|
|
29
31
|
Once installation completes, you'll find a comprehensive usage guide in your IDE's skills directory:
|
|
30
32
|
|
|
@@ -86,16 +88,16 @@ This CLI automates the setup of accessibility tooling by:
|
|
|
86
88
|
- MCP servers to configure
|
|
87
89
|
- Even the IDE list itself
|
|
88
90
|
|
|
89
|
-
**Adding Support for a New IDE** takes just 5 lines of JSON:
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"id": "new-ide",
|
|
93
|
-
"displayName": "New IDE",
|
|
94
|
-
"mcpServerKey": "servers",
|
|
95
|
-
"skillsFolder": ".new-ide/skills",
|
|
96
|
-
"mcpConfigFile": ".new-ide/mcp.json"
|
|
97
|
-
}
|
|
98
|
-
```
|
|
91
|
+
**Adding Support for a New IDE** takes just 5 lines of JSON:
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"id": "new-ide",
|
|
95
|
+
"displayName": "New IDE",
|
|
96
|
+
"mcpServerKey": "servers",
|
|
97
|
+
"skillsFolder": ".new-ide/skills",
|
|
98
|
+
"mcpConfigFile": ".new-ide/mcp.json"
|
|
99
|
+
}
|
|
100
|
+
```
|
|
99
101
|
|
|
100
102
|
**Safe by Default** - Won't overwrite your existing:
|
|
101
103
|
- Custom MCP servers in your IDE configs
|
|
@@ -193,38 +195,34 @@ Add an object to the `hostApplications` array with the host application's config
|
|
|
193
195
|
|
|
194
196
|
```json
|
|
195
197
|
{
|
|
196
|
-
"hostApplications": [
|
|
197
|
-
{
|
|
198
|
-
"id": "windsurf",
|
|
199
|
-
"displayName": "Windsurf",
|
|
200
|
-
"mcpServerKey": "servers",
|
|
201
|
-
"skillsFolder": ".codeium/windsurf/skills",
|
|
202
|
-
"mcpConfigFile": ".codeium/windsurf/mcp_config.json"
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
"id": "vscode",
|
|
206
|
-
"displayName": "VSCode",
|
|
207
|
-
"mcpServerKey": "servers",
|
|
208
|
-
"skillsFolder": ".github/skills",
|
|
209
|
-
"mcpConfigFile": "
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
- `
|
|
220
|
-
- `
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
- `
|
|
224
|
-
- `globalMcpConfigFile` - (Optional) Path to global MCP config relative to AppData/Application Support instead of home directory. Used for hosts like VSCode that store configs in platform-specific app directories:
|
|
225
|
-
- Windows: `%APPDATA%` (e.g., `C:\Users\name\AppData\Roaming`)
|
|
226
|
-
- macOS: `~/Library/Application Support`
|
|
227
|
-
- Linux: `$XDG_CONFIG_HOME` or `~/.config`
|
|
198
|
+
"hostApplications": [
|
|
199
|
+
{
|
|
200
|
+
"id": "windsurf",
|
|
201
|
+
"displayName": "Windsurf",
|
|
202
|
+
"mcpServerKey": "servers",
|
|
203
|
+
"skillsFolder": ".codeium/windsurf/skills",
|
|
204
|
+
"mcpConfigFile": ".codeium/windsurf/mcp_config.json"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"id": "vscode",
|
|
208
|
+
"displayName": "VSCode",
|
|
209
|
+
"mcpServerKey": "servers",
|
|
210
|
+
"skillsFolder": ".github/skills",
|
|
211
|
+
"mcpConfigFile": "Code/User/mcp.json"
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Host Application Configuration Properties:**
|
|
218
|
+
- `id` - Unique identifier for the host application
|
|
219
|
+
- `displayName` - Human-readable name shown in prompts
|
|
220
|
+
- `mcpServerKey` - MCP config key name (`"servers"`, `"mcpServers"`, or `"mcp_servers"` for TOML)
|
|
221
|
+
- `skillsFolder` - Path to skills directory (relative to home/project root)
|
|
222
|
+
- `mcpConfigFile` - Path to MCP config file (relative to AppData/Application Support). Supports both JSON (`.json`) and TOML (`.toml`) formats. TOML format is auto-detected by file extension (used by Codex):
|
|
223
|
+
- Windows: `%APPDATA%` (e.g., `C:\Users\name\AppData\Roaming`)
|
|
224
|
+
- macOS: `~/Library/Application Support`
|
|
225
|
+
- Linux: `$XDG_CONFIG_HOME` or `~/.config`
|
|
228
226
|
|
|
229
227
|
**Note:** Codex uses TOML format for its MCP configuration (`~/.codex/config.toml`), which requires the `mcpServerKey` to be `"mcp_servers"` and generates config entries like:
|
|
230
228
|
```toml
|
|
@@ -252,53 +250,48 @@ The CLI **safely merges** with existing configurations:
|
|
|
252
250
|
|
|
253
251
|
## Directory Structure
|
|
254
252
|
|
|
255
|
-
### Local Install (Project-Specific)
|
|
256
|
-
```
|
|
257
|
-
your-project/
|
|
258
|
-
├── .claude/
|
|
259
|
-
│
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
│ └── skills/ #
|
|
264
|
-
├── .
|
|
265
|
-
│
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
~/.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
└── skills/ #
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# VSCode MCP config lives in AppData/Application Support:
|
|
299
|
-
# Windows: %APPDATA%/Code/User/mcp.json
|
|
300
|
-
# macOS: ~/Library/Application Support/Code/User/mcp.json
|
|
301
|
-
```
|
|
253
|
+
### Local Install (Project-Specific)
|
|
254
|
+
```
|
|
255
|
+
your-project/
|
|
256
|
+
├── .claude/
|
|
257
|
+
│ └── skills/ # Claude Code skills
|
|
258
|
+
├── .cursor/
|
|
259
|
+
│ └── skills/ # Cursor skills
|
|
260
|
+
├── .codex/
|
|
261
|
+
│ └── skills/ # Codex skills
|
|
262
|
+
├── .github/
|
|
263
|
+
│ └── skills/ # VSCode skills
|
|
264
|
+
├── .codeium/windsurf/
|
|
265
|
+
│ └── skills/ # Windsurf skills
|
|
266
|
+
└── .factory/
|
|
267
|
+
└── skills/ # Factory skills
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Global Install (User-Wide)
|
|
271
|
+
```
|
|
272
|
+
~/.claude/
|
|
273
|
+
└── skills/ # Claude Code global skills
|
|
274
|
+
~/.cursor/
|
|
275
|
+
└── skills/ # Cursor global skills
|
|
276
|
+
~/.codex/
|
|
277
|
+
└── skills/ # Codex global skills
|
|
278
|
+
~/.github/
|
|
279
|
+
└── skills/ # VSCode global skills
|
|
280
|
+
~/.codeium/windsurf/
|
|
281
|
+
└── skills/ # Windsurf global skills
|
|
282
|
+
~/.factory/
|
|
283
|
+
└── skills/ # Factory global skills
|
|
284
|
+
|
|
285
|
+
# MCP config files live in AppData/Application Support:
|
|
286
|
+
# Windows: %APPDATA%/.claude.json
|
|
287
|
+
# Windows: %APPDATA%/.cursor/mcp.json
|
|
288
|
+
# Windows: %APPDATA%/.codex/config.toml
|
|
289
|
+
# Windows: %APPDATA%/Code/User/mcp.json
|
|
290
|
+
# Windows: %APPDATA%/.codeium/windsurf/mcp_config.json
|
|
291
|
+
# Windows: %APPDATA%/.factory/mcp.json
|
|
292
|
+
# macOS: ~/Library/Application Support/<same paths as above>
|
|
293
|
+
# Linux: $XDG_CONFIG_HOME/<same paths as above> (or ~/.config)
|
|
294
|
+
```
|
|
302
295
|
|
|
303
296
|
**Note:** Paths are fully customizable per IDE in `config/settings.json`
|
|
304
297
|
|
package/config/settings.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"skillsFolder": "a11y",
|
|
3
3
|
"readmeTemplate": "deploy-README.md",
|
|
4
|
-
"supportLocalMcpInstallation": false,
|
|
5
4
|
"skills": [
|
|
6
5
|
{
|
|
7
6
|
"name": "A11y Base Web Skill",
|
|
@@ -50,51 +49,50 @@
|
|
|
50
49
|
{
|
|
51
50
|
"id": "claude",
|
|
52
51
|
"displayName": "Claude Code",
|
|
53
|
-
"mcpServerKey": "
|
|
54
|
-
"globalMcpServerKey": "mcpServers",
|
|
52
|
+
"mcpServerKey": "mcpServers",
|
|
55
53
|
"skillsFolder": ".claude/skills",
|
|
56
|
-
"mcpConfigFile": ".claude.json"
|
|
54
|
+
"mcpConfigFile": ".claude.json",
|
|
55
|
+
"nestSkillsFolder": false
|
|
57
56
|
},
|
|
58
57
|
{
|
|
59
58
|
"id": "cursor",
|
|
60
59
|
"displayName": "Cursor",
|
|
61
60
|
"mcpServerKey": "mcpServers",
|
|
62
|
-
"globalMcpServerKey": "mcpServers",
|
|
63
61
|
"skillsFolder": ".cursor/skills",
|
|
64
|
-
"mcpConfigFile": ".cursor/mcp.json"
|
|
62
|
+
"mcpConfigFile": ".cursor/mcp.json",
|
|
63
|
+
"nestSkillsFolder": true
|
|
65
64
|
},
|
|
66
65
|
{
|
|
67
66
|
"id": "codex",
|
|
68
67
|
"displayName": "Codex",
|
|
69
68
|
"mcpServerKey": "mcp_servers",
|
|
70
|
-
"globalMcpServerKey": "mcp_servers",
|
|
71
69
|
"skillsFolder": ".codex/skills",
|
|
72
|
-
"mcpConfigFile": ".codex/config.toml"
|
|
70
|
+
"mcpConfigFile": ".codex/config.toml",
|
|
71
|
+
"nestSkillsFolder": true
|
|
73
72
|
},
|
|
74
73
|
{
|
|
75
74
|
"id": "vscode",
|
|
76
75
|
"displayName": "VSCode",
|
|
77
76
|
"mcpServerKey": "servers",
|
|
78
|
-
"globalMcpServerKey": "servers",
|
|
79
77
|
"skillsFolder": ".github/skills",
|
|
80
|
-
"mcpConfigFile": "
|
|
81
|
-
"
|
|
78
|
+
"mcpConfigFile": "Code/User/mcp.json",
|
|
79
|
+
"nestSkillsFolder": true
|
|
82
80
|
},
|
|
83
81
|
{
|
|
84
82
|
"id": "windsurf",
|
|
85
83
|
"displayName": "Windsurf",
|
|
86
84
|
"mcpServerKey": "servers",
|
|
87
|
-
"globalMcpServerKey": "servers",
|
|
88
85
|
"skillsFolder": ".codeium/windsurf/skills",
|
|
89
|
-
"mcpConfigFile": ".codeium/windsurf/mcp_config.json"
|
|
86
|
+
"mcpConfigFile": ".codeium/windsurf/mcp_config.json",
|
|
87
|
+
"nestSkillsFolder": true
|
|
90
88
|
},
|
|
91
89
|
{
|
|
92
90
|
"id": "factory",
|
|
93
91
|
"displayName": "Factory",
|
|
94
92
|
"mcpServerKey": "mcpServers",
|
|
95
|
-
"globalMcpServerKey": "mcpServers",
|
|
96
93
|
"skillsFolder": ".factory/skills",
|
|
97
|
-
"mcpConfigFile": ".factory/mcp.json"
|
|
94
|
+
"mcpConfigFile": ".factory/mcp.json",
|
|
95
|
+
"nestSkillsFolder": true
|
|
98
96
|
}
|
|
99
97
|
],
|
|
100
98
|
"mcpServers": [
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -231,7 +231,6 @@ async function run() {
|
|
|
231
231
|
}));
|
|
232
232
|
|
|
233
233
|
let scope = args.scope;
|
|
234
|
-
let mcpScope = null;
|
|
235
234
|
let hostSelection = config.hostApplications.map((host) => host.id);
|
|
236
235
|
|
|
237
236
|
if (!args.autoYes) {
|
|
@@ -250,26 +249,6 @@ async function run() {
|
|
|
250
249
|
],
|
|
251
250
|
initial: 0,
|
|
252
251
|
},
|
|
253
|
-
{
|
|
254
|
-
type: config.supportLocalMcpInstallation ? "select" : null,
|
|
255
|
-
name: "mcpScope",
|
|
256
|
-
message: "Install MCP configs locally or globally?",
|
|
257
|
-
choices: [
|
|
258
|
-
{
|
|
259
|
-
title: `Local to this project (${formatPath(projectRoot)})`,
|
|
260
|
-
value: "local",
|
|
261
|
-
description:
|
|
262
|
-
"Write to project-level host application config folders (version-controllable)",
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
title: "Global for this user",
|
|
266
|
-
value: "global",
|
|
267
|
-
description:
|
|
268
|
-
"Write to user-level host application config folders",
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
initial: 0,
|
|
272
|
-
},
|
|
273
252
|
{
|
|
274
253
|
type: "multiselect",
|
|
275
254
|
name: "hosts",
|
|
@@ -287,18 +266,12 @@ async function run() {
|
|
|
287
266
|
);
|
|
288
267
|
|
|
289
268
|
scope = scope || response.scope;
|
|
290
|
-
mcpScope =
|
|
291
|
-
response.mcpScope ||
|
|
292
|
-
(config.supportLocalMcpInstallation ? "local" : "global");
|
|
293
269
|
hostSelection = response.hosts || hostSelection;
|
|
294
270
|
}
|
|
295
271
|
|
|
296
272
|
if (!scope) {
|
|
297
273
|
scope = "local";
|
|
298
274
|
}
|
|
299
|
-
if (!mcpScope) {
|
|
300
|
-
mcpScope = config.supportLocalMcpInstallation ? "local" : "global";
|
|
301
|
-
}
|
|
302
275
|
|
|
303
276
|
if (!hostSelection.length) {
|
|
304
277
|
warn(
|
|
@@ -308,7 +281,7 @@ async function run() {
|
|
|
308
281
|
}
|
|
309
282
|
|
|
310
283
|
info(`Skills scope: ${scope === "local" ? "Local" : "Global"}`);
|
|
311
|
-
info(
|
|
284
|
+
info("MCP configs: Global (user-level)");
|
|
312
285
|
|
|
313
286
|
// Create temp directory for npm install
|
|
314
287
|
const tempDir = path.join(getTempDir(), `.a11y-devkit-${Date.now()}`);
|
|
@@ -316,10 +289,17 @@ async function run() {
|
|
|
316
289
|
const skillsSpinner = startSpinner("Installing skills from npm...");
|
|
317
290
|
|
|
318
291
|
try {
|
|
319
|
-
const skillTargets =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
292
|
+
const skillTargets = hostSelection.map((hostId) => {
|
|
293
|
+
const host = config.hostApplications.find((h) => h.id === hostId);
|
|
294
|
+
const targetPath =
|
|
295
|
+
scope === "local"
|
|
296
|
+
? hostPaths[hostId].localSkillsDir
|
|
297
|
+
: hostPaths[hostId].skillsDir;
|
|
298
|
+
return {
|
|
299
|
+
path: targetPath,
|
|
300
|
+
shouldNest: host.nestSkillsFolder ?? true, // Default to true for backwards compatibility
|
|
301
|
+
};
|
|
302
|
+
});
|
|
323
303
|
|
|
324
304
|
const skillNames = skillsToInstall.map((skill) =>
|
|
325
305
|
typeof skill === "string" ? skill : skill.npmName,
|
|
@@ -340,24 +320,20 @@ async function run() {
|
|
|
340
320
|
|
|
341
321
|
// Configure MCP servers using npx (no local installation needed!)
|
|
342
322
|
const mcpSpinner = startSpinner("Updating MCP configurations...");
|
|
343
|
-
const mcpConfigPaths =
|
|
344
|
-
mcpScope === "local"
|
|
345
|
-
? hostSelection.map((host) => hostPaths[host].localMcpConfig)
|
|
346
|
-
: hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
323
|
+
const mcpConfigPaths = hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
347
324
|
|
|
348
325
|
for (let i = 0; i < hostSelection.length; i++) {
|
|
349
326
|
const host = hostSelection[i];
|
|
350
|
-
const serverKey =
|
|
351
|
-
? hostPaths[host].globalMcpServerKey
|
|
352
|
-
: hostPaths[host].mcpServerKey;
|
|
327
|
+
const serverKey = hostPaths[host].mcpServerKey;
|
|
353
328
|
await installMcpConfig(
|
|
354
329
|
mcpConfigPaths[i],
|
|
355
330
|
mcpServersToInstall,
|
|
356
331
|
serverKey,
|
|
332
|
+
platformInfo,
|
|
357
333
|
);
|
|
358
334
|
}
|
|
359
335
|
mcpSpinner.succeed(
|
|
360
|
-
`MCP configs updated for ${hostSelection.length} host application(s) (
|
|
336
|
+
`MCP configs updated for ${hostSelection.length} host application(s) (global scope).`,
|
|
361
337
|
);
|
|
362
338
|
|
|
363
339
|
// Clean up temporary directory
|
|
@@ -370,11 +346,15 @@ async function run() {
|
|
|
370
346
|
info("MCP servers use npx - no local installation needed!");
|
|
371
347
|
console.log("");
|
|
372
348
|
success("Next Steps:");
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
349
|
+
// Determine the README path based on the first selected host's nesting preference
|
|
350
|
+
const firstHost = config.hostApplications.find((h) => h.id === hostSelection[0]);
|
|
351
|
+
const firstHostSkillsPath = scope === "local"
|
|
352
|
+
? hostPaths[hostSelection[0]].localSkillsDir
|
|
353
|
+
: hostPaths[hostSelection[0]].skillsDir;
|
|
354
|
+
const skillsFolderPath = (firstHost?.nestSkillsFolder ?? true) && config.skillsFolder
|
|
355
|
+
? `${config.skillsFolder}/`
|
|
356
|
+
: "";
|
|
357
|
+
const skillsPath = `${firstHostSkillsPath}/${skillsFolderPath}a11y-devkit-README.md`;
|
|
378
358
|
info(`📖 Check ${skillsPath} for comprehensive usage guide`);
|
|
379
359
|
info("✨ Includes 70+ example prompts for all skills and MCP servers");
|
|
380
360
|
info(
|
|
@@ -399,7 +379,6 @@ async function runUninstall(
|
|
|
399
379
|
let removeSkills = true;
|
|
400
380
|
let removeMcp = true;
|
|
401
381
|
let scope = args.scope;
|
|
402
|
-
let mcpScope = null;
|
|
403
382
|
let hostSelection = config.hostApplications.map((host) => host.id);
|
|
404
383
|
|
|
405
384
|
if (!args.autoYes) {
|
|
@@ -456,29 +435,6 @@ async function runUninstall(
|
|
|
456
435
|
});
|
|
457
436
|
}
|
|
458
437
|
|
|
459
|
-
if (removeMcp && config.supportLocalMcpInstallation) {
|
|
460
|
-
questions.push({
|
|
461
|
-
type: "select",
|
|
462
|
-
name: "mcpScope",
|
|
463
|
-
message: "Remove MCP configs locally or globally?",
|
|
464
|
-
choices: [
|
|
465
|
-
{
|
|
466
|
-
title: `Local to this project (${formatPath(projectRoot)})`,
|
|
467
|
-
value: "local",
|
|
468
|
-
description:
|
|
469
|
-
"Remove from project-level host application config folders (version-controllable)",
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
title: "Global for this user",
|
|
473
|
-
value: "global",
|
|
474
|
-
description:
|
|
475
|
-
"Remove from user-level host application config folders",
|
|
476
|
-
},
|
|
477
|
-
],
|
|
478
|
-
initial: 0,
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
438
|
questions.push({
|
|
483
439
|
type: "multiselect",
|
|
484
440
|
name: "hosts",
|
|
@@ -495,7 +451,6 @@ async function runUninstall(
|
|
|
495
451
|
});
|
|
496
452
|
|
|
497
453
|
scope = scope || response.scope;
|
|
498
|
-
mcpScope = response.mcpScope || mcpScope;
|
|
499
454
|
hostSelection = response.hosts || hostSelection;
|
|
500
455
|
}
|
|
501
456
|
|
|
@@ -503,10 +458,6 @@ async function runUninstall(
|
|
|
503
458
|
scope = "local";
|
|
504
459
|
}
|
|
505
460
|
|
|
506
|
-
if (removeMcp && !mcpScope) {
|
|
507
|
-
mcpScope = config.supportLocalMcpInstallation ? "local" : "global";
|
|
508
|
-
}
|
|
509
|
-
|
|
510
461
|
if (!hostSelection.length) {
|
|
511
462
|
warn(
|
|
512
463
|
"No host applications selected. Uninstall requires at least one host application.",
|
|
@@ -518,16 +469,23 @@ async function runUninstall(
|
|
|
518
469
|
info(`Skills scope: ${scope === "local" ? "Local" : "Global"}`);
|
|
519
470
|
}
|
|
520
471
|
if (removeMcp) {
|
|
521
|
-
info(
|
|
472
|
+
info("MCP configs: Global (user-level)");
|
|
522
473
|
}
|
|
523
474
|
|
|
524
475
|
if (removeSkills) {
|
|
525
476
|
const skillsSpinner = startSpinner("Removing skills...");
|
|
526
477
|
try {
|
|
527
|
-
const skillTargets =
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
478
|
+
const skillTargets = hostSelection.map((hostId) => {
|
|
479
|
+
const host = config.hostApplications.find((h) => h.id === hostId);
|
|
480
|
+
const targetPath =
|
|
481
|
+
scope === "local"
|
|
482
|
+
? hostPaths[hostId].localSkillsDir
|
|
483
|
+
: hostPaths[hostId].skillsDir;
|
|
484
|
+
return {
|
|
485
|
+
path: targetPath,
|
|
486
|
+
shouldNest: host.nestSkillsFolder ?? true, // Default to true for backwards compatibility
|
|
487
|
+
};
|
|
488
|
+
});
|
|
531
489
|
|
|
532
490
|
const skillNames = config.skills.map((skill) =>
|
|
533
491
|
typeof skill === "string" ? skill : skill.npmName,
|
|
@@ -549,19 +507,14 @@ async function runUninstall(
|
|
|
549
507
|
|
|
550
508
|
if (removeMcp) {
|
|
551
509
|
const mcpSpinner = startSpinner("Removing MCP configurations...");
|
|
552
|
-
const mcpConfigPaths =
|
|
553
|
-
mcpScope === "local"
|
|
554
|
-
? hostSelection.map((host) => hostPaths[host].localMcpConfig)
|
|
555
|
-
: hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
510
|
+
const mcpConfigPaths = hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
556
511
|
|
|
557
512
|
let removedCount = 0;
|
|
558
513
|
const serverNames = config.mcpServers.map((server) => server.name);
|
|
559
514
|
|
|
560
515
|
for (let i = 0; i < hostSelection.length; i++) {
|
|
561
516
|
const host = hostSelection[i];
|
|
562
|
-
const serverKey =
|
|
563
|
-
? hostPaths[host].globalMcpServerKey
|
|
564
|
-
: hostPaths[host].mcpServerKey;
|
|
517
|
+
const serverKey = hostPaths[host].mcpServerKey;
|
|
565
518
|
const result = await removeMcpConfig(
|
|
566
519
|
mcpConfigPaths[i],
|
|
567
520
|
serverNames,
|
|
@@ -572,7 +525,7 @@ async function runUninstall(
|
|
|
572
525
|
|
|
573
526
|
if (removedCount > 0) {
|
|
574
527
|
mcpSpinner.succeed(
|
|
575
|
-
`Removed ${removedCount} MCP entries from ${hostSelection.length} host application(s) (
|
|
528
|
+
`Removed ${removedCount} MCP entries from ${hostSelection.length} host application(s) (global scope).`,
|
|
576
529
|
);
|
|
577
530
|
} else {
|
|
578
531
|
mcpSpinner.succeed("No matching MCP entries found to remove.");
|
|
@@ -640,40 +593,6 @@ async function runGitMcpInstallation(
|
|
|
640
593
|
},
|
|
641
594
|
);
|
|
642
595
|
|
|
643
|
-
// Prompt for MCP Config Scope (where to write MCP configurations)
|
|
644
|
-
// Skip if local MCP installation is not supported
|
|
645
|
-
let mcpScopeResponse = {
|
|
646
|
-
mcpScope: config.supportLocalMcpInstallation ? null : "global",
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
if (config.supportLocalMcpInstallation) {
|
|
650
|
-
mcpScopeResponse = await prompts(
|
|
651
|
-
{
|
|
652
|
-
type: "select",
|
|
653
|
-
name: "mcpScope",
|
|
654
|
-
message: "Where to write MCP configurations?",
|
|
655
|
-
choices: [
|
|
656
|
-
{
|
|
657
|
-
title: `Local to this project (${formatPath(projectRoot)})`,
|
|
658
|
-
value: "local",
|
|
659
|
-
description: "Write to project-level IDE config folders",
|
|
660
|
-
},
|
|
661
|
-
{
|
|
662
|
-
title: "Global for this user",
|
|
663
|
-
value: "global",
|
|
664
|
-
description: "Write to user-level IDE config folders",
|
|
665
|
-
},
|
|
666
|
-
],
|
|
667
|
-
initial: 0,
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
onCancel: () => {
|
|
671
|
-
warn("Git MCP installation cancelled.");
|
|
672
|
-
process.exit(0);
|
|
673
|
-
},
|
|
674
|
-
},
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
596
|
|
|
678
597
|
// Prompt for host application selection
|
|
679
598
|
const hostChoices = config.hostApplications.map((host) => ({
|
|
@@ -707,10 +626,9 @@ async function runGitMcpInstallation(
|
|
|
707
626
|
}
|
|
708
627
|
|
|
709
628
|
const repoScope = repoScopeResponse.repoScope;
|
|
710
|
-
const mcpScope = mcpScopeResponse.mcpScope;
|
|
711
629
|
|
|
712
630
|
info(`Repository clone scope: ${repoScope === "local" ? "Local" : "Global"}`);
|
|
713
|
-
info(
|
|
631
|
+
info("MCP configs: Global (user-level)");
|
|
714
632
|
|
|
715
633
|
// Install Git MCP
|
|
716
634
|
const gitSpinner = startSpinner("Cloning Git repository...");
|
|
@@ -740,10 +658,7 @@ async function runGitMcpInstallation(
|
|
|
740
658
|
// Install MCP configurations to selected host applications
|
|
741
659
|
const mcpConfigSpinner = startSpinner("Updating MCP configurations...");
|
|
742
660
|
|
|
743
|
-
const mcpConfigPaths =
|
|
744
|
-
mcpScope === "local"
|
|
745
|
-
? hostSelection.map((host) => hostPaths[host].localMcpConfig)
|
|
746
|
-
: hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
661
|
+
const mcpConfigPaths = hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
747
662
|
|
|
748
663
|
// Construct the MCP server configuration with absolute path
|
|
749
664
|
const mcpServerConfig = {
|
|
@@ -769,18 +684,17 @@ async function runGitMcpInstallation(
|
|
|
769
684
|
|
|
770
685
|
for (let i = 0; i < hostSelection.length; i++) {
|
|
771
686
|
const host = hostSelection[i];
|
|
772
|
-
const serverKey =
|
|
773
|
-
? hostPaths[host].globalMcpServerKey
|
|
774
|
-
: hostPaths[host].mcpServerKey;
|
|
687
|
+
const serverKey = hostPaths[host].mcpServerKey;
|
|
775
688
|
await installMcpConfig(
|
|
776
689
|
mcpConfigPaths[i],
|
|
777
690
|
[mcpServerConfig],
|
|
778
691
|
serverKey,
|
|
692
|
+
platformInfo,
|
|
779
693
|
);
|
|
780
694
|
}
|
|
781
695
|
|
|
782
696
|
mcpConfigSpinner.succeed(
|
|
783
|
-
`MCP configs updated for ${hostSelection.length} host application(s) (
|
|
697
|
+
`MCP configs updated for ${hostSelection.length} host application(s) (global scope).`,
|
|
784
698
|
);
|
|
785
699
|
|
|
786
700
|
// Display success message
|
package/src/installers/mcp.js
CHANGED
|
@@ -143,7 +143,7 @@ async function loadConfig(filePath) {
|
|
|
143
143
|
return isTomlFile(filePath) ? loadToml(filePath) : loadJson(filePath);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
function mergeServers(existing, incoming, serverKey = "servers") {
|
|
146
|
+
function mergeServers(existing, incoming, serverKey = "servers", platformInfo = null) {
|
|
147
147
|
const existingServers = existing[serverKey] && typeof existing[serverKey] === "object"
|
|
148
148
|
? existing[serverKey]
|
|
149
149
|
: {};
|
|
@@ -151,9 +151,18 @@ function mergeServers(existing, incoming, serverKey = "servers") {
|
|
|
151
151
|
const merged = { ...existing, [serverKey]: { ...existingServers } };
|
|
152
152
|
|
|
153
153
|
for (const server of incoming) {
|
|
154
|
+
let command = server.command;
|
|
155
|
+
let args = server.args || [];
|
|
156
|
+
|
|
157
|
+
// On Windows, wrap npx commands with cmd /c
|
|
158
|
+
if (platformInfo?.isWindows && command === "npx") {
|
|
159
|
+
command = "cmd";
|
|
160
|
+
args = ["/c", "npx", ...args];
|
|
161
|
+
}
|
|
162
|
+
|
|
154
163
|
const serverConfig = {
|
|
155
|
-
command
|
|
156
|
-
args
|
|
164
|
+
command,
|
|
165
|
+
args,
|
|
157
166
|
startup_timeout_sec: 30
|
|
158
167
|
};
|
|
159
168
|
|
|
@@ -201,10 +210,10 @@ function removeServers(existing, removeNames, serverKey = "servers") {
|
|
|
201
210
|
return { updated, removed };
|
|
202
211
|
}
|
|
203
212
|
|
|
204
|
-
async function installMcpConfig(configPath, servers, serverKey = "servers") {
|
|
213
|
+
async function installMcpConfig(configPath, servers, serverKey = "servers", platformInfo = null) {
|
|
205
214
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
206
215
|
const existing = await loadConfig(configPath);
|
|
207
|
-
const updated = mergeServers(existing, servers, serverKey);
|
|
216
|
+
const updated = mergeServers(existing, servers, serverKey, platformInfo);
|
|
208
217
|
|
|
209
218
|
if (isTomlFile(configPath)) {
|
|
210
219
|
await fs.writeFile(configPath, stringifySimpleToml(updated), "utf8");
|
package/src/installers/skills.js
CHANGED
|
@@ -77,7 +77,7 @@ async function removeDirIfEmpty(targetDir) {
|
|
|
77
77
|
* 4. Returns temp directory path for cleanup
|
|
78
78
|
*
|
|
79
79
|
* @param {string[]} skills - Array of npm package names
|
|
80
|
-
* @param {string
|
|
80
|
+
* @param {Array<{path: string, shouldNest: boolean}>} targetConfigs - Array of target configs with path and nesting preference
|
|
81
81
|
* @param {string} tempDir - Temporary directory for npm install
|
|
82
82
|
* @param {string} skillsFolder - Optional subfolder name to bundle skills (e.g., "a11y")
|
|
83
83
|
* @param {string} readmeTemplate - README template filename from templates folder
|
|
@@ -85,7 +85,7 @@ async function removeDirIfEmpty(targetDir) {
|
|
|
85
85
|
*/
|
|
86
86
|
async function installSkillsFromNpm(
|
|
87
87
|
skills,
|
|
88
|
-
|
|
88
|
+
targetConfigs,
|
|
89
89
|
tempDir,
|
|
90
90
|
skillsFolder = null,
|
|
91
91
|
readmeTemplate = "deploy-README.md",
|
|
@@ -117,9 +117,12 @@ async function installSkillsFromNpm(
|
|
|
117
117
|
const nodeModulesDir = path.join(tempDir, "node_modules");
|
|
118
118
|
let installedCount = 0;
|
|
119
119
|
|
|
120
|
-
for (const
|
|
120
|
+
for (const targetConfig of targetConfigs) {
|
|
121
|
+
const targetDir = targetConfig.path;
|
|
122
|
+
const shouldNest = targetConfig.shouldNest;
|
|
123
|
+
|
|
121
124
|
// Determine the actual skills directory (with or without bundle folder)
|
|
122
|
-
const skillsDir = skillsFolder
|
|
125
|
+
const skillsDir = shouldNest && skillsFolder
|
|
123
126
|
? path.join(targetDir, skillsFolder)
|
|
124
127
|
: targetDir;
|
|
125
128
|
|
|
@@ -156,7 +159,7 @@ async function installSkillsFromNpm(
|
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
return {
|
|
159
|
-
installed: installedCount /
|
|
162
|
+
installed: installedCount / targetConfigs.length,
|
|
160
163
|
tempDir,
|
|
161
164
|
};
|
|
162
165
|
}
|
|
@@ -165,21 +168,24 @@ async function installSkillsFromNpm(
|
|
|
165
168
|
* Remove skills installed by this tool from target directories.
|
|
166
169
|
*
|
|
167
170
|
* @param {string[]} skills - Array of npm package names
|
|
168
|
-
* @param {string
|
|
171
|
+
* @param {Array<{path: string, shouldNest: boolean}>} targetConfigs - Array of target configs with path and nesting preference
|
|
169
172
|
* @param {string} skillsFolder - Optional subfolder name used for bundled skills
|
|
170
173
|
* @param {string} readmeTemplate - README template filename from templates folder
|
|
171
174
|
* @returns {Promise<{removed: number}>}
|
|
172
175
|
*/
|
|
173
176
|
async function uninstallSkillsFromTargets(
|
|
174
177
|
skills,
|
|
175
|
-
|
|
178
|
+
targetConfigs,
|
|
176
179
|
skillsFolder = null,
|
|
177
180
|
readmeTemplate = "deploy-README.md",
|
|
178
181
|
) {
|
|
179
182
|
let removedCount = 0;
|
|
180
183
|
|
|
181
|
-
for (const
|
|
182
|
-
const
|
|
184
|
+
for (const targetConfig of targetConfigs) {
|
|
185
|
+
const targetDir = targetConfig.path;
|
|
186
|
+
const shouldNest = targetConfig.shouldNest;
|
|
187
|
+
|
|
188
|
+
const skillsDir = shouldNest && skillsFolder
|
|
183
189
|
? path.join(targetDir, skillsFolder)
|
|
184
190
|
: targetDir;
|
|
185
191
|
|
|
@@ -198,7 +204,7 @@ async function uninstallSkillsFromTargets(
|
|
|
198
204
|
await fs.rm(readmePath, { force: true });
|
|
199
205
|
}
|
|
200
206
|
|
|
201
|
-
if (skillsFolder) {
|
|
207
|
+
if (shouldNest && skillsFolder) {
|
|
202
208
|
await removeDirIfEmpty(skillsDir);
|
|
203
209
|
}
|
|
204
210
|
}
|
package/src/paths.js
CHANGED
|
@@ -36,32 +36,28 @@ function getHostApplicationPaths(projectRoot, platformInfo = getPlatform(), host
|
|
|
36
36
|
const paths = {};
|
|
37
37
|
|
|
38
38
|
for (const host of hostConfigs) {
|
|
39
|
-
// Default paths for
|
|
39
|
+
// Default paths for global scope (relative to home)
|
|
40
40
|
const skillsFolder = host.skillsFolder || `.${host.id}/skills`;
|
|
41
41
|
const mcpConfigFile = host.mcpConfigFile || `.${host.id}/mcp.json`;
|
|
42
42
|
|
|
43
|
-
// MCP config: use AppData/Application Support
|
|
43
|
+
// MCP config: use AppData/Application Support for config files
|
|
44
44
|
// appSupport resolves to:
|
|
45
45
|
// - Windows: %APPDATA% (e.g., C:\Users\name\AppData\Roaming)
|
|
46
46
|
// - macOS: ~/Library/Application Support
|
|
47
47
|
// - Linux: $XDG_CONFIG_HOME or ~/.config
|
|
48
|
-
const
|
|
49
|
-
? path.join(appSupport, host.globalMcpConfigFile)
|
|
50
|
-
: path.join(home, mcpConfigFile);
|
|
48
|
+
const mcpConfig = path.join(appSupport, mcpConfigFile);
|
|
51
49
|
|
|
52
|
-
// Skills always use home directory
|
|
53
|
-
const
|
|
50
|
+
// Skills always use home directory
|
|
51
|
+
const skillsDir = path.join(home, skillsFolder);
|
|
54
52
|
|
|
55
|
-
paths[host.id] = {
|
|
56
|
-
name: host.displayName,
|
|
57
|
-
mcpConfig:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
}
|
|
53
|
+
paths[host.id] = {
|
|
54
|
+
name: host.displayName,
|
|
55
|
+
mcpConfig: mcpConfig,
|
|
56
|
+
mcpServerKey: host.mcpServerKey,
|
|
57
|
+
skillsDir: skillsDir,
|
|
58
|
+
localSkillsDir: path.join(projectRoot, skillsFolder) // Still needed for skills scope
|
|
59
|
+
};
|
|
60
|
+
}
|
|
65
61
|
|
|
66
62
|
return paths;
|
|
67
63
|
}
|
|
@@ -81,4 +77,4 @@ export {
|
|
|
81
77
|
getHostApplicationPaths,
|
|
82
78
|
getTempDir,
|
|
83
79
|
getMcpRepoDir
|
|
84
|
-
};
|
|
80
|
+
};
|