skills-reference 1.0.5 → 1.0.6
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/package.json +2 -2
- package/src/cli.ts +0 -101
- package/src/errors.ts +0 -23
- package/src/index.ts +0 -5
- package/src/models.ts +0 -46
- package/src/parser.ts +0 -113
- package/src/prompt.ts +0 -45
- package/src/validator.ts +0 -176
package/package.json
CHANGED
package/src/cli.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
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, "&")
|
|
41
|
-
.replace(/</g, "<")
|
|
42
|
-
.replace(/>/g, ">")
|
|
43
|
-
.replace(/"/g, """)
|
|
44
|
-
.replace(/'/g, "'");
|
|
45
|
-
}
|
package/src/validator.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
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
|
-
}
|