@elisym/sdk 0.5.0 → 0.7.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,673 @@
1
+ 'use strict';
2
+
3
+ var child_process = require('child_process');
4
+ var string_decoder = require('string_decoder');
5
+ var fs = require('fs');
6
+ var path = require('path');
7
+ var YAML = require('yaml');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var YAML__default = /*#__PURE__*/_interopDefault(YAML);
12
+
13
+ // src/skills/llmClient.ts
14
+ var LLM_TIMEOUT_MS = 12e4;
15
+ var MAX_RETRIES = 2;
16
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
17
+ var DEFAULT_MAX_TOKENS = 4096;
18
+ var DEFAULT_ANTHROPIC_MODEL = "claude-haiku-4-5-20251001";
19
+ var DEFAULT_OPENAI_MODEL = "gpt-4o-mini";
20
+ function createAbortError() {
21
+ const err = new Error("The operation was aborted");
22
+ err.name = "AbortError";
23
+ return err;
24
+ }
25
+ function sleepWithSignal(ms, signal) {
26
+ if (signal?.aborted) {
27
+ return Promise.reject(createAbortError());
28
+ }
29
+ if (!signal) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+ return new Promise((resolve, reject) => {
33
+ const cleanup = () => {
34
+ clearTimeout(timer);
35
+ signal.removeEventListener("abort", onAbort);
36
+ };
37
+ const onAbort = () => {
38
+ cleanup();
39
+ reject(createAbortError());
40
+ };
41
+ const timer = setTimeout(() => {
42
+ cleanup();
43
+ resolve();
44
+ }, ms);
45
+ signal.addEventListener("abort", onAbort, { once: true });
46
+ });
47
+ }
48
+ async function fetchWithTimeout(url, init, signal) {
49
+ if (signal?.aborted) {
50
+ throw createAbortError();
51
+ }
52
+ const controller = new AbortController();
53
+ const timer = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS);
54
+ const onAbort = () => controller.abort();
55
+ signal?.addEventListener("abort", onAbort, { once: true });
56
+ try {
57
+ return await fetch(url, { ...init, signal: controller.signal });
58
+ } finally {
59
+ clearTimeout(timer);
60
+ signal?.removeEventListener("abort", onAbort);
61
+ }
62
+ }
63
+ async function fetchWithRetry(url, init, signal) {
64
+ for (let attempt = 0; ; attempt++) {
65
+ let response;
66
+ try {
67
+ response = await fetchWithTimeout(url, init, signal);
68
+ } catch (error) {
69
+ const name = error instanceof Error ? error.name : "";
70
+ if (attempt >= MAX_RETRIES || name === "AbortError") {
71
+ throw error;
72
+ }
73
+ await sleepWithSignal(Math.min(1e3 * 2 ** attempt, 8e3), signal);
74
+ continue;
75
+ }
76
+ if (response.ok || attempt >= MAX_RETRIES || !RETRYABLE_STATUSES.has(response.status)) {
77
+ return response;
78
+ }
79
+ const retryAfter = response.headers.get("retry-after");
80
+ const delay = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3 || 1e3 * 2 ** attempt, 3e4) : Math.min(1e3 * 2 ** attempt, 8e3);
81
+ await response.body?.cancel().catch(() => void 0);
82
+ await sleepWithSignal(delay, signal);
83
+ }
84
+ }
85
+ var AnthropicClient = class {
86
+ constructor(config) {
87
+ this.config = config;
88
+ }
89
+ async complete(systemPrompt, userInput, signal) {
90
+ const response = await fetchWithRetry(
91
+ "https://api.anthropic.com/v1/messages",
92
+ {
93
+ method: "POST",
94
+ headers: {
95
+ "Content-Type": "application/json",
96
+ "x-api-key": this.config.apiKey,
97
+ "anthropic-version": "2023-06-01"
98
+ },
99
+ body: JSON.stringify({
100
+ model: this.config.model,
101
+ max_tokens: this.config.maxTokens,
102
+ system: systemPrompt,
103
+ messages: [{ role: "user", content: userInput }]
104
+ })
105
+ },
106
+ signal
107
+ );
108
+ if (!response.ok) {
109
+ throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
110
+ }
111
+ const data = await response.json();
112
+ const textBlock = data.content?.find((block) => block.type === "text");
113
+ return textBlock?.text ?? "";
114
+ }
115
+ async completeWithTools(systemPrompt, messages, tools, signal) {
116
+ const anthropicTools = tools.map((tool) => ({
117
+ name: tool.name,
118
+ description: tool.description,
119
+ input_schema: {
120
+ type: "object",
121
+ properties: Object.fromEntries(
122
+ tool.parameters.map((param) => [
123
+ param.name,
124
+ { type: "string", description: param.description }
125
+ ])
126
+ ),
127
+ required: tool.parameters.filter((param) => param.required).map((param) => param.name)
128
+ }
129
+ }));
130
+ const response = await fetchWithRetry(
131
+ "https://api.anthropic.com/v1/messages",
132
+ {
133
+ method: "POST",
134
+ headers: {
135
+ "Content-Type": "application/json",
136
+ "x-api-key": this.config.apiKey,
137
+ "anthropic-version": "2023-06-01"
138
+ },
139
+ body: JSON.stringify({
140
+ model: this.config.model,
141
+ max_tokens: this.config.maxTokens,
142
+ system: systemPrompt,
143
+ messages,
144
+ tools: anthropicTools
145
+ })
146
+ },
147
+ signal
148
+ );
149
+ if (!response.ok) {
150
+ throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
151
+ }
152
+ const data = await response.json();
153
+ const content = data.content ?? [];
154
+ const toolUses = content.filter((block) => block.type === "tool_use");
155
+ if (toolUses.length > 0) {
156
+ const calls = toolUses.map((block) => ({
157
+ id: block.id ?? "",
158
+ name: block.name ?? "",
159
+ arguments: block.input ?? {}
160
+ }));
161
+ return {
162
+ type: "tool_use",
163
+ calls,
164
+ assistantMessage: { role: "assistant", content }
165
+ };
166
+ }
167
+ const textBlock = content.find((block) => block.type === "text");
168
+ return { type: "text", text: textBlock?.text ?? "" };
169
+ }
170
+ formatToolResultMessages(results) {
171
+ return [
172
+ {
173
+ role: "user",
174
+ content: results.map((result) => ({
175
+ type: "tool_result",
176
+ tool_use_id: result.callId,
177
+ content: result.content
178
+ }))
179
+ }
180
+ ];
181
+ }
182
+ };
183
+ var OpenAIClient = class {
184
+ constructor(config) {
185
+ this.config = config;
186
+ }
187
+ isReasoningModel() {
188
+ return /^o\d/.test(this.config.model);
189
+ }
190
+ async complete(systemPrompt, userInput, signal) {
191
+ const reasoning = this.isReasoningModel();
192
+ const response = await fetchWithRetry(
193
+ "https://api.openai.com/v1/chat/completions",
194
+ {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ Authorization: `Bearer ${this.config.apiKey}`
199
+ },
200
+ body: JSON.stringify({
201
+ model: this.config.model,
202
+ ...reasoning ? { max_completion_tokens: this.config.maxTokens } : { max_tokens: this.config.maxTokens },
203
+ messages: [
204
+ { role: reasoning ? "developer" : "system", content: systemPrompt },
205
+ { role: "user", content: userInput }
206
+ ]
207
+ })
208
+ },
209
+ signal
210
+ );
211
+ if (!response.ok) {
212
+ throw new Error(`OpenAI API error: ${response.status} ${await response.text()}`);
213
+ }
214
+ const data = await response.json();
215
+ return data.choices?.[0]?.message?.content ?? "";
216
+ }
217
+ async completeWithTools(systemPrompt, messages, tools, signal) {
218
+ const openaiTools = tools.map((tool) => ({
219
+ type: "function",
220
+ function: {
221
+ name: tool.name,
222
+ description: tool.description,
223
+ parameters: {
224
+ type: "object",
225
+ properties: Object.fromEntries(
226
+ tool.parameters.map((param) => [
227
+ param.name,
228
+ { type: "string", description: param.description }
229
+ ])
230
+ ),
231
+ required: tool.parameters.filter((param) => param.required).map((param) => param.name)
232
+ }
233
+ }
234
+ }));
235
+ const reasoning = this.isReasoningModel();
236
+ const response = await fetchWithRetry(
237
+ "https://api.openai.com/v1/chat/completions",
238
+ {
239
+ method: "POST",
240
+ headers: {
241
+ "Content-Type": "application/json",
242
+ Authorization: `Bearer ${this.config.apiKey}`
243
+ },
244
+ body: JSON.stringify({
245
+ model: this.config.model,
246
+ ...reasoning ? { max_completion_tokens: this.config.maxTokens } : { max_tokens: this.config.maxTokens },
247
+ messages: [
248
+ { role: reasoning ? "developer" : "system", content: systemPrompt },
249
+ ...messages
250
+ ],
251
+ tools: openaiTools
252
+ })
253
+ },
254
+ signal
255
+ );
256
+ if (!response.ok) {
257
+ throw new Error(`OpenAI API error: ${response.status} ${await response.text()}`);
258
+ }
259
+ const data = await response.json();
260
+ const message = data.choices?.[0]?.message;
261
+ const toolCalls = message?.tool_calls ?? [];
262
+ if (toolCalls.length > 0) {
263
+ const calls = toolCalls.map((call) => {
264
+ let args;
265
+ try {
266
+ args = JSON.parse(call.function?.arguments ?? "{}");
267
+ } catch {
268
+ args = {};
269
+ }
270
+ return { id: call.id ?? "", name: call.function?.name ?? "", arguments: args };
271
+ });
272
+ return { type: "tool_use", calls, assistantMessage: message };
273
+ }
274
+ return { type: "text", text: message?.content ?? "" };
275
+ }
276
+ formatToolResultMessages(results) {
277
+ return results.map((result) => ({
278
+ role: "tool",
279
+ tool_call_id: result.callId,
280
+ content: result.content
281
+ }));
282
+ }
283
+ };
284
+ function createAnthropicClient(config) {
285
+ if (!config.apiKey) {
286
+ throw new Error("ANTHROPIC_API_KEY is required for skill runtime");
287
+ }
288
+ return new AnthropicClient({
289
+ apiKey: config.apiKey,
290
+ model: config.model ?? DEFAULT_ANTHROPIC_MODEL,
291
+ maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS
292
+ });
293
+ }
294
+ function createOpenAIClient(config) {
295
+ if (!config.apiKey) {
296
+ throw new Error("OPENAI_API_KEY is required for skill runtime");
297
+ }
298
+ return new OpenAIClient({
299
+ apiKey: config.apiKey,
300
+ model: config.model ?? DEFAULT_OPENAI_MODEL,
301
+ maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS
302
+ });
303
+ }
304
+ function createLlmClient(config) {
305
+ if (config.provider === "openai") {
306
+ return createOpenAIClient(config);
307
+ }
308
+ return createAnthropicClient(config);
309
+ }
310
+ var MAX_TOOL_OUTPUT = 1e6;
311
+ var TOOL_TIMEOUT_MS = 6e4;
312
+ var ScriptSkill = class {
313
+ name;
314
+ description;
315
+ capabilities;
316
+ priceLamports;
317
+ image;
318
+ imageFile;
319
+ skillDir;
320
+ systemPrompt;
321
+ tools;
322
+ maxToolRounds;
323
+ logger;
324
+ constructor(params) {
325
+ this.name = params.name;
326
+ this.description = params.description;
327
+ this.capabilities = params.capabilities;
328
+ this.priceLamports = params.priceLamports;
329
+ this.image = params.image;
330
+ this.imageFile = params.imageFile;
331
+ this.skillDir = params.skillDir;
332
+ this.systemPrompt = params.systemPrompt;
333
+ this.tools = params.tools;
334
+ this.maxToolRounds = params.maxToolRounds;
335
+ this.logger = params.logger ?? {};
336
+ }
337
+ async execute(input, ctx) {
338
+ if (!ctx.llm) {
339
+ throw new Error("LLM client not configured for skill runtime");
340
+ }
341
+ if (this.tools.length === 0) {
342
+ const result = await ctx.llm.complete(this.systemPrompt, input.data, ctx.signal);
343
+ return { data: result };
344
+ }
345
+ const toolDefs = this.tools.map((tool) => ({
346
+ name: tool.name,
347
+ description: tool.description,
348
+ parameters: (tool.parameters ?? []).map((param) => ({
349
+ name: param.name,
350
+ description: param.description,
351
+ required: param.required ?? true
352
+ }))
353
+ }));
354
+ const messages = [{ role: "user", content: input.data }];
355
+ const llm = ctx.llm;
356
+ for (let round = 0; round < this.maxToolRounds; round++) {
357
+ if (ctx.signal?.aborted) {
358
+ throw new Error("Job aborted");
359
+ }
360
+ const result = await llm.completeWithTools(
361
+ this.systemPrompt,
362
+ messages,
363
+ toolDefs,
364
+ ctx.signal
365
+ );
366
+ if (result.type === "text") {
367
+ return { data: result.text };
368
+ }
369
+ messages.push(result.assistantMessage);
370
+ const toolResults = [];
371
+ for (const call of result.calls) {
372
+ const toolDef = this.tools.find((tool) => tool.name === call.name);
373
+ if (!toolDef) {
374
+ toolResults.push({
375
+ callId: call.id,
376
+ content: `Error: unknown tool "${call.name}"`
377
+ });
378
+ continue;
379
+ }
380
+ const output = await this.runTool(toolDef, call, ctx.signal);
381
+ toolResults.push({ callId: call.id, content: output });
382
+ }
383
+ messages.push(...llm.formatToolResultMessages(toolResults));
384
+ }
385
+ throw new Error(`Max tool rounds (${this.maxToolRounds}) exceeded`);
386
+ }
387
+ runTool(toolDef, call, signal) {
388
+ return new Promise((resolve) => {
389
+ const args = [...toolDef.command];
390
+ const cmd = args.shift();
391
+ if (!cmd) {
392
+ resolve(`Error: tool "${toolDef.name}" has an empty command`);
393
+ return;
394
+ }
395
+ const params = toolDef.parameters ?? [];
396
+ for (let index = 0; index < params.length; index++) {
397
+ const param = params[index];
398
+ if (!param) {
399
+ continue;
400
+ }
401
+ const value = call.arguments[param.name];
402
+ if (value === void 0) {
403
+ continue;
404
+ }
405
+ if (param.required && index === 0) {
406
+ args.push(String(value));
407
+ } else {
408
+ args.push(`--${param.name}`, String(value));
409
+ }
410
+ }
411
+ const child = child_process.spawn(cmd, args, {
412
+ cwd: this.skillDir,
413
+ stdio: ["pipe", "pipe", "pipe"],
414
+ timeout: TOOL_TIMEOUT_MS,
415
+ killSignal: "SIGKILL",
416
+ signal
417
+ });
418
+ let stdout = "";
419
+ let stderr = "";
420
+ const stdoutDecoder = new string_decoder.StringDecoder("utf8");
421
+ const stderrDecoder = new string_decoder.StringDecoder("utf8");
422
+ child.stdout?.on("data", (data) => {
423
+ if (stdout.length < MAX_TOOL_OUTPUT) {
424
+ stdout += stdoutDecoder.write(data);
425
+ if (stdout.length > MAX_TOOL_OUTPUT) {
426
+ stdout = stdout.slice(0, MAX_TOOL_OUTPUT);
427
+ }
428
+ }
429
+ });
430
+ child.stderr?.on("data", (data) => {
431
+ if (stderr.length < MAX_TOOL_OUTPUT) {
432
+ stderr += stderrDecoder.write(data);
433
+ if (stderr.length > MAX_TOOL_OUTPUT) {
434
+ stderr = stderr.slice(0, MAX_TOOL_OUTPUT);
435
+ }
436
+ }
437
+ });
438
+ child.on("close", (code) => {
439
+ stdout += stdoutDecoder.end();
440
+ stderr += stderrDecoder.end();
441
+ if (code === 0) {
442
+ resolve(stdout.trim());
443
+ return;
444
+ }
445
+ this.logger.debug?.(
446
+ { tool: toolDef.name, code, stderrLen: stderr.length },
447
+ "skill tool exited non-zero"
448
+ );
449
+ resolve(`Error (exit ${code}): ${stderr.trim() || stdout.trim()}`);
450
+ });
451
+ child.on("error", (err) => {
452
+ resolve(`Error: ${err.message}`);
453
+ });
454
+ });
455
+ }
456
+ };
457
+ var LAMPORTS_PER_SOL = 1e9;
458
+
459
+ // src/skills/loader.ts
460
+ var DEFAULT_MAX_TOOL_ROUNDS = 10;
461
+ function solToLamports(sol) {
462
+ const asNumber = typeof sol === "string" ? Number(sol) : sol;
463
+ if (!Number.isFinite(asNumber) || asNumber < 0) {
464
+ throw new Error(`Invalid SOL amount: ${sol}`);
465
+ }
466
+ return BigInt(Math.round(asNumber * LAMPORTS_PER_SOL));
467
+ }
468
+ function parseSkillMd(content) {
469
+ const lines = content.split("\n");
470
+ let start = -1;
471
+ let end = -1;
472
+ for (let index = 0; index < lines.length; index++) {
473
+ if (lines[index]?.trim() === "---") {
474
+ if (start === -1) {
475
+ start = index;
476
+ } else {
477
+ end = index;
478
+ break;
479
+ }
480
+ }
481
+ }
482
+ if (start === -1 || end === -1) {
483
+ throw new Error("SKILL.md must have YAML frontmatter between --- delimiters");
484
+ }
485
+ const yamlStr = lines.slice(start + 1, end).join("\n");
486
+ const frontmatter = YAML__default.default.parse(yamlStr);
487
+ const systemPrompt = lines.slice(end + 1).join("\n").trim();
488
+ return { frontmatter, systemPrompt };
489
+ }
490
+ function validateTool(raw, skillName, index) {
491
+ if (typeof raw !== "object" || raw === null) {
492
+ throw new Error(`skill "${skillName}" tool[${index}] must be an object`);
493
+ }
494
+ const tool = raw;
495
+ if (typeof tool.name !== "string" || tool.name.length === 0) {
496
+ throw new Error(`skill "${skillName}" tool[${index}] missing name`);
497
+ }
498
+ if (typeof tool.description !== "string" || tool.description.length === 0) {
499
+ throw new Error(`skill "${skillName}" tool "${tool.name}" missing description`);
500
+ }
501
+ if (!Array.isArray(tool.command) || tool.command.length === 0) {
502
+ throw new Error(`skill "${skillName}" tool "${tool.name}" missing command[] array`);
503
+ }
504
+ for (const part of tool.command) {
505
+ if (typeof part !== "string") {
506
+ throw new Error(`skill "${skillName}" tool "${tool.name}" command[] must be strings`);
507
+ }
508
+ }
509
+ const parameters = [];
510
+ if (tool.parameters !== void 0) {
511
+ if (!Array.isArray(tool.parameters)) {
512
+ throw new Error(`skill "${skillName}" tool "${tool.name}" parameters must be an array`);
513
+ }
514
+ for (let paramIndex = 0; paramIndex < tool.parameters.length; paramIndex++) {
515
+ const param = tool.parameters[paramIndex];
516
+ if (typeof param !== "object" || param === null) {
517
+ throw new Error(
518
+ `skill "${skillName}" tool "${tool.name}" parameter[${paramIndex}] must be an object`
519
+ );
520
+ }
521
+ const record = param;
522
+ if (typeof record.name !== "string" || record.name.length === 0) {
523
+ throw new Error(
524
+ `skill "${skillName}" tool "${tool.name}" parameter[${paramIndex}] missing name`
525
+ );
526
+ }
527
+ if (typeof record.description !== "string") {
528
+ throw new Error(
529
+ `skill "${skillName}" tool "${tool.name}" parameter "${record.name}" missing description`
530
+ );
531
+ }
532
+ parameters.push({
533
+ name: record.name,
534
+ description: record.description,
535
+ required: record.required === void 0 ? void 0 : Boolean(record.required)
536
+ });
537
+ }
538
+ }
539
+ return {
540
+ name: tool.name,
541
+ description: tool.description,
542
+ command: tool.command,
543
+ parameters
544
+ };
545
+ }
546
+ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
547
+ if (typeof frontmatter.name !== "string" || frontmatter.name.length === 0) {
548
+ throw new Error('SKILL.md: missing or invalid "name" field');
549
+ }
550
+ if (typeof frontmatter.description !== "string" || frontmatter.description.length === 0) {
551
+ throw new Error('SKILL.md: missing or invalid "description" field');
552
+ }
553
+ if (!Array.isArray(frontmatter.capabilities) || frontmatter.capabilities.length === 0) {
554
+ throw new Error('SKILL.md: "capabilities" must be a non-empty array');
555
+ }
556
+ const capabilities = [];
557
+ for (const capability of frontmatter.capabilities) {
558
+ if (typeof capability !== "string" || capability.length === 0) {
559
+ throw new Error(
560
+ `SKILL.md "${frontmatter.name}": capability entries must be non-empty strings`
561
+ );
562
+ }
563
+ capabilities.push(capability);
564
+ }
565
+ let priceLamports;
566
+ if (frontmatter.price === void 0 || frontmatter.price === null) {
567
+ if (!options.allowFreeSkills) {
568
+ throw new Error(
569
+ `SKILL.md "${frontmatter.name}": "price" is required (SOL; e.g. 0.002). Free skills are not supported on the protocol yet.`
570
+ );
571
+ }
572
+ priceLamports = 0n;
573
+ } else {
574
+ const priceRaw = frontmatter.price;
575
+ if (typeof priceRaw !== "number" && typeof priceRaw !== "string") {
576
+ throw new Error(`SKILL.md "${frontmatter.name}": "price" must be a number or numeric string`);
577
+ }
578
+ priceLamports = solToLamports(priceRaw);
579
+ if (priceLamports <= 0n && !options.allowFreeSkills) {
580
+ throw new Error(
581
+ `SKILL.md "${frontmatter.name}": price must be > 0 SOL (got ${priceRaw}); free skills are not yet supported`
582
+ );
583
+ }
584
+ }
585
+ const tools = [];
586
+ if (frontmatter.tools !== void 0) {
587
+ if (!Array.isArray(frontmatter.tools)) {
588
+ throw new Error(`SKILL.md "${frontmatter.name}": "tools" must be an array`);
589
+ }
590
+ for (let index = 0; index < frontmatter.tools.length; index++) {
591
+ tools.push(validateTool(frontmatter.tools[index], frontmatter.name, index));
592
+ }
593
+ }
594
+ let maxToolRounds = DEFAULT_MAX_TOOL_ROUNDS;
595
+ if (frontmatter.max_tool_rounds !== void 0) {
596
+ if (typeof frontmatter.max_tool_rounds !== "number" || !Number.isInteger(frontmatter.max_tool_rounds) || frontmatter.max_tool_rounds <= 0) {
597
+ throw new Error(
598
+ `SKILL.md "${frontmatter.name}": "max_tool_rounds" must be a positive integer`
599
+ );
600
+ }
601
+ maxToolRounds = frontmatter.max_tool_rounds;
602
+ }
603
+ const image = typeof frontmatter.image === "string" ? frontmatter.image : void 0;
604
+ const imageFile = typeof frontmatter.image_file === "string" ? frontmatter.image_file : void 0;
605
+ return {
606
+ name: frontmatter.name,
607
+ description: frontmatter.description,
608
+ capabilities,
609
+ priceLamports,
610
+ systemPrompt,
611
+ tools,
612
+ maxToolRounds,
613
+ image,
614
+ imageFile
615
+ };
616
+ }
617
+ function loadSkillsFromDir(skillsDir, options = {}) {
618
+ const logger = options.logger ?? {};
619
+ const skills = [];
620
+ let entries;
621
+ try {
622
+ entries = fs.readdirSync(skillsDir);
623
+ } catch (error) {
624
+ logger.debug?.({ err: error, skillsDir }, "skills directory not readable; no skills loaded");
625
+ return skills;
626
+ }
627
+ for (const entry of entries) {
628
+ const entryPath = path.join(skillsDir, entry);
629
+ try {
630
+ if (!fs.statSync(entryPath).isDirectory()) {
631
+ continue;
632
+ }
633
+ } catch {
634
+ continue;
635
+ }
636
+ const skillMdPath = path.join(entryPath, "SKILL.md");
637
+ try {
638
+ const content = fs.readFileSync(skillMdPath, "utf-8");
639
+ const { frontmatter, systemPrompt } = parseSkillMd(content);
640
+ const parsed = validateSkillFrontmatter(frontmatter, systemPrompt, options);
641
+ skills.push(
642
+ new ScriptSkill({
643
+ name: parsed.name,
644
+ description: parsed.description,
645
+ capabilities: parsed.capabilities,
646
+ priceLamports: parsed.priceLamports,
647
+ skillDir: entryPath,
648
+ systemPrompt: parsed.systemPrompt,
649
+ tools: parsed.tools,
650
+ maxToolRounds: parsed.maxToolRounds,
651
+ image: parsed.image,
652
+ imageFile: parsed.imageFile,
653
+ logger
654
+ })
655
+ );
656
+ } catch (error) {
657
+ const message = error instanceof Error ? error.message : String(error);
658
+ logger.warn?.({ dir: entry, err: message }, "skipping malformed skill directory");
659
+ }
660
+ }
661
+ return skills;
662
+ }
663
+
664
+ exports.DEFAULT_MAX_TOOL_ROUNDS = DEFAULT_MAX_TOOL_ROUNDS;
665
+ exports.ScriptSkill = ScriptSkill;
666
+ exports.createAnthropicClient = createAnthropicClient;
667
+ exports.createLlmClient = createLlmClient;
668
+ exports.createOpenAIClient = createOpenAIClient;
669
+ exports.loadSkillsFromDir = loadSkillsFromDir;
670
+ exports.parseSkillMd = parseSkillMd;
671
+ exports.validateSkillFrontmatter = validateSkillFrontmatter;
672
+ //# sourceMappingURL=skills.cjs.map
673
+ //# sourceMappingURL=skills.cjs.map