lua-cli 1.3.2-alpha.3 â 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 +280 -216
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/dev.js +352 -81
- package/dist/commands/init.js +43 -79
- 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 +12 -25
- package/dist/utils/sandbox.d.ts +7 -0
- package/dist/utils/sandbox.js +44 -8
- 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 +3 -2
- package/template/env.example +17 -0
- package/template/lua.skill.yaml +14 -16
- package/template/package.json +1 -1
- package/template/src/index.ts +42 -13
- package/template/src/tools/PaymentTool.ts +2 -3
- package/dist/commands/deploy-new.d.ts +0 -0
- package/dist/commands/deploy-new.js +0 -130
- package/template/create-test.cjs +0 -39
- package/template/package-lock.json +0 -1555
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.2-alpha.3] - 2025-09-25
|
|
9
|
+
|
|
10
|
+
### đ Major Rewrite
|
|
11
|
+
- **Complete compiler rewrite** using TypeScript AST (ts-morph) instead of regex
|
|
12
|
+
- **Self-contained tool bundling** with esbuild and minification
|
|
13
|
+
- **85% file size reduction** (4MB â 612KB total bundle size)
|
|
14
|
+
- **Zero regex usage** - pure AST-based parsing for reliability
|
|
15
|
+
|
|
16
|
+
### ⨠New Features
|
|
17
|
+
- **Enhanced Zod schema parsing** for nested objects and arrays
|
|
18
|
+
- **Comprehensive polyfills** for browser/Node.js APIs (AbortController, FormData, TextEncoder, crypto, etc.)
|
|
19
|
+
- **Optimized bundling** excludes lua-cli modules (injected by VM) for smaller sizes
|
|
20
|
+
- **Dual output format** - both new (dist/) and legacy (.lua/) formats
|
|
21
|
+
|
|
22
|
+
### đ§ Improvements
|
|
23
|
+
- **Proper environment variable injection** from lua.skill.yaml into VM
|
|
24
|
+
- **Minified output** for production deployment
|
|
25
|
+
- **Better error handling** with proper TypeScript AST validation
|
|
26
|
+
- **Full backward compatibility** with existing deployment workflow
|
|
27
|
+
|
|
28
|
+
### đĻ Bundle Size Optimizations
|
|
29
|
+
- Simple tools (Weather, UserData): 545KB â 4KB (99% reduction)
|
|
30
|
+
- HTTP tools (CreatePost): 863KB â 232KB (73% reduction)
|
|
31
|
+
- AI tools (SearchMovies): 1.4MB â 364KB (74% reduction)
|
|
32
|
+
|
|
8
33
|
## [1.3.0-alpha.1] - 2024-12-19
|
|
9
34
|
|
|
10
35
|
### Added
|
package/README.md
CHANGED
|
@@ -195,7 +195,7 @@ This command will:
|
|
|
195
195
|
- Available at `http://localhost:3000` (opens automatically)
|
|
196
196
|
- **Live Log Panel**:
|
|
197
197
|
- Real-time log feed showing execution details
|
|
198
|
-
- WebSocket connection to `wss://api.
|
|
198
|
+
- WebSocket connection to `wss://api.heylua.ai/feed`
|
|
199
199
|
- Console-style interface with color-coded log levels
|
|
200
200
|
- Shows tool calls, errors, metrics, and execution metadata
|
|
201
201
|
- Displays detailed information in expandable sections
|
|
@@ -498,7 +498,7 @@ For more details, see [USER_DATA_API.md](./USER_DATA_API.md).
|
|
|
498
498
|
|
|
499
499
|
For support and questions:
|
|
500
500
|
- Create an issue on [GitHub](https://github.com/lua-ai-global/lua-cli/issues)
|
|
501
|
-
- Contact: stefan@
|
|
501
|
+
- Contact: stefan@heylua.ai
|
|
502
502
|
|
|
503
503
|
## Changelog
|
|
504
504
|
|
package/dist/commands/agents.js
CHANGED
|
@@ -5,7 +5,7 @@ export async function agentsCommand() {
|
|
|
5
5
|
return withErrorHandling(async () => {
|
|
6
6
|
const apiKey = await loadApiKey();
|
|
7
7
|
if (!apiKey) {
|
|
8
|
-
console.error("â No API key found. Run `lua configure` first.");
|
|
8
|
+
console.error("â No API key found. Run `lua auth configure` first.");
|
|
9
9
|
process.exit(1);
|
|
10
10
|
}
|
|
11
11
|
const response = await ApiService.Agent.getOrganizations(apiKey);
|
package/dist/commands/apiKey.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import { loadApiKey } from "../services/auth.js";
|
|
3
|
-
import { withErrorHandling,
|
|
3
|
+
import { withErrorHandling, writeProgress, writeSuccess } from "../utils/cli.js";
|
|
4
4
|
export async function apiKeyCommand() {
|
|
5
5
|
return withErrorHandling(async () => {
|
|
6
6
|
const apiKey = await loadApiKey();
|
|
7
7
|
if (!apiKey) {
|
|
8
|
-
writeProgress("âšī¸ No API key found. Run `lua configure` first.");
|
|
8
|
+
writeProgress("âšī¸ No API key found. Run `lua auth configure` first.");
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
const { confirm } = await inquirer.prompt([
|
|
@@ -16,8 +16,6 @@ export async function apiKeyCommand() {
|
|
|
16
16
|
default: false
|
|
17
17
|
}
|
|
18
18
|
]);
|
|
19
|
-
// Clear the confirmation prompt lines
|
|
20
|
-
clearPromptLines(2);
|
|
21
19
|
if (confirm) {
|
|
22
20
|
writeSuccess("đ Your API key:");
|
|
23
21
|
console.log(apiKey);
|
package/dist/commands/compile.js
CHANGED
|
@@ -3,8 +3,11 @@ import path from "path";
|
|
|
3
3
|
import { gzipSync } from "zlib";
|
|
4
4
|
import { withErrorHandling, writeProgress, writeSuccess } from "../utils/cli.js";
|
|
5
5
|
import { readSkillConfig } from '../utils/files.js';
|
|
6
|
+
import { loadApiKey } from '../services/auth.js';
|
|
7
|
+
import { ApiService } from '../services/api.js';
|
|
6
8
|
import { Project, Node } from "ts-morph";
|
|
7
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);
|
|
@@ -65,7 +68,36 @@ function findIndexFile() {
|
|
|
65
68
|
}
|
|
66
69
|
async function detectTools(indexFile, project) {
|
|
67
70
|
const tools = [];
|
|
68
|
-
// Find
|
|
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
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// Find all call expressions in the file (addTools method calls)
|
|
69
101
|
indexFile.forEachDescendant((node) => {
|
|
70
102
|
if (Node.isCallExpression(node)) {
|
|
71
103
|
const expression = node.getExpression();
|
|
@@ -73,7 +105,7 @@ async function detectTools(indexFile, project) {
|
|
|
73
105
|
if (Node.isPropertyAccessExpression(expression)) {
|
|
74
106
|
const object = expression.getExpression();
|
|
75
107
|
const property = expression.getName();
|
|
76
|
-
if (
|
|
108
|
+
if (property === 'addTools' || property === 'addTool') {
|
|
77
109
|
const args = node.getArguments();
|
|
78
110
|
if (property === 'addTools' && args.length > 0) {
|
|
79
111
|
// Handle skill.addTools([...]) - array of tools
|
|
@@ -152,12 +184,13 @@ function extractToolFromNewExpressionSync(newExpr, project) {
|
|
|
152
184
|
description = descValue.replace(/['"]/g, '');
|
|
153
185
|
}
|
|
154
186
|
}
|
|
155
|
-
|
|
187
|
+
const toolInfo = {
|
|
156
188
|
name: toolName,
|
|
157
189
|
className,
|
|
158
190
|
filePath: toolFilePath,
|
|
159
191
|
description
|
|
160
192
|
};
|
|
193
|
+
return toolInfo;
|
|
161
194
|
}
|
|
162
195
|
}
|
|
163
196
|
catch (fileError) {
|
|
@@ -360,13 +393,20 @@ async function extractExecuteCode(tool, project) {
|
|
|
360
393
|
const executeFunction = createExecuteFunction(bundledCode, tool);
|
|
361
394
|
tool.executeCode = executeFunction;
|
|
362
395
|
}
|
|
363
|
-
// Extract input schema from zod definition
|
|
396
|
+
// Extract input schema from zod definition using proper library
|
|
364
397
|
const inputSchemaProperty = classDecl.getProperty('inputSchema');
|
|
365
398
|
if (inputSchemaProperty && inputSchemaProperty.getInitializer()) {
|
|
366
399
|
const initializer = inputSchemaProperty.getInitializer();
|
|
367
400
|
if (initializer) {
|
|
368
|
-
|
|
369
|
-
|
|
401
|
+
try {
|
|
402
|
+
// Evaluate the Zod schema to get the actual schema object
|
|
403
|
+
const zodSchemaCode = initializer.getText();
|
|
404
|
+
tool.inputSchema = await evaluateZodSchemaToJsonSchema(zodSchemaCode);
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
console.warn(`Warning: Could not parse Zod schema for ${tool.className}:`, error);
|
|
408
|
+
tool.inputSchema = { type: "object" };
|
|
409
|
+
}
|
|
370
410
|
}
|
|
371
411
|
}
|
|
372
412
|
if (!tool.inputSchema) {
|
|
@@ -382,200 +422,37 @@ function createExecuteFunction(bundledCode, tool) {
|
|
|
382
422
|
// Just return it directly since it's already an async function
|
|
383
423
|
return bundledCode;
|
|
384
424
|
}
|
|
385
|
-
function
|
|
425
|
+
async function evaluateZodSchemaToJsonSchema(zodSchemaCode) {
|
|
386
426
|
try {
|
|
387
|
-
//
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
//
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
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;
|
|
400
439
|
}
|
|
401
|
-
//
|
|
402
|
-
|
|
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;
|
|
403
443
|
}
|
|
404
444
|
catch (error) {
|
|
405
|
-
console.warn('Warning: Could not
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
function parseZodASTToJsonSchema(node) {
|
|
410
|
-
try {
|
|
411
|
-
// Handle z.object({ ... })
|
|
412
|
-
if (Node.isCallExpression(node)) {
|
|
413
|
-
const expression = node.getExpression();
|
|
414
|
-
if (Node.isPropertyAccessExpression(expression)) {
|
|
415
|
-
const object = expression.getExpression();
|
|
416
|
-
const property = expression.getName();
|
|
417
|
-
if (object.getText() === 'z' && property === 'object') {
|
|
418
|
-
const args = node.getArguments();
|
|
419
|
-
if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
|
|
420
|
-
const objectLiteral = args[0];
|
|
421
|
-
const properties = {};
|
|
422
|
-
const required = [];
|
|
423
|
-
objectLiteral.getProperties().forEach((prop) => {
|
|
424
|
-
if (Node.isPropertyAssignment(prop)) {
|
|
425
|
-
const name = prop.getName();
|
|
426
|
-
const value = prop.getInitializer();
|
|
427
|
-
if (value) {
|
|
428
|
-
const propSchema = parseZodASTToJsonSchema(value);
|
|
429
|
-
properties[name] = propSchema;
|
|
430
|
-
// Check if it's required (not optional)
|
|
431
|
-
if (!isOptionalZodType(value)) {
|
|
432
|
-
required.push(name);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
return {
|
|
438
|
-
type: 'object',
|
|
439
|
-
properties,
|
|
440
|
-
required
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
// Handle other z.* types
|
|
445
|
-
if (object.getText() === 'z') {
|
|
446
|
-
switch (property) {
|
|
447
|
-
case 'string':
|
|
448
|
-
return { type: 'string' };
|
|
449
|
-
case 'number':
|
|
450
|
-
return { type: 'number' };
|
|
451
|
-
case 'boolean':
|
|
452
|
-
return { type: 'boolean' };
|
|
453
|
-
case 'any':
|
|
454
|
-
return { type: 'object' };
|
|
455
|
-
case 'array':
|
|
456
|
-
const arrayArgs = node.getArguments();
|
|
457
|
-
if (arrayArgs.length > 0) {
|
|
458
|
-
const itemSchema = parseZodASTToJsonSchema(arrayArgs[0]);
|
|
459
|
-
return { type: 'array', items: itemSchema };
|
|
460
|
-
}
|
|
461
|
-
return { type: 'array' };
|
|
462
|
-
case 'enum':
|
|
463
|
-
const enumArgs = node.getArguments();
|
|
464
|
-
if (enumArgs.length > 0 && Node.isArrayLiteralExpression(enumArgs[0])) {
|
|
465
|
-
const enumValues = enumArgs[0].getElements().map((element) => {
|
|
466
|
-
if (Node.isStringLiteral(element)) {
|
|
467
|
-
return element.getLiteralValue();
|
|
468
|
-
}
|
|
469
|
-
return element.getText().replace(/['"]/g, '');
|
|
470
|
-
});
|
|
471
|
-
return { type: 'string', enum: enumValues };
|
|
472
|
-
}
|
|
473
|
-
return { type: 'string' };
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
// Handle method chaining like z.string().optional()
|
|
478
|
-
if (Node.isPropertyAccessExpression(expression)) {
|
|
479
|
-
const baseExpression = expression.getExpression();
|
|
480
|
-
const method = expression.getName();
|
|
481
|
-
if (method === 'optional' && Node.isCallExpression(baseExpression)) {
|
|
482
|
-
// This is an optional field, parse the base type
|
|
483
|
-
return parseZodASTToJsonSchema(baseExpression);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
// Fallback
|
|
488
|
-
return { type: 'object' };
|
|
489
|
-
}
|
|
490
|
-
catch (error) {
|
|
491
|
-
console.warn('Warning: Could not parse Zod AST node:', error);
|
|
492
|
-
return { type: 'object' };
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
function isOptionalZodType(node) {
|
|
496
|
-
// Check if this is a method call ending with .optional()
|
|
497
|
-
if (Node.isCallExpression(node)) {
|
|
498
|
-
const expression = node.getExpression();
|
|
499
|
-
if (Node.isPropertyAccessExpression(expression)) {
|
|
500
|
-
const method = expression.getName();
|
|
501
|
-
return method === 'optional';
|
|
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' };
|
|
502
449
|
}
|
|
503
|
-
|
|
504
|
-
return false;
|
|
505
|
-
}
|
|
506
|
-
function parseZodTextToJsonSchema(zodSchemaText) {
|
|
507
|
-
try {
|
|
508
|
-
// Fallback text-based parsing for when AST parsing fails
|
|
509
|
-
if (zodSchemaText.includes('z.object({')) {
|
|
510
|
-
const properties = {};
|
|
511
|
-
const required = [];
|
|
512
|
-
// Extract object properties - enhanced pattern matching
|
|
513
|
-
const propertyPattern = /(\w+):\s*z\.(\w+)\(\)/g;
|
|
514
|
-
let match;
|
|
515
|
-
while ((match = propertyPattern.exec(zodSchemaText)) !== null) {
|
|
516
|
-
const [, propName, zodType] = match;
|
|
517
|
-
switch (zodType) {
|
|
518
|
-
case 'string':
|
|
519
|
-
properties[propName] = { type: 'string' };
|
|
520
|
-
break;
|
|
521
|
-
case 'number':
|
|
522
|
-
properties[propName] = { type: 'number' };
|
|
523
|
-
break;
|
|
524
|
-
case 'boolean':
|
|
525
|
-
properties[propName] = { type: 'boolean' };
|
|
526
|
-
break;
|
|
527
|
-
case 'any':
|
|
528
|
-
properties[propName] = { type: 'object' };
|
|
529
|
-
break;
|
|
530
|
-
default:
|
|
531
|
-
properties[propName] = { type: 'string' };
|
|
532
|
-
}
|
|
533
|
-
// Check if it's optional
|
|
534
|
-
if (!zodSchemaText.includes(`${propName}: z.${zodType}().optional()`)) {
|
|
535
|
-
required.push(propName);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// Handle arrays
|
|
539
|
-
const arrayPattern = /(\w+):\s*z\.array\(z\.(\w+)\(\)\)/g;
|
|
540
|
-
while ((match = arrayPattern.exec(zodSchemaText)) !== null) {
|
|
541
|
-
const [, propName, itemType] = match;
|
|
542
|
-
let itemSchema = { type: 'string' };
|
|
543
|
-
switch (itemType) {
|
|
544
|
-
case 'string':
|
|
545
|
-
itemSchema = { type: 'string' };
|
|
546
|
-
break;
|
|
547
|
-
case 'number':
|
|
548
|
-
itemSchema = { type: 'number' };
|
|
549
|
-
break;
|
|
550
|
-
case 'boolean':
|
|
551
|
-
itemSchema = { type: 'boolean' };
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
properties[propName] = { type: 'array', items: itemSchema };
|
|
555
|
-
if (!zodSchemaText.includes(`${propName}: z.array(z.${itemType}()).optional()`)) {
|
|
556
|
-
required.push(propName);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
return {
|
|
560
|
-
type: 'object',
|
|
561
|
-
properties,
|
|
562
|
-
required
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
// Handle simple types
|
|
566
|
-
if (zodSchemaText.includes('z.string()'))
|
|
450
|
+
if (zodSchemaCode.includes('z.string()'))
|
|
567
451
|
return { type: 'string' };
|
|
568
|
-
if (
|
|
452
|
+
if (zodSchemaCode.includes('z.number()'))
|
|
569
453
|
return { type: 'number' };
|
|
570
|
-
if (
|
|
454
|
+
if (zodSchemaCode.includes('z.boolean()'))
|
|
571
455
|
return { type: 'boolean' };
|
|
572
|
-
if (zodSchemaText.includes('z.array('))
|
|
573
|
-
return { type: 'array' };
|
|
574
|
-
// Fallback
|
|
575
|
-
return { type: 'object' };
|
|
576
|
-
}
|
|
577
|
-
catch (error) {
|
|
578
|
-
console.warn('Warning: Could not parse Zod schema text:', error);
|
|
579
456
|
return { type: 'object' };
|
|
580
457
|
}
|
|
581
458
|
}
|
|
@@ -602,21 +479,72 @@ async function createLegacyDeploymentData(tools, luaDir, indexFile) {
|
|
|
602
479
|
const config = readSkillConfig();
|
|
603
480
|
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
604
481
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
605
|
-
// Extract
|
|
606
|
-
const
|
|
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);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
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
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
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);
|
|
607
546
|
const deployData = {
|
|
608
|
-
|
|
609
|
-
name: config?.skill?.name || packageJson.name || "lua-skill",
|
|
610
|
-
skillsName: config?.skill?.name || packageJson.name || "lua-skill",
|
|
611
|
-
skillId: config?.skill?.skillId || skillMetadata.skillId || "",
|
|
612
|
-
description: config?.skill?.description || skillMetadata.description || "",
|
|
613
|
-
context: config?.skill?.context || skillMetadata.context || "",
|
|
614
|
-
tools: tools.map(tool => ({
|
|
615
|
-
name: tool.name,
|
|
616
|
-
description: tool.description || "",
|
|
617
|
-
inputSchema: tool.inputSchema || { type: "object" },
|
|
618
|
-
execute: compressCode(tool.executeCode || "")
|
|
619
|
-
}))
|
|
547
|
+
skills: updatedSkillsArray
|
|
620
548
|
};
|
|
621
549
|
// Write legacy deploy.json to .lua directory
|
|
622
550
|
fs.writeFileSync(path.join(luaDir, "deploy.json"), JSON.stringify(deployData, null, 2));
|
|
@@ -628,11 +556,9 @@ async function createLegacyDeploymentData(tools, luaDir, indexFile) {
|
|
|
628
556
|
}
|
|
629
557
|
}
|
|
630
558
|
}
|
|
631
|
-
function
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
let context = '';
|
|
635
|
-
// Find LuaSkill constructor call
|
|
559
|
+
function extractSkillsMetadata(indexFile) {
|
|
560
|
+
const skills = [];
|
|
561
|
+
// Find all LuaSkill constructor calls
|
|
636
562
|
indexFile.forEachDescendant((node) => {
|
|
637
563
|
if (Node.isNewExpression(node)) {
|
|
638
564
|
const expression = node.getExpression();
|
|
@@ -640,22 +566,160 @@ function extractSkillMetadata(indexFile) {
|
|
|
640
566
|
const args = node.getArguments();
|
|
641
567
|
if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
|
|
642
568
|
const configObj = args[0];
|
|
569
|
+
let skillName = '';
|
|
570
|
+
let skillVersion = '';
|
|
571
|
+
let description = '';
|
|
572
|
+
let context = '';
|
|
573
|
+
let constructorTools = [];
|
|
643
574
|
// Extract properties
|
|
644
575
|
configObj.getProperties().forEach((prop) => {
|
|
645
576
|
if (Node.isPropertyAssignment(prop)) {
|
|
646
577
|
const name = prop.getName();
|
|
647
|
-
const value = prop.getInitializer()
|
|
648
|
-
if (name === '
|
|
649
|
-
|
|
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, '');
|
|
650
587
|
}
|
|
651
588
|
else if (name === 'context' && value) {
|
|
652
|
-
context = value.replace(/['"]/g, '');
|
|
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);
|
|
653
600
|
}
|
|
654
601
|
}
|
|
655
602
|
});
|
|
603
|
+
if (skillName) {
|
|
604
|
+
skills.push({
|
|
605
|
+
name: skillName,
|
|
606
|
+
version: skillVersion || '1.0.0',
|
|
607
|
+
description,
|
|
608
|
+
context,
|
|
609
|
+
constructorTools
|
|
610
|
+
});
|
|
611
|
+
}
|
|
656
612
|
}
|
|
657
613
|
}
|
|
658
614
|
}
|
|
659
615
|
});
|
|
660
|
-
return
|
|
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;
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
// Update existing skill with new skillId
|
|
679
|
+
existingSkill.skillId = newSkillId;
|
|
680
|
+
yamlUpdated = true;
|
|
681
|
+
}
|
|
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
|
+
}
|
|
687
|
+
}
|
|
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
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// Update YAML file if needed
|
|
695
|
+
if (yamlUpdated) {
|
|
696
|
+
await updateYamlWithSkills(existingSkills, config);
|
|
697
|
+
}
|
|
698
|
+
return updatedSkillsArray;
|
|
699
|
+
}
|
|
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
|
|
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');
|
|
661
725
|
}
|