lua-cli 3.0.2-alpha.1 → 3.0.2-alpha.3

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.
@@ -51,6 +51,7 @@ export function setupSkillCommands(program) {
51
51
  program
52
52
  .command("compile")
53
53
  .description("šŸ“¦ Compile skill to deployable format")
54
+ .option("--debug", "Enable debug mode with verbose logging and temp file preservation")
54
55
  .action(compileCommand);
55
56
  program
56
57
  .command("test [type]")
@@ -27,6 +27,9 @@
27
27
  * - Skills deleted from code are removed from YAML
28
28
  * - Skills not in YAML are deleted from server (or deactivated if they have versions)
29
29
  *
30
+ * @param options - Command options including debug flag
30
31
  * @returns Promise that resolves when compilation is complete
31
32
  */
32
- export declare function compileCommand(): Promise<void>;
33
+ export declare function compileCommand(options?: {
34
+ debug?: boolean;
35
+ }): Promise<void>;
@@ -43,17 +43,43 @@ import { syncAgentPersonaWithYaml } from '../utils/agent-management.js';
43
43
  * - Skills deleted from code are removed from YAML
44
44
  * - Skills not in YAML are deleted from server (or deactivated if they have versions)
45
45
  *
46
+ * @param options - Command options including debug flag
46
47
  * @returns Promise that resolves when compilation is complete
47
48
  */
48
- export async function compileCommand() {
49
+ export async function compileCommand(options) {
49
50
  return withErrorHandling(async () => {
51
+ const debugMode = options?.debug || process.env.LUA_DEBUG === 'true';
52
+ if (debugMode) {
53
+ console.log('šŸ› Debug mode enabled');
54
+ console.log(' - Verbose logging: ON');
55
+ console.log(' - Temp file preservation: ON');
56
+ console.log(' - Full error stacks: ON\n');
57
+ }
50
58
  writeProgress("šŸ”Ø Compiling Lua skill...");
59
+ // Track compilation issues
60
+ const compilationWarnings = [];
61
+ const failedBundles = [];
62
+ const startTime = Date.now();
51
63
  // Step 1: Prepare output directories
64
+ if (debugMode)
65
+ console.log('šŸ“ Preparing output directories...');
52
66
  const { distDir, luaDir } = prepareOutputDirectories();
67
+ if (debugMode)
68
+ console.log(` āœ“ Created: ${distDir}`);
69
+ if (debugMode)
70
+ console.log(` āœ“ Created: ${luaDir}\n`);
53
71
  // Step 2: Analyze TypeScript project and detect tools
72
+ if (debugMode)
73
+ console.log('šŸ” Finding index file...');
54
74
  const indexPath = findIndexFile();
75
+ if (debugMode)
76
+ console.log(` āœ“ Found: ${indexPath}\n`);
77
+ if (debugMode)
78
+ console.log('šŸ“š Creating TypeScript project...');
55
79
  const project = createTypeScriptProject();
56
80
  const indexFile = project.addSourceFileAtPath(indexPath);
81
+ if (debugMode)
82
+ console.log(` āœ“ Loaded source file\n`);
57
83
  // Step 2a: Check for LuaAgent (unified agent configuration)
58
84
  writeProgress("šŸ” Checking for LuaAgent configuration...");
59
85
  const { extractLuaAgentMetadata, resolveLuaAgentReferences, getSkillFilePaths } = await import('../utils/compile.js');
@@ -69,7 +95,7 @@ export async function compileCommand() {
69
95
  resolvedAgentData = resolveLuaAgentReferences(agentMetadata, indexFile, project);
70
96
  // Get file paths where skills are defined so we can scan them for tools
71
97
  skillFilePaths = getSkillFilePaths(agentMetadata, indexFile);
72
- writeProgress(`šŸ“¦ Agent contains: ${resolvedAgentData.skills.length} skill(s), ${resolvedAgentData.webhooks.length} webhook(s), ${resolvedAgentData.jobs.length} job(s), ${resolvedAgentData.preProcessors.length} preprocessor(s), ${resolvedAgentData.postProcessors.length} postprocessor(s)`);
98
+ writeProgress(`šŸ“¦ Agent contains: ${resolvedAgentData?.skills?.length || 0} skill(s), ${resolvedAgentData?.webhooks?.length || 0} webhook(s), ${resolvedAgentData?.jobs?.length || 0} job(s), ${resolvedAgentData?.preProcessors?.length || 0} preprocessor(s), ${resolvedAgentData?.postProcessors?.length || 0} postprocessor(s)`);
73
99
  }
74
100
  else {
75
101
  writeProgress(`ā„¹ļø No LuaAgent found, using legacy detection for individual components`);
@@ -80,21 +106,41 @@ export async function compileCommand() {
80
106
  // Step 3: Bundle each tool and extract metadata
81
107
  const { preBundleJobsInSource, replaceJobPlaceholders } = await import('../utils/pre-bundle-jobs.js');
82
108
  for (const tool of tools) {
109
+ if (debugMode)
110
+ console.log(`\nšŸ”§ Processing tool: ${tool.className}`);
111
+ if (debugMode)
112
+ console.log(` File: ${tool.filePath}`);
83
113
  // Step 3a: Pre-bundle any Jobs.create() in the tool source
114
+ const toolStartTime = Date.now();
84
115
  const { modifiedSource, jobBundles } = await preBundleJobsInSource(tool.filePath, project, distDir);
116
+ if (debugMode && jobBundles.size > 0) {
117
+ console.log(` āœ“ Found ${jobBundles.size} nested job(s) to pre-bundle`);
118
+ }
85
119
  // Step 3b: Bundle the tool (with placeholders for job execute functions)
86
- await bundleTool(tool, distDir, modifiedSource);
120
+ await bundleTool(tool, distDir, modifiedSource, debugMode);
87
121
  // Step 3c: Replace placeholders in the bundled tool file
88
122
  if (jobBundles.size > 0) {
89
123
  const toolBundlePath = path.join(distDir, 'tools', `${tool.className}.js`);
90
124
  if (fs.existsSync(toolBundlePath)) {
91
125
  let bundledToolCode = fs.readFileSync(toolBundlePath, 'utf8');
126
+ const beforeSize = bundledToolCode.length;
92
127
  bundledToolCode = replaceJobPlaceholders(bundledToolCode, jobBundles);
128
+ const afterSize = bundledToolCode.length;
129
+ if (debugMode)
130
+ console.log(` āœ“ Replaced placeholders (+${afterSize - beforeSize} bytes)`);
93
131
  fs.writeFileSync(toolBundlePath, bundledToolCode);
94
132
  }
95
133
  }
96
134
  // Step 3d: Extract execute code
97
135
  await extractExecuteCode(tool, project, distDir);
136
+ if (debugMode) {
137
+ const toolTime = Date.now() - toolStartTime;
138
+ const bundledPath = path.join(distDir, 'tools', `${tool.className}.js`);
139
+ if (fs.existsSync(bundledPath)) {
140
+ const size = fs.statSync(bundledPath).size;
141
+ console.log(` āœ“ Bundled: ${(size / 1024).toFixed(2)}KB in ${toolTime}ms`);
142
+ }
143
+ }
98
144
  }
99
145
  // Step 4: Bundle the main index file
100
146
  await bundleMainIndex(indexPath, distDir);
@@ -119,8 +165,15 @@ export async function compileCommand() {
119
165
  writeProgress(`šŸ“¦ Found ${webhooksMetadata.length} webhook(s)...`);
120
166
  // Bundle each webhook (extract execute function and schemas)
121
167
  for (const webhook of webhooksMetadata) {
122
- const bundled = await bundleWebhook(webhook, indexFile, distDir, project);
168
+ if (debugMode)
169
+ console.log(`\n🌐 Processing webhook: ${webhook.name}`);
170
+ const webhookStartTime = Date.now();
171
+ const bundled = await bundleWebhook(webhook, indexFile, distDir, project, debugMode);
123
172
  bundledWebhooks.push(bundled);
173
+ if (debugMode) {
174
+ const webhookTime = Date.now() - webhookStartTime;
175
+ console.log(` āœ“ Completed in ${webhookTime}ms`);
176
+ }
124
177
  }
125
178
  // Ensure webhooks exist in YAML with valid IDs
126
179
  const configForWebhooks = readSkillConfig(); // Re-read for webhooks
@@ -141,8 +194,15 @@ export async function compileCommand() {
141
194
  writeProgress(`šŸ“¦ Found ${jobsMetadata.length} job(s)...`);
142
195
  // Bundle each job (extract execute function)
143
196
  for (const job of jobsMetadata) {
144
- const bundled = await bundleJob(job, indexFile, distDir, project);
197
+ if (debugMode)
198
+ console.log(`\nāš™ļø Processing job: ${job.name}`);
199
+ const jobStartTime = Date.now();
200
+ const bundled = await bundleJob(job, indexFile, distDir, project, debugMode);
145
201
  bundledJobs.push(bundled);
202
+ if (debugMode) {
203
+ const jobTime = Date.now() - jobStartTime;
204
+ console.log(` āœ“ Completed in ${jobTime}ms`);
205
+ }
146
206
  }
147
207
  // Ensure jobs exist in YAML with valid IDs
148
208
  const configForJobs = readSkillConfig(); // Re-read for jobs
@@ -162,7 +222,9 @@ export async function compileCommand() {
162
222
  if (preprocessorsMetadata.length > 0) {
163
223
  writeProgress(`šŸ“¦ Found ${preprocessorsMetadata.length} preprocessor(s)...`);
164
224
  for (const preprocessor of preprocessorsMetadata) {
165
- const bundled = await bundlePreProcessor(preprocessor, indexFile, distDir, project);
225
+ if (debugMode)
226
+ console.log(`\n⚔ Processing preprocessor: ${preprocessor.name}`);
227
+ const bundled = await bundlePreProcessor(preprocessor, indexFile, distDir, project, debugMode);
166
228
  bundledPreProcessors.push(bundled);
167
229
  }
168
230
  // Ensure preprocessors exist in YAML with valid IDs
@@ -182,7 +244,9 @@ export async function compileCommand() {
182
244
  if (postprocessorsMetadata.length > 0) {
183
245
  writeProgress(`šŸ“¦ Found ${postprocessorsMetadata.length} postprocessor(s)...`);
184
246
  for (const postprocessor of postprocessorsMetadata) {
185
- const bundled = await bundlePostProcessor(postprocessor, indexFile, distDir, project);
247
+ if (debugMode)
248
+ console.log(`\n⚔ Processing postprocessor: ${postprocessor.name}`);
249
+ const bundled = await bundlePostProcessor(postprocessor, indexFile, distDir, project, debugMode);
186
250
  bundledPostProcessors.push(bundled);
187
251
  }
188
252
  // Ensure postprocessors exist in YAML with valid IDs
@@ -194,6 +258,7 @@ export async function compileCommand() {
194
258
  await syncServerPostProcessorsWithYaml(postprocessorConfig);
195
259
  fs.writeFileSync(path.join(distDir, 'postprocessors.json'), JSON.stringify(bundledPostProcessors, null, 2));
196
260
  }
261
+ // Build compilation summary
197
262
  const summaryParts = [`${tools.length} tools bundled`];
198
263
  if (webhooksMetadata.length > 0)
199
264
  summaryParts.push(`${webhooksMetadata.length} webhook(s) registered`);
@@ -203,6 +268,36 @@ export async function compileCommand() {
203
268
  summaryParts.push(`${preprocessorsMetadata.length} preprocessor(s) registered`);
204
269
  if (postprocessorsMetadata.length > 0)
205
270
  summaryParts.push(`${postprocessorsMetadata.length} postprocessor(s) registered`);
271
+ // Check for empty bundles (potential failures)
272
+ const emptyBundles = [];
273
+ for (const tool of tools) {
274
+ const bundledPath = path.join(distDir, 'tools', `${tool.className}.js`);
275
+ if (fs.existsSync(bundledPath)) {
276
+ const size = fs.statSync(bundledPath).size;
277
+ if (size < 100) { // Less than 100 bytes is suspiciously small
278
+ emptyBundles.push(`${tool.className} (${size} bytes)`);
279
+ }
280
+ }
281
+ else {
282
+ emptyBundles.push(`${tool.className} (file missing)`);
283
+ }
284
+ }
285
+ if (emptyBundles.length > 0) {
286
+ console.warn(`\nāš ļø Warning: ${emptyBundles.length} tool(s) may have bundling issues:`);
287
+ emptyBundles.forEach(name => console.warn(` - ${name}`));
288
+ console.warn(` Run with NODE_ENV=development for detailed error logs.\n`);
289
+ }
290
+ const totalTime = Date.now() - startTime;
291
+ if (debugMode) {
292
+ console.log(`\nā±ļø Total compilation time: ${(totalTime / 1000).toFixed(2)}s`);
293
+ console.log(`šŸ“Š Output directories:`);
294
+ console.log(` - ${distDir}`);
295
+ console.log(` - ${luaDir}`);
296
+ const tempDir = path.join(distDir, '.temp');
297
+ if (fs.existsSync(tempDir)) {
298
+ console.log(` - ${tempDir} (preserved for debugging)`);
299
+ }
300
+ }
206
301
  writeSuccess(`āœ… Skill compiled successfully - ${summaryParts.join(', ')}`);
207
302
  }, "compilation");
208
303
  }
@@ -230,11 +325,22 @@ function prepareOutputDirectories() {
230
325
  }
231
326
  /**
232
327
  * Creates and configures a TypeScript project for AST analysis.
328
+ * Validates that tsconfig.json exists before creating project.
233
329
  *
234
330
  * @returns Configured ts-morph Project instance
331
+ * @throws Error if tsconfig.json is missing or invalid
235
332
  */
236
333
  function createTypeScriptProject() {
237
- return new Project({
238
- tsConfigFilePath: path.join(process.cwd(), COMPILE_FILES.TSCONFIG_JSON),
239
- });
334
+ const tsconfigPath = path.join(process.cwd(), COMPILE_FILES.TSCONFIG_JSON);
335
+ if (!fs.existsSync(tsconfigPath)) {
336
+ throw new Error(`tsconfig.json not found at ${tsconfigPath}\n` +
337
+ `Please ensure you're in a Lua CLI project directory.`);
338
+ }
339
+ try {
340
+ return new Project({ tsConfigFilePath: tsconfigPath });
341
+ }
342
+ catch (error) {
343
+ throw new Error(`Failed to parse tsconfig.json: ${error.message}\n` +
344
+ `Please check that your tsconfig.json is valid.`);
345
+ }
240
346
  }
@@ -22,8 +22,10 @@ export declare const sandboxGlobalsPlugin: Plugin;
22
22
  *
23
23
  * @param tool - The tool to bundle
24
24
  * @param distDir - The distribution directory for output
25
+ * @param modifiedSource - Optional modified source (for pre-bundled jobs)
26
+ * @param debugMode - Enable verbose logging and preserve temp files
25
27
  */
26
- export declare function bundleTool(tool: ToolInfo, distDir: string, modifiedSource?: string): Promise<void>;
28
+ export declare function bundleTool(tool: ToolInfo, distDir: string, modifiedSource?: string, debugMode?: boolean): Promise<void>;
27
29
  /**
28
30
  * Bundles the main index.ts file into a standalone JavaScript file.
29
31
  * This creates the entry point for the skill.
@@ -39,9 +41,10 @@ export declare function bundleMainIndex(indexPath: string, distDir: string): Pro
39
41
  * @param indexFile - The TypeScript source file containing the webhook
40
42
  * @param distDir - Distribution directory for output
41
43
  * @param project - Optional ts-morph Project for resolving imports
44
+ * @param debugMode - Enable verbose logging and preserve temp files
42
45
  * @returns Webhook with bundled code and schemas
43
46
  */
44
- export declare function bundleWebhook(webhook: any, indexFile: any, distDir: string, project?: any): Promise<any>;
47
+ export declare function bundleWebhook(webhook: any, indexFile: any, distDir: string, project?: any, debugMode?: boolean): Promise<any>;
45
48
  /**
46
49
  * Extracts and bundles job execute function.
47
50
  *
@@ -49,17 +52,18 @@ export declare function bundleWebhook(webhook: any, indexFile: any, distDir: str
49
52
  * @param indexFile - The TypeScript source file containing the job
50
53
  * @param distDir - Distribution directory for output
51
54
  * @param project - Optional ts-morph Project for resolving imports
55
+ * @param debugMode - Enable verbose logging and preserve temp files
52
56
  * @returns Job with bundled code
53
57
  */
54
- export declare function bundleJob(job: any, indexFile: any, distDir: string, project?: any): Promise<any>;
58
+ export declare function bundleJob(job: any, indexFile: any, distDir: string, project?: any, debugMode?: boolean): Promise<any>;
55
59
  /**
56
60
  * Bundles and compresses preprocessor execute function code.
57
61
  */
58
- export declare function bundlePreProcessor(preprocessor: any, indexFile: any, distDir: string, project?: any): Promise<any>;
62
+ export declare function bundlePreProcessor(preprocessor: any, indexFile: any, distDir: string, project?: any, debugMode?: boolean): Promise<any>;
59
63
  /**
60
64
  * Bundles and compresses postprocessor execute function code.
61
65
  */
62
- export declare function bundlePostProcessor(postprocessor: any, indexFile: any, distDir: string, project?: any): Promise<any>;
66
+ export declare function bundlePostProcessor(postprocessor: any, indexFile: any, distDir: string, project?: any, debugMode?: boolean): Promise<any>;
63
67
  /**
64
68
  * Extracts execute code and input schema from a tool.
65
69
  * This function:
@@ -7,7 +7,7 @@ import path from "path";
7
7
  import { build } from "esbuild";
8
8
  import { Project, Node } from "ts-morph";
9
9
  import { writeProgress } from "./cli.js";
10
- import { COMPILE_DIRS, COMPILE_FILES, ESBUILD_TOOL_CONFIG, ESBUILD_INDEX_CONFIG, DEFAULT_INPUT_SCHEMA, } from '../config/compile.constants.js';
10
+ import { COMPILE_DIRS, COMPILE_FILES, ESBUILD_TOOL_CONFIG, ESBUILD_INDEX_CONFIG, DEFAULT_INPUT_SCHEMA, EXTERNAL_PACKAGES, } from '../config/compile.constants.js';
11
11
  import { wrapToolForVM, createExecuteFunction, evaluateZodSchemaToJsonSchema } from './compile.js';
12
12
  /**
13
13
  * Helper function to remove lua-cli and api-exports imports from source code.
@@ -30,6 +30,156 @@ function stripLuaCliImports(sourceCode) {
30
30
  : '// lua-cli/skill imports removed - using sandbox globals';
31
31
  });
32
32
  }
33
+ /**
34
+ * Extracts and filters relevant imports from source code.
35
+ * Removes lua-cli internal classes and API imports that are available in the sandbox.
36
+ *
37
+ * @param sourceFilePath - Path to the source file
38
+ * @returns Array of relevant import statements
39
+ */
40
+ function extractRelevantImports(sourceFilePath) {
41
+ const sourceContent = fs.readFileSync(sourceFilePath, 'utf8');
42
+ // Extract all import statements from the source
43
+ const importPattern = /^import\s+.+\s+from\s+['"][^'"]+['"];?$/gm;
44
+ const imports = sourceContent.match(importPattern) || [];
45
+ // Filter to only imports that might be used (exclude lua-cli internal classes and APIs)
46
+ return imports.filter(imp => !imp.includes('LuaTool') &&
47
+ !imp.includes('LuaWebhook') &&
48
+ !imp.includes('LuaJob') &&
49
+ !imp.includes('PreProcessor') &&
50
+ !imp.includes('PostProcessor') &&
51
+ !imp.includes('LuaAgent') &&
52
+ !imp.includes('lua-cli') && // Exclude all lua-cli imports (they're available in sandbox)
53
+ !imp.includes('api-exports') && // Exclude api-exports
54
+ !imp.includes('../../../dist/api-exports') // Exclude direct api-exports imports
55
+ );
56
+ }
57
+ /**
58
+ * Generic function to bundle and compress execute function code with dependencies.
59
+ *
60
+ * @param executeFunction - Raw execute function code
61
+ * @param componentName - Name of the component (webhook/job/processor)
62
+ * @param componentType - Type identifier for temp files
63
+ * @param vmWrapperGenerator - Function that generates the VM wrapper code
64
+ * @param distDir - Distribution directory
65
+ * @param sourceFilePath - Path to source file containing the component
66
+ * @param debugMode - Enable verbose logging and preserve temp files
67
+ * @returns Compressed base64-encoded bundled code
68
+ */
69
+ async function bundleAndCompressExecuteFunction(executeFunction, componentName, componentType, vmWrapperGenerator, distDir, sourceFilePath, debugMode) {
70
+ const { compressCode } = await import('./compile.js');
71
+ // Create temporary file with the execute function wrapped as a module
72
+ const tempDir = path.join(distDir, '.temp');
73
+ if (!fs.existsSync(tempDir)) {
74
+ fs.mkdirSync(tempDir, { recursive: true });
75
+ }
76
+ const tempFile = path.join(tempDir, `${componentName}-${componentType}.ts`);
77
+ const tempOutput = path.join(tempDir, `${componentName}-${componentType}.js`);
78
+ try {
79
+ // Extract relevant imports from source file
80
+ const relevantImports = extractRelevantImports(sourceFilePath);
81
+ if (debugMode) {
82
+ console.log(` → Found ${relevantImports.length} import(s) to bundle`);
83
+ if (relevantImports.length > 0 && debugMode) {
84
+ relevantImports.forEach(imp => console.log(` - ${imp}`));
85
+ }
86
+ }
87
+ // Write execute function as a module export with all relevant imports
88
+ const moduleCode = `
89
+ // ${componentType} execute function for ${componentName}
90
+ ${relevantImports.join('\n')}
91
+
92
+ // The execute function with all dependencies available
93
+ const executeFunc = ${executeFunction};
94
+
95
+ export default executeFunc;
96
+ `;
97
+ fs.writeFileSync(tempFile, moduleCode);
98
+ if (debugMode) {
99
+ console.log(` → Created temp file: ${path.basename(tempFile)}`);
100
+ }
101
+ // Bundle with esbuild using the source file's directory for import resolution
102
+ await build({
103
+ entryPoints: [tempFile],
104
+ bundle: true,
105
+ platform: 'node',
106
+ target: 'node16',
107
+ format: 'cjs',
108
+ minify: true,
109
+ outfile: tempOutput,
110
+ external: [...EXTERNAL_PACKAGES], // Use same external packages as tools
111
+ plugins: [sandboxGlobalsPlugin],
112
+ absWorkingDir: path.dirname(sourceFilePath), // Use source file's directory for resolution
113
+ });
114
+ // Read bundled code and validate it exists and has content
115
+ if (!fs.existsSync(tempOutput)) {
116
+ throw new Error(`esbuild failed to create output file for ${componentName}`);
117
+ }
118
+ const bundledCode = fs.readFileSync(tempOutput, 'utf8');
119
+ if (bundledCode.length === 0) {
120
+ throw new Error(`esbuild created empty bundle for ${componentName}`);
121
+ }
122
+ // Wrap for VM execution using provided generator
123
+ const wrappedCode = vmWrapperGenerator(bundledCode);
124
+ // Compress the wrapped code
125
+ const compressed = compressCode(wrappedCode);
126
+ if (debugMode) {
127
+ console.log(` → Bundle size: ${(bundledCode.length / 1024).toFixed(2)}KB (uncompressed)`);
128
+ console.log(` → Compressed: ${(compressed.length / 1024).toFixed(2)}KB (base64 gzip)`);
129
+ }
130
+ // Clean up temp files (unless in debug mode)
131
+ if (!debugMode) {
132
+ try {
133
+ fs.unlinkSync(tempFile);
134
+ fs.unlinkSync(tempOutput);
135
+ }
136
+ catch (cleanupError) {
137
+ // Ignore cleanup errors
138
+ }
139
+ }
140
+ else {
141
+ console.log(` ā„¹ļø Preserved temp files for debugging:`);
142
+ console.log(` - ${tempFile}`);
143
+ console.log(` - ${tempOutput}`);
144
+ }
145
+ return compressed;
146
+ }
147
+ catch (error) {
148
+ // Provide helpful error messages based on error type
149
+ let errorMessage = `Warning: Could not bundle ${componentType} ${componentName}`;
150
+ if (error.message && error.message.includes('Could not resolve')) {
151
+ errorMessage += `\n Dependency resolution failed: ${error.message}`;
152
+ errorMessage += `\n Hint: Ensure all imported packages are in package.json and run 'npm install'`;
153
+ }
154
+ else if (error.message && error.message.includes('Transform failed')) {
155
+ errorMessage += `\n TypeScript compilation failed: ${error.message}`;
156
+ errorMessage += `\n Hint: Check syntax in ${path.basename(sourceFilePath)}`;
157
+ }
158
+ else {
159
+ errorMessage += `: ${error.message || error}`;
160
+ }
161
+ console.warn(errorMessage);
162
+ if (debugMode && error.stack) {
163
+ console.error(' Full stack trace:', error.stack);
164
+ }
165
+ // Clean up on error (unless in debug mode)
166
+ if (!debugMode) {
167
+ try {
168
+ if (fs.existsSync(tempFile))
169
+ fs.unlinkSync(tempFile);
170
+ if (fs.existsSync(tempOutput))
171
+ fs.unlinkSync(tempOutput);
172
+ }
173
+ catch (cleanupError) {
174
+ // Ignore cleanup errors
175
+ }
176
+ }
177
+ else {
178
+ console.log(` ā„¹ļø Temp files preserved for debugging (check dist/.temp/)`);
179
+ }
180
+ return '';
181
+ }
182
+ }
33
183
  /**
34
184
  * esbuild plugin to inject sandbox globals instead of requiring lua-cli
35
185
  * This removes require("lua-cli") statements and injects the global API objects
@@ -109,9 +259,14 @@ export const sandboxGlobalsPlugin = {
109
259
  *
110
260
  * @param tool - The tool to bundle
111
261
  * @param distDir - The distribution directory for output
262
+ * @param modifiedSource - Optional modified source (for pre-bundled jobs)
263
+ * @param debugMode - Enable verbose logging and preserve temp files
112
264
  */
113
- export async function bundleTool(tool, distDir, modifiedSource) {
114
- writeProgress(`šŸ“¦ Bundling ${tool.className}...`);
265
+ export async function bundleTool(tool, distDir, modifiedSource, debugMode) {
266
+ if (!debugMode)
267
+ writeProgress(`šŸ“¦ Bundling ${tool.className}...`);
268
+ if (debugMode)
269
+ console.log(` → Bundling ${tool.className}...`);
115
270
  try {
116
271
  const outputPath = path.join(distDir, COMPILE_DIRS.TOOLS, `${tool.className}.js`);
117
272
  let entryPoint = tool.filePath;
@@ -175,8 +330,8 @@ export async function bundleTool(tool, distDir, modifiedSource) {
175
330
  }
176
331
  // Wrap the bundled code for VM execution environment
177
332
  await wrapToolForVM(outputPath, tool);
178
- // Clean up temp file if created
179
- if (tempFile && fs.existsSync(tempFile)) {
333
+ // Clean up temp file if created (unless in debug mode)
334
+ if (tempFile && fs.existsSync(tempFile) && !debugMode) {
180
335
  try {
181
336
  fs.unlinkSync(tempFile);
182
337
  }
@@ -184,9 +339,16 @@ export async function bundleTool(tool, distDir, modifiedSource) {
184
339
  // Ignore cleanup errors
185
340
  }
186
341
  }
342
+ else if (tempFile && debugMode) {
343
+ if (debugMode)
344
+ console.log(` ā„¹ļø Preserved temp file: ${tempFile}`);
345
+ }
187
346
  }
188
347
  catch (error) {
189
348
  console.warn(`Warning: Failed to bundle ${tool.className}:`, error);
349
+ if (debugMode && error instanceof Error) {
350
+ console.error(' Stack trace:', error.stack);
351
+ }
190
352
  }
191
353
  }
192
354
  /**
@@ -216,16 +378,19 @@ export async function bundleMainIndex(indexPath, distDir) {
216
378
  * @param indexFile - The TypeScript source file containing the webhook
217
379
  * @param distDir - Distribution directory for output
218
380
  * @param project - Optional ts-morph Project for resolving imports
381
+ * @param debugMode - Enable verbose logging and preserve temp files
219
382
  * @returns Webhook with bundled code and schemas
220
383
  */
221
- export async function bundleWebhook(webhook, indexFile, distDir, project) {
222
- writeProgress(`šŸ“¦ Bundling webhook ${webhook.name}...`);
384
+ export async function bundleWebhook(webhook, indexFile, distDir, project, debugMode) {
385
+ if (!debugMode)
386
+ writeProgress(`šŸ“¦ Bundling webhook ${webhook.name}...`);
223
387
  try {
224
388
  // Find the LuaWebhook constructor in the AST
225
389
  let executeFunction = '';
226
390
  let querySchema = undefined;
227
391
  let headerSchema = undefined;
228
392
  let bodySchema = undefined;
393
+ let sourceFilePath = indexFile.getFilePath();
229
394
  // Helper function to search for webhook in a file
230
395
  const searchFileForWebhook = (file) => {
231
396
  file.forEachDescendant((node) => {
@@ -271,6 +436,8 @@ export async function bundleWebhook(webhook, indexFile, distDir, project) {
271
436
  querySchema = foundQuerySchema;
272
437
  headerSchema = foundHeaderSchema;
273
438
  bodySchema = foundBodySchema;
439
+ // Update source file path to the file where webhook was found
440
+ sourceFilePath = file.getFilePath();
274
441
  }
275
442
  }
276
443
  }
@@ -315,7 +482,7 @@ export async function bundleWebhook(webhook, indexFile, distDir, project) {
315
482
  // Bundle and compress the execute function (like tools)
316
483
  let compressedCode = '';
317
484
  if (executeFunction) {
318
- compressedCode = await bundleAndCompressWebhookCode(executeFunction, webhook.name, distDir);
485
+ compressedCode = await bundleAndCompressWebhookCode(executeFunction, webhook.name, distDir, sourceFilePath, debugMode);
319
486
  }
320
487
  return {
321
488
  ...webhook,
@@ -332,44 +499,17 @@ export async function bundleWebhook(webhook, indexFile, distDir, project) {
332
499
  /**
333
500
  * Bundles and compresses webhook execute function code.
334
501
  * Creates a temporary file, bundles with esbuild, compresses with gzip.
502
+ * Includes all imports from the source file to ensure dependencies are bundled.
335
503
  *
336
504
  * @param executeFunction - Raw execute function code
337
505
  * @param webhookName - Name of the webhook
338
506
  * @param distDir - Distribution directory
507
+ * @param sourceFilePath - Path to the source file containing the webhook
508
+ * @param debugMode - Enable verbose logging and preserve temp files
339
509
  * @returns Compressed base64-encoded bundled code
340
510
  */
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) => {
511
+ async function bundleAndCompressWebhookCode(executeFunction, webhookName, distDir, sourceFilePath, debugMode) {
512
+ return bundleAndCompressExecuteFunction(executeFunction, webhookName, 'webhook', (bundledCode) => `(async (query, headers, body) => {
373
513
 
374
514
  // Execute the bundled webhook code
375
515
  ${bundledCode}
@@ -379,33 +519,7 @@ export default ${executeFunction};
379
519
 
380
520
  // Execute with three separate parameters (not an object)
381
521
  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
- }
522
+ })`, distDir, sourceFilePath, debugMode);
409
523
  }
410
524
  /**
411
525
  * Extracts and bundles job execute function.
@@ -414,13 +528,16 @@ export default ${executeFunction};
414
528
  * @param indexFile - The TypeScript source file containing the job
415
529
  * @param distDir - Distribution directory for output
416
530
  * @param project - Optional ts-morph Project for resolving imports
531
+ * @param debugMode - Enable verbose logging and preserve temp files
417
532
  * @returns Job with bundled code
418
533
  */
419
- export async function bundleJob(job, indexFile, distDir, project) {
420
- writeProgress(`šŸ“¦ Bundling job ${job.name}...`);
534
+ export async function bundleJob(job, indexFile, distDir, project, debugMode) {
535
+ if (!debugMode)
536
+ writeProgress(`šŸ“¦ Bundling job ${job.name}...`);
421
537
  try {
422
538
  // Find the LuaJob constructor in the AST
423
539
  let executeFunction = '';
540
+ let sourceFilePath = indexFile.getFilePath();
424
541
  // Helper function to search for job in a file
425
542
  const searchFileForJob = (file) => {
426
543
  file.forEachDescendant((node) => {
@@ -451,6 +568,8 @@ export async function bundleJob(job, indexFile, distDir, project) {
451
568
  // Only set executeFunction if this is the matching job
452
569
  if (isMatchingJob && foundExecute) {
453
570
  executeFunction = foundExecute;
571
+ // Update source file path to the file where job was found
572
+ sourceFilePath = file.getFilePath();
454
573
  }
455
574
  }
456
575
  }
@@ -484,7 +603,7 @@ export async function bundleJob(job, indexFile, distDir, project) {
484
603
  // Bundle and compress the execute function (like tools)
485
604
  let compressedCode = '';
486
605
  if (executeFunction) {
487
- compressedCode = await bundleAndCompressJobCode(executeFunction, job.name, distDir);
606
+ compressedCode = await bundleAndCompressJobCode(executeFunction, job.name, distDir, sourceFilePath, debugMode);
488
607
  }
489
608
  return {
490
609
  ...job,
@@ -500,44 +619,17 @@ export async function bundleJob(job, indexFile, distDir, project) {
500
619
  /**
501
620
  * Bundles and compresses job execute function code.
502
621
  * Creates a temporary file, bundles with esbuild, compresses with gzip.
622
+ * Includes all imports from the source file to ensure dependencies are bundled.
503
623
  *
504
624
  * @param executeFunction - Raw execute function code
505
625
  * @param jobName - Name of the job
506
626
  * @param distDir - Distribution directory
627
+ * @param sourceFilePath - Path to the source file containing the job
628
+ * @param debugMode - Enable verbose logging and preserve temp files
507
629
  * @returns Compressed base64-encoded bundled code
508
630
  */
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) => {
631
+ async function bundleAndCompressJobCode(executeFunction, jobName, distDir, sourceFilePath, debugMode) {
632
+ return bundleAndCompressExecuteFunction(executeFunction, jobName, 'job', (bundledCode) => `(async (job) => {
541
633
  // Execute the bundled job code
542
634
  ${bundledCode}
543
635
 
@@ -546,41 +638,17 @@ export default ${executeFunction};
546
638
 
547
639
  // Execute job with job instance parameter
548
640
  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
- }
641
+ })`, distDir, sourceFilePath, debugMode);
576
642
  }
577
643
  /**
578
644
  * Bundles and compresses preprocessor execute function code.
579
645
  */
580
- export async function bundlePreProcessor(preprocessor, indexFile, distDir, project) {
581
- writeProgress(`šŸ“¦ Bundling preprocessor ${preprocessor.name}...`);
646
+ export async function bundlePreProcessor(preprocessor, indexFile, distDir, project, debugMode) {
647
+ if (!debugMode)
648
+ writeProgress(`šŸ“¦ Bundling preprocessor ${preprocessor.name}...`);
582
649
  try {
583
650
  let executeFunction = '';
651
+ let sourceFilePath = indexFile.getFilePath();
584
652
  const searchFileForPreProcessor = (file) => {
585
653
  file.forEachDescendant((node) => {
586
654
  if (Node.isNewExpression(node)) {
@@ -608,6 +676,8 @@ export async function bundlePreProcessor(preprocessor, indexFile, distDir, proje
608
676
  });
609
677
  if (isMatching && foundExecute) {
610
678
  executeFunction = foundExecute;
679
+ // Update source file path to the file where preprocessor was found
680
+ sourceFilePath = file.getFilePath();
611
681
  }
612
682
  }
613
683
  }
@@ -639,7 +709,7 @@ export async function bundlePreProcessor(preprocessor, indexFile, distDir, proje
639
709
  }
640
710
  let compressedCode = '';
641
711
  if (executeFunction) {
642
- compressedCode = await bundleAndCompressProcessorCode(executeFunction, preprocessor.name, 'pre', distDir);
712
+ compressedCode = await bundleAndCompressProcessorCode(executeFunction, preprocessor.name, 'pre', distDir, sourceFilePath, debugMode);
643
713
  }
644
714
  return {
645
715
  name: preprocessor.name,
@@ -659,10 +729,12 @@ export async function bundlePreProcessor(preprocessor, indexFile, distDir, proje
659
729
  /**
660
730
  * Bundles and compresses postprocessor execute function code.
661
731
  */
662
- export async function bundlePostProcessor(postprocessor, indexFile, distDir, project) {
663
- writeProgress(`šŸ“¦ Bundling postprocessor ${postprocessor.name}...`);
732
+ export async function bundlePostProcessor(postprocessor, indexFile, distDir, project, debugMode) {
733
+ if (!debugMode)
734
+ writeProgress(`šŸ“¦ Bundling postprocessor ${postprocessor.name}...`);
664
735
  try {
665
736
  let executeFunction = '';
737
+ let sourceFilePath = indexFile.getFilePath();
666
738
  const searchFileForPostProcessor = (file) => {
667
739
  file.forEachDescendant((node) => {
668
740
  if (Node.isNewExpression(node)) {
@@ -690,6 +762,8 @@ export async function bundlePostProcessor(postprocessor, indexFile, distDir, pro
690
762
  });
691
763
  if (isMatching && foundExecute) {
692
764
  executeFunction = foundExecute;
765
+ // Update source file path to the file where postprocessor was found
766
+ sourceFilePath = file.getFilePath();
693
767
  }
694
768
  }
695
769
  }
@@ -721,7 +795,7 @@ export async function bundlePostProcessor(postprocessor, indexFile, distDir, pro
721
795
  }
722
796
  let compressedCode = '';
723
797
  if (executeFunction) {
724
- compressedCode = await bundleAndCompressProcessorCode(executeFunction, postprocessor.name, 'post', distDir);
798
+ compressedCode = await bundleAndCompressProcessorCode(executeFunction, postprocessor.name, 'post', distDir, sourceFilePath, debugMode);
725
799
  }
726
800
  return {
727
801
  name: postprocessor.name,
@@ -740,65 +814,18 @@ export async function bundlePostProcessor(postprocessor, indexFile, distDir, pro
740
814
  }
741
815
  /**
742
816
  * Bundles and compresses processor execute function code.
817
+ * Includes all imports from the source file to ensure dependencies are bundled.
743
818
  */
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}) => {
819
+ async function bundleAndCompressProcessorCode(executeFunction, processorName, type, distDir, sourceFilePath, debugMode) {
820
+ // Processor execute functions receive: (user, message, [response], channel)
821
+ const paramList = type === 'pre' ? 'user, message, channel' : 'user, message, response, channel';
822
+ return bundleAndCompressExecuteFunction(executeFunction, processorName, `${type}processor`, (bundledCode) => `(async (${paramList}) => {
773
823
  // Execute the bundled processor code
774
824
  ${bundledCode}
775
825
 
776
826
  const executeFunction = module.exports.default || module.exports;
777
827
  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
- }
828
+ })`, distDir, sourceFilePath, debugMode);
802
829
  }
803
830
  /**
804
831
  * Extracts execute code and input schema from a tool.
@@ -12,7 +12,10 @@ export declare function compressCode(code: string): string;
12
12
  */
13
13
  export declare function findIndexFile(): string;
14
14
  /**
15
- * Resolves import path from module specifier
15
+ * Resolves import path from module specifier with support for multiple extensions and index files
16
+ * @param moduleSpecifier - The import path (e.g., './tools/MyTool' or '../utils')
17
+ * @param currentFilePath - The file doing the importing
18
+ * @returns Resolved absolute file path
16
19
  */
17
20
  export declare function resolveImportPath(moduleSpecifier: string, currentFilePath: string): string;
18
21
  /**
@@ -29,16 +29,37 @@ export function findIndexFile() {
29
29
  throw new Error("index.ts not found in current directory or src/ directory");
30
30
  }
31
31
  /**
32
- * Resolves import path from module specifier
32
+ * Resolves import path from module specifier with support for multiple extensions and index files
33
+ * @param moduleSpecifier - The import path (e.g., './tools/MyTool' or '../utils')
34
+ * @param currentFilePath - The file doing the importing
35
+ * @returns Resolved absolute file path
33
36
  */
34
37
  export function resolveImportPath(moduleSpecifier, currentFilePath) {
38
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
35
39
  if (moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../')) {
36
40
  // Relative import - resolve relative to current file
37
41
  const currentDir = path.dirname(currentFilePath);
38
- return path.resolve(currentDir, moduleSpecifier + '.ts');
42
+ const basePath = path.resolve(currentDir, moduleSpecifier);
43
+ // Try each extension in order
44
+ for (const ext of extensions) {
45
+ const fullPath = ext.startsWith('/') ? path.join(basePath, ext) : basePath + ext;
46
+ if (fs.existsSync(fullPath)) {
47
+ return fullPath;
48
+ }
49
+ }
50
+ // Fallback: return .ts extension (original behavior)
51
+ return basePath + '.ts';
39
52
  }
40
53
  else {
41
- // Absolute import - assume it's in the project
54
+ // Absolute import - check in src/ directory
55
+ const srcBase = path.resolve(process.cwd(), 'src', moduleSpecifier);
56
+ for (const ext of extensions) {
57
+ const fullPath = ext.startsWith('/') ? path.join(srcBase, ext) : srcBase + ext;
58
+ if (fs.existsSync(fullPath)) {
59
+ return fullPath;
60
+ }
61
+ }
62
+ // Fallback: return .ts in src/ (original behavior)
42
63
  return path.resolve(process.cwd(), 'src', moduleSpecifier + '.ts');
43
64
  }
44
65
  }
@@ -56,6 +56,10 @@ function readPackageJson() {
56
56
  */
57
57
  export async function createLegacyDeploymentData(tools, luaDir, indexFile, agentData) {
58
58
  const config = readSkillConfig();
59
+ // Handle null config gracefully
60
+ if (!config) {
61
+ console.warn('āš ļø Warning: lua.skill.yaml not found. Creating deployment with default configuration.');
62
+ }
59
63
  let skillsMetadata;
60
64
  // Check if we have agent data (from LuaAgent approach)
61
65
  if (agentData && agentData.skills && agentData.skills.length > 0) {
@@ -75,9 +79,9 @@ export async function createLegacyDeploymentData(tools, luaDir, indexFile, agent
75
79
  // Build skills array with their associated tools
76
80
  const skillsArray = buildSkillsArray(skillsMetadata, skillToTools, tools);
77
81
  // Ensure all skills exist in YAML config and have valid IDs
78
- const updatedSkillsArray = await ensureSkillsExistInYaml(skillsArray, config);
82
+ const updatedSkillsArray = config ? await ensureSkillsExistInYaml(skillsArray, config) : skillsArray;
79
83
  // Override versions from YAML config if they exist
80
- const finalSkillsArray = overrideVersionsFromConfig(updatedSkillsArray, config);
84
+ const finalSkillsArray = config ? overrideVersionsFromConfig(updatedSkillsArray, config) : updatedSkillsArray;
81
85
  // Write deployment data
82
86
  const deployData = {
83
87
  skills: finalSkillsArray
@@ -169,6 +173,10 @@ function buildSkillsArray(skillsMetadata, skillToTools, tools) {
169
173
  * @returns Skills array with versions overridden from config
170
174
  */
171
175
  function overrideVersionsFromConfig(skillsArray, config) {
176
+ // Handle null config
177
+ if (!config) {
178
+ return skillsArray;
179
+ }
172
180
  // Get version map from YAML config
173
181
  const configVersionMap = new Map();
174
182
  if (config.skills && Array.isArray(config.skills)) {
@@ -22,5 +22,6 @@ export declare function preBundleJobsInSource(sourceFilePath: string, project: P
22
22
  /**
23
23
  * Replaces placeholders in bundled code with actual job bundles
24
24
  * The bundled job code is NOT compressed - it's JavaScript that will be part of the tool
25
+ * Validates that all placeholders are found and replaced
25
26
  */
26
27
  export declare function replaceJobPlaceholders(bundledCode: string, jobBundles: Map<string, string>): string;
@@ -162,15 +162,27 @@ export default executeFunc;
162
162
  /**
163
163
  * Replaces placeholders in bundled code with actual job bundles
164
164
  * The bundled job code is NOT compressed - it's JavaScript that will be part of the tool
165
+ * Validates that all placeholders are found and replaced
165
166
  */
166
167
  export function replaceJobPlaceholders(bundledCode, jobBundles) {
167
168
  let result = bundledCode;
169
+ const replacementsMade = new Map();
168
170
  jobBundles.forEach((bundledJobCode, placeholder) => {
169
171
  // The placeholder is used as an identifier (not a string), just replace it directly
170
172
  // It appears as: execute: __BUNDLED_JOB_EXECUTE___1
171
- const placeholderPattern = new RegExp(placeholder, 'g');
173
+ // Use word boundary to prevent partial matches
174
+ const placeholderPattern = new RegExp(`\\b${placeholder}\\b`, 'g');
175
+ const beforeLength = result.length;
172
176
  result = result.replace(placeholderPattern, bundledJobCode);
173
- // console.log(`[PreBundleJobs] āœ… Replaced ${placeholder} with bundled code (${(bundledJobCode.length / 1024).toFixed(1)}KB)`);
177
+ const wasReplaced = result.length !== beforeLength;
178
+ replacementsMade.set(placeholder, wasReplaced);
179
+ if (wasReplaced) {
180
+ // console.log(`[PreBundleJobs] āœ… Replaced ${placeholder} with bundled code (${(bundledJobCode.length / 1024).toFixed(1)}KB)`);
181
+ }
182
+ else {
183
+ console.warn(`āš ļø Warning: Placeholder ${placeholder} not found in bundled code. Nested job may not work correctly.`);
184
+ console.warn(` This can happen if esbuild mangled the identifier during minification.`);
185
+ }
174
186
  });
175
187
  return result;
176
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lua-cli",
3
- "version": "3.0.2-alpha.1",
3
+ "version": "3.0.2-alpha.3",
4
4
  "description": "Build, test, and deploy AI agents with custom tools, webhooks, and scheduled jobs. Features LuaAgent unified configuration, streaming chat, and batch deployment.",
5
5
  "readmeFilename": "README.md",
6
6
  "main": "dist/api-exports.js",
@@ -89,6 +89,7 @@
89
89
  "@types/inquirer": "^9.0.9",
90
90
  "@types/jest": "^29.5.8",
91
91
  "@types/js-yaml": "^4.0.9",
92
+ "@types/lodash": "^4.17.20",
92
93
  "@types/node": "^24.5.1",
93
94
  "@types/node-fetch": "^2.6.13",
94
95
  "@types/react": "^18.2.0",
@@ -96,7 +97,10 @@
96
97
  "@types/ws": "^8.18.1",
97
98
  "@typescript-eslint/parser": "^8.44.1",
98
99
  "@typescript-eslint/typescript-estree": "^8.44.1",
100
+ "date-fns": "^4.1.0",
99
101
  "jest": "^29.7.0",
102
+ "lodash": "^4.17.21",
103
+ "stripe": "^19.2.0",
100
104
  "ts-jest": "^29.1.1",
101
105
  "ts-node": "^10.9.2",
102
106
  "typescript": "^5.9.2"
@@ -0,0 +1 @@
1
+ skills: []
@@ -20,7 +20,7 @@
20
20
  "inquirer": "^12.9.6",
21
21
  "stripe": "^17.5.0",
22
22
  "js-yaml": "^4.1.0",
23
- "lua-cli": "^3.0.2-alpha.1",
23
+ "lua-cli": "file:..",
24
24
  "openai": "^5.23.0",
25
25
  "uuid": "^13.0.0",
26
26
  "zod": "^3.24.1"
@@ -1,17 +0,0 @@
1
- /**
2
- * Dynamic Job Bundler
3
- * Handles detection and bundling of Jobs.create() calls within execute functions
4
- */
5
- /**
6
- * Detects if code contains Jobs.create() calls
7
- */
8
- export declare function containsJobsCreate(code: string): boolean;
9
- /**
10
- * Extracts Jobs.create() execute functions and bundles them with dependencies.
11
- * Finds execute functions in the code and creates standalone bundles.
12
- */
13
- export declare function bundleNestedJobs(code: string, parentName: string, distDir: string): Promise<string>;
14
- /**
15
- * Cleans up temporary bundling directory
16
- */
17
- export declare function cleanupTempDir(distDir: string): void;
@@ -1,143 +0,0 @@
1
- /**
2
- * Dynamic Job Bundler
3
- * Handles detection and bundling of Jobs.create() calls within execute functions
4
- */
5
- import { build } from 'esbuild';
6
- import fs from 'fs';
7
- import path from 'path';
8
- import { compressCode } from './compile.js';
9
- import { sandboxGlobalsPlugin } from './bundling.js';
10
- /**
11
- * Detects if code contains Jobs.create() calls
12
- */
13
- export function containsJobsCreate(code) {
14
- const found = /Jobs\.create\s*\(/i.test(code);
15
- if (found) {
16
- console.log('[DynamicJobs] āœ… Detected Jobs.create() in code');
17
- }
18
- return found;
19
- }
20
- /**
21
- * Extracts Jobs.create() execute functions and bundles them with dependencies.
22
- * Finds execute functions in the code and creates standalone bundles.
23
- */
24
- export async function bundleNestedJobs(code, parentName, distDir) {
25
- if (!containsJobsCreate(code)) {
26
- return code;
27
- }
28
- console.log(`[DynamicJobs] āš ļø Jobs.create() with external dependencies not yet fully supported`);
29
- console.log(`[DynamicJobs] šŸ’” Recommendation: Use fetch() or lua-cli APIs in job execute functions`);
30
- console.log(`[DynamicJobs] šŸ“‹ Full bundling support planned for v3.1.0`);
31
- // Return original code as-is
32
- // The job will have access to lua-cli globals but external packages won't be bundled
33
- return code;
34
- }
35
- /**
36
- * Helper to extract job execute function from code
37
- */
38
- function extractJobExecuteFromCode(code) {
39
- // This extracts the execute function body
40
- // For now, return a placeholder that indicates the limitation
41
- return `
42
- // Note: This job execute function runs with limited context
43
- // Use fetch() for external APIs or lua-cli globals (User, Data, Products)
44
- console.log('Job execute running with user:', user);
45
- return { executed: true };
46
- `;
47
- }
48
- /**
49
- * Bundles a job's execute function independently
50
- */
51
- async function bundleJobExecuteFunction(executeFunction, jobName, distDir) {
52
- const tempDir = path.join(distDir, '.temp');
53
- if (!fs.existsSync(tempDir)) {
54
- fs.mkdirSync(tempDir, { recursive: true });
55
- }
56
- const tempFile = path.join(tempDir, `${jobName}.js`);
57
- const tempOutput = path.join(tempDir, `${jobName}-bundled.js`);
58
- try {
59
- // Extract imports that the execute function might need
60
- // Common ones: Stripe, axios, etc.
61
- const moduleCode = `
62
- // Job execute function for ${jobName}
63
- // Import dependencies that might be used
64
- import Stripe from 'stripe';
65
- import { env, User, Data, Products, Baskets, Orders } from 'lua-cli';
66
-
67
- // The execute function with all dependencies available
68
- const executeFunc = ${executeFunction};
69
-
70
- // Export as default for esbuild to bundle
71
- export default executeFunc;
72
- `;
73
- fs.writeFileSync(tempFile, moduleCode);
74
- // Bundle with esbuild - bundle ALL dependencies (including Stripe, axios, etc.)
75
- await build({
76
- entryPoints: [tempFile],
77
- bundle: true,
78
- platform: 'node',
79
- target: 'node16',
80
- format: 'cjs',
81
- minify: true,
82
- outfile: tempOutput,
83
- external: [], // Bundle everything except lua-cli APIs (handled by plugin)
84
- plugins: [sandboxGlobalsPlugin], // This handles lua-cli globals
85
- // Important: Don't externalize packages like stripe, axios, etc.
86
- // They need to be included in the bundle
87
- });
88
- // Read bundled code (includes all dependencies like Stripe, axios, etc.)
89
- let bundledCode = fs.readFileSync(tempOutput, 'utf8');
90
- // Log bundle size for debugging
91
- console.log(`[DynamicJobs] Bundled size for ${jobName}: ${(bundledCode.length / 1024).toFixed(2)}KB`);
92
- // Wrap for VM execution - the bundled code includes ALL dependencies
93
- const wrappedCode = `(async (job) => {
94
-
95
- ${bundledCode}
96
-
97
- // Get the execute function from exports
98
- const executeFunction = module.exports || module.exports.default;
99
-
100
- // Execute with job
101
- return await executeFunction(job);
102
- })`;
103
- // Compress the wrapped code
104
- const compressed = compressCode(wrappedCode);
105
- // Clean up temp files
106
- try {
107
- fs.unlinkSync(tempFile);
108
- fs.unlinkSync(tempOutput);
109
- }
110
- catch (cleanupError) {
111
- // Ignore cleanup errors
112
- }
113
- return compressed;
114
- }
115
- catch (error) {
116
- console.warn(`Warning: Could not bundle job execute function ${jobName}:`, error);
117
- // Clean up on error
118
- try {
119
- if (fs.existsSync(tempFile))
120
- fs.unlinkSync(tempFile);
121
- if (fs.existsSync(tempOutput))
122
- fs.unlinkSync(tempOutput);
123
- }
124
- catch (cleanupError) {
125
- // Ignore cleanup errors
126
- }
127
- return '';
128
- }
129
- }
130
- /**
131
- * Cleans up temporary bundling directory
132
- */
133
- export function cleanupTempDir(distDir) {
134
- const tempDir = path.join(distDir, '.temp');
135
- if (fs.existsSync(tempDir)) {
136
- try {
137
- fs.rmSync(tempDir, { recursive: true, force: true });
138
- }
139
- catch (error) {
140
- // Ignore cleanup errors
141
- }
142
- }
143
- }