@poolzin/pool-bot 2026.3.7 → 2026.3.10

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.
Files changed (150) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +147 -69
  3. package/dist/.buildstamp +1 -1
  4. package/dist/agents/error-classifier.js +251 -0
  5. package/dist/agents/skills/security.js +211 -0
  6. package/dist/build-info.json +3 -3
  7. package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
  8. package/dist/cli/cron-cli/register.js +2 -0
  9. package/dist/cli/errors.js +187 -0
  10. package/dist/cli/lazy-commands.example.js +113 -0
  11. package/dist/cli/lazy-commands.js +329 -0
  12. package/dist/cli/program/command-registry.js +26 -0
  13. package/dist/cli/program/register.maintenance.js +21 -0
  14. package/dist/cli/program/register.skills.js +4 -0
  15. package/dist/cli/program/register.subclis.js +9 -0
  16. package/dist/cli/swarm-cli/register.js +8 -0
  17. package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
  18. package/dist/cli/telemetry-cli/register.js +10 -0
  19. package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
  20. package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
  21. package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
  22. package/dist/commands/doctor-checks.js +498 -0
  23. package/dist/config/config.js +1 -0
  24. package/dist/config/secrets-integration.js +88 -0
  25. package/dist/context-engine/index.js +33 -0
  26. package/dist/context-engine/legacy.js +179 -0
  27. package/dist/context-engine/registry.js +86 -0
  28. package/dist/context-engine/summarizing.js +290 -0
  29. package/dist/context-engine/types.js +7 -0
  30. package/dist/cron/service/timer.js +18 -0
  31. package/dist/gateway/protocol/index.js +5 -2
  32. package/dist/gateway/protocol/schema/error-codes.js +1 -0
  33. package/dist/gateway/protocol/schema/swarm.js +80 -0
  34. package/dist/gateway/protocol/schema.js +1 -0
  35. package/dist/gateway/server-close.js +4 -0
  36. package/dist/gateway/server-constants.js +1 -0
  37. package/dist/gateway/server-cron.js +29 -0
  38. package/dist/gateway/server-maintenance.js +35 -2
  39. package/dist/gateway/server-methods/swarm.js +58 -0
  40. package/dist/gateway/server-methods/telemetry.js +71 -0
  41. package/dist/gateway/server-methods-list.js +8 -0
  42. package/dist/gateway/server-methods.js +9 -2
  43. package/dist/gateway/server.impl.js +33 -16
  44. package/dist/infra/abort-pattern.js +106 -0
  45. package/dist/infra/retry.js +96 -0
  46. package/dist/secrets/index.js +28 -0
  47. package/dist/secrets/resolver.js +185 -0
  48. package/dist/secrets/runtime.js +142 -0
  49. package/dist/secrets/types.js +11 -0
  50. package/dist/security/dangerous-tools.js +80 -0
  51. package/dist/security/types.js +12 -0
  52. package/dist/skills/commands.js +333 -0
  53. package/dist/skills/index.js +164 -0
  54. package/dist/skills/loader.js +282 -0
  55. package/dist/skills/parser.js +446 -0
  56. package/dist/skills/registry.js +394 -0
  57. package/dist/skills/security.js +312 -0
  58. package/dist/skills/types.js +21 -0
  59. package/dist/swarm/service.js +247 -0
  60. package/dist/telemetry/alert-engine.js +258 -0
  61. package/dist/telemetry/cron-instrumentation.js +49 -0
  62. package/dist/telemetry/gateway-instrumentation.js +80 -0
  63. package/dist/telemetry/instrumentation.js +66 -0
  64. package/dist/telemetry/service.js +345 -0
  65. package/dist/test-utils/index.js +219 -0
  66. package/dist/tui/components/assistant-message.js +6 -2
  67. package/dist/tui/components/hyperlink-markdown.js +32 -0
  68. package/dist/tui/components/searchable-select-list.js +12 -1
  69. package/dist/tui/components/user-message.js +6 -2
  70. package/dist/tui/index.js +611 -0
  71. package/dist/tui/theme/theme-detection.js +226 -0
  72. package/dist/tui/tui-command-handlers.js +20 -0
  73. package/dist/tui/tui-formatters.js +4 -3
  74. package/dist/tui/utils/ctrl-c-handler.js +67 -0
  75. package/dist/tui/utils/osc8-hyperlinks.js +208 -0
  76. package/dist/tui/utils/safe-stop.js +180 -0
  77. package/dist/tui/utils/session-key-utils.js +81 -0
  78. package/dist/tui/utils/text-sanitization.js +284 -0
  79. package/dist/utils/lru-cache.js +116 -0
  80. package/dist/utils/performance.js +199 -0
  81. package/dist/utils/retry.js +240 -0
  82. package/docs/INTEGRATION_PLAN.md +475 -0
  83. package/docs/INTEGRATION_SUMMARY.md +215 -0
  84. package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
  85. package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
  86. package/docs/PLANO_ACAO_TUI.md +357 -0
  87. package/docs/PROGRESSO_TUI.md +66 -0
  88. package/docs/RELATORIO_FINAL.md +217 -0
  89. package/docs/diagnostico-shell-completion.md +265 -0
  90. package/docs/features/advanced-memory.md +585 -0
  91. package/docs/features/discord-components-v2.md +277 -0
  92. package/docs/features/swarm.md +100 -0
  93. package/docs/features/telemetry.md +284 -0
  94. package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
  95. package/docs/integrations/INTEGRATION_PLAN.md +744 -0
  96. package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
  97. package/docs/integrations/XYOPS_PLAN.md +978 -0
  98. package/docs/models/provider-infrastructure.md +400 -0
  99. package/docs/security/exec-approvals.md +294 -0
  100. package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
  101. package/docs/skills/SKILL.md +524 -0
  102. package/docs/skills.md +405 -0
  103. package/extensions/bluebubbles/package.json +1 -1
  104. package/extensions/copilot-proxy/package.json +1 -1
  105. package/extensions/diagnostics-otel/package.json +1 -1
  106. package/extensions/discord/package.json +1 -1
  107. package/extensions/feishu/package.json +1 -1
  108. package/extensions/google-antigravity-auth/package.json +1 -1
  109. package/extensions/google-gemini-cli-auth/package.json +1 -1
  110. package/extensions/googlechat/package.json +1 -1
  111. package/extensions/hexstrike-bridge/README.md +119 -0
  112. package/extensions/hexstrike-bridge/index.test.ts +247 -0
  113. package/extensions/hexstrike-bridge/index.ts +487 -0
  114. package/extensions/hexstrike-bridge/package.json +17 -0
  115. package/extensions/imessage/package.json +1 -1
  116. package/extensions/irc/package.json +1 -1
  117. package/extensions/line/package.json +1 -1
  118. package/extensions/llm-task/package.json +1 -1
  119. package/extensions/lobster/package.json +1 -1
  120. package/extensions/matrix/CHANGELOG.md +5 -0
  121. package/extensions/matrix/package.json +1 -1
  122. package/extensions/mattermost/package.json +1 -1
  123. package/extensions/mcp-server/index.ts +14 -0
  124. package/extensions/mcp-server/package.json +11 -0
  125. package/extensions/mcp-server/src/service.ts +540 -0
  126. package/extensions/memory-core/package.json +1 -1
  127. package/extensions/memory-lancedb/package.json +1 -1
  128. package/extensions/minimax-portal-auth/package.json +1 -1
  129. package/extensions/msteams/CHANGELOG.md +5 -0
  130. package/extensions/msteams/package.json +1 -1
  131. package/extensions/nextcloud-talk/package.json +1 -1
  132. package/extensions/nostr/CHANGELOG.md +5 -0
  133. package/extensions/nostr/package.json +1 -1
  134. package/extensions/open-prose/package.json +1 -1
  135. package/extensions/openai-codex-auth/package.json +1 -1
  136. package/extensions/signal/package.json +1 -1
  137. package/extensions/slack/package.json +1 -1
  138. package/extensions/telegram/package.json +1 -1
  139. package/extensions/tlon/package.json +1 -1
  140. package/extensions/twitch/CHANGELOG.md +5 -0
  141. package/extensions/twitch/package.json +1 -1
  142. package/extensions/voice-call/CHANGELOG.md +5 -0
  143. package/extensions/voice-call/package.json +1 -1
  144. package/extensions/whatsapp/package.json +1 -1
  145. package/extensions/zalo/CHANGELOG.md +5 -0
  146. package/extensions/zalo/package.json +1 -1
  147. package/extensions/zalouser/CHANGELOG.md +5 -0
  148. package/extensions/zalouser/package.json +1 -1
  149. package/package.json +8 -1
  150. package/skills/example-skill/SKILL.md +195 -0
@@ -0,0 +1,446 @@
1
+ /**
2
+ * SKILL.md parser with YAML frontmatter support
3
+ * Parses skill files into structured data
4
+ *
5
+ * @module skills/parser
6
+ */
7
+ import { readFile } from "node:fs/promises";
8
+ import { basename, dirname } from "node:path";
9
+ import { SkillError, } from "./types.js";
10
+ // ============================================================================
11
+ // Constants
12
+ // ============================================================================
13
+ const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
14
+ const SECTION_REGEX = /^##\s+(.+)$/gm;
15
+ const DEFAULT_MAX_FILE_SIZE = 1024 * 1024; // 1MB
16
+ const VALID_CATEGORIES = [
17
+ "automation",
18
+ "integration",
19
+ "utility",
20
+ "development",
21
+ "communication",
22
+ "data",
23
+ "ai",
24
+ "custom",
25
+ ];
26
+ // Required fields for agentskills.io format (strict mode)
27
+ const REQUIRED_FIELDS = ["id", "name", "description", "version", "category"];
28
+ // Required fields for PoolBot legacy format (lenient mode)
29
+ const _LEGACY_REQUIRED_FIELDS = ["name", "description"];
30
+ // ============================================================================
31
+ // YAML Parser (lightweight, no external deps)
32
+ // ============================================================================
33
+ /**
34
+ * Parse simple YAML frontmatter
35
+ * Handles basic key-value pairs, arrays, and nested objects
36
+ * Also handles JSON-style metadata values (for PoolBot legacy format)
37
+ */
38
+ function parseYaml(yaml) {
39
+ const result = {};
40
+ const lines = yaml.split("\n");
41
+ let currentArray = null;
42
+ let currentKey = null;
43
+ let multilineValue = [];
44
+ for (let i = 0; i < lines.length; i++) {
45
+ const line = lines[i];
46
+ const trimmed = line.trim();
47
+ // Skip empty lines and comments (but not inside multiline values)
48
+ if (!trimmed || trimmed.startsWith("#")) {
49
+ if (multilineValue.length > 0) {
50
+ multilineValue.push(line);
51
+ }
52
+ continue;
53
+ }
54
+ // Check for multiline value start (pipe | or greater-than >)
55
+ if (currentKey &&
56
+ (trimmed === "|" || trimmed === ">" || trimmed.startsWith("|-") || trimmed.startsWith(">-"))) {
57
+ multilineValue = [];
58
+ continue;
59
+ }
60
+ // Collect multiline value
61
+ if (currentKey && multilineValue.length > 0) {
62
+ // Check if next line is still part of multiline (indented or empty)
63
+ const nextLine = lines[i + 1];
64
+ if (nextLine !== undefined &&
65
+ (nextLine.startsWith(" ") || nextLine.startsWith("\t") || nextLine.trim() === "")) {
66
+ multilineValue.push(line);
67
+ continue;
68
+ }
69
+ else {
70
+ // End of multiline value
71
+ multilineValue.push(line);
72
+ result[currentKey] = multilineValue.join("\n").trim();
73
+ multilineValue = [];
74
+ currentKey = null;
75
+ continue;
76
+ }
77
+ }
78
+ // Check for array continuation
79
+ if (currentArray !== null && trimmed.startsWith("- ")) {
80
+ currentArray.push(trimmed.slice(2).trim());
81
+ continue;
82
+ }
83
+ // Reset array context if we see a new key
84
+ if (currentArray !== null && !trimmed.startsWith("- ")) {
85
+ currentArray = null;
86
+ }
87
+ // Parse key-value pair
88
+ const colonIndex = trimmed.indexOf(":");
89
+ if (colonIndex === -1)
90
+ continue;
91
+ const key = trimmed.slice(0, colonIndex).trim();
92
+ let value = trimmed.slice(colonIndex + 1).trim();
93
+ // Store key for potential multiline value
94
+ currentKey = key;
95
+ // Handle JSON-style values (for PoolBot legacy format)
96
+ if (value.startsWith("{") && value.endsWith("}")) {
97
+ try {
98
+ result[key] = JSON.parse(value);
99
+ continue;
100
+ }
101
+ catch {
102
+ // Not valid JSON, treat as string
103
+ }
104
+ }
105
+ // Handle arrays
106
+ if (value === "" || value === "[]") {
107
+ // Check if next line starts array
108
+ currentArray = [];
109
+ result[key] = currentArray;
110
+ continue;
111
+ }
112
+ // Handle inline arrays
113
+ if (value.startsWith("[") && value.endsWith("]")) {
114
+ result[key] = value
115
+ .slice(1, -1)
116
+ .split(",")
117
+ .map((v) => v.trim().replace(/^["']|["']$/g, ""))
118
+ .filter(Boolean);
119
+ continue;
120
+ }
121
+ // Handle multiline indicator
122
+ if (value === "|" || value === ">" || value.startsWith("|-") || value.startsWith(">-")) {
123
+ multilineValue = [];
124
+ continue;
125
+ }
126
+ // Handle quoted strings
127
+ if ((value.startsWith('"') && value.endsWith('"')) ||
128
+ (value.startsWith("'") && value.endsWith("'"))) {
129
+ value = value.slice(1, -1);
130
+ }
131
+ // Handle booleans
132
+ if (value === "true") {
133
+ result[key] = true;
134
+ }
135
+ else if (value === "false") {
136
+ result[key] = false;
137
+ }
138
+ else if (/^\d+$/.test(value)) {
139
+ // Handle numbers
140
+ result[key] = parseInt(value, 10);
141
+ }
142
+ else {
143
+ result[key] = value;
144
+ }
145
+ currentKey = null;
146
+ }
147
+ // Handle any remaining multiline value
148
+ if (currentKey && multilineValue.length > 0) {
149
+ result[currentKey] = multilineValue.join("\n").trim();
150
+ }
151
+ return result;
152
+ }
153
+ // ============================================================================
154
+ // Section Parser
155
+ // ============================================================================
156
+ /**
157
+ * Parse markdown body into sections
158
+ */
159
+ function parseSections(body) {
160
+ const sections = new Map();
161
+ const matches = Array.from(body.matchAll(SECTION_REGEX));
162
+ for (let i = 0; i < matches.length; i++) {
163
+ const match = matches[i];
164
+ const sectionName = match[1].toLowerCase().trim();
165
+ const startIndex = match.index + match[0].length;
166
+ const endIndex = i < matches.length - 1 ? matches[i + 1].index : body.length;
167
+ const content = body.slice(startIndex, endIndex).trim();
168
+ sections.set(sectionName, content);
169
+ }
170
+ return sections;
171
+ }
172
+ // ============================================================================
173
+ // Metadata Validation
174
+ // ============================================================================
175
+ /**
176
+ * Validate and transform raw frontmatter into SkillMetadata
177
+ * Supports both agentskills.io format and PoolBot legacy format
178
+ */
179
+ function validateMetadata(frontmatter, options = {}) {
180
+ const required = options.requiredFields ?? REQUIRED_FIELDS;
181
+ const lenient = options.lenient ?? false;
182
+ // Detect format: agentskills.io has 'id', legacy has 'name' but no 'id'
183
+ const isLegacyFormat = !frontmatter.id && frontmatter.name;
184
+ // For legacy format, use name as id
185
+ let id;
186
+ if (isLegacyFormat) {
187
+ id = String(frontmatter.name).toLowerCase().replace(/\s+/g, "-");
188
+ }
189
+ else {
190
+ id = frontmatter.id;
191
+ }
192
+ // Check required fields (skip id check in lenient mode for legacy)
193
+ for (const field of required) {
194
+ if (field === "id" && lenient && isLegacyFormat)
195
+ continue;
196
+ if (field === "category" && lenient && isLegacyFormat)
197
+ continue;
198
+ if (field === "version" && lenient && isLegacyFormat)
199
+ continue;
200
+ if (!(field in frontmatter) || frontmatter[field] === undefined) {
201
+ throw new SkillError("INVALID_METADATA", `Missing required field: ${field}`, id);
202
+ }
203
+ }
204
+ // Validate category (use 'custom' as default for legacy)
205
+ let category;
206
+ if (isLegacyFormat && lenient && !frontmatter.category) {
207
+ category = "custom";
208
+ }
209
+ else {
210
+ category = frontmatter.category;
211
+ }
212
+ if (!VALID_CATEGORIES.includes(category)) {
213
+ if (!lenient) {
214
+ throw new SkillError("INVALID_METADATA", `Invalid category: ${category}. Must be one of: ${VALID_CATEGORIES.join(", ")}`, id);
215
+ }
216
+ category = "custom";
217
+ }
218
+ // Validate id format (kebab-case) - only for non-legacy
219
+ if (!isLegacyFormat && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)) {
220
+ throw new SkillError("INVALID_METADATA", `Invalid skill ID: ${id}. Must be kebab-case (e.g., 'my-skill-name')`, id);
221
+ }
222
+ // Validate version (basic semver check) - use '1.0.0' as default for legacy
223
+ let version;
224
+ if (isLegacyFormat && lenient && !frontmatter.version) {
225
+ version = "1.0.0";
226
+ }
227
+ else {
228
+ version = frontmatter.version;
229
+ }
230
+ if (options.validateVersions !== false && !isLegacyFormat) {
231
+ if (!/^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/.test(version)) {
232
+ throw new SkillError("INVALID_METADATA", `Invalid version: ${version}. Must follow semver (e.g., '1.0.0')`, id);
233
+ }
234
+ }
235
+ // Parse dates
236
+ let createdAt;
237
+ let updatedAt;
238
+ if (frontmatter.createdAt) {
239
+ createdAt = new Date(frontmatter.createdAt);
240
+ }
241
+ if (frontmatter.updatedAt) {
242
+ updatedAt = new Date(frontmatter.updatedAt);
243
+ }
244
+ // Parse arrays
245
+ const tags = Array.isArray(frontmatter.tags)
246
+ ? frontmatter.tags
247
+ : frontmatter.tags
248
+ ? [String(frontmatter.tags)]
249
+ : [];
250
+ const dependencies = Array.isArray(frontmatter.dependencies)
251
+ ? frontmatter.dependencies
252
+ : frontmatter.dependencies
253
+ ? [String(frontmatter.dependencies)]
254
+ : undefined;
255
+ const externalTools = Array.isArray(frontmatter.externalTools)
256
+ ? frontmatter.externalTools
257
+ : frontmatter.externalTools
258
+ ? [String(frontmatter.externalTools)]
259
+ : undefined;
260
+ return {
261
+ id,
262
+ name: String(frontmatter.name),
263
+ description: String(frontmatter.description),
264
+ version,
265
+ author: frontmatter.author ? String(frontmatter.author) : undefined,
266
+ category: category,
267
+ tags,
268
+ minPoolBotVersion: frontmatter.minPoolBotVersion
269
+ ? String(frontmatter.minPoolBotVersion)
270
+ : undefined,
271
+ dependencies,
272
+ externalTools,
273
+ createdAt,
274
+ updatedAt,
275
+ license: frontmatter.license ? String(frontmatter.license) : undefined,
276
+ repository: frontmatter.repository ? String(frontmatter.repository) : undefined,
277
+ documentation: frontmatter.documentation ? String(frontmatter.documentation) : undefined,
278
+ enabledByDefault: frontmatter.enabledByDefault === true,
279
+ };
280
+ }
281
+ // ============================================================================
282
+ // Content Extraction
283
+ // ============================================================================
284
+ /**
285
+ * Extract skill content from parsed sections
286
+ */
287
+ function extractContent(sections) {
288
+ // Map section aliases
289
+ const quickstart = sections.get("quick start") ?? sections.get("quickstart") ?? sections.get("getting started");
290
+ const usage = sections.get("usage") ?? sections.get("how to use") ?? sections.get("using this skill") ?? "";
291
+ const configuration = sections.get("configuration") ?? sections.get("config") ?? sections.get("setup");
292
+ const environment = sections.get("environment") ?? sections.get("env") ?? sections.get("environment variables");
293
+ const examples = sections.get("examples") ?? sections.get("example") ?? sections.get("example usage");
294
+ const api = sections.get("api") ?? sections.get("api reference") ?? sections.get("reference");
295
+ const troubleshooting = sections.get("troubleshooting") ??
296
+ sections.get("troubleshoot") ??
297
+ sections.get("common issues");
298
+ return {
299
+ quickstart,
300
+ usage,
301
+ configuration,
302
+ environment,
303
+ examples,
304
+ api,
305
+ troubleshooting,
306
+ };
307
+ }
308
+ // ============================================================================
309
+ // Linked Files Discovery
310
+ // ============================================================================
311
+ /**
312
+ * Discover linked files in skill directory
313
+ */
314
+ async function discoverLinkedFiles(skillDir) {
315
+ const linkedFiles = [];
316
+ try {
317
+ const { readdir } = await import("node:fs/promises");
318
+ const entries = await readdir(skillDir, { withFileTypes: true });
319
+ for (const entry of entries) {
320
+ if (entry.isFile() && entry.name.toLowerCase() !== "skill.md") {
321
+ linkedFiles.push({
322
+ path: entry.name,
323
+ description: undefined, // Could parse from a manifest
324
+ required: false, // Could be determined by references in content
325
+ });
326
+ }
327
+ }
328
+ }
329
+ catch {
330
+ // Directory might not exist or be accessible
331
+ }
332
+ return linkedFiles;
333
+ }
334
+ // ============================================================================
335
+ // Main Parser Functions
336
+ // ============================================================================
337
+ /**
338
+ * Parse a SKILL.md file into structured data
339
+ */
340
+ export async function parseSkillFile(filePath, options = {}) {
341
+ const maxSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
342
+ // Read file
343
+ let content;
344
+ try {
345
+ const stats = await import("node:fs/promises").then((fs) => fs.stat(filePath));
346
+ if (stats.size > maxSize) {
347
+ throw new SkillError("PARSE_ERROR", `Skill file too large: ${stats.size} bytes (max: ${maxSize})`, undefined);
348
+ }
349
+ content = await readFile(filePath, "utf-8");
350
+ }
351
+ catch (error) {
352
+ if (error instanceof SkillError)
353
+ throw error;
354
+ throw new SkillError("PARSE_ERROR", `Failed to read skill file: ${error}`, undefined, error);
355
+ }
356
+ // Parse frontmatter
357
+ const match = content.match(FRONTMATTER_REGEX);
358
+ if (!match) {
359
+ throw new SkillError("PARSE_ERROR", "Invalid SKILL.md format: missing YAML frontmatter", undefined);
360
+ }
361
+ const [, yamlContent, bodyContent] = match;
362
+ // Parse YAML
363
+ let frontmatter;
364
+ try {
365
+ frontmatter = parseYaml(yamlContent);
366
+ }
367
+ catch (error) {
368
+ throw new SkillError("PARSE_ERROR", `Failed to parse YAML frontmatter: ${error}`, undefined, error);
369
+ }
370
+ // Parse sections
371
+ const sections = parseSections(bodyContent);
372
+ return {
373
+ frontmatter,
374
+ body: bodyContent,
375
+ sections,
376
+ sourcePath: filePath,
377
+ };
378
+ }
379
+ /**
380
+ * Parse and validate a complete skill
381
+ */
382
+ export async function parseSkill(filePath, options = {}) {
383
+ // Parse the file
384
+ const parsed = await parseSkillFile(filePath, options);
385
+ // Validate metadata
386
+ const metadata = validateMetadata(parsed.frontmatter, options);
387
+ // Extract content
388
+ const content = extractContent(parsed.sections);
389
+ content.raw = parsed.body;
390
+ // Discover linked files
391
+ const skillDir = dirname(filePath.toString());
392
+ const linkedFiles = await discoverLinkedFiles(skillDir);
393
+ return {
394
+ metadata,
395
+ content,
396
+ linkedFiles,
397
+ sourcePath: filePath,
398
+ status: "installed",
399
+ verification: "unverified",
400
+ enabled: metadata.enabledByDefault,
401
+ };
402
+ }
403
+ /**
404
+ * Parse multiple skill files
405
+ */
406
+ export async function parseSkills(filePaths, options = {}) {
407
+ const skills = [];
408
+ const errors = new Map();
409
+ await Promise.all(filePaths.map(async (path) => {
410
+ try {
411
+ const skill = await parseSkill(path, options);
412
+ skills.push(skill);
413
+ }
414
+ catch (error) {
415
+ errors.set(path, error);
416
+ }
417
+ }));
418
+ return { skills, errors };
419
+ }
420
+ /**
421
+ * Check if a file is a valid SKILL.md
422
+ */
423
+ export async function isSkillFile(filePath) {
424
+ try {
425
+ const fileName = basename(filePath.toString()).toLowerCase();
426
+ if (fileName !== "skill.md")
427
+ return false;
428
+ const content = await readFile(filePath, "utf-8");
429
+ return FRONTMATTER_REGEX.test(content);
430
+ }
431
+ catch {
432
+ return false;
433
+ }
434
+ }
435
+ /**
436
+ * Extract skill ID from file path
437
+ */
438
+ export function extractSkillId(filePath) {
439
+ const dir = dirname(filePath.toString());
440
+ const base = basename(dir);
441
+ return base.toLowerCase().replace(/\s+/g, "-");
442
+ }
443
+ // ============================================================================
444
+ // Re-exports
445
+ // ============================================================================
446
+ export { parseYaml, parseSections, validateMetadata, extractContent };