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 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
- ## After Installation
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": ".github/mcp.json",
210
- "globalMcpConfigFile": "Code/User/mcp.json"
211
- }
212
- ]
213
- }
214
- ```
215
-
216
- **Note:** The `globalMcpConfigFile` property is optional. When specified, the global MCP config path is relative to the platform's app support directory (AppData on Windows, Application Support on macOS) instead of the home directory.
217
-
218
- **Host Application Configuration Properties:**
219
- - `id` - Unique identifier for the host application
220
- - `displayName` - Human-readable name shown in prompts
221
- - `mcpServerKey` - MCP config key name (`"servers"`, `"mcpServers"`, or `"mcp_servers"` for TOML)
222
- - `skillsFolder` - Path to skills directory (relative to home/project root)
223
- - `mcpConfigFile` - Path to MCP config file (relative to home/project root). Supports both JSON (`.json`) and TOML (`.toml`) formats. TOML format is auto-detected by file extension (used by Codex).
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
- ├── mcp.json # Claude Code MCP config
260
- │ └── skills/ # Claude Code skills
261
- ├── .cursor/
262
- ├── mcp.json # Cursor MCP config
263
- │ └── skills/ # Cursor skills
264
- ├── .codex/
265
- ├── config.toml # Codex MCP config
266
- │ └── skills/ # Codex skills
267
- ├── .github/
268
- │ ├── mcp.json # VSCode MCP config
269
- └── skills/ # VSCode skills
270
- ├── .codeium/windsurf/
271
- │ ├── mcp_config.json # Windsurf MCP config
272
- │ └── skills/ # Windsurf skills
273
- └── .factory/
274
- ├── mcp.json # Factory MCP config
275
- └── skills/ # Factory skills
276
- ```
277
-
278
- ### Global Install (User-Wide)
279
- ```
280
- ~/.claude/
281
- ├── mcp.json # Claude Code global MCP config
282
- └── skills/ # Claude Code global skills
283
- ~/.cursor/
284
- ├── mcp.json # Cursor global MCP config
285
- └── skills/ # Cursor global skills
286
- ~/.codex/
287
- ├── config.toml # Codex global MCP config
288
- └── skills/ # Codex global skills
289
- ~/.github/
290
- └── skills/ # VSCode global skills
291
- ~/.codeium/windsurf/
292
- ├── mcp_config.json # Windsurf global MCP config
293
- └── skills/ # Windsurf global skills
294
- ~/.factory/
295
- ├── mcp.json # Factory global MCP config
296
- └── skills/ # Factory global skills
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
 
@@ -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": "servers",
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": ".github/mcp.json",
81
- "globalMcpConfigFile": "Code/User/mcp.json"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a11y-devkit-deploy",
3
- "version": "0.9.7",
3
+ "version": "1.0.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
@@ -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(`MCP scope: ${mcpScope === "local" ? "Local" : "Global"}`);
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
- scope === "local"
321
- ? hostSelection.map((host) => hostPaths[host].localSkillsDir)
322
- : hostSelection.map((host) => hostPaths[host].skillsDir);
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 = mcpScope === "global"
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) (${mcpScope} scope).`,
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
- const skillsFolderPath = config.skillsFolder ? `${config.skillsFolder}/` : "";
374
- const skillsPath =
375
- scope === "local"
376
- ? `.claude/skills/${skillsFolderPath}README.md (or your host application's equivalent)`
377
- : `~/.claude/skills/${skillsFolderPath}README.md (or your host application's global skills directory)`;
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(`MCP scope: ${mcpScope === "local" ? "Local" : "Global"}`);
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
- scope === "local"
529
- ? hostSelection.map((host) => hostPaths[host].localSkillsDir)
530
- : hostSelection.map((host) => hostPaths[host].skillsDir);
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 = mcpScope === "global"
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) (${mcpScope} scope).`,
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(`MCP config scope: ${mcpScope === "local" ? "Local" : "Global"}`);
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 = mcpScope === "global"
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) (${mcpScope} scope).`,
697
+ `MCP configs updated for ${hostSelection.length} host application(s) (global scope).`,
784
698
  );
785
699
 
786
700
  // Display success message
@@ -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: server.command,
156
- args: server.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");
@@ -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[]} targetDirs - Array of target directories to install skills to
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
- targetDirs,
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 targetDir of targetDirs) {
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 / targetDirs.length,
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[]} targetDirs - Array of target directories to uninstall from
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
- targetDirs,
178
+ targetConfigs,
176
179
  skillsFolder = null,
177
180
  readmeTemplate = "deploy-README.md",
178
181
  ) {
179
182
  let removedCount = 0;
180
183
 
181
- for (const targetDir of targetDirs) {
182
- const skillsDir = skillsFolder
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 local scope (relative to home or project)
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 if globalMcpConfigFile specified, otherwise home
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 globalMcpConfig = host.globalMcpConfigFile
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 (skills live at project level, not in AppData)
53
- const globalSkillsDir = path.join(home, skillsFolder);
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: globalMcpConfig,
58
- localMcpConfig: path.join(projectRoot, mcpConfigFile),
59
- mcpServerKey: host.mcpServerKey,
60
- globalMcpServerKey: host.globalMcpServerKey || host.mcpServerKey,
61
- skillsDir: globalSkillsDir,
62
- localSkillsDir: path.join(projectRoot, skillsFolder)
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
+ };