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