lua-cli 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +244 -63
- package/dist/commands/agents.js +17 -17
- package/dist/commands/apiKey.js +24 -19
- package/dist/commands/compile.d.ts +1 -0
- package/dist/commands/compile.js +1004 -0
- package/dist/commands/configure.js +93 -68
- package/dist/commands/deploy-new.d.ts +0 -0
- package/dist/commands/deploy-new.js +130 -0
- package/dist/commands/deploy.d.ts +19 -0
- package/dist/commands/deploy.js +81 -763
- package/dist/commands/destroy.js +26 -21
- package/dist/commands/dev.d.ts +63 -0
- package/dist/commands/dev.js +656 -0
- package/dist/commands/index.d.ts +4 -2
- package/dist/commands/index.js +4 -2
- package/dist/commands/init.js +297 -62
- package/dist/commands/push.d.ts +22 -0
- package/dist/commands/push.js +127 -0
- package/dist/commands/test.js +14 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +35 -19
- package/dist/services/api.d.ts +195 -0
- package/dist/services/api.js +209 -0
- package/dist/services/auth.d.ts +102 -0
- package/dist/services/auth.js +129 -40
- package/dist/skill.d.ts +22 -1
- package/dist/skill.js +21 -1
- package/dist/types/index.d.ts +16 -2
- package/dist/types/index.js +16 -1
- package/dist/user-data-api.d.ts +52 -0
- package/dist/user-data-api.js +151 -0
- package/dist/utils/cli.d.ts +34 -0
- package/dist/utils/cli.js +58 -0
- package/dist/utils/files.d.ts +4 -1
- package/dist/utils/files.js +61 -14
- package/dist/web/app.css +1050 -0
- package/dist/web/app.js +79 -0
- package/dist/web/tools-page.css +377 -0
- package/package.json +18 -5
- package/template/package-lock.json +32 -3
- package/template/package.json +3 -1
- package/template/{index.ts → src/index.ts} +13 -4
- package/template/src/tools/UserPreferencesTool.ts +73 -0
- package/template/tools/UserPreferencesTool.ts +73 -0
- package/template/tsconfig.json +1 -1
- package/template/.lua/deploy.json +0 -145
- /package/template/{services → src/services}/ApiService.ts +0 -0
- /package/template/{services → src/services}/GetWeather.ts +0 -0
- /package/template/{services → src/services}/MathService.ts +0 -0
- /package/template/{tools → src/tools}/AdvancedMathTool.ts +0 -0
- /package/template/{tools → src/tools}/CalculatorTool.ts +0 -0
- /package/template/{tools → src/tools}/CreatePostTool.ts +0 -0
- /package/template/{tools → src/tools}/GetUserDataTool.ts +0 -0
- /package/template/{tools → src/tools}/GetWeatherTool.ts +0 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { gzipSync, gunzipSync } from "zlib";
|
|
4
|
+
import { Buffer } from "buffer";
|
|
5
|
+
import { withErrorHandling, writeProgress, writeSuccess } from "../utils/cli.js";
|
|
6
|
+
import { readSkillConfig } from '../utils/files.js';
|
|
7
|
+
import * as ts from "typescript";
|
|
8
|
+
// Compression utilities
|
|
9
|
+
function compressCode(code) {
|
|
10
|
+
const compressed = gzipSync(code);
|
|
11
|
+
return compressed.toString('base64');
|
|
12
|
+
}
|
|
13
|
+
function decompressCode(compressedCode) {
|
|
14
|
+
const buffer = Buffer.from(compressedCode, 'base64');
|
|
15
|
+
return gunzipSync(buffer).toString('utf8');
|
|
16
|
+
}
|
|
17
|
+
export async function compileCommand() {
|
|
18
|
+
return withErrorHandling(async () => {
|
|
19
|
+
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
|
|
59
|
+
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
|
|
182
|
+
});
|
|
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
|
+
}
|
|
193
|
+
}
|
|
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;
|
|
205
|
+
}
|
|
206
|
+
// Fallback regex-based extraction (renamed from the original function)
|
|
207
|
+
async function extractSkillInfoRegex(indexContent, indexFilePath) {
|
|
208
|
+
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);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
searchIndex = callEnd + 1;
|
|
291
|
+
}
|
|
292
|
+
return tools;
|
|
293
|
+
}
|
|
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;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
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;
|
|
419
|
+
}
|
|
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;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (isUsed) {
|
|
431
|
+
dependencies.push(fullClass);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
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
|
+
}
|
|
444
|
+
}
|
|
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
|
+
}
|
|
464
|
+
}
|
|
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
|
+
}
|
|
479
|
+
}
|
|
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
|
+
}
|
|
515
|
+
async function bundlePackageCode(packagePath, namedImports, defaultImport) {
|
|
516
|
+
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],
|
|
545
|
+
bundle: true,
|
|
546
|
+
format: 'cjs', // CommonJS format
|
|
547
|
+
platform: 'node',
|
|
548
|
+
target: 'node16',
|
|
549
|
+
outfile: outputFile,
|
|
550
|
+
external: [], // Bundle everything
|
|
551
|
+
minify: false, // Keep readable for debugging
|
|
552
|
+
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(),
|
|
563
|
+
});
|
|
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;
|
|
596
|
+
}
|
|
597
|
+
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;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
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
|
|
619
|
+
}
|
|
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;
|
|
652
|
+
}
|
|
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
|
+
},
|
|
663
|
+
|
|
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;
|
|
678
|
+
}
|
|
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
|
+
},
|
|
689
|
+
|
|
690
|
+
delete: async (url, config = {}) => {
|
|
691
|
+
const response = await fetch(url, {
|
|
692
|
+
method: 'DELETE',
|
|
693
|
+
headers: {
|
|
694
|
+
'Content-Type': 'application/json',
|
|
695
|
+
...config.headers
|
|
696
|
+
}
|
|
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;
|
|
703
|
+
}
|
|
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
|
|
712
|
+
};
|
|
713
|
+
},
|
|
714
|
+
|
|
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;
|
|
729
|
+
}
|
|
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;
|
|
750
|
+
}
|
|
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');
|
|
758
|
+
}
|
|
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;
|
|
783
|
+
}
|
|
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;
|
|
792
|
+
}
|
|
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
|
+
}
|
|
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');`);
|
|
835
|
+
}
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
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);
|
|
843
|
+
}
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
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);
|
|
874
|
+
}
|
|
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
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
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);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
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);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
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}();`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
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
|
+
}`;
|
|
969
|
+
}
|
|
970
|
+
async function extractSkillMetadata(indexContent) {
|
|
971
|
+
// Extract skillId and version from config file
|
|
972
|
+
let skillId = '';
|
|
973
|
+
let version = '';
|
|
974
|
+
const config = readSkillConfig();
|
|
975
|
+
if (config) {
|
|
976
|
+
skillId = config.skill?.skillId || '';
|
|
977
|
+
version = config.skill?.version || '';
|
|
978
|
+
}
|
|
979
|
+
// Extract description and context from LuaSkill constructor
|
|
980
|
+
let description = '';
|
|
981
|
+
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];
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return {
|
|
999
|
+
skillId,
|
|
1000
|
+
version,
|
|
1001
|
+
description,
|
|
1002
|
+
context
|
|
1003
|
+
};
|
|
1004
|
+
}
|