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.
- package/dist/commands/compile.js +580 -923
- package/dist/commands/dev.js +117 -114
- package/dist/commands/init.js +10 -0
- package/dist/commands/test.js +122 -130
- package/dist/services/api.d.ts +4 -4
- package/dist/services/api.js +8 -4
- package/dist/skill.d.ts +16 -1
- package/dist/skill.js +49 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/user-data-api.d.ts +1 -0
- package/dist/user-data-api.js +9 -4
- package/dist/utils/files.js +31 -5
- package/dist/utils/sandbox.d.ts +27 -0
- package/dist/utils/sandbox.js +271 -0
- package/dist/web/app.css +274 -43
- package/dist/web/app.js +13 -13
- package/dist/web/tools-page.css +70 -53
- package/package.json +2 -1
- package/template/create-test.cjs +39 -0
- package/template/lua.skill.yaml +17 -0
- package/template/package-lock.json +42 -2
- package/template/package.json +6 -4
- package/template/src/index.ts +15 -14
- package/template/src/seed.ts +46 -0
- package/template/src/tools/CreatePostTool.ts +15 -23
- package/template/src/tools/GetWeatherTool.ts +45 -29
- package/template/src/tools/PaymentTool.ts +52 -0
- package/template/src/tools/SearchProducts.ts +43 -0
- package/template/src/tools/UserDataTool.ts +56 -0
- package/template/src/services/MathService.ts +0 -61
- package/template/src/tools/AdvancedMathTool.ts +0 -82
- package/template/src/tools/CalculatorTool.ts +0 -65
- package/template/src/tools/GetUserDataTool.ts +0 -38
- package/template/tools/UserPreferencesTool.ts +0 -73
package/dist/commands/compile.js
CHANGED
|
@@ -1,1004 +1,661 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { gzipSync
|
|
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
|
|
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
|
-
//
|
|
21
|
-
const
|
|
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 (
|
|
61
|
-
fs.
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
async function extractSkillInfoRegex(indexContent, indexFilePath) {
|
|
66
|
+
async function detectTools(indexFile, project) {
|
|
208
67
|
const tools = [];
|
|
209
|
-
// Find all
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
291
|
-
}
|
|
106
|
+
});
|
|
292
107
|
return tools;
|
|
293
108
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
411
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
|
186
|
+
async function bundleTool(tool, distDir) {
|
|
187
|
+
writeProgress(`📦 Bundling ${tool.className}...`);
|
|
516
188
|
try {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
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',
|
|
193
|
+
format: 'cjs',
|
|
547
194
|
platform: 'node',
|
|
548
195
|
target: 'node16',
|
|
549
|
-
outfile:
|
|
550
|
-
external: [], //
|
|
551
|
-
minify:
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
565
|
-
|
|
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:
|
|
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
|
|
607
|
-
|
|
608
|
-
//
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
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
|
-
|
|
752
|
-
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
-
|
|
785
|
-
|
|
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
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
//
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
-
//
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
.
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
if (
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
-
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
630
|
+
}
|
|
631
|
+
function extractSkillMetadata(indexFile) {
|
|
632
|
+
let skillId = '';
|
|
980
633
|
let description = '';
|
|
981
634
|
let context = '';
|
|
982
|
-
//
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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
|
}
|