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
|
@@ -11,6 +11,7 @@ var TARGET_IDS = [
|
|
|
11
11
|
"cursor",
|
|
12
12
|
"claudecode",
|
|
13
13
|
"codexcli",
|
|
14
|
+
"mistralvibe",
|
|
14
15
|
"geminicli",
|
|
15
16
|
"copilot",
|
|
16
17
|
"agentsmd",
|
|
@@ -351,6 +352,8 @@ function agentMatchesTarget(agent, targetId) {
|
|
|
351
352
|
// src/features/skills.ts
|
|
352
353
|
import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
|
|
353
354
|
import { basename as basename4, join as join2 } from "path";
|
|
355
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
356
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
354
357
|
function parseSkills(skillsDir, packName) {
|
|
355
358
|
const dirs = listDirs(skillsDir);
|
|
356
359
|
const skills = [];
|
|
@@ -374,6 +377,59 @@ function parseSkillFile(filepath, skillDir, packName) {
|
|
|
374
377
|
content
|
|
375
378
|
};
|
|
376
379
|
}
|
|
380
|
+
function buildSkillFrontmatter(skill) {
|
|
381
|
+
return {
|
|
382
|
+
...skill.meta,
|
|
383
|
+
name: skill.name
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function serializeSkill(skill) {
|
|
387
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
388
|
+
}
|
|
389
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
390
|
+
const { data, content } = parseFrontmatter(source);
|
|
391
|
+
const normalized = {
|
|
392
|
+
...data,
|
|
393
|
+
name: skillName
|
|
394
|
+
};
|
|
395
|
+
let addedDescription = false;
|
|
396
|
+
const description = normalized.description;
|
|
397
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
398
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
399
|
+
addedDescription = true;
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
content: serializeFrontmatter(normalized, content),
|
|
403
|
+
addedDescription
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
407
|
+
const errors = [];
|
|
408
|
+
const dirName = basename4(skill.sourceDir);
|
|
409
|
+
const declaredName = skill.meta.name;
|
|
410
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
411
|
+
errors.push('Missing required frontmatter field "name".');
|
|
412
|
+
} else {
|
|
413
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
414
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
415
|
+
}
|
|
416
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
417
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
418
|
+
}
|
|
419
|
+
if (declaredName !== dirName) {
|
|
420
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const description = skill.meta.description;
|
|
424
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
425
|
+
errors.push('Missing required frontmatter field "description".');
|
|
426
|
+
}
|
|
427
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
428
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
429
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
430
|
+
}
|
|
431
|
+
return errors;
|
|
432
|
+
}
|
|
377
433
|
function skillMatchesTarget(skill, targetId) {
|
|
378
434
|
const { targets } = skill.meta;
|
|
379
435
|
if (!targets || targets === "*")
|
|
@@ -11,6 +11,7 @@ var TARGET_IDS = [
|
|
|
11
11
|
"cursor",
|
|
12
12
|
"claudecode",
|
|
13
13
|
"codexcli",
|
|
14
|
+
"mistralvibe",
|
|
14
15
|
"geminicli",
|
|
15
16
|
"copilot",
|
|
16
17
|
"agentsmd",
|
|
@@ -243,8 +244,116 @@ function getHeader(type) {
|
|
|
243
244
|
}
|
|
244
245
|
}
|
|
245
246
|
|
|
247
|
+
// src/utils/frontmatter.ts
|
|
248
|
+
import matter from "gray-matter";
|
|
249
|
+
function parseFrontmatter(source) {
|
|
250
|
+
const { data, content } = matter(source);
|
|
251
|
+
return {
|
|
252
|
+
data,
|
|
253
|
+
content: content.trim(),
|
|
254
|
+
raw: source
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function serializeFrontmatter(data, content) {
|
|
258
|
+
const filtered = Object.fromEntries(Object.entries(data).filter(([, v]) => v !== undefined));
|
|
259
|
+
if (Object.keys(filtered).length === 0) {
|
|
260
|
+
return content;
|
|
261
|
+
}
|
|
262
|
+
return matter.stringify(content, filtered);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/features/skills.ts
|
|
266
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
267
|
+
import { basename, join as join2 } from "path";
|
|
268
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
269
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
270
|
+
function parseSkills(skillsDir, packName) {
|
|
271
|
+
const dirs = listDirs(skillsDir);
|
|
272
|
+
const skills = [];
|
|
273
|
+
for (const dir of dirs) {
|
|
274
|
+
const skillMd = join2(dir, "SKILL.md");
|
|
275
|
+
if (existsSync3(skillMd)) {
|
|
276
|
+
skills.push(parseSkillFile(skillMd, dir, packName));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return skills;
|
|
280
|
+
}
|
|
281
|
+
function parseSkillFile(filepath, skillDir, packName) {
|
|
282
|
+
const raw = readFileSync3(filepath, "utf-8");
|
|
283
|
+
const { data, content } = parseFrontmatter(raw);
|
|
284
|
+
return {
|
|
285
|
+
name: data.name ?? basename(skillDir),
|
|
286
|
+
sourcePath: filepath,
|
|
287
|
+
sourceDir: skillDir,
|
|
288
|
+
packName,
|
|
289
|
+
meta: data,
|
|
290
|
+
content
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function buildSkillFrontmatter(skill) {
|
|
294
|
+
return {
|
|
295
|
+
...skill.meta,
|
|
296
|
+
name: skill.name
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function serializeSkill(skill) {
|
|
300
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
301
|
+
}
|
|
302
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
303
|
+
const { data, content } = parseFrontmatter(source);
|
|
304
|
+
const normalized = {
|
|
305
|
+
...data,
|
|
306
|
+
name: skillName
|
|
307
|
+
};
|
|
308
|
+
let addedDescription = false;
|
|
309
|
+
const description = normalized.description;
|
|
310
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
311
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
312
|
+
addedDescription = true;
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
content: serializeFrontmatter(normalized, content),
|
|
316
|
+
addedDescription
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
320
|
+
const errors = [];
|
|
321
|
+
const dirName = basename(skill.sourceDir);
|
|
322
|
+
const declaredName = skill.meta.name;
|
|
323
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
324
|
+
errors.push('Missing required frontmatter field "name".');
|
|
325
|
+
} else {
|
|
326
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
327
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
328
|
+
}
|
|
329
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
330
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
331
|
+
}
|
|
332
|
+
if (declaredName !== dirName) {
|
|
333
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const description = skill.meta.description;
|
|
337
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
338
|
+
errors.push('Missing required frontmatter field "description".');
|
|
339
|
+
}
|
|
340
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
341
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
342
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
343
|
+
}
|
|
344
|
+
return errors;
|
|
345
|
+
}
|
|
346
|
+
function skillMatchesTarget(skill, targetId) {
|
|
347
|
+
const { targets } = skill.meta;
|
|
348
|
+
if (!targets || targets === "*")
|
|
349
|
+
return true;
|
|
350
|
+
if (Array.isArray(targets) && targets.includes("*"))
|
|
351
|
+
return true;
|
|
352
|
+
return Array.isArray(targets) && targets.includes(targetId);
|
|
353
|
+
}
|
|
354
|
+
|
|
246
355
|
// src/features/models.ts
|
|
247
|
-
import { join as
|
|
356
|
+
import { join as join3 } from "path";
|
|
248
357
|
import { z as z2 } from "zod";
|
|
249
358
|
var SECRET_PATTERNS = [
|
|
250
359
|
/["']api[_-]?key["']\s*:/i,
|
|
@@ -304,7 +413,7 @@ var ModelsSchema = z2.object({
|
|
|
304
413
|
})).optional()
|
|
305
414
|
});
|
|
306
415
|
function parseModels(packDir, packName) {
|
|
307
|
-
const modelsPath =
|
|
416
|
+
const modelsPath = join3(packDir, "models.json");
|
|
308
417
|
const raw = readJsonOrNull(modelsPath);
|
|
309
418
|
if (!raw)
|
|
310
419
|
return null;
|
|
@@ -413,8 +522,8 @@ function scanModelsForSecrets(config) {
|
|
|
413
522
|
}
|
|
414
523
|
|
|
415
524
|
// src/cli/pack/validate.ts
|
|
416
|
-
import { existsSync as
|
|
417
|
-
import { resolve as resolve2, join as
|
|
525
|
+
import { existsSync as existsSync4 } from "fs";
|
|
526
|
+
import { resolve as resolve2, join as join4, basename as basename2 } from "path";
|
|
418
527
|
import chalk from "chalk";
|
|
419
528
|
function runPackValidate(projectRoot) {
|
|
420
529
|
const config = loadWorkspaceConfig(projectRoot);
|
|
@@ -423,13 +532,13 @@ function runPackValidate(projectRoot) {
|
|
|
423
532
|
const packDir = resolvePackDir(projectRoot, packRef);
|
|
424
533
|
console.log(chalk.bold(`
|
|
425
534
|
Validating pack: ${packRef}`));
|
|
426
|
-
if (!packDir || !
|
|
535
|
+
if (!packDir || !existsSync4(packDir)) {
|
|
427
536
|
console.log(chalk.red(` ERROR: Pack directory not found: ${packRef}`));
|
|
428
537
|
hasErrors = true;
|
|
429
538
|
continue;
|
|
430
539
|
}
|
|
431
540
|
const packJsonPath = resolve2(packDir, "pack.json");
|
|
432
|
-
if (!
|
|
541
|
+
if (!existsSync4(packJsonPath)) {
|
|
433
542
|
console.log(chalk.yellow(" warn: No pack.json found. Name will be inferred from directory."));
|
|
434
543
|
} else {
|
|
435
544
|
try {
|
|
@@ -443,14 +552,30 @@ Validating pack: ${packRef}`));
|
|
|
443
552
|
}
|
|
444
553
|
const subdirs = ["rules", "commands", "agents", "skills"];
|
|
445
554
|
for (const sub of subdirs) {
|
|
446
|
-
const subDir =
|
|
447
|
-
if (
|
|
555
|
+
const subDir = join4(packDir, sub);
|
|
556
|
+
if (existsSync4(subDir)) {
|
|
448
557
|
if (sub === "skills") {
|
|
449
558
|
const skillDirs = listDirs(subDir);
|
|
450
559
|
for (const skillDir of skillDirs) {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
560
|
+
const skillName = basename2(skillDir);
|
|
561
|
+
const skillMd = join4(skillDir, "SKILL.md");
|
|
562
|
+
if (!existsSync4(skillMd)) {
|
|
563
|
+
console.log(chalk.yellow(` warn: skills/${skillName} missing SKILL.md`));
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
const parsed = parseSkillFile(skillMd, skillDir, "__validation__");
|
|
568
|
+
const skillErrors = validateAgentSkillsFrontmatter(parsed);
|
|
569
|
+
for (const err of skillErrors) {
|
|
570
|
+
console.log(chalk.red(` ERROR skills/${skillName}/SKILL.md: ${err}`));
|
|
571
|
+
}
|
|
572
|
+
if (skillErrors.length > 0) {
|
|
573
|
+
hasErrors = true;
|
|
574
|
+
}
|
|
575
|
+
} catch (err) {
|
|
576
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
577
|
+
console.log(chalk.red(` ERROR skills/${skillName}/SKILL.md: failed to parse (${message})`));
|
|
578
|
+
hasErrors = true;
|
|
454
579
|
}
|
|
455
580
|
}
|
|
456
581
|
console.log(chalk.green(` ${sub}/: ${skillDirs.length} skill(s)`));
|
|
@@ -462,24 +587,24 @@ Validating pack: ${packRef}`));
|
|
|
462
587
|
}
|
|
463
588
|
const optionalFiles = ["mcp.json", "ignore", ".aiignore"];
|
|
464
589
|
for (const file of optionalFiles) {
|
|
465
|
-
if (
|
|
590
|
+
if (existsSync4(join4(packDir, file))) {
|
|
466
591
|
console.log(chalk.green(` ${file}: present`));
|
|
467
592
|
}
|
|
468
593
|
}
|
|
469
|
-
const hooksJson =
|
|
470
|
-
if (
|
|
594
|
+
const hooksJson = join4(packDir, "hooks", "hooks.json");
|
|
595
|
+
if (existsSync4(hooksJson)) {
|
|
471
596
|
console.log(chalk.green(" hooks/hooks.json: present"));
|
|
472
597
|
}
|
|
473
|
-
const pluginsDir =
|
|
474
|
-
if (
|
|
598
|
+
const pluginsDir = join4(packDir, "plugins");
|
|
599
|
+
if (existsSync4(pluginsDir)) {
|
|
475
600
|
const pluginFiles = [
|
|
476
601
|
...listFiles(pluginsDir, { extension: ".ts" }),
|
|
477
602
|
...listFiles(pluginsDir, { extension: ".js" })
|
|
478
603
|
];
|
|
479
604
|
console.log(chalk.green(` plugins/: ${pluginFiles.length} file(s)`));
|
|
480
605
|
}
|
|
481
|
-
const modelsJsonPath =
|
|
482
|
-
if (
|
|
606
|
+
const modelsJsonPath = join4(packDir, "models.json");
|
|
607
|
+
if (existsSync4(modelsJsonPath)) {
|
|
483
608
|
try {
|
|
484
609
|
const parsed = parseModels(packDir, "validate");
|
|
485
610
|
if (parsed) {
|
package/dist/node/cli/publish.js
CHANGED
package/dist/node/core/config.js
CHANGED
package/dist/node/core/index.js
CHANGED
|
@@ -11,6 +11,7 @@ var TARGET_IDS = [
|
|
|
11
11
|
"cursor",
|
|
12
12
|
"claudecode",
|
|
13
13
|
"codexcli",
|
|
14
|
+
"mistralvibe",
|
|
14
15
|
"geminicli",
|
|
15
16
|
"copilot",
|
|
16
17
|
"agentsmd",
|
|
@@ -351,6 +352,8 @@ function agentMatchesTarget(agent, targetId) {
|
|
|
351
352
|
// src/features/skills.ts
|
|
352
353
|
import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
|
|
353
354
|
import { basename as basename4, join as join2 } from "path";
|
|
355
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
356
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
354
357
|
function parseSkills(skillsDir, packName) {
|
|
355
358
|
const dirs = listDirs(skillsDir);
|
|
356
359
|
const skills = [];
|
|
@@ -374,6 +377,59 @@ function parseSkillFile(filepath, skillDir, packName) {
|
|
|
374
377
|
content
|
|
375
378
|
};
|
|
376
379
|
}
|
|
380
|
+
function buildSkillFrontmatter(skill) {
|
|
381
|
+
return {
|
|
382
|
+
...skill.meta,
|
|
383
|
+
name: skill.name
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function serializeSkill(skill) {
|
|
387
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
388
|
+
}
|
|
389
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
390
|
+
const { data, content } = parseFrontmatter(source);
|
|
391
|
+
const normalized = {
|
|
392
|
+
...data,
|
|
393
|
+
name: skillName
|
|
394
|
+
};
|
|
395
|
+
let addedDescription = false;
|
|
396
|
+
const description = normalized.description;
|
|
397
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
398
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
399
|
+
addedDescription = true;
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
content: serializeFrontmatter(normalized, content),
|
|
403
|
+
addedDescription
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
407
|
+
const errors = [];
|
|
408
|
+
const dirName = basename4(skill.sourceDir);
|
|
409
|
+
const declaredName = skill.meta.name;
|
|
410
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
411
|
+
errors.push('Missing required frontmatter field "name".');
|
|
412
|
+
} else {
|
|
413
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
414
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
415
|
+
}
|
|
416
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
417
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
418
|
+
}
|
|
419
|
+
if (declaredName !== dirName) {
|
|
420
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const description = skill.meta.description;
|
|
424
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
425
|
+
errors.push('Missing required frontmatter field "description".');
|
|
426
|
+
}
|
|
427
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
428
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
429
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
430
|
+
}
|
|
431
|
+
return errors;
|
|
432
|
+
}
|
|
377
433
|
function skillMatchesTarget(skill, targetId) {
|
|
378
434
|
const { targets } = skill.meta;
|
|
379
435
|
if (!targets || targets === "*")
|
|
@@ -11,6 +11,7 @@ var TARGET_IDS = [
|
|
|
11
11
|
"cursor",
|
|
12
12
|
"claudecode",
|
|
13
13
|
"codexcli",
|
|
14
|
+
"mistralvibe",
|
|
14
15
|
"geminicli",
|
|
15
16
|
"copilot",
|
|
16
17
|
"agentsmd",
|
|
@@ -351,6 +352,8 @@ function agentMatchesTarget(agent, targetId) {
|
|
|
351
352
|
// src/features/skills.ts
|
|
352
353
|
import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
|
|
353
354
|
import { basename as basename4, join as join2 } from "path";
|
|
355
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
356
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
354
357
|
function parseSkills(skillsDir, packName) {
|
|
355
358
|
const dirs = listDirs(skillsDir);
|
|
356
359
|
const skills = [];
|
|
@@ -374,6 +377,59 @@ function parseSkillFile(filepath, skillDir, packName) {
|
|
|
374
377
|
content
|
|
375
378
|
};
|
|
376
379
|
}
|
|
380
|
+
function buildSkillFrontmatter(skill) {
|
|
381
|
+
return {
|
|
382
|
+
...skill.meta,
|
|
383
|
+
name: skill.name
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function serializeSkill(skill) {
|
|
387
|
+
return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
|
|
388
|
+
}
|
|
389
|
+
function normalizeImportedSkillMarkdown(source, skillName) {
|
|
390
|
+
const { data, content } = parseFrontmatter(source);
|
|
391
|
+
const normalized = {
|
|
392
|
+
...data,
|
|
393
|
+
name: skillName
|
|
394
|
+
};
|
|
395
|
+
let addedDescription = false;
|
|
396
|
+
const description = normalized.description;
|
|
397
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
398
|
+
normalized.description = `Imported skill: ${skillName}`;
|
|
399
|
+
addedDescription = true;
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
content: serializeFrontmatter(normalized, content),
|
|
403
|
+
addedDescription
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function validateAgentSkillsFrontmatter(skill) {
|
|
407
|
+
const errors = [];
|
|
408
|
+
const dirName = basename4(skill.sourceDir);
|
|
409
|
+
const declaredName = skill.meta.name;
|
|
410
|
+
if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
|
|
411
|
+
errors.push('Missing required frontmatter field "name".');
|
|
412
|
+
} else {
|
|
413
|
+
if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
|
|
414
|
+
errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
|
|
415
|
+
}
|
|
416
|
+
if (!SKILL_NAME_PATTERN.test(declaredName)) {
|
|
417
|
+
errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
|
|
418
|
+
}
|
|
419
|
+
if (declaredName !== dirName) {
|
|
420
|
+
errors.push(`Invalid "name": must match containing directory "${dirName}".`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const description = skill.meta.description;
|
|
424
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
425
|
+
errors.push('Missing required frontmatter field "description".');
|
|
426
|
+
}
|
|
427
|
+
const allowedTools = skill.meta["allowed-tools"];
|
|
428
|
+
if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
|
|
429
|
+
errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
|
|
430
|
+
}
|
|
431
|
+
return errors;
|
|
432
|
+
}
|
|
377
433
|
function skillMatchesTarget(skill, targetId) {
|
|
378
434
|
const { targets } = skill.meta;
|
|
379
435
|
if (!targets || targets === "*")
|