lua-cli 1.1.2 → 1.1.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.
@@ -0,0 +1 @@
1
+ export declare function agentsCommand(): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function apiKeyCommand(): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function configureCommand(): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function deployCommand(): Promise<void>;
@@ -0,0 +1,534 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export async function deployCommand() {
4
+ try {
5
+ console.log("🔨 Compiling Lua skill...");
6
+ // Read package.json to get version and name
7
+ const packageJsonPath = path.join(process.cwd(), "package.json");
8
+ if (!fs.existsSync(packageJsonPath)) {
9
+ throw new Error("package.json not found in current directory");
10
+ }
11
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
12
+ const version = packageJson.version || "1.0.0";
13
+ const skillsName = packageJson.name || "lua-skill";
14
+ // Read index.ts file
15
+ const indexPath = path.join(process.cwd(), "index.ts");
16
+ if (!fs.existsSync(indexPath)) {
17
+ throw new Error("index.ts not found in current directory");
18
+ }
19
+ const indexContent = fs.readFileSync(indexPath, "utf8");
20
+ // Extract skill information
21
+ const skillInfo = await extractSkillInfo(indexContent);
22
+ // Create deployment data
23
+ const deployData = {
24
+ version,
25
+ skillsName,
26
+ tools: skillInfo
27
+ };
28
+ // Create .lua directory
29
+ const luaDir = path.join(process.cwd(), ".lua");
30
+ if (!fs.existsSync(luaDir)) {
31
+ fs.mkdirSync(luaDir, { recursive: true });
32
+ }
33
+ // Write JSON output to .lua directory
34
+ const jsonOutputPath = path.join(luaDir, "deploy.json");
35
+ fs.writeFileSync(jsonOutputPath, JSON.stringify(deployData, null, 2));
36
+ // Write individual tool files to .lua directory
37
+ for (const tool of skillInfo) {
38
+ const toolFilePath = path.join(luaDir, `${tool.name}.js`);
39
+ fs.writeFileSync(toolFilePath, tool.execute);
40
+ }
41
+ console.log(`📁 Compiled files written to: ${luaDir}`);
42
+ console.log(`📄 JSON output: ${jsonOutputPath}`);
43
+ console.log(`🔧 Tool files: ${skillInfo.map(t => `${t.name}.js`).join(', ')}`);
44
+ console.log("✅ Skill compilation completed successfully!");
45
+ }
46
+ catch (error) {
47
+ console.error("❌ Compilation failed:", error.message);
48
+ process.exit(1);
49
+ }
50
+ }
51
+ async function extractSkillInfo(indexContent) {
52
+ const tools = [];
53
+ // Find all addTool calls
54
+ const addToolRegex = /skill\.addTool\(\s*\{([\s\S]*?)\}\s*\)/g;
55
+ let match;
56
+ while ((match = addToolRegex.exec(indexContent)) !== null) {
57
+ const toolContent = match[1];
58
+ // Extract tool properties
59
+ const nameMatch = toolContent.match(/name:\s*["']([^"']+)["']/);
60
+ const descriptionMatch = toolContent.match(/description:\s*["']([^"']+)["']/);
61
+ const inputSchemaMatch = toolContent.match(/inputSchema:\s*(\w+)/);
62
+ const outputSchemaMatch = toolContent.match(/outputSchema:\s*(\w+)/);
63
+ const executeMatch = toolContent.match(/execute:\s*async\s*\([^)]*\)\s*=>\s*\{([\s\S]*?)\}/);
64
+ if (nameMatch && descriptionMatch && inputSchemaMatch && outputSchemaMatch && executeMatch) {
65
+ const toolName = nameMatch[1];
66
+ const toolDescription = descriptionMatch[1];
67
+ const inputSchemaVar = inputSchemaMatch[1];
68
+ const outputSchemaVar = outputSchemaMatch[1];
69
+ const executeBody = executeMatch[1];
70
+ // Convert schemas to JSON Schema format
71
+ const inputSchema = convertSchemaToJSON(inputSchemaVar, indexContent);
72
+ const outputSchema = convertSchemaToJSON(outputSchemaVar, indexContent);
73
+ // Create self-contained execute function
74
+ const selfContainedExecute = await createSelfContainedExecute(executeBody, indexContent);
75
+ tools.push({
76
+ name: toolName,
77
+ description: toolDescription,
78
+ inputSchema,
79
+ outputSchema,
80
+ execute: selfContainedExecute
81
+ });
82
+ }
83
+ }
84
+ return tools;
85
+ }
86
+ function convertSchemaToJSON(schemaVar, indexContent) {
87
+ // Find the schema definition
88
+ const schemaRegex = new RegExp(`const\\s+${schemaVar}\\s*=\\s*z\\.object\\(\\{([\\s\\S]*?)\\}\\\);`, 'g');
89
+ const match = schemaRegex.exec(indexContent);
90
+ if (match) {
91
+ const schemaContent = match[1];
92
+ // Convert Zod schema to JSON Schema format
93
+ return {
94
+ type: "object",
95
+ properties: parseZodProperties(schemaContent),
96
+ required: extractRequiredFields(schemaContent)
97
+ };
98
+ }
99
+ // If no match found, return empty schema
100
+ return { type: "object", properties: {} };
101
+ }
102
+ function parseZodProperties(schemaContent) {
103
+ const properties = {};
104
+ // Simple regex to find z.string(), z.number(), etc.
105
+ const fieldRegex = /(\w+):\s*z\.(\w+)\(\)/g;
106
+ let match;
107
+ while ((match = fieldRegex.exec(schemaContent)) !== null) {
108
+ const fieldName = match[1];
109
+ const fieldType = match[2];
110
+ switch (fieldType) {
111
+ case 'string':
112
+ properties[fieldName] = { type: 'string' };
113
+ break;
114
+ case 'number':
115
+ properties[fieldName] = { type: 'number' };
116
+ break;
117
+ case 'boolean':
118
+ properties[fieldName] = { type: 'boolean' };
119
+ break;
120
+ default:
121
+ properties[fieldName] = { type: 'string' };
122
+ }
123
+ }
124
+ return properties;
125
+ }
126
+ function extractRequiredFields(schemaContent) {
127
+ const required = [];
128
+ const fieldRegex = /(\w+):\s*z\.(\w+)\(\)/g;
129
+ let match;
130
+ while ((match = fieldRegex.exec(schemaContent)) !== null) {
131
+ required.push(match[1]);
132
+ }
133
+ return required;
134
+ }
135
+ async function createSelfContainedExecute(executeBody, indexContent) {
136
+ const dependencies = [];
137
+ const bundledPackages = new Set();
138
+ // 1. Parse external package imports and bundle their code
139
+ const allImportRegex = /import\s+(?:(?:\{([^}]+)\})|(\w+))\s+from\s+["']([^"']+)["']/g;
140
+ let importMatch;
141
+ while ((importMatch = allImportRegex.exec(indexContent)) !== null) {
142
+ const namedImports = importMatch[1]; // Named imports like { z }
143
+ const defaultImport = importMatch[2]; // Default import like axios
144
+ const packagePath = importMatch[3];
145
+ // Skip local imports (relative paths)
146
+ if (packagePath.startsWith('./') || packagePath.startsWith('../')) {
147
+ continue;
148
+ }
149
+ // Skip lua-cli imports (these are handled separately)
150
+ if (packagePath.startsWith('lua-cli')) {
151
+ continue;
152
+ }
153
+ // Skip zod - assume it's always available on target machine
154
+ if (packagePath === 'zod') {
155
+ // Add require statement for zod instead of bundling
156
+ if (namedImports) {
157
+ const importsList = namedImports.split(',').map(imp => imp.trim());
158
+ const usedImports = importsList.filter(imp => executeBody.includes(imp) || indexContent.includes(`${imp}.`));
159
+ if (usedImports.length > 0) {
160
+ const requireStatement = usedImports.length === 1
161
+ ? `const { ${usedImports[0]} } = require('zod');`
162
+ : `const { ${usedImports.join(', ')} } = require('zod');`;
163
+ bundledPackages.add(requireStatement);
164
+ }
165
+ }
166
+ else if (defaultImport) {
167
+ bundledPackages.add(`const ${defaultImport} = require('zod');`);
168
+ }
169
+ continue;
170
+ }
171
+ // Bundle other external packages
172
+ if (namedImports || defaultImport) {
173
+ const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
174
+ if (packageCode) {
175
+ bundledPackages.add(packageCode);
176
+ }
177
+ }
178
+ }
179
+ // 2. Extract class definitions with proper brace matching
180
+ const classRegex = /class\s+(\w+)(?:\s+extends\s+\w+)?\s*\{/g;
181
+ let classMatch;
182
+ while ((classMatch = classRegex.exec(indexContent)) !== null) {
183
+ const className = classMatch[1];
184
+ const classStart = classMatch.index;
185
+ // Find the matching closing brace
186
+ let braceCount = 0;
187
+ let classEnd = classStart;
188
+ let found = false;
189
+ for (let i = classStart; i < indexContent.length; i++) {
190
+ if (indexContent[i] === '{') {
191
+ braceCount++;
192
+ }
193
+ else if (indexContent[i] === '}') {
194
+ braceCount--;
195
+ if (braceCount === 0) {
196
+ classEnd = i;
197
+ found = true;
198
+ break;
199
+ }
200
+ }
201
+ }
202
+ if (found) {
203
+ const fullClass = indexContent.substring(classStart, classEnd + 1);
204
+ // Check if this class is used in the execute function
205
+ let isUsed = false;
206
+ // Direct usage in execute body
207
+ if (executeBody.includes(`new ${className}`) ||
208
+ executeBody.includes(`${className}.`) ||
209
+ executeBody.includes(`${className}(`)) {
210
+ isUsed = true;
211
+ }
212
+ // Check if any variable that uses this class is referenced in execute body
213
+ const variableRegex = new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+${className}\\s*\\([^)]*\\);`, 'g');
214
+ let varMatch;
215
+ while ((varMatch = variableRegex.exec(indexContent)) !== null) {
216
+ const varName = varMatch[1];
217
+ if (executeBody.includes(varName)) {
218
+ isUsed = true;
219
+ break;
220
+ }
221
+ }
222
+ if (isUsed) {
223
+ dependencies.push(fullClass);
224
+ }
225
+ }
226
+ }
227
+ // 3. Extract function definitions
228
+ const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\}/g;
229
+ let functionMatch;
230
+ while ((functionMatch = functionRegex.exec(indexContent)) !== null) {
231
+ const functionName = functionMatch[1];
232
+ const functionBody = functionMatch[2];
233
+ if (executeBody.includes(functionName)) {
234
+ dependencies.push(`function ${functionName}() {\n${functionBody}\n}`);
235
+ }
236
+ }
237
+ // 4. Extract const/let/var declarations (avoid duplicates)
238
+ const varRegex = /(?:const|let|var)\s+(\w+)\s*=\s*([^;]+);/g;
239
+ const declaredVars = new Set();
240
+ let varMatch;
241
+ while ((varMatch = varRegex.exec(indexContent)) !== null) {
242
+ const varName = varMatch[1];
243
+ const varValue = varMatch[2];
244
+ // Skip if it's a class instantiation (we'll handle that separately)
245
+ if (varValue.includes('new ') && varValue.includes('()')) {
246
+ continue;
247
+ }
248
+ // Skip if already declared
249
+ if (declaredVars.has(varName)) {
250
+ continue;
251
+ }
252
+ if (executeBody.includes(varName)) {
253
+ declaredVars.add(varName);
254
+ dependencies.push(`const ${varName} = ${varValue};`);
255
+ }
256
+ }
257
+ // 5. Extract class instantiations (avoid duplicates)
258
+ const instantiationRegex = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(\w+)\([^)]*\);/g;
259
+ let instantiationMatch;
260
+ while ((instantiationMatch = instantiationRegex.exec(indexContent)) !== null) {
261
+ const instanceName = instantiationMatch[1];
262
+ const className = instantiationMatch[2];
263
+ // Skip if already declared
264
+ if (declaredVars.has(instanceName)) {
265
+ continue;
266
+ }
267
+ if (executeBody.includes(instanceName)) {
268
+ declaredVars.add(instanceName);
269
+ dependencies.push(`const ${instanceName} = new ${className}();`);
270
+ }
271
+ }
272
+ // 6. Create the self-contained execute function
273
+ const allDependencies = [...Array.from(bundledPackages), ...dependencies];
274
+ const dependencyCode = allDependencies.join('\n');
275
+ // Strip TypeScript type annotations for JavaScript compatibility (only for local code)
276
+ const cleanDependencyCode = allDependencies.map(dep => {
277
+ // Only strip TypeScript from local dependencies, not bundled packages
278
+ if (dep.includes('require(') || dep.includes('import ')) {
279
+ return dep; // Skip bundled packages
280
+ }
281
+ return dep
282
+ .replace(/:\s*string/g, '') // Remove : string
283
+ .replace(/:\s*number/g, '') // Remove : number
284
+ .replace(/:\s*boolean/g, '') // Remove : boolean
285
+ .replace(/:\s*any/g, '') // Remove : any
286
+ .replace(/:\s*void/g, '') // Remove : void
287
+ .replace(/:\s*object/g, '') // Remove : object
288
+ .replace(/:\s*Array<[^>]+>/g, '') // Remove : Array<Type>
289
+ .replace(/:\s*Promise<[^>]+>/g, '') // Remove : Promise<Type>
290
+ .replace(/:\s*Record<[^>]+>/g, ''); // Remove : Record<Type>
291
+ }).join('\n');
292
+ const cleanExecuteBody = executeBody
293
+ .replace(/:\s*string/g, '') // Remove : string
294
+ .replace(/:\s*number/g, '') // Remove : number
295
+ .replace(/:\s*boolean/g, '') // Remove : boolean
296
+ .replace(/:\s*any/g, '') // Remove : any
297
+ .replace(/:\s*void/g, '') // Remove : void
298
+ .replace(/:\s*object/g, '') // Remove : object
299
+ .replace(/:\s*Array<[^>]+>/g, '') // Remove : Array<Type>
300
+ .replace(/:\s*Promise<[^>]+>/g, '') // Remove : Promise<Type>
301
+ .replace(/:\s*Record<[^>]+>/g, ''); // Remove : Record<Type>
302
+ const selfContainedExecute = `async (input) => {
303
+ ${cleanDependencyCode ? ` ${cleanDependencyCode.split('\n').join('\n ')}\n` : ''} ${cleanExecuteBody.trim()}
304
+ }`;
305
+ return selfContainedExecute;
306
+ }
307
+ async function bundlePackageCode(packagePath, namedImports, defaultImport) {
308
+ try {
309
+ const { build } = await import('esbuild');
310
+ // Create a temporary entry file for esbuild in .lua directory
311
+ const luaDir = path.join(process.cwd(), '.lua');
312
+ if (!fs.existsSync(luaDir)) {
313
+ fs.mkdirSync(luaDir, { recursive: true });
314
+ }
315
+ const entryFile = path.join(luaDir, `${packagePath}-entry.js`);
316
+ const outputFile = path.join(luaDir, `${packagePath}-bundle.js`);
317
+ // Create entry file based on import type
318
+ let entryContent = '';
319
+ if (defaultImport) {
320
+ // For default imports like `import axios from 'axios'`
321
+ entryContent = `import ${defaultImport} from '${packagePath}';\nmodule.exports = ${defaultImport};`;
322
+ }
323
+ else if (namedImports) {
324
+ // For named imports like `import { z } from 'zod'`
325
+ const importsList = namedImports.split(',').map(imp => imp.trim());
326
+ entryContent = `import { ${importsList.join(', ')} } from '${packagePath}';\nmodule.exports = { ${importsList.join(', ')} };`;
327
+ }
328
+ else {
329
+ // Fallback - import everything
330
+ entryContent = `import * as ${packagePath.replace(/[^a-zA-Z0-9]/g, '_')} from '${packagePath}';\nmodule.exports = ${packagePath.replace(/[^a-zA-Z0-9]/g, '_')};`;
331
+ }
332
+ // Write entry file
333
+ fs.writeFileSync(entryFile, entryContent);
334
+ // Bundle with esbuild
335
+ const result = await build({
336
+ entryPoints: [entryFile],
337
+ bundle: true,
338
+ format: 'cjs', // CommonJS format
339
+ platform: 'node',
340
+ target: 'node16',
341
+ outfile: outputFile,
342
+ external: [], // Bundle everything
343
+ minify: false, // Keep readable for debugging
344
+ sourcemap: false,
345
+ write: true,
346
+ resolveExtensions: ['.js', '.ts', '.json'],
347
+ mainFields: ['main', 'module', 'browser'],
348
+ conditions: ['node'],
349
+ nodePaths: [
350
+ path.join(process.cwd(), 'node_modules'),
351
+ path.join(process.cwd(), '..', 'node_modules'),
352
+ path.join(process.cwd(), '..', '..', 'node_modules')
353
+ ],
354
+ absWorkingDir: process.cwd(),
355
+ });
356
+ if (result.errors.length > 0) {
357
+ console.warn(`Warning: esbuild errors for package ${packagePath}:`, result.errors);
358
+ return null;
359
+ }
360
+ // Read the bundled output
361
+ if (!fs.existsSync(outputFile)) {
362
+ console.warn(`Warning: Bundle output not found for package ${packagePath}`);
363
+ return null;
364
+ }
365
+ const bundledContent = fs.readFileSync(outputFile, 'utf8');
366
+ // Clean up temporary files
367
+ try {
368
+ fs.unlinkSync(entryFile);
369
+ fs.unlinkSync(outputFile);
370
+ }
371
+ catch (cleanupError) {
372
+ // Ignore cleanup errors
373
+ }
374
+ // Create the final bundled code
375
+ let finalCode = '';
376
+ if (defaultImport) {
377
+ finalCode = `const ${defaultImport} = (function() {\n${bundledContent}\n return module.exports;\n})();\n`;
378
+ }
379
+ else if (namedImports) {
380
+ const importsList = namedImports.split(',').map(imp => imp.trim());
381
+ finalCode = `(function() {\n${bundledContent}\n})();\n`;
382
+ finalCode += `const { ${importsList.join(', ')} } = module.exports;\n`;
383
+ }
384
+ else {
385
+ finalCode = `(function() {\n${bundledContent}\n})();\n`;
386
+ }
387
+ return finalCode;
388
+ }
389
+ catch (error) {
390
+ console.warn(`Warning: Could not bundle package ${packagePath} with esbuild:`, error);
391
+ // For test environments or when esbuild fails, provide a fallback
392
+ if (packagePath === 'axios') {
393
+ return createWorkingAxiosImplementation();
394
+ }
395
+ return null;
396
+ }
397
+ }
398
+ function createWorkingAxiosImplementation() {
399
+ return `
400
+ // Working axios implementation using native fetch (for test environments)
401
+ const axios = {
402
+ get: async (url, config = {}) => {
403
+ const searchParams = new URLSearchParams(config.params || {});
404
+ const fullUrl = searchParams.toString() ? \`\${url}?\${searchParams}\` : url;
405
+
406
+ const response = await fetch(fullUrl, {
407
+ method: 'GET',
408
+ headers: {
409
+ 'Content-Type': 'application/json',
410
+ ...config.headers
411
+ }
412
+ });
413
+
414
+ if (!response.ok) {
415
+ const error = new Error(\`Request failed with status \${response.status}\`);
416
+ error.response = { status: response.status, statusText: response.statusText };
417
+ throw error;
418
+ }
419
+
420
+ const data = await response.json();
421
+ return {
422
+ data,
423
+ status: response.status,
424
+ statusText: response.statusText,
425
+ headers: response.headers,
426
+ config: config
427
+ };
428
+ },
429
+
430
+ post: async (url, data, config = {}) => {
431
+ const response = await fetch(url, {
432
+ method: 'POST',
433
+ headers: {
434
+ 'Content-Type': 'application/json',
435
+ ...config.headers
436
+ },
437
+ body: JSON.stringify(data)
438
+ });
439
+
440
+ if (!response.ok) {
441
+ const error = new Error(\`Request failed with status \${response.status}\`);
442
+ error.response = { status: response.status, statusText: response.statusText };
443
+ throw error;
444
+ }
445
+
446
+ const responseData = await response.json();
447
+ return {
448
+ data: responseData,
449
+ status: response.status,
450
+ statusText: response.statusText,
451
+ headers: response.headers,
452
+ config: config
453
+ };
454
+ },
455
+
456
+ put: async (url, data, config = {}) => {
457
+ const response = await fetch(url, {
458
+ method: 'PUT',
459
+ headers: {
460
+ 'Content-Type': 'application/json',
461
+ ...config.headers
462
+ },
463
+ body: JSON.stringify(data)
464
+ });
465
+
466
+ if (!response.ok) {
467
+ const error = new Error(\`Request failed with status \${response.status}\`);
468
+ error.response = { status: response.status, statusText: response.statusText };
469
+ throw error;
470
+ }
471
+
472
+ const responseData = await response.json();
473
+ return {
474
+ data: responseData,
475
+ status: response.status,
476
+ statusText: response.statusText,
477
+ headers: response.headers,
478
+ config: config
479
+ };
480
+ },
481
+
482
+ delete: async (url, config = {}) => {
483
+ const response = await fetch(url, {
484
+ method: 'DELETE',
485
+ headers: {
486
+ 'Content-Type': 'application/json',
487
+ ...config.headers
488
+ }
489
+ });
490
+
491
+ if (!response.ok) {
492
+ const error = new Error(\`Request failed with status \${response.status}\`);
493
+ error.response = { status: response.status, statusText: response.statusText };
494
+ throw error;
495
+ }
496
+
497
+ const responseData = await response.json();
498
+ return {
499
+ data: responseData,
500
+ status: response.status,
501
+ statusText: response.statusText,
502
+ headers: response.headers,
503
+ config: config
504
+ };
505
+ },
506
+
507
+ patch: async (url, data, config = {}) => {
508
+ const response = await fetch(url, {
509
+ method: 'PATCH',
510
+ headers: {
511
+ 'Content-Type': 'application/json',
512
+ ...config.headers
513
+ },
514
+ body: JSON.stringify(data)
515
+ });
516
+
517
+ if (!response.ok) {
518
+ const error = new Error(\`Request failed with status \${response.status}\`);
519
+ error.response = { status: response.status, statusText: response.statusText };
520
+ throw error;
521
+ }
522
+
523
+ const responseData = await response.json();
524
+ return {
525
+ data: responseData,
526
+ status: response.status,
527
+ statusText: response.statusText,
528
+ headers: response.headers,
529
+ config: config
530
+ };
531
+ }
532
+ };
533
+ `;
534
+ }
@@ -0,0 +1 @@
1
+ export declare function destroyCommand(): Promise<void>;
@@ -0,0 +1,7 @@
1
+ export { configureCommand } from "./configure.js";
2
+ export { initCommand } from "./init.js";
3
+ export { destroyCommand } from "./destroy.js";
4
+ export { apiKeyCommand } from "./apiKey.js";
5
+ export { agentsCommand } from "./agents.js";
6
+ export { deployCommand } from "./deploy.js";
7
+ export { testCommand } from "./test.js";
@@ -3,3 +3,5 @@ export { initCommand } from "./init.js";
3
3
  export { destroyCommand } from "./destroy.js";
4
4
  export { apiKeyCommand } from "./apiKey.js";
5
5
  export { agentsCommand } from "./agents.js";
6
+ export { deployCommand } from "./deploy.js";
7
+ export { testCommand } from "./test.js";
@@ -0,0 +1 @@
1
+ export declare function initCommand(): Promise<void>;
@@ -66,4 +66,5 @@ export async function initCommand() {
66
66
  copyTemplateFiles(templateDir, currentDir);
67
67
  console.log("✅ Copied template files");
68
68
  console.log("🎉 Lua skill project initialized successfully!");
69
+ console.log("📦 Run `npm install` to install dependencies");
69
70
  }
@@ -0,0 +1 @@
1
+ export declare function testCommand(): Promise<void>;
@@ -0,0 +1,141 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import inquirer from "inquirer";
4
+ import { deployCommand } from "./deploy.js";
5
+ export async function testCommand() {
6
+ try {
7
+ console.log("🧪 Testing Lua skill...");
8
+ // First, compile the code
9
+ console.log("📦 Compiling code first...");
10
+ await deployCommand();
11
+ // Check if .lua directory exists
12
+ const luaDir = path.join(process.cwd(), ".lua");
13
+ if (!fs.existsSync(luaDir)) {
14
+ throw new Error(".lua directory not found. Run 'lua compile' first.");
15
+ }
16
+ // Read deploy.json
17
+ const deployJsonPath = path.join(luaDir, "deploy.json");
18
+ if (!fs.existsSync(deployJsonPath)) {
19
+ throw new Error("deploy.json not found. Run 'lua compile' first.");
20
+ }
21
+ const deployData = JSON.parse(fs.readFileSync(deployJsonPath, "utf8"));
22
+ if (!deployData.tools || deployData.tools.length === 0) {
23
+ throw new Error("No tools found in deploy.json");
24
+ }
25
+ // Let user select a tool using picker
26
+ const toolChoices = deployData.tools.map((tool) => ({
27
+ name: `${tool.name} - ${tool.description}`,
28
+ value: tool
29
+ }));
30
+ const { selectedTool } = await inquirer.prompt([
31
+ {
32
+ type: 'list',
33
+ name: 'selectedTool',
34
+ message: '🔧 Select a tool to test:',
35
+ choices: toolChoices,
36
+ pageSize: 10
37
+ }
38
+ ]);
39
+ console.log(`\n✅ Selected tool: ${selectedTool.name}`);
40
+ // Get input values for each required parameter using inquirer
41
+ const inputValues = {};
42
+ const inputSchema = selectedTool.inputSchema;
43
+ if (inputSchema.properties) {
44
+ console.log("\n📝 Enter input values:");
45
+ const inputPrompts = [];
46
+ for (const [key, value] of Object.entries(inputSchema.properties)) {
47
+ const property = value;
48
+ const isRequired = inputSchema.required?.includes(key) || false;
49
+ let promptType = 'input';
50
+ let validate = undefined;
51
+ // Set up validation and input type based on schema
52
+ switch (property.type) {
53
+ case "string":
54
+ if (isRequired) {
55
+ validate = (input) => input.trim() !== "" || `${key} is required`;
56
+ }
57
+ break;
58
+ case "number":
59
+ promptType = 'number';
60
+ if (isRequired) {
61
+ validate = (input) => !isNaN(input) || `${key} must be a valid number`;
62
+ }
63
+ break;
64
+ case "boolean":
65
+ promptType = 'confirm';
66
+ break;
67
+ default:
68
+ if (isRequired) {
69
+ validate = (input) => input.trim() !== "" || `${key} is required`;
70
+ }
71
+ }
72
+ inputPrompts.push({
73
+ type: promptType,
74
+ name: key,
75
+ message: `${key}${isRequired ? " (required)" : " (optional)"}:`,
76
+ validate: validate,
77
+ when: isRequired ? true : (answers) => {
78
+ // For optional fields, ask if user wants to provide a value
79
+ return true;
80
+ }
81
+ });
82
+ }
83
+ const answers = await inquirer.prompt(inputPrompts);
84
+ // Convert answers to proper types
85
+ for (const [key, value] of Object.entries(answers)) {
86
+ const property = inputSchema.properties[key];
87
+ switch (property.type) {
88
+ case "string":
89
+ inputValues[key] = value;
90
+ break;
91
+ case "number":
92
+ inputValues[key] = parseFloat(value);
93
+ break;
94
+ case "boolean":
95
+ inputValues[key] = value;
96
+ break;
97
+ default:
98
+ inputValues[key] = value;
99
+ }
100
+ }
101
+ }
102
+ console.log("\n🚀 Executing tool...");
103
+ console.log(`Input: ${JSON.stringify(inputValues, null, 2)}`);
104
+ // Get the execute function string directly from the selected tool
105
+ const toolCode = selectedTool.execute;
106
+ // Execute the tool
107
+ try {
108
+ // Create a temporary CommonJS file to run the tool
109
+ const tempFile = path.join(luaDir, `temp-${selectedTool.name}.cjs`);
110
+ const commonJsWrapper = `
111
+ const executeFunction = ${toolCode};
112
+
113
+ // Export the function for testing
114
+ module.exports = async (input) => {
115
+ return await executeFunction(input);
116
+ };
117
+ `;
118
+ fs.writeFileSync(tempFile, commonJsWrapper);
119
+ // Import and execute the CommonJS module
120
+ const { createRequire } = await import('module');
121
+ const require = createRequire(import.meta.url);
122
+ const executeFunction = require(tempFile);
123
+ const result = await executeFunction(inputValues);
124
+ // Clean up temp file
125
+ fs.unlinkSync(tempFile);
126
+ console.log("\n✅ Tool execution successful!");
127
+ console.log(`Output: ${JSON.stringify(result, null, 2)}`);
128
+ }
129
+ catch (executionError) {
130
+ console.error("\n❌ Tool execution failed:");
131
+ console.error(executionError.message);
132
+ if (executionError.stack) {
133
+ console.error(executionError.stack);
134
+ }
135
+ }
136
+ }
137
+ catch (error) {
138
+ console.error("❌ Test failed:", error.message);
139
+ process.exit(1);
140
+ }
141
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- import { configureCommand, initCommand, destroyCommand, apiKeyCommand, agentsCommand } from "./commands/index.js";
3
+ import { configureCommand, initCommand, destroyCommand, apiKeyCommand, agentsCommand, deployCommand, testCommand } from "./commands/index.js";
4
4
  const program = new Command();
5
5
  program
6
6
  .command("init")
@@ -22,4 +22,12 @@ program
22
22
  .command("agents")
23
23
  .description("Fetch agents from HeyLua API")
24
24
  .action(agentsCommand);
25
+ program
26
+ .command("compile")
27
+ .description("Compile Lua skill to generate deployable format")
28
+ .action(deployCommand);
29
+ program
30
+ .command("test")
31
+ .description("Test Lua skill tools interactively")
32
+ .action(testCommand);
25
33
  program.parse(process.argv);
@@ -0,0 +1,8 @@
1
+ import { UserData } from "../types/index.js";
2
+ export declare function saveApiKey(apiKey: string): Promise<void>;
3
+ export declare function loadApiKey(): Promise<string | null>;
4
+ export declare function deleteApiKey(): Promise<boolean>;
5
+ export declare function checkApiKey(apiKey: string): Promise<UserData>;
6
+ export declare function requestEmailOTP(email: string): Promise<boolean>;
7
+ export declare function verifyOTPAndGetToken(email: string, pin: string): Promise<string | null>;
8
+ export declare function generateApiKey(signInToken: string): Promise<string | null>;
@@ -0,0 +1,9 @@
1
+ import { ZodType } from "zod";
2
+ import { LuaTool } from "./types/index.js";
3
+ export declare class LuaSkill {
4
+ private readonly apiKey;
5
+ private readonly tools;
6
+ constructor(apiKey: string);
7
+ addTool<TInput extends ZodType, TOutput extends ZodType>(tool: LuaTool<TInput, TOutput>): void;
8
+ run(input: Record<string, any>): Promise<any>;
9
+ }
package/dist/skill.js ADDED
@@ -0,0 +1,18 @@
1
+ export class LuaSkill {
2
+ constructor(apiKey) {
3
+ this.apiKey = apiKey;
4
+ this.tools = [];
5
+ }
6
+ addTool(tool) {
7
+ this.tools.push(tool);
8
+ }
9
+ async run(input) {
10
+ const tool = this.tools.find(tool => tool.name === input.tool);
11
+ if (!tool) {
12
+ throw new Error(`Tool ${input.tool} not found`);
13
+ }
14
+ // Validate input against the tool's schema
15
+ const validatedInput = tool.inputSchema.parse(input);
16
+ return tool.execute(validatedInput);
17
+ }
18
+ }
@@ -0,0 +1,69 @@
1
+ export interface UserData {
2
+ uid: string;
3
+ email: string;
4
+ emailVerified: boolean;
5
+ fullName: string;
6
+ mobileNumbers: any[];
7
+ emailAddresses: EmailAddress[];
8
+ country: Country;
9
+ admin: Admin;
10
+ channels: Record<string, any>;
11
+ rights: Record<string, any>;
12
+ setupPersona: Record<string, any>;
13
+ notifications: Record<string, any>;
14
+ }
15
+ export interface EmailAddress {
16
+ address: string;
17
+ validated: boolean;
18
+ validatedAt: number;
19
+ _id: string;
20
+ }
21
+ export interface Country {
22
+ code: string;
23
+ name: string;
24
+ }
25
+ export interface Admin {
26
+ userId: string;
27
+ orgs: Organization[];
28
+ id: string;
29
+ createdAt: number;
30
+ __v: number;
31
+ }
32
+ export interface Organization {
33
+ id: string;
34
+ rights?: string[];
35
+ agents: Agent[];
36
+ registeredName: string;
37
+ country: string;
38
+ phoneNumber?: string | null;
39
+ type: string;
40
+ }
41
+ export interface Agent {
42
+ agentId: string;
43
+ rights: string[];
44
+ name: string;
45
+ }
46
+ export interface OTPResponse {
47
+ signInToken: string;
48
+ }
49
+ export interface ApiKeyResponse {
50
+ message: string;
51
+ userId: string;
52
+ apiKey: string;
53
+ apiId: string;
54
+ }
55
+ import { ZodType } from "zod";
56
+ export interface LuaTool<TInput extends ZodType = ZodType, TOutput extends ZodType = ZodType> {
57
+ name: string;
58
+ description: string;
59
+ inputSchema: TInput;
60
+ outputSchema: TOutput;
61
+ execute: (input: any) => Promise<any>;
62
+ }
63
+ export declare class LuaSkill {
64
+ private readonly apiKey;
65
+ private readonly tools;
66
+ constructor(apiKey: string);
67
+ addTool<TInput extends ZodType, TOutput extends ZodType>(tool: LuaTool<TInput, TOutput>): void;
68
+ run(input: Record<string, any>): Promise<any>;
69
+ }
@@ -0,0 +1 @@
1
+ export * from "./types/index.js";
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ // Re-export types for external use
2
+ export * from "./types/index.js";
@@ -0,0 +1,2 @@
1
+ export declare function copyTemplateFiles(templateDir: string, targetDir: string): void;
2
+ export declare function createSkillToml(agentId: string, orgId: string, skillName: string, skillDescription: string): void;
@@ -1,19 +1,44 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import { fileURLToPath } from "url";
3
4
  export function copyTemplateFiles(templateDir, targetDir) {
4
5
  const files = fs.readdirSync(templateDir);
5
6
  for (const file of files) {
7
+ // Skip node_modules and package-lock.json to avoid circular dependencies
8
+ if (file === 'node_modules' || file === 'package-lock.json') {
9
+ continue;
10
+ }
6
11
  const srcPath = path.join(templateDir, file);
7
12
  const destPath = path.join(targetDir, file);
8
13
  if (fs.statSync(srcPath).isDirectory()) {
9
14
  fs.mkdirSync(destPath, { recursive: true });
10
15
  copyTemplateFiles(srcPath, destPath);
11
16
  }
17
+ else if (file === 'package.json') {
18
+ // Special handling for package.json to update lua-cli version
19
+ updatePackageJson(srcPath, destPath);
20
+ }
12
21
  else {
13
22
  fs.copyFileSync(srcPath, destPath);
14
23
  }
15
24
  }
16
25
  }
26
+ function updatePackageJson(srcPath, destPath) {
27
+ // Read the template package.json
28
+ const templatePackageJson = JSON.parse(fs.readFileSync(srcPath, 'utf8'));
29
+ // Get the current CLI version from the main package.json
30
+ const __filename = fileURLToPath(import.meta.url);
31
+ const __dirname = path.dirname(__filename);
32
+ const mainPackageJsonPath = path.join(__dirname, '..', '..', 'package.json');
33
+ const mainPackageJson = JSON.parse(fs.readFileSync(mainPackageJsonPath, 'utf8'));
34
+ const currentCliVersion = mainPackageJson.version;
35
+ // Update the lua-cli dependency version
36
+ if (templatePackageJson.dependencies && templatePackageJson.dependencies['lua-cli']) {
37
+ templatePackageJson.dependencies['lua-cli'] = `^${currentCliVersion}`;
38
+ }
39
+ // Write the updated package.json
40
+ fs.writeFileSync(destPath, JSON.stringify(templatePackageJson, null, 2) + '\n');
41
+ }
17
42
  export function createSkillToml(agentId, orgId, skillName, skillDescription) {
18
43
  const tomlContent = `[agent]
19
44
  agentId = "${agentId}"
package/package.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "name": "lua-cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Command-line interface for Lua AI platform - manage agents, organizations, and skills",
5
5
  "readmeFilename": "README.md",
6
6
  "main": "dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./types": "./dist/types.js",
10
+ "./skill": "./dist/skill.js"
11
+ },
7
12
  "scripts": {
8
- "build": "tsc",
13
+ "clean": "rm -rf dist",
14
+ "build": "npm run clean && tsc",
9
15
  "prepublishOnly": "npm run build",
10
- "test": "echo \"Error: no test specified\" && exit 1"
16
+ "test": "jest",
17
+ "test:watch": "jest --watch",
18
+ "test:coverage": "jest --coverage"
11
19
  },
12
20
  "keywords": [
13
21
  "lua",
@@ -36,7 +44,6 @@
36
44
  },
37
45
  "files": [
38
46
  "dist/**/*",
39
- "template/**/*",
40
47
  "README.md",
41
48
  "CHANGELOG.md",
42
49
  "LICENSE"
@@ -45,16 +52,21 @@
45
52
  "commander": "^14.0.1",
46
53
  "inquirer": "^12.9.6",
47
54
  "keytar": "^7.9.0",
48
- "node-fetch": "^3.3.2"
55
+ "node-fetch": "^3.3.2",
56
+ "zod": "^4.1.9"
49
57
  },
50
58
  "devDependencies": {
51
59
  "@types/inquirer": "^9.0.9",
60
+ "@types/jest": "^29.5.8",
52
61
  "@types/node": "^24.5.1",
53
62
  "@types/node-fetch": "^2.6.13",
63
+ "esbuild": "^0.25.10",
64
+ "jest": "^29.7.0",
65
+ "ts-jest": "^29.1.1",
54
66
  "ts-node": "^10.9.2",
55
67
  "typescript": "^5.9.2"
56
68
  },
57
69
  "bin": {
58
70
  "lua": "dist/index.js"
59
71
  }
60
- }
72
+ }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "lua-skill",
3
- "version": "1.0.0",
4
- "description": "",
5
- "license": "ISC",
6
- "author": "",
7
- "type": "commonjs",
8
- "main": "index.ts",
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- }
12
- }