skills-reference 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 ADDED
@@ -0,0 +1,78 @@
1
+ # skills-reference
2
+
3
+ TypeScript port of the Agent Skills reference tooling. It mirrors the Python `skills-ref`.
4
+
5
+ ## Installation
6
+
7
+ ### Prerequisites
8
+
9
+ - Node.js (v14.0.0 or higher)
10
+ - npm (v6.0.0 or higher)
11
+
12
+ ### From npm
13
+
14
+ ```bash
15
+ # Install the package
16
+ npm install skills-reference
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### CLI
22
+
23
+ ```bash
24
+ # Validate a skill
25
+ skills-reference validate path/to/skill
26
+
27
+ # Read skill properties (outputs JSON)
28
+ skills-reference read-properties path/to/skill
29
+
30
+ # Generate <available_skills> XML for agent prompts
31
+ skills-reference to-prompt path/to/skill-a path/to/skill-b
32
+ ```
33
+
34
+ ### Node.js API
35
+
36
+ #### CommonJS Syntax
37
+
38
+ ```javascript
39
+ const { validate, readProperties, toPrompt } = require("skills-reference");
40
+
41
+ // Validate a skill directory
42
+ const errors = validate("path/to/skill");
43
+ if (errors.length > 0) {
44
+ console.log("Validation errors:", errors);
45
+ }
46
+
47
+ // Read skill properties
48
+ const props = readProperties("path/to/skill");
49
+ console.log(`Skill: ${props.name} - ${props.description}`);
50
+
51
+ // Generate prompt for available skills
52
+ const prompt = toPrompt(["path/to/skill-a", "path/to/skill-b"]);
53
+ console.log(prompt);
54
+ ```
55
+
56
+ #### ESM Syntax
57
+
58
+ ```javascript
59
+ import { validate, readProperties, toPrompt } from "skills-reference";
60
+
61
+ // Validate a skill directory
62
+ const errors = validate("path/to/skill");
63
+ if (errors.length > 0) {
64
+ console.log("Validation errors:", errors);
65
+ }
66
+
67
+ // Read skill properties
68
+ const props = readProperties("path/to/skill");
69
+ console.log(`Skill: ${props.name} - ${props.description}`);
70
+
71
+ // Generate prompt for available skills
72
+ const prompt = toPrompt(["path/to/skill-a", "path/to/skill-b"]);
73
+ console.log(prompt);
74
+ ```
75
+
76
+ ## License
77
+
78
+ ISC
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import * as path from "path";
3
+ import * as fs from "fs";
4
+ import { Command } from "commander";
5
+ import { readProperties } from "./parser.js";
6
+ import { toPrompt } from "./prompt.js";
7
+ import { validate } from "./validator.js";
8
+ const program = new Command();
9
+ function isSkillMdFile(filePath) {
10
+ const stats = fs.statSync(filePath);
11
+ return (stats.isFile() && path.basename(filePath).toLowerCase() === "skill.md");
12
+ }
13
+ program
14
+ .name("skills-reference")
15
+ .description("Reference library for Agent Skills")
16
+ .version("1.0.0");
17
+ program
18
+ .command("validate")
19
+ .description("Validate a skill directory")
20
+ .argument("<skillPath>", "Path to skill directory or SKILL.md file")
21
+ .action((skillPath) => {
22
+ try {
23
+ let resolvedPath = path.resolve(skillPath);
24
+ if (isSkillMdFile(resolvedPath)) {
25
+ resolvedPath = path.dirname(resolvedPath);
26
+ }
27
+ const errors = validate(resolvedPath);
28
+ if (errors.length > 0) {
29
+ console.error(`Validation failed for ${resolvedPath}:`);
30
+ errors.forEach((error) => {
31
+ console.error(` - ${error}`);
32
+ });
33
+ process.exit(1);
34
+ }
35
+ else {
36
+ console.log(`Valid skill: ${resolvedPath}`);
37
+ }
38
+ }
39
+ catch (error) {
40
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
41
+ process.exit(1);
42
+ }
43
+ });
44
+ program
45
+ .command("read-properties")
46
+ .description("Read and print skill properties as JSON")
47
+ .argument("<skillPath>", "Path to skill directory or SKILL.md file")
48
+ .action((skillPath) => {
49
+ try {
50
+ let resolvedPath = path.resolve(skillPath);
51
+ if (isSkillMdFile(resolvedPath)) {
52
+ resolvedPath = path.dirname(resolvedPath);
53
+ }
54
+ const props = readProperties(resolvedPath);
55
+ console.log(JSON.stringify(props.toDict(), null, 2));
56
+ }
57
+ catch (error) {
58
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
59
+ process.exit(1);
60
+ }
61
+ });
62
+ program
63
+ .command("to-prompt")
64
+ .description("Generate <available_skills> XML for agent prompts")
65
+ .argument("<skillPaths...>", "Paths to skill directories or SKILL.md files")
66
+ .action((skillPaths) => {
67
+ try {
68
+ const resolvedPaths = skillPaths.map((skillPath) => {
69
+ const resolvedPath = path.resolve(skillPath);
70
+ if (isSkillMdFile(resolvedPath)) {
71
+ return path.dirname(resolvedPath);
72
+ }
73
+ return resolvedPath;
74
+ });
75
+ const output = toPrompt(resolvedPaths);
76
+ console.log(output);
77
+ }
78
+ catch (error) {
79
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
80
+ process.exit(1);
81
+ }
82
+ });
83
+ program.parse(process.argv);
84
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare class SkillError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class ParseError extends SkillError {
5
+ constructor(message: string);
6
+ }
7
+ export declare class ValidationError extends SkillError {
8
+ errors: string[];
9
+ constructor(message: string, errors?: string[]);
10
+ }
11
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,UAAW,SAAQ,UAAU;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,eAAgB,SAAQ,UAAU;IACtC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE;CAK/C"}
package/dist/errors.js ADDED
@@ -0,0 +1,20 @@
1
+ export class SkillError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'SkillError';
5
+ }
6
+ }
7
+ export class ParseError extends SkillError {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'ParseError';
11
+ }
12
+ }
13
+ export class ValidationError extends SkillError {
14
+ constructor(message, errors) {
15
+ super(message);
16
+ this.name = 'ValidationError';
17
+ this.errors = errors || [message];
18
+ }
19
+ }
20
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,UAAW,SAAQ,UAAU;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,UAAU;IAG7C,YAAY,OAAe,EAAE,MAAiB;QAC5C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ export { SkillError, ParseError, ValidationError } from "./errors.js";
2
+ export { SkillProperties, SkillPropertiesImpl } from "./models.js";
3
+ export { findSkillMd, parseFrontmatter, readProperties } from "./parser.js";
4
+ export { toPrompt } from "./prompt.js";
5
+ export { validate, validateMetadata } from "./validator.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { SkillError, ParseError, ValidationError } from "./errors.js";
2
+ export { SkillPropertiesImpl } from "./models.js";
3
+ export { findSkillMd, parseFrontmatter, readProperties } from "./parser.js";
4
+ export { toPrompt } from "./prompt.js";
5
+ export { validate, validateMetadata } from "./validator.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAmB,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,20 @@
1
+ export interface SkillProperties {
2
+ name: string;
3
+ description: string;
4
+ license?: string;
5
+ compatibility?: string;
6
+ allowedTools?: string;
7
+ metadata: Record<string, string>;
8
+ toDict(): Record<string, any>;
9
+ }
10
+ export declare class SkillPropertiesImpl implements SkillProperties {
11
+ name: string;
12
+ description: string;
13
+ license?: string | undefined;
14
+ compatibility?: string | undefined;
15
+ allowedTools?: string | undefined;
16
+ metadata: Record<string, string>;
17
+ constructor(name: string, description: string, license?: string | undefined, compatibility?: string | undefined, allowedTools?: string | undefined, metadata?: Record<string, string>);
18
+ toDict(): Record<string, any>;
19
+ }
20
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,qBAAa,mBAAoB,YAAW,eAAe;IAEhD,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,MAAM;IACnB,OAAO,CAAC,EAAE,MAAM;IAChB,aAAa,CAAC,EAAE,MAAM;IACtB,YAAY,CAAC,EAAE,MAAM;IACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;gBALhC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,YAAA,EAChB,aAAa,CAAC,EAAE,MAAM,YAAA,EACtB,YAAY,CAAC,EAAE,MAAM,YAAA,EACrB,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAG9C,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAwB9B"}
package/dist/models.js ADDED
@@ -0,0 +1,30 @@
1
+ export class SkillPropertiesImpl {
2
+ constructor(name, description, license, compatibility, allowedTools, metadata = {}) {
3
+ this.name = name;
4
+ this.description = description;
5
+ this.license = license;
6
+ this.compatibility = compatibility;
7
+ this.allowedTools = allowedTools;
8
+ this.metadata = metadata;
9
+ }
10
+ toDict() {
11
+ const result = {
12
+ name: this.name,
13
+ description: this.description
14
+ };
15
+ if (this.license) {
16
+ result.license = this.license;
17
+ }
18
+ if (this.compatibility) {
19
+ result.compatibility = this.compatibility;
20
+ }
21
+ if (this.allowedTools) {
22
+ result['allowed-tools'] = this.allowedTools;
23
+ }
24
+ if (Object.keys(this.metadata).length > 0) {
25
+ result.metadata = this.metadata;
26
+ }
27
+ return result;
28
+ }
29
+ }
30
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,mBAAmB;IAC9B,YACS,IAAY,EACZ,WAAmB,EACnB,OAAgB,EAChB,aAAsB,EACtB,YAAqB,EACrB,WAAmC,EAAE;QALrC,SAAI,GAAJ,IAAI,CAAQ;QACZ,gBAAW,GAAX,WAAW,CAAQ;QACnB,YAAO,GAAP,OAAO,CAAS;QAChB,kBAAa,GAAb,aAAa,CAAS;QACtB,iBAAY,GAAZ,YAAY,CAAS;QACrB,aAAQ,GAAR,QAAQ,CAA6B;IAC3C,CAAC;IAEJ,MAAM;QACJ,MAAM,MAAM,GAAwB;YAClC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC;QAEF,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAChC,CAAC;QAED,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;QAC5C,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC;QAC9C,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAClC,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ import { SkillProperties } from "./models.js";
2
+ export declare function findSkillMd(skillDir: string): string | null;
3
+ export declare function parseFrontmatter(content: string): {
4
+ metadata: Record<string, any>;
5
+ body: string;
6
+ };
7
+ export declare function readProperties(skillDir: string): SkillProperties;
8
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAuB,MAAM,aAAa,CAAC;AAEnE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW3D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAC/C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CAChB,CA0CA;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CA8ChE"}
package/dist/parser.js ADDED
@@ -0,0 +1,71 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as yaml from "js-yaml";
4
+ import { ParseError, ValidationError } from "./errors.js";
5
+ import { SkillPropertiesImpl } from "./models.js";
6
+ export function findSkillMd(skillDir) {
7
+ const skillPath = path.resolve(skillDir);
8
+ for (const name of ["SKILL.md", "skill.md"]) {
9
+ const filePath = path.join(skillPath, name);
10
+ if (fs.existsSync(filePath)) {
11
+ return filePath;
12
+ }
13
+ }
14
+ return null;
15
+ }
16
+ export function parseFrontmatter(content) {
17
+ if (!content.startsWith("---")) {
18
+ throw new ParseError("SKILL.md must start with YAML frontmatter (---)");
19
+ }
20
+ const parts = content.split("---", 3);
21
+ if (parts.length < 3) {
22
+ throw new ParseError("SKILL.md frontmatter not properly closed with ---");
23
+ }
24
+ const frontmatterStr = parts[1];
25
+ const body = parts[2].trim();
26
+ try {
27
+ const metadata = yaml.load(frontmatterStr);
28
+ if (typeof metadata !== "object" || metadata === null) {
29
+ throw new ParseError("SKILL.md frontmatter must be a YAML mapping");
30
+ }
31
+ if (metadata.metadata &&
32
+ typeof metadata.metadata === "object" &&
33
+ metadata.metadata !== null) {
34
+ metadata.metadata = Object.entries(metadata.metadata).reduce((acc, [key, value]) => {
35
+ acc[key] = String(value);
36
+ return acc;
37
+ }, {});
38
+ }
39
+ return { metadata, body };
40
+ }
41
+ catch (error) {
42
+ throw new ParseError(`Invalid YAML in frontmatter: ${error instanceof Error ? error.message : String(error)}`);
43
+ }
44
+ }
45
+ export function readProperties(skillDir) {
46
+ const skillPath = path.resolve(skillDir);
47
+ const skillMdPath = findSkillMd(skillPath);
48
+ if (!skillMdPath) {
49
+ throw new ParseError(`SKILL.md not found in ${skillPath}`);
50
+ }
51
+ const content = fs.readFileSync(skillMdPath, "utf8");
52
+ const { metadata } = parseFrontmatter(content);
53
+ if (!metadata.name) {
54
+ throw new ValidationError("Missing required field in frontmatter: name");
55
+ }
56
+ if (!metadata.description) {
57
+ throw new ValidationError("Missing required field in frontmatter: description");
58
+ }
59
+ const name = String(metadata.name).trim();
60
+ const description = String(metadata.description).trim();
61
+ if (!name) {
62
+ throw new ValidationError("Field 'name' must be a non-empty string");
63
+ }
64
+ if (!description) {
65
+ throw new ValidationError("Field 'description' must be a non-empty string");
66
+ }
67
+ return new SkillPropertiesImpl(name, description, metadata.license ? String(metadata.license) : undefined, metadata.compatibility ? String(metadata.compatibility) : undefined, metadata["allowed-tools"]
68
+ ? String(metadata["allowed-tools"])
69
+ : undefined, metadata.metadata || {});
70
+ }
71
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function toPrompt(skillDirs: string[]): string;
2
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAGA,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAgCpD"}
package/dist/prompt.js ADDED
@@ -0,0 +1,37 @@
1
+ import * as path from "path";
2
+ import { findSkillMd, readProperties } from "./parser.js";
3
+ export function toPrompt(skillDirs) {
4
+ if (!skillDirs || skillDirs.length === 0) {
5
+ return "<available_skills>\n</available_skills>";
6
+ }
7
+ const lines = ["<available_skills>"];
8
+ for (const skillDir of skillDirs) {
9
+ const resolvedDir = path.resolve(skillDir);
10
+ const props = readProperties(resolvedDir);
11
+ lines.push("<skill>");
12
+ lines.push("<name>");
13
+ lines.push(escapeHtml(props.name));
14
+ lines.push("</name>");
15
+ lines.push("<description>");
16
+ lines.push(escapeHtml(props.description));
17
+ lines.push("</description>");
18
+ const skillMdPath = findSkillMd(resolvedDir);
19
+ if (skillMdPath) {
20
+ lines.push("<location>");
21
+ lines.push(skillMdPath);
22
+ lines.push("</location>");
23
+ }
24
+ lines.push("</skill>");
25
+ }
26
+ lines.push("</available_skills>");
27
+ return lines.join("\n");
28
+ }
29
+ function escapeHtml(text) {
30
+ return text
31
+ .replace(/&/g, "&amp;")
32
+ .replace(/</g, "&lt;")
33
+ .replace(/>/g, "&gt;")
34
+ .replace(/"/g, "&quot;")
35
+ .replace(/'/g, "&#039;");
36
+ }
37
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,UAAU,QAAQ,CAAC,SAAmB;IACxC,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,yCAAyC,CAAC;IACrD,CAAC;IAED,MAAM,KAAK,GAAa,CAAC,oBAAoB,CAAC,CAAC;IAE/C,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;QAE1C,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAE7B,MAAM,WAAW,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;QAC7C,IAAI,WAAW,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC9B,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAElC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC5B,OAAO,IAAI;SACN,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function validateMetadata(metadata: Record<string, any>, skillDir?: string): string[];
2
+ export declare function validate(skillDir: string): string[];
3
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AA8HA,wBAAgB,gBAAgB,CAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,EAAE,CAqBV;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAuBnD"}
@@ -0,0 +1,126 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { findSkillMd, parseFrontmatter } from "./parser.js";
4
+ const MAX_SKILL_NAME_LENGTH = 64;
5
+ const MAX_DESCRIPTION_LENGTH = 1024;
6
+ const MAX_COMPATIBILITY_LENGTH = 500;
7
+ const ALLOWED_FIELDS = new Set([
8
+ "name",
9
+ "description",
10
+ "license",
11
+ "allowed-tools",
12
+ "metadata",
13
+ "compatibility",
14
+ ]);
15
+ function normalizeString(str) {
16
+ return str.normalize("NFKC");
17
+ }
18
+ function validateName(name, skillDir) {
19
+ const errors = [];
20
+ if (!name || typeof name !== "string" || !name.trim()) {
21
+ errors.push("Field 'name' must be a non-empty string");
22
+ return errors;
23
+ }
24
+ const normalizedName = normalizeString(name.trim());
25
+ if (normalizedName.length > MAX_SKILL_NAME_LENGTH) {
26
+ errors.push(`Skill name '${normalizedName}' exceeds ${MAX_SKILL_NAME_LENGTH} character limit ` +
27
+ `(${normalizedName.length} chars)`);
28
+ }
29
+ if (normalizedName !== normalizedName.toLowerCase()) {
30
+ errors.push(`Skill name '${normalizedName}' must be lowercase`);
31
+ }
32
+ if (normalizedName.startsWith("-") || normalizedName.endsWith("-")) {
33
+ errors.push("Skill name cannot start or end with a hyphen");
34
+ }
35
+ if (normalizedName.includes("--")) {
36
+ errors.push("Skill name cannot contain consecutive hyphens");
37
+ }
38
+ if (!/^[a-z0-9-]+$/.test(normalizedName)) {
39
+ errors.push(`Skill name '${normalizedName}' contains invalid characters. ` +
40
+ "Only letters, digits, and hyphens are allowed.");
41
+ }
42
+ if (skillDir) {
43
+ const dirName = normalizeString(path.basename(skillDir));
44
+ if (dirName !== normalizedName) {
45
+ errors.push(`Directory name '${path.basename(skillDir)}' must match skill name '${normalizedName}'`);
46
+ }
47
+ }
48
+ return errors;
49
+ }
50
+ function validateDescription(description) {
51
+ const errors = [];
52
+ if (!description ||
53
+ typeof description !== "string" ||
54
+ !description.trim()) {
55
+ errors.push("Field 'description' must be a non-empty string");
56
+ return errors;
57
+ }
58
+ if (description.length > MAX_DESCRIPTION_LENGTH) {
59
+ errors.push(`Description exceeds ${MAX_DESCRIPTION_LENGTH} character limit ` +
60
+ `(${description.length} chars)`);
61
+ }
62
+ return errors;
63
+ }
64
+ function validateCompatibility(compatibility) {
65
+ const errors = [];
66
+ if (typeof compatibility !== "string") {
67
+ errors.push("Field 'compatibility' must be a string");
68
+ return errors;
69
+ }
70
+ if (compatibility.length > MAX_COMPATIBILITY_LENGTH) {
71
+ errors.push(`Compatibility exceeds ${MAX_COMPATIBILITY_LENGTH} character limit ` +
72
+ `(${compatibility.length} chars)`);
73
+ }
74
+ return errors;
75
+ }
76
+ function validateMetadataFields(metadata) {
77
+ const errors = [];
78
+ const extraFields = Object.keys(metadata).filter((field) => !ALLOWED_FIELDS.has(field));
79
+ if (extraFields.length > 0) {
80
+ errors.push(`Unexpected fields in frontmatter: ${extraFields.sort().join(", ")}. ` +
81
+ `Only ${Array.from(ALLOWED_FIELDS).sort().join(", ")} are allowed.`);
82
+ }
83
+ return errors;
84
+ }
85
+ export function validateMetadata(metadata, skillDir) {
86
+ const errors = [];
87
+ errors.push(...validateMetadataFields(metadata));
88
+ if (!metadata.name) {
89
+ errors.push("Missing required field in frontmatter: name");
90
+ }
91
+ else {
92
+ errors.push(...validateName(metadata.name, skillDir));
93
+ }
94
+ if (!metadata.description) {
95
+ errors.push("Missing required field in frontmatter: description");
96
+ }
97
+ else {
98
+ errors.push(...validateDescription(metadata.description));
99
+ }
100
+ if (metadata.compatibility) {
101
+ errors.push(...validateCompatibility(metadata.compatibility));
102
+ }
103
+ return errors;
104
+ }
105
+ export function validate(skillDir) {
106
+ const skillPath = path.resolve(skillDir);
107
+ if (!fs.existsSync(skillPath)) {
108
+ return [`Path does not exist: ${skillPath}`];
109
+ }
110
+ if (!fs.statSync(skillPath).isDirectory()) {
111
+ return [`Not a directory: ${skillPath}`];
112
+ }
113
+ const skillMdPath = findSkillMd(skillPath);
114
+ if (!skillMdPath) {
115
+ return ["Missing required file: SKILL.md"];
116
+ }
117
+ try {
118
+ const content = fs.readFileSync(skillMdPath, "utf8");
119
+ const { metadata } = parseFrontmatter(content);
120
+ return validateMetadata(metadata, skillPath);
121
+ }
122
+ catch (error) {
123
+ return [error instanceof Error ? error.message : String(error)];
124
+ }
125
+ }
126
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "skills-reference",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "bin": {
16
+ "skills-reference": "dist/cli.js"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "test": "jest",
21
+ "start": "node dist/cli.js"
22
+ },
23
+ "keywords": [
24
+ "agent",
25
+ "skills",
26
+ "cli"
27
+ ],
28
+ "author": "carbide",
29
+ "license": "ISC",
30
+ "description": "Reference library for Agent Skills",
31
+ "dependencies": {
32
+ "commander": "^14.0.0",
33
+ "js-yaml": "^4.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/jest": "^30.0.0",
37
+ "@types/js-yaml": "^4.0.9",
38
+ "@types/node": "^25.2.3",
39
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
40
+ "@typescript-eslint/parser": "^8.55.0",
41
+ "eslint": "^9.39.2",
42
+ "jest": "^30.0.0",
43
+ "ts-node": "^10.9.2",
44
+ "typescript": "^5.6.2"
45
+ }
46
+ }
Binary file
package/src/cli.ts ADDED
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ import * as path from "path";
3
+ import * as fs from "fs";
4
+ import { Command } from "commander";
5
+ import { SkillError } from "./errors.js";
6
+ import { readProperties } from "./parser.js";
7
+ import { toPrompt } from "./prompt.js";
8
+ import { validate } from "./validator.js";
9
+
10
+ const program = new Command();
11
+
12
+ function isSkillMdFile(filePath: string): boolean {
13
+ const stats = fs.statSync(filePath);
14
+ return (
15
+ stats.isFile() && path.basename(filePath).toLowerCase() === "skill.md"
16
+ );
17
+ }
18
+
19
+ program
20
+ .name("skills-reference")
21
+ .description("Reference library for Agent Skills")
22
+ .version("1.0.0");
23
+
24
+ program
25
+ .command("validate")
26
+ .description("Validate a skill directory")
27
+ .argument("<skillPath>", "Path to skill directory or SKILL.md file")
28
+ .action((skillPath: string) => {
29
+ try {
30
+ let resolvedPath = path.resolve(skillPath);
31
+
32
+ if (isSkillMdFile(resolvedPath)) {
33
+ resolvedPath = path.dirname(resolvedPath);
34
+ }
35
+
36
+ const errors = validate(resolvedPath);
37
+
38
+ if (errors.length > 0) {
39
+ console.error(`Validation failed for ${resolvedPath}:`);
40
+ errors.forEach((error) => {
41
+ console.error(` - ${error}`);
42
+ });
43
+ process.exit(1);
44
+ } else {
45
+ console.log(`Valid skill: ${resolvedPath}`);
46
+ }
47
+ } catch (error) {
48
+ console.error(
49
+ `Error: ${error instanceof Error ? error.message : String(error)}`,
50
+ );
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ program
56
+ .command("read-properties")
57
+ .description("Read and print skill properties as JSON")
58
+ .argument("<skillPath>", "Path to skill directory or SKILL.md file")
59
+ .action((skillPath: string) => {
60
+ try {
61
+ let resolvedPath = path.resolve(skillPath);
62
+
63
+ if (isSkillMdFile(resolvedPath)) {
64
+ resolvedPath = path.dirname(resolvedPath);
65
+ }
66
+
67
+ const props = readProperties(resolvedPath);
68
+ console.log(JSON.stringify(props.toDict(), null, 2));
69
+ } catch (error) {
70
+ console.error(
71
+ `Error: ${error instanceof Error ? error.message : String(error)}`,
72
+ );
73
+ process.exit(1);
74
+ }
75
+ });
76
+
77
+ program
78
+ .command("to-prompt")
79
+ .description("Generate <available_skills> XML for agent prompts")
80
+ .argument("<skillPaths...>", "Paths to skill directories or SKILL.md files")
81
+ .action((skillPaths: string[]) => {
82
+ try {
83
+ const resolvedPaths = skillPaths.map((skillPath) => {
84
+ const resolvedPath = path.resolve(skillPath);
85
+ if (isSkillMdFile(resolvedPath)) {
86
+ return path.dirname(resolvedPath);
87
+ }
88
+ return resolvedPath;
89
+ });
90
+
91
+ const output = toPrompt(resolvedPaths);
92
+ console.log(output);
93
+ } catch (error) {
94
+ console.error(
95
+ `Error: ${error instanceof Error ? error.message : String(error)}`,
96
+ );
97
+ process.exit(1);
98
+ }
99
+ });
100
+
101
+ program.parse(process.argv);
package/src/errors.ts ADDED
@@ -0,0 +1,23 @@
1
+ export class SkillError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'SkillError';
5
+ }
6
+ }
7
+
8
+ export class ParseError extends SkillError {
9
+ constructor(message: string) {
10
+ super(message);
11
+ this.name = 'ParseError';
12
+ }
13
+ }
14
+
15
+ export class ValidationError extends SkillError {
16
+ public errors: string[];
17
+
18
+ constructor(message: string, errors?: string[]) {
19
+ super(message);
20
+ this.name = 'ValidationError';
21
+ this.errors = errors || [message];
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { SkillError, ParseError, ValidationError } from "./errors.js";
2
+ export { SkillProperties, SkillPropertiesImpl } from "./models.js";
3
+ export { findSkillMd, parseFrontmatter, readProperties } from "./parser.js";
4
+ export { toPrompt } from "./prompt.js";
5
+ export { validate, validateMetadata } from "./validator.js";
package/src/models.ts ADDED
@@ -0,0 +1,46 @@
1
+ export interface SkillProperties {
2
+ name: string;
3
+ description: string;
4
+ license?: string;
5
+ compatibility?: string;
6
+ allowedTools?: string;
7
+ metadata: Record<string, string>;
8
+
9
+ toDict(): Record<string, any>;
10
+ }
11
+
12
+ export class SkillPropertiesImpl implements SkillProperties {
13
+ constructor(
14
+ public name: string,
15
+ public description: string,
16
+ public license?: string,
17
+ public compatibility?: string,
18
+ public allowedTools?: string,
19
+ public metadata: Record<string, string> = {}
20
+ ) {}
21
+
22
+ toDict(): Record<string, any> {
23
+ const result: Record<string, any> = {
24
+ name: this.name,
25
+ description: this.description
26
+ };
27
+
28
+ if (this.license) {
29
+ result.license = this.license;
30
+ }
31
+
32
+ if (this.compatibility) {
33
+ result.compatibility = this.compatibility;
34
+ }
35
+
36
+ if (this.allowedTools) {
37
+ result['allowed-tools'] = this.allowedTools;
38
+ }
39
+
40
+ if (Object.keys(this.metadata).length > 0) {
41
+ result.metadata = this.metadata;
42
+ }
43
+
44
+ return result;
45
+ }
46
+ }
package/src/parser.ts ADDED
@@ -0,0 +1,113 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as yaml from "js-yaml";
4
+ import { ParseError, ValidationError } from "./errors.js";
5
+ import { SkillProperties, SkillPropertiesImpl } from "./models.js";
6
+
7
+ export function findSkillMd(skillDir: string): string | null {
8
+ const skillPath = path.resolve(skillDir);
9
+
10
+ for (const name of ["SKILL.md", "skill.md"]) {
11
+ const filePath = path.join(skillPath, name);
12
+ if (fs.existsSync(filePath)) {
13
+ return filePath;
14
+ }
15
+ }
16
+
17
+ return null;
18
+ }
19
+
20
+ export function parseFrontmatter(content: string): {
21
+ metadata: Record<string, any>;
22
+ body: string;
23
+ } {
24
+ if (!content.startsWith("---")) {
25
+ throw new ParseError("SKILL.md must start with YAML frontmatter (---)");
26
+ }
27
+
28
+ const parts = content.split("---", 3);
29
+ if (parts.length < 3) {
30
+ throw new ParseError(
31
+ "SKILL.md frontmatter not properly closed with ---",
32
+ );
33
+ }
34
+
35
+ const frontmatterStr = parts[1];
36
+ const body = parts[2].trim();
37
+
38
+ try {
39
+ const metadata = yaml.load(frontmatterStr) as Record<string, any>;
40
+
41
+ if (typeof metadata !== "object" || metadata === null) {
42
+ throw new ParseError("SKILL.md frontmatter must be a YAML mapping");
43
+ }
44
+
45
+ if (
46
+ metadata.metadata &&
47
+ typeof metadata.metadata === "object" &&
48
+ metadata.metadata !== null
49
+ ) {
50
+ metadata.metadata = Object.entries(metadata.metadata).reduce(
51
+ (acc, [key, value]) => {
52
+ acc[key] = String(value);
53
+ return acc;
54
+ },
55
+ {} as Record<string, string>,
56
+ );
57
+ }
58
+
59
+ return { metadata, body };
60
+ } catch (error) {
61
+ throw new ParseError(
62
+ `Invalid YAML in frontmatter: ${error instanceof Error ? error.message : String(error)}`,
63
+ );
64
+ }
65
+ }
66
+
67
+ export function readProperties(skillDir: string): SkillProperties {
68
+ const skillPath = path.resolve(skillDir);
69
+ const skillMdPath = findSkillMd(skillPath);
70
+
71
+ if (!skillMdPath) {
72
+ throw new ParseError(`SKILL.md not found in ${skillPath}`);
73
+ }
74
+
75
+ const content = fs.readFileSync(skillMdPath, "utf8");
76
+ const { metadata } = parseFrontmatter(content);
77
+
78
+ if (!metadata.name) {
79
+ throw new ValidationError(
80
+ "Missing required field in frontmatter: name",
81
+ );
82
+ }
83
+
84
+ if (!metadata.description) {
85
+ throw new ValidationError(
86
+ "Missing required field in frontmatter: description",
87
+ );
88
+ }
89
+
90
+ const name = String(metadata.name).trim();
91
+ const description = String(metadata.description).trim();
92
+
93
+ if (!name) {
94
+ throw new ValidationError("Field 'name' must be a non-empty string");
95
+ }
96
+
97
+ if (!description) {
98
+ throw new ValidationError(
99
+ "Field 'description' must be a non-empty string",
100
+ );
101
+ }
102
+
103
+ return new SkillPropertiesImpl(
104
+ name,
105
+ description,
106
+ metadata.license ? String(metadata.license) : undefined,
107
+ metadata.compatibility ? String(metadata.compatibility) : undefined,
108
+ metadata["allowed-tools"]
109
+ ? String(metadata["allowed-tools"])
110
+ : undefined,
111
+ metadata.metadata || {},
112
+ );
113
+ }
package/src/prompt.ts ADDED
@@ -0,0 +1,45 @@
1
+ import * as path from "path";
2
+ import { findSkillMd, readProperties } from "./parser.js";
3
+
4
+ export function toPrompt(skillDirs: string[]): string {
5
+ if (!skillDirs || skillDirs.length === 0) {
6
+ return "<available_skills>\n</available_skills>";
7
+ }
8
+
9
+ const lines: string[] = ["<available_skills>"];
10
+
11
+ for (const skillDir of skillDirs) {
12
+ const resolvedDir = path.resolve(skillDir);
13
+ const props = readProperties(resolvedDir);
14
+
15
+ lines.push("<skill>");
16
+ lines.push("<name>");
17
+ lines.push(escapeHtml(props.name));
18
+ lines.push("</name>");
19
+ lines.push("<description>");
20
+ lines.push(escapeHtml(props.description));
21
+ lines.push("</description>");
22
+
23
+ const skillMdPath = findSkillMd(resolvedDir);
24
+ if (skillMdPath) {
25
+ lines.push("<location>");
26
+ lines.push(skillMdPath);
27
+ lines.push("</location>");
28
+ }
29
+
30
+ lines.push("</skill>");
31
+ }
32
+
33
+ lines.push("</available_skills>");
34
+
35
+ return lines.join("\n");
36
+ }
37
+
38
+ function escapeHtml(text: string): string {
39
+ return text
40
+ .replace(/&/g, "&amp;")
41
+ .replace(/</g, "&lt;")
42
+ .replace(/>/g, "&gt;")
43
+ .replace(/"/g, "&quot;")
44
+ .replace(/'/g, "&#039;");
45
+ }
@@ -0,0 +1,176 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { ParseError } from "./errors.js";
4
+ import { findSkillMd, parseFrontmatter } from "./parser.js";
5
+
6
+ const MAX_SKILL_NAME_LENGTH = 64;
7
+ const MAX_DESCRIPTION_LENGTH = 1024;
8
+ const MAX_COMPATIBILITY_LENGTH = 500;
9
+
10
+ const ALLOWED_FIELDS = new Set([
11
+ "name",
12
+ "description",
13
+ "license",
14
+ "allowed-tools",
15
+ "metadata",
16
+ "compatibility",
17
+ ]);
18
+
19
+ function normalizeString(str: string): string {
20
+ return str.normalize("NFKC");
21
+ }
22
+
23
+ function validateName(name: string, skillDir?: string): string[] {
24
+ const errors: string[] = [];
25
+
26
+ if (!name || typeof name !== "string" || !name.trim()) {
27
+ errors.push("Field 'name' must be a non-empty string");
28
+ return errors;
29
+ }
30
+
31
+ const normalizedName = normalizeString(name.trim());
32
+
33
+ if (normalizedName.length > MAX_SKILL_NAME_LENGTH) {
34
+ errors.push(
35
+ `Skill name '${normalizedName}' exceeds ${MAX_SKILL_NAME_LENGTH} character limit ` +
36
+ `(${normalizedName.length} chars)`,
37
+ );
38
+ }
39
+
40
+ if (normalizedName !== normalizedName.toLowerCase()) {
41
+ errors.push(`Skill name '${normalizedName}' must be lowercase`);
42
+ }
43
+
44
+ if (normalizedName.startsWith("-") || normalizedName.endsWith("-")) {
45
+ errors.push("Skill name cannot start or end with a hyphen");
46
+ }
47
+
48
+ if (normalizedName.includes("--")) {
49
+ errors.push("Skill name cannot contain consecutive hyphens");
50
+ }
51
+
52
+ if (!/^[a-z0-9-]+$/.test(normalizedName)) {
53
+ errors.push(
54
+ `Skill name '${normalizedName}' contains invalid characters. ` +
55
+ "Only letters, digits, and hyphens are allowed.",
56
+ );
57
+ }
58
+
59
+ if (skillDir) {
60
+ const dirName = normalizeString(path.basename(skillDir));
61
+ if (dirName !== normalizedName) {
62
+ errors.push(
63
+ `Directory name '${path.basename(skillDir)}' must match skill name '${normalizedName}'`,
64
+ );
65
+ }
66
+ }
67
+
68
+ return errors;
69
+ }
70
+
71
+ function validateDescription(description: string): string[] {
72
+ const errors: string[] = [];
73
+
74
+ if (
75
+ !description ||
76
+ typeof description !== "string" ||
77
+ !description.trim()
78
+ ) {
79
+ errors.push("Field 'description' must be a non-empty string");
80
+ return errors;
81
+ }
82
+
83
+ if (description.length > MAX_DESCRIPTION_LENGTH) {
84
+ errors.push(
85
+ `Description exceeds ${MAX_DESCRIPTION_LENGTH} character limit ` +
86
+ `(${description.length} chars)`,
87
+ );
88
+ }
89
+
90
+ return errors;
91
+ }
92
+
93
+ function validateCompatibility(compatibility: string): string[] {
94
+ const errors: string[] = [];
95
+
96
+ if (typeof compatibility !== "string") {
97
+ errors.push("Field 'compatibility' must be a string");
98
+ return errors;
99
+ }
100
+
101
+ if (compatibility.length > MAX_COMPATIBILITY_LENGTH) {
102
+ errors.push(
103
+ `Compatibility exceeds ${MAX_COMPATIBILITY_LENGTH} character limit ` +
104
+ `(${compatibility.length} chars)`,
105
+ );
106
+ }
107
+
108
+ return errors;
109
+ }
110
+
111
+ function validateMetadataFields(metadata: Record<string, any>): string[] {
112
+ const errors: string[] = [];
113
+
114
+ const extraFields = Object.keys(metadata).filter(
115
+ (field) => !ALLOWED_FIELDS.has(field),
116
+ );
117
+ if (extraFields.length > 0) {
118
+ errors.push(
119
+ `Unexpected fields in frontmatter: ${extraFields.sort().join(", ")}. ` +
120
+ `Only ${Array.from(ALLOWED_FIELDS).sort().join(", ")} are allowed.`,
121
+ );
122
+ }
123
+
124
+ return errors;
125
+ }
126
+
127
+ export function validateMetadata(
128
+ metadata: Record<string, any>,
129
+ skillDir?: string,
130
+ ): string[] {
131
+ const errors: string[] = [];
132
+ errors.push(...validateMetadataFields(metadata));
133
+
134
+ if (!metadata.name) {
135
+ errors.push("Missing required field in frontmatter: name");
136
+ } else {
137
+ errors.push(...validateName(metadata.name, skillDir));
138
+ }
139
+
140
+ if (!metadata.description) {
141
+ errors.push("Missing required field in frontmatter: description");
142
+ } else {
143
+ errors.push(...validateDescription(metadata.description));
144
+ }
145
+
146
+ if (metadata.compatibility) {
147
+ errors.push(...validateCompatibility(metadata.compatibility));
148
+ }
149
+
150
+ return errors;
151
+ }
152
+
153
+ export function validate(skillDir: string): string[] {
154
+ const skillPath = path.resolve(skillDir);
155
+
156
+ if (!fs.existsSync(skillPath)) {
157
+ return [`Path does not exist: ${skillPath}`];
158
+ }
159
+
160
+ if (!fs.statSync(skillPath).isDirectory()) {
161
+ return [`Not a directory: ${skillPath}`];
162
+ }
163
+
164
+ const skillMdPath = findSkillMd(skillPath);
165
+ if (!skillMdPath) {
166
+ return ["Missing required file: SKILL.md"];
167
+ }
168
+
169
+ try {
170
+ const content = fs.readFileSync(skillMdPath, "utf8");
171
+ const { metadata } = parseFrontmatter(content);
172
+ return validateMetadata(metadata, skillPath);
173
+ } catch (error) {
174
+ return [error instanceof Error ? error.message : String(error)];
175
+ }
176
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "moduleResolution": "node",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": [
18
+ "src/**/*"
19
+ ],
20
+ "exclude": [
21
+ "node_modules",
22
+ "dist",
23
+ "**/*.test.ts"
24
+ ]
25
+ }