@codemcp/skills 2.1.0 → 2.2.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.
@@ -1,1294 +0,0 @@
1
- // ../core/dist/config-generators.js
2
- var ConfigGeneratorRegistry = class {
3
- generators = /* @__PURE__ */ new Map();
4
- /**
5
- * Register a config generator
6
- */
7
- register(generator) {
8
- for (const agentType of generator.agentTypes) {
9
- this.generators.set(agentType, generator);
10
- }
11
- }
12
- /**
13
- * Get a generator for an agent type
14
- */
15
- getGenerator(agentType) {
16
- return this.generators.get(agentType);
17
- }
18
- /**
19
- * Generate config for an agent
20
- */
21
- async generate(agentType, config, options) {
22
- const generator = this.getGenerator(agentType);
23
- if (!generator)
24
- return null;
25
- return generator.generate(config, options);
26
- }
27
- /**
28
- * List all registered generators
29
- */
30
- listGenerators() {
31
- const seen = /* @__PURE__ */ new Set();
32
- const result = [];
33
- for (const generator of this.generators.values()) {
34
- const name = generator.getMetadata().name;
35
- if (!seen.has(name)) {
36
- result.push(generator.getMetadata());
37
- seen.add(name);
38
- }
39
- }
40
- return result;
41
- }
42
- /**
43
- * Check if any generator supports an agent type
44
- */
45
- supports(agentType) {
46
- return this.generators.has(agentType);
47
- }
48
- /**
49
- * Get all supported agent types
50
- */
51
- getSupportedAgentTypes() {
52
- return Array.from(this.generators.keys());
53
- }
54
- /**
55
- * Clear all generators (mainly for testing)
56
- */
57
- clear() {
58
- this.generators.clear();
59
- }
60
- };
61
-
62
- // ../core/dist/generators/skills-agent-description.js
63
- var SKILLS_AGENT_DESCRIPTION_MARKDOWN = `# Skill Usage
64
-
65
- ## Mandatory Workflow Before Every Response
66
-
67
- Before responding to a message, ALWAYS check:
68
-
69
- 1. List available skills
70
- 2. Ask yourself: "Does ONE skill fit this task?" (at just 1% probability \u2192 call the skill)
71
- 3. If yes \u2192 load and execute skill via \`use_skill\` MCP tool
72
- 4. Announce: "I'm using [Skill-Name] for [Purpose]."
73
- 5. Follow skill instructions exactly
74
-
75
- ## Rules
76
-
77
- - ALWAYS call skills via \`use_skill\` tool, never work from memory
78
- - If a skill contains a checklist \u2192 create EACH point as its own todo
79
- - Never rationalize that a skill is "not needed" or "overkill"
80
- - Answering without skill check = error`;
81
-
82
- // ../core/dist/generators/github-copilot-generator.js
83
- var GitHubCopilotGenerator = class {
84
- agentTypes = [
85
- "github-copilot",
86
- "copilot-cli",
87
- "copilot-coding-agent"
88
- ];
89
- async generate(config, options) {
90
- const yaml = this.generateYamlFrontmatter(config);
91
- const markdown = this.generateMarkdownContent();
92
- return {
93
- filePath: `${options.skillsDir}/.github/agents/skills-mcp.agent.md`,
94
- content: `${yaml}
95
-
96
- ${markdown}`,
97
- format: "markdown"
98
- };
99
- }
100
- supports(agentType) {
101
- return this.agentTypes.includes(agentType);
102
- }
103
- getOutputPath(skillsDir) {
104
- return `${skillsDir}/.github/agents/skills-mcp.agent.md`;
105
- }
106
- getMetadata() {
107
- return {
108
- name: "GitHub Copilot",
109
- description: "Generates Markdown+YAML agent configs for GitHub Copilot CLI and coding agent",
110
- agentTypes: this.agentTypes,
111
- docsUrl: "https://docs.github.com/en/copilot/reference/custom-agents-configuration",
112
- version: "1.0.0"
113
- };
114
- }
115
- generateYamlFrontmatter(config) {
116
- const tools = this.mapToolsToGitHubFormat(config.tools, config.mcp_servers);
117
- const yamlObj = {
118
- name: config.id,
119
- description: config.description
120
- };
121
- if (tools.length > 0) {
122
- yamlObj.tools = tools;
123
- }
124
- if (config.mcp_servers && Object.keys(config.mcp_servers).length > 0) {
125
- yamlObj["mcp-servers"] = this.generateMcpServers(config.mcp_servers);
126
- }
127
- yamlObj["disable-model-invocation"] = false;
128
- yamlObj["user-invocable"] = true;
129
- let yaml = "---\n";
130
- for (const [k, v] of Object.entries(yamlObj)) {
131
- yaml += this.formatYamlEntry(k, v) + "\n";
132
- }
133
- yaml += "---";
134
- return yaml;
135
- }
136
- generateMarkdownContent() {
137
- return SKILLS_AGENT_DESCRIPTION_MARKDOWN;
138
- }
139
- generateMcpServers(servers) {
140
- const result = {};
141
- for (const [name, config] of Object.entries(servers)) {
142
- const serverConfig = config;
143
- const serverEntry = {};
144
- if (serverConfig.type) {
145
- serverEntry.type = serverConfig.type;
146
- }
147
- if (serverConfig.command) {
148
- serverEntry.command = serverConfig.command;
149
- }
150
- if (serverConfig.args) {
151
- serverEntry.args = serverConfig.args;
152
- }
153
- if (serverConfig.url) {
154
- serverEntry.url = serverConfig.url;
155
- }
156
- if (serverConfig.headers) {
157
- serverEntry.headers = serverConfig.headers;
158
- }
159
- if (serverConfig.env) {
160
- serverEntry.env = serverConfig.env;
161
- }
162
- if (serverConfig.tools) {
163
- serverEntry.tools = serverConfig.tools;
164
- }
165
- result[name] = serverEntry;
166
- }
167
- return result;
168
- }
169
- mapToolsToGitHubFormat(tools, servers) {
170
- const result = [];
171
- if (tools?.write)
172
- result.push("edit");
173
- if (tools?.read)
174
- result.push("read");
175
- if (tools?.bash)
176
- result.push("execute");
177
- if (tools?.use_skill)
178
- result.push("use_skill");
179
- if (servers) {
180
- for (const [name, server] of Object.entries(servers)) {
181
- const serverConfig = server;
182
- if (serverConfig.tools && !serverConfig.tools.includes("*")) {
183
- for (const tool of serverConfig.tools) {
184
- result.push(`${name}/${tool}`);
185
- }
186
- } else {
187
- result.push(`${name}/*`);
188
- }
189
- }
190
- }
191
- return result.length > 0 ? result : ["*"];
192
- }
193
- formatYamlEntry(key, value, indent = 0) {
194
- const prefix = " ".repeat(indent);
195
- if (Array.isArray(value)) {
196
- return `${prefix}${key}:
197
- ${value.map((v) => `${prefix} - ${v}`).join("\n")}`;
198
- }
199
- if (typeof value === "object" && value !== null) {
200
- const entries = Object.entries(value);
201
- if (entries.length === 0)
202
- return `${prefix}${key}: {}`;
203
- let result = `${prefix}${key}:
204
- `;
205
- for (const [k, v] of entries) {
206
- const entry = this.formatYamlEntry(k, v, indent + 2);
207
- result += entry + "\n";
208
- }
209
- return result.trimEnd();
210
- }
211
- if (typeof value === "boolean") {
212
- return `${prefix}${key}: ${value}`;
213
- }
214
- return `${prefix}${key}: ${value}`;
215
- }
216
- };
217
-
218
- // ../core/dist/generators/kiro-generator.js
219
- var KiroGenerator = class {
220
- agentTypes = ["kiro", "kiro-cli"];
221
- async generate(config, _options) {
222
- const agentConfig = this.generateAgentConfig(config);
223
- return {
224
- filePath: `${_options.skillsDir}/.kiro/agents/skills-mcp.json`,
225
- content: JSON.stringify(agentConfig, null, 2),
226
- format: "json"
227
- };
228
- }
229
- supports(agentType) {
230
- return this.agentTypes.includes(agentType);
231
- }
232
- getOutputPath(skillsDir) {
233
- return `${skillsDir}/.kiro/agents/skills-mcp.json`;
234
- }
235
- getMetadata() {
236
- return {
237
- name: "Kiro",
238
- description: "Generates JSON agent configs for Kiro CLI",
239
- agentTypes: this.agentTypes,
240
- docsUrl: "https://kiro.dev/docs/cli/custom-agents/",
241
- version: "1.0.0"
242
- };
243
- }
244
- generateAgentConfig(config) {
245
- const agentConfig = {
246
- name: config.id,
247
- prompt: SKILLS_AGENT_DESCRIPTION_MARKDOWN,
248
- mcpServers: this.generateMcpServers(config.mcp_servers),
249
- tools: this.generateTools(config),
250
- allowedTools: this.generateAllowedTools(config)
251
- };
252
- return agentConfig;
253
- }
254
- generateMcpServers(servers) {
255
- const result = {};
256
- if (!servers)
257
- return result;
258
- for (const [name, config] of Object.entries(servers)) {
259
- const serverConfig = config;
260
- const serverEntry = {};
261
- if (serverConfig.command) {
262
- serverEntry.command = serverConfig.command;
263
- }
264
- if (serverConfig.args && Array.isArray(serverConfig.args)) {
265
- serverEntry.args = serverConfig.args;
266
- }
267
- if (serverConfig.env) {
268
- serverEntry.env = serverConfig.env;
269
- }
270
- result[name] = serverEntry;
271
- }
272
- return result;
273
- }
274
- generateTools(config) {
275
- const tools = [
276
- "execute_bash",
277
- "fs_read",
278
- "fs_write",
279
- "report_issue",
280
- "knowledge",
281
- "thinking",
282
- "use_aws"
283
- ];
284
- if (config.mcp_servers && Object.keys(config.mcp_servers).length > 0) {
285
- for (const name of Object.keys(config.mcp_servers)) {
286
- tools.push(`@${name}`);
287
- }
288
- }
289
- return tools;
290
- }
291
- generateAllowedTools(config) {
292
- const allowed = ["fs_read", "use_skill"];
293
- if (config.mcp_servers) {
294
- for (const [name, server] of Object.entries(config.mcp_servers)) {
295
- if (server.tools && !server.tools.includes("*")) {
296
- for (const tool of server.tools) {
297
- allowed.push(`@${name}/${tool}`);
298
- }
299
- } else {
300
- allowed.push(`@${name}/*`);
301
- }
302
- }
303
- }
304
- return allowed;
305
- }
306
- };
307
-
308
- // ../core/dist/generators/opencode-generator.js
309
- var OpenCodeMcpGenerator = class {
310
- agentTypes = ["opencode", "opencode-cli"];
311
- async generate(config, options) {
312
- const opencodeConfig = this.generateOpenCodeConfig(config);
313
- return {
314
- filePath: `${options.skillsDir}/opencode.json`,
315
- content: JSON.stringify(opencodeConfig, null, 2),
316
- format: "json"
317
- };
318
- }
319
- supports(agentType) {
320
- return this.agentTypes.includes(agentType);
321
- }
322
- getOutputPath(skillsDir) {
323
- return `${skillsDir}/opencode.json`;
324
- }
325
- getMetadata() {
326
- return {
327
- name: "OpenCode MCP",
328
- description: "Writes opencode.json with MCP server configurations for OpenCode",
329
- agentTypes: this.agentTypes,
330
- docsUrl: "https://opencode.ai/docs/mcp-servers/",
331
- version: "1.0.0"
332
- };
333
- }
334
- generateOpenCodeConfig(config) {
335
- const opencodeConfig = {
336
- $schema: "https://opencode.ai/config.json",
337
- permission: {
338
- skill: "deny"
339
- // Disable native skill tool to avoid conflicts
340
- },
341
- mcp: {}
342
- };
343
- if (config.mcp_servers) {
344
- const mcp = opencodeConfig.mcp;
345
- for (const [name, serverConfig] of Object.entries(config.mcp_servers)) {
346
- const entry = {
347
- type: "local",
348
- enabled: true,
349
- environment: serverConfig.env || {}
350
- };
351
- if (serverConfig.command) {
352
- entry.command = [serverConfig.command, ...serverConfig.args || []];
353
- }
354
- mcp[name] = entry;
355
- }
356
- }
357
- return opencodeConfig;
358
- }
359
- };
360
- var OpenCodeAgentGenerator = class {
361
- agentTypes = ["opencode", "opencode-cli"];
362
- async generate(config, options) {
363
- const markdown = this.generateMarkdown(config);
364
- return {
365
- filePath: `${options.skillsDir}/.opencode/agents/skills-mcp.md`,
366
- content: markdown,
367
- format: "markdown"
368
- };
369
- }
370
- supports(agentType) {
371
- return this.agentTypes.includes(agentType);
372
- }
373
- getOutputPath(skillsDir) {
374
- return `${skillsDir}/.opencode/agents/skills-mcp.md`;
375
- }
376
- getMetadata() {
377
- return {
378
- name: "OpenCode Agent",
379
- description: "Generates Markdown agent configs for OpenCode",
380
- agentTypes: this.agentTypes,
381
- docsUrl: "https://opencode.ai/docs/agents/",
382
- version: "1.0.0"
383
- };
384
- }
385
- generateMarkdown(config) {
386
- const frontmatter = this.generateFrontmatter(config);
387
- const content = this.generateContent();
388
- return `${frontmatter}
389
-
390
- ${content}`;
391
- }
392
- generateFrontmatter(config) {
393
- const permissions = this.mapPermissions(config.permissions);
394
- const frontmatterObj = {
395
- name: config.id,
396
- description: config.description,
397
- tools: this.mapTools(config.tools),
398
- permission: permissions
399
- };
400
- if (config.mcp_servers && Object.keys(config.mcp_servers).length > 0) {
401
- frontmatterObj.mcp = this.generateMcpServers(config.mcp_servers);
402
- }
403
- let yaml = "---\n";
404
- for (const [key, value] of Object.entries(frontmatterObj)) {
405
- yaml += this.formatYamlValue(key, value);
406
- }
407
- yaml += "---";
408
- return yaml;
409
- }
410
- formatYamlValue(key, value, indent = 0) {
411
- const prefix = " ".repeat(indent);
412
- if (typeof value === "boolean" || typeof value === "number") {
413
- return `${prefix}${key}: ${value}
414
- `;
415
- }
416
- if (Array.isArray(value)) {
417
- let result = `${prefix}${key}:
418
- `;
419
- for (const item of value) {
420
- result += `${prefix} - ${item}
421
- `;
422
- }
423
- return result;
424
- }
425
- if (typeof value === "object" && value !== null) {
426
- let result = `${prefix}${key}:
427
- `;
428
- for (const [k, v] of Object.entries(value)) {
429
- result += this.formatYamlValue(k, v, indent + 2);
430
- }
431
- return result;
432
- }
433
- return `${prefix}${key}: ${value}
434
- `;
435
- }
436
- mapTools(tools) {
437
- const mapped = {
438
- read: true,
439
- write: false,
440
- edit: false,
441
- bash: false,
442
- use_skill: true
443
- };
444
- if (tools) {
445
- Object.assign(mapped, tools);
446
- }
447
- return mapped;
448
- }
449
- mapPermissions(permissions) {
450
- if (!permissions) {
451
- return {
452
- edit: "ask",
453
- bash: "ask",
454
- use_skill: "allow"
455
- };
456
- }
457
- return permissions || {
458
- edit: "ask",
459
- bash: "ask",
460
- use_skill: "allow"
461
- };
462
- }
463
- generateMcpServers(servers) {
464
- const result = {};
465
- for (const [name, config] of Object.entries(servers)) {
466
- const serverConfig = config;
467
- const serverEntry = {};
468
- if (serverConfig.type) {
469
- serverEntry.type = serverConfig.type;
470
- }
471
- if (serverConfig.command) {
472
- serverEntry.command = serverConfig.command;
473
- }
474
- if (serverConfig.args) {
475
- serverEntry.args = serverConfig.args;
476
- }
477
- if (serverConfig.url) {
478
- serverEntry.url = serverConfig.url;
479
- }
480
- if (serverConfig.env) {
481
- serverEntry.env = serverConfig.env;
482
- }
483
- if (serverConfig.tools) {
484
- serverEntry.tools = serverConfig.tools;
485
- }
486
- result[name] = serverEntry;
487
- }
488
- return result;
489
- }
490
- generateContent() {
491
- return SKILLS_AGENT_DESCRIPTION_MARKDOWN;
492
- }
493
- };
494
-
495
- // ../core/dist/generators/vscode-generator.js
496
- var VsCodeGenerator = class {
497
- // github-copilot is the primary agent type; the others are aliases
498
- // that also live inside VS Code and share the same MCP config file.
499
- agentTypes = [
500
- "github-copilot",
501
- "copilot-cli",
502
- "copilot-coding-agent"
503
- ];
504
- async generate(config, options) {
505
- const vscodeConfig = this.generateVsCodeConfig(config);
506
- return {
507
- filePath: `${options.skillsDir}/.vscode/mcp.json`,
508
- content: JSON.stringify(vscodeConfig, null, 2),
509
- format: "json"
510
- };
511
- }
512
- supports(agentType) {
513
- return this.agentTypes.includes(agentType);
514
- }
515
- getOutputPath(skillsDir) {
516
- return `${skillsDir}/.vscode/mcp.json`;
517
- }
518
- getMetadata() {
519
- return {
520
- name: "VS Code",
521
- description: "Writes .vscode/mcp.json (servers format) so MCP servers are available to GitHub Copilot and other VS Code extensions",
522
- agentTypes: this.agentTypes,
523
- docsUrl: "https://code.visualstudio.com/docs/copilot/chat/mcp-servers",
524
- version: "1.0.0"
525
- };
526
- }
527
- generateVsCodeConfig(config) {
528
- const servers = {};
529
- if (config.mcp_servers) {
530
- for (const [name, serverConfig] of Object.entries(config.mcp_servers)) {
531
- const entry = {};
532
- if (serverConfig.url) {
533
- entry.type = serverConfig.type ?? "http";
534
- entry.url = serverConfig.url;
535
- if (serverConfig.headers)
536
- entry.headers = serverConfig.headers;
537
- } else if (serverConfig.command) {
538
- entry.command = serverConfig.command;
539
- if (serverConfig.args?.length)
540
- entry.args = serverConfig.args;
541
- if (serverConfig.env && Object.keys(serverConfig.env).length) {
542
- entry.env = serverConfig.env;
543
- }
544
- }
545
- servers[name] = entry;
546
- }
547
- }
548
- return { servers };
549
- }
550
- };
551
-
552
- // ../core/dist/parser.js
553
- import matter from "gray-matter";
554
- import { promises as fs } from "fs";
555
- var FIELD_MAP = {
556
- name: "name",
557
- description: "description",
558
- license: "license",
559
- compatibility: "compatibility",
560
- metadata: "metadata",
561
- "allowed-tools": "allowedTools",
562
- "disable-model-invocation": "disableModelInvocation",
563
- "user-invocable": "userInvocable",
564
- "argument-hint": "argumentHint",
565
- context: "context",
566
- agent: "agent",
567
- model: "model",
568
- hooks: "hooks",
569
- labels: "labels",
570
- "requires-mcp-servers": "requiresMcpServers"
571
- };
572
- var REQUIRED_FIELDS = ["name", "description"];
573
- function createError(code, message, field) {
574
- return {
575
- success: false,
576
- error: { code, message, ...field && { field } }
577
- };
578
- }
579
- function mapFieldNames(data) {
580
- const metadata = {};
581
- for (const [key, value] of Object.entries(data)) {
582
- const mappedKey = FIELD_MAP[key];
583
- if (mappedKey !== void 0) {
584
- if (mappedKey === "allowedTools" && typeof value === "string") {
585
- metadata[mappedKey] = value.split(/\s+/).filter(Boolean);
586
- } else {
587
- metadata[mappedKey] = value;
588
- }
589
- }
590
- }
591
- return metadata;
592
- }
593
- function parseSkillContent(content) {
594
- if (!content || content.trim().length === 0) {
595
- return createError("EMPTY_FILE", "Skill file is empty");
596
- }
597
- let parsed;
598
- try {
599
- parsed = matter(content);
600
- } catch (error) {
601
- return createError("INVALID_YAML", `Failed to parse YAML frontmatter: ${error.message}`);
602
- }
603
- if (!parsed.data || Object.keys(parsed.data).length === 0) {
604
- return createError("MISSING_FRONTMATTER", "Skill file must contain YAML frontmatter");
605
- }
606
- for (const field of REQUIRED_FIELDS) {
607
- if (!(field in parsed.data)) {
608
- return createError("MISSING_REQUIRED_FIELD", `required field '${field}' is missing from skill metadata`, field);
609
- }
610
- }
611
- const metadata = mapFieldNames(parsed.data);
612
- const skill = Object.freeze({
613
- metadata: Object.freeze(metadata),
614
- body: parsed.content
615
- });
616
- return {
617
- success: true,
618
- skill
619
- };
620
- }
621
- async function parseSkill(filePath) {
622
- try {
623
- const content = await fs.readFile(filePath, "utf-8");
624
- return parseSkillContent(content);
625
- } catch (error) {
626
- const nodeError = error;
627
- if (nodeError.code === "ENOENT") {
628
- return createError("FILE_NOT_FOUND", `File not found: ${filePath}`);
629
- }
630
- if (nodeError.code === "EACCES" || nodeError.code === "EISDIR") {
631
- return createError("FILE_READ_ERROR", `Failed to read file: ${nodeError.message}`);
632
- }
633
- return createError("FILE_READ_ERROR", `Failed to read file: ${nodeError.message}`);
634
- }
635
- }
636
-
637
- // ../core/dist/validator.js
638
- import Ajv from "ajv";
639
-
640
- // ../core/dist/skill-frontmatter.schema.json
641
- var skill_frontmatter_schema_default = {
642
- $schema: "https://json-schema.org/draft-07/schema#",
643
- $id: "https://mrsimpson.github.io/agentskills-mcp/skill-frontmatter-schema.json",
644
- title: "Agent Skill Frontmatter",
645
- description: "YAML frontmatter schema for Agent Skills (SKILL.md files). See https://mrsimpson.github.io/agentskills-mcp/reference/skill-format for full documentation.",
646
- type: "object",
647
- required: ["name", "description"],
648
- additionalProperties: false,
649
- properties: {
650
- name: {
651
- type: "string",
652
- description: "Unique skill identifier. Lowercase letters, numbers, and hyphens only. No leading/trailing/consecutive hyphens.",
653
- minLength: 1,
654
- maxLength: 64,
655
- allOf: [
656
- { pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$" },
657
- { not: { pattern: "--" } }
658
- ]
659
- },
660
- description: {
661
- type: "string",
662
- description: "Short summary of the skill shown in tool descriptions.",
663
- minLength: 1,
664
- maxLength: 1024
665
- },
666
- license: {
667
- type: "string",
668
- description: "SPDX license identifier (e.g. MIT, Apache-2.0). Recommended for published skills."
669
- },
670
- compatibility: {
671
- type: "string",
672
- description: "Agent compatibility string.",
673
- maxLength: 500
674
- },
675
- metadata: {
676
- type: "object",
677
- description: "Arbitrary key-value metadata.",
678
- additionalProperties: true
679
- },
680
- "allowed-tools": {
681
- type: "string",
682
- description: "Space-delimited list of tools this skill is permitted to use. MCP server tools use the format '@server-name/tool-name'. When specified, only these tools are whitelisted in the generated agent config instead of allowing all tools from each MCP server."
683
- },
684
- "disable-model-invocation": {
685
- type: "boolean",
686
- description: "If true, the skill is excluded from the use_skill tool enum and cannot be invoked via MCP."
687
- },
688
- "user-invocable": {
689
- type: "boolean",
690
- description: "If true, the skill is designed for direct user invocation (e.g. via /skill-name)."
691
- },
692
- "argument-hint": {
693
- type: "string",
694
- description: 'Hint shown to users about expected arguments, e.g. "<pr-url>" or "<target> [options]".'
695
- },
696
- context: {
697
- type: "string",
698
- description: "Execution context hint for the agent."
699
- },
700
- agent: {
701
- type: "string",
702
- description: "Target agent identifier this skill is optimized for."
703
- },
704
- model: {
705
- type: "string",
706
- description: "Preferred model for executing this skill."
707
- },
708
- hooks: {
709
- type: "object",
710
- description: "Lifecycle hook definitions keyed by hook name.",
711
- additionalProperties: {
712
- type: "string"
713
- }
714
- },
715
- labels: {
716
- type: "array",
717
- description: "Optional labels/tags for categorizing skills. Used with the SKILL_LABELS environment variable to filter which skills are loaded by the MCP server.",
718
- items: {
719
- type: "string",
720
- minLength: 1,
721
- maxLength: 64,
722
- pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
723
- },
724
- uniqueItems: true
725
- },
726
- "requires-mcp-servers": {
727
- type: "array",
728
- description: "MCP servers that must be configured for this skill to work. Used by 'agentskills install --agent' for validation and auto-configuration.",
729
- items: {
730
- $ref: "#/$defs/McpServerDependency"
731
- }
732
- }
733
- },
734
- $defs: {
735
- McpServerDependency: {
736
- type: "object",
737
- description: "An MCP server that this skill depends on.",
738
- required: ["name", "description", "command"],
739
- additionalProperties: false,
740
- properties: {
741
- name: {
742
- type: "string",
743
- description: "Server identifier. Lowercase letters, numbers, and hyphens. Must start and end with alphanumeric.",
744
- allOf: [
745
- { pattern: "^[a-z0-9][a-z0-9-]*[a-z0-9]$" },
746
- { not: { pattern: "--" } }
747
- ]
748
- },
749
- package: {
750
- type: "string",
751
- description: 'npm package name used for auto-installation (e.g. "@modelcontextprotocol/server-filesystem").'
752
- },
753
- description: {
754
- type: "string",
755
- description: "Why this MCP server is needed by the skill."
756
- },
757
- command: {
758
- type: "string",
759
- description: 'Executable to run (e.g. "npx", "node", "/usr/local/bin/my-server").'
760
- },
761
- args: {
762
- type: "array",
763
- description: "Arguments passed to the command. May contain {{PARAM_NAME}} placeholders resolved at install time.",
764
- items: {
765
- type: "string"
766
- }
767
- },
768
- env: {
769
- type: "object",
770
- description: "Environment variables to set when running the server.",
771
- additionalProperties: {
772
- type: "string"
773
- }
774
- },
775
- cwd: {
776
- type: "string",
777
- description: "Working directory for the server process."
778
- },
779
- parameters: {
780
- type: "object",
781
- description: "Parameter definitions for {{PARAM}} placeholders used in args or env.",
782
- propertyNames: {
783
- allOf: [
784
- { pattern: "^[a-z0-9][a-z0-9-]*[a-z0-9]$" },
785
- { not: { pattern: "--" } }
786
- ]
787
- },
788
- additionalProperties: {
789
- $ref: "#/$defs/McpParameterSpec"
790
- }
791
- }
792
- }
793
- },
794
- McpParameterSpec: {
795
- type: "object",
796
- description: "Definition of a parameter placeholder used in an MCP server dependency.",
797
- required: ["description", "required"],
798
- additionalProperties: false,
799
- properties: {
800
- description: {
801
- type: "string",
802
- description: "What this parameter configures."
803
- },
804
- required: {
805
- type: "boolean",
806
- description: "Whether the user must supply this parameter."
807
- },
808
- sensitive: {
809
- type: "boolean",
810
- description: "If true, treat as a secret or credential (mask in output, do not log)."
811
- },
812
- default: {
813
- type: "string",
814
- description: "Default value used when the parameter is not supplied."
815
- },
816
- example: {
817
- type: "string",
818
- description: "Example value shown to guide users."
819
- }
820
- }
821
- }
822
- }
823
- };
824
-
825
- // ../core/dist/validator.js
826
- var ajv = new Ajv({ validateSchema: false });
827
- var validateFrontmatter = ajv.compile(skill_frontmatter_schema_default);
828
- var CAMEL_TO_KEBAB = {
829
- allowedTools: "allowed-tools",
830
- disableModelInvocation: "disable-model-invocation",
831
- userInvocable: "user-invocable",
832
- argumentHint: "argument-hint",
833
- requiresMcpServers: "requires-mcp-servers"
834
- };
835
- function toRawYamlKeys(metadata) {
836
- const result = {};
837
- for (const [key, value] of Object.entries(metadata)) {
838
- const yamlKey = CAMEL_TO_KEBAB[key] ?? key;
839
- if (key === "allowedTools" && Array.isArray(value)) {
840
- result[yamlKey] = value.join(" ");
841
- } else {
842
- result[yamlKey] = value;
843
- }
844
- }
845
- return result;
846
- }
847
- function validateSkill(skill) {
848
- const rawData = toRawYamlKeys(skill.metadata);
849
- const valid = validateFrontmatter(rawData);
850
- return {
851
- valid,
852
- errors: valid ? [] : (validateFrontmatter.errors ?? []).map((e) => ({
853
- message: e.message ?? "Validation error"
854
- })),
855
- warnings: []
856
- };
857
- }
858
-
859
- // ../core/dist/registry.js
860
- import { promises as fs2 } from "fs";
861
- import { join, basename } from "path";
862
- function makeInvalidSkillPlaceholder(dirName, sourcePath, errors) {
863
- return Object.freeze({
864
- metadata: Object.freeze({
865
- name: dirName,
866
- description: "[INVALID] This skill failed validation and cannot be applied"
867
- }),
868
- body: `This skill is invalid and cannot be applied.
869
-
870
- Validation errors:
871
- ${errors}
872
-
873
- Please inform the user that the skill at \`${sourcePath}\` is invalid and needs to be fixed before it can be used.`
874
- });
875
- }
876
- var SkillRegistry = class {
877
- skills = /* @__PURE__ */ new Map();
878
- skillsDir = "";
879
- skillsDirs = [];
880
- lastLoaded;
881
- /**
882
- * Load skills from multiple directories with optional filtering
883
- *
884
- * Loads skills from each directory in order, with later directories
885
- * overriding skills from earlier ones. Supports optional filtering
886
- * to only include skills specified in an allow list.
887
- *
888
- * Expected structure: <skillsDir>/<skill-name>/SKILL.md (exactly 2 levels deep)
889
- * - Throws on first required directory error (fail fast)
890
- * - Skips non-existent optional directories (2nd onwards)
891
- * - Ignores hidden directories (.git/, etc.)
892
- * - Ignores non-directory files
893
- * - Validates directory name matches skill name in SKILL.md
894
- *
895
- * @param skillsDirs - Array of directories containing skill subdirectories
896
- * @param allowedSkills - Optional set of skill names to include. If provided, only these skills are loaded.
897
- * @param requiredLabels - Optional set of labels. If provided, only skills that have at least one matching label are loaded.
898
- * @returns Load result with count, directories, and timestamp
899
- * @throws Error if first directory is invalid or any skill is invalid
900
- */
901
- async loadSkillsFromMultiple(skillsDirs, allowedSkills, requiredLabels) {
902
- this.skills.clear();
903
- this.skillsDir = "";
904
- this.skillsDirs = [];
905
- let totalLoaded = 0;
906
- for (let i = 0; i < skillsDirs.length; i++) {
907
- const skillsDir = skillsDirs[i];
908
- const isRequired = i === 0;
909
- totalLoaded += await this.loadSkillsFromDirectory(skillsDir, allowedSkills, isRequired, requiredLabels);
910
- }
911
- this.skillsDirs = skillsDirs;
912
- this.skillsDir = skillsDirs[0] || "";
913
- this.lastLoaded = /* @__PURE__ */ new Date();
914
- return {
915
- loaded: totalLoaded,
916
- skillsDir: this.skillsDirs.join(":"),
917
- timestamp: this.lastLoaded
918
- };
919
- }
920
- /**
921
- * Load skills from a single directory (internal helper)
922
- *
923
- * @param skillsDir - Directory containing skill subdirectories
924
- * @param allowedSkills - Optional set of skill names to include
925
- * @param isRequired - Whether this directory is required (first directory always is)
926
- * @param requiredLabels - Optional set of labels. If provided, only skills with at least one matching label are loaded.
927
- * @returns Number of skills loaded from this directory
928
- * @throws Error if directory is invalid and required
929
- */
930
- async loadSkillsFromDirectory(skillsDir, allowedSkills, isRequired = true, requiredLabels) {
931
- let stat;
932
- try {
933
- stat = await fs2.stat(skillsDir);
934
- } catch {
935
- if (isRequired) {
936
- throw new Error(`Skills directory does not exist: ${skillsDir}`);
937
- }
938
- return 0;
939
- }
940
- if (!stat.isDirectory()) {
941
- throw new Error(`Skills directory is not a directory: ${skillsDir}`);
942
- }
943
- const entries = await fs2.readdir(skillsDir, { withFileTypes: true });
944
- let loadedCount = 0;
945
- for (const entry of entries) {
946
- if (!entry.isDirectory()) {
947
- continue;
948
- }
949
- if (entry.name.startsWith(".")) {
950
- continue;
951
- }
952
- if (allowedSkills && !allowedSkills.has(entry.name)) {
953
- continue;
954
- }
955
- const skillDir = join(skillsDir, entry.name);
956
- const skillPath = join(skillDir, "SKILL.md");
957
- let skillStat;
958
- try {
959
- skillStat = await fs2.stat(skillPath);
960
- } catch {
961
- throw new Error(`Missing SKILL.md in: ${skillDir}`);
962
- }
963
- if (!skillStat.isFile()) {
964
- throw new Error(`Missing SKILL.md in: ${skillDir}`);
965
- }
966
- const parseResult = await parseSkill(skillPath);
967
- if (!parseResult.success) {
968
- throw new Error(`Failed to parse SKILL.md in ${skillDir}: ${parseResult.error.message}`);
969
- }
970
- const { skill } = parseResult;
971
- const validationResult = validateSkill(skill);
972
- if (!validationResult.valid) {
973
- const errorMessages = validationResult.errors.map((e) => e.message).join(", ");
974
- const dirName2 = basename(skillDir);
975
- const placeholder = makeInvalidSkillPlaceholder(dirName2, skillPath, errorMessages);
976
- this.skills.set(dirName2, { skill: placeholder, sourcePath: skillPath });
977
- loadedCount++;
978
- continue;
979
- }
980
- const dirName = basename(skillDir);
981
- if (skill.metadata.name !== dirName) {
982
- throw new Error(`Directory name '${dirName}' does not match skill name '${skill.metadata.name}' in ${skillPath}`);
983
- }
984
- if (requiredLabels && requiredLabels.size > 0) {
985
- const skillLabels = skill.metadata.labels;
986
- if (!skillLabels || !skillLabels.some((label) => requiredLabels.has(label))) {
987
- continue;
988
- }
989
- }
990
- this.skills.set(skill.metadata.name, { skill, sourcePath: skillPath });
991
- loadedCount++;
992
- }
993
- return loadedCount;
994
- }
995
- /**
996
- * Load skills from a single directory with strict error handling
997
- *
998
- * Expected structure: <skillsDir>/<skill-name>/SKILL.md (exactly 2 levels deep)
999
- * - Throws on any error (fail fast)
1000
- * - Ignores hidden directories (.git/, etc.)
1001
- * - Ignores non-directory files
1002
- * - Validates directory name matches skill name in SKILL.md
1003
- *
1004
- * @param skillsDir - Directory containing skill subdirectories
1005
- * @returns Load result with count, directory, and timestamp
1006
- * @throws Error if directory doesn't exist, isn't a directory, or any skill is invalid
1007
- */
1008
- async loadSkills(skillsDir) {
1009
- this.skills.clear();
1010
- this.skillsDir = "";
1011
- this.skillsDirs = [];
1012
- let stat;
1013
- try {
1014
- stat = await fs2.stat(skillsDir);
1015
- } catch {
1016
- throw new Error(`Skills directory does not exist: ${skillsDir}`);
1017
- }
1018
- if (!stat.isDirectory()) {
1019
- throw new Error(`Skills directory is not a directory: ${skillsDir}`);
1020
- }
1021
- const entries = await fs2.readdir(skillsDir, { withFileTypes: true });
1022
- let loadedCount = 0;
1023
- for (const entry of entries) {
1024
- if (!entry.isDirectory()) {
1025
- continue;
1026
- }
1027
- if (entry.name.startsWith(".")) {
1028
- continue;
1029
- }
1030
- const skillDir = join(skillsDir, entry.name);
1031
- const skillPath = join(skillDir, "SKILL.md");
1032
- let skillStat;
1033
- try {
1034
- skillStat = await fs2.stat(skillPath);
1035
- } catch {
1036
- throw new Error(`Missing SKILL.md in: ${skillDir}`);
1037
- }
1038
- if (!skillStat.isFile()) {
1039
- throw new Error(`Missing SKILL.md in: ${skillDir}`);
1040
- }
1041
- const parseResult = await parseSkill(skillPath);
1042
- if (!parseResult.success) {
1043
- throw new Error(`Failed to parse SKILL.md in ${skillDir}: ${parseResult.error.message}`);
1044
- }
1045
- const { skill } = parseResult;
1046
- const validationResult = validateSkill(skill);
1047
- if (!validationResult.valid) {
1048
- const errorMessages = validationResult.errors.map((e) => e.message).join(", ");
1049
- const dirName2 = basename(skillDir);
1050
- const placeholder = makeInvalidSkillPlaceholder(dirName2, skillPath, errorMessages);
1051
- this.skills.set(dirName2, { skill: placeholder, sourcePath: skillPath });
1052
- loadedCount++;
1053
- continue;
1054
- }
1055
- const dirName = basename(skillDir);
1056
- if (skill.metadata.name !== dirName) {
1057
- throw new Error(`Directory name '${dirName}' does not match skill name '${skill.metadata.name}' in ${skillPath}`);
1058
- }
1059
- this.skills.set(skill.metadata.name, { skill, sourcePath: skillPath });
1060
- loadedCount++;
1061
- }
1062
- this.skillsDir = skillsDir;
1063
- this.skillsDirs = [skillsDir];
1064
- this.lastLoaded = /* @__PURE__ */ new Date();
1065
- return {
1066
- loaded: loadedCount,
1067
- skillsDir,
1068
- timestamp: this.lastLoaded
1069
- };
1070
- }
1071
- /**
1072
- * Get a skill by name
1073
- *
1074
- * @param name - The skill name
1075
- * @returns The skill or undefined if not found
1076
- */
1077
- getSkill(name) {
1078
- const entry = this.skills.get(name);
1079
- if (!entry) {
1080
- return void 0;
1081
- }
1082
- return {
1083
- metadata: { ...entry.skill.metadata },
1084
- body: entry.skill.body
1085
- };
1086
- }
1087
- /**
1088
- * Get all loaded skills
1089
- *
1090
- * @returns Array of all skills
1091
- */
1092
- getAllSkills() {
1093
- return Array.from(this.skills.values()).map((entry) => ({
1094
- metadata: { ...entry.skill.metadata },
1095
- body: entry.skill.body
1096
- }));
1097
- }
1098
- /**
1099
- * Get skill metadata without body content
1100
- *
1101
- * @param name - The skill name
1102
- * @returns The skill metadata or undefined if not found
1103
- */
1104
- getSkillMetadata(name) {
1105
- const entry = this.skills.get(name);
1106
- if (!entry) {
1107
- return void 0;
1108
- }
1109
- return { ...entry.skill.metadata };
1110
- }
1111
- /**
1112
- * Get all skill metadata without body content
1113
- *
1114
- * @returns Array of all skill metadata
1115
- */
1116
- getAllMetadata() {
1117
- return Array.from(this.skills.values()).map((entry) => ({
1118
- ...entry.skill.metadata
1119
- }));
1120
- }
1121
- /**
1122
- * Get current registry state
1123
- *
1124
- * @returns Current state with counts and source info
1125
- */
1126
- getState() {
1127
- return {
1128
- skillCount: this.skills.size,
1129
- skillsDir: this.skillsDir,
1130
- lastLoaded: this.lastLoaded
1131
- };
1132
- }
1133
- /**
1134
- * Get the skills directory (base path for resolving relative references)
1135
- *
1136
- * @returns Absolute path to the skills directory, or empty string if no skills loaded
1137
- */
1138
- getSkillsDirectory() {
1139
- return this.skillsDir;
1140
- }
1141
- };
1142
-
1143
- // ../core/dist/skills-lock.js
1144
- import { promises as fs3 } from "fs";
1145
- import { join as join2 } from "path";
1146
- async function loadSkillsLock(lockFilePath) {
1147
- try {
1148
- const content = await fs3.readFile(lockFilePath, "utf-8");
1149
- const lock = JSON.parse(content);
1150
- return lock;
1151
- } catch (error) {
1152
- if (error.code === "ENOENT") {
1153
- return null;
1154
- }
1155
- throw error;
1156
- }
1157
- }
1158
- async function getAllowedSkills(lockFilePath) {
1159
- const lock = await loadSkillsLock(lockFilePath);
1160
- if (!lock) {
1161
- return void 0;
1162
- }
1163
- return new Set(Object.keys(lock.skills));
1164
- }
1165
- async function getAllowedSkillsFromProject(projectDir) {
1166
- const lockFilePath = join2(projectDir, "skills-lock.json");
1167
- return getAllowedSkills(lockFilePath);
1168
- }
1169
- async function getAllowedSkillsFromAgentskills(projectDir) {
1170
- const lockFilePath = join2(projectDir, "skills-lock.json");
1171
- return getAllowedSkills(lockFilePath);
1172
- }
1173
-
1174
- // ../core/dist/mcp-config-adapters.js
1175
- var StandardMcpConfigAdapter = class {
1176
- toStandard(clientConfig) {
1177
- const config = clientConfig;
1178
- if (!config.mcpServers) {
1179
- config.mcpServers = {};
1180
- }
1181
- return config;
1182
- }
1183
- toClient(mcpConfig) {
1184
- return mcpConfig;
1185
- }
1186
- };
1187
- var OpenCodeConfigAdapter = class {
1188
- toStandard(clientConfig) {
1189
- const config = clientConfig;
1190
- const mcpServers = {};
1191
- if (config.mcp) {
1192
- for (const [name, serverConfig] of Object.entries(config.mcp)) {
1193
- const server = serverConfig;
1194
- if (server.type === "local" && server.command) {
1195
- const [command, ...args] = server.command;
1196
- mcpServers[name] = {
1197
- command,
1198
- args,
1199
- env: server.environment || {}
1200
- };
1201
- }
1202
- }
1203
- }
1204
- return { mcpServers };
1205
- }
1206
- toClient(mcpConfig, existingConfig) {
1207
- const openCodeConfig = existingConfig || {
1208
- $schema: "https://opencode.ai/config.json"
1209
- };
1210
- openCodeConfig.permission = {
1211
- ...openCodeConfig.permission || {},
1212
- skill: "deny"
1213
- };
1214
- openCodeConfig.mcp = {};
1215
- for (const [name, serverConfig] of Object.entries(mcpConfig.mcpServers)) {
1216
- openCodeConfig.mcp[name] = {
1217
- type: "local",
1218
- command: [serverConfig.command, ...serverConfig.args || []],
1219
- enabled: true,
1220
- environment: serverConfig.env || {}
1221
- };
1222
- }
1223
- delete openCodeConfig.mcpServers;
1224
- return openCodeConfig;
1225
- }
1226
- };
1227
- var VsCodeConfigAdapter = class {
1228
- toStandard(clientConfig) {
1229
- const config = clientConfig;
1230
- const mcpServers = config.servers || {};
1231
- return { mcpServers };
1232
- }
1233
- toClient(mcpConfig, existingConfig) {
1234
- const vsCodeConfig = existingConfig || {
1235
- $schema: "https://opencode.ai/config.json"
1236
- };
1237
- return {
1238
- ...vsCodeConfig,
1239
- servers: mcpConfig.mcpServers
1240
- };
1241
- }
1242
- };
1243
- var McpConfigAdapterRegistry = class {
1244
- static adapters = /* @__PURE__ */ new Map();
1245
- static standardAdapter = new StandardMcpConfigAdapter();
1246
- /**
1247
- * Initialize the registry with default adapters
1248
- */
1249
- static initialize() {
1250
- this.register("opencode", new OpenCodeConfigAdapter());
1251
- this.register("github-copilot", new VsCodeConfigAdapter());
1252
- }
1253
- /**
1254
- * Register a custom adapter for a client type
1255
- */
1256
- static register(clientType, adapter) {
1257
- this.adapters.set(clientType, adapter);
1258
- }
1259
- /**
1260
- * Get the adapter for a client type
1261
- * Returns standard adapter if no custom adapter is registered
1262
- */
1263
- static getAdapter(clientType) {
1264
- return this.adapters.get(clientType) || this.standardAdapter;
1265
- }
1266
- /**
1267
- * Check if a client has a custom adapter
1268
- */
1269
- static hasCustomAdapter(clientType) {
1270
- return this.adapters.has(clientType);
1271
- }
1272
- };
1273
- McpConfigAdapterRegistry.initialize();
1274
-
1275
- export {
1276
- ConfigGeneratorRegistry,
1277
- GitHubCopilotGenerator,
1278
- KiroGenerator,
1279
- OpenCodeMcpGenerator,
1280
- OpenCodeAgentGenerator,
1281
- VsCodeGenerator,
1282
- parseSkillContent,
1283
- parseSkill,
1284
- validateSkill,
1285
- SkillRegistry,
1286
- loadSkillsLock,
1287
- getAllowedSkills,
1288
- getAllowedSkillsFromProject,
1289
- getAllowedSkillsFromAgentskills,
1290
- StandardMcpConfigAdapter,
1291
- OpenCodeConfigAdapter,
1292
- VsCodeConfigAdapter,
1293
- McpConfigAdapterRegistry
1294
- };