a11y-devkit-deploy 0.9.8 → 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 +43 -131
- 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,16 +320,11 @@ 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,
|
|
@@ -358,7 +333,7 @@ async function run() {
|
|
|
358
333
|
);
|
|
359
334
|
}
|
|
360
335
|
mcpSpinner.succeed(
|
|
361
|
-
`MCP configs updated for ${hostSelection.length} host application(s) (
|
|
336
|
+
`MCP configs updated for ${hostSelection.length} host application(s) (global scope).`,
|
|
362
337
|
);
|
|
363
338
|
|
|
364
339
|
// Clean up temporary directory
|
|
@@ -371,11 +346,15 @@ async function run() {
|
|
|
371
346
|
info("MCP servers use npx - no local installation needed!");
|
|
372
347
|
console.log("");
|
|
373
348
|
success("Next Steps:");
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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`;
|
|
379
358
|
info(`📖 Check ${skillsPath} for comprehensive usage guide`);
|
|
380
359
|
info("✨ Includes 70+ example prompts for all skills and MCP servers");
|
|
381
360
|
info(
|
|
@@ -400,7 +379,6 @@ async function runUninstall(
|
|
|
400
379
|
let removeSkills = true;
|
|
401
380
|
let removeMcp = true;
|
|
402
381
|
let scope = args.scope;
|
|
403
|
-
let mcpScope = null;
|
|
404
382
|
let hostSelection = config.hostApplications.map((host) => host.id);
|
|
405
383
|
|
|
406
384
|
if (!args.autoYes) {
|
|
@@ -457,29 +435,6 @@ async function runUninstall(
|
|
|
457
435
|
});
|
|
458
436
|
}
|
|
459
437
|
|
|
460
|
-
if (removeMcp && config.supportLocalMcpInstallation) {
|
|
461
|
-
questions.push({
|
|
462
|
-
type: "select",
|
|
463
|
-
name: "mcpScope",
|
|
464
|
-
message: "Remove MCP configs locally or globally?",
|
|
465
|
-
choices: [
|
|
466
|
-
{
|
|
467
|
-
title: `Local to this project (${formatPath(projectRoot)})`,
|
|
468
|
-
value: "local",
|
|
469
|
-
description:
|
|
470
|
-
"Remove from project-level host application config folders (version-controllable)",
|
|
471
|
-
},
|
|
472
|
-
{
|
|
473
|
-
title: "Global for this user",
|
|
474
|
-
value: "global",
|
|
475
|
-
description:
|
|
476
|
-
"Remove from user-level host application config folders",
|
|
477
|
-
},
|
|
478
|
-
],
|
|
479
|
-
initial: 0,
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
|
|
483
438
|
questions.push({
|
|
484
439
|
type: "multiselect",
|
|
485
440
|
name: "hosts",
|
|
@@ -496,7 +451,6 @@ async function runUninstall(
|
|
|
496
451
|
});
|
|
497
452
|
|
|
498
453
|
scope = scope || response.scope;
|
|
499
|
-
mcpScope = response.mcpScope || mcpScope;
|
|
500
454
|
hostSelection = response.hosts || hostSelection;
|
|
501
455
|
}
|
|
502
456
|
|
|
@@ -504,10 +458,6 @@ async function runUninstall(
|
|
|
504
458
|
scope = "local";
|
|
505
459
|
}
|
|
506
460
|
|
|
507
|
-
if (removeMcp && !mcpScope) {
|
|
508
|
-
mcpScope = config.supportLocalMcpInstallation ? "local" : "global";
|
|
509
|
-
}
|
|
510
|
-
|
|
511
461
|
if (!hostSelection.length) {
|
|
512
462
|
warn(
|
|
513
463
|
"No host applications selected. Uninstall requires at least one host application.",
|
|
@@ -519,16 +469,23 @@ async function runUninstall(
|
|
|
519
469
|
info(`Skills scope: ${scope === "local" ? "Local" : "Global"}`);
|
|
520
470
|
}
|
|
521
471
|
if (removeMcp) {
|
|
522
|
-
info(
|
|
472
|
+
info("MCP configs: Global (user-level)");
|
|
523
473
|
}
|
|
524
474
|
|
|
525
475
|
if (removeSkills) {
|
|
526
476
|
const skillsSpinner = startSpinner("Removing skills...");
|
|
527
477
|
try {
|
|
528
|
-
const skillTargets =
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
+
});
|
|
532
489
|
|
|
533
490
|
const skillNames = config.skills.map((skill) =>
|
|
534
491
|
typeof skill === "string" ? skill : skill.npmName,
|
|
@@ -550,19 +507,14 @@ async function runUninstall(
|
|
|
550
507
|
|
|
551
508
|
if (removeMcp) {
|
|
552
509
|
const mcpSpinner = startSpinner("Removing MCP configurations...");
|
|
553
|
-
const mcpConfigPaths =
|
|
554
|
-
mcpScope === "local"
|
|
555
|
-
? hostSelection.map((host) => hostPaths[host].localMcpConfig)
|
|
556
|
-
: hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
510
|
+
const mcpConfigPaths = hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
557
511
|
|
|
558
512
|
let removedCount = 0;
|
|
559
513
|
const serverNames = config.mcpServers.map((server) => server.name);
|
|
560
514
|
|
|
561
515
|
for (let i = 0; i < hostSelection.length; i++) {
|
|
562
516
|
const host = hostSelection[i];
|
|
563
|
-
const serverKey =
|
|
564
|
-
? hostPaths[host].globalMcpServerKey
|
|
565
|
-
: hostPaths[host].mcpServerKey;
|
|
517
|
+
const serverKey = hostPaths[host].mcpServerKey;
|
|
566
518
|
const result = await removeMcpConfig(
|
|
567
519
|
mcpConfigPaths[i],
|
|
568
520
|
serverNames,
|
|
@@ -573,7 +525,7 @@ async function runUninstall(
|
|
|
573
525
|
|
|
574
526
|
if (removedCount > 0) {
|
|
575
527
|
mcpSpinner.succeed(
|
|
576
|
-
`Removed ${removedCount} MCP entries from ${hostSelection.length} host application(s) (
|
|
528
|
+
`Removed ${removedCount} MCP entries from ${hostSelection.length} host application(s) (global scope).`,
|
|
577
529
|
);
|
|
578
530
|
} else {
|
|
579
531
|
mcpSpinner.succeed("No matching MCP entries found to remove.");
|
|
@@ -641,40 +593,6 @@ async function runGitMcpInstallation(
|
|
|
641
593
|
},
|
|
642
594
|
);
|
|
643
595
|
|
|
644
|
-
// Prompt for MCP Config Scope (where to write MCP configurations)
|
|
645
|
-
// Skip if local MCP installation is not supported
|
|
646
|
-
let mcpScopeResponse = {
|
|
647
|
-
mcpScope: config.supportLocalMcpInstallation ? null : "global",
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
if (config.supportLocalMcpInstallation) {
|
|
651
|
-
mcpScopeResponse = await prompts(
|
|
652
|
-
{
|
|
653
|
-
type: "select",
|
|
654
|
-
name: "mcpScope",
|
|
655
|
-
message: "Where to write MCP configurations?",
|
|
656
|
-
choices: [
|
|
657
|
-
{
|
|
658
|
-
title: `Local to this project (${formatPath(projectRoot)})`,
|
|
659
|
-
value: "local",
|
|
660
|
-
description: "Write to project-level IDE config folders",
|
|
661
|
-
},
|
|
662
|
-
{
|
|
663
|
-
title: "Global for this user",
|
|
664
|
-
value: "global",
|
|
665
|
-
description: "Write to user-level IDE config folders",
|
|
666
|
-
},
|
|
667
|
-
],
|
|
668
|
-
initial: 0,
|
|
669
|
-
},
|
|
670
|
-
{
|
|
671
|
-
onCancel: () => {
|
|
672
|
-
warn("Git MCP installation cancelled.");
|
|
673
|
-
process.exit(0);
|
|
674
|
-
},
|
|
675
|
-
},
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
596
|
|
|
679
597
|
// Prompt for host application selection
|
|
680
598
|
const hostChoices = config.hostApplications.map((host) => ({
|
|
@@ -708,10 +626,9 @@ async function runGitMcpInstallation(
|
|
|
708
626
|
}
|
|
709
627
|
|
|
710
628
|
const repoScope = repoScopeResponse.repoScope;
|
|
711
|
-
const mcpScope = mcpScopeResponse.mcpScope;
|
|
712
629
|
|
|
713
630
|
info(`Repository clone scope: ${repoScope === "local" ? "Local" : "Global"}`);
|
|
714
|
-
info(
|
|
631
|
+
info("MCP configs: Global (user-level)");
|
|
715
632
|
|
|
716
633
|
// Install Git MCP
|
|
717
634
|
const gitSpinner = startSpinner("Cloning Git repository...");
|
|
@@ -741,10 +658,7 @@ async function runGitMcpInstallation(
|
|
|
741
658
|
// Install MCP configurations to selected host applications
|
|
742
659
|
const mcpConfigSpinner = startSpinner("Updating MCP configurations...");
|
|
743
660
|
|
|
744
|
-
const mcpConfigPaths =
|
|
745
|
-
mcpScope === "local"
|
|
746
|
-
? hostSelection.map((host) => hostPaths[host].localMcpConfig)
|
|
747
|
-
: hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
661
|
+
const mcpConfigPaths = hostSelection.map((host) => hostPaths[host].mcpConfig);
|
|
748
662
|
|
|
749
663
|
// Construct the MCP server configuration with absolute path
|
|
750
664
|
const mcpServerConfig = {
|
|
@@ -770,9 +684,7 @@ async function runGitMcpInstallation(
|
|
|
770
684
|
|
|
771
685
|
for (let i = 0; i < hostSelection.length; i++) {
|
|
772
686
|
const host = hostSelection[i];
|
|
773
|
-
const serverKey =
|
|
774
|
-
? hostPaths[host].globalMcpServerKey
|
|
775
|
-
: hostPaths[host].mcpServerKey;
|
|
687
|
+
const serverKey = hostPaths[host].mcpServerKey;
|
|
776
688
|
await installMcpConfig(
|
|
777
689
|
mcpConfigPaths[i],
|
|
778
690
|
[mcpServerConfig],
|
|
@@ -782,7 +694,7 @@ async function runGitMcpInstallation(
|
|
|
782
694
|
}
|
|
783
695
|
|
|
784
696
|
mcpConfigSpinner.succeed(
|
|
785
|
-
`MCP configs updated for ${hostSelection.length} host application(s) (
|
|
697
|
+
`MCP configs updated for ${hostSelection.length} host application(s) (global scope).`,
|
|
786
698
|
);
|
|
787
699
|
|
|
788
700
|
// Display success message
|
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
|
+
};
|