lua-cli 1.2.0 → 1.3.0-alpha.1

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.
@@ -1,779 +1,125 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { gzipSync, gunzipSync } from "zlib";
4
- import { Buffer } from "buffer";
5
- // Compression utilities
6
- function compressCode(code) {
7
- const compressed = gzipSync(code);
8
- return compressed.toString('base64');
9
- }
10
- function decompressCode(compressedCode) {
11
- const buffer = Buffer.from(compressedCode, 'base64');
12
- return gunzipSync(buffer).toString('utf8');
13
- }
14
- export async function deployCommand() {
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import inquirer from 'inquirer';
4
+ import { loadApiKey, checkApiKey } from '../services/auth.js';
5
+ import { withErrorHandling, clearPromptLines, writeProgress, writeSuccess } from '../utils/cli.js';
6
+ export async function fetchVersions(apiKey, agentId, skillId) {
15
7
  try {
16
- console.log("🔨 Compiling Lua skill...");
17
- // Read package.json to get version and name
18
- const packageJsonPath = path.join(process.cwd(), "package.json");
19
- if (!fs.existsSync(packageJsonPath)) {
20
- throw new Error("package.json not found in current directory");
21
- }
22
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
23
- const version = packageJson.version || "1.0.0";
24
- const skillsName = packageJson.name || "lua-skill";
25
- // Read index.ts file
26
- const indexPath = path.join(process.cwd(), "index.ts");
27
- if (!fs.existsSync(indexPath)) {
28
- throw new Error("index.ts not found in current directory");
29
- }
30
- const indexContent = fs.readFileSync(indexPath, "utf8");
31
- // Extract skill information
32
- const skillInfo = await extractSkillInfo(indexContent);
33
- // Create deployment data with compressed execute code
34
- const deployData = {
35
- version,
36
- skillsName,
37
- tools: skillInfo.map(tool => ({
38
- ...tool,
39
- execute: compressCode(tool.execute)
40
- }))
41
- };
42
- // Create .lua directory
43
- const luaDir = path.join(process.cwd(), ".lua");
44
- if (!fs.existsSync(luaDir)) {
45
- fs.mkdirSync(luaDir, { recursive: true });
46
- }
47
- // Write JSON output to .lua directory
48
- const jsonOutputPath = path.join(luaDir, "deploy.json");
49
- fs.writeFileSync(jsonOutputPath, JSON.stringify(deployData, null, 2));
50
- // Write individual tool files to .lua directory
51
- for (const tool of skillInfo) {
52
- const toolFilePath = path.join(luaDir, `${tool.name}.js`);
53
- fs.writeFileSync(toolFilePath, tool.execute);
54
- }
55
- console.log(`📁 Compiled files written to: ${luaDir}`);
56
- console.log(`📄 JSON output: ${jsonOutputPath}`);
57
- console.log(`🔧 Tool files: ${skillInfo.map(t => `${t.name}.js`).join(', ')}`);
58
- console.log("✅ Skill compilation completed successfully!");
59
- }
60
- catch (error) {
61
- console.error("❌ Compilation failed:", error.message);
62
- process.exit(1);
63
- }
64
- }
65
- async function extractSkillInfo(indexContent) {
66
- const tools = [];
67
- // Find inline addTool calls: skill.addTool({...})
68
- const inlineAddToolRegex = /skill\.addTool\(\s*\{([\s\S]*?)\}\s*\)/g;
69
- let match;
70
- while ((match = inlineAddToolRegex.exec(indexContent)) !== null) {
71
- const toolContent = match[1];
72
- // Extract tool properties
73
- const nameMatch = toolContent.match(/name:\s*["']([^"']+)["']/);
74
- const descriptionMatch = toolContent.match(/description:\s*["']([^"']+)["']/);
75
- const inputSchemaMatch = toolContent.match(/inputSchema:\s*(\w+)/);
76
- const outputSchemaMatch = toolContent.match(/outputSchema:\s*(\w+)/);
77
- const executeMatch = toolContent.match(/execute:\s*async\s*\([^)]*\)\s*=>\s*\{([\s\S]*?)\}/);
78
- if (nameMatch && descriptionMatch && inputSchemaMatch && outputSchemaMatch && executeMatch) {
79
- const toolName = nameMatch[1];
80
- const toolDescription = descriptionMatch[1];
81
- const inputSchemaVar = inputSchemaMatch[1];
82
- const outputSchemaVar = outputSchemaMatch[1];
83
- const executeBody = executeMatch[1];
84
- // Convert schemas to JSON Schema format
85
- const inputSchema = convertSchemaToJSON(inputSchemaVar, indexContent);
86
- const outputSchema = convertSchemaToJSON(outputSchemaVar, indexContent);
87
- // Create self-contained execute function
88
- const selfContainedExecute = await createSelfContainedExecute(executeBody, indexContent);
89
- tools.push({
90
- name: toolName,
91
- description: toolDescription,
92
- inputSchema,
93
- outputSchema,
94
- execute: selfContainedExecute
95
- });
96
- }
97
- }
98
- // Find class-based addTool calls: skill.addTool(new SomeTool())
99
- const classAddToolRegex = /skill\.addTool\(\s*new\s+(\w+)\(\)\s*\)/g;
100
- let classMatch;
101
- while ((classMatch = classAddToolRegex.exec(indexContent)) !== null) {
102
- const className = classMatch[1];
103
- // Find the tool class definition
104
- const toolInfo = await extractToolFromClass(className, indexContent);
105
- if (toolInfo) {
106
- tools.push(toolInfo);
107
- }
108
- }
109
- return tools;
110
- }
111
- function convertSchemaToJSON(schemaVar, indexContent) {
112
- // Find the schema definition
113
- const schemaRegex = new RegExp(`const\\s+${schemaVar}\\s*=\\s*z\\.object\\(\\{([\\s\\S]*?)\\}\\\);`, 'g');
114
- const match = schemaRegex.exec(indexContent);
115
- if (match) {
116
- const schemaContent = match[1];
117
- // Convert Zod schema to JSON Schema format
118
- return {
119
- type: "object",
120
- properties: parseZodProperties(schemaContent),
121
- required: extractRequiredFields(schemaContent)
122
- };
123
- }
124
- // If no match found, return empty schema
125
- return { type: "object", properties: {} };
126
- }
127
- function parseZodProperties(schemaContent) {
128
- const properties = {};
129
- // Simple regex to find z.string(), z.number(), etc.
130
- const fieldRegex = /(\w+):\s*z\.(\w+)\(\)/g;
131
- let match;
132
- while ((match = fieldRegex.exec(schemaContent)) !== null) {
133
- const fieldName = match[1];
134
- const fieldType = match[2];
135
- switch (fieldType) {
136
- case 'string':
137
- properties[fieldName] = { type: 'string' };
138
- break;
139
- case 'number':
140
- properties[fieldName] = { type: 'number' };
141
- break;
142
- case 'boolean':
143
- properties[fieldName] = { type: 'boolean' };
144
- break;
145
- default:
146
- properties[fieldName] = { type: 'string' };
147
- }
148
- }
149
- return properties;
150
- }
151
- function extractRequiredFields(schemaContent) {
152
- const required = [];
153
- const fieldRegex = /(\w+):\s*z\.(\w+)\(\)/g;
154
- let match;
155
- while ((match = fieldRegex.exec(schemaContent)) !== null) {
156
- required.push(match[1]);
157
- }
158
- return required;
159
- }
160
- async function createSelfContainedExecute(executeBody, indexContent) {
161
- const dependencies = [];
162
- const bundledPackages = new Set();
163
- // 1. Parse external package imports and bundle their code
164
- const allImportRegex = /import\s+(?:(?:\{([^}]+)\})|(\w+))\s+from\s+["']([^"']+)["']/g;
165
- let importMatch;
166
- while ((importMatch = allImportRegex.exec(indexContent)) !== null) {
167
- const namedImports = importMatch[1]; // Named imports like { z }
168
- const defaultImport = importMatch[2]; // Default import like axios
169
- const packagePath = importMatch[3];
170
- // Skip local imports (relative paths)
171
- if (packagePath.startsWith('./') || packagePath.startsWith('../')) {
172
- continue;
173
- }
174
- // Skip lua-cli imports (these are handled separately)
175
- if (packagePath.startsWith('lua-cli')) {
176
- continue;
177
- }
178
- // Skip zod - assume it's always available on target machine
179
- if (packagePath === 'zod') {
180
- // Add require statement for zod instead of bundling
181
- if (namedImports) {
182
- const importsList = namedImports.split(',').map(imp => imp.trim());
183
- const usedImports = importsList.filter(imp => executeBody.includes(imp) || indexContent.includes(`${imp}.`));
184
- if (usedImports.length > 0) {
185
- const requireStatement = usedImports.length === 1
186
- ? `const { ${usedImports[0]} } = require('zod');`
187
- : `const { ${usedImports.join(', ')} } = require('zod');`;
188
- bundledPackages.add(requireStatement);
189
- }
190
- }
191
- else if (defaultImport) {
192
- bundledPackages.add(`const ${defaultImport} = require('zod');`);
193
- }
194
- continue;
195
- }
196
- // Bundle other external packages
197
- if (namedImports || defaultImport) {
198
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
199
- if (packageCode) {
200
- bundledPackages.add(packageCode);
201
- }
202
- }
203
- }
204
- // 2. Extract class definitions with proper brace matching
205
- const classRegex = /class\s+(\w+)(?:\s+extends\s+\w+)?\s*\{/g;
206
- let classMatch;
207
- while ((classMatch = classRegex.exec(indexContent)) !== null) {
208
- const className = classMatch[1];
209
- const classStart = classMatch.index;
210
- // Find the matching closing brace
211
- let braceCount = 0;
212
- let classEnd = classStart;
213
- let found = false;
214
- for (let i = classStart; i < indexContent.length; i++) {
215
- if (indexContent[i] === '{') {
216
- braceCount++;
217
- }
218
- else if (indexContent[i] === '}') {
219
- braceCount--;
220
- if (braceCount === 0) {
221
- classEnd = i;
222
- found = true;
223
- break;
224
- }
225
- }
226
- }
227
- if (found) {
228
- const fullClass = indexContent.substring(classStart, classEnd + 1);
229
- // Check if this class is used in the execute function
230
- let isUsed = false;
231
- // Direct usage in execute body
232
- if (executeBody.includes(`new ${className}`) ||
233
- executeBody.includes(`${className}.`) ||
234
- executeBody.includes(`${className}(`)) {
235
- isUsed = true;
8
+ const response = await fetch(`https://api.lua.dev/developer/skills/${agentId}/${skillId}/versions`, {
9
+ method: "GET",
10
+ headers: {
11
+ "accept": "application/json",
12
+ "Authorization": `Bearer ${apiKey}`
236
13
  }
237
- // Check if any variable that uses this class is referenced in execute body
238
- const variableRegex = new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+${className}\\s*\\([^)]*\\);`, 'g');
239
- let varMatch;
240
- while ((varMatch = variableRegex.exec(indexContent)) !== null) {
241
- const varName = varMatch[1];
242
- if (executeBody.includes(varName)) {
243
- isUsed = true;
244
- break;
245
- }
246
- }
247
- if (isUsed) {
248
- dependencies.push(fullClass);
249
- }
250
- }
251
- }
252
- // 3. Extract function definitions
253
- const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\}/g;
254
- let functionMatch;
255
- while ((functionMatch = functionRegex.exec(indexContent)) !== null) {
256
- const functionName = functionMatch[1];
257
- const functionBody = functionMatch[2];
258
- if (executeBody.includes(functionName)) {
259
- dependencies.push(`function ${functionName}() {\n${functionBody}\n}`);
260
- }
261
- }
262
- // 4. Extract const/let/var declarations (avoid duplicates)
263
- const varRegex = /(?:const|let|var)\s+(\w+)\s*=\s*([^;]+);/g;
264
- const declaredVars = new Set();
265
- let varMatch;
266
- while ((varMatch = varRegex.exec(indexContent)) !== null) {
267
- const varName = varMatch[1];
268
- const varValue = varMatch[2];
269
- // Skip if it's a class instantiation (we'll handle that separately)
270
- if (varValue.includes('new ') && varValue.includes('()')) {
271
- continue;
272
- }
273
- // Skip if already declared
274
- if (declaredVars.has(varName)) {
275
- continue;
276
- }
277
- if (executeBody.includes(varName)) {
278
- declaredVars.add(varName);
279
- dependencies.push(`const ${varName} = ${varValue};`);
14
+ });
15
+ if (!response.ok) {
16
+ throw new Error(`Failed to fetch versions: ${response.status} ${response.statusText}`);
280
17
  }
18
+ return await response.json();
281
19
  }
282
- // 5. Extract class instantiations (avoid duplicates)
283
- const instantiationRegex = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(\w+)\([^)]*\);/g;
284
- let instantiationMatch;
285
- while ((instantiationMatch = instantiationRegex.exec(indexContent)) !== null) {
286
- const instanceName = instantiationMatch[1];
287
- const className = instantiationMatch[2];
288
- // Skip if already declared
289
- if (declaredVars.has(instanceName)) {
290
- continue;
291
- }
292
- if (executeBody.includes(instanceName)) {
293
- declaredVars.add(instanceName);
294
- dependencies.push(`const ${instanceName} = new ${className}();`);
295
- }
20
+ catch (error) {
21
+ console.error("❌ Error fetching versions:", error);
22
+ throw error;
296
23
  }
297
- // 6. Create the self-contained execute function
298
- const allDependencies = [...Array.from(bundledPackages), ...dependencies];
299
- const dependencyCode = allDependencies.join('\n');
300
- // Strip TypeScript type annotations for JavaScript compatibility (only for local code)
301
- const cleanDependencyCode = allDependencies.map(dep => {
302
- // Only strip TypeScript from local dependencies, not bundled packages
303
- if (dep.includes('require(') || dep.includes('import ')) {
304
- return dep; // Skip bundled packages
305
- }
306
- return dep
307
- .replace(/:\s*string/g, '') // Remove : string
308
- .replace(/:\s*number/g, '') // Remove : number
309
- .replace(/:\s*boolean/g, '') // Remove : boolean
310
- .replace(/:\s*any/g, '') // Remove : any
311
- .replace(/:\s*void/g, '') // Remove : void
312
- .replace(/:\s*object/g, '') // Remove : object
313
- .replace(/:\s*Array<[^>]+>/g, '') // Remove : Array<Type>
314
- .replace(/:\s*Promise<[^>]+>/g, '') // Remove : Promise<Type>
315
- .replace(/:\s*Record<[^>]+>/g, ''); // Remove : Record<Type>
316
- }).join('\n');
317
- const cleanExecuteBody = executeBody
318
- .replace(/:\s*string/g, '') // Remove : string
319
- .replace(/:\s*number/g, '') // Remove : number
320
- .replace(/:\s*boolean/g, '') // Remove : boolean
321
- .replace(/:\s*any/g, '') // Remove : any
322
- .replace(/:\s*void/g, '') // Remove : void
323
- .replace(/:\s*object/g, '') // Remove : object
324
- .replace(/:\s*Array<[^>]+>/g, '') // Remove : Array<Type>
325
- .replace(/:\s*Promise<[^>]+>/g, '') // Remove : Promise<Type>
326
- .replace(/:\s*Record<[^>]+>/g, ''); // Remove : Record<Type>
327
- const selfContainedExecute = `async (input) => {
328
- ${cleanDependencyCode ? ` ${cleanDependencyCode.split('\n').join('\n ')}\n` : ''} ${cleanExecuteBody.trim()}
329
- }`;
330
- return selfContainedExecute;
331
24
  }
332
- async function bundlePackageCode(packagePath, namedImports, defaultImport) {
25
+ export async function publishVersion(apiKey, agentId, skillId, version) {
333
26
  try {
334
- const { build } = await import('esbuild');
335
- // Create a temporary entry file for esbuild in .lua directory
336
- const luaDir = path.join(process.cwd(), '.lua');
337
- if (!fs.existsSync(luaDir)) {
338
- fs.mkdirSync(luaDir, { recursive: true });
339
- }
340
- const entryFile = path.join(luaDir, `${packagePath}-entry.cjs`);
341
- const outputFile = path.join(luaDir, `${packagePath}-bundle.cjs`);
342
- // Create entry file based on import type
343
- let entryContent = '';
344
- if (defaultImport) {
345
- // For default imports like `import axios from 'axios'`
346
- entryContent = `import ${defaultImport} from '${packagePath}';\nmodule.exports = ${defaultImport};`;
347
- }
348
- else if (namedImports) {
349
- // For named imports like `import { z } from 'zod'`
350
- const importsList = namedImports.split(',').map(imp => imp.trim());
351
- entryContent = `import { ${importsList.join(', ')} } from '${packagePath}';\nmodule.exports = { ${importsList.join(', ')} };`;
352
- }
353
- else {
354
- // Fallback - import everything
355
- entryContent = `import * as ${packagePath.replace(/[^a-zA-Z0-9]/g, '_')} from '${packagePath}';\nmodule.exports = ${packagePath.replace(/[^a-zA-Z0-9]/g, '_')};`;
356
- }
357
- // Write entry file
358
- fs.writeFileSync(entryFile, entryContent);
359
- // Bundle with esbuild
360
- const result = await build({
361
- entryPoints: [entryFile],
362
- bundle: true,
363
- format: 'cjs', // CommonJS format
364
- platform: 'node',
365
- target: 'node16',
366
- outfile: outputFile,
367
- external: [], // Bundle everything
368
- minify: false, // Keep readable for debugging
369
- sourcemap: false,
370
- write: true,
371
- resolveExtensions: ['.js', '.ts', '.json'],
372
- mainFields: ['main', 'module', 'browser'],
373
- conditions: ['node'],
374
- nodePaths: [
375
- path.join(process.cwd(), 'node_modules'),
376
- path.join(process.cwd(), '..', 'node_modules'),
377
- path.join(process.cwd(), '..', '..', 'node_modules')
378
- ],
379
- absWorkingDir: process.cwd(),
27
+ const response = await fetch(`https://api.lua.dev/developer/skills/${agentId}/${skillId}/${version}/publish`, {
28
+ method: "PUT",
29
+ headers: {
30
+ "accept": "application/json",
31
+ "Authorization": `Bearer ${apiKey}`
32
+ }
380
33
  });
381
- if (result.errors.length > 0) {
382
- console.warn(`Warning: esbuild errors for package ${packagePath}:`, result.errors);
383
- return null;
384
- }
385
- // Read the bundled output
386
- if (!fs.existsSync(outputFile)) {
387
- console.warn(`Warning: Bundle output not found for package ${packagePath}`);
388
- return null;
389
- }
390
- const bundledContent = fs.readFileSync(outputFile, 'utf8');
391
- // Clean up temporary files
392
- try {
393
- fs.unlinkSync(entryFile);
394
- fs.unlinkSync(outputFile);
395
- }
396
- catch (cleanupError) {
397
- // Ignore cleanup errors
398
- }
399
- // Create the final bundled code
400
- let finalCode = '';
401
- if (defaultImport) {
402
- finalCode = `const ${defaultImport} = (function() {\n${bundledContent}\n return module.exports;\n})();\n`;
403
- }
404
- else if (namedImports) {
405
- const importsList = namedImports.split(',').map(imp => imp.trim());
406
- finalCode = `(function() {\n${bundledContent}\n})();\n`;
407
- finalCode += `const { ${importsList.join(', ')} } = module.exports;\n`;
34
+ if (!response.ok) {
35
+ throw new Error(`Failed to publish version: ${response.status} ${response.statusText}`);
408
36
  }
409
- else {
410
- finalCode = `(function() {\n${bundledContent}\n})();\n`;
411
- }
412
- return finalCode;
37
+ return await response.json();
413
38
  }
414
39
  catch (error) {
415
- console.warn(`Warning: Could not bundle package ${packagePath} with esbuild:`, error);
416
- // For test environments or when esbuild fails, provide a fallback
417
- if (packagePath === 'axios') {
418
- return createWorkingAxiosImplementation();
419
- }
420
- return null;
40
+ console.error("❌ Error publishing version:", error);
41
+ throw error;
421
42
  }
422
43
  }
423
- function createWorkingAxiosImplementation() {
424
- return `
425
- // Working axios implementation using native fetch (for test environments)
426
- const axios = {
427
- get: async (url, config = {}) => {
428
- const searchParams = new URLSearchParams(config.params || {});
429
- const fullUrl = searchParams.toString() ? \`\${url}?\${searchParams}\` : url;
430
-
431
- const response = await fetch(fullUrl, {
432
- method: 'GET',
433
- headers: {
434
- 'Content-Type': 'application/json',
435
- ...config.headers
436
- }
437
- });
438
-
439
- if (!response.ok) {
440
- const error = new Error(\`Request failed with status \${response.status}\`);
441
- error.response = { status: response.status, statusText: response.statusText };
442
- throw error;
443
- }
444
-
445
- const data = await response.json();
446
- return {
447
- data,
448
- status: response.status,
449
- statusText: response.statusText,
450
- headers: response.headers,
451
- config: config
452
- };
453
- },
454
-
455
- post: async (url, data, config = {}) => {
456
- const response = await fetch(url, {
457
- method: 'POST',
458
- headers: {
459
- 'Content-Type': 'application/json',
460
- ...config.headers
461
- },
462
- body: JSON.stringify(data)
463
- });
464
-
465
- if (!response.ok) {
466
- const error = new Error(\`Request failed with status \${response.status}\`);
467
- error.response = { status: response.status, statusText: response.statusText };
468
- throw error;
469
- }
470
-
471
- const responseData = await response.json();
472
- return {
473
- data: responseData,
474
- status: response.status,
475
- statusText: response.statusText,
476
- headers: response.headers,
477
- config: config
478
- };
479
- },
480
-
481
- put: async (url, data, config = {}) => {
482
- const response = await fetch(url, {
483
- method: 'PUT',
484
- headers: {
485
- 'Content-Type': 'application/json',
486
- ...config.headers
487
- },
488
- body: JSON.stringify(data)
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
- delete: async (url, config = {}) => {
508
- const response = await fetch(url, {
509
- method: 'DELETE',
510
- headers: {
511
- 'Content-Type': 'application/json',
512
- ...config.headers
513
- }
514
- });
515
-
516
- if (!response.ok) {
517
- const error = new Error(\`Request failed with status \${response.status}\`);
518
- error.response = { status: response.status, statusText: response.statusText };
519
- throw error;
520
- }
521
-
522
- const responseData = await response.json();
523
- return {
524
- data: responseData,
525
- status: response.status,
526
- statusText: response.statusText,
527
- headers: response.headers,
528
- config: config
529
- };
530
- },
531
-
532
- patch: async (url, data, config = {}) => {
533
- const response = await fetch(url, {
534
- method: 'PATCH',
535
- headers: {
536
- 'Content-Type': 'application/json',
537
- ...config.headers
538
- },
539
- body: JSON.stringify(data)
540
- });
541
-
542
- if (!response.ok) {
543
- const error = new Error(\`Request failed with status \${response.status}\`);
544
- error.response = { status: response.status, statusText: response.statusText };
545
- throw error;
546
- }
547
-
548
- const responseData = await response.json();
549
- return {
550
- data: responseData,
551
- status: response.status,
552
- statusText: response.statusText,
553
- headers: response.headers,
554
- config: config
555
- };
556
- }
557
- };
558
- `;
559
- }
560
- async function extractToolFromClass(className, indexContent) {
561
- // Find the import statement for this class
562
- const importRegex = new RegExp(`import\\s+${className}\\s+from\\s+["']([^"']+)["']`, 'g');
563
- const importMatch = importRegex.exec(indexContent);
564
- if (!importMatch) {
565
- console.warn(`Warning: Could not find import for class ${className}`);
44
+ function readTomlData() {
45
+ const tomlPath = path.join(process.cwd(), 'lua.skill.toml');
46
+ if (!fs.existsSync(tomlPath)) {
566
47
  return null;
567
48
  }
568
- const importPath = importMatch[1];
569
- // Read the tool file
570
- const toolFilePath = path.join(process.cwd(), importPath.replace('./', '') + '.ts');
571
- if (!fs.existsSync(toolFilePath)) {
572
- console.warn(`Warning: Tool file not found: ${toolFilePath}`);
49
+ const tomlContent = fs.readFileSync(tomlPath, 'utf8');
50
+ const agentIdMatch = tomlContent.match(/agentId\s*=\s*["']([^"']+)["']/);
51
+ const skillIdMatch = tomlContent.match(/skillId\s*=\s*["']([^"']+)["']/);
52
+ if (!agentIdMatch || !skillIdMatch) {
573
53
  return null;
574
54
  }
575
- const toolContent = fs.readFileSync(toolFilePath, 'utf8');
576
- // Extract tool properties from the class
577
- const nameMatch = toolContent.match(/this\.name\s*=\s*["']([^"']+)["']/);
578
- const descriptionMatch = toolContent.match(/this\.description\s*=\s*["']([^"']+)["']/);
579
- if (!nameMatch || !descriptionMatch) {
580
- console.warn(`Warning: Could not extract name or description from ${className}`);
581
- return null;
582
- }
583
- const toolName = nameMatch[1];
584
- const toolDescription = descriptionMatch[1];
585
- // Extract schemas
586
- const inputSchemaMatch = toolContent.match(/const\s+(\w+)\s*=\s*z\.object\(/);
587
- const outputSchemaMatch = toolContent.match(/const\s+(\w+)\s*=\s*z\.object\(/);
588
- if (!inputSchemaMatch) {
589
- console.warn(`Warning: Could not find input schema in ${className}`);
590
- return null;
591
- }
592
- // Convert schemas to JSON Schema format
593
- const inputSchema = convertSchemaToJSON(inputSchemaMatch[1], toolContent);
594
- const outputSchema = outputSchemaMatch ? convertSchemaToJSON(outputSchemaMatch[1], toolContent) : { type: "object" };
595
- // Extract execute method
596
- const executeMatch = toolContent.match(/async\s+execute\s*\([^)]*\)\s*\{([\s\S]*?)\}/);
597
- if (!executeMatch) {
598
- console.warn(`Warning: Could not find execute method in ${className}`);
599
- return null;
600
- }
601
- const executeBody = executeMatch[1];
602
- // For class-based tools, we need to create a self-contained function that includes:
603
- // 1. The service classes
604
- // 2. Class instantiation
605
- // 3. The execute logic
606
- const selfContainedExecute = await createClassBasedExecute(executeBody, toolContent, className);
607
55
  return {
608
- name: toolName,
609
- description: toolDescription,
610
- inputSchema,
611
- outputSchema,
612
- execute: selfContainedExecute
56
+ agentId: agentIdMatch[1],
57
+ skillId: skillIdMatch[1]
613
58
  };
614
59
  }
615
- async function createClassBasedExecute(executeBody, toolContent, className) {
616
- const dependencies = [];
617
- const bundledPackages = new Set();
618
- // 1. Parse imports from the tool file
619
- const importRegex = /import\s+(?:(?:\{([^}]+)\})|(\w+))\s+from\s+["']([^"']+)["']/g;
620
- let importMatch;
621
- while ((importMatch = importRegex.exec(toolContent)) !== null) {
622
- const namedImports = importMatch[1];
623
- const defaultImport = importMatch[2];
624
- const packagePath = importMatch[3];
625
- // Skip lua-cli imports
626
- if (packagePath.startsWith('lua-cli')) {
627
- continue;
628
- }
629
- // Handle zod
630
- if (packagePath === 'zod') {
631
- if (namedImports) {
632
- const importsList = namedImports.split(',').map(imp => imp.trim());
633
- const usedImports = importsList.filter(imp => executeBody.includes(imp) || toolContent.includes(`${imp}.`));
634
- if (usedImports.length > 0) {
635
- const requireStatement = usedImports.length === 1
636
- ? `const { ${usedImports[0]} } = require('zod');`
637
- : `const { ${usedImports.join(', ')} } = require('zod');`;
638
- bundledPackages.add(requireStatement);
639
- }
640
- }
641
- else if (defaultImport) {
642
- bundledPackages.add(`const ${defaultImport} = require('zod');`);
643
- }
644
- continue;
645
- }
646
- // Handle axios - bundle it properly
647
- if (packagePath === 'axios') {
648
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
649
- if (packageCode) {
650
- bundledPackages.add(packageCode);
60
+ function formatVersionChoice(version) {
61
+ const currentIndicator = version.isCurrent ? ' (CURRENT)' : '';
62
+ const date = new Date(version.createdDate).toLocaleDateString();
63
+ return `${version.version}${currentIndicator} - Created: ${date} by ${version.createdByEmail}`;
64
+ }
65
+ export async function deployCommand() {
66
+ return withErrorHandling(async () => {
67
+ // Check if we're in a skill directory
68
+ const tomlData = readTomlData();
69
+ if (!tomlData) {
70
+ console.error("❌ No lua.skill.toml found or missing agentId/skillId. Please run this command from a skill directory.");
71
+ process.exit(1);
72
+ }
73
+ // Load API key
74
+ const apiKey = await loadApiKey();
75
+ if (!apiKey) {
76
+ console.error("❌ No API key found. Please run 'lua configure' to set up your API key.");
77
+ process.exit(1);
78
+ }
79
+ // Validate API key
80
+ const userData = await checkApiKey(apiKey);
81
+ writeProgress("✅ Authenticated");
82
+ // Fetch available versions
83
+ writeProgress("🔄 Fetching available versions...");
84
+ const versionsResponse = await fetchVersions(apiKey, tomlData.agentId, tomlData.skillId);
85
+ if (!versionsResponse.versions || versionsResponse.versions.length === 0) {
86
+ console.log("❌ No versions found for this skill.");
87
+ process.exit(1);
88
+ }
89
+ // Sort versions by creation date (newest first)
90
+ const sortedVersions = versionsResponse.versions.sort((a, b) => new Date(b.createdDate).getTime() - new Date(a.createdDate).getTime());
91
+ // Let user select a version
92
+ const { selectedVersion } = await inquirer.prompt([
93
+ {
94
+ type: "list",
95
+ name: "selectedVersion",
96
+ message: "Select a version to deploy:",
97
+ choices: sortedVersions.map(version => ({
98
+ name: formatVersionChoice(version),
99
+ value: version.version
100
+ }))
651
101
  }
652
- continue;
653
- }
654
- // Handle local service imports
655
- if (packagePath.startsWith('./') || packagePath.startsWith('../')) {
656
- // The tool files are in tools/ subdirectory, so we need to resolve from there
657
- const toolDir = path.join(process.cwd(), 'tools');
658
- // Resolve the service file path correctly
659
- // If the import is ../services/ApiService, resolve it relative to the tools directory
660
- const serviceFilePath = path.resolve(process.cwd(), 'tools', packagePath + '.ts');
661
- if (fs.existsSync(serviceFilePath)) {
662
- const serviceContent = fs.readFileSync(serviceFilePath, 'utf8');
663
- // Process all imports in the service file
664
- const serviceImportRegex = /import\s+(?:(?:\{([^}]+)\})|(\w+))\s+from\s+["']([^"']+)["']/g;
665
- let serviceImportMatch;
666
- while ((serviceImportMatch = serviceImportRegex.exec(serviceContent)) !== null) {
667
- const namedImports = serviceImportMatch[1];
668
- const defaultImport = serviceImportMatch[2];
669
- const packagePath = serviceImportMatch[3];
670
- // Skip lua-cli imports
671
- if (packagePath.startsWith('lua-cli')) {
672
- continue;
673
- }
674
- // Handle zod
675
- if (packagePath === 'zod') {
676
- if (namedImports) {
677
- const importsList = namedImports.split(',').map(imp => imp.trim());
678
- const usedImports = importsList.filter(imp => serviceContent.includes(`${imp}.`));
679
- if (usedImports.length > 0) {
680
- const requireStatement = usedImports.length === 1
681
- ? `const { ${usedImports[0]} } = require('zod');`
682
- : `const { ${usedImports.join(', ')} } = require('zod');`;
683
- bundledPackages.add(requireStatement);
684
- }
685
- }
686
- else if (defaultImport) {
687
- bundledPackages.add(`const ${defaultImport} = require('zod');`);
688
- }
689
- continue;
690
- }
691
- // Handle axios - bundle it properly
692
- if (packagePath === 'axios') {
693
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
694
- if (packageCode) {
695
- bundledPackages.add(packageCode);
696
- }
697
- continue;
698
- }
699
- // Bundle other external packages
700
- if (namedImports || defaultImport) {
701
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
702
- if (packageCode) {
703
- bundledPackages.add(packageCode);
704
- }
705
- }
706
- }
707
- // Extract the service class with proper brace matching
708
- const classRegex = /class\s+(\w+)(?:\s+extends\s+\w+)?\s*\{/g;
709
- let classMatch = classRegex.exec(serviceContent);
710
- if (classMatch) {
711
- const serviceClassName = classMatch[1];
712
- const startIndex = classMatch.index + classMatch[0].length - 1; // Position of opening brace
713
- // Find matching closing brace
714
- let braceCount = 1;
715
- let endIndex = startIndex + 1;
716
- while (endIndex < serviceContent.length && braceCount > 0) {
717
- if (serviceContent[endIndex] === '{')
718
- braceCount++;
719
- else if (serviceContent[endIndex] === '}')
720
- braceCount--;
721
- endIndex++;
722
- }
723
- if (braceCount === 0) {
724
- const serviceClassBody = serviceContent.substring(startIndex + 1, endIndex - 1);
725
- // Clean up the class body (remove TypeScript types)
726
- const cleanClassBody = serviceClassBody
727
- .replace(/:\s*string/g, '')
728
- .replace(/:\s*number/g, '')
729
- .replace(/:\s*boolean/g, '')
730
- .replace(/:\s*any/g, '')
731
- .replace(/:\s*void/g, '')
732
- .replace(/:\s*Promise<[^>]+>/g, '')
733
- .replace(/:\s*Record<[^>]+>/g, '');
734
- // Create the service class
735
- const serviceClass = `class ${serviceClassName} {\n${cleanClassBody}\n}`;
736
- dependencies.push(serviceClass);
737
- }
738
- }
102
+ ]);
103
+ // Clear the selection prompt lines
104
+ clearPromptLines(2);
105
+ // Show warning and confirm deployment
106
+ const { confirmed } = await inquirer.prompt([
107
+ {
108
+ type: "confirm",
109
+ name: "confirmed",
110
+ message: "⚠️ Warning: This version will be deployed to all users. Do you want to proceed?",
111
+ default: false
739
112
  }
740
- continue;
741
- }
742
- // Bundle other external packages
743
- if (namedImports || defaultImport) {
744
- const packageCode = await bundlePackageCode(packagePath, namedImports, defaultImport);
745
- if (packageCode) {
746
- bundledPackages.add(packageCode);
747
- }
748
- }
749
- }
750
- // 2. Extract class instantiation from constructor
751
- const constructorMatch = toolContent.match(/constructor\s*\([^)]*\)\s*\{([\s\S]*?)\}/);
752
- if (constructorMatch) {
753
- const constructorBody = constructorMatch[1];
754
- // Extract service instantiation
755
- const serviceInstantiationMatch = constructorBody.match(/this\.(\w+)\s*=\s*new\s+(\w+)\([^)]*\);/);
756
- if (serviceInstantiationMatch) {
757
- const serviceProperty = serviceInstantiationMatch[1];
758
- const serviceClass = serviceInstantiationMatch[2];
759
- dependencies.push(`const ${serviceProperty} = new ${serviceClass}();`);
760
- }
761
- }
762
- // 3. Create the self-contained execute function
763
- const allDependencies = [...Array.from(bundledPackages), ...dependencies];
764
- const dependencyCode = allDependencies.join('\n');
765
- // Clean the execute body (remove TypeScript types)
766
- const cleanExecuteBody = executeBody
767
- .replace(/:\s*string/g, '')
768
- .replace(/:\s*number/g, '')
769
- .replace(/:\s*boolean/g, '')
770
- .replace(/:\s*any/g, '')
771
- .replace(/:\s*void/g, '')
772
- .replace(/:\s*Promise<[^>]+>/g, '')
773
- .replace(/:\s*Record<[^>]+>/g, '');
774
- // Replace this.serviceProperty with the instantiated service
775
- const finalExecuteBody = cleanExecuteBody.replace(/this\.(\w+)/g, '$1');
776
- return `async (input) => {
777
- ${dependencyCode ? dependencyCode + '\n' : ''}${finalExecuteBody}
778
- }`;
113
+ ]);
114
+ // Clear the confirmation prompt lines
115
+ clearPromptLines(2);
116
+ if (!confirmed) {
117
+ console.log("❌ Deployment cancelled.");
118
+ process.exit(0);
119
+ }
120
+ // Publish the selected version
121
+ writeProgress("🔄 Publishing version...");
122
+ const publishResponse = await publishVersion(apiKey, tomlData.agentId, tomlData.skillId, selectedVersion);
123
+ writeSuccess(`✅ Version ${publishResponse.activeVersionId} deployed successfully`);
124
+ }, "deployment");
779
125
  }