@codemcp/agentskills-cli 0.0.9 → 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.
@@ -0,0 +1,1495 @@
1
+ /**
2
+ * Tests for CLI install command with MCP dependency validation
3
+ *
4
+ * TDD RED PHASE - Tests written before implementation
5
+ * These tests define the expected behavior of MCP dependency validation
6
+ * during skill installation via the CLI install command.
7
+ */
8
+ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
9
+ import { installCommand } from "../commands/install.js";
10
+ import { PackageConfigManager, SkillInstaller, MCPConfigManager, MCPDependencyChecker } from "@codemcp/agentskills-core";
11
+ // Mock fs module
12
+ vi.mock("fs", () => ({
13
+ promises: {
14
+ mkdir: vi.fn().mockResolvedValue(undefined),
15
+ access: vi.fn().mockResolvedValue(undefined),
16
+ readFile: vi.fn(),
17
+ writeFile: vi.fn(),
18
+ stat: vi.fn(),
19
+ rm: vi.fn()
20
+ }
21
+ }));
22
+ // Mock all dependencies
23
+ vi.mock("@codemcp/agentskills-core", () => {
24
+ const actualCore = vi.importActual("@codemcp/agentskills-core");
25
+ return {
26
+ ...actualCore,
27
+ PackageConfigManager: vi.fn(),
28
+ SkillInstaller: vi.fn(),
29
+ MCPConfigManager: vi.fn(),
30
+ MCPDependencyChecker: vi.fn()
31
+ };
32
+ });
33
+ vi.mock("ora", () => ({
34
+ default: vi.fn(() => ({
35
+ start: vi.fn().mockReturnThis(),
36
+ stop: vi.fn().mockReturnThis(),
37
+ succeed: vi.fn().mockReturnThis(),
38
+ fail: vi.fn().mockReturnThis()
39
+ }))
40
+ }));
41
+ describe("Install Command - MCP Dependency Validation", () => {
42
+ let mockConfigManager;
43
+ let mockInstaller;
44
+ let mockMCPConfigManager;
45
+ let mockMCPDependencyChecker;
46
+ let consoleLogSpy;
47
+ let consoleErrorSpy;
48
+ let processExitSpy;
49
+ beforeEach(() => {
50
+ // Setup mocks
51
+ mockConfigManager = {
52
+ loadConfig: vi.fn()
53
+ };
54
+ mockInstaller = {
55
+ install: vi.fn(),
56
+ generateLockFile: vi.fn(),
57
+ loadInstalledSkills: vi.fn()
58
+ };
59
+ mockMCPConfigManager = {
60
+ isServerConfigured: vi.fn()
61
+ };
62
+ mockMCPDependencyChecker = {
63
+ collectDependencies: vi.fn(),
64
+ checkDependencies: vi.fn()
65
+ };
66
+ vi.mocked(PackageConfigManager).mockImplementation(() => mockConfigManager);
67
+ vi.mocked(SkillInstaller).mockImplementation(() => mockInstaller);
68
+ vi.mocked(MCPConfigManager).mockImplementation(() => mockMCPConfigManager);
69
+ vi.mocked(MCPDependencyChecker).mockImplementation(() => mockMCPDependencyChecker);
70
+ // Setup spies
71
+ consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => { });
72
+ consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
73
+ processExitSpy = vi
74
+ .spyOn(process, "exit")
75
+ .mockImplementation((() => { }));
76
+ // Default mock implementations
77
+ mockConfigManager.loadConfig.mockResolvedValue({
78
+ skills: {},
79
+ config: {
80
+ skillsDirectory: ".agentskills/skills",
81
+ autoDiscover: [],
82
+ maxSkillSize: 5000,
83
+ logLevel: "info"
84
+ },
85
+ source: {
86
+ type: "file",
87
+ path: "/test/package.json"
88
+ }
89
+ });
90
+ });
91
+ afterEach(() => {
92
+ vi.clearAllMocks();
93
+ consoleLogSpy.mockRestore();
94
+ consoleErrorSpy.mockRestore();
95
+ processExitSpy.mockRestore();
96
+ });
97
+ describe("MCP Agent Parameter", () => {
98
+ it("should use explicit --agent parameter for MCP validation", async () => {
99
+ // Setup
100
+ const config = {
101
+ skills: {
102
+ "test-skill": "github:user/repo#v1.0.0"
103
+ },
104
+ config: {
105
+ skillsDirectory: ".agentskills/skills",
106
+ autoDiscover: [],
107
+ maxSkillSize: 5000,
108
+ logLevel: "info"
109
+ },
110
+ source: {
111
+ type: "file",
112
+ path: "/test/package.json"
113
+ }
114
+ };
115
+ mockConfigManager.loadConfig.mockResolvedValue(config);
116
+ mockMCPConfigManager.isServerConfigured.mockResolvedValue(true);
117
+ mockInstaller.install.mockResolvedValue({
118
+ success: true,
119
+ name: "test-skill",
120
+ spec: "github:user/repo#v1.0.0",
121
+ resolvedVersion: "1.0.0",
122
+ integrity: "sha512-abc123",
123
+ installPath: "/test/.agentskills/skills/test-skill"
124
+ });
125
+ mockInstaller.loadInstalledSkills.mockResolvedValue([]);
126
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
127
+ // Execute with explicit agent
128
+ await installCommand({ cwd: "/test", agent: "claude" });
129
+ // Verify - should proceed normally
130
+ expect(processExitSpy).toHaveBeenCalledWith(0);
131
+ });
132
+ it("should proceed without MCP validation if no --agent specified", async () => {
133
+ // Setup
134
+ const config = {
135
+ skills: {
136
+ "test-skill": "github:user/repo#v1.0.0"
137
+ },
138
+ config: {
139
+ skillsDirectory: ".agentskills/skills",
140
+ autoDiscover: [],
141
+ maxSkillSize: 5000,
142
+ logLevel: "info"
143
+ },
144
+ source: {
145
+ type: "file",
146
+ path: "/test/package.json"
147
+ }
148
+ };
149
+ mockConfigManager.loadConfig.mockResolvedValue(config);
150
+ mockInstaller.install.mockResolvedValue({
151
+ success: true,
152
+ name: "test-skill",
153
+ spec: "github:user/repo#v1.0.0",
154
+ resolvedVersion: "1.0.0",
155
+ integrity: "sha512-abc123",
156
+ installPath: "/test/.agentskills/skills/test-skill"
157
+ });
158
+ mockInstaller.generateLockFile.mockResolvedValue(undefined);
159
+ mockInstaller.loadInstalledSkills.mockResolvedValue([]);
160
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
161
+ // Execute without agent parameter
162
+ await installCommand({ cwd: "/test" });
163
+ // Verify - should not check MCP configuration (no agent specified)
164
+ expect(mockMCPConfigManager.isServerConfigured).not.toHaveBeenCalled();
165
+ expect(processExitSpy).toHaveBeenCalledWith(0);
166
+ });
167
+ it("should handle all supported MCP agent types via --agent", async () => {
168
+ const agentMap = {
169
+ claude: "claude-desktop",
170
+ "claude-desktop": "claude-desktop",
171
+ cline: "cline",
172
+ continue: "continue",
173
+ cursor: "cursor",
174
+ junie: "junie",
175
+ zed: "zed",
176
+ vscode: "cline"
177
+ };
178
+ for (const [agentName, clientType] of Object.entries(agentMap)) {
179
+ vi.clearAllMocks();
180
+ const config = {
181
+ skills: {
182
+ "test-skill": "github:user/repo#v1.0.0"
183
+ },
184
+ config: {
185
+ skillsDirectory: ".agentskills/skills",
186
+ autoDiscover: [],
187
+ maxSkillSize: 5000,
188
+ logLevel: "info"
189
+ },
190
+ source: {
191
+ type: "file",
192
+ path: "/test/package.json"
193
+ }
194
+ };
195
+ mockConfigManager.loadConfig.mockResolvedValue(config);
196
+ mockMCPConfigManager.isServerConfigured.mockResolvedValue(true);
197
+ mockInstaller.install.mockResolvedValue({
198
+ success: true,
199
+ name: "test-skill",
200
+ spec: "github:user/repo#v1.0.0",
201
+ resolvedVersion: "1.0.0",
202
+ integrity: "sha512-abc123",
203
+ installPath: "/test/.agentskills/skills/test-skill"
204
+ });
205
+ mockInstaller.loadInstalledSkills.mockResolvedValue([]);
206
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
207
+ // Execute with specific agent
208
+ await installCommand({ cwd: "/test", agent: agentName });
209
+ // Verify
210
+ expect(processExitSpy).toHaveBeenCalledWith(0);
211
+ }
212
+ });
213
+ });
214
+ describe("Dependency Collection", () => {
215
+ it("should collect MCP dependencies from installed skills", async () => {
216
+ // Setup
217
+ const config = {
218
+ skills: {
219
+ "file-manager": "github:user/file-manager#v1.0.0"
220
+ },
221
+ config: {
222
+ skillsDirectory: ".agentskills/skills",
223
+ autoDiscover: [],
224
+ maxSkillSize: 5000,
225
+ logLevel: "info"
226
+ },
227
+ source: {
228
+ type: "file",
229
+ path: "/test/package.json"
230
+ }
231
+ };
232
+ const installedSkills = [
233
+ {
234
+ metadata: {
235
+ name: "file-manager",
236
+ description: "File management skill",
237
+ requiresMcpServers: [
238
+ {
239
+ name: "filesystem",
240
+ description: "File system access",
241
+ command: "npx",
242
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
243
+ }
244
+ ]
245
+ },
246
+ body: "Skill content"
247
+ }
248
+ ];
249
+ mockConfigManager.loadConfig.mockResolvedValue(config);
250
+ mockInstaller.install.mockResolvedValue({
251
+ success: true,
252
+ name: "file-manager",
253
+ spec: "github:user/file-manager#v1.0.0",
254
+ resolvedVersion: "1.0.0",
255
+ integrity: "sha512-abc123",
256
+ installPath: "/test/.agentskills/skills/file-manager"
257
+ });
258
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
259
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
260
+ // Execute
261
+ await installCommand({ cwd: "/test", agent: "claude" });
262
+ // Verify
263
+ expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
264
+ });
265
+ it("should handle skills with no MCP dependencies", async () => {
266
+ // Setup
267
+ const config = {
268
+ skills: {
269
+ "simple-skill": "github:user/simple-skill#v1.0.0"
270
+ },
271
+ config: {
272
+ skillsDirectory: ".agentskills/skills",
273
+ autoDiscover: [],
274
+ maxSkillSize: 5000,
275
+ logLevel: "info"
276
+ },
277
+ source: {
278
+ type: "file",
279
+ path: "/test/package.json"
280
+ }
281
+ };
282
+ const installedSkills = [
283
+ {
284
+ metadata: {
285
+ name: "simple-skill",
286
+ description: "Simple skill without MCP dependencies"
287
+ },
288
+ body: "Skill content"
289
+ }
290
+ ];
291
+ mockConfigManager.loadConfig.mockResolvedValue(config);
292
+ mockInstaller.install.mockResolvedValue({
293
+ success: true,
294
+ name: "simple-skill",
295
+ spec: "github:user/simple-skill#v1.0.0",
296
+ resolvedVersion: "1.0.0",
297
+ integrity: "sha512-abc123",
298
+ installPath: "/test/.agentskills/skills/simple-skill"
299
+ });
300
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
301
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
302
+ // Execute
303
+ await installCommand({ cwd: "/test", agent: "claude" });
304
+ // Verify - should still check but find no dependencies
305
+ expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
306
+ expect(processExitSpy).toHaveBeenCalledWith(0);
307
+ });
308
+ it("should collect dependencies from multiple skills", async () => {
309
+ // Setup
310
+ const config = {
311
+ skills: {
312
+ "file-manager": "github:user/file-manager#v1.0.0",
313
+ "github-helper": "github:user/github-helper#v1.0.0"
314
+ },
315
+ config: {
316
+ skillsDirectory: ".agentskills/skills",
317
+ autoDiscover: [],
318
+ maxSkillSize: 5000,
319
+ logLevel: "info"
320
+ },
321
+ source: {
322
+ type: "file",
323
+ path: "/test/package.json"
324
+ }
325
+ };
326
+ const installedSkills = [
327
+ {
328
+ metadata: {
329
+ name: "file-manager",
330
+ description: "File management skill",
331
+ requiresMcpServers: [
332
+ {
333
+ name: "filesystem",
334
+ description: "File system access",
335
+ command: "npx",
336
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
337
+ }
338
+ ]
339
+ },
340
+ body: "Skill content"
341
+ },
342
+ {
343
+ metadata: {
344
+ name: "github-helper",
345
+ description: "GitHub operations",
346
+ requiresMcpServers: [
347
+ {
348
+ name: "github",
349
+ description: "GitHub API access",
350
+ command: "npx",
351
+ args: ["-y", "@modelcontextprotocol/server-github"]
352
+ }
353
+ ]
354
+ },
355
+ body: "Skill content"
356
+ }
357
+ ];
358
+ mockConfigManager.loadConfig.mockResolvedValue(config);
359
+ mockInstaller.install
360
+ .mockResolvedValueOnce({
361
+ success: true,
362
+ name: "file-manager",
363
+ spec: "github:user/file-manager#v1.0.0",
364
+ resolvedVersion: "1.0.0",
365
+ integrity: "sha512-abc123",
366
+ installPath: "/test/.agentskills/skills/file-manager"
367
+ })
368
+ .mockResolvedValueOnce({
369
+ success: true,
370
+ name: "github-helper",
371
+ spec: "github:user/github-helper#v1.0.0",
372
+ resolvedVersion: "1.0.0",
373
+ integrity: "sha512-def456",
374
+ installPath: "/test/.agentskills/skills/github-helper"
375
+ });
376
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
377
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
378
+ // Execute
379
+ await installCommand({ cwd: "/test", agent: "claude" });
380
+ // Verify
381
+ expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
382
+ });
383
+ });
384
+ describe("Dependency Checking", () => {
385
+ it("should check dependencies against MCP client configuration", async () => {
386
+ // Setup
387
+ const config = {
388
+ skills: {
389
+ "file-manager": "github:user/file-manager#v1.0.0"
390
+ },
391
+ config: {
392
+ skillsDirectory: ".agentskills/skills",
393
+ autoDiscover: [],
394
+ maxSkillSize: 5000,
395
+ logLevel: "info"
396
+ },
397
+ source: {
398
+ type: "file",
399
+ path: "/test/package.json"
400
+ }
401
+ };
402
+ const installedSkills = [
403
+ {
404
+ metadata: {
405
+ name: "file-manager",
406
+ description: "File management skill",
407
+ requiresMcpServers: [
408
+ {
409
+ name: "filesystem",
410
+ description: "File system access",
411
+ command: "npx",
412
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
413
+ }
414
+ ]
415
+ },
416
+ body: "Skill content"
417
+ }
418
+ ];
419
+ const dependencies = [
420
+ {
421
+ serverName: "filesystem",
422
+ neededBy: ["file-manager"],
423
+ spec: {
424
+ name: "filesystem",
425
+ description: "File system access",
426
+ command: "npx",
427
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
428
+ }
429
+ }
430
+ ];
431
+ const checkResult = {
432
+ allConfigured: true,
433
+ missing: [],
434
+ configured: ["filesystem"]
435
+ };
436
+ mockConfigManager.loadConfig.mockResolvedValue(config);
437
+ mockInstaller.install.mockResolvedValue({
438
+ success: true,
439
+ name: "file-manager",
440
+ spec: "github:user/file-manager#v1.0.0",
441
+ resolvedVersion: "1.0.0",
442
+ integrity: "sha512-abc123",
443
+ installPath: "/test/.agentskills/skills/file-manager"
444
+ });
445
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
446
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
447
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
448
+ // Execute
449
+ await installCommand({ cwd: "/test", agent: "claude" });
450
+ // Verify
451
+ expect(mockMCPDependencyChecker.checkDependencies).toHaveBeenCalledWith("claude-desktop", dependencies, expect.any(Object), // MCPConfigManager instance
452
+ "/test");
453
+ expect(processExitSpy).toHaveBeenCalledWith(0);
454
+ });
455
+ it("should skip dependency checking if no dependencies are found", async () => {
456
+ // Setup
457
+ const config = {
458
+ skills: {
459
+ "simple-skill": "github:user/simple-skill#v1.0.0"
460
+ },
461
+ config: {
462
+ skillsDirectory: ".agentskills/skills",
463
+ autoDiscover: [],
464
+ maxSkillSize: 5000,
465
+ logLevel: "info"
466
+ },
467
+ source: {
468
+ type: "file",
469
+ path: "/test/package.json"
470
+ }
471
+ };
472
+ const installedSkills = [
473
+ {
474
+ metadata: {
475
+ name: "simple-skill",
476
+ description: "Simple skill"
477
+ },
478
+ body: "Skill content"
479
+ }
480
+ ];
481
+ mockConfigManager.loadConfig.mockResolvedValue(config);
482
+ mockInstaller.install.mockResolvedValue({
483
+ success: true,
484
+ name: "simple-skill",
485
+ spec: "github:user/simple-skill#v1.0.0",
486
+ resolvedVersion: "1.0.0",
487
+ integrity: "sha512-abc123",
488
+ installPath: "/test/.agentskills/skills/simple-skill"
489
+ });
490
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
491
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
492
+ // Execute
493
+ await installCommand({ cwd: "/test", agent: "claude" });
494
+ // Verify
495
+ expect(mockMCPDependencyChecker.checkDependencies).not.toHaveBeenCalled();
496
+ expect(processExitSpy).toHaveBeenCalledWith(0);
497
+ });
498
+ });
499
+ describe("Success Cases - All Dependencies Configured", () => {
500
+ it("should succeed when all MCP dependencies are configured", async () => {
501
+ // Setup
502
+ const config = {
503
+ skills: {
504
+ "file-manager": "github:user/file-manager#v1.0.0"
505
+ },
506
+ config: {
507
+ skillsDirectory: ".agentskills/skills",
508
+ autoDiscover: [],
509
+ maxSkillSize: 5000,
510
+ logLevel: "info"
511
+ },
512
+ source: {
513
+ type: "file",
514
+ path: "/test/package.json"
515
+ }
516
+ };
517
+ const installedSkills = [
518
+ {
519
+ metadata: {
520
+ name: "file-manager",
521
+ description: "File management skill",
522
+ requiresMcpServers: [
523
+ {
524
+ name: "filesystem",
525
+ description: "File system access",
526
+ command: "npx",
527
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
528
+ }
529
+ ]
530
+ },
531
+ body: "Skill content"
532
+ }
533
+ ];
534
+ const dependencies = [
535
+ {
536
+ serverName: "filesystem",
537
+ neededBy: ["file-manager"],
538
+ spec: {
539
+ name: "filesystem",
540
+ description: "File system access",
541
+ command: "npx",
542
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
543
+ }
544
+ }
545
+ ];
546
+ const checkResult = {
547
+ allConfigured: true,
548
+ missing: [],
549
+ configured: ["filesystem"]
550
+ };
551
+ mockConfigManager.loadConfig.mockResolvedValue(config);
552
+ mockInstaller.install.mockResolvedValue({
553
+ success: true,
554
+ name: "file-manager",
555
+ spec: "github:user/file-manager#v1.0.0",
556
+ resolvedVersion: "1.0.0",
557
+ integrity: "sha512-abc123",
558
+ installPath: "/test/.agentskills/skills/file-manager"
559
+ });
560
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
561
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
562
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
563
+ // Execute
564
+ await installCommand({ cwd: "/test", agent: "claude" });
565
+ // Verify
566
+ expect(processExitSpy).toHaveBeenCalledWith(0);
567
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Successfully installed"));
568
+ });
569
+ it("should succeed with multiple skills when all dependencies are configured", async () => {
570
+ // Setup
571
+ const config = {
572
+ skills: {
573
+ "file-manager": "github:user/file-manager#v1.0.0",
574
+ "github-helper": "github:user/github-helper#v1.0.0"
575
+ },
576
+ config: {
577
+ skillsDirectory: ".agentskills/skills",
578
+ autoDiscover: [],
579
+ maxSkillSize: 5000,
580
+ logLevel: "info"
581
+ },
582
+ source: {
583
+ type: "file",
584
+ path: "/test/package.json"
585
+ }
586
+ };
587
+ const installedSkills = [
588
+ {
589
+ metadata: {
590
+ name: "file-manager",
591
+ description: "File management skill",
592
+ requiresMcpServers: [
593
+ {
594
+ name: "filesystem",
595
+ description: "File system access",
596
+ command: "npx",
597
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
598
+ }
599
+ ]
600
+ },
601
+ body: "Skill content"
602
+ },
603
+ {
604
+ metadata: {
605
+ name: "github-helper",
606
+ description: "GitHub operations",
607
+ requiresMcpServers: [
608
+ {
609
+ name: "github",
610
+ description: "GitHub API access",
611
+ command: "npx",
612
+ args: ["-y", "@modelcontextprotocol/server-github"]
613
+ }
614
+ ]
615
+ },
616
+ body: "Skill content"
617
+ }
618
+ ];
619
+ const dependencies = [
620
+ {
621
+ serverName: "filesystem",
622
+ neededBy: ["file-manager"],
623
+ spec: {
624
+ name: "filesystem",
625
+ description: "File system access",
626
+ command: "npx",
627
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
628
+ }
629
+ },
630
+ {
631
+ serverName: "github",
632
+ neededBy: ["github-helper"],
633
+ spec: {
634
+ name: "github",
635
+ description: "GitHub API access",
636
+ command: "npx",
637
+ args: ["-y", "@modelcontextprotocol/server-github"]
638
+ }
639
+ }
640
+ ];
641
+ const checkResult = {
642
+ allConfigured: true,
643
+ missing: [],
644
+ configured: ["filesystem", "github"]
645
+ };
646
+ mockConfigManager.loadConfig.mockResolvedValue(config);
647
+ mockInstaller.install
648
+ .mockResolvedValueOnce({
649
+ success: true,
650
+ name: "file-manager",
651
+ spec: "github:user/file-manager#v1.0.0",
652
+ resolvedVersion: "1.0.0",
653
+ integrity: "sha512-abc123",
654
+ installPath: "/test/.agentskills/skills/file-manager"
655
+ })
656
+ .mockResolvedValueOnce({
657
+ success: true,
658
+ name: "github-helper",
659
+ spec: "github:user/github-helper#v1.0.0",
660
+ resolvedVersion: "1.0.0",
661
+ integrity: "sha512-def456",
662
+ installPath: "/test/.agentskills/skills/github-helper"
663
+ });
664
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
665
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
666
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
667
+ // Execute
668
+ await installCommand({ cwd: "/test", agent: "claude" });
669
+ // Verify
670
+ expect(processExitSpy).toHaveBeenCalledWith(0);
671
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Successfully installed 2 skills"));
672
+ });
673
+ });
674
+ describe("Failure Cases - Missing Dependencies", () => {
675
+ it("should fail when MCP dependencies are missing", async () => {
676
+ // Setup
677
+ const config = {
678
+ skills: {
679
+ "file-manager": "github:user/file-manager#v1.0.0"
680
+ },
681
+ config: {
682
+ skillsDirectory: ".agentskills/skills",
683
+ autoDiscover: [],
684
+ maxSkillSize: 5000,
685
+ logLevel: "info"
686
+ },
687
+ source: {
688
+ type: "file",
689
+ path: "/test/package.json"
690
+ }
691
+ };
692
+ const installedSkills = [
693
+ {
694
+ metadata: {
695
+ name: "file-manager",
696
+ description: "File management skill",
697
+ requiresMcpServers: [
698
+ {
699
+ name: "filesystem",
700
+ description: "File system access",
701
+ command: "npx",
702
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
703
+ }
704
+ ]
705
+ },
706
+ body: "Skill content"
707
+ }
708
+ ];
709
+ const dependencies = [
710
+ {
711
+ serverName: "filesystem",
712
+ neededBy: ["file-manager"],
713
+ spec: {
714
+ name: "filesystem",
715
+ description: "File system access",
716
+ command: "npx",
717
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
718
+ }
719
+ }
720
+ ];
721
+ const checkResult = {
722
+ allConfigured: false,
723
+ missing: dependencies,
724
+ configured: []
725
+ };
726
+ mockConfigManager.loadConfig.mockResolvedValue(config);
727
+ mockInstaller.install.mockResolvedValue({
728
+ success: true,
729
+ name: "file-manager",
730
+ spec: "github:user/file-manager#v1.0.0",
731
+ resolvedVersion: "1.0.0",
732
+ integrity: "sha512-abc123",
733
+ installPath: "/test/.agentskills/skills/file-manager"
734
+ });
735
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
736
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
737
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
738
+ // Execute
739
+ await installCommand({ cwd: "/test", agent: "claude" });
740
+ // Verify
741
+ expect(processExitSpy).toHaveBeenCalledWith(1);
742
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Missing MCP server dependencies"));
743
+ });
744
+ it("should display helpful error message with missing server details", async () => {
745
+ // Setup
746
+ const config = {
747
+ skills: {
748
+ "file-manager": "github:user/file-manager#v1.0.0"
749
+ },
750
+ config: {
751
+ skillsDirectory: ".agentskills/skills",
752
+ autoDiscover: [],
753
+ maxSkillSize: 5000,
754
+ logLevel: "info"
755
+ },
756
+ source: {
757
+ type: "file",
758
+ path: "/test/package.json"
759
+ }
760
+ };
761
+ const installedSkills = [
762
+ {
763
+ metadata: {
764
+ name: "file-manager",
765
+ description: "File management skill",
766
+ requiresMcpServers: [
767
+ {
768
+ name: "filesystem",
769
+ description: "File system access",
770
+ command: "npx",
771
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
772
+ }
773
+ ]
774
+ },
775
+ body: "Skill content"
776
+ }
777
+ ];
778
+ const dependencies = [
779
+ {
780
+ serverName: "filesystem",
781
+ neededBy: ["file-manager"],
782
+ spec: {
783
+ name: "filesystem",
784
+ description: "File system access",
785
+ command: "npx",
786
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
787
+ }
788
+ }
789
+ ];
790
+ const checkResult = {
791
+ allConfigured: false,
792
+ missing: dependencies,
793
+ configured: []
794
+ };
795
+ mockConfigManager.loadConfig.mockResolvedValue(config);
796
+ mockInstaller.install.mockResolvedValue({
797
+ success: true,
798
+ name: "file-manager",
799
+ spec: "github:user/file-manager#v1.0.0",
800
+ resolvedVersion: "1.0.0",
801
+ integrity: "sha512-abc123",
802
+ installPath: "/test/.agentskills/skills/file-manager"
803
+ });
804
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
805
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
806
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
807
+ // Execute
808
+ await installCommand({ cwd: "/test", agent: "claude" });
809
+ // Verify error message contains server name
810
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("filesystem"));
811
+ });
812
+ it("should show which skills need each missing server", async () => {
813
+ // Setup
814
+ const config = {
815
+ skills: {
816
+ "file-manager": "github:user/file-manager#v1.0.0",
817
+ "file-reader": "github:user/file-reader#v1.0.0"
818
+ },
819
+ config: {
820
+ skillsDirectory: ".agentskills/skills",
821
+ autoDiscover: [],
822
+ maxSkillSize: 5000,
823
+ logLevel: "info"
824
+ },
825
+ source: {
826
+ type: "file",
827
+ path: "/test/package.json"
828
+ }
829
+ };
830
+ const installedSkills = [
831
+ {
832
+ metadata: {
833
+ name: "file-manager",
834
+ description: "File management skill",
835
+ requiresMcpServers: [
836
+ {
837
+ name: "filesystem",
838
+ description: "File system access",
839
+ command: "npx",
840
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
841
+ }
842
+ ]
843
+ },
844
+ body: "Skill content"
845
+ },
846
+ {
847
+ metadata: {
848
+ name: "file-reader",
849
+ description: "File reading skill",
850
+ requiresMcpServers: [
851
+ {
852
+ name: "filesystem",
853
+ description: "File system access",
854
+ command: "npx",
855
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
856
+ }
857
+ ]
858
+ },
859
+ body: "Skill content"
860
+ }
861
+ ];
862
+ const dependencies = [
863
+ {
864
+ serverName: "filesystem",
865
+ neededBy: ["file-manager", "file-reader"],
866
+ spec: {
867
+ name: "filesystem",
868
+ description: "File system access",
869
+ command: "npx",
870
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
871
+ }
872
+ }
873
+ ];
874
+ const checkResult = {
875
+ allConfigured: false,
876
+ missing: dependencies,
877
+ configured: []
878
+ };
879
+ mockConfigManager.loadConfig.mockResolvedValue(config);
880
+ mockInstaller.install
881
+ .mockResolvedValueOnce({
882
+ success: true,
883
+ name: "file-manager",
884
+ spec: "github:user/file-manager#v1.0.0",
885
+ resolvedVersion: "1.0.0",
886
+ integrity: "sha512-abc123",
887
+ installPath: "/test/.agentskills/skills/file-manager"
888
+ })
889
+ .mockResolvedValueOnce({
890
+ success: true,
891
+ name: "file-reader",
892
+ spec: "github:user/file-reader#v1.0.0",
893
+ resolvedVersion: "1.0.0",
894
+ integrity: "sha512-def456",
895
+ installPath: "/test/.agentskills/skills/file-reader"
896
+ });
897
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
898
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
899
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
900
+ // Execute
901
+ await installCommand({ cwd: "/test", agent: "claude" });
902
+ // Verify error message mentions both skills
903
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("file-manager"));
904
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("file-reader"));
905
+ });
906
+ it("should suggest using --with-mcp flag for auto-install", async () => {
907
+ // Setup
908
+ const config = {
909
+ skills: {
910
+ "file-manager": "github:user/file-manager#v1.0.0"
911
+ },
912
+ config: {
913
+ skillsDirectory: ".agentskills/skills",
914
+ autoDiscover: [],
915
+ maxSkillSize: 5000,
916
+ logLevel: "info"
917
+ },
918
+ source: {
919
+ type: "file",
920
+ path: "/test/package.json"
921
+ }
922
+ };
923
+ const installedSkills = [
924
+ {
925
+ metadata: {
926
+ name: "file-manager",
927
+ description: "File management skill",
928
+ requiresMcpServers: [
929
+ {
930
+ name: "filesystem",
931
+ description: "File system access",
932
+ command: "npx",
933
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
934
+ }
935
+ ]
936
+ },
937
+ body: "Skill content"
938
+ }
939
+ ];
940
+ const dependencies = [
941
+ {
942
+ serverName: "filesystem",
943
+ neededBy: ["file-manager"],
944
+ spec: {
945
+ name: "filesystem",
946
+ description: "File system access",
947
+ command: "npx",
948
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
949
+ }
950
+ }
951
+ ];
952
+ const checkResult = {
953
+ allConfigured: false,
954
+ missing: dependencies,
955
+ configured: []
956
+ };
957
+ mockConfigManager.loadConfig.mockResolvedValue(config);
958
+ mockInstaller.install.mockResolvedValue({
959
+ success: true,
960
+ name: "file-manager",
961
+ spec: "github:user/file-manager#v1.0.0",
962
+ resolvedVersion: "1.0.0",
963
+ integrity: "sha512-abc123",
964
+ installPath: "/test/.agentskills/skills/file-manager"
965
+ });
966
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
967
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
968
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
969
+ // Execute
970
+ await installCommand({ cwd: "/test", agent: "claude" });
971
+ // Verify suggestion for --with-mcp flag
972
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--with-mcp"));
973
+ });
974
+ it("should fail with multiple missing dependencies", async () => {
975
+ // Setup
976
+ const config = {
977
+ skills: {
978
+ "devops-tool": "github:user/devops-tool#v1.0.0"
979
+ },
980
+ config: {
981
+ skillsDirectory: ".agentskills/skills",
982
+ autoDiscover: [],
983
+ maxSkillSize: 5000,
984
+ logLevel: "info"
985
+ },
986
+ source: {
987
+ type: "file",
988
+ path: "/test/package.json"
989
+ }
990
+ };
991
+ const installedSkills = [
992
+ {
993
+ metadata: {
994
+ name: "devops-tool",
995
+ description: "DevOps operations",
996
+ requiresMcpServers: [
997
+ {
998
+ name: "filesystem",
999
+ description: "File system access",
1000
+ command: "npx",
1001
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1002
+ },
1003
+ {
1004
+ name: "github",
1005
+ description: "GitHub API access",
1006
+ command: "npx",
1007
+ args: ["-y", "@modelcontextprotocol/server-github"]
1008
+ },
1009
+ {
1010
+ name: "slack",
1011
+ description: "Slack API access",
1012
+ command: "npx",
1013
+ args: ["-y", "@modelcontextprotocol/server-slack"]
1014
+ }
1015
+ ]
1016
+ },
1017
+ body: "Skill content"
1018
+ }
1019
+ ];
1020
+ const dependencies = [
1021
+ {
1022
+ serverName: "filesystem",
1023
+ neededBy: ["devops-tool"],
1024
+ spec: {
1025
+ name: "filesystem",
1026
+ description: "File system access",
1027
+ command: "npx",
1028
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1029
+ }
1030
+ },
1031
+ {
1032
+ serverName: "github",
1033
+ neededBy: ["devops-tool"],
1034
+ spec: {
1035
+ name: "github",
1036
+ description: "GitHub API access",
1037
+ command: "npx",
1038
+ args: ["-y", "@modelcontextprotocol/server-github"]
1039
+ }
1040
+ },
1041
+ {
1042
+ serverName: "slack",
1043
+ neededBy: ["devops-tool"],
1044
+ spec: {
1045
+ name: "slack",
1046
+ description: "Slack API access",
1047
+ command: "npx",
1048
+ args: ["-y", "@modelcontextprotocol/server-slack"]
1049
+ }
1050
+ }
1051
+ ];
1052
+ const checkResult = {
1053
+ allConfigured: false,
1054
+ missing: dependencies,
1055
+ configured: []
1056
+ };
1057
+ mockConfigManager.loadConfig.mockResolvedValue(config);
1058
+ mockInstaller.install.mockResolvedValue({
1059
+ success: true,
1060
+ name: "devops-tool",
1061
+ spec: "github:user/devops-tool#v1.0.0",
1062
+ resolvedVersion: "1.0.0",
1063
+ integrity: "sha512-abc123",
1064
+ installPath: "/test/.agentskills/skills/devops-tool"
1065
+ });
1066
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
1067
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
1068
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
1069
+ // Execute
1070
+ await installCommand({ cwd: "/test", agent: "claude" });
1071
+ // Verify
1072
+ expect(processExitSpy).toHaveBeenCalledWith(1);
1073
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("filesystem"));
1074
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("github"));
1075
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("slack"));
1076
+ });
1077
+ it("should fail with partially missing dependencies", async () => {
1078
+ // Setup
1079
+ const config = {
1080
+ skills: {
1081
+ "file-manager": "github:user/file-manager#v1.0.0",
1082
+ "github-helper": "github:user/github-helper#v1.0.0"
1083
+ },
1084
+ config: {
1085
+ skillsDirectory: ".agentskills/skills",
1086
+ autoDiscover: [],
1087
+ maxSkillSize: 5000,
1088
+ logLevel: "info"
1089
+ },
1090
+ source: {
1091
+ type: "file",
1092
+ path: "/test/package.json"
1093
+ }
1094
+ };
1095
+ const installedSkills = [
1096
+ {
1097
+ metadata: {
1098
+ name: "file-manager",
1099
+ description: "File management skill",
1100
+ requiresMcpServers: [
1101
+ {
1102
+ name: "filesystem",
1103
+ description: "File system access",
1104
+ command: "npx",
1105
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1106
+ }
1107
+ ]
1108
+ },
1109
+ body: "Skill content"
1110
+ },
1111
+ {
1112
+ metadata: {
1113
+ name: "github-helper",
1114
+ description: "GitHub operations",
1115
+ requiresMcpServers: [
1116
+ {
1117
+ name: "github",
1118
+ description: "GitHub API access",
1119
+ command: "npx",
1120
+ args: ["-y", "@modelcontextprotocol/server-github"]
1121
+ }
1122
+ ]
1123
+ },
1124
+ body: "Skill content"
1125
+ }
1126
+ ];
1127
+ const dependencies = [
1128
+ {
1129
+ serverName: "filesystem",
1130
+ neededBy: ["file-manager"],
1131
+ spec: {
1132
+ name: "filesystem",
1133
+ description: "File system access",
1134
+ command: "npx",
1135
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1136
+ }
1137
+ },
1138
+ {
1139
+ serverName: "github",
1140
+ neededBy: ["github-helper"],
1141
+ spec: {
1142
+ name: "github",
1143
+ description: "GitHub API access",
1144
+ command: "npx",
1145
+ args: ["-y", "@modelcontextprotocol/server-github"]
1146
+ }
1147
+ }
1148
+ ];
1149
+ // filesystem is configured, github is missing
1150
+ const checkResult = {
1151
+ allConfigured: false,
1152
+ missing: [dependencies[1]], // only github is missing
1153
+ configured: ["filesystem"]
1154
+ };
1155
+ mockConfigManager.loadConfig.mockResolvedValue(config);
1156
+ mockInstaller.install
1157
+ .mockResolvedValueOnce({
1158
+ success: true,
1159
+ name: "file-manager",
1160
+ spec: "github:user/file-manager#v1.0.0",
1161
+ resolvedVersion: "1.0.0",
1162
+ integrity: "sha512-abc123",
1163
+ installPath: "/test/.agentskills/skills/file-manager"
1164
+ })
1165
+ .mockResolvedValueOnce({
1166
+ success: true,
1167
+ name: "github-helper",
1168
+ spec: "github:user/github-helper#v1.0.0",
1169
+ resolvedVersion: "1.0.0",
1170
+ integrity: "sha512-def456",
1171
+ installPath: "/test/.agentskills/skills/github-helper"
1172
+ });
1173
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
1174
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
1175
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
1176
+ // Execute
1177
+ await installCommand({ cwd: "/test", agent: "claude" });
1178
+ // Verify
1179
+ expect(processExitSpy).toHaveBeenCalledWith(1);
1180
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("github"));
1181
+ // Should not mention filesystem since it's configured
1182
+ const calls = consoleErrorSpy.mock.calls.map((call) => call[0]).join(" ");
1183
+ expect(calls).not.toContain("filesystem");
1184
+ });
1185
+ });
1186
+ describe("Edge Cases", () => {
1187
+ it("should handle error during dependency collection", async () => {
1188
+ // Setup
1189
+ const config = {
1190
+ skills: {
1191
+ "test-skill": "github:user/repo#v1.0.0"
1192
+ },
1193
+ config: {
1194
+ skillsDirectory: ".agentskills/skills",
1195
+ autoDiscover: [],
1196
+ maxSkillSize: 5000,
1197
+ logLevel: "info"
1198
+ },
1199
+ source: {
1200
+ type: "file",
1201
+ path: "/test/package.json"
1202
+ }
1203
+ };
1204
+ mockConfigManager.loadConfig.mockResolvedValue(config);
1205
+ mockInstaller.install.mockResolvedValue({
1206
+ success: true,
1207
+ name: "test-skill",
1208
+ spec: "github:user/repo#v1.0.0",
1209
+ resolvedVersion: "1.0.0",
1210
+ integrity: "sha512-abc123",
1211
+ installPath: "/test/.agentskills/skills/test-skill"
1212
+ });
1213
+ mockInstaller.loadInstalledSkills.mockRejectedValue(new Error("Failed to load skills"));
1214
+ // Execute
1215
+ await installCommand({ cwd: "/test", agent: "claude" });
1216
+ // Verify - should handle gracefully
1217
+ expect(processExitSpy).toHaveBeenCalledWith(1);
1218
+ expect(consoleErrorSpy).toHaveBeenCalled();
1219
+ });
1220
+ it("should handle error during dependency checking", async () => {
1221
+ // Setup
1222
+ const config = {
1223
+ skills: {
1224
+ "test-skill": "github:user/repo#v1.0.0"
1225
+ },
1226
+ config: {
1227
+ skillsDirectory: ".agentskills/skills",
1228
+ autoDiscover: [],
1229
+ maxSkillSize: 5000,
1230
+ logLevel: "info"
1231
+ },
1232
+ source: {
1233
+ type: "file",
1234
+ path: "/test/package.json"
1235
+ }
1236
+ };
1237
+ const installedSkills = [
1238
+ {
1239
+ metadata: {
1240
+ name: "test-skill",
1241
+ description: "Test skill",
1242
+ requiresMcpServers: [
1243
+ {
1244
+ name: "filesystem",
1245
+ description: "File system access",
1246
+ command: "npx",
1247
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1248
+ }
1249
+ ]
1250
+ },
1251
+ body: "Skill content"
1252
+ }
1253
+ ];
1254
+ const dependencies = [
1255
+ {
1256
+ serverName: "filesystem",
1257
+ neededBy: ["test-skill"],
1258
+ spec: {
1259
+ name: "filesystem",
1260
+ description: "File system access",
1261
+ command: "npx",
1262
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1263
+ }
1264
+ }
1265
+ ];
1266
+ mockConfigManager.loadConfig.mockResolvedValue(config);
1267
+ mockInstaller.install.mockResolvedValue({
1268
+ success: true,
1269
+ name: "test-skill",
1270
+ spec: "github:user/repo#v1.0.0",
1271
+ resolvedVersion: "1.0.0",
1272
+ integrity: "sha512-abc123",
1273
+ installPath: "/test/.agentskills/skills/test-skill"
1274
+ });
1275
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
1276
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
1277
+ mockMCPDependencyChecker.checkDependencies.mockRejectedValue(new Error("Failed to check dependencies"));
1278
+ // Execute
1279
+ await installCommand({ cwd: "/test", agent: "claude" });
1280
+ // Verify - should handle gracefully
1281
+ expect(processExitSpy).toHaveBeenCalledWith(1);
1282
+ expect(consoleErrorSpy).toHaveBeenCalled();
1283
+ });
1284
+ it("should handle skills that fail to install before MCP validation", async () => {
1285
+ // Setup
1286
+ const config = {
1287
+ skills: {
1288
+ "failing-skill": "github:user/failing-skill#v1.0.0",
1289
+ "working-skill": "github:user/working-skill#v1.0.0"
1290
+ },
1291
+ config: {
1292
+ skillsDirectory: ".agentskills/skills",
1293
+ autoDiscover: [],
1294
+ maxSkillSize: 5000,
1295
+ logLevel: "info"
1296
+ },
1297
+ source: {
1298
+ type: "file",
1299
+ path: "/test/package.json"
1300
+ }
1301
+ };
1302
+ mockConfigManager.loadConfig.mockResolvedValue(config);
1303
+ mockInstaller.install
1304
+ .mockResolvedValueOnce({
1305
+ success: false,
1306
+ name: "failing-skill",
1307
+ spec: "github:user/failing-skill#v1.0.0",
1308
+ error: {
1309
+ code: "INSTALL_FAILED",
1310
+ message: "Failed to download"
1311
+ }
1312
+ })
1313
+ .mockResolvedValueOnce({
1314
+ success: true,
1315
+ name: "working-skill",
1316
+ spec: "github:user/working-skill#v1.0.0",
1317
+ resolvedVersion: "1.0.0",
1318
+ integrity: "sha512-abc123",
1319
+ installPath: "/test/.agentskills/skills/working-skill"
1320
+ });
1321
+ const installedSkills = [
1322
+ {
1323
+ metadata: {
1324
+ name: "working-skill",
1325
+ description: "Working skill"
1326
+ },
1327
+ body: "Skill content"
1328
+ }
1329
+ ];
1330
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
1331
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
1332
+ // Execute
1333
+ await installCommand({ cwd: "/test", agent: "claude" });
1334
+ // Verify - should still run MCP validation on successful installs
1335
+ expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
1336
+ });
1337
+ });
1338
+ describe("Error Message Formatting", () => {
1339
+ it("should format error message with proper structure", async () => {
1340
+ // Setup
1341
+ const config = {
1342
+ skills: {
1343
+ "file-manager": "github:user/file-manager#v1.0.0"
1344
+ },
1345
+ config: {
1346
+ skillsDirectory: ".agentskills/skills",
1347
+ autoDiscover: [],
1348
+ maxSkillSize: 5000,
1349
+ logLevel: "info"
1350
+ },
1351
+ source: {
1352
+ type: "file",
1353
+ path: "/test/package.json"
1354
+ }
1355
+ };
1356
+ const installedSkills = [
1357
+ {
1358
+ metadata: {
1359
+ name: "file-manager",
1360
+ description: "File management skill",
1361
+ requiresMcpServers: [
1362
+ {
1363
+ name: "filesystem",
1364
+ description: "File system access",
1365
+ command: "npx",
1366
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1367
+ }
1368
+ ]
1369
+ },
1370
+ body: "Skill content"
1371
+ }
1372
+ ];
1373
+ const dependencies = [
1374
+ {
1375
+ serverName: "filesystem",
1376
+ neededBy: ["file-manager"],
1377
+ spec: {
1378
+ name: "filesystem",
1379
+ description: "File system access",
1380
+ command: "npx",
1381
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1382
+ }
1383
+ }
1384
+ ];
1385
+ const checkResult = {
1386
+ allConfigured: false,
1387
+ missing: dependencies,
1388
+ configured: []
1389
+ };
1390
+ mockConfigManager.loadConfig.mockResolvedValue(config);
1391
+ mockInstaller.install.mockResolvedValue({
1392
+ success: true,
1393
+ name: "file-manager",
1394
+ spec: "github:user/file-manager#v1.0.0",
1395
+ resolvedVersion: "1.0.0",
1396
+ integrity: "sha512-abc123",
1397
+ installPath: "/test/.agentskills/skills/file-manager"
1398
+ });
1399
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
1400
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
1401
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
1402
+ // Execute
1403
+ await installCommand({ cwd: "/test", agent: "claude" });
1404
+ // Verify error message structure
1405
+ const errorCalls = consoleErrorSpy.mock.calls.map((call) => call[0]);
1406
+ const errorOutput = errorCalls.join("\n");
1407
+ // Should contain main error message
1408
+ expect(errorOutput).toContain("Missing MCP server dependencies");
1409
+ // Should contain server name
1410
+ expect(errorOutput).toContain("filesystem");
1411
+ // Should contain needed by information
1412
+ expect(errorOutput).toContain("file-manager");
1413
+ // Should contain suggestion
1414
+ expect(errorOutput).toContain("--with-mcp");
1415
+ });
1416
+ it("should use chalk for colorized output in error messages", async () => {
1417
+ // Setup
1418
+ const config = {
1419
+ skills: {
1420
+ "file-manager": "github:user/file-manager#v1.0.0"
1421
+ },
1422
+ config: {
1423
+ skillsDirectory: ".agentskills/skills",
1424
+ autoDiscover: [],
1425
+ maxSkillSize: 5000,
1426
+ logLevel: "info"
1427
+ },
1428
+ source: {
1429
+ type: "file",
1430
+ path: "/test/package.json"
1431
+ }
1432
+ };
1433
+ const installedSkills = [
1434
+ {
1435
+ metadata: {
1436
+ name: "file-manager",
1437
+ description: "File management skill",
1438
+ requiresMcpServers: [
1439
+ {
1440
+ name: "filesystem",
1441
+ description: "File system access",
1442
+ command: "npx",
1443
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1444
+ }
1445
+ ]
1446
+ },
1447
+ body: "Skill content"
1448
+ }
1449
+ ];
1450
+ const dependencies = [
1451
+ {
1452
+ serverName: "filesystem",
1453
+ neededBy: ["file-manager"],
1454
+ spec: {
1455
+ name: "filesystem",
1456
+ description: "File system access",
1457
+ command: "npx",
1458
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
1459
+ }
1460
+ }
1461
+ ];
1462
+ const checkResult = {
1463
+ allConfigured: false,
1464
+ missing: dependencies,
1465
+ configured: []
1466
+ };
1467
+ mockConfigManager.loadConfig.mockResolvedValue(config);
1468
+ mockInstaller.install.mockResolvedValue({
1469
+ success: true,
1470
+ name: "file-manager",
1471
+ spec: "github:user/file-manager#v1.0.0",
1472
+ resolvedVersion: "1.0.0",
1473
+ integrity: "sha512-abc123",
1474
+ installPath: "/test/.agentskills/skills/file-manager"
1475
+ });
1476
+ mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
1477
+ mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
1478
+ mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
1479
+ // Execute
1480
+ await installCommand({ cwd: "/test", agent: "claude" });
1481
+ // Verify chalk was used
1482
+ expect(consoleErrorSpy).toHaveBeenCalled();
1483
+ // Check that error output contains expected content (chalk's job is to colorize)
1484
+ // In test environment, chalk may strip colors, but the content should still be there
1485
+ const allOutput = consoleErrorSpy.mock.calls
1486
+ .map((call) => String(call[0]))
1487
+ .join(" ");
1488
+ // Just verify the messages are present - chalk may strip colors in test env
1489
+ expect(allOutput).toContain("Missing MCP server dependencies");
1490
+ expect(allOutput).toContain("filesystem");
1491
+ expect(allOutput).toContain("file-manager");
1492
+ });
1493
+ });
1494
+ });
1495
+ //# sourceMappingURL=install-mcp-validation.test.js.map