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