@vertesia/build-tools 0.80.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.
Files changed (76) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +334 -0
  3. package/lib/build-tools.js +616 -0
  4. package/lib/build-tools.js.map +1 -0
  5. package/lib/cjs/index.js +34 -0
  6. package/lib/cjs/index.js.map +1 -0
  7. package/lib/cjs/package.json +3 -0
  8. package/lib/cjs/parsers/frontmatter.js +25 -0
  9. package/lib/cjs/parsers/frontmatter.js.map +1 -0
  10. package/lib/cjs/plugin.js +142 -0
  11. package/lib/cjs/plugin.js.map +1 -0
  12. package/lib/cjs/presets/index.js +12 -0
  13. package/lib/cjs/presets/index.js.map +1 -0
  14. package/lib/cjs/presets/raw.js +25 -0
  15. package/lib/cjs/presets/raw.js.map +1 -0
  16. package/lib/cjs/presets/skill.js +224 -0
  17. package/lib/cjs/presets/skill.js.map +1 -0
  18. package/lib/cjs/types.js +6 -0
  19. package/lib/cjs/types.js.map +1 -0
  20. package/lib/cjs/utils/asset-copy.js +61 -0
  21. package/lib/cjs/utils/asset-copy.js.map +1 -0
  22. package/lib/cjs/utils/asset-discovery.js +100 -0
  23. package/lib/cjs/utils/asset-discovery.js.map +1 -0
  24. package/lib/cjs/utils/widget-compiler.js +115 -0
  25. package/lib/cjs/utils/widget-compiler.js.map +1 -0
  26. package/lib/esm/index.js +26 -0
  27. package/lib/esm/index.js.map +1 -0
  28. package/lib/esm/parsers/frontmatter.js +19 -0
  29. package/lib/esm/parsers/frontmatter.js.map +1 -0
  30. package/lib/esm/plugin.js +136 -0
  31. package/lib/esm/plugin.js.map +1 -0
  32. package/lib/esm/presets/index.js +6 -0
  33. package/lib/esm/presets/index.js.map +1 -0
  34. package/lib/esm/presets/raw.js +22 -0
  35. package/lib/esm/presets/raw.js.map +1 -0
  36. package/lib/esm/presets/skill.js +221 -0
  37. package/lib/esm/presets/skill.js.map +1 -0
  38. package/lib/esm/types.js +5 -0
  39. package/lib/esm/types.js.map +1 -0
  40. package/lib/esm/utils/asset-copy.js +54 -0
  41. package/lib/esm/utils/asset-copy.js.map +1 -0
  42. package/lib/esm/utils/asset-discovery.js +94 -0
  43. package/lib/esm/utils/asset-discovery.js.map +1 -0
  44. package/lib/esm/utils/widget-compiler.js +76 -0
  45. package/lib/esm/utils/widget-compiler.js.map +1 -0
  46. package/lib/types/index.d.ts +24 -0
  47. package/lib/types/index.d.ts.map +1 -0
  48. package/lib/types/parsers/frontmatter.d.ts +19 -0
  49. package/lib/types/parsers/frontmatter.d.ts.map +1 -0
  50. package/lib/types/plugin.d.ts +10 -0
  51. package/lib/types/plugin.d.ts.map +1 -0
  52. package/lib/types/presets/index.d.ts +6 -0
  53. package/lib/types/presets/index.d.ts.map +1 -0
  54. package/lib/types/presets/raw.d.ts +16 -0
  55. package/lib/types/presets/raw.d.ts.map +1 -0
  56. package/lib/types/presets/skill.d.ts +139 -0
  57. package/lib/types/presets/skill.d.ts.map +1 -0
  58. package/lib/types/types.d.ts +113 -0
  59. package/lib/types/types.d.ts.map +1 -0
  60. package/lib/types/utils/asset-copy.d.ts +20 -0
  61. package/lib/types/utils/asset-copy.d.ts.map +1 -0
  62. package/lib/types/utils/asset-discovery.d.ts +38 -0
  63. package/lib/types/utils/asset-discovery.d.ts.map +1 -0
  64. package/lib/types/utils/widget-compiler.d.ts +15 -0
  65. package/lib/types/utils/widget-compiler.d.ts.map +1 -0
  66. package/package.json +67 -0
  67. package/src/index.ts +45 -0
  68. package/src/parsers/frontmatter.ts +32 -0
  69. package/src/plugin.ts +158 -0
  70. package/src/presets/index.ts +6 -0
  71. package/src/presets/raw.ts +24 -0
  72. package/src/presets/skill.ts +271 -0
  73. package/src/types.ts +137 -0
  74. package/src/utils/asset-copy.ts +63 -0
  75. package/src/utils/asset-discovery.ts +138 -0
  76. package/src/utils/widget-compiler.ts +98 -0
@@ -0,0 +1,616 @@
1
+ import { copyFileSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { rollup } from 'rollup';
4
+ import { z } from 'zod';
5
+ import matter from 'gray-matter';
6
+
7
+ /**
8
+ * Utilities for copying asset files during build
9
+ */
10
+ /**
11
+ * Ensure a directory exists, creating it recursively if needed
12
+ */
13
+ function ensureDirectory(dirPath) {
14
+ try {
15
+ mkdirSync(dirPath, { recursive: true });
16
+ }
17
+ catch (error) {
18
+ // Ignore if directory already exists
19
+ if (error.code !== 'EEXIST') {
20
+ throw error;
21
+ }
22
+ }
23
+ }
24
+ /**
25
+ * Copy an asset file to its destination
26
+ *
27
+ * @param asset - Asset file information
28
+ * @param assetsRoot - Root directory for assets
29
+ */
30
+ function copyAssetFile(asset, assetsRoot) {
31
+ const destPath = path.join(assetsRoot, asset.destPath);
32
+ const destDir = path.dirname(destPath);
33
+ // Ensure destination directory exists
34
+ ensureDirectory(destDir);
35
+ // Copy file
36
+ try {
37
+ copyFileSync(asset.sourcePath, destPath);
38
+ }
39
+ catch (error) {
40
+ throw new Error(`Failed to copy asset from ${asset.sourcePath} to ${destPath}: ${error instanceof Error ? error.message : String(error)}`);
41
+ }
42
+ }
43
+ /**
44
+ * Copy multiple asset files
45
+ *
46
+ * @param assets - Array of asset files to copy
47
+ * @param assetsRoot - Root directory for assets
48
+ * @returns Number of files copied
49
+ */
50
+ function copyAssets(assets, assetsRoot) {
51
+ let copied = 0;
52
+ for (const asset of assets) {
53
+ copyAssetFile(asset, assetsRoot);
54
+ copied++;
55
+ }
56
+ return copied;
57
+ }
58
+
59
+ /**
60
+ * Widget compilation utility using Rollup
61
+ */
62
+ /**
63
+ * Default external dependencies for widgets
64
+ */
65
+ const DEFAULT_EXTERNALS = [
66
+ 'react',
67
+ 'react-dom',
68
+ 'react/jsx-runtime',
69
+ 'react/jsx-dev-runtime',
70
+ 'react-dom/client'
71
+ ];
72
+ /**
73
+ * Compile widgets using Rollup
74
+ *
75
+ * @param widgets - Array of widget metadata to compile
76
+ * @param outputDir - Directory to write compiled widgets
77
+ * @param config - Widget compilation configuration
78
+ * @returns Number of widgets compiled
79
+ */
80
+ async function compileWidgets(widgets, outputDir, config = {}) {
81
+ if (widgets.length === 0) {
82
+ return 0;
83
+ }
84
+ const { external = DEFAULT_EXTERNALS, tsconfig = './tsconfig.json', typescript: typescriptOptions = {}, minify = false } = config;
85
+ // Build each widget separately to get individual bundles
86
+ const buildPromises = widgets.map(async (widget) => {
87
+ // Dynamically import plugins - use any to bypass TypeScript module resolution issues
88
+ const typescript = (await import('@rollup/plugin-typescript')).default;
89
+ const nodeResolve = (await import('@rollup/plugin-node-resolve')).default;
90
+ const commonjs = (await import('@rollup/plugin-commonjs')).default;
91
+ const plugins = [
92
+ typescript({
93
+ tsconfig,
94
+ declaration: false,
95
+ sourceMap: true,
96
+ ...typescriptOptions
97
+ }),
98
+ nodeResolve({
99
+ browser: true,
100
+ preferBuiltins: false,
101
+ extensions: ['.tsx', '.ts', '.jsx', '.js']
102
+ }),
103
+ commonjs()
104
+ ];
105
+ // Add minification if requested
106
+ if (minify) {
107
+ const { terser } = await import('rollup-plugin-terser');
108
+ plugins.push(terser({
109
+ compress: {
110
+ drop_console: false
111
+ }
112
+ }));
113
+ }
114
+ const rollupConfig = {
115
+ input: widget.path,
116
+ output: {
117
+ file: path.join(outputDir, `${widget.name}.js`),
118
+ format: 'es',
119
+ sourcemap: true,
120
+ inlineDynamicImports: true
121
+ },
122
+ external,
123
+ plugins
124
+ };
125
+ const bundle = await rollup(rollupConfig);
126
+ await bundle.write(rollupConfig.output);
127
+ await bundle.close();
128
+ });
129
+ await Promise.all(buildPromises);
130
+ return widgets.length;
131
+ }
132
+
133
+ /**
134
+ * Core Rollup plugin implementation for transforming imports
135
+ */
136
+ /**
137
+ * Creates a Rollup plugin that transforms imports based on configured rules
138
+ */
139
+ function vertesiaImportPlugin(config) {
140
+ const { transformers, assetsDir = './dist', widgetConfig } = config;
141
+ if (!transformers || transformers.length === 0) {
142
+ throw new Error('vertesiaImportPlugin: At least one transformer must be configured');
143
+ }
144
+ // Track assets to copy and widgets to compile
145
+ const assetsToProcess = [];
146
+ const widgetsToCompile = [];
147
+ const shouldCopyAssets = assetsDir !== false;
148
+ const shouldCompileWidgets = widgetConfig !== undefined && assetsDir !== false;
149
+ return {
150
+ name: 'vertesia-import-plugin',
151
+ /**
152
+ * Resolve import IDs to handle pattern-based imports
153
+ */
154
+ resolveId(source, importer) {
155
+ // Check if any transformer pattern matches
156
+ for (const transformer of transformers) {
157
+ if (transformer.pattern.test(source)) {
158
+ // Handle relative imports
159
+ if (source.startsWith('.') && importer) {
160
+ const cleanSource = source.replace(transformer.pattern, '');
161
+ const resolved = path.resolve(path.dirname(importer), cleanSource);
162
+ // Return with the pattern suffix to identify it in load
163
+ const suffix = source.match(transformer.pattern)?.[0] || '';
164
+ return resolved + suffix;
165
+ }
166
+ return source;
167
+ }
168
+ }
169
+ return null; // Let other plugins handle it
170
+ },
171
+ /**
172
+ * Load and transform the file content
173
+ */
174
+ async load(id) {
175
+ // Find matching transformer
176
+ let matchedTransformer;
177
+ let cleanId = id;
178
+ for (const transformer of transformers) {
179
+ if (transformer.pattern.test(id)) {
180
+ matchedTransformer = transformer;
181
+ // Remove query parameters to get actual file path
182
+ // For example: '/path/file.md?skill' -> '/path/file.md'
183
+ // '/path/file.html?raw' -> '/path/file.html'
184
+ const queryIndex = id.indexOf('?');
185
+ cleanId = queryIndex >= 0 ? id.substring(0, queryIndex) : id;
186
+ break;
187
+ }
188
+ }
189
+ if (!matchedTransformer) {
190
+ return null; // Not for us
191
+ }
192
+ try {
193
+ // Read file content
194
+ const content = readFileSync(cleanId, 'utf-8');
195
+ // Transform the content
196
+ const result = await matchedTransformer.transform(content, cleanId);
197
+ // Collect assets if any
198
+ if (result.assets && shouldCopyAssets) {
199
+ assetsToProcess.push(...result.assets);
200
+ }
201
+ // Collect widgets if any
202
+ if (result.widgets && shouldCompileWidgets) {
203
+ widgetsToCompile.push(...result.widgets);
204
+ }
205
+ // Validate if schema provided
206
+ if (matchedTransformer.schema) {
207
+ const validation = matchedTransformer.schema.safeParse(result.data);
208
+ if (!validation.success) {
209
+ const errors = validation.error.errors
210
+ .map((err) => ` - ${err.path.join('.')}: ${err.message}`)
211
+ .join('\n');
212
+ throw new Error(`Validation failed for ${id}:\n${errors}`);
213
+ }
214
+ }
215
+ // Generate code
216
+ if (result.code) {
217
+ // Custom code provided
218
+ return result.code;
219
+ }
220
+ else {
221
+ // Default: export data (escape if string, otherwise stringify as JSON)
222
+ const imports = result.imports ? result.imports.join('\n') + '\n\n' : '';
223
+ const dataJson = JSON.stringify(result.data, null, 2);
224
+ return `${imports}export default ${dataJson};`;
225
+ }
226
+ }
227
+ catch (error) {
228
+ const message = error instanceof Error ? error.message : String(error);
229
+ this.error(`Failed to transform ${id}: ${message}`);
230
+ }
231
+ },
232
+ /**
233
+ * Copy assets and compile widgets after all modules are loaded
234
+ */
235
+ async buildEnd() {
236
+ // Copy script assets
237
+ if (shouldCopyAssets && assetsToProcess.length > 0) {
238
+ try {
239
+ const copied = copyAssets(assetsToProcess, assetsDir);
240
+ console.log(`Copied ${copied} asset file(s) to ${assetsDir}`);
241
+ }
242
+ catch (error) {
243
+ const message = error instanceof Error ? error.message : String(error);
244
+ this.warn(`Failed to copy assets: ${message}`);
245
+ }
246
+ }
247
+ // Compile widgets
248
+ if (shouldCompileWidgets && widgetsToCompile.length > 0) {
249
+ try {
250
+ const widgetsDir = config.widgetsDir || 'widgets';
251
+ const outputDir = path.join(assetsDir, widgetsDir);
252
+ console.log(`Compiling ${widgetsToCompile.length} widget(s)...`);
253
+ const compiled = await compileWidgets(widgetsToCompile, outputDir, widgetConfig);
254
+ console.log(`Compiled ${compiled} widget(s) to ${outputDir}`);
255
+ }
256
+ catch (error) {
257
+ const message = error instanceof Error ? error.message : String(error);
258
+ this.error(`Failed to compile widgets: ${message}`);
259
+ }
260
+ }
261
+ }
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Frontmatter parser utility using gray-matter
267
+ */
268
+ /**
269
+ * Parse YAML frontmatter from markdown content
270
+ *
271
+ * @param content - Raw markdown content with optional frontmatter
272
+ * @returns Parsed frontmatter and content
273
+ */
274
+ function parseFrontmatter(content) {
275
+ const result = matter(content);
276
+ return {
277
+ frontmatter: result.data,
278
+ content: result.content,
279
+ original: content
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Utilities for discovering asset files (scripts, widgets) in skill directories
285
+ */
286
+ /**
287
+ * Check if a file exists and is a regular file
288
+ */
289
+ function isFile(filePath) {
290
+ try {
291
+ return statSync(filePath).isFile();
292
+ }
293
+ catch {
294
+ return false;
295
+ }
296
+ }
297
+ /**
298
+ * Get all files in a directory (non-recursive)
299
+ */
300
+ function getFilesInDirectory(dirPath) {
301
+ try {
302
+ return readdirSync(dirPath).filter(file => {
303
+ const fullPath = path.join(dirPath, file);
304
+ return isFile(fullPath);
305
+ });
306
+ }
307
+ catch {
308
+ return [];
309
+ }
310
+ }
311
+ /**
312
+ * Check if a file is a script file (.js or .py)
313
+ */
314
+ function isScriptFile(fileName) {
315
+ return /\.(js|py)$/.test(fileName);
316
+ }
317
+ /**
318
+ * Check if a file is a widget file (.tsx)
319
+ */
320
+ function isWidgetFile(fileName) {
321
+ return /\.tsx$/.test(fileName);
322
+ }
323
+ /**
324
+ * Extract widget name from .tsx file (remove extension)
325
+ */
326
+ function getWidgetName(fileName) {
327
+ return fileName.replace(/\.tsx$/, '');
328
+ }
329
+ /**
330
+ * Discover assets (scripts and widgets) in a skill directory
331
+ *
332
+ * @param skillFilePath - Absolute path to the skill.md file
333
+ * @param options - Asset discovery options
334
+ * @returns Discovered assets and metadata
335
+ */
336
+ function discoverSkillAssets(skillFilePath, options = {}) {
337
+ const skillDir = path.dirname(skillFilePath);
338
+ const files = getFilesInDirectory(skillDir);
339
+ const scripts = [];
340
+ const widgets = [];
341
+ const widgetMetadata = [];
342
+ const assetFiles = [];
343
+ const scriptsDir = options.scriptsDir || 'scripts';
344
+ for (const file of files) {
345
+ const fullPath = path.join(skillDir, file);
346
+ if (isScriptFile(file)) {
347
+ // Script file (.js or .py)
348
+ scripts.push(file);
349
+ assetFiles.push({
350
+ sourcePath: fullPath,
351
+ destPath: path.join(scriptsDir, file),
352
+ type: 'script'
353
+ });
354
+ }
355
+ else if (isWidgetFile(file)) {
356
+ // Widget file (.tsx)
357
+ const widgetName = getWidgetName(file);
358
+ widgets.push(widgetName);
359
+ widgetMetadata.push({
360
+ name: widgetName,
361
+ path: fullPath
362
+ });
363
+ // Note: We don't add widget .tsx files to assetFiles
364
+ // Widgets are compiled by the plugin if widgetConfig is provided
365
+ }
366
+ }
367
+ return {
368
+ scripts,
369
+ widgets,
370
+ widgetMetadata,
371
+ assetFiles
372
+ };
373
+ }
374
+
375
+ /**
376
+ * Skill transformer preset for markdown files with frontmatter
377
+ */
378
+ /**
379
+ * Context triggers for auto-injection of skills (for frontmatter validation)
380
+ */
381
+ const SkillContextTriggersFrontmatterSchema = z.object({
382
+ keywords: z.array(z.string()).optional(),
383
+ tool_names: z.array(z.string()).optional(),
384
+ data_patterns: z.array(z.string()).optional()
385
+ }).strict();
386
+ /**
387
+ * Context triggers for auto-injection of skills (for output validation)
388
+ */
389
+ const SkillContextTriggersSchema = z.object({
390
+ keywords: z.array(z.string()).optional(),
391
+ tool_names: z.array(z.string()).optional(),
392
+ data_patterns: z.array(z.string()).optional()
393
+ }).optional();
394
+ /**
395
+ * Execution configuration for skills that need code execution (for frontmatter validation)
396
+ */
397
+ const SkillExecutionFrontmatterSchema = z.object({
398
+ language: z.string(),
399
+ packages: z.array(z.string()).optional(),
400
+ system_packages: z.array(z.string()).optional(),
401
+ template: z.string().optional()
402
+ }).strict();
403
+ /**
404
+ * Execution configuration for skills that need code execution (for output validation)
405
+ */
406
+ const SkillExecutionSchema = z.object({
407
+ language: z.string(),
408
+ packages: z.array(z.string()).optional(),
409
+ system_packages: z.array(z.string()).optional(),
410
+ template: z.string().optional()
411
+ }).optional();
412
+ /**
413
+ * Zod schema for skill frontmatter validation
414
+ * This validates the YAML frontmatter before transformation
415
+ * Supports both flat and nested structures
416
+ */
417
+ const SkillFrontmatterSchema = z.object({
418
+ // Required fields
419
+ name: z.string().min(1, 'Skill name is required'),
420
+ description: z.string().min(1, 'Skill description is required'),
421
+ // Optional fields
422
+ title: z.string().optional(),
423
+ content_type: z.enum(['md', 'jst']).optional(),
424
+ // Flat structure fields (legacy)
425
+ keywords: z.array(z.string()).optional(),
426
+ tools: z.array(z.string()).optional(),
427
+ data_patterns: z.array(z.string()).optional(),
428
+ language: z.string().optional(),
429
+ packages: z.array(z.string()).optional(),
430
+ system_packages: z.array(z.string()).optional(),
431
+ // Nested structure fields
432
+ context_triggers: SkillContextTriggersFrontmatterSchema.optional(),
433
+ execution: SkillExecutionFrontmatterSchema.optional(),
434
+ related_tools: z.array(z.string()).optional(),
435
+ input_schema: z.object({
436
+ type: z.literal('object'),
437
+ properties: z.record(z.any()).optional(),
438
+ required: z.array(z.string()).optional()
439
+ }).optional(),
440
+ // Asset fields (auto-discovered but can be overridden)
441
+ scripts: z.array(z.string()).optional(),
442
+ widgets: z.array(z.string()).optional()
443
+ }).strict();
444
+ /**
445
+ * MUST be kept in sync with @vertesia/tools-sdk SkillDefinition
446
+ * Zod schema for skill definition
447
+ * This validates the structure of skill objects generated from markdown
448
+ * Matches the SkillDefinition interface from @vertesia/tools-sdk
449
+ */
450
+ const SkillDefinitionSchema = z.object({
451
+ name: z.string().min(1, 'Skill name is required'),
452
+ title: z.string().optional(),
453
+ description: z.string().min(1, 'Skill description is required'),
454
+ instructions: z.string(),
455
+ content_type: z.enum(['md', 'jst']),
456
+ input_schema: z.object({
457
+ type: z.literal('object'),
458
+ properties: z.record(z.any()).optional(),
459
+ required: z.array(z.string()).optional()
460
+ }).optional(),
461
+ context_triggers: SkillContextTriggersSchema,
462
+ execution: SkillExecutionSchema,
463
+ related_tools: z.array(z.string()).optional(),
464
+ scripts: z.array(z.string()).optional(),
465
+ widgets: z.array(z.string()).optional()
466
+ });
467
+ /**
468
+ * Build a SkillDefinition from frontmatter and markdown content.
469
+ * This mirrors the logic in @vertesia/tools-sdk parseSkillFile function.
470
+ *
471
+ * Supports two frontmatter structures:
472
+ *
473
+ * 1. Flat structure (matches parseSkillFile in tools-sdk):
474
+ * keywords: [...]
475
+ * tools: [...]
476
+ * language: python
477
+ * packages: [...]
478
+ *
479
+ * 2. Nested structure (for more explicit YAML):
480
+ * context_triggers:
481
+ * keywords: [...]
482
+ * tool_names: [...]
483
+ * execution:
484
+ * language: python
485
+ * packages: [...]
486
+ * related_tools: [...]
487
+ *
488
+ * @param frontmatter - Parsed frontmatter object
489
+ * @param instructions - Markdown content (body of the file)
490
+ * @param contentType - Content type ('md' or 'jst')
491
+ * @param widgets - Discovered widget names
492
+ * @param scripts - Discovered script names
493
+ * @returns Skill definition object
494
+ */
495
+ function buildSkillDefinition(frontmatter, instructions, contentType, widgets, scripts) {
496
+ const skill = {
497
+ name: frontmatter.name,
498
+ title: frontmatter.title,
499
+ description: frontmatter.description,
500
+ instructions,
501
+ content_type: contentType,
502
+ widgets: widgets.length > 0 ? widgets : undefined,
503
+ scripts: scripts.length > 0 ? scripts : undefined,
504
+ };
505
+ // Build context triggers - support both flat and nested structure
506
+ // Nested: context_triggers: { keywords: [...], tool_names: [...] }
507
+ // Flat: keywords: [...], tools: [...]
508
+ const contextTriggers = frontmatter.context_triggers;
509
+ const hasNestedTriggers = contextTriggers && typeof contextTriggers === 'object';
510
+ const hasFlatTriggers = frontmatter.keywords || frontmatter.tools || frontmatter.data_patterns;
511
+ if (hasNestedTriggers || hasFlatTriggers) {
512
+ skill.context_triggers = {
513
+ keywords: hasNestedTriggers ? contextTriggers.keywords : frontmatter.keywords,
514
+ tool_names: hasNestedTriggers ? contextTriggers.tool_names : frontmatter.tools,
515
+ data_patterns: hasNestedTriggers ? contextTriggers.data_patterns : frontmatter.data_patterns,
516
+ };
517
+ }
518
+ // Build execution config - support both flat and nested structure
519
+ const execution = frontmatter.execution;
520
+ const hasNestedExecution = execution && typeof execution === 'object';
521
+ const hasFlatExecution = frontmatter.language;
522
+ if (hasNestedExecution || hasFlatExecution) {
523
+ skill.execution = {
524
+ language: hasNestedExecution ? execution.language : frontmatter.language,
525
+ packages: hasNestedExecution ? execution.packages : frontmatter.packages,
526
+ system_packages: hasNestedExecution ? execution.system_packages : frontmatter.system_packages,
527
+ };
528
+ // Extract code template from instructions if present
529
+ const codeBlockMatch = instructions.match(/```(?:python|javascript|typescript|js|ts|py)\n([\s\S]*?)```/);
530
+ if (codeBlockMatch) {
531
+ skill.execution.template = codeBlockMatch[1].trim();
532
+ }
533
+ }
534
+ // Related tools - support both direct field and from tools field
535
+ if (frontmatter.related_tools) {
536
+ skill.related_tools = frontmatter.related_tools;
537
+ }
538
+ else if (frontmatter.tools && !hasNestedTriggers) {
539
+ // If tools is not part of context_triggers, use it as related_tools
540
+ skill.related_tools = frontmatter.tools;
541
+ }
542
+ // Input schema from frontmatter
543
+ if (frontmatter.input_schema) {
544
+ skill.input_schema = frontmatter.input_schema;
545
+ }
546
+ return skill;
547
+ }
548
+ /**
549
+ * Skill transformer preset
550
+ * Transforms markdown files with ?skill suffix OR SKILL.md files into skill definition objects
551
+ *
552
+ * Matches:
553
+ * - Files with ?skill suffix: ./my-skill.md?skill
554
+ * - SKILL.md files: ./my-skill/SKILL.md
555
+ *
556
+ * @example
557
+ * ```typescript
558
+ * import skill1 from './my-skill.md?skill';
559
+ * import skill2 from './my-skill/SKILL.md';
560
+ * // Both are SkillDefinition objects
561
+ * ```
562
+ */
563
+ const skillTransformer = {
564
+ pattern: /(\.md\?skill$|\/SKILL\.md$)/,
565
+ schema: SkillDefinitionSchema,
566
+ transform: (content, filePath) => {
567
+ const { frontmatter, content: markdown } = parseFrontmatter(content);
568
+ // Validate frontmatter first to catch unknown properties
569
+ const frontmatterValidation = SkillFrontmatterSchema.safeParse(frontmatter);
570
+ if (!frontmatterValidation.success) {
571
+ const errors = frontmatterValidation.error.errors
572
+ .map((err) => {
573
+ const path = err.path.length > 0 ? err.path.join('.') : 'frontmatter';
574
+ return ` - ${path}: ${err.message}`;
575
+ })
576
+ .join('\n');
577
+ throw new Error(`Invalid frontmatter in ${filePath}:\n${errors}`);
578
+ }
579
+ // Determine content type from frontmatter or file extension
580
+ const content_type = frontmatter.content_type || 'md';
581
+ // Discover assets (scripts and widgets) in the skill directory
582
+ const assets = discoverSkillAssets(filePath);
583
+ // Build skill definition using the same logic as parseSkillFile in tools-sdk
584
+ const skillData = buildSkillDefinition(frontmatter, markdown, content_type, assets.widgets, assets.scripts);
585
+ return {
586
+ data: skillData,
587
+ assets: assets.assetFiles,
588
+ widgets: assets.widgetMetadata
589
+ };
590
+ }
591
+ };
592
+
593
+ /**
594
+ * Raw transformer preset for importing file content as strings
595
+ */
596
+ /**
597
+ * Raw transformer preset
598
+ * Transforms any file with ?raw suffix into a string export
599
+ *
600
+ * @example
601
+ * ```typescript
602
+ * import template from './template.html?raw';
603
+ * // template is a string containing the file content
604
+ * ```
605
+ */
606
+ const rawTransformer = {
607
+ pattern: /\?raw$/,
608
+ transform: (content) => {
609
+ return {
610
+ data: content
611
+ };
612
+ }
613
+ };
614
+
615
+ export { SkillDefinitionSchema, parseFrontmatter, rawTransformer, skillTransformer, vertesiaImportPlugin };
616
+ //# sourceMappingURL=build-tools.js.map