lua-cli 1.3.2-alpha.0 → 1.3.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.
@@ -1,1004 +1,661 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { gzipSync, gunzipSync } from "zlib";
4
- import { Buffer } from "buffer";
3
+ import { gzipSync } from "zlib";
5
4
  import { withErrorHandling, writeProgress, writeSuccess } from "../utils/cli.js";
6
5
  import { readSkillConfig } from '../utils/files.js';
7
- import * as ts from "typescript";
6
+ import { Project, Node } from "ts-morph";
7
+ import { build } from "esbuild";
8
8
  // Compression utilities
9
9
  function compressCode(code) {
10
10
  const compressed = gzipSync(code);
11
11
  return compressed.toString('base64');
12
12
  }
13
- function decompressCode(compressedCode) {
14
- const buffer = Buffer.from(compressedCode, 'base64');
15
- return gunzipSync(buffer).toString('utf8');
16
- }
17
13
  export async function compileCommand() {
18
14
  return withErrorHandling(async () => {
19
15
  writeProgress("🔨 Compiling Lua skill...");
20
- // Read package.json to get version and name
21
- const packageJsonPath = path.join(process.cwd(), "package.json");
22
- if (!fs.existsSync(packageJsonPath)) {
23
- throw new Error("package.json not found in current directory");
24
- }
25
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
26
- const packageVersion = packageJson.version || "1.0.0";
27
- // Get skill name from config file, fallback to package.json name
28
- const config = readSkillConfig();
29
- const skillName = config?.skill?.name || packageJson.name || "lua-skill";
30
- // Read index.ts file (check both root and src directory)
31
- let indexPath = path.join(process.cwd(), "index.ts");
32
- if (!fs.existsSync(indexPath)) {
33
- indexPath = path.join(process.cwd(), "src", "index.ts");
34
- if (!fs.existsSync(indexPath)) {
35
- throw new Error("index.ts not found in current directory or src/ directory");
36
- }
37
- }
38
- const indexContent = fs.readFileSync(indexPath, "utf8");
39
- // Extract skill information
40
- const skillInfo = await extractSkillInfo(indexContent, indexPath);
41
- // Extract skill metadata from LuaSkill constructor and config file
42
- const skillMetadata = await extractSkillMetadata(indexContent);
43
- // Use version from config file, fallback to package.json version
44
- const version = skillMetadata.version || packageVersion || "1.0.0";
45
- // Create deployment data with compressed execute code
46
- const deployData = {
47
- version,
48
- name: skillName,
49
- skillsName: skillName,
50
- skillId: skillMetadata.skillId,
51
- description: skillMetadata.description,
52
- context: skillMetadata.context,
53
- tools: skillInfo.map(tool => ({
54
- ...tool,
55
- execute: compressCode(tool.execute)
56
- }))
57
- };
58
- // Create .lua directory
16
+ // Clean up old directories
17
+ const distDir = path.join(process.cwd(), "dist");
59
18
  const luaDir = path.join(process.cwd(), ".lua");
60
- if (!fs.existsSync(luaDir)) {
61
- fs.mkdirSync(luaDir, { recursive: true });
62
- }
63
- // Write JSON output to .lua directory
64
- const jsonOutputPath = path.join(luaDir, "deploy.json");
65
- fs.writeFileSync(jsonOutputPath, JSON.stringify(deployData, null, 2));
66
- // Write individual tool files to .lua directory
67
- for (const tool of skillInfo) {
68
- const toolFilePath = path.join(luaDir, `${tool.name}.js`);
69
- fs.writeFileSync(toolFilePath, tool.execute);
70
- }
71
- writeSuccess("✅ Skill compiled successfully");
72
- }, "compilation");
73
- }
74
- async function extractSkillInfo(indexContent, indexFilePath) {
75
- const tools = [];
76
- const toolArguments = [];
77
- try {
78
- // Create a TypeScript source file
79
- const sourceFile = ts.createSourceFile(indexFilePath, indexContent, ts.ScriptTarget.Latest, true);
80
- // Traverse the AST to find skill.addTool() calls
81
- function visit(node) {
82
- // Check for skill.addTool() calls
83
- if (ts.isCallExpression(node) &&
84
- ts.isPropertyAccessExpression(node.expression) &&
85
- ts.isIdentifier(node.expression.expression) &&
86
- node.expression.expression.text === 'skill' &&
87
- ts.isIdentifier(node.expression.name) &&
88
- node.expression.name.text === 'addTool') {
89
- // Check if this call is commented out
90
- if (isNodeCommentedOut(node, sourceFile)) {
91
- return;
92
- }
93
- // Collect the tool argument for later processing
94
- if (node.arguments.length > 0) {
95
- const toolArgument = node.arguments[0];
96
- toolArguments.push(toolArgument);
97
- }
98
- }
99
- // Continue traversing
100
- ts.forEachChild(node, visit);
101
- }
102
- visit(sourceFile);
103
- // Process all collected tool arguments
104
- for (const toolArgument of toolArguments) {
105
- await processToolArgument(toolArgument, sourceFile, indexContent, tools);
106
- }
107
- }
108
- catch (error) {
109
- console.warn('Warning: Could not parse TypeScript AST, falling back to regex approach:', error);
110
- // Fallback to the old regex approach if AST parsing fails
111
- return await extractSkillInfoRegex(indexContent, indexFilePath);
112
- }
113
- return tools;
114
- }
115
- function isNodeCommentedOut(node, sourceFile) {
116
- const nodeStart = node.getStart(sourceFile);
117
- const nodeLine = sourceFile.getLineAndCharacterOfPosition(nodeStart).line;
118
- // Get the text of the line up to the node
119
- const lineStart = sourceFile.getPositionOfLineAndCharacter(nodeLine, 0);
120
- const lineText = sourceFile.text.substring(lineStart, nodeStart);
121
- // Check if the line starts with a comment
122
- const trimmedLine = lineText.trim();
123
- return trimmedLine.startsWith('//') ||
124
- trimmedLine.startsWith('*') ||
125
- trimmedLine.startsWith('#');
126
- }
127
- async function processToolArgument(toolArgument, sourceFile, indexContent, tools) {
128
- if (ts.isObjectLiteralExpression(toolArgument)) {
129
- // Inline tool definition: skill.addTool({...})
130
- await processInlineTool(toolArgument, sourceFile, indexContent, tools);
131
- }
132
- else if (ts.isNewExpression(toolArgument)) {
133
- // Class-based tool: skill.addTool(new SomeTool())
134
- await processClassBasedTool(toolArgument, sourceFile, indexContent, tools);
135
- }
136
- }
137
- async function processInlineTool(objectNode, sourceFile, indexContent, tools) {
138
- const toolProperties = {};
139
- // Extract properties from the object
140
- for (const property of objectNode.properties) {
141
- if (ts.isPropertyAssignment(property) &&
142
- ts.isIdentifier(property.name) &&
143
- property.initializer) {
144
- const key = property.name.text;
145
- const value = property.initializer;
146
- if (key === 'name' && ts.isStringLiteral(value)) {
147
- toolProperties.name = value.text;
148
- }
149
- else if (key === 'description' && ts.isStringLiteral(value)) {
150
- toolProperties.description = value.text;
151
- }
152
- else if (key === 'inputSchema' && ts.isIdentifier(value)) {
153
- toolProperties.inputSchemaVar = value.text;
154
- }
155
- else if (key === 'outputSchema' && ts.isIdentifier(value)) {
156
- toolProperties.outputSchemaVar = value.text;
157
- }
158
- else if (key === 'execute' && ts.isArrowFunction(value)) {
159
- // Extract the function body
160
- if (ts.isBlock(value.body)) {
161
- const bodyText = extractFunctionBody(value.body, sourceFile);
162
- toolProperties.executeBody = bodyText;
163
- }
164
- }
165
- }
166
- }
167
- // Create the tool if all required properties are present
168
- if (toolProperties.name &&
169
- toolProperties.description &&
170
- toolProperties.inputSchemaVar &&
171
- toolProperties.outputSchemaVar &&
172
- toolProperties.executeBody) {
173
- const inputSchema = convertSchemaToJSON(toolProperties.inputSchemaVar, indexContent);
174
- const outputSchema = convertSchemaToJSON(toolProperties.outputSchemaVar, indexContent);
175
- const selfContainedExecute = await createSelfContainedExecute(toolProperties.executeBody, indexContent);
176
- tools.push({
177
- name: toolProperties.name,
178
- description: toolProperties.description,
179
- inputSchema,
180
- outputSchema,
181
- execute: selfContainedExecute
19
+ if (fs.existsSync(distDir)) {
20
+ fs.rmSync(distDir, { recursive: true, force: true });
21
+ }
22
+ if (fs.existsSync(luaDir)) {
23
+ fs.rmSync(luaDir, { recursive: true, force: true });
24
+ }
25
+ // Create directory structures
26
+ fs.mkdirSync(distDir, { recursive: true });
27
+ fs.mkdirSync(path.join(distDir, "tools"), { recursive: true });
28
+ fs.mkdirSync(luaDir, { recursive: true });
29
+ // Find index.ts file
30
+ const indexPath = findIndexFile();
31
+ // Use ts-morph to analyze the TypeScript project
32
+ const project = new Project({
33
+ tsConfigFilePath: path.join(process.cwd(), "tsconfig.json"),
182
34
  });
183
- }
184
- }
185
- async function processClassBasedTool(newNode, sourceFile, indexContent, tools) {
186
- if (ts.isIdentifier(newNode.expression)) {
187
- const className = newNode.expression.text;
188
- const toolInfo = await extractToolFromClass(className, indexContent, sourceFile.fileName);
189
- if (toolInfo) {
190
- tools.push(toolInfo);
191
- }
192
- }
35
+ // Add the index file to the project
36
+ const indexFile = project.addSourceFileAtPath(indexPath);
37
+ // Detect tools from skill.addTools calls
38
+ const tools = await detectTools(indexFile, project);
39
+ writeProgress(`📦 Found ${tools.length} tools to bundle...`);
40
+ // Bundle each tool individually and extract execute code
41
+ for (const tool of tools) {
42
+ await bundleTool(tool, distDir);
43
+ await extractExecuteCode(tool, project);
44
+ }
45
+ // Bundle the main index file
46
+ await bundleMainIndex(indexPath, distDir);
47
+ // Create both deployment formats
48
+ await createDeploymentData(tools, distDir);
49
+ await createLegacyDeploymentData(tools, luaDir, indexFile);
50
+ writeSuccess(`✅ Skill compiled successfully - ${tools.length} tools bundled`);
51
+ }, "compilation");
193
52
  }
194
- function extractFunctionBody(blockStatement, sourceFile) {
195
- // Extract the function body text from the AST
196
- const start = blockStatement.getStart(sourceFile);
197
- const end = blockStatement.getEnd();
198
- // Get the text between the braces
199
- const fullText = sourceFile.text.substring(start, end);
200
- // Remove the outer braces and return the inner content
201
- if (fullText.startsWith('{') && fullText.endsWith('}')) {
202
- return fullText.slice(1, -1).trim();
203
- }
204
- return fullText;
53
+ function findIndexFile() {
54
+ // Check for index.ts in current directory
55
+ let indexPath = path.join(process.cwd(), "index.ts");
56
+ if (fs.existsSync(indexPath)) {
57
+ return indexPath;
58
+ }
59
+ // Check for index.ts in src directory
60
+ indexPath = path.join(process.cwd(), "src", "index.ts");
61
+ if (fs.existsSync(indexPath)) {
62
+ return indexPath;
63
+ }
64
+ throw new Error("index.ts not found in current directory or src/ directory");
205
65
  }
206
- // Fallback regex-based extraction (renamed from the original function)
207
- async function extractSkillInfoRegex(indexContent, indexFilePath) {
66
+ async function detectTools(indexFile, project) {
208
67
  const tools = [];
209
- // Find all addTool calls by searching for the pattern and manually parsing
210
- let searchIndex = 0;
211
- while (true) {
212
- const addToolIndex = indexContent.indexOf('skill.addTool(', searchIndex);
213
- if (addToolIndex === -1)
214
- break;
215
- // Check if this line is commented out
216
- const lineStart = indexContent.lastIndexOf('\n', addToolIndex) + 1;
217
- const lineContent = indexContent.substring(lineStart, addToolIndex).trim();
218
- if (lineContent.startsWith('//') || lineContent.startsWith('*') || lineContent.startsWith('#')) {
219
- searchIndex = addToolIndex + 1;
220
- continue;
221
- }
222
- // Find the matching closing parenthesis
223
- let parenCount = 0;
224
- let braceCount = 0;
225
- let callStart = addToolIndex + 'skill.addTool('.length;
226
- let callEnd = callStart;
227
- let found = false;
228
- for (let i = callStart; i < indexContent.length; i++) {
229
- const char = indexContent[i];
230
- if (char === '(')
231
- parenCount++;
232
- else if (char === ')') {
233
- if (parenCount === 0) {
234
- callEnd = i;
235
- found = true;
236
- break;
237
- }
238
- parenCount--;
239
- }
240
- else if (char === '{')
241
- braceCount++;
242
- else if (char === '}')
243
- braceCount--;
244
- }
245
- if (!found) {
246
- searchIndex = addToolIndex + 1;
247
- continue;
248
- }
249
- const toolArgument = indexContent.substring(callStart, callEnd).trim();
250
- // Check if it's an inline tool definition: skill.addTool({...})
251
- if (toolArgument.startsWith('{')) {
252
- // Extract tool properties
253
- const nameMatch = toolArgument.match(/name:\s*["']([^"']+)["']/);
254
- const descriptionMatch = toolArgument.match(/description:\s*["']([^"']+)["']/);
255
- const inputSchemaMatch = toolArgument.match(/inputSchema:\s*(\w+)/);
256
- const outputSchemaMatch = toolArgument.match(/outputSchema:\s*(\w+)/);
257
- const executeMatch = toolArgument.match(/execute:\s*async\s*\([^)]*\)\s*=>\s*\{([\s\S]*?)\}/);
258
- if (nameMatch && descriptionMatch && inputSchemaMatch && outputSchemaMatch && executeMatch) {
259
- const toolName = nameMatch[1];
260
- const toolDescription = descriptionMatch[1];
261
- const inputSchemaVar = inputSchemaMatch[1];
262
- const outputSchemaVar = outputSchemaMatch[1];
263
- const executeBody = executeMatch[1];
264
- // Convert schemas to JSON Schema format
265
- const inputSchema = convertSchemaToJSON(inputSchemaVar, indexContent);
266
- const outputSchema = convertSchemaToJSON(outputSchemaVar, indexContent);
267
- // Create self-contained execute function
268
- const selfContainedExecute = await createSelfContainedExecute(executeBody, indexContent);
269
- tools.push({
270
- name: toolName,
271
- description: toolDescription,
272
- inputSchema,
273
- outputSchema,
274
- execute: selfContainedExecute
275
- });
276
- }
277
- }
278
- // Check if it's a class-based tool: skill.addTool(new SomeTool())
279
- else if (toolArgument.startsWith('new ')) {
280
- const classMatch = toolArgument.match(/new\s+(\w+)\(\)/);
281
- if (classMatch) {
282
- const className = classMatch[1];
283
- // Find the tool class definition
284
- const toolInfo = await extractToolFromClass(className, indexContent, indexFilePath);
285
- if (toolInfo) {
286
- tools.push(toolInfo);
68
+ // Find all call expressions in the file
69
+ indexFile.forEachDescendant((node) => {
70
+ if (Node.isCallExpression(node)) {
71
+ const expression = node.getExpression();
72
+ // Check if this is skill.addTools or skill.addTool
73
+ if (Node.isPropertyAccessExpression(expression)) {
74
+ const object = expression.getExpression();
75
+ const property = expression.getName();
76
+ if (object.getText() === 'skill' && (property === 'addTools' || property === 'addTool')) {
77
+ const args = node.getArguments();
78
+ if (property === 'addTools' && args.length > 0) {
79
+ // Handle skill.addTools([...]) - array of tools
80
+ const arrayArg = args[0];
81
+ if (Node.isArrayLiteralExpression(arrayArg)) {
82
+ const elements = arrayArg.getElements();
83
+ for (const element of elements) {
84
+ if (Node.isNewExpression(element)) {
85
+ const toolInfo = extractToolFromNewExpressionSync(element, project);
86
+ if (toolInfo) {
87
+ tools.push(toolInfo);
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ else if (property === 'addTool' && args.length > 0) {
94
+ // Handle skill.addTool(new ToolClass()) - single tool
95
+ const arg = args[0];
96
+ if (Node.isNewExpression(arg)) {
97
+ const toolInfo = extractToolFromNewExpressionSync(arg, project);
98
+ if (toolInfo) {
99
+ tools.push(toolInfo);
100
+ }
101
+ }
102
+ }
287
103
  }
288
104
  }
289
105
  }
290
- searchIndex = callEnd + 1;
291
- }
106
+ });
292
107
  return tools;
293
108
  }
294
- function convertSchemaToJSON(schemaVar, indexContent) {
295
- // Find the schema definition
296
- const schemaRegex = new RegExp(`const\\s+${schemaVar}\\s*=\\s*z\\.object\\(\\{([\\s\\S]*?)\\}\\\);`, 'g');
297
- const match = schemaRegex.exec(indexContent);
298
- if (match) {
299
- const schemaContent = match[1];
300
- // Convert Zod schema to JSON Schema format
301
- return {
302
- type: "object",
303
- properties: parseZodProperties(schemaContent),
304
- required: extractRequiredFields(schemaContent)
305
- };
306
- }
307
- // If no match found, return empty schema
308
- return { type: "object", properties: {} };
309
- }
310
- function parseZodProperties(schemaContent) {
311
- const properties = {};
312
- // Simple regex to find z.string(), z.number(), etc.
313
- const fieldRegex = /(\w+):\s*z\.(\w+)\(\)/g;
314
- let match;
315
- while ((match = fieldRegex.exec(schemaContent)) !== null) {
316
- const fieldName = match[1];
317
- const fieldType = match[2];
318
- switch (fieldType) {
319
- case 'string':
320
- properties[fieldName] = { type: 'string' };
321
- break;
322
- case 'number':
323
- properties[fieldName] = { type: 'number' };
324
- break;
325
- case 'boolean':
326
- properties[fieldName] = { type: 'boolean' };
327
- break;
328
- default:
329
- properties[fieldName] = { type: 'string' };
330
- }
331
- }
332
- return properties;
333
- }
334
- function extractRequiredFields(schemaContent) {
335
- const required = [];
336
- const fieldRegex = /(\w+):\s*z\.(\w+)\(\)/g;
337
- let match;
338
- while ((match = fieldRegex.exec(schemaContent)) !== null) {
339
- required.push(match[1]);
340
- }
341
- return required;
342
- }
343
- async function createSelfContainedExecute(executeBody, indexContent) {
344
- const dependencies = [];
345
- const bundledPackages = new Set();
346
- // 1. Parse external package imports and bundle their code
347
- const allImportRegex = /import\s+(?:(?:\{([^}]+)\})|(\w+))\s+from\s+["']([^"']+)["']/g;
348
- let importMatch;
349
- while ((importMatch = allImportRegex.exec(indexContent)) !== null) {
350
- const namedImports = importMatch[1]; // Named imports like { z }
351
- const defaultImport = importMatch[2]; // Default import like axios
352
- const packagePath = importMatch[3];
353
- // Skip local imports (relative paths)
354
- if (packagePath.startsWith('./') || packagePath.startsWith('../')) {
355
- continue;
356
- }
357
- // Skip lua-cli imports (these are handled separately)
358
- if (packagePath.startsWith('lua-cli')) {
359
- continue;
360
- }
361
- // Skip zod - assume it's always available on target machine
362
- if (packagePath === 'zod') {
363
- // Add require statement for zod instead of bundling
364
- if (namedImports) {
365
- const importsList = namedImports.split(',').map(imp => imp.trim());
366
- const usedImports = importsList.filter(imp => executeBody.includes(imp) || indexContent.includes(`${imp}.`));
367
- if (usedImports.length > 0) {
368
- const requireStatement = usedImports.length === 1
369
- ? `const { ${usedImports[0]} } = require('zod');`
370
- : `const { ${usedImports.join(', ')} } = require('zod');`;
371
- bundledPackages.add(requireStatement);
372
- }
373
- }
374
- else if (defaultImport) {
375
- bundledPackages.add(`const ${defaultImport} = require('zod');`);
376
- }
377
- continue;
378
- }
379
- // Bundle other external packages
380
- if (namedImports || defaultImport) {
381
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
382
- if (packageCode) {
383
- bundledPackages.add(packageCode);
384
- }
385
- }
386
- }
387
- // 2. Extract class definitions with proper brace matching
388
- const classRegex = /class\s+(\w+)(?:\s+extends\s+\w+)?\s*\{/g;
389
- let classMatch;
390
- while ((classMatch = classRegex.exec(indexContent)) !== null) {
391
- const className = classMatch[1];
392
- const classStart = classMatch.index;
393
- // Find the matching closing brace
394
- let braceCount = 0;
395
- let classEnd = classStart;
396
- let found = false;
397
- for (let i = classStart; i < indexContent.length; i++) {
398
- if (indexContent[i] === '{') {
399
- braceCount++;
400
- }
401
- else if (indexContent[i] === '}') {
402
- braceCount--;
403
- if (braceCount === 0) {
404
- classEnd = i;
405
- found = true;
109
+ function extractToolFromNewExpressionSync(newExpr, project) {
110
+ try {
111
+ const expression = newExpr.getExpression();
112
+ const className = expression.getText();
113
+ // Find the import declaration for this class
114
+ const sourceFile = newExpr.getSourceFile();
115
+ const imports = sourceFile.getImportDeclarations();
116
+ for (const importDecl of imports) {
117
+ const namedImports = importDecl.getNamedImports();
118
+ const defaultImport = importDecl.getDefaultImport();
119
+ let toolFilePath = null;
120
+ // Check named imports
121
+ for (const namedImport of namedImports) {
122
+ if (namedImport.getName() === className) {
123
+ toolFilePath = resolveImportPath(importDecl.getModuleSpecifierValue(), sourceFile.getFilePath());
406
124
  break;
407
125
  }
408
126
  }
409
- }
410
- if (found) {
411
- const fullClass = indexContent.substring(classStart, classEnd + 1);
412
- // Check if this class is used in the execute function
413
- let isUsed = false;
414
- // Direct usage in execute body
415
- if (executeBody.includes(`new ${className}`) ||
416
- executeBody.includes(`${className}.`) ||
417
- executeBody.includes(`${className}(`)) {
418
- isUsed = true;
127
+ // Check default import
128
+ if (!toolFilePath && defaultImport && defaultImport.getText() === className) {
129
+ toolFilePath = resolveImportPath(importDecl.getModuleSpecifierValue(), sourceFile.getFilePath());
419
130
  }
420
- // Check if any variable that uses this class is referenced in execute body
421
- const variableRegex = new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+${className}\\s*\\([^)]*\\);`, 'g');
422
- let varMatch;
423
- while ((varMatch = variableRegex.exec(indexContent)) !== null) {
424
- const varName = varMatch[1];
425
- if (executeBody.includes(varName)) {
426
- isUsed = true;
427
- break;
131
+ if (toolFilePath && fs.existsSync(toolFilePath)) {
132
+ // Extract tool metadata from the class file
133
+ try {
134
+ const toolSourceFile = project.addSourceFileAtPath(toolFilePath);
135
+ const classDecl = toolSourceFile.getClass(className);
136
+ if (classDecl) {
137
+ const nameProperty = classDecl.getProperty('name');
138
+ const descProperty = classDecl.getProperty('description');
139
+ let toolName = className.replace(/Tool$/, '').toLowerCase();
140
+ let description = '';
141
+ // Extract name from property if available
142
+ if (nameProperty && nameProperty.getInitializer()) {
143
+ const nameValue = nameProperty.getInitializer()?.getText();
144
+ if (nameValue) {
145
+ toolName = nameValue.replace(/['"]/g, '');
146
+ }
147
+ }
148
+ // Extract description from property if available
149
+ if (descProperty && descProperty.getInitializer()) {
150
+ const descValue = descProperty.getInitializer()?.getText();
151
+ if (descValue) {
152
+ description = descValue.replace(/['"]/g, '');
153
+ }
154
+ }
155
+ return {
156
+ name: toolName,
157
+ className,
158
+ filePath: toolFilePath,
159
+ description
160
+ };
161
+ }
162
+ }
163
+ catch (fileError) {
164
+ console.warn(`Warning: Could not load tool file ${toolFilePath}:`, fileError);
428
165
  }
429
- }
430
- if (isUsed) {
431
- dependencies.push(fullClass);
432
166
  }
433
167
  }
168
+ return null;
434
169
  }
435
- // 3. Extract function definitions
436
- const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\}/g;
437
- let functionMatch;
438
- while ((functionMatch = functionRegex.exec(indexContent)) !== null) {
439
- const functionName = functionMatch[1];
440
- const functionBody = functionMatch[2];
441
- if (executeBody.includes(functionName)) {
442
- dependencies.push(`function ${functionName}() {\n${functionBody}\n}`);
443
- }
170
+ catch (error) {
171
+ console.warn(`Warning: Could not extract tool info for ${newExpr.getText()}:`, error);
172
+ return null;
444
173
  }
445
- // 4. Extract const/let/var declarations (avoid duplicates)
446
- const varRegex = /(?:const|let|var)\s+(\w+)\s*=\s*([^;]+);/g;
447
- const declaredVars = new Set();
448
- let varMatch;
449
- while ((varMatch = varRegex.exec(indexContent)) !== null) {
450
- const varName = varMatch[1];
451
- const varValue = varMatch[2];
452
- // Skip if it's a class instantiation (we'll handle that separately)
453
- if (varValue.includes('new ') && varValue.includes('()')) {
454
- continue;
455
- }
456
- // Skip if already declared
457
- if (declaredVars.has(varName)) {
458
- continue;
459
- }
460
- if (executeBody.includes(varName)) {
461
- declaredVars.add(varName);
462
- dependencies.push(`const ${varName} = ${varValue};`);
463
- }
174
+ }
175
+ function resolveImportPath(moduleSpecifier, currentFilePath) {
176
+ if (moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../')) {
177
+ // Relative import - resolve relative to current file
178
+ const currentDir = path.dirname(currentFilePath);
179
+ return path.resolve(currentDir, moduleSpecifier + '.ts');
464
180
  }
465
- // 5. Extract class instantiations (avoid duplicates)
466
- const instantiationRegex = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(\w+)\([^)]*\);/g;
467
- let instantiationMatch;
468
- while ((instantiationMatch = instantiationRegex.exec(indexContent)) !== null) {
469
- const instanceName = instantiationMatch[1];
470
- const className = instantiationMatch[2];
471
- // Skip if already declared
472
- if (declaredVars.has(instanceName)) {
473
- continue;
474
- }
475
- if (executeBody.includes(instanceName)) {
476
- declaredVars.add(instanceName);
477
- dependencies.push(`const ${instanceName} = new ${className}();`);
478
- }
181
+ else {
182
+ // Absolute import - assume it's in the project
183
+ return path.resolve(process.cwd(), 'src', moduleSpecifier + '.ts');
479
184
  }
480
- // 6. Create the self-contained execute function
481
- const allDependencies = [...Array.from(bundledPackages), ...dependencies];
482
- const dependencyCode = allDependencies.join('\n');
483
- // Strip TypeScript type annotations for JavaScript compatibility (only for local code)
484
- const cleanDependencyCode = allDependencies.map(dep => {
485
- // Only strip TypeScript from local dependencies, not bundled packages
486
- if (dep.includes('require(') || dep.includes('import ')) {
487
- return dep; // Skip bundled packages
488
- }
489
- return dep
490
- .replace(/:\s*string/g, '') // Remove : string
491
- .replace(/:\s*number/g, '') // Remove : number
492
- .replace(/:\s*boolean/g, '') // Remove : boolean
493
- .replace(/:\s*any/g, '') // Remove : any
494
- .replace(/:\s*void/g, '') // Remove : void
495
- .replace(/:\s*object/g, '') // Remove : object
496
- .replace(/:\s*Array<[^>]+>/g, '') // Remove : Array<Type>
497
- .replace(/:\s*Promise<[^>]+>/g, '') // Remove : Promise<Type>
498
- .replace(/:\s*Record<[^>]+>/g, ''); // Remove : Record<Type>
499
- }).join('\n');
500
- const cleanExecuteBody = executeBody
501
- .replace(/:\s*string/g, '') // Remove : string
502
- .replace(/:\s*number/g, '') // Remove : number
503
- .replace(/:\s*boolean/g, '') // Remove : boolean
504
- .replace(/:\s*any/g, '') // Remove : any
505
- .replace(/:\s*void/g, '') // Remove : void
506
- .replace(/:\s*object/g, '') // Remove : object
507
- .replace(/:\s*Array<[^>]+>/g, '') // Remove : Array<Type>
508
- .replace(/:\s*Promise<[^>]+>/g, '') // Remove : Promise<Type>
509
- .replace(/:\s*Record<[^>]+>/g, ''); // Remove : Record<Type>
510
- const selfContainedExecute = `async (input) => {
511
- ${cleanDependencyCode ? ` ${cleanDependencyCode.split('\n').join('\n ')}\n` : ''} ${cleanExecuteBody.trim()}
512
- }`;
513
- return selfContainedExecute;
514
185
  }
515
- async function bundlePackageCode(packagePath, namedImports, defaultImport) {
186
+ async function bundleTool(tool, distDir) {
187
+ writeProgress(`📦 Bundling ${tool.className}...`);
516
188
  try {
517
- const { build } = await import('esbuild');
518
- // Create a temporary entry file for esbuild in .lua directory
519
- const luaDir = path.join(process.cwd(), '.lua');
520
- if (!fs.existsSync(luaDir)) {
521
- fs.mkdirSync(luaDir, { recursive: true });
522
- }
523
- const entryFile = path.join(luaDir, `${packagePath}-entry.cjs`);
524
- const outputFile = path.join(luaDir, `${packagePath}-bundle.cjs`);
525
- // Create entry file based on import type
526
- let entryContent = '';
527
- if (defaultImport) {
528
- // For default imports like `import axios from 'axios'`
529
- entryContent = `import ${defaultImport} from '${packagePath}';\nmodule.exports = ${defaultImport};`;
530
- }
531
- else if (namedImports) {
532
- // For named imports like `import { z } from 'zod'`
533
- const importsList = namedImports.split(',').map(imp => imp.trim());
534
- entryContent = `import { ${importsList.join(', ')} } from '${packagePath}';\nmodule.exports = { ${importsList.join(', ')} };`;
535
- }
536
- else {
537
- // Fallback - import everything
538
- entryContent = `import * as ${packagePath.replace(/[^a-zA-Z0-9]/g, '_')} from '${packagePath}';\nmodule.exports = ${packagePath.replace(/[^a-zA-Z0-9]/g, '_')};`;
539
- }
540
- // Write entry file
541
- fs.writeFileSync(entryFile, entryContent);
542
- // Bundle with esbuild
543
- const result = await build({
544
- entryPoints: [entryFile],
189
+ const outputPath = path.join(distDir, 'tools', `${tool.className}.js`);
190
+ await build({
191
+ entryPoints: [tool.filePath],
545
192
  bundle: true,
546
- format: 'cjs', // CommonJS format
193
+ format: 'cjs',
547
194
  platform: 'node',
548
195
  target: 'node16',
549
- outfile: outputFile,
550
- external: [], // Bundle everything
551
- minify: false, // Keep readable for debugging
196
+ outfile: outputPath,
197
+ external: ['lua-cli/skill', 'lua-cli/user-data-api', 'zod'], // Exclude lua-cli modules and zod - injected into VM
198
+ minify: true, // Minify for smaller file sizes
552
199
  sourcemap: false,
553
- write: true,
554
- resolveExtensions: ['.js', '.ts', '.json'],
555
- mainFields: ['main', 'module', 'browser'],
556
- conditions: ['node'],
557
- nodePaths: [
558
- path.join(process.cwd(), 'node_modules'),
559
- path.join(process.cwd(), '..', 'node_modules'),
560
- path.join(process.cwd(), '..', '..', 'node_modules')
561
- ],
562
- absWorkingDir: process.cwd(),
200
+ resolveExtensions: ['.ts', '.js', '.json'],
201
+ define: {
202
+ 'process.env.NODE_ENV': '"production"'
203
+ },
204
+ // Ensure all other dependencies are bundled
205
+ packages: 'bundle',
206
+ // Handle different module formats
207
+ mainFields: ['main', 'module'],
208
+ conditions: ['node']
563
209
  });
564
- if (result.errors.length > 0) {
565
- console.warn(`Warning: esbuild errors for package ${packagePath}:`, result.errors);
566
- return null;
567
- }
568
- // Read the bundled output
569
- if (!fs.existsSync(outputFile)) {
570
- console.warn(`Warning: Bundle output not found for package ${packagePath}`);
571
- return null;
572
- }
573
- const bundledContent = fs.readFileSync(outputFile, 'utf8');
574
- // Clean up temporary files
575
- try {
576
- fs.unlinkSync(entryFile);
577
- fs.unlinkSync(outputFile);
578
- }
579
- catch (cleanupError) {
580
- // Ignore cleanup errors
581
- }
582
- // Create the final bundled code
583
- let finalCode = '';
584
- if (defaultImport) {
585
- finalCode = `const ${defaultImport} = (function() {\n${bundledContent}\n return module.exports;\n})();\n`;
586
- }
587
- else if (namedImports) {
588
- const importsList = namedImports.split(',').map(imp => imp.trim());
589
- finalCode = `(function() {\n${bundledContent}\n})();\n`;
590
- finalCode += `const { ${importsList.join(', ')} } = module.exports;\n`;
591
- }
592
- else {
593
- finalCode = `(function() {\n${bundledContent}\n})();\n`;
594
- }
595
- return finalCode;
210
+ // Add VM-compatible wrapper
211
+ await wrapToolForVM(outputPath, tool);
596
212
  }
597
213
  catch (error) {
598
- console.warn(`Warning: Could not bundle package ${packagePath} with esbuild:`, error);
599
- // For test environments or when esbuild fails, provide a fallback
600
- if (packagePath === 'axios') {
601
- return createWorkingAxiosImplementation();
602
- }
603
- return null;
214
+ console.warn(`Warning: Failed to bundle ${tool.className}:`, error);
604
215
  }
605
216
  }
606
- function createWorkingAxiosImplementation() {
607
- return `
608
- // Working axios implementation using native fetch (for test environments)
609
- const axios = {
610
- get: async (url, config = {}) => {
611
- const searchParams = new URLSearchParams(config.params || {});
612
- const fullUrl = searchParams.toString() ? \`\${url}?\${searchParams}\` : url;
613
-
614
- const response = await fetch(fullUrl, {
615
- method: 'GET',
616
- headers: {
617
- 'Content-Type': 'application/json',
618
- ...config.headers
217
+ async function wrapToolForVM(outputPath, tool) {
218
+ const bundledCode = fs.readFileSync(outputPath, 'utf8');
219
+ // Create a wrapper that's compatible with the existing sandbox.ts VM system
220
+ // The sandbox expects: const executeFunction = ${toolCode}; module.exports = async (input) => { return await executeFunction(input); };
221
+ const wrappedCode = `async (input) => {
222
+ // Mock lua-cli/skill module for external dependencies
223
+ const luaCliSkill = {
224
+ env: function(key) {
225
+ if (typeof env !== 'undefined') {
226
+ return env(key);
619
227
  }
620
- });
621
-
622
- if (!response.ok) {
623
- const error = new Error(\`Request failed with status \${response.status}\`);
624
- error.response = { status: response.status, statusText: response.statusText };
625
- throw error;
626
- }
627
-
628
- const data = await response.json();
629
- return {
630
- data,
631
- status: response.status,
632
- statusText: response.statusText,
633
- headers: response.headers,
634
- config: config
635
- };
636
- },
637
-
638
- post: async (url, data, config = {}) => {
639
- const response = await fetch(url, {
640
- method: 'POST',
641
- headers: {
642
- 'Content-Type': 'application/json',
643
- ...config.headers
644
- },
645
- body: JSON.stringify(data)
646
- });
647
-
648
- if (!response.ok) {
649
- const error = new Error(\`Request failed with status \${response.status}\`);
650
- error.response = { status: response.status, statusText: response.statusText };
651
- throw error;
228
+ return process.env[key] || '';
652
229
  }
653
-
654
- const responseData = await response.json();
655
- return {
656
- data: responseData,
657
- status: response.status,
658
- statusText: response.statusText,
659
- headers: response.headers,
660
- config: config
661
- };
662
- },
230
+ };
663
231
 
664
- put: async (url, data, config = {}) => {
665
- const response = await fetch(url, {
666
- method: 'PUT',
667
- headers: {
668
- 'Content-Type': 'application/json',
669
- ...config.headers
670
- },
671
- body: JSON.stringify(data)
672
- });
673
-
674
- if (!response.ok) {
675
- const error = new Error(\`Request failed with status \${response.status}\`);
676
- error.response = { status: response.status, statusText: response.statusText };
677
- throw error;
232
+ // Mock lua-cli/user-data-api module
233
+ const luaCliUserDataApi = {
234
+ user: typeof user !== 'undefined' ? user : {
235
+ data: {
236
+ get: async () => ({}),
237
+ update: async (data) => data,
238
+ create: async (data) => data
239
+ }
678
240
  }
679
-
680
- const responseData = await response.json();
681
- return {
682
- data: responseData,
683
- status: response.status,
684
- statusText: response.statusText,
685
- headers: response.headers,
686
- config: config
687
- };
688
- },
241
+ };
689
242
 
690
- delete: async (url, config = {}) => {
691
- const response = await fetch(url, {
692
- method: 'DELETE',
693
- headers: {
694
- 'Content-Type': 'application/json',
695
- ...config.headers
243
+ // Mock zod module
244
+ const zodModule = (() => {
245
+ try {
246
+ if (typeof require !== 'undefined') {
247
+ return require('zod');
696
248
  }
697
- });
698
-
699
- if (!response.ok) {
700
- const error = new Error(\`Request failed with status \${response.status}\`);
701
- error.response = { status: response.status, statusText: response.statusText };
702
- throw error;
249
+ } catch (e) {
250
+ // Fallback zod implementation
703
251
  }
704
-
705
- const responseData = await response.json();
706
- return {
707
- data: responseData,
708
- status: response.status,
709
- statusText: response.statusText,
710
- headers: response.headers,
711
- config: config
252
+ return {
253
+ z: {
254
+ object: (schema) => ({
255
+ parse: (data) => data,
256
+ safeParse: (data) => ({ success: true, data })
257
+ }),
258
+ string: () => ({
259
+ parse: (data) => String(data),
260
+ safeParse: (data) => ({ success: true, data: String(data) })
261
+ }),
262
+ number: () => ({
263
+ parse: (data) => Number(data),
264
+ safeParse: (data) => ({ success: true, data: Number(data) })
265
+ }),
266
+ boolean: () => ({
267
+ parse: (data) => Boolean(data),
268
+ safeParse: (data) => ({ success: true, data: Boolean(data) })
269
+ })
270
+ }
712
271
  };
713
- },
272
+ })();
714
273
 
715
- patch: async (url, data, config = {}) => {
716
- const response = await fetch(url, {
717
- method: 'PATCH',
718
- headers: {
719
- 'Content-Type': 'application/json',
720
- ...config.headers
721
- },
722
- body: JSON.stringify(data)
723
- });
724
-
725
- if (!response.ok) {
726
- const error = new Error(\`Request failed with status \${response.status}\`);
727
- error.response = { status: response.status, statusText: response.statusText };
728
- throw error;
274
+ // Override require for external modules during execution
275
+ const originalRequire = require;
276
+ require = function(id) {
277
+ if (id === 'lua-cli/skill') {
278
+ return luaCliSkill;
729
279
  }
730
-
731
- const responseData = await response.json();
732
- return {
733
- data: responseData,
734
- status: response.status,
735
- statusText: response.statusText,
736
- headers: response.headers,
737
- config: config
738
- };
739
- }
740
- };
741
- `;
742
- }
743
- async function extractToolFromClass(className, indexContent, indexFilePath) {
744
- // Find the import statement for this class
745
- const importRegex = new RegExp(`import\\s+${className}\\s+from\\s+["']([^"']+)["']`, 'g');
746
- const importMatch = importRegex.exec(indexContent);
747
- if (!importMatch) {
748
- console.warn(`Warning: Could not find import for class ${className}`);
749
- return null;
280
+ if (id === 'lua-cli/user-data-api') {
281
+ return luaCliUserDataApi;
750
282
  }
751
- const importPath = importMatch[1];
752
- // Read the tool file - handle both relative and absolute paths
753
- let toolFilePath;
754
- if (importPath.startsWith('./') || importPath.startsWith('../')) {
755
- // Relative path - resolve from the index file directory
756
- const indexDir = path.dirname(indexFilePath);
757
- toolFilePath = path.join(indexDir, importPath + '.ts');
283
+ if (id === 'zod') {
284
+ return zodModule;
758
285
  }
759
- else {
760
- // Absolute path or just filename
761
- toolFilePath = path.join(process.cwd(), importPath + '.ts');
762
- }
763
- if (!fs.existsSync(toolFilePath)) {
764
- console.warn(`Warning: Tool file not found: ${toolFilePath}`);
765
- return null;
766
- }
767
- const toolContent = fs.readFileSync(toolFilePath, 'utf8');
768
- // Extract tool properties from the class
769
- const nameMatch = toolContent.match(/this\.name\s*=\s*["']([^"']+)["']/);
770
- const descriptionMatch = toolContent.match(/this\.description\s*=\s*["']([^"']+)["']/);
771
- if (!nameMatch || !descriptionMatch) {
772
- console.warn(`Warning: Could not extract name or description from ${className}`);
773
- return null;
774
- }
775
- const toolName = nameMatch[1];
776
- const toolDescription = descriptionMatch[1];
777
- // Extract schemas - look for multiple schema definitions
778
- const inputSchemaMatch = toolContent.match(/const\s+(\w+)\s*=\s*z\.object\(/);
779
- const outputSchemaMatch = toolContent.match(/const\s+(\w+)\s*=\s*z\.object\(/);
780
- if (!inputSchemaMatch) {
781
- console.warn(`Warning: Could not find input schema in ${className}`);
782
- return null;
286
+ return originalRequire(id);
287
+ };
288
+
289
+ // Execute the bundled tool code
290
+ ${bundledCode}
291
+
292
+ // Restore original require
293
+ require = originalRequire;
294
+
295
+ // Get the tool class from exports
296
+ const ToolClass = module.exports.default || module.exports.${tool.className} || module.exports;
297
+
298
+ // Create and execute the tool
299
+ const toolInstance = new ToolClass();
300
+ return await toolInstance.execute(input);
301
+ }`;
302
+ fs.writeFileSync(outputPath, wrappedCode);
303
+ }
304
+ async function bundleMainIndex(indexPath, distDir) {
305
+ writeProgress("📦 Bundling main index...");
306
+ try {
307
+ await build({
308
+ entryPoints: [indexPath],
309
+ bundle: true,
310
+ format: 'cjs',
311
+ platform: 'node',
312
+ target: 'node16',
313
+ outfile: path.join(distDir, 'index.js'),
314
+ external: ['lua-cli/skill', 'lua-cli/user-data-api', 'zod'], // Exclude lua-cli modules and zod
315
+ minify: true, // Minify for smaller file sizes
316
+ sourcemap: false,
317
+ resolveExtensions: ['.ts', '.js', '.json'],
318
+ define: {
319
+ 'process.env.NODE_ENV': '"production"'
320
+ },
321
+ packages: 'bundle',
322
+ mainFields: ['main', 'module'],
323
+ conditions: ['node']
324
+ });
783
325
  }
784
- // Convert schemas to JSON Schema format
785
- const inputSchema = convertSchemaToJSON(inputSchemaMatch[1], toolContent);
786
- const outputSchema = outputSchemaMatch ? convertSchemaToJSON(outputSchemaMatch[1], toolContent) : { type: "object" };
787
- // Extract execute method
788
- const executeMatch = toolContent.match(/async\s+execute\s*\([^)]*\)\s*\{([\s\S]*?)\}/);
789
- if (!executeMatch) {
790
- console.warn(`Warning: Could not find execute method in ${className}`);
791
- return null;
326
+ catch (error) {
327
+ console.warn("Warning: Failed to bundle main index:", error);
792
328
  }
793
- const executeBody = executeMatch[1];
794
- // For class-based tools, we need to create a self-contained function that includes:
795
- // 1. The service classes
796
- // 2. Class instantiation
797
- // 3. The execute logic
798
- const selfContainedExecute = await createClassBasedExecute(executeBody, toolContent, className, toolFilePath);
799
- return {
800
- name: toolName,
801
- description: toolDescription,
802
- inputSchema,
803
- outputSchema,
804
- execute: selfContainedExecute
805
- };
806
329
  }
807
- async function createClassBasedExecute(executeBody, toolContent, className, toolFilePath) {
808
- const dependencies = [];
809
- const bundledPackages = new Set();
810
- // 1. Parse imports from the tool file
811
- const importRegex = /import\s+(?:(?:\{([^}]+)\})|(\w+))\s+from\s+["']([^"']+)["']/g;
812
- let importMatch;
813
- while ((importMatch = importRegex.exec(toolContent)) !== null) {
814
- const namedImports = importMatch[1];
815
- const defaultImport = importMatch[2];
816
- const packagePath = importMatch[3];
817
- // Skip lua-cli imports
818
- if (packagePath.startsWith('lua-cli')) {
819
- continue;
820
- }
821
- // Handle zod
822
- if (packagePath === 'zod') {
823
- if (namedImports) {
824
- const importsList = namedImports.split(',').map(imp => imp.trim());
825
- const usedImports = importsList.filter(imp => executeBody.includes(imp) || toolContent.includes(`${imp}.`));
826
- if (usedImports.length > 0) {
827
- const requireStatement = usedImports.length === 1
828
- ? `const { ${usedImports[0]} } = require('zod');`
829
- : `const { ${usedImports.join(', ')} } = require('zod');`;
830
- bundledPackages.add(requireStatement);
831
- }
832
- }
833
- else if (defaultImport) {
834
- bundledPackages.add(`const ${defaultImport} = require('zod');`);
330
+ async function extractExecuteCode(tool, project) {
331
+ try {
332
+ const toolSourceFile = project.getSourceFile(tool.filePath);
333
+ if (!toolSourceFile) {
334
+ console.warn(`Warning: Could not find source file for ${tool.className}`);
335
+ return;
336
+ }
337
+ const classDecl = toolSourceFile.getClass(tool.className);
338
+ if (!classDecl) {
339
+ console.warn(`Warning: Could not find class ${tool.className} in ${tool.filePath}`);
340
+ return;
341
+ }
342
+ // Extract the execute method
343
+ const executeMethod = classDecl.getMethod('execute');
344
+ if (!executeMethod) {
345
+ console.warn(`Warning: Could not find execute method in ${tool.className}`);
346
+ return;
347
+ }
348
+ // Get the execute method body
349
+ const executeBody = executeMethod.getBodyText();
350
+ if (!executeBody) {
351
+ console.warn(`Warning: Execute method has no body in ${tool.className}`);
352
+ return;
353
+ }
354
+ // Read the bundled tool file to get the self-contained code
355
+ const bundledPath = path.join(process.cwd(), 'dist', 'tools', `${tool.className}.js`);
356
+ if (fs.existsSync(bundledPath)) {
357
+ const bundledCode = fs.readFileSync(bundledPath, 'utf8');
358
+ // Extract just the tool execution function from the bundled code
359
+ // The bundled code contains the VM wrapper, we need to extract the core functionality
360
+ const executeFunction = createExecuteFunction(bundledCode, tool);
361
+ tool.executeCode = executeFunction;
362
+ }
363
+ // Extract input schema from zod definition
364
+ const inputSchemaProperty = classDecl.getProperty('inputSchema');
365
+ if (inputSchemaProperty && inputSchemaProperty.getInitializer()) {
366
+ const initializer = inputSchemaProperty.getInitializer();
367
+ if (initializer) {
368
+ // Parse the zod schema to extract JSON schema
369
+ tool.inputSchema = parseZodSchemaToJsonSchema(initializer.getText());
835
370
  }
836
- continue;
837
371
  }
838
- // Handle axios - bundle it properly
839
- if (packagePath === 'axios') {
840
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
841
- if (packageCode) {
842
- bundledPackages.add(packageCode);
372
+ if (!tool.inputSchema) {
373
+ tool.inputSchema = { type: "object" };
374
+ }
375
+ }
376
+ catch (error) {
377
+ console.warn(`Warning: Could not extract execute code for ${tool.className}:`, error);
378
+ }
379
+ }
380
+ function createExecuteFunction(bundledCode, tool) {
381
+ // The bundled code is already wrapped for VM execution by wrapToolForVM
382
+ // Just return it directly since it's already an async function
383
+ return bundledCode;
384
+ }
385
+ function parseZodSchemaToJsonSchema(zodSchemaText) {
386
+ try {
387
+ // Use ts-morph to properly parse the Zod schema
388
+ const tempProject = new Project();
389
+ const tempFile = tempProject.createSourceFile('temp.ts', `
390
+ import { z } from 'zod';
391
+ const schema = ${zodSchemaText};
392
+ `);
393
+ // Find the schema variable declaration
394
+ const schemaDeclaration = tempFile.getVariableDeclaration('schema');
395
+ if (schemaDeclaration && schemaDeclaration.getInitializer()) {
396
+ const initializer = schemaDeclaration.getInitializer();
397
+ if (initializer) {
398
+ return parseZodASTToJsonSchema(initializer);
843
399
  }
844
- continue;
845
400
  }
846
- // Handle local service imports
847
- if (packagePath.startsWith('./') || packagePath.startsWith('../')) {
848
- // Resolve the service file path relative to the tool file location
849
- const toolDir = path.dirname(toolFilePath);
850
- const serviceFilePath = path.resolve(toolDir, packagePath + '.ts');
851
- if (fs.existsSync(serviceFilePath)) {
852
- const serviceContent = fs.readFileSync(serviceFilePath, 'utf8');
853
- // Process all imports in the service file
854
- const serviceImportRegex = /import\s+(?:(?:\{([^}]+)\})|(\w+))\s+from\s+["']([^"']+)["']/g;
855
- let serviceImportMatch;
856
- while ((serviceImportMatch = serviceImportRegex.exec(serviceContent)) !== null) {
857
- const namedImports = serviceImportMatch[1];
858
- const defaultImport = serviceImportMatch[2];
859
- const packagePath = serviceImportMatch[3];
860
- // Skip lua-cli imports
861
- if (packagePath.startsWith('lua-cli')) {
862
- continue;
863
- }
864
- // Handle zod
865
- if (packagePath === 'zod') {
866
- if (namedImports) {
867
- const importsList = namedImports.split(',').map(imp => imp.trim());
868
- const usedImports = importsList.filter(imp => serviceContent.includes(`${imp}.`));
869
- if (usedImports.length > 0) {
870
- const requireStatement = usedImports.length === 1
871
- ? `const { ${usedImports[0]} } = require('zod');`
872
- : `const { ${usedImports.join(', ')} } = require('zod');`;
873
- bundledPackages.add(requireStatement);
401
+ // Fallback to direct parsing if AST approach fails
402
+ return parseZodTextToJsonSchema(zodSchemaText);
403
+ }
404
+ catch (error) {
405
+ console.warn('Warning: Could not parse Zod schema with AST, falling back to text parsing:', error);
406
+ return parseZodTextToJsonSchema(zodSchemaText);
407
+ }
408
+ }
409
+ function parseZodASTToJsonSchema(node) {
410
+ try {
411
+ // Handle z.object({ ... })
412
+ if (Node.isCallExpression(node)) {
413
+ const expression = node.getExpression();
414
+ if (Node.isPropertyAccessExpression(expression)) {
415
+ const object = expression.getExpression();
416
+ const property = expression.getName();
417
+ if (object.getText() === 'z' && property === 'object') {
418
+ const args = node.getArguments();
419
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
420
+ const objectLiteral = args[0];
421
+ const properties = {};
422
+ const required = [];
423
+ objectLiteral.getProperties().forEach((prop) => {
424
+ if (Node.isPropertyAssignment(prop)) {
425
+ const name = prop.getName();
426
+ const value = prop.getInitializer();
427
+ if (value) {
428
+ const propSchema = parseZodASTToJsonSchema(value);
429
+ properties[name] = propSchema;
430
+ // Check if it's required (not optional)
431
+ if (!isOptionalZodType(value)) {
432
+ required.push(name);
433
+ }
434
+ }
874
435
  }
875
- }
876
- else if (defaultImport) {
877
- bundledPackages.add(`const ${defaultImport} = require('zod');`);
878
- }
879
- continue;
880
- }
881
- // Handle axios - bundle it properly
882
- if (packagePath === 'axios') {
883
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
884
- if (packageCode) {
885
- bundledPackages.add(packageCode);
886
- }
887
- continue;
888
- }
889
- // Bundle other external packages
890
- if (namedImports || defaultImport) {
891
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
892
- if (packageCode) {
893
- bundledPackages.add(packageCode);
894
- }
436
+ });
437
+ return {
438
+ type: 'object',
439
+ properties,
440
+ required
441
+ };
895
442
  }
896
443
  }
897
- // Extract the service class with proper brace matching
898
- const classRegex = /class\s+(\w+)(?:\s+extends\s+\w+)?\s*\{/g;
899
- let classMatch = classRegex.exec(serviceContent);
900
- if (classMatch) {
901
- const serviceClassName = classMatch[1];
902
- const startIndex = classMatch.index + classMatch[0].length - 1; // Position of opening brace
903
- // Find matching closing brace
904
- let braceCount = 1;
905
- let endIndex = startIndex + 1;
906
- while (endIndex < serviceContent.length && braceCount > 0) {
907
- if (serviceContent[endIndex] === '{')
908
- braceCount++;
909
- else if (serviceContent[endIndex] === '}')
910
- braceCount--;
911
- endIndex++;
912
- }
913
- if (braceCount === 0) {
914
- const serviceClassBody = serviceContent.substring(startIndex + 1, endIndex - 1);
915
- // Clean up the class body (remove TypeScript types)
916
- const cleanClassBody = serviceClassBody
917
- .replace(/:\s*string/g, '')
918
- .replace(/:\s*number/g, '')
919
- .replace(/:\s*boolean/g, '')
920
- .replace(/:\s*any/g, '')
921
- .replace(/:\s*void/g, '')
922
- .replace(/:\s*Promise<[^>]+>/g, '')
923
- .replace(/:\s*Record<[^>]+>/g, '');
924
- // Create the service class
925
- const serviceClass = `class ${serviceClassName} {\n${cleanClassBody}\n}`;
926
- dependencies.push(serviceClass);
444
+ // Handle other z.* types
445
+ if (object.getText() === 'z') {
446
+ switch (property) {
447
+ case 'string':
448
+ return { type: 'string' };
449
+ case 'number':
450
+ return { type: 'number' };
451
+ case 'boolean':
452
+ return { type: 'boolean' };
453
+ case 'any':
454
+ return { type: 'object' };
455
+ case 'array':
456
+ const arrayArgs = node.getArguments();
457
+ if (arrayArgs.length > 0) {
458
+ const itemSchema = parseZodASTToJsonSchema(arrayArgs[0]);
459
+ return { type: 'array', items: itemSchema };
460
+ }
461
+ return { type: 'array' };
462
+ case 'enum':
463
+ const enumArgs = node.getArguments();
464
+ if (enumArgs.length > 0 && Node.isArrayLiteralExpression(enumArgs[0])) {
465
+ const enumValues = enumArgs[0].getElements().map((element) => {
466
+ if (Node.isStringLiteral(element)) {
467
+ return element.getLiteralValue();
468
+ }
469
+ return element.getText().replace(/['"]/g, '');
470
+ });
471
+ return { type: 'string', enum: enumValues };
472
+ }
473
+ return { type: 'string' };
927
474
  }
928
475
  }
929
476
  }
930
- continue;
931
- }
932
- // Bundle other external packages
933
- if (namedImports || defaultImport) {
934
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
935
- if (packageCode) {
936
- bundledPackages.add(packageCode);
477
+ // Handle method chaining like z.string().optional()
478
+ if (Node.isPropertyAccessExpression(expression)) {
479
+ const baseExpression = expression.getExpression();
480
+ const method = expression.getName();
481
+ if (method === 'optional' && Node.isCallExpression(baseExpression)) {
482
+ // This is an optional field, parse the base type
483
+ return parseZodASTToJsonSchema(baseExpression);
484
+ }
937
485
  }
938
486
  }
487
+ // Fallback
488
+ return { type: 'object' };
489
+ }
490
+ catch (error) {
491
+ console.warn('Warning: Could not parse Zod AST node:', error);
492
+ return { type: 'object' };
939
493
  }
940
- // 2. Extract class instantiation from constructor
941
- const constructorMatch = toolContent.match(/constructor\s*\([^)]*\)\s*\{([\s\S]*?)\}/);
942
- if (constructorMatch) {
943
- const constructorBody = constructorMatch[1];
944
- // Extract service instantiation
945
- const serviceInstantiationMatch = constructorBody.match(/this\.(\w+)\s*=\s*new\s+(\w+)\([^)]*\);/);
946
- if (serviceInstantiationMatch) {
947
- const serviceProperty = serviceInstantiationMatch[1];
948
- const serviceClass = serviceInstantiationMatch[2];
949
- dependencies.push(`const ${serviceProperty} = new ${serviceClass}();`);
494
+ }
495
+ function isOptionalZodType(node) {
496
+ // Check if this is a method call ending with .optional()
497
+ if (Node.isCallExpression(node)) {
498
+ const expression = node.getExpression();
499
+ if (Node.isPropertyAccessExpression(expression)) {
500
+ const method = expression.getName();
501
+ return method === 'optional';
950
502
  }
951
503
  }
952
- // 3. Create the self-contained execute function
953
- const allDependencies = [...Array.from(bundledPackages), ...dependencies];
954
- const dependencyCode = allDependencies.join('\n');
955
- // Clean the execute body (remove TypeScript types)
956
- const cleanExecuteBody = executeBody
957
- .replace(/:\s*string/g, '')
958
- .replace(/:\s*number/g, '')
959
- .replace(/:\s*boolean/g, '')
960
- .replace(/:\s*any/g, '')
961
- .replace(/:\s*void/g, '')
962
- .replace(/:\s*Promise<[^>]+>/g, '')
963
- .replace(/:\s*Record<[^>]+>/g, '');
964
- // Replace this.serviceProperty with the instantiated service
965
- const finalExecuteBody = cleanExecuteBody.replace(/this\.(\w+)/g, '$1');
966
- return `async (input) => {
967
- ${dependencyCode ? dependencyCode + '\n' : ''}${finalExecuteBody}
968
- }`;
504
+ return false;
969
505
  }
970
- async function extractSkillMetadata(indexContent) {
971
- // Extract skillId and version from config file
972
- let skillId = '';
973
- let version = '';
506
+ function parseZodTextToJsonSchema(zodSchemaText) {
507
+ try {
508
+ // Fallback text-based parsing for when AST parsing fails
509
+ if (zodSchemaText.includes('z.object({')) {
510
+ const properties = {};
511
+ const required = [];
512
+ // Extract object properties - enhanced pattern matching
513
+ const propertyPattern = /(\w+):\s*z\.(\w+)\(\)/g;
514
+ let match;
515
+ while ((match = propertyPattern.exec(zodSchemaText)) !== null) {
516
+ const [, propName, zodType] = match;
517
+ switch (zodType) {
518
+ case 'string':
519
+ properties[propName] = { type: 'string' };
520
+ break;
521
+ case 'number':
522
+ properties[propName] = { type: 'number' };
523
+ break;
524
+ case 'boolean':
525
+ properties[propName] = { type: 'boolean' };
526
+ break;
527
+ case 'any':
528
+ properties[propName] = { type: 'object' };
529
+ break;
530
+ default:
531
+ properties[propName] = { type: 'string' };
532
+ }
533
+ // Check if it's optional
534
+ if (!zodSchemaText.includes(`${propName}: z.${zodType}().optional()`)) {
535
+ required.push(propName);
536
+ }
537
+ }
538
+ // Handle arrays
539
+ const arrayPattern = /(\w+):\s*z\.array\(z\.(\w+)\(\)\)/g;
540
+ while ((match = arrayPattern.exec(zodSchemaText)) !== null) {
541
+ const [, propName, itemType] = match;
542
+ let itemSchema = { type: 'string' };
543
+ switch (itemType) {
544
+ case 'string':
545
+ itemSchema = { type: 'string' };
546
+ break;
547
+ case 'number':
548
+ itemSchema = { type: 'number' };
549
+ break;
550
+ case 'boolean':
551
+ itemSchema = { type: 'boolean' };
552
+ break;
553
+ }
554
+ properties[propName] = { type: 'array', items: itemSchema };
555
+ if (!zodSchemaText.includes(`${propName}: z.array(z.${itemType}()).optional()`)) {
556
+ required.push(propName);
557
+ }
558
+ }
559
+ return {
560
+ type: 'object',
561
+ properties,
562
+ required
563
+ };
564
+ }
565
+ // Handle simple types
566
+ if (zodSchemaText.includes('z.string()'))
567
+ return { type: 'string' };
568
+ if (zodSchemaText.includes('z.number()'))
569
+ return { type: 'number' };
570
+ if (zodSchemaText.includes('z.boolean()'))
571
+ return { type: 'boolean' };
572
+ if (zodSchemaText.includes('z.array('))
573
+ return { type: 'array' };
574
+ // Fallback
575
+ return { type: 'object' };
576
+ }
577
+ catch (error) {
578
+ console.warn('Warning: Could not parse Zod schema text:', error);
579
+ return { type: 'object' };
580
+ }
581
+ }
582
+ async function createDeploymentData(tools, distDir) {
583
+ const config = readSkillConfig();
584
+ const packageJsonPath = path.join(process.cwd(), "package.json");
585
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
586
+ const deploymentData = {
587
+ name: config?.skill?.name || packageJson.name || "lua-skill",
588
+ version: config?.skill?.version || packageJson.version || "1.0.0",
589
+ skillId: config?.skill?.skillId || "",
590
+ description: config?.skill?.description || packageJson.description || "",
591
+ context: config?.skill?.context || "",
592
+ tools: tools.map(tool => ({
593
+ name: tool.name,
594
+ className: tool.className,
595
+ description: tool.description || "",
596
+ filePath: `tools/${tool.className}.js`
597
+ }))
598
+ };
599
+ fs.writeFileSync(path.join(distDir, 'deployment.json'), JSON.stringify(deploymentData, null, 2));
600
+ }
601
+ async function createLegacyDeploymentData(tools, luaDir, indexFile) {
974
602
  const config = readSkillConfig();
975
- if (config) {
976
- skillId = config.skill?.skillId || '';
977
- version = config.skill?.version || '';
603
+ const packageJsonPath = path.join(process.cwd(), "package.json");
604
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
605
+ // Extract skill metadata from index.ts
606
+ const skillMetadata = extractSkillMetadata(indexFile);
607
+ const deployData = {
608
+ version: config?.skill?.version || packageJson.version || "1.0.0",
609
+ name: config?.skill?.name || packageJson.name || "lua-skill",
610
+ skillsName: config?.skill?.name || packageJson.name || "lua-skill",
611
+ skillId: config?.skill?.skillId || skillMetadata.skillId || "",
612
+ description: config?.skill?.description || skillMetadata.description || "",
613
+ context: config?.skill?.context || skillMetadata.context || "",
614
+ tools: tools.map(tool => ({
615
+ name: tool.name,
616
+ description: tool.description || "",
617
+ inputSchema: tool.inputSchema || { type: "object" },
618
+ execute: compressCode(tool.executeCode || "")
619
+ }))
620
+ };
621
+ // Write legacy deploy.json to .lua directory
622
+ fs.writeFileSync(path.join(luaDir, "deploy.json"), JSON.stringify(deployData, null, 2));
623
+ // Write individual tool files to .lua directory (uncompressed)
624
+ for (const tool of tools) {
625
+ if (tool.executeCode) {
626
+ const toolFilePath = path.join(luaDir, `${tool.name}.js`);
627
+ fs.writeFileSync(toolFilePath, tool.executeCode);
628
+ }
978
629
  }
979
- // Extract description and context from LuaSkill constructor
630
+ }
631
+ function extractSkillMetadata(indexFile) {
632
+ let skillId = '';
980
633
  let description = '';
981
634
  let context = '';
982
- // Look for LuaSkill constructor with object configuration
983
- const luaSkillRegex = /new\s+LuaSkill\s*\(\s*\{([\s\S]*?)\}\s*\)/;
984
- const match = luaSkillRegex.exec(indexContent);
985
- if (match) {
986
- const configContent = match[1];
987
- // Extract description
988
- const descriptionMatch = configContent.match(/description:\s*["']([^"']+)["']/);
989
- if (descriptionMatch) {
990
- description = descriptionMatch[1];
991
- }
992
- // Extract context
993
- const contextMatch = configContent.match(/context:\s*["']([^"']+)["']/);
994
- if (contextMatch) {
995
- context = contextMatch[1];
635
+ // Find LuaSkill constructor call
636
+ indexFile.forEachDescendant((node) => {
637
+ if (Node.isNewExpression(node)) {
638
+ const expression = node.getExpression();
639
+ if (expression.getText() === 'LuaSkill') {
640
+ const args = node.getArguments();
641
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
642
+ const configObj = args[0];
643
+ // Extract properties
644
+ configObj.getProperties().forEach((prop) => {
645
+ if (Node.isPropertyAssignment(prop)) {
646
+ const name = prop.getName();
647
+ const value = prop.getInitializer()?.getText();
648
+ if (name === 'description' && value) {
649
+ description = value.replace(/['"]/g, '');
650
+ }
651
+ else if (name === 'context' && value) {
652
+ context = value.replace(/['"]/g, '');
653
+ }
654
+ }
655
+ });
656
+ }
657
+ }
996
658
  }
997
- }
998
- return {
999
- skillId,
1000
- version,
1001
- description,
1002
- context
1003
- };
659
+ });
660
+ return { skillId, description, context };
1004
661
  }