lua-cli 2.5.8 → 3.0.0-alpha.4

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 (115) hide show
  1. package/dist/api/job.api.service.d.ts +219 -0
  2. package/dist/api/job.api.service.js +216 -0
  3. package/dist/api/lazy-instances.d.ts +24 -0
  4. package/dist/api/lazy-instances.js +48 -0
  5. package/dist/api/postprocessor.api.service.d.ts +158 -0
  6. package/dist/api/postprocessor.api.service.js +111 -0
  7. package/dist/api/preprocessor.api.service.d.ts +158 -0
  8. package/dist/api/preprocessor.api.service.js +111 -0
  9. package/dist/api/user.data.api.service.d.ts +13 -0
  10. package/dist/api/user.data.api.service.js +20 -0
  11. package/dist/api/webhook.api.service.d.ts +151 -0
  12. package/dist/api/webhook.api.service.js +134 -0
  13. package/dist/api-exports.d.ts +176 -41
  14. package/dist/api-exports.js +195 -21
  15. package/dist/cli/command-definitions.js +75 -5
  16. package/dist/commands/chat.js +32 -5
  17. package/dist/commands/compile.js +140 -7
  18. package/dist/commands/dev.js +23 -2
  19. package/dist/commands/index.d.ts +4 -0
  20. package/dist/commands/index.js +4 -0
  21. package/dist/commands/init.js +53 -7
  22. package/dist/commands/jobs.d.ts +20 -0
  23. package/dist/commands/jobs.js +533 -0
  24. package/dist/commands/logs.js +2 -5
  25. package/dist/commands/postprocessors.d.ts +8 -0
  26. package/dist/commands/postprocessors.js +431 -0
  27. package/dist/commands/preprocessors.d.ts +8 -0
  28. package/dist/commands/preprocessors.js +431 -0
  29. package/dist/commands/push.js +686 -5
  30. package/dist/commands/test.d.ts +9 -18
  31. package/dist/commands/test.js +574 -82
  32. package/dist/commands/webhooks.d.ts +18 -0
  33. package/dist/commands/webhooks.js +424 -0
  34. package/dist/common/job.instance.d.ts +80 -0
  35. package/dist/common/job.instance.js +116 -0
  36. package/dist/common/user.instance.d.ts +1 -0
  37. package/dist/common/user.instance.js +9 -0
  38. package/dist/config/constants.d.ts +4 -3
  39. package/dist/config/constants.js +10 -8
  40. package/dist/interfaces/agent.d.ts +2 -1
  41. package/dist/interfaces/chat.d.ts +52 -1
  42. package/dist/interfaces/index.d.ts +10 -0
  43. package/dist/interfaces/index.js +7 -0
  44. package/dist/interfaces/jobs.d.ts +193 -0
  45. package/dist/interfaces/jobs.js +5 -0
  46. package/dist/interfaces/postprocessors.d.ts +35 -0
  47. package/dist/interfaces/postprocessors.js +4 -0
  48. package/dist/interfaces/preprocessors.d.ts +35 -0
  49. package/dist/interfaces/preprocessors.js +4 -0
  50. package/dist/interfaces/webhooks.d.ts +104 -0
  51. package/dist/interfaces/webhooks.js +5 -0
  52. package/dist/types/api-contracts.d.ts +5 -0
  53. package/dist/types/compile.types.d.ts +49 -0
  54. package/dist/types/index.d.ts +1 -1
  55. package/dist/types/index.js +1 -1
  56. package/dist/types/skill.d.ts +521 -0
  57. package/dist/types/skill.js +471 -0
  58. package/dist/utils/agent-management.d.ts +25 -0
  59. package/dist/utils/agent-management.js +67 -0
  60. package/dist/utils/bundling.d.ts +44 -5
  61. package/dist/utils/bundling.js +723 -23
  62. package/dist/utils/compile.d.ts +63 -0
  63. package/dist/utils/compile.js +712 -36
  64. package/dist/utils/deployment.d.ts +2 -1
  65. package/dist/utils/deployment.js +16 -2
  66. package/dist/utils/dev-api.d.ts +42 -2
  67. package/dist/utils/dev-api.js +177 -4
  68. package/dist/utils/dev-server.d.ts +1 -1
  69. package/dist/utils/dev-server.js +4 -4
  70. package/dist/utils/dynamic-job-bundler.d.ts +17 -0
  71. package/dist/utils/dynamic-job-bundler.js +143 -0
  72. package/dist/utils/init-agent.d.ts +3 -1
  73. package/dist/utils/init-agent.js +6 -4
  74. package/dist/utils/init-prompts.d.ts +2 -1
  75. package/dist/utils/init-prompts.js +14 -9
  76. package/dist/utils/job-management.d.ts +24 -0
  77. package/dist/utils/job-management.js +264 -0
  78. package/dist/utils/postprocessor-management.d.ts +9 -0
  79. package/dist/utils/postprocessor-management.js +118 -0
  80. package/dist/utils/pre-bundle-jobs.d.ts +26 -0
  81. package/dist/utils/pre-bundle-jobs.js +176 -0
  82. package/dist/utils/preprocessor-management.d.ts +9 -0
  83. package/dist/utils/preprocessor-management.js +118 -0
  84. package/dist/utils/sandbox-storage.d.ts +48 -0
  85. package/dist/utils/sandbox-storage.js +114 -0
  86. package/dist/utils/sandbox.d.ts +61 -1
  87. package/dist/utils/sandbox.js +299 -72
  88. package/dist/utils/tool-detection.d.ts +3 -2
  89. package/dist/utils/tool-detection.js +18 -4
  90. package/dist/utils/webhook-management.d.ts +24 -0
  91. package/dist/utils/webhook-management.js +256 -0
  92. package/package.json +1 -1
  93. package/template/README.md +30 -2
  94. package/template/lua.skill.yaml +47 -0
  95. package/template/package-lock.json +10505 -0
  96. package/template/package.json +2 -1
  97. package/template/src/index.ts +103 -2
  98. package/template/src/jobs/AbandonedBasketProcessorJob.ts +139 -0
  99. package/template/src/jobs/DailyCleanupJob.ts +100 -0
  100. package/template/src/jobs/DataMigrationJob.ts +133 -0
  101. package/template/src/jobs/HealthCheckJob.ts +87 -0
  102. package/template/src/tools/CreateInlineJob.ts +42 -0
  103. package/template/src/tools/GameScoreTrackerTool.ts +356 -0
  104. package/template/src/tools/SmartBasketTool.ts +188 -0
  105. package/template/src/webhooks/PaymentWebhook.ts +113 -0
  106. package/template/src/webhooks/UserEventWebhook.ts +77 -0
  107. package/API_REFERENCE.md +0 -1408
  108. package/CHANGELOG.md +0 -236
  109. package/CLI_REFERENCE.md +0 -908
  110. package/GETTING_STARTED.md +0 -1040
  111. package/INSTANCE_TYPES.md +0 -1158
  112. package/README.md +0 -865
  113. package/TEMPLATE_GUIDE.md +0 -1398
  114. package/USER_DATA_INSTANCE.md +0 -621
  115. package/template/TOOL_EXAMPLES.md +0 -655
@@ -5,15 +5,37 @@
5
5
  import fs from "fs";
6
6
  import path from "path";
7
7
  import { build } from "esbuild";
8
+ import { Project, Node } from "ts-morph";
8
9
  import { writeProgress } from "./cli.js";
9
10
  import { COMPILE_DIRS, COMPILE_FILES, ESBUILD_TOOL_CONFIG, ESBUILD_INDEX_CONFIG, DEFAULT_INPUT_SCHEMA, } from '../config/compile.constants.js';
10
- import { wrapToolForVM, createExecuteFunction, evaluateZodSchemaToJsonSchema, } from './compile.js';
11
+ import { wrapToolForVM, createExecuteFunction, evaluateZodSchemaToJsonSchema } from './compile.js';
12
+ /**
13
+ * Helper function to remove lua-cli and api-exports imports from source code.
14
+ * Used when bundling via stdin or temp files where the plugin can't process them.
15
+ */
16
+ function stripLuaCliImports(sourceCode) {
17
+ return sourceCode
18
+ // Remove api-exports imports
19
+ .replace(/import\s+{([^}]+)}\s+from\s+["'][^"']*api-exports["'];?/g, '// api-exports imports removed - using sandbox globals')
20
+ // Remove lua-cli imports
21
+ .replace(/import\s+{([^}]+)}\s+from\s+["']lua-cli["'];?/g, '// lua-cli imports removed - using sandbox globals')
22
+ // Remove lua-cli/skill imports (but keep LuaTool, etc. for type checking in temp files)
23
+ .replace(/import\s+{([^}]+)}\s+from\s+["']lua-cli\/skill["'];?/g, (match, imports) => {
24
+ // Keep only LuaTool, LuaWebhook, LuaJob types for temp files
25
+ const typeImports = imports.split(',')
26
+ .map((imp) => imp.trim())
27
+ .filter((imp) => /^(LuaTool|LuaWebhook|LuaJob|LuaPreProcessor|LuaPostProcessor)$/.test(imp));
28
+ return typeImports.length > 0
29
+ ? `// lua-cli/skill imports removed - using sandbox globals (types kept for compilation)`
30
+ : '// lua-cli/skill imports removed - using sandbox globals';
31
+ });
32
+ }
11
33
  /**
12
34
  * esbuild plugin to inject sandbox globals instead of requiring lua-cli
13
35
  * This removes require("lua-cli") statements and injects the global API objects
14
- * that are available in the sandbox (Product, User, Data, Baskets, Order)
36
+ * that are available in the sandbox (Products, User, Data, Baskets, Orders, Webhooks, Jobs)
15
37
  */
16
- const sandboxGlobalsPlugin = {
38
+ export const sandboxGlobalsPlugin = {
17
39
  name: 'sandbox-globals',
18
40
  setup(build) {
19
41
  // Only process user files, not node_modules
@@ -23,12 +45,18 @@ const sandboxGlobalsPlugin = {
23
45
  return null;
24
46
  }
25
47
  const contents = await fs.promises.readFile(args.path, 'utf8');
26
- // Only transform files that import from lua-cli
27
- if (!contents.includes('lua-cli')) {
48
+ // Only transform files that import from lua-cli or api-exports
49
+ if (!contents.includes('lua-cli') && !contents.includes('api-exports')) {
28
50
  return null;
29
51
  }
30
52
  // Replace lua-cli imports with global references
31
53
  let transformedContents = contents;
54
+ // Replace named imports from api-exports (e.g., ../../../dist/api-exports)
55
+ // Match: import { Jobs, Products, etc. } from "../../../dist/api-exports"
56
+ transformedContents = transformedContents.replace(/import\s+{([^}]+)}\s+from\s+["'][^"']*api-exports["'];?/g, (match, imports) => {
57
+ // Just remove the import, globals will be available in sandbox
58
+ return '// api-exports imports removed - using sandbox globals';
59
+ });
32
60
  // Replace named imports from lua-cli
33
61
  // Match: import { Products, User, Data, etc. } from "lua-cli"
34
62
  transformedContents = transformedContents.replace(/import\s+{([^}]+)}\s+from\s+["']lua-cli["'];?/g, (match, imports) => {
@@ -50,7 +78,9 @@ const sandboxGlobalsPlugin = {
50
78
  'User': 'User',
51
79
  'Data': 'Data',
52
80
  'Baskets': 'Baskets',
53
- 'Orders': 'Orders'
81
+ 'Orders': 'Orders',
82
+ 'Webhooks': 'Webhooks',
83
+ 'Jobs': 'Jobs'
54
84
  };
55
85
  // Replace usage of imported names with globals
56
86
  for (const [importName, globalName] of Object.entries(globalMappings)) {
@@ -75,21 +105,85 @@ const sandboxGlobalsPlugin = {
75
105
  * - Minify code for production
76
106
  * - Wrap for VM execution
77
107
  *
108
+ * For inline tools (defined in index.ts), creates a temporary export file first.
109
+ *
78
110
  * @param tool - The tool to bundle
79
111
  * @param distDir - The distribution directory for output
80
112
  */
81
- export async function bundleTool(tool, distDir) {
113
+ export async function bundleTool(tool, distDir, modifiedSource) {
82
114
  writeProgress(`📦 Bundling ${tool.className}...`);
83
115
  try {
84
116
  const outputPath = path.join(distDir, COMPILE_DIRS.TOOLS, `${tool.className}.js`);
85
- await build({
86
- ...ESBUILD_TOOL_CONFIG,
87
- entryPoints: [tool.filePath],
88
- outfile: outputPath,
89
- plugins: [sandboxGlobalsPlugin],
90
- });
117
+ let entryPoint = tool.filePath;
118
+ let tempFile = null;
119
+ // Note: modifiedSource is handled via stdin below, no temp file needed
120
+ // Check if tool is inline (in index.ts or src/index.ts)
121
+ const isInlineInIndex = tool.filePath.endsWith('index.ts') || tool.filePath.endsWith('index.tsx');
122
+ if (isInlineInIndex) {
123
+ // Create a temporary file that exports just this tool class
124
+ const tempDir = path.join(distDir, '.temp');
125
+ if (!fs.existsSync(tempDir)) {
126
+ fs.mkdirSync(tempDir, { recursive: true });
127
+ }
128
+ tempFile = path.join(tempDir, `${tool.className}.ts`);
129
+ // Read the source file and extract just the tool class
130
+ const sourceContent = fs.readFileSync(tool.filePath, 'utf8');
131
+ const project = new Project();
132
+ const sourceFile = project.addSourceFileAtPath(tool.filePath);
133
+ const classDecl = sourceFile.getClass(tool.className);
134
+ if (classDecl) {
135
+ // Get all imports from the original file that this class might need
136
+ const imports = sourceFile.getImportDeclarations();
137
+ let importsText = '';
138
+ // Include imports from lua-cli and zod (tools commonly need these)
139
+ imports.forEach(importDecl => {
140
+ const moduleSpec = importDecl.getModuleSpecifierValue();
141
+ if (moduleSpec === 'lua-cli' || moduleSpec === 'zod' || moduleSpec.startsWith('./')) {
142
+ importsText += importDecl.getText() + '\n';
143
+ }
144
+ });
145
+ // Create temporary file with imports and the tool class
146
+ const toolClassText = classDecl.getText();
147
+ const tempContent = `${importsText}\n${toolClassText}\n\nexport default ${tool.className};\n`;
148
+ fs.writeFileSync(tempFile, tempContent);
149
+ entryPoint = tempFile;
150
+ }
151
+ }
152
+ // Use stdin for modified source to preserve import resolution
153
+ if (modifiedSource && !isInlineInIndex) {
154
+ // Remove api-exports and lua-cli imports (they're available in sandbox)
155
+ const transformedSource = stripLuaCliImports(modifiedSource);
156
+ await build({
157
+ ...ESBUILD_TOOL_CONFIG,
158
+ stdin: {
159
+ contents: transformedSource,
160
+ resolveDir: path.dirname(tool.filePath),
161
+ sourcefile: tool.filePath,
162
+ loader: 'ts',
163
+ },
164
+ outfile: outputPath,
165
+ plugins: [sandboxGlobalsPlugin],
166
+ });
167
+ }
168
+ else {
169
+ await build({
170
+ ...ESBUILD_TOOL_CONFIG,
171
+ entryPoints: [entryPoint],
172
+ outfile: outputPath,
173
+ plugins: [sandboxGlobalsPlugin],
174
+ });
175
+ }
91
176
  // Wrap the bundled code for VM execution environment
92
177
  await wrapToolForVM(outputPath, tool);
178
+ // Clean up temp file if created
179
+ if (tempFile && fs.existsSync(tempFile)) {
180
+ try {
181
+ fs.unlinkSync(tempFile);
182
+ }
183
+ catch (cleanupError) {
184
+ // Ignore cleanup errors
185
+ }
186
+ }
93
187
  }
94
188
  catch (error) {
95
189
  console.warn(`Warning: Failed to bundle ${tool.className}:`, error);
@@ -115,17 +209,610 @@ export async function bundleMainIndex(indexPath, distDir) {
115
209
  console.warn("Warning: Failed to bundle main index:", error);
116
210
  }
117
211
  }
212
+ /**
213
+ * Extracts and bundles webhook execute function and schemas.
214
+ *
215
+ * @param webhook - Webhook metadata with name and version
216
+ * @param indexFile - The TypeScript source file containing the webhook
217
+ * @param distDir - Distribution directory for output
218
+ * @param project - Optional ts-morph Project for resolving imports
219
+ * @returns Webhook with bundled code and schemas
220
+ */
221
+ export async function bundleWebhook(webhook, indexFile, distDir, project) {
222
+ writeProgress(`📦 Bundling webhook ${webhook.name}...`);
223
+ try {
224
+ // Find the LuaWebhook constructor in the AST
225
+ let executeFunction = '';
226
+ let querySchema = undefined;
227
+ let headerSchema = undefined;
228
+ let bodySchema = undefined;
229
+ // Helper function to search for webhook in a file
230
+ const searchFileForWebhook = (file) => {
231
+ file.forEachDescendant((node) => {
232
+ if (Node.isNewExpression(node)) {
233
+ const expression = node.getExpression();
234
+ if (expression.getText() === 'LuaWebhook') {
235
+ const args = node.getArguments();
236
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
237
+ const configObj = args[0];
238
+ // First pass: check if this is the webhook we're looking for
239
+ let isMatchingWebhook = false;
240
+ let foundExecute = '';
241
+ let foundQuerySchema = undefined;
242
+ let foundHeaderSchema = undefined;
243
+ let foundBodySchema = undefined;
244
+ configObj.getProperties().forEach((prop) => {
245
+ if (Node.isPropertyAssignment(prop)) {
246
+ const name = prop.getName();
247
+ const value = prop.getInitializer();
248
+ if (name === 'name' && value) {
249
+ const nameValue = value.getText().replace(/['"]/g, '');
250
+ if (nameValue === webhook.name) {
251
+ isMatchingWebhook = true;
252
+ }
253
+ }
254
+ if (name === 'execute' && value) {
255
+ foundExecute = value.getText();
256
+ }
257
+ else if (name === 'querySchema' && value) {
258
+ foundQuerySchema = value.getText();
259
+ }
260
+ else if (name === 'headerSchema' && value) {
261
+ foundHeaderSchema = value.getText();
262
+ }
263
+ else if (name === 'bodySchema' && value) {
264
+ foundBodySchema = value.getText();
265
+ }
266
+ }
267
+ });
268
+ // Only set values if this is the matching webhook
269
+ if (isMatchingWebhook) {
270
+ executeFunction = foundExecute;
271
+ querySchema = foundQuerySchema;
272
+ headerSchema = foundHeaderSchema;
273
+ bodySchema = foundBodySchema;
274
+ }
275
+ }
276
+ }
277
+ }
278
+ });
279
+ };
280
+ // Search in index file first
281
+ searchFileForWebhook(indexFile);
282
+ // If not found and project is available, search in imported files
283
+ if (!executeFunction && project) {
284
+ const imports = indexFile.getImportDeclarations();
285
+ for (const importDecl of imports) {
286
+ try {
287
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
288
+ const { resolveImportPath } = await import('./compile.js');
289
+ const importPath = resolveImportPath(moduleSpecifier, indexFile.getFilePath());
290
+ if (fs.existsSync(importPath)) {
291
+ const importedFile = project.addSourceFileAtPath(importPath);
292
+ searchFileForWebhook(importedFile);
293
+ // If found, stop searching
294
+ if (executeFunction) {
295
+ break;
296
+ }
297
+ }
298
+ }
299
+ catch (error) {
300
+ // Continue searching other imports
301
+ }
302
+ }
303
+ }
304
+ // Convert Zod schemas to JSON Schema
305
+ const convertedSchemas = {};
306
+ if (querySchema) {
307
+ convertedSchemas.querySchema = await evaluateZodSchemaToJsonSchema(querySchema);
308
+ }
309
+ if (headerSchema) {
310
+ convertedSchemas.headerSchema = await evaluateZodSchemaToJsonSchema(headerSchema);
311
+ }
312
+ if (bodySchema) {
313
+ convertedSchemas.bodySchema = await evaluateZodSchemaToJsonSchema(bodySchema);
314
+ }
315
+ // Bundle and compress the execute function (like tools)
316
+ let compressedCode = '';
317
+ if (executeFunction) {
318
+ compressedCode = await bundleAndCompressWebhookCode(executeFunction, webhook.name, distDir);
319
+ }
320
+ return {
321
+ ...webhook,
322
+ executeFunction,
323
+ code: compressedCode, // Add compressed bundled code
324
+ ...convertedSchemas
325
+ };
326
+ }
327
+ catch (error) {
328
+ console.warn(`Warning: Could not bundle webhook ${webhook.name}:`, error);
329
+ return webhook;
330
+ }
331
+ }
332
+ /**
333
+ * Bundles and compresses webhook execute function code.
334
+ * Creates a temporary file, bundles with esbuild, compresses with gzip.
335
+ *
336
+ * @param executeFunction - Raw execute function code
337
+ * @param webhookName - Name of the webhook
338
+ * @param distDir - Distribution directory
339
+ * @returns Compressed base64-encoded bundled code
340
+ */
341
+ async function bundleAndCompressWebhookCode(executeFunction, webhookName, distDir) {
342
+ const { compressCode } = await import('./compile.js');
343
+ // Create temporary file with the execute function wrapped as a module
344
+ const tempDir = path.join(distDir, '.temp');
345
+ if (!fs.existsSync(tempDir)) {
346
+ fs.mkdirSync(tempDir, { recursive: true });
347
+ }
348
+ const tempFile = path.join(tempDir, `${webhookName}-webhook.ts`);
349
+ const tempOutput = path.join(tempDir, `${webhookName}-webhook.js`);
350
+ try {
351
+ // Write execute function as a module export
352
+ const moduleCode = stripLuaCliImports(`
353
+ // Webhook execute function for ${webhookName}
354
+ export default ${executeFunction};
355
+ `);
356
+ fs.writeFileSync(tempFile, moduleCode);
357
+ // Bundle with esbuild
358
+ await build({
359
+ entryPoints: [tempFile],
360
+ bundle: true,
361
+ platform: 'node',
362
+ target: 'node16',
363
+ format: 'cjs',
364
+ minify: true,
365
+ outfile: tempOutput,
366
+ external: [], // Bundle everything
367
+ plugins: [sandboxGlobalsPlugin],
368
+ });
369
+ // Read bundled code
370
+ let bundledCode = fs.readFileSync(tempOutput, 'utf8');
371
+ // Wrap for webhook VM execution (similar to tools)
372
+ const wrappedCode = `(async (query, headers, body) => {
373
+
374
+ // Execute the bundled webhook code
375
+ ${bundledCode}
376
+
377
+ // Get the execute function from exports
378
+ const executeFunction = module.exports.default || module.exports;
379
+
380
+ // Execute with three separate parameters (not an object)
381
+ return await executeFunction(query, headers, body);
382
+ })`;
383
+ // Compress the wrapped code
384
+ const compressed = compressCode(wrappedCode);
385
+ // Clean up temp files
386
+ try {
387
+ fs.unlinkSync(tempFile);
388
+ fs.unlinkSync(tempOutput);
389
+ }
390
+ catch (cleanupError) {
391
+ // Ignore cleanup errors
392
+ }
393
+ return compressed;
394
+ }
395
+ catch (error) {
396
+ console.warn(`Warning: Could not bundle webhook ${webhookName} code:`, error);
397
+ // Clean up on error
398
+ try {
399
+ if (fs.existsSync(tempFile))
400
+ fs.unlinkSync(tempFile);
401
+ if (fs.existsSync(tempOutput))
402
+ fs.unlinkSync(tempOutput);
403
+ }
404
+ catch (cleanupError) {
405
+ // Ignore cleanup errors
406
+ }
407
+ return '';
408
+ }
409
+ }
410
+ /**
411
+ * Extracts and bundles job execute function.
412
+ *
413
+ * @param job - Job metadata with name and version
414
+ * @param indexFile - The TypeScript source file containing the job
415
+ * @param distDir - Distribution directory for output
416
+ * @param project - Optional ts-morph Project for resolving imports
417
+ * @returns Job with bundled code
418
+ */
419
+ export async function bundleJob(job, indexFile, distDir, project) {
420
+ writeProgress(`📦 Bundling job ${job.name}...`);
421
+ try {
422
+ // Find the LuaJob constructor in the AST
423
+ let executeFunction = '';
424
+ // Helper function to search for job in a file
425
+ const searchFileForJob = (file) => {
426
+ file.forEachDescendant((node) => {
427
+ if (Node.isNewExpression(node)) {
428
+ const expression = node.getExpression();
429
+ if (expression.getText() === 'LuaJob') {
430
+ const args = node.getArguments();
431
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
432
+ const configObj = args[0];
433
+ // First pass: check if this is the job we're looking for
434
+ let isMatchingJob = false;
435
+ let foundExecute = '';
436
+ configObj.getProperties().forEach((prop) => {
437
+ if (Node.isPropertyAssignment(prop)) {
438
+ const name = prop.getName();
439
+ const value = prop.getInitializer();
440
+ if (name === 'name' && value) {
441
+ const nameValue = value.getText().replace(/['"]/g, '');
442
+ if (nameValue === job.name) {
443
+ isMatchingJob = true;
444
+ }
445
+ }
446
+ if (name === 'execute' && value) {
447
+ foundExecute = value.getText();
448
+ }
449
+ }
450
+ });
451
+ // Only set executeFunction if this is the matching job
452
+ if (isMatchingJob && foundExecute) {
453
+ executeFunction = foundExecute;
454
+ }
455
+ }
456
+ }
457
+ }
458
+ });
459
+ };
460
+ // Search in index file first
461
+ searchFileForJob(indexFile);
462
+ // If not found and project is available, search in imported files
463
+ if (!executeFunction && project) {
464
+ const imports = indexFile.getImportDeclarations();
465
+ for (const importDecl of imports) {
466
+ try {
467
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
468
+ const { resolveImportPath } = await import('./compile.js');
469
+ const importPath = resolveImportPath(moduleSpecifier, indexFile.getFilePath());
470
+ if (fs.existsSync(importPath)) {
471
+ const importedFile = project.addSourceFileAtPath(importPath);
472
+ searchFileForJob(importedFile);
473
+ // If found, stop searching
474
+ if (executeFunction) {
475
+ break;
476
+ }
477
+ }
478
+ }
479
+ catch (error) {
480
+ // Continue searching other imports
481
+ }
482
+ }
483
+ }
484
+ // Bundle and compress the execute function (like tools)
485
+ let compressedCode = '';
486
+ if (executeFunction) {
487
+ compressedCode = await bundleAndCompressJobCode(executeFunction, job.name, distDir);
488
+ }
489
+ return {
490
+ ...job,
491
+ executeFunction,
492
+ code: compressedCode // Add compressed bundled code
493
+ };
494
+ }
495
+ catch (error) {
496
+ console.warn(`Warning: Could not bundle job ${job.name}:`, error);
497
+ return job;
498
+ }
499
+ }
500
+ /**
501
+ * Bundles and compresses job execute function code.
502
+ * Creates a temporary file, bundles with esbuild, compresses with gzip.
503
+ *
504
+ * @param executeFunction - Raw execute function code
505
+ * @param jobName - Name of the job
506
+ * @param distDir - Distribution directory
507
+ * @returns Compressed base64-encoded bundled code
508
+ */
509
+ async function bundleAndCompressJobCode(executeFunction, jobName, distDir) {
510
+ const { compressCode } = await import('./compile.js');
511
+ // Create temporary file with the execute function wrapped as a module
512
+ const tempDir = path.join(distDir, '.temp');
513
+ if (!fs.existsSync(tempDir)) {
514
+ fs.mkdirSync(tempDir, { recursive: true });
515
+ }
516
+ const tempFile = path.join(tempDir, `${jobName}-job.ts`);
517
+ const tempOutput = path.join(tempDir, `${jobName}-job.js`);
518
+ try {
519
+ // Write execute function as a module export
520
+ const moduleCode = stripLuaCliImports(`
521
+ // Job execute function for ${jobName}
522
+ export default ${executeFunction};
523
+ `);
524
+ fs.writeFileSync(tempFile, moduleCode);
525
+ // Bundle with esbuild
526
+ await build({
527
+ entryPoints: [tempFile],
528
+ bundle: true,
529
+ platform: 'node',
530
+ target: 'node16',
531
+ format: 'cjs',
532
+ minify: true,
533
+ outfile: tempOutput,
534
+ external: [], // Bundle everything
535
+ plugins: [sandboxGlobalsPlugin],
536
+ });
537
+ // Read bundled code
538
+ let bundledCode = fs.readFileSync(tempOutput, 'utf8');
539
+ // Wrap for job VM execution (similar to tools, but accepts job parameter)
540
+ const wrappedCode = `(async (job) => {
541
+ // Execute the bundled job code
542
+ ${bundledCode}
543
+
544
+ // Get the execute function from exports
545
+ const executeFunction = module.exports.default || module.exports;
546
+
547
+ // Execute job with job instance parameter
548
+ return await executeFunction(job);
549
+ })`;
550
+ // Compress the wrapped code
551
+ const compressed = compressCode(wrappedCode);
552
+ // Clean up temp files
553
+ try {
554
+ fs.unlinkSync(tempFile);
555
+ fs.unlinkSync(tempOutput);
556
+ }
557
+ catch (cleanupError) {
558
+ // Ignore cleanup errors
559
+ }
560
+ return compressed;
561
+ }
562
+ catch (error) {
563
+ console.warn(`Warning: Could not bundle job ${jobName} code:`, error);
564
+ // Clean up on error
565
+ try {
566
+ if (fs.existsSync(tempFile))
567
+ fs.unlinkSync(tempFile);
568
+ if (fs.existsSync(tempOutput))
569
+ fs.unlinkSync(tempOutput);
570
+ }
571
+ catch (cleanupError) {
572
+ // Ignore cleanup errors
573
+ }
574
+ return '';
575
+ }
576
+ }
577
+ /**
578
+ * Bundles and compresses preprocessor execute function code.
579
+ */
580
+ export async function bundlePreProcessor(preprocessor, indexFile, distDir, project) {
581
+ writeProgress(`📦 Bundling preprocessor ${preprocessor.name}...`);
582
+ try {
583
+ let executeFunction = '';
584
+ const searchFileForPreProcessor = (file) => {
585
+ file.forEachDescendant((node) => {
586
+ if (Node.isNewExpression(node)) {
587
+ const expression = node.getExpression();
588
+ if (expression.getText() === 'PreProcessor') {
589
+ const args = node.getArguments();
590
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
591
+ const configObj = args[0];
592
+ let isMatching = false;
593
+ let foundExecute = '';
594
+ configObj.getProperties().forEach((prop) => {
595
+ if (Node.isPropertyAssignment(prop)) {
596
+ const name = prop.getName();
597
+ const value = prop.getInitializer();
598
+ if (name === 'name' && value) {
599
+ const nameValue = value.getText().replace(/['"]/g, '');
600
+ if (nameValue === preprocessor.name) {
601
+ isMatching = true;
602
+ }
603
+ }
604
+ if (name === 'execute' && value) {
605
+ foundExecute = value.getText();
606
+ }
607
+ }
608
+ });
609
+ if (isMatching && foundExecute) {
610
+ executeFunction = foundExecute;
611
+ }
612
+ }
613
+ }
614
+ }
615
+ });
616
+ };
617
+ // Search in index file first
618
+ searchFileForPreProcessor(indexFile);
619
+ // If not found and project is available, search in imported files
620
+ if (!executeFunction && project) {
621
+ const imports = indexFile.getImportDeclarations();
622
+ for (const importDecl of imports) {
623
+ try {
624
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
625
+ const { resolveImportPath } = await import('./compile.js');
626
+ const importPath = resolveImportPath(moduleSpecifier, indexFile.getFilePath());
627
+ if (fs.existsSync(importPath)) {
628
+ const importedFile = project.addSourceFileAtPath(importPath);
629
+ searchFileForPreProcessor(importedFile);
630
+ if (executeFunction) {
631
+ break;
632
+ }
633
+ }
634
+ }
635
+ catch (error) {
636
+ // Continue searching
637
+ }
638
+ }
639
+ }
640
+ let compressedCode = '';
641
+ if (executeFunction) {
642
+ compressedCode = await bundleAndCompressProcessorCode(executeFunction, preprocessor.name, 'pre', distDir);
643
+ }
644
+ return {
645
+ name: preprocessor.name,
646
+ version: preprocessor.version,
647
+ description: preprocessor.description,
648
+ context: preprocessor.context,
649
+ async: preprocessor.async === true ? true : false, // Explicitly set boolean
650
+ executeFunction,
651
+ code: compressedCode
652
+ };
653
+ }
654
+ catch (error) {
655
+ console.warn(`Warning: Could not bundle preprocessor ${preprocessor.name}:`, error);
656
+ return preprocessor;
657
+ }
658
+ }
659
+ /**
660
+ * Bundles and compresses postprocessor execute function code.
661
+ */
662
+ export async function bundlePostProcessor(postprocessor, indexFile, distDir, project) {
663
+ writeProgress(`📦 Bundling postprocessor ${postprocessor.name}...`);
664
+ try {
665
+ let executeFunction = '';
666
+ const searchFileForPostProcessor = (file) => {
667
+ file.forEachDescendant((node) => {
668
+ if (Node.isNewExpression(node)) {
669
+ const expression = node.getExpression();
670
+ if (expression.getText() === 'PostProcessor') {
671
+ const args = node.getArguments();
672
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
673
+ const configObj = args[0];
674
+ let isMatching = false;
675
+ let foundExecute = '';
676
+ configObj.getProperties().forEach((prop) => {
677
+ if (Node.isPropertyAssignment(prop)) {
678
+ const name = prop.getName();
679
+ const value = prop.getInitializer();
680
+ if (name === 'name' && value) {
681
+ const nameValue = value.getText().replace(/['"]/g, '');
682
+ if (nameValue === postprocessor.name) {
683
+ isMatching = true;
684
+ }
685
+ }
686
+ if (name === 'execute' && value) {
687
+ foundExecute = value.getText();
688
+ }
689
+ }
690
+ });
691
+ if (isMatching && foundExecute) {
692
+ executeFunction = foundExecute;
693
+ }
694
+ }
695
+ }
696
+ }
697
+ });
698
+ };
699
+ // Search in index file first
700
+ searchFileForPostProcessor(indexFile);
701
+ // If not found and project is available, search in imported files
702
+ if (!executeFunction && project) {
703
+ const imports = indexFile.getImportDeclarations();
704
+ for (const importDecl of imports) {
705
+ try {
706
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
707
+ const { resolveImportPath } = await import('./compile.js');
708
+ const importPath = resolveImportPath(moduleSpecifier, indexFile.getFilePath());
709
+ if (fs.existsSync(importPath)) {
710
+ const importedFile = project.addSourceFileAtPath(importPath);
711
+ searchFileForPostProcessor(importedFile);
712
+ if (executeFunction) {
713
+ break;
714
+ }
715
+ }
716
+ }
717
+ catch (error) {
718
+ // Continue searching
719
+ }
720
+ }
721
+ }
722
+ let compressedCode = '';
723
+ if (executeFunction) {
724
+ compressedCode = await bundleAndCompressProcessorCode(executeFunction, postprocessor.name, 'post', distDir);
725
+ }
726
+ return {
727
+ name: postprocessor.name,
728
+ version: postprocessor.version,
729
+ description: postprocessor.description,
730
+ context: postprocessor.context,
731
+ async: postprocessor.async === true ? true : false, // Explicitly set boolean
732
+ executeFunction,
733
+ code: compressedCode
734
+ };
735
+ }
736
+ catch (error) {
737
+ console.warn(`Warning: Could not bundle postprocessor ${postprocessor.name}:`, error);
738
+ return postprocessor;
739
+ }
740
+ }
741
+ /**
742
+ * Bundles and compresses processor execute function code.
743
+ */
744
+ async function bundleAndCompressProcessorCode(executeFunction, processorName, type, distDir) {
745
+ const { compressCode } = await import('./compile.js');
746
+ const tempDir = path.join(distDir, '.temp');
747
+ if (!fs.existsSync(tempDir)) {
748
+ fs.mkdirSync(tempDir, { recursive: true });
749
+ }
750
+ const tempFile = path.join(tempDir, `${processorName}-${type}processor.ts`);
751
+ const tempOutput = path.join(tempDir, `${processorName}-${type}processor.js`);
752
+ try {
753
+ // Processor execute functions receive: (user, message, [response], channel)
754
+ const paramList = type === 'pre' ? 'user, message, channel' : 'user, message, response, channel';
755
+ const moduleCode = stripLuaCliImports(`
756
+ // ${type === 'pre' ? 'Pre' : 'Post'}Processor execute function for ${processorName}
757
+ export default ${executeFunction};
758
+ `);
759
+ fs.writeFileSync(tempFile, moduleCode);
760
+ await build({
761
+ entryPoints: [tempFile],
762
+ bundle: true,
763
+ platform: 'node',
764
+ target: 'node16',
765
+ format: 'cjs',
766
+ minify: true,
767
+ outfile: tempOutput,
768
+ external: [],
769
+ plugins: [sandboxGlobalsPlugin],
770
+ });
771
+ let bundledCode = fs.readFileSync(tempOutput, 'utf8');
772
+ const wrappedCode = `(async (${paramList}) => {
773
+ // Execute the bundled processor code
774
+ ${bundledCode}
775
+
776
+ const executeFunction = module.exports.default || module.exports;
777
+ return await executeFunction(${paramList});
778
+ })`;
779
+ const compressed = compressCode(wrappedCode);
780
+ try {
781
+ fs.unlinkSync(tempFile);
782
+ fs.unlinkSync(tempOutput);
783
+ }
784
+ catch (cleanupError) {
785
+ // Ignore
786
+ }
787
+ return compressed;
788
+ }
789
+ catch (error) {
790
+ console.warn(`Warning: Could not bundle ${type}processor ${processorName} code:`, error);
791
+ try {
792
+ if (fs.existsSync(tempFile))
793
+ fs.unlinkSync(tempFile);
794
+ if (fs.existsSync(tempOutput))
795
+ fs.unlinkSync(tempOutput);
796
+ }
797
+ catch (cleanupError) {
798
+ // Ignore
799
+ }
800
+ return '';
801
+ }
802
+ }
118
803
  /**
119
804
  * Extracts execute code and input schema from a tool.
120
805
  * This function:
121
806
  * 1. Reads the bundled tool code
122
- * 2. Extracts the execute function
123
- * 3. Converts Zod schema to JSON Schema format
807
+ * 2. Detects and bundles any Jobs.create() calls within execute
808
+ * 3. Extracts the execute function
809
+ * 4. Converts Zod schema to JSON Schema format
124
810
  *
125
811
  * @param tool - The tool to extract metadata from (mutated with executeCode and inputSchema)
126
812
  * @param project - The ts-morph Project instance for AST analysis
813
+ * @param distDir - Distribution directory for bundling nested jobs
127
814
  */
128
- export async function extractExecuteCode(tool, project) {
815
+ export async function extractExecuteCode(tool, project, distDir) {
129
816
  try {
130
817
  const toolSourceFile = project.getSourceFile(tool.filePath);
131
818
  if (!toolSourceFile) {
@@ -137,19 +824,32 @@ export async function extractExecuteCode(tool, project) {
137
824
  console.warn(`Warning: Could not find class ${tool.className} in ${tool.filePath}`);
138
825
  return;
139
826
  }
140
- // Verify execute method exists
827
+ // Verify execute method or property exists
141
828
  const executeMethod = classDecl.getMethod('execute');
142
- if (!executeMethod) {
143
- console.warn(`Warning: Could not find execute method in ${tool.className}`);
829
+ const executeProperty = classDecl.getProperty('execute');
830
+ if (!executeMethod && !executeProperty) {
831
+ console.warn(`Warning: Could not find execute method or property in ${tool.className}`);
144
832
  return;
145
833
  }
146
- const executeBody = executeMethod.getBodyText();
147
- if (!executeBody) {
148
- console.warn(`Warning: Execute method has no body in ${tool.className}`);
149
- return;
834
+ // Validate execute has a body (for methods) or initializer (for arrow function properties)
835
+ if (executeMethod) {
836
+ const executeBody = executeMethod.getBodyText();
837
+ if (!executeBody) {
838
+ console.warn(`Warning: Execute method has no body in ${tool.className}`);
839
+ return;
840
+ }
841
+ }
842
+ else if (executeProperty) {
843
+ const initializer = executeProperty.getInitializer();
844
+ if (!initializer) {
845
+ console.warn(`Warning: Execute property has no initializer in ${tool.className}`);
846
+ return;
847
+ }
150
848
  }
151
849
  // Extract execute code from bundled file
152
850
  tool.executeCode = extractExecuteCodeFromBundledFile(tool);
851
+ // NOTE: Dynamic job bundling moved to source-level processing
852
+ // Attempting to extract from minified bundled code is too fragile
153
853
  // Extract and convert input schema
154
854
  tool.inputSchema = await extractInputSchema(classDecl, tool);
155
855
  }