agentpacks 0.9.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -14
- package/dist/api.js +403 -179
- package/dist/cli/export-cmd.js +58 -5
- package/dist/cli/generate.js +268 -59
- package/dist/cli/import-cmd.js +203 -95
- package/dist/cli/install.js +1 -0
- package/dist/cli/models-explain.js +56 -0
- package/dist/cli/pack/list.js +56 -0
- package/dist/cli/pack/validate.js +143 -18
- package/dist/cli/publish.js +1 -0
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +1 -0
- package/dist/core/index.js +56 -0
- package/dist/core/metarepo.js +1 -0
- package/dist/core/pack-loader.js +56 -0
- package/dist/exporters/cursor-plugin.js +109 -22
- package/dist/exporters/index.js +109 -22
- package/dist/features/index.d.ts +1 -1
- package/dist/features/index.js +59 -0
- package/dist/features/skills.d.ts +22 -0
- package/dist/features/skills.js +60 -1
- package/dist/importers/cursor.js +122 -26
- package/dist/importers/opencode.js +138 -24
- package/dist/importers/rulesync.js +147 -33
- package/dist/index.js +484 -244
- package/dist/node/api.js +403 -179
- package/dist/node/cli/export-cmd.js +58 -5
- package/dist/node/cli/generate.js +268 -59
- package/dist/node/cli/import-cmd.js +203 -95
- package/dist/node/cli/install.js +1 -0
- package/dist/node/cli/models-explain.js +56 -0
- package/dist/node/cli/pack/list.js +56 -0
- package/dist/node/cli/pack/validate.js +143 -18
- package/dist/node/cli/publish.js +1 -0
- package/dist/node/core/config.js +1 -0
- package/dist/node/core/index.js +56 -0
- package/dist/node/core/metarepo.js +1 -0
- package/dist/node/core/pack-loader.js +56 -0
- package/dist/node/exporters/cursor-plugin.js +109 -22
- package/dist/node/exporters/index.js +109 -22
- package/dist/node/features/index.js +59 -0
- package/dist/node/features/skills.js +60 -1
- package/dist/node/importers/cursor.js +122 -26
- package/dist/node/importers/opencode.js +138 -24
- package/dist/node/importers/rulesync.js +147 -33
- package/dist/node/index.js +484 -244
- package/dist/node/targets/claude-code.js +56 -1
- package/dist/node/targets/codex-cli.js +56 -1
- package/dist/node/targets/copilot.js +56 -1
- package/dist/node/targets/cursor.js +56 -5
- package/dist/node/targets/index.js +268 -59
- package/dist/node/targets/mistral-vibe.js +661 -0
- package/dist/node/targets/opencode.js +56 -1
- package/dist/node/targets/registry.js +267 -59
- package/dist/node/utils/model-allowlist.js +6 -2
- package/dist/targets/claude-code.js +56 -1
- package/dist/targets/codex-cli.js +56 -1
- package/dist/targets/copilot.js +56 -1
- package/dist/targets/cursor.js +56 -5
- package/dist/targets/index.d.ts +1 -0
- package/dist/targets/index.js +268 -59
- package/dist/targets/mistral-vibe.d.ts +13 -0
- package/dist/targets/mistral-vibe.js +661 -0
- package/dist/targets/opencode.js +56 -1
- package/dist/targets/registry.js +267 -59
- package/dist/utils/model-allowlist.js +6 -2
- package/package.json +15 -3
|
@@ -129,11 +129,101 @@ function serializeFrontmatter(data, content) {
|
|
|
129
129
|
return matter.stringify(content, filtered);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// src/features/skills.ts
|
|
133
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
134
|
+
import { basename, join as join2 } from "path";
|
|
135
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
136
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
137
|
+
function parseSkills(skillsDir, packName) {
|
|
138
|
+
const dirs = listDirs(skillsDir);
|
|
139
|
+
const skills = [];
|
|
140
|
+
for (const dir of dirs) {
|
|
141
|
+
const skillMd = join2(dir, "SKILL.md");
|
|
142
|
+
if (existsSync2(skillMd)) {
|
|
143
|
+
skills.push(parseSkillFile(skillMd, dir, packName));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return skills;
|
|
147
|
+
}
|
|
148
|
+
function parseSkillFile(filepath, skillDir, packName) {
|
|
149
|
+
const raw = readFileSync2(filepath, "utf-8");
|
|
150
|
+
const { data, content } = parseFrontmatter(raw);
|
|
151
|
+
return {
|
|
152
|
+
name: data.name ?? basename(skillDir),
|
|
153
|
+
sourcePath: filepath,
|
|
154
|
+
sourceDir: skillDir,
|
|
155
|
+
packName,
|
|
156
|
+
meta: data,
|
|
157
|
+
content
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function buildSkillFrontmatter(skill) {
|
|
161
|
+
return {
|
|
162
|
+
...skill.meta,
|
|
163
|
+
name: skill.name
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function serializeSkill(skill) {
|
|
167
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
168
|
+
}
|
|
169
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
170
|
+
const { data, content } = parseFrontmatter(source);
|
|
171
|
+
const normalized = {
|
|
172
|
+
...data,
|
|
173
|
+
name: skillName
|
|
174
|
+
};
|
|
175
|
+
let addedDescription = false;
|
|
176
|
+
const description = normalized.description;
|
|
177
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
178
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
179
|
+
addedDescription = true;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
content: serializeFrontmatter(normalized, content),
|
|
183
|
+
addedDescription
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
187
|
+
const errors = [];
|
|
188
|
+
const dirName = basename(skill.sourceDir);
|
|
189
|
+
const declaredName = skill.meta.name;
|
|
190
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
191
|
+
errors.push('Missing required frontmatter field "name".');
|
|
192
|
+
} else {
|
|
193
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
194
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
195
|
+
}
|
|
196
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
197
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
198
|
+
}
|
|
199
|
+
if (declaredName !== dirName) {
|
|
200
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const description = skill.meta.description;
|
|
204
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
205
|
+
errors.push('Missing required frontmatter field "description".');
|
|
206
|
+
}
|
|
207
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
208
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
209
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
210
|
+
}
|
|
211
|
+
return errors;
|
|
212
|
+
}
|
|
213
|
+
function skillMatchesTarget(skill, targetId) {
|
|
214
|
+
const { targets } = skill.meta;
|
|
215
|
+
if (!targets || targets === "*")
|
|
216
|
+
return true;
|
|
217
|
+
if (Array.isArray(targets) && targets.includes("*"))
|
|
218
|
+
return true;
|
|
219
|
+
return Array.isArray(targets) && targets.includes(targetId);
|
|
220
|
+
}
|
|
221
|
+
|
|
132
222
|
// src/features/hooks.ts
|
|
133
|
-
import { join as
|
|
223
|
+
import { join as join3 } from "path";
|
|
134
224
|
var TARGET_OVERRIDE_KEYS = ["cursor", "claudecode", "opencode"];
|
|
135
225
|
function parseHooks(packDir, packName) {
|
|
136
|
-
const hooksPath =
|
|
226
|
+
const hooksPath = join3(packDir, "hooks", "hooks.json");
|
|
137
227
|
const raw = readJsonOrNull(hooksPath);
|
|
138
228
|
if (!raw)
|
|
139
229
|
return null;
|
|
@@ -168,7 +258,7 @@ function resolveHooksForTarget(hooks, targetId) {
|
|
|
168
258
|
}
|
|
169
259
|
|
|
170
260
|
// src/exporters/cursor-plugin.ts
|
|
171
|
-
import { resolve, join as
|
|
261
|
+
import { resolve, join as join4 } from "path";
|
|
172
262
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
173
263
|
function exportCursorPlugin(pack, outputDir) {
|
|
174
264
|
const filesWritten = [];
|
|
@@ -204,7 +294,7 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
204
294
|
manifest.keywords = pack.manifest.tags;
|
|
205
295
|
}
|
|
206
296
|
if (pack.rules.length > 0) {
|
|
207
|
-
const rulesDir =
|
|
297
|
+
const rulesDir = join4(pluginDir, "rules");
|
|
208
298
|
ensureDir(rulesDir);
|
|
209
299
|
manifest.rules = "rules";
|
|
210
300
|
for (const rule of pack.rules) {
|
|
@@ -217,13 +307,13 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
217
307
|
if (globs)
|
|
218
308
|
fm.globs = globs;
|
|
219
309
|
const filename = `${rule.name}.mdc`;
|
|
220
|
-
const filepath =
|
|
310
|
+
const filepath = join4(rulesDir, filename);
|
|
221
311
|
writeFileSync2(filepath, serializeFrontmatter(fm, rule.content));
|
|
222
312
|
filesWritten.push(filepath);
|
|
223
313
|
}
|
|
224
314
|
}
|
|
225
315
|
if (pack.agents.length > 0) {
|
|
226
|
-
const agentsDir =
|
|
316
|
+
const agentsDir = join4(pluginDir, "agents");
|
|
227
317
|
ensureDir(agentsDir);
|
|
228
318
|
manifest.agents = "agents";
|
|
229
319
|
for (const agent of pack.agents) {
|
|
@@ -232,29 +322,26 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
232
322
|
description: agent.meta.description ?? ""
|
|
233
323
|
};
|
|
234
324
|
const filename = `${agent.name}.md`;
|
|
235
|
-
const filepath =
|
|
325
|
+
const filepath = join4(agentsDir, filename);
|
|
236
326
|
writeFileSync2(filepath, serializeFrontmatter(fm, agent.content));
|
|
237
327
|
filesWritten.push(filepath);
|
|
238
328
|
}
|
|
239
329
|
}
|
|
240
330
|
if (pack.skills.length > 0) {
|
|
241
|
-
const skillsDir =
|
|
331
|
+
const skillsDir = join4(pluginDir, "skills");
|
|
242
332
|
ensureDir(skillsDir);
|
|
243
333
|
manifest.skills = "skills";
|
|
244
334
|
for (const skill of pack.skills) {
|
|
245
|
-
const skillSubDir =
|
|
335
|
+
const skillSubDir = join4(skillsDir, skill.name);
|
|
246
336
|
ensureDir(skillSubDir);
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
};
|
|
251
|
-
const filepath = join3(skillSubDir, "SKILL.md");
|
|
252
|
-
writeFileSync2(filepath, serializeFrontmatter(fm, skill.content));
|
|
337
|
+
const filepath = join4(skillSubDir, "SKILL.md");
|
|
338
|
+
const content = serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
339
|
+
writeFileSync2(filepath, content);
|
|
253
340
|
filesWritten.push(filepath);
|
|
254
341
|
}
|
|
255
342
|
}
|
|
256
343
|
if (pack.commands.length > 0) {
|
|
257
|
-
const commandsDir =
|
|
344
|
+
const commandsDir = join4(pluginDir, "commands");
|
|
258
345
|
ensureDir(commandsDir);
|
|
259
346
|
manifest.commands = "commands";
|
|
260
347
|
for (const cmd of pack.commands) {
|
|
@@ -265,7 +352,7 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
265
352
|
fm.description = cmd.meta.description;
|
|
266
353
|
}
|
|
267
354
|
const filename = `${cmd.name}.md`;
|
|
268
|
-
const filepath =
|
|
355
|
+
const filepath = join4(commandsDir, filename);
|
|
269
356
|
writeFileSync2(filepath, serializeFrontmatter(fm, cmd.content));
|
|
270
357
|
filesWritten.push(filepath);
|
|
271
358
|
}
|
|
@@ -273,9 +360,9 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
273
360
|
if (pack.hooks) {
|
|
274
361
|
const events = resolveHooksForTarget(pack.hooks, "cursor");
|
|
275
362
|
if (Object.keys(events).length > 0) {
|
|
276
|
-
const hooksDir =
|
|
363
|
+
const hooksDir = join4(pluginDir, "hooks");
|
|
277
364
|
ensureDir(hooksDir);
|
|
278
|
-
const filepath =
|
|
365
|
+
const filepath = join4(hooksDir, "hooks.json");
|
|
279
366
|
writeFileSync2(filepath, JSON.stringify({ version: pack.hooks.version ?? 1, hooks: events }, null, 2) + `
|
|
280
367
|
`);
|
|
281
368
|
filesWritten.push(filepath);
|
|
@@ -284,14 +371,14 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
284
371
|
}
|
|
285
372
|
if (pack.mcp && Object.keys(pack.mcp.servers).length > 0) {
|
|
286
373
|
manifest.mcpServers = ".mcp.json";
|
|
287
|
-
const filepath =
|
|
374
|
+
const filepath = join4(pluginDir, ".mcp.json");
|
|
288
375
|
writeFileSync2(filepath, JSON.stringify({ mcpServers: pack.mcp.servers }, null, 2) + `
|
|
289
376
|
`);
|
|
290
377
|
filesWritten.push(filepath);
|
|
291
378
|
}
|
|
292
|
-
const manifestDir =
|
|
379
|
+
const manifestDir = join4(pluginDir, ".cursor-plugin");
|
|
293
380
|
ensureDir(manifestDir);
|
|
294
|
-
const manifestPath =
|
|
381
|
+
const manifestPath = join4(manifestDir, "plugin.json");
|
|
295
382
|
writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
296
383
|
`);
|
|
297
384
|
filesWritten.push(manifestPath);
|
package/dist/exporters/index.js
CHANGED
|
@@ -129,11 +129,101 @@ function serializeFrontmatter(data, content) {
|
|
|
129
129
|
return matter.stringify(content, filtered);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// src/features/skills.ts
|
|
133
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
134
|
+
import { basename, join as join2 } from "path";
|
|
135
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
136
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
137
|
+
function parseSkills(skillsDir, packName) {
|
|
138
|
+
const dirs = listDirs(skillsDir);
|
|
139
|
+
const skills = [];
|
|
140
|
+
for (const dir of dirs) {
|
|
141
|
+
const skillMd = join2(dir, "SKILL.md");
|
|
142
|
+
if (existsSync2(skillMd)) {
|
|
143
|
+
skills.push(parseSkillFile(skillMd, dir, packName));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return skills;
|
|
147
|
+
}
|
|
148
|
+
function parseSkillFile(filepath, skillDir, packName) {
|
|
149
|
+
const raw = readFileSync2(filepath, "utf-8");
|
|
150
|
+
const { data, content } = parseFrontmatter(raw);
|
|
151
|
+
return {
|
|
152
|
+
name: data.name ?? basename(skillDir),
|
|
153
|
+
sourcePath: filepath,
|
|
154
|
+
sourceDir: skillDir,
|
|
155
|
+
packName,
|
|
156
|
+
meta: data,
|
|
157
|
+
content
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function buildSkillFrontmatter(skill) {
|
|
161
|
+
return {
|
|
162
|
+
...skill.meta,
|
|
163
|
+
name: skill.name
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function serializeSkill(skill) {
|
|
167
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
168
|
+
}
|
|
169
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
170
|
+
const { data, content } = parseFrontmatter(source);
|
|
171
|
+
const normalized = {
|
|
172
|
+
...data,
|
|
173
|
+
name: skillName
|
|
174
|
+
};
|
|
175
|
+
let addedDescription = false;
|
|
176
|
+
const description = normalized.description;
|
|
177
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
178
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
179
|
+
addedDescription = true;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
content: serializeFrontmatter(normalized, content),
|
|
183
|
+
addedDescription
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
187
|
+
const errors = [];
|
|
188
|
+
const dirName = basename(skill.sourceDir);
|
|
189
|
+
const declaredName = skill.meta.name;
|
|
190
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
191
|
+
errors.push('Missing required frontmatter field "name".');
|
|
192
|
+
} else {
|
|
193
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
194
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
195
|
+
}
|
|
196
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
197
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
198
|
+
}
|
|
199
|
+
if (declaredName !== dirName) {
|
|
200
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const description = skill.meta.description;
|
|
204
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
205
|
+
errors.push('Missing required frontmatter field "description".');
|
|
206
|
+
}
|
|
207
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
208
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
209
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
210
|
+
}
|
|
211
|
+
return errors;
|
|
212
|
+
}
|
|
213
|
+
function skillMatchesTarget(skill, targetId) {
|
|
214
|
+
const { targets } = skill.meta;
|
|
215
|
+
if (!targets || targets === "*")
|
|
216
|
+
return true;
|
|
217
|
+
if (Array.isArray(targets) && targets.includes("*"))
|
|
218
|
+
return true;
|
|
219
|
+
return Array.isArray(targets) && targets.includes(targetId);
|
|
220
|
+
}
|
|
221
|
+
|
|
132
222
|
// src/features/hooks.ts
|
|
133
|
-
import { join as
|
|
223
|
+
import { join as join3 } from "path";
|
|
134
224
|
var TARGET_OVERRIDE_KEYS = ["cursor", "claudecode", "opencode"];
|
|
135
225
|
function parseHooks(packDir, packName) {
|
|
136
|
-
const hooksPath =
|
|
226
|
+
const hooksPath = join3(packDir, "hooks", "hooks.json");
|
|
137
227
|
const raw = readJsonOrNull(hooksPath);
|
|
138
228
|
if (!raw)
|
|
139
229
|
return null;
|
|
@@ -168,7 +258,7 @@ function resolveHooksForTarget(hooks, targetId) {
|
|
|
168
258
|
}
|
|
169
259
|
|
|
170
260
|
// src/exporters/cursor-plugin.ts
|
|
171
|
-
import { resolve, join as
|
|
261
|
+
import { resolve, join as join4 } from "path";
|
|
172
262
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
173
263
|
function exportCursorPlugin(pack, outputDir) {
|
|
174
264
|
const filesWritten = [];
|
|
@@ -204,7 +294,7 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
204
294
|
manifest.keywords = pack.manifest.tags;
|
|
205
295
|
}
|
|
206
296
|
if (pack.rules.length > 0) {
|
|
207
|
-
const rulesDir =
|
|
297
|
+
const rulesDir = join4(pluginDir, "rules");
|
|
208
298
|
ensureDir(rulesDir);
|
|
209
299
|
manifest.rules = "rules";
|
|
210
300
|
for (const rule of pack.rules) {
|
|
@@ -217,13 +307,13 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
217
307
|
if (globs)
|
|
218
308
|
fm.globs = globs;
|
|
219
309
|
const filename = `${rule.name}.mdc`;
|
|
220
|
-
const filepath =
|
|
310
|
+
const filepath = join4(rulesDir, filename);
|
|
221
311
|
writeFileSync2(filepath, serializeFrontmatter(fm, rule.content));
|
|
222
312
|
filesWritten.push(filepath);
|
|
223
313
|
}
|
|
224
314
|
}
|
|
225
315
|
if (pack.agents.length > 0) {
|
|
226
|
-
const agentsDir =
|
|
316
|
+
const agentsDir = join4(pluginDir, "agents");
|
|
227
317
|
ensureDir(agentsDir);
|
|
228
318
|
manifest.agents = "agents";
|
|
229
319
|
for (const agent of pack.agents) {
|
|
@@ -232,29 +322,26 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
232
322
|
description: agent.meta.description ?? ""
|
|
233
323
|
};
|
|
234
324
|
const filename = `${agent.name}.md`;
|
|
235
|
-
const filepath =
|
|
325
|
+
const filepath = join4(agentsDir, filename);
|
|
236
326
|
writeFileSync2(filepath, serializeFrontmatter(fm, agent.content));
|
|
237
327
|
filesWritten.push(filepath);
|
|
238
328
|
}
|
|
239
329
|
}
|
|
240
330
|
if (pack.skills.length > 0) {
|
|
241
|
-
const skillsDir =
|
|
331
|
+
const skillsDir = join4(pluginDir, "skills");
|
|
242
332
|
ensureDir(skillsDir);
|
|
243
333
|
manifest.skills = "skills";
|
|
244
334
|
for (const skill of pack.skills) {
|
|
245
|
-
const skillSubDir =
|
|
335
|
+
const skillSubDir = join4(skillsDir, skill.name);
|
|
246
336
|
ensureDir(skillSubDir);
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
};
|
|
251
|
-
const filepath = join3(skillSubDir, "SKILL.md");
|
|
252
|
-
writeFileSync2(filepath, serializeFrontmatter(fm, skill.content));
|
|
337
|
+
const filepath = join4(skillSubDir, "SKILL.md");
|
|
338
|
+
const content = serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
339
|
+
writeFileSync2(filepath, content);
|
|
253
340
|
filesWritten.push(filepath);
|
|
254
341
|
}
|
|
255
342
|
}
|
|
256
343
|
if (pack.commands.length > 0) {
|
|
257
|
-
const commandsDir =
|
|
344
|
+
const commandsDir = join4(pluginDir, "commands");
|
|
258
345
|
ensureDir(commandsDir);
|
|
259
346
|
manifest.commands = "commands";
|
|
260
347
|
for (const cmd of pack.commands) {
|
|
@@ -265,7 +352,7 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
265
352
|
fm.description = cmd.meta.description;
|
|
266
353
|
}
|
|
267
354
|
const filename = `${cmd.name}.md`;
|
|
268
|
-
const filepath =
|
|
355
|
+
const filepath = join4(commandsDir, filename);
|
|
269
356
|
writeFileSync2(filepath, serializeFrontmatter(fm, cmd.content));
|
|
270
357
|
filesWritten.push(filepath);
|
|
271
358
|
}
|
|
@@ -273,9 +360,9 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
273
360
|
if (pack.hooks) {
|
|
274
361
|
const events = resolveHooksForTarget(pack.hooks, "cursor");
|
|
275
362
|
if (Object.keys(events).length > 0) {
|
|
276
|
-
const hooksDir =
|
|
363
|
+
const hooksDir = join4(pluginDir, "hooks");
|
|
277
364
|
ensureDir(hooksDir);
|
|
278
|
-
const filepath =
|
|
365
|
+
const filepath = join4(hooksDir, "hooks.json");
|
|
279
366
|
writeFileSync2(filepath, JSON.stringify({ version: pack.hooks.version ?? 1, hooks: events }, null, 2) + `
|
|
280
367
|
`);
|
|
281
368
|
filesWritten.push(filepath);
|
|
@@ -284,14 +371,14 @@ function exportCursorPlugin(pack, outputDir) {
|
|
|
284
371
|
}
|
|
285
372
|
if (pack.mcp && Object.keys(pack.mcp.servers).length > 0) {
|
|
286
373
|
manifest.mcpServers = ".mcp.json";
|
|
287
|
-
const filepath =
|
|
374
|
+
const filepath = join4(pluginDir, ".mcp.json");
|
|
288
375
|
writeFileSync2(filepath, JSON.stringify({ mcpServers: pack.mcp.servers }, null, 2) + `
|
|
289
376
|
`);
|
|
290
377
|
filesWritten.push(filepath);
|
|
291
378
|
}
|
|
292
|
-
const manifestDir =
|
|
379
|
+
const manifestDir = join4(pluginDir, ".cursor-plugin");
|
|
293
380
|
ensureDir(manifestDir);
|
|
294
|
-
const manifestPath =
|
|
381
|
+
const manifestPath = join4(manifestDir, "plugin.json");
|
|
295
382
|
writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
296
383
|
`);
|
|
297
384
|
filesWritten.push(manifestPath);
|
package/dist/features/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { type ParsedRule, type RuleFrontmatter, parseRules, ruleMatchesTarget, getRootRules, getDetailRules, } from './rules.js';
|
|
2
2
|
export { type ParsedCommand, type CommandFrontmatter, parseCommands, commandMatchesTarget, } from './commands.js';
|
|
3
3
|
export { type ParsedAgent, type AgentFrontmatter, parseAgents, agentMatchesTarget, } from './agents.js';
|
|
4
|
-
export { type ParsedSkill, type SkillFrontmatter, parseSkills, skillMatchesTarget, } from './skills.js';
|
|
4
|
+
export { type ParsedSkill, type SkillFrontmatter, parseSkills, buildSkillFrontmatter, serializeSkill, normalizeImportedSkillMarkdown, validateAgentSkillsFrontmatter, skillMatchesTarget, } from './skills.js';
|
|
5
5
|
export { type ParsedHooks, type HookEvents, type HookEntry, parseHooks, resolveHooksForTarget, } from './hooks.js';
|
|
6
6
|
export { type ParsedPlugin, parsePlugins } from './plugins.js';
|
|
7
7
|
export { type ParsedMcp, type McpServerEntry, parseMcp, mergeMcpConfigs, } from './mcp.js';
|
package/dist/features/index.js
CHANGED
|
@@ -219,6 +219,8 @@ function agentMatchesTarget(agent, targetId) {
|
|
|
219
219
|
// src/features/skills.ts
|
|
220
220
|
import { readFileSync as readFileSync5, existsSync as existsSync2 } from "fs";
|
|
221
221
|
import { basename as basename4, join as join2 } from "path";
|
|
222
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
223
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
222
224
|
function parseSkills(skillsDir, packName) {
|
|
223
225
|
const dirs = listDirs(skillsDir);
|
|
224
226
|
const skills = [];
|
|
@@ -242,6 +244,59 @@ function parseSkillFile(filepath, skillDir, packName) {
|
|
|
242
244
|
content
|
|
243
245
|
};
|
|
244
246
|
}
|
|
247
|
+
function buildSkillFrontmatter(skill) {
|
|
248
|
+
return {
|
|
249
|
+
...skill.meta,
|
|
250
|
+
name: skill.name
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function serializeSkill(skill) {
|
|
254
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
255
|
+
}
|
|
256
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
257
|
+
const { data, content } = parseFrontmatter(source);
|
|
258
|
+
const normalized = {
|
|
259
|
+
...data,
|
|
260
|
+
name: skillName
|
|
261
|
+
};
|
|
262
|
+
let addedDescription = false;
|
|
263
|
+
const description = normalized.description;
|
|
264
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
265
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
266
|
+
addedDescription = true;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
content: serializeFrontmatter(normalized, content),
|
|
270
|
+
addedDescription
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
274
|
+
const errors = [];
|
|
275
|
+
const dirName = basename4(skill.sourceDir);
|
|
276
|
+
const declaredName = skill.meta.name;
|
|
277
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
278
|
+
errors.push('Missing required frontmatter field "name".');
|
|
279
|
+
} else {
|
|
280
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
281
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
282
|
+
}
|
|
283
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
284
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
285
|
+
}
|
|
286
|
+
if (declaredName !== dirName) {
|
|
287
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const description = skill.meta.description;
|
|
291
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
292
|
+
errors.push('Missing required frontmatter field "description".');
|
|
293
|
+
}
|
|
294
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
295
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
296
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
297
|
+
}
|
|
298
|
+
return errors;
|
|
299
|
+
}
|
|
245
300
|
function skillMatchesTarget(skill, targetId) {
|
|
246
301
|
const { targets } = skill.meta;
|
|
247
302
|
if (!targets || targets === "*")
|
|
@@ -547,7 +602,9 @@ function scanModelsForSecrets(config) {
|
|
|
547
602
|
return warnings;
|
|
548
603
|
}
|
|
549
604
|
export {
|
|
605
|
+
validateAgentSkillsFrontmatter,
|
|
550
606
|
skillMatchesTarget,
|
|
607
|
+
serializeSkill,
|
|
551
608
|
scanModelsForSecrets,
|
|
552
609
|
ruleMatchesTarget,
|
|
553
610
|
resolveHooksForTarget,
|
|
@@ -560,12 +617,14 @@ export {
|
|
|
560
617
|
parseHooks,
|
|
561
618
|
parseCommands,
|
|
562
619
|
parseAgents,
|
|
620
|
+
normalizeImportedSkillMarkdown,
|
|
563
621
|
mergeModelsConfigs,
|
|
564
622
|
mergeMcpConfigs,
|
|
565
623
|
mergeIgnorePatterns,
|
|
566
624
|
getRootRules,
|
|
567
625
|
getDetailRules,
|
|
568
626
|
commandMatchesTarget,
|
|
627
|
+
buildSkillFrontmatter,
|
|
569
628
|
agentMatchesTarget,
|
|
570
629
|
ModelsSchema
|
|
571
630
|
};
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
export interface SkillFrontmatter {
|
|
5
5
|
name?: string;
|
|
6
6
|
description?: string;
|
|
7
|
+
'allowed-tools'?: string[];
|
|
8
|
+
compatibility?: unknown;
|
|
7
9
|
targets?: string[] | '*';
|
|
8
10
|
/** Per-target overrides */
|
|
9
11
|
claudecode?: {
|
|
@@ -36,6 +38,26 @@ export declare function parseSkills(skillsDir: string, packName: string): Parsed
|
|
|
36
38
|
* Parse a single SKILL.md file.
|
|
37
39
|
*/
|
|
38
40
|
export declare function parseSkillFile(filepath: string, skillDir: string, packName: string): ParsedSkill;
|
|
41
|
+
/**
|
|
42
|
+
* Build output frontmatter for a SKILL.md file.
|
|
43
|
+
* Preserves all metadata while ensuring `name` exists.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildSkillFrontmatter(skill: ParsedSkill): Record<string, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* Serialize a parsed skill back to SKILL.md format.
|
|
48
|
+
*/
|
|
49
|
+
export declare function serializeSkill(skill: ParsedSkill): string;
|
|
50
|
+
/**
|
|
51
|
+
* Normalize imported SKILL.md content to AgentSkills-compatible minimum metadata.
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizeImportedSkillMarkdown(source: string, skillName: string): {
|
|
54
|
+
content: string;
|
|
55
|
+
addedDescription: boolean;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Validate AgentSkills frontmatter requirements for a parsed skill.
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateAgentSkillsFrontmatter(skill: ParsedSkill): string[];
|
|
39
61
|
/**
|
|
40
62
|
* Check if a skill targets a specific tool.
|
|
41
63
|
*/
|
package/dist/features/skills.js
CHANGED
|
@@ -132,6 +132,8 @@ function serializeFrontmatter(data, content) {
|
|
|
132
132
|
// src/features/skills.ts
|
|
133
133
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
134
134
|
import { basename, join as join2 } from "path";
|
|
135
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
136
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
135
137
|
function parseSkills(skillsDir, packName) {
|
|
136
138
|
const dirs = listDirs(skillsDir);
|
|
137
139
|
const skills = [];
|
|
@@ -155,6 +157,59 @@ function parseSkillFile(filepath, skillDir, packName) {
|
|
|
155
157
|
content
|
|
156
158
|
};
|
|
157
159
|
}
|
|
160
|
+
function buildSkillFrontmatter(skill) {
|
|
161
|
+
return {
|
|
162
|
+
...skill.meta,
|
|
163
|
+
name: skill.name
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function serializeSkill(skill) {
|
|
167
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
168
|
+
}
|
|
169
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
170
|
+
const { data, content } = parseFrontmatter(source);
|
|
171
|
+
const normalized = {
|
|
172
|
+
...data,
|
|
173
|
+
name: skillName
|
|
174
|
+
};
|
|
175
|
+
let addedDescription = false;
|
|
176
|
+
const description = normalized.description;
|
|
177
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
178
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
179
|
+
addedDescription = true;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
content: serializeFrontmatter(normalized, content),
|
|
183
|
+
addedDescription
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
187
|
+
const errors = [];
|
|
188
|
+
const dirName = basename(skill.sourceDir);
|
|
189
|
+
const declaredName = skill.meta.name;
|
|
190
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
191
|
+
errors.push('Missing required frontmatter field "name".');
|
|
192
|
+
} else {
|
|
193
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
194
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
195
|
+
}
|
|
196
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
197
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
198
|
+
}
|
|
199
|
+
if (declaredName !== dirName) {
|
|
200
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const description = skill.meta.description;
|
|
204
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
205
|
+
errors.push('Missing required frontmatter field "description".');
|
|
206
|
+
}
|
|
207
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
208
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
209
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
210
|
+
}
|
|
211
|
+
return errors;
|
|
212
|
+
}
|
|
158
213
|
function skillMatchesTarget(skill, targetId) {
|
|
159
214
|
const { targets } = skill.meta;
|
|
160
215
|
if (!targets || targets === "*")
|
|
@@ -164,7 +219,11 @@ function skillMatchesTarget(skill, targetId) {
|
|
|
164
219
|
return Array.isArray(targets) && targets.includes(targetId);
|
|
165
220
|
}
|
|
166
221
|
export {
|
|
222
|
+
validateAgentSkillsFrontmatter,
|
|
167
223
|
skillMatchesTarget,
|
|
224
|
+
serializeSkill,
|
|
168
225
|
parseSkills,
|
|
169
|
-
parseSkillFile
|
|
226
|
+
parseSkillFile,
|
|
227
|
+
normalizeImportedSkillMarkdown,
|
|
228
|
+
buildSkillFrontmatter
|
|
170
229
|
};
|