@easynet/agent-skill 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.
@@ -0,0 +1,8 @@
1
+ export type { SkillMetadata, SkillsConfig, LoadedSkills } from "./types.js";
2
+ export type { ParsedFrontmatter } from "./parser.js";
3
+ export type { MatchResult } from "./matcher.js";
4
+ export { parseSkillFrontmatter } from "./parser.js";
5
+ export { scanSkillsDirectory } from "./scanner.js";
6
+ export { generatePromptSection, loadAndInjectSkills } from "./injector.js";
7
+ export { buildSkillIndex, matchSkill } from "./matcher.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5E,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { parseSkillFrontmatter } from "./parser.js";
2
+ export { scanSkillsDirectory } from "./scanner.js";
3
+ export { generatePromptSection, loadAndInjectSkills } from "./injector.js";
4
+ export { buildSkillIndex, matchSkill } from "./matcher.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { SkillMetadata, SkillsConfig, LoadedSkills } from "./types.js";
2
+ /**
3
+ * Generate a system prompt section listing available skills.
4
+ * The agent uses this metadata to decide when to read a skill's SKILL.md.
5
+ */
6
+ export declare function generatePromptSection(skills: SkillMetadata[]): string;
7
+ /**
8
+ * Load skills from a config, generate the prompt section, and inject it
9
+ * into the system prompt.
10
+ *
11
+ * For mode "prompt" (default): appends skill metadata to system prompt.
12
+ * For mode "subagent": still loads metadata but does NOT inject into prompt
13
+ * (the activateSkill tool handles discovery instead).
14
+ */
15
+ export declare function loadAndInjectSkills(config: SkillsConfig, systemPrompt: string): Promise<{
16
+ systemPrompt: string;
17
+ skills: LoadedSkills;
18
+ }>;
19
+ //# sourceMappingURL=injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5E;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAerE;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,CAgBzD"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Generate system prompt sections from discovered skills and inject them.
3
+ */
4
+ import { resolve } from "node:path";
5
+ import { scanSkillsDirectory } from "./scanner.js";
6
+ /**
7
+ * Generate a system prompt section listing available skills.
8
+ * The agent uses this metadata to decide when to read a skill's SKILL.md.
9
+ */
10
+ export function generatePromptSection(skills) {
11
+ if (skills.length === 0)
12
+ return "";
13
+ const lines = [
14
+ "## Available Skills",
15
+ "When a user request matches a skill below, read its SKILL.md file for detailed instructions, then follow them step by step.",
16
+ "",
17
+ ];
18
+ for (const skill of skills) {
19
+ lines.push(`- **${skill.name}**: ${skill.description}`);
20
+ lines.push(` Path: ${skill.skillMdPath}`);
21
+ }
22
+ return lines.join("\n");
23
+ }
24
+ /**
25
+ * Load skills from a config, generate the prompt section, and inject it
26
+ * into the system prompt.
27
+ *
28
+ * For mode "prompt" (default): appends skill metadata to system prompt.
29
+ * For mode "subagent": still loads metadata but does NOT inject into prompt
30
+ * (the activateSkill tool handles discovery instead).
31
+ */
32
+ export async function loadAndInjectSkills(config, systemPrompt) {
33
+ const dirPath = resolve(config.path);
34
+ const skills = await scanSkillsDirectory(dirPath);
35
+ const loaded = { config, skills };
36
+ const shouldInject = config.inject_metadata !== false;
37
+ const mode = config.mode ?? "prompt";
38
+ if (mode === "prompt" && shouldInject && skills.length > 0) {
39
+ const section = generatePromptSection(skills);
40
+ const injected = systemPrompt.trimEnd() + "\n\n" + section;
41
+ return { systemPrompt: injected, skills: loaded };
42
+ }
43
+ return { systemPrompt, skills: loaded };
44
+ }
45
+ //# sourceMappingURL=injector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.js","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGnD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAuB;IAC3D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,KAAK,GAAG;QACZ,qBAAqB;QACrB,6HAA6H;QAC7H,EAAE;KACH,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QACxD,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAoB,EACpB,YAAoB;IAEpB,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAElD,MAAM,MAAM,GAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAEhD,MAAM,YAAY,GAAG,MAAM,CAAC,eAAe,KAAK,KAAK,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;IAErC,IAAI,IAAI,KAAK,QAAQ,IAAI,YAAY,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC;QAC3D,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACpD,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { SkillMetadata } from "./types.js";
2
+ export interface MatchResult {
3
+ skill: SkillMetadata;
4
+ score: number;
5
+ /** Full SKILL.md content, loaded on match. */
6
+ instructions: string;
7
+ }
8
+ /** Precomputed keyword set for a skill, derived from name + description. */
9
+ interface SkillKeywords {
10
+ skill: SkillMetadata;
11
+ keywords: Set<string>;
12
+ }
13
+ /**
14
+ * Build keyword index for skills. Call once at startup, reuse across requests.
15
+ */
16
+ export declare function buildSkillIndex(skills: SkillMetadata[]): SkillKeywords[];
17
+ /**
18
+ * Match user message against skill index.
19
+ * Returns the best matching skill if score meets threshold, otherwise null.
20
+ *
21
+ * Scoring: proportion of skill keywords found in user message.
22
+ * A skill with 5 keywords where 3 appear in the message scores 0.6.
23
+ */
24
+ export declare function matchSkill(message: string, index: SkillKeywords[], threshold?: number): Promise<MatchResult | null>;
25
+ export {};
26
+ //# sourceMappingURL=matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAsBhD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;CACtB;AA0CD,4EAA4E;AAC5E,UAAU,aAAa;IACrB,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAMxE;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,aAAa,EAAE,EACtB,SAAS,SAAM,GACd,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAmC7B"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Match a user message against available skills using keyword overlap.
3
+ * Zero-latency, no LLM call — pure string matching on skill descriptions and names.
4
+ */
5
+ import { readFile } from "node:fs/promises";
6
+ /** Stop words excluded from keyword extraction (EN + common CN particles). */
7
+ const STOP_WORDS = new Set([
8
+ // English
9
+ "a", "an", "the", "and", "or", "but", "in", "on", "at", "to", "for",
10
+ "of", "with", "by", "from", "is", "are", "was", "were", "be", "been",
11
+ "being", "have", "has", "had", "do", "does", "did", "will", "would",
12
+ "could", "should", "may", "might", "shall", "can", "need", "must",
13
+ "it", "its", "that", "this", "these", "those", "i", "you", "we", "they",
14
+ "he", "she", "my", "your", "our", "their", "what", "which", "who",
15
+ "how", "when", "where", "why", "not", "no", "so", "if", "then",
16
+ "just", "also", "very", "too", "more", "most", "some", "any", "all",
17
+ // Chinese particles
18
+ "的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都",
19
+ "一", "一个", "上", "也", "很", "到", "说", "要", "去", "你",
20
+ "会", "着", "没有", "看", "好", "自己", "这", "他", "她", "它",
21
+ "吗", "吧", "呢", "啊", "哪", "那", "这个", "什么", "怎么",
22
+ "可以", "没", "把", "被", "让", "给", "用", "还", "而", "但",
23
+ "如果", "因为", "所以",
24
+ ]);
25
+ /**
26
+ * Extract meaningful keywords from a string.
27
+ * Handles both English (space-delimited) and Chinese (character bigrams + individual chars).
28
+ */
29
+ function extractKeywords(text) {
30
+ const lower = text.toLowerCase();
31
+ const words = new Set();
32
+ // Extract English words and hyphenated segments
33
+ for (const match of lower.matchAll(/[a-z][a-z0-9-]*/g)) {
34
+ const word = match[0];
35
+ if (word.length >= 2 && !STOP_WORDS.has(word)) {
36
+ words.add(word);
37
+ // Also add hyphen-split parts: "disk-usage" → "disk", "usage"
38
+ if (word.includes("-")) {
39
+ for (const part of word.split("-")) {
40
+ if (part.length >= 2 && !STOP_WORDS.has(part))
41
+ words.add(part);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ // Extract Chinese characters and bigrams
47
+ const cjk = lower.match(/[\u4e00-\u9fff]+/g);
48
+ if (cjk) {
49
+ for (const segment of cjk) {
50
+ for (const char of segment) {
51
+ if (!STOP_WORDS.has(char))
52
+ words.add(char);
53
+ }
54
+ // Bigrams for better matching
55
+ for (let i = 0; i < segment.length - 1; i++) {
56
+ const bigram = segment.slice(i, i + 2);
57
+ if (!STOP_WORDS.has(bigram))
58
+ words.add(bigram);
59
+ }
60
+ }
61
+ }
62
+ return words;
63
+ }
64
+ /**
65
+ * Build keyword index for skills. Call once at startup, reuse across requests.
66
+ */
67
+ export function buildSkillIndex(skills) {
68
+ return skills.map((skill) => ({
69
+ skill,
70
+ // Combine name (with hyphens as spaces) and description for richer matching
71
+ keywords: extractKeywords(`${skill.name.replace(/-/g, " ")} ${skill.description}`),
72
+ }));
73
+ }
74
+ /**
75
+ * Match user message against skill index.
76
+ * Returns the best matching skill if score meets threshold, otherwise null.
77
+ *
78
+ * Scoring: proportion of skill keywords found in user message.
79
+ * A skill with 5 keywords where 3 appear in the message scores 0.6.
80
+ */
81
+ export async function matchSkill(message, index, threshold = 0.3) {
82
+ if (index.length === 0)
83
+ return null;
84
+ const messageKeywords = extractKeywords(message);
85
+ if (messageKeywords.size === 0)
86
+ return null;
87
+ let bestMatch = null;
88
+ for (const entry of index) {
89
+ if (entry.keywords.size === 0)
90
+ continue;
91
+ let hits = 0;
92
+ for (const kw of entry.keywords) {
93
+ if (messageKeywords.has(kw))
94
+ hits++;
95
+ }
96
+ const score = hits / entry.keywords.size;
97
+ if (score >= threshold && (!bestMatch || score > bestMatch.score)) {
98
+ bestMatch = { entry, score };
99
+ }
100
+ }
101
+ if (!bestMatch)
102
+ return null;
103
+ // Load SKILL.md content for the matched skill
104
+ try {
105
+ const instructions = await readFile(bestMatch.entry.skill.skillMdPath, "utf-8");
106
+ return {
107
+ skill: bestMatch.entry.skill,
108
+ score: bestMatch.score,
109
+ instructions,
110
+ };
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface ParsedFrontmatter {
2
+ name: string;
3
+ description: string;
4
+ allowedTools?: string;
5
+ }
6
+ /**
7
+ * Parse YAML frontmatter from a SKILL.md file content string.
8
+ * Returns extracted metadata fields, or throws on malformed frontmatter.
9
+ */
10
+ export declare function parseSkillFrontmatter(content: string, filePath: string): ParsedFrontmatter;
11
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAsC1F"}
package/dist/parser.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Minimal SKILL.md frontmatter parser.
3
+ * Extracts name, description, and allowed-tools from YAML frontmatter.
4
+ * Full validation (name pattern, length limits, etc.) belongs in agent-tool.
5
+ */
6
+ import yaml from "js-yaml";
7
+ /**
8
+ * Parse YAML frontmatter from a SKILL.md file content string.
9
+ * Returns extracted metadata fields, or throws on malformed frontmatter.
10
+ */
11
+ export function parseSkillFrontmatter(content, filePath) {
12
+ const trimmed = content.trimStart();
13
+ if (!trimmed.startsWith("---")) {
14
+ throw new Error(`SKILL.md must start with YAML frontmatter (---): ${filePath}`);
15
+ }
16
+ const endIndex = trimmed.indexOf("\n---", 3);
17
+ if (endIndex === -1) {
18
+ throw new Error(`SKILL.md frontmatter is not closed (missing closing ---): ${filePath}`);
19
+ }
20
+ const yamlBlock = trimmed.slice(4, endIndex).trim();
21
+ let raw;
22
+ try {
23
+ const parsed = yaml.load(yamlBlock);
24
+ if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
25
+ throw new Error("YAML frontmatter must be an object (key: value)");
26
+ }
27
+ raw = parsed;
28
+ }
29
+ catch (err) {
30
+ throw new Error(`Invalid YAML frontmatter in ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
31
+ }
32
+ const name = stringField(raw, "name");
33
+ const description = stringField(raw, "description");
34
+ if (!name) {
35
+ throw new Error(`SKILL.md frontmatter missing required field "name": ${filePath}`);
36
+ }
37
+ if (!description) {
38
+ throw new Error(`SKILL.md frontmatter missing required field "description": ${filePath}`);
39
+ }
40
+ const allowedTools = stringField(raw, "allowed-tools") || undefined;
41
+ return { name, description, allowedTools };
42
+ }
43
+ function stringField(raw, key) {
44
+ const v = raw[key];
45
+ if (v == null)
46
+ return "";
47
+ if (typeof v === "string")
48
+ return v;
49
+ if (typeof v === "number" || typeof v === "boolean")
50
+ return String(v);
51
+ if (Array.isArray(v)) {
52
+ return v.map((x) => (typeof x === "string" ? x : String(x))).join("\n");
53
+ }
54
+ return String(v);
55
+ }
56
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,IAAI,MAAM,SAAS,CAAC;AAQ3B;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe,EAAE,QAAgB;IACrE,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAEpC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,oDAAoD,QAAQ,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7C,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,6DAA6D,QAAQ,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IAEpD,IAAI,GAA4B,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1E,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QACD,GAAG,GAAG,MAAiC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClH,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAEpD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,uDAAuD,QAAQ,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,8DAA8D,QAAQ,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,SAAS,CAAC;IAEpE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,WAAW,CAAC,GAA4B,EAAE,GAAW;IAC5D,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IACzB,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { SkillMetadata } from "./types.js";
2
+ /**
3
+ * Discover all skills in a directory.
4
+ * Each subdirectory containing a SKILL.md file is treated as a skill.
5
+ * Returns metadata (Level 1) for all valid skills found.
6
+ */
7
+ export declare function scanSkillsDirectory(dirPath: string): Promise<SkillMetadata[]>;
8
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAwCnF"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Scan a directory for SKILL.md-based skills and return their metadata.
3
+ */
4
+ import { readdir, readFile, access } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { parseSkillFrontmatter } from "./parser.js";
7
+ /**
8
+ * Discover all skills in a directory.
9
+ * Each subdirectory containing a SKILL.md file is treated as a skill.
10
+ * Returns metadata (Level 1) for all valid skills found.
11
+ */
12
+ export async function scanSkillsDirectory(dirPath) {
13
+ let entries;
14
+ try {
15
+ entries = await readdir(dirPath, { withFileTypes: true });
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ const skills = [];
21
+ for (const entry of entries) {
22
+ if (!entry.isDirectory())
23
+ continue;
24
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
25
+ continue;
26
+ const skillDir = join(dirPath, entry.name);
27
+ const skillMdPath = join(skillDir, "SKILL.md");
28
+ try {
29
+ await access(skillMdPath);
30
+ }
31
+ catch {
32
+ continue;
33
+ }
34
+ try {
35
+ const content = await readFile(skillMdPath, "utf-8");
36
+ const frontmatter = parseSkillFrontmatter(content, skillMdPath);
37
+ skills.push({
38
+ name: frontmatter.name,
39
+ description: frontmatter.description,
40
+ allowedTools: frontmatter.allowedTools,
41
+ skillMdPath,
42
+ dirPath: skillDir,
43
+ });
44
+ }
45
+ catch (err) {
46
+ console.error(`[agent-skill] Failed to parse ${skillMdPath}: ${err instanceof Error ? err.message : String(err)}`);
47
+ }
48
+ }
49
+ return skills;
50
+ }
51
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGpD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAe;IACvD,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;YAAE,SAAS;QAE1E,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAE/C,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YACrD,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAEhE,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,WAAW,CAAC,IAAI;gBACtB,WAAW,EAAE,WAAW,CAAC,WAAW;gBACpC,YAAY,EAAE,WAAW,CAAC,YAAY;gBACtC,WAAW;gBACX,OAAO,EAAE,QAAQ;aAClB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,iCAAiC,WAAW,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/test/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert";
3
+ import { parseSkillFrontmatter, scanSkillsDirectory, loadAndInjectSkills } from "../index.js";
4
+ describe("@easynet/agent-skill", () => {
5
+ it("exports parseSkillFrontmatter", () => {
6
+ assert.strictEqual(typeof parseSkillFrontmatter, "function");
7
+ });
8
+ it("exports scanSkillsDirectory", () => {
9
+ assert.strictEqual(typeof scanSkillsDirectory, "function");
10
+ });
11
+ it("exports loadAndInjectSkills", () => {
12
+ assert.strictEqual(typeof loadAndInjectSkills, "function");
13
+ });
14
+ });
15
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/test/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE9F,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,WAAW,CAAC,OAAO,qBAAqB,EAAE,UAAU,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,WAAW,CAAC,OAAO,mBAAmB,EAAE,UAAU,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,WAAW,CAAC,OAAO,mBAAmB,EAAE,UAAU,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Skill metadata extracted from SKILL.md frontmatter (Level 1).
3
+ * Always loaded at startup for discovery (~100 tokens per skill).
4
+ */
5
+ export interface SkillMetadata {
6
+ /** Skill name identifier from frontmatter. */
7
+ name: string;
8
+ /** What the skill does and when to use it. */
9
+ description: string;
10
+ /** Optional space-delimited list of pre-approved tools. */
11
+ allowedTools?: string;
12
+ /** Absolute path to SKILL.md. */
13
+ skillMdPath: string;
14
+ /** Absolute path to the skill directory. */
15
+ dirPath: string;
16
+ }
17
+ /**
18
+ * Configuration for skill loading and injection.
19
+ * Corresponds to the `skills` field in agent YAML config.
20
+ */
21
+ export interface SkillsConfig {
22
+ /** Skills directory path (resolved to absolute). */
23
+ path: string;
24
+ /** How skills are surfaced to the agent. Default: "prompt". */
25
+ mode?: "prompt" | "subagent";
26
+ /** Whether to inject skill metadata into the system prompt. Default: true. */
27
+ inject_metadata?: boolean;
28
+ }
29
+ /**
30
+ * Result of loading skills from a directory.
31
+ */
32
+ export interface LoadedSkills {
33
+ config: SkillsConfig;
34
+ skills: SkillMetadata[];
35
+ }
36
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,8EAA8E;IAC9E,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@easynet/agent-skill",
3
+ "version": "1.0.0",
4
+ "description": "Agent Skills discovery, parsing, and prompt injection for Easynet agents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "typecheck": "tsc --noEmit",
20
+ "pretest": "npm run build",
21
+ "test": "node --test dist/test/index.test.js"
22
+ },
23
+ "dependencies": {
24
+ "js-yaml": "^4.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "@semantic-release/git": "^10.0.1",
28
+ "@semantic-release/npm": "^12.0.0",
29
+ "@types/js-yaml": "^4.0.9",
30
+ "@types/node": "^22.10.0",
31
+ "semantic-release": "^24.0.0",
32
+ "typescript": "~5.7.2"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "license": "MIT",
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org/"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/easynet-world/agent-skill.git"
45
+ }
46
+ }