lua-cli 2.3.0 → 2.4.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.
Files changed (56) hide show
  1. package/dist/api/products.api.service.js +1 -0
  2. package/dist/cli/command-definitions.d.ts +0 -8
  3. package/dist/cli/command-definitions.js +51 -18
  4. package/dist/commands/chat.d.ts +17 -0
  5. package/dist/commands/chat.js +236 -0
  6. package/dist/commands/chatClear.d.ts +15 -0
  7. package/dist/commands/chatClear.js +75 -0
  8. package/dist/commands/deploy.d.ts +7 -5
  9. package/dist/commands/deploy.js +34 -15
  10. package/dist/commands/env.d.ts +19 -0
  11. package/dist/commands/env.js +434 -0
  12. package/dist/commands/index.d.ts +6 -0
  13. package/dist/commands/index.js +6 -0
  14. package/dist/commands/init.js +6 -6
  15. package/dist/commands/persona.d.ts +15 -0
  16. package/dist/commands/persona.js +503 -0
  17. package/dist/commands/production.d.ts +15 -0
  18. package/dist/commands/production.js +532 -0
  19. package/dist/commands/push.d.ts +7 -5
  20. package/dist/commands/push.js +100 -21
  21. package/dist/commands/resources.d.ts +17 -0
  22. package/dist/commands/resources.js +361 -0
  23. package/dist/common/data.entry.instance.js +6 -2
  24. package/dist/common/http.client.js +7 -0
  25. package/dist/config/compile.constants.d.ts +4 -3
  26. package/dist/config/compile.constants.js +3 -7
  27. package/dist/config/constants.d.ts +6 -0
  28. package/dist/config/constants.js +11 -0
  29. package/dist/errors/auth.error.d.ts +15 -0
  30. package/dist/errors/auth.error.js +27 -0
  31. package/dist/errors/index.d.ts +4 -0
  32. package/dist/errors/index.js +4 -0
  33. package/dist/index.d.ts +0 -9
  34. package/dist/index.js +30 -11
  35. package/dist/interfaces/agent.d.ts +33 -49
  36. package/dist/services/auth.d.ts +1 -2
  37. package/dist/services/auth.js +3 -4
  38. package/dist/utils/bundling.js +61 -0
  39. package/dist/utils/cli.js +6 -0
  40. package/dist/utils/deploy-helpers.d.ts +22 -0
  41. package/dist/utils/deploy-helpers.js +61 -2
  42. package/dist/utils/deployment.js +33 -1
  43. package/dist/utils/dev-server.js +255 -0
  44. package/dist/utils/init-agent.js +3 -3
  45. package/dist/utils/prompt-handler.d.ts +12 -0
  46. package/dist/utils/prompt-handler.js +31 -0
  47. package/dist/utils/push-helpers.d.ts +47 -3
  48. package/dist/utils/push-helpers.js +167 -10
  49. package/dist/utils/sandbox.js +18 -5
  50. package/dist/web/app.css +1302 -465
  51. package/dist/web/app.js +53 -46
  52. package/package.json +1 -1
  53. package/template/package.json +1 -1
  54. package/template/src/tools/BasketTool.ts +4 -1
  55. package/template/src/tools/ProductsTool.ts +7 -7
  56. package/dist/web/tools-page.css +0 -381
@@ -12,6 +12,11 @@ import { executeTool, createBroadcastConsole, loadEnvironmentVariables } from '.
12
12
  import { getSandboxSkillId } from './sandbox-storage.js';
13
13
  import { sendChatMessage } from './dev-api.js';
14
14
  import { CORS_HEADERS, HTTP_STATUS, DEV_DEFAULTS } from '../config/dev.constants.js';
15
+ import { BASE_URLS } from '../config/constants.js';
16
+ import ProductApi from '../api/products.api.service.js';
17
+ import BasketApi from '../api/basket.api.service.js';
18
+ import OrderApi from '../api/order.api.service.js';
19
+ import CustomDataApi from '../api/custom.data.api.service.js';
15
20
  const __filename = fileURLToPath(import.meta.url);
16
21
  const __dirname = path.dirname(__filename);
17
22
  /**
@@ -86,6 +91,26 @@ export function createChatServer(apiKey, agentId, skillId, sandboxId, port) {
86
91
  handleToolTestEndpoint(req, res, apiKey, agentId, wss);
87
92
  return;
88
93
  }
94
+ // Products routes
95
+ if (req.url?.startsWith('/api/products')) {
96
+ handleProductsEndpoint(req, res, apiKey, agentId);
97
+ return;
98
+ }
99
+ // Baskets routes
100
+ if (req.url?.startsWith('/api/baskets')) {
101
+ handleBasketsEndpoint(req, res, apiKey, agentId);
102
+ return;
103
+ }
104
+ // Orders routes
105
+ if (req.url?.startsWith('/api/orders')) {
106
+ handleOrdersEndpoint(req, res, apiKey, agentId);
107
+ return;
108
+ }
109
+ // Custom Data routes
110
+ if (req.url?.startsWith('/api/custom-data')) {
111
+ handleCustomDataEndpoint(req, res, apiKey, agentId);
112
+ return;
113
+ }
89
114
  if (req.method === 'GET' && req.url === '/') {
90
115
  handleIndexEndpoint(req, res, apiKey, agentId, skillId, sandboxId);
91
116
  return;
@@ -553,3 +578,233 @@ function handleStaticFile(req, res, filename, contentType) {
553
578
  res.end(`${filename} not found`);
554
579
  }
555
580
  }
581
+ /**
582
+ * Handler for products API using ProductApi service
583
+ */
584
+ async function handleProductsEndpoint(req, res, apiKey, agentId) {
585
+ const productApi = new ProductApi(BASE_URLS.API, apiKey, agentId);
586
+ try {
587
+ let result;
588
+ if (req.method === 'GET') {
589
+ if (req.url.includes('/search')) {
590
+ const searchQuery = new URL(req.url, 'http://localhost').searchParams.get('searchQuery') || '';
591
+ result = await productApi.search(searchQuery);
592
+ }
593
+ else if (req.url.match(/\/api\/products\/[^/]+$/)) {
594
+ const productId = req.url.split('/').pop();
595
+ result = await productApi.getById(productId);
596
+ }
597
+ else {
598
+ const url = new URL(req.url, 'http://localhost');
599
+ const page = parseInt(url.searchParams.get('page') || '1');
600
+ const limit = parseInt(url.searchParams.get('limit') || '20');
601
+ result = await productApi.get(page, limit);
602
+ }
603
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
604
+ res.end(JSON.stringify({ success: true, data: result }));
605
+ }
606
+ else if (req.method === 'POST') {
607
+ const body = await new Promise((resolve) => {
608
+ let data = '';
609
+ req.on('data', (chunk) => { data += chunk; });
610
+ req.on('end', () => resolve(data));
611
+ });
612
+ const productData = JSON.parse(body);
613
+ result = await productApi.create(productData);
614
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
615
+ res.end(JSON.stringify({ success: true, data: { product: result } }));
616
+ }
617
+ else if (req.method === 'PUT') {
618
+ const body = await new Promise((resolve) => {
619
+ let data = '';
620
+ req.on('data', (chunk) => { data += chunk; });
621
+ req.on('end', () => resolve(data));
622
+ });
623
+ const productData = JSON.parse(body);
624
+ result = await productApi.update(productData, productData.id);
625
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
626
+ res.end(JSON.stringify({ success: true, data: result }));
627
+ }
628
+ else if (req.method === 'DELETE') {
629
+ const productId = req.url.split('/').pop();
630
+ result = await productApi.delete(productId);
631
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
632
+ res.end(JSON.stringify({ success: true, data: result }));
633
+ }
634
+ }
635
+ catch (error) {
636
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
637
+ res.end(JSON.stringify({ success: false, error: error.message || 'API error' }));
638
+ }
639
+ }
640
+ /**
641
+ * Handler for baskets API using BasketApi service
642
+ */
643
+ async function handleBasketsEndpoint(req, res, apiKey, agentId) {
644
+ const basketApi = new BasketApi(BASE_URLS.API, apiKey, agentId);
645
+ try {
646
+ let result;
647
+ if (req.method === 'GET') {
648
+ if (req.url.match(/\/api\/baskets\/[^/]+$/)) {
649
+ const basketId = req.url.split('/').pop();
650
+ result = await basketApi.getById(basketId);
651
+ }
652
+ else {
653
+ const url = new URL(req.url, 'http://localhost');
654
+ const status = url.searchParams.get('status');
655
+ result = await basketApi.get(status);
656
+ }
657
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
658
+ res.end(JSON.stringify({ success: true, data: result }));
659
+ }
660
+ else if (req.method === 'POST') {
661
+ const body = await new Promise((resolve) => {
662
+ let data = '';
663
+ req.on('data', (chunk) => { data += chunk; });
664
+ req.on('end', () => resolve(data));
665
+ });
666
+ const basketData = JSON.parse(body);
667
+ result = await basketApi.create(basketData);
668
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
669
+ res.end(JSON.stringify({ success: true, data: result }));
670
+ }
671
+ else if (req.method === 'DELETE') {
672
+ const basketId = req.url.split('/').pop();
673
+ // Baskets don't have a direct delete in the API, use clear instead
674
+ result = await basketApi.clear(basketId);
675
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
676
+ res.end(JSON.stringify({ success: true, data: result }));
677
+ }
678
+ }
679
+ catch (error) {
680
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
681
+ res.end(JSON.stringify({ success: false, error: error.message || 'API error' }));
682
+ }
683
+ }
684
+ /**
685
+ * Handler for orders API using OrderApi service
686
+ */
687
+ async function handleOrdersEndpoint(req, res, apiKey, agentId) {
688
+ const orderApi = new OrderApi(BASE_URLS.API, apiKey, agentId);
689
+ try {
690
+ let result;
691
+ if (req.method === 'GET') {
692
+ if (req.url.match(/\/api\/orders\/[^/]+$/)) {
693
+ const orderId = req.url.split('/').pop();
694
+ result = await orderApi.getById(orderId);
695
+ }
696
+ else {
697
+ const url = new URL(req.url, 'http://localhost');
698
+ const status = url.searchParams.get('status');
699
+ result = await orderApi.get(status);
700
+ }
701
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
702
+ res.end(JSON.stringify({ success: true, data: result }));
703
+ }
704
+ else if (req.method === 'POST') {
705
+ const body = await new Promise((resolve) => {
706
+ let data = '';
707
+ req.on('data', (chunk) => { data += chunk; });
708
+ req.on('end', () => resolve(data));
709
+ });
710
+ const orderData = JSON.parse(body);
711
+ result = await orderApi.create(orderData);
712
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
713
+ res.end(JSON.stringify({ success: true, data: result }));
714
+ }
715
+ else if (req.method === 'PUT') {
716
+ if (req.url.includes('/status')) {
717
+ const orderId = req.url.split('/')[3];
718
+ const body = await new Promise((resolve) => {
719
+ let data = '';
720
+ req.on('data', (chunk) => { data += chunk; });
721
+ req.on('end', () => resolve(data));
722
+ });
723
+ const { status } = JSON.parse(body);
724
+ result = await orderApi.updateStatus(status, orderId);
725
+ }
726
+ else {
727
+ const body = await new Promise((resolve) => {
728
+ let data = '';
729
+ req.on('data', (chunk) => { data += chunk; });
730
+ req.on('end', () => resolve(data));
731
+ });
732
+ const orderData = JSON.parse(body);
733
+ const orderId = req.url.split('/').pop();
734
+ result = await orderApi.updateData(orderData, orderId);
735
+ }
736
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
737
+ res.end(JSON.stringify({ success: true, data: result }));
738
+ }
739
+ }
740
+ catch (error) {
741
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
742
+ res.end(JSON.stringify({ success: false, error: error.message || 'API error' }));
743
+ }
744
+ }
745
+ /**
746
+ * Handler for custom data API using CustomDataApi service
747
+ */
748
+ async function handleCustomDataEndpoint(req, res, apiKey, agentId) {
749
+ const customDataApi = new CustomDataApi(BASE_URLS.API, apiKey, agentId);
750
+ try {
751
+ let result;
752
+ const pathParts = req.url.split('/');
753
+ const collectionName = pathParts[3]; // /api/custom-data/{collection}
754
+ if (req.method === 'GET') {
755
+ if (req.url.includes('/search')) {
756
+ const url = new URL(req.url, 'http://localhost');
757
+ const searchText = url.searchParams.get('searchText') || '';
758
+ const limit = parseInt(url.searchParams.get('limit') || '10');
759
+ const scoreThreshold = parseFloat(url.searchParams.get('scoreThreshold') || '0.7');
760
+ result = await customDataApi.search(collectionName, searchText, limit, scoreThreshold);
761
+ }
762
+ else if (pathParts.length > 4) {
763
+ const entryId = pathParts[4];
764
+ result = await customDataApi.getEntry(collectionName, entryId);
765
+ }
766
+ else {
767
+ const url = new URL(req.url, 'http://localhost');
768
+ const filter = url.searchParams.get('filter') ? JSON.parse(url.searchParams.get('filter')) : undefined;
769
+ const page = parseInt(url.searchParams.get('page') || '1');
770
+ const limit = parseInt(url.searchParams.get('limit') || '10');
771
+ result = await customDataApi.get(collectionName, filter, page, limit);
772
+ }
773
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
774
+ res.end(JSON.stringify({ success: true, data: result }));
775
+ }
776
+ else if (req.method === 'POST') {
777
+ const body = await new Promise((resolve) => {
778
+ let data = '';
779
+ req.on('data', (chunk) => { data += chunk; });
780
+ req.on('end', () => resolve(data));
781
+ });
782
+ const { data, searchText } = JSON.parse(body);
783
+ result = await customDataApi.create(collectionName, data, searchText);
784
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
785
+ res.end(JSON.stringify({ success: true, data: result }));
786
+ }
787
+ else if (req.method === 'PUT') {
788
+ const entryId = pathParts[4];
789
+ const body = await new Promise((resolve) => {
790
+ let data = '';
791
+ req.on('data', (chunk) => { data += chunk; });
792
+ req.on('end', () => resolve(data));
793
+ });
794
+ const updateData = JSON.parse(body);
795
+ result = await customDataApi.update(collectionName, entryId, updateData);
796
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
797
+ res.end(JSON.stringify({ success: true, data: result }));
798
+ }
799
+ else if (req.method === 'DELETE') {
800
+ const entryId = pathParts[4];
801
+ result = await customDataApi.delete(collectionName, entryId);
802
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
803
+ res.end(JSON.stringify({ success: true, data: result }));
804
+ }
805
+ }
806
+ catch (error) {
807
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
808
+ res.end(JSON.stringify({ success: false, error: error.message || 'API error' }));
809
+ }
810
+ }
@@ -80,9 +80,9 @@ export async function createNewAgent(apiKey, agentType, businessConfig, metadata
80
80
  // Wait for agent to be ready
81
81
  await waitForAgentReady();
82
82
  // Validate required fields from API response
83
- const agentId = createAgentResult.data?.data?.agentId;
84
- const agentName = createAgentResult.data?.data?.name;
85
- const orgId = createAgentResult.data?.data?.org?.id;
83
+ const agentId = createAgentResult.data?.agentId;
84
+ const agentName = createAgentResult.data?.name;
85
+ const orgId = createAgentResult.data?.org?.id;
86
86
  if (!agentId || !agentName || !orgId) {
87
87
  throw new Error("Failed to create agent: Missing required fields in API response");
88
88
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Prompt Handler Utility
3
+ * Gracefully handles user interruptions (Ctrl+C) in inquirer prompts
4
+ */
5
+ /**
6
+ * Wraps inquirer.prompt to gracefully handle Ctrl+C interruptions
7
+ */
8
+ export declare function safePrompt<T = any>(questions: any): Promise<T | null>;
9
+ /**
10
+ * Setup global SIGINT handler for graceful exits
11
+ */
12
+ export declare function setupGlobalInterruptHandler(): void;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Prompt Handler Utility
3
+ * Gracefully handles user interruptions (Ctrl+C) in inquirer prompts
4
+ */
5
+ import inquirer from 'inquirer';
6
+ /**
7
+ * Wraps inquirer.prompt to gracefully handle Ctrl+C interruptions
8
+ */
9
+ export async function safePrompt(questions) {
10
+ try {
11
+ const answers = await inquirer.prompt(questions);
12
+ return answers;
13
+ }
14
+ catch (error) {
15
+ // Handle Ctrl+C gracefully
16
+ if (error.name === 'ExitPromptError' || error.message?.includes('SIGINT')) {
17
+ console.log('\n\n👋 Operation cancelled. Goodbye!\n');
18
+ process.exit(0);
19
+ }
20
+ throw error;
21
+ }
22
+ }
23
+ /**
24
+ * Setup global SIGINT handler for graceful exits
25
+ */
26
+ export function setupGlobalInterruptHandler() {
27
+ process.on('SIGINT', () => {
28
+ console.log('\n\n👋 Operation cancelled. Goodbye!\n');
29
+ process.exit(0);
30
+ });
31
+ }
@@ -2,6 +2,28 @@
2
2
  * Push Helper Utilities
3
3
  * Helper functions for the push command
4
4
  */
5
+ /**
6
+ * Selected skill for pushing.
7
+ */
8
+ export interface SelectedSkill {
9
+ name: string;
10
+ version: string;
11
+ skillId: string;
12
+ }
13
+ /**
14
+ * Prompts user to select a skill from available skills.
15
+ *
16
+ * @param skills - Array of available skills
17
+ * @returns Selected skill
18
+ */
19
+ export declare function promptSkillSelection(skills: SelectedSkill[]): Promise<SelectedSkill>;
20
+ /**
21
+ * Prompts user to confirm or update the version.
22
+ *
23
+ * @param currentVersion - Current version of the skill
24
+ * @returns Confirmed or updated version
25
+ */
26
+ export declare function promptVersionConfirmOrUpdate(currentVersion: string): Promise<string>;
5
27
  /**
6
28
  * Reads the version from skill configuration.
7
29
  *
@@ -29,10 +51,32 @@ export declare function confirmVersionPush(version: string): Promise<boolean>;
29
51
  */
30
52
  export declare function validatePushConfig(config: any): void;
31
53
  /**
32
- * Validates that deploy data matches configuration.
54
+ * Gets available skills from configuration.
55
+ *
56
+ * @param config - Skill configuration
57
+ * @returns Array of available skills
58
+ */
59
+ export declare function getAvailableSkills(config: any): SelectedSkill[];
60
+ /**
61
+ * Updates a skill's version in the YAML configuration file.
62
+ *
63
+ * @param skillName - Name of the skill to update
64
+ * @param newVersion - New version to set
65
+ */
66
+ export declare function updateSkillVersionInYaml(skillName: string, newVersion: string): void;
67
+ /**
68
+ * Validates that deploy data matches configuration for a specific skill.
33
69
  *
34
70
  * @param deployData - Deploy data from deploy.json
35
- * @param configVersion - Version from configuration
71
+ * @param selectedSkill - The selected skill to validate
36
72
  * @throws Error if deploy data is invalid or version mismatch
37
73
  */
38
- export declare function validateDeployData(deployData: any, configVersion: string): void;
74
+ export declare function validateDeployData(deployData: any, selectedSkill: SelectedSkill): void;
75
+ /**
76
+ * Extracts the deploy data for a specific skill from the deploy.json.
77
+ *
78
+ * @param deployData - Deploy data from deploy.json
79
+ * @param skillName - Name of the skill to extract
80
+ * @returns The deploy data for the specific skill
81
+ */
82
+ export declare function getSkillDeployData(deployData: any, skillName: string): any;
@@ -7,6 +7,77 @@ import path from 'path';
7
7
  import inquirer from 'inquirer';
8
8
  import { readSkillConfig } from './files.js';
9
9
  import { clearPromptLines } from './cli.js';
10
+ /**
11
+ * Prompts user to select a skill from available skills.
12
+ *
13
+ * @param skills - Array of available skills
14
+ * @returns Selected skill
15
+ */
16
+ export async function promptSkillSelection(skills) {
17
+ const skillChoices = skills.map((skill) => ({
18
+ name: `${skill.name} (v${skill.version})`,
19
+ value: skill
20
+ }));
21
+ const { selectedSkill } = await inquirer.prompt([
22
+ {
23
+ type: "list",
24
+ name: "selectedSkill",
25
+ message: "Select a skill to push:",
26
+ choices: skillChoices
27
+ }
28
+ ]);
29
+ clearPromptLines(2);
30
+ return selectedSkill;
31
+ }
32
+ /**
33
+ * Prompts user to confirm or update the version.
34
+ *
35
+ * @param currentVersion - Current version of the skill
36
+ * @returns Confirmed or updated version
37
+ */
38
+ export async function promptVersionConfirmOrUpdate(currentVersion) {
39
+ console.log(`\nCurrent version: ${currentVersion}`);
40
+ const { newVersion } = await inquirer.prompt([
41
+ {
42
+ type: "input",
43
+ name: "newVersion",
44
+ message: "Enter new version to push:",
45
+ default: incrementPatchVersion(currentVersion),
46
+ validate: (input) => {
47
+ if (!input || input.trim() === "") {
48
+ return "Version cannot be empty";
49
+ }
50
+ // Basic semver validation
51
+ const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/;
52
+ if (!semverRegex.test(input.trim())) {
53
+ return "Version must follow semver format (e.g., 1.0.0, 1.0.0-alpha.1)";
54
+ }
55
+ return true;
56
+ }
57
+ }
58
+ ]);
59
+ clearPromptLines(2);
60
+ return newVersion.trim();
61
+ }
62
+ /**
63
+ * Increments the patch version automatically (e.g., 1.0.0 → 1.0.1)
64
+ *
65
+ * @param version - Current version string
66
+ * @returns Incremented version
67
+ */
68
+ function incrementPatchVersion(version) {
69
+ const semverRegex = /^(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9.-]+)?$/;
70
+ const match = version.match(semverRegex);
71
+ if (match) {
72
+ const major = match[1];
73
+ const minor = match[2];
74
+ const patch = parseInt(match[3]) + 1;
75
+ const prerelease = match[4] || '';
76
+ return `${major}.${minor}.${patch}${prerelease}`;
77
+ }
78
+ // If doesn't match semver, return as-is
79
+ return version;
80
+ }
10
81
  /**
11
82
  * Reads the version from skill configuration.
12
83
  *
@@ -57,28 +128,114 @@ export function validatePushConfig(config) {
57
128
  if (!config) {
58
129
  throw new Error("No lua.skill.yaml found. Please run this command from a skill directory.");
59
130
  }
60
- if (!config.skill?.version) {
61
- throw new Error("No version found in skill configuration");
62
- }
63
131
  if (!config.agent?.agentId) {
64
132
  throw new Error("Missing agentId in skill configuration");
65
133
  }
66
- if (!config.skill?.skillId) {
67
- throw new Error("Missing skillId in skill configuration");
134
+ // Check for new multi-skill format
135
+ if (config.skills && Array.isArray(config.skills) && config.skills.length > 0) {
136
+ return; // Valid multi-skill config
137
+ }
138
+ // Check for legacy single-skill format
139
+ if (config.skill?.version && config.skill?.skillId) {
140
+ return; // Valid legacy config
68
141
  }
142
+ throw new Error("No skills found in configuration. Please compile your skill first.");
69
143
  }
70
144
  /**
71
- * Validates that deploy data matches configuration.
145
+ * Gets available skills from configuration.
146
+ *
147
+ * @param config - Skill configuration
148
+ * @returns Array of available skills
149
+ */
150
+ export function getAvailableSkills(config) {
151
+ // Check for new multi-skill format
152
+ if (config.skills && Array.isArray(config.skills) && config.skills.length > 0) {
153
+ return config.skills.map((skill) => ({
154
+ name: skill.name,
155
+ version: skill.version,
156
+ skillId: skill.skillId
157
+ }));
158
+ }
159
+ // Fallback to legacy single-skill format
160
+ if (config.skill) {
161
+ return [{
162
+ name: config.skill.name || 'unnamed-skill',
163
+ version: config.skill.version,
164
+ skillId: config.skill.skillId
165
+ }];
166
+ }
167
+ return [];
168
+ }
169
+ /**
170
+ * Updates a skill's version in the YAML configuration file.
171
+ *
172
+ * @param skillName - Name of the skill to update
173
+ * @param newVersion - New version to set
174
+ */
175
+ export function updateSkillVersionInYaml(skillName, newVersion) {
176
+ const yamlPath = path.join(process.cwd(), 'lua.skill.yaml');
177
+ if (!fs.existsSync(yamlPath)) {
178
+ throw new Error("lua.skill.yaml not found");
179
+ }
180
+ const yamlContent = fs.readFileSync(yamlPath, 'utf8');
181
+ // Simple regex-based replacement to preserve formatting
182
+ // Match the skill entry and update its version
183
+ const skillPattern = new RegExp(`(- name: ${skillName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s+version: )[^\\n]+`, 'g');
184
+ const updatedContent = yamlContent.replace(skillPattern, `$1${newVersion}`);
185
+ if (updatedContent === yamlContent) {
186
+ // Try legacy format
187
+ const legacyPattern = /(\s+version: )[^\n]+/;
188
+ const legacyUpdated = yamlContent.replace(legacyPattern, `$1${newVersion}`);
189
+ fs.writeFileSync(yamlPath, legacyUpdated, 'utf8');
190
+ }
191
+ else {
192
+ fs.writeFileSync(yamlPath, updatedContent, 'utf8');
193
+ }
194
+ }
195
+ /**
196
+ * Validates that deploy data matches configuration for a specific skill.
72
197
  *
73
198
  * @param deployData - Deploy data from deploy.json
74
- * @param configVersion - Version from configuration
199
+ * @param selectedSkill - The selected skill to validate
75
200
  * @throws Error if deploy data is invalid or version mismatch
76
201
  */
77
- export function validateDeployData(deployData, configVersion) {
202
+ export function validateDeployData(deployData, selectedSkill) {
78
203
  if (!deployData) {
79
204
  throw new Error("No deploy.json found. Compilation may have failed.");
80
205
  }
81
- if (deployData.version !== configVersion) {
82
- throw new Error(`Version mismatch: config has ${configVersion}, deploy.json has ${deployData.version}`);
206
+ // Handle multi-skill format
207
+ if (deployData.skills && Array.isArray(deployData.skills)) {
208
+ const skillData = deployData.skills.find((s) => s.name === selectedSkill.name);
209
+ if (!skillData) {
210
+ throw new Error(`Skill "${selectedSkill.name}" not found in deploy.json. Compilation may have failed.`);
211
+ }
212
+ if (skillData.version !== selectedSkill.version) {
213
+ throw new Error(`Version mismatch for "${selectedSkill.name}": ` +
214
+ `config has ${selectedSkill.version}, deploy.json has ${skillData.version || 'undefined'}`);
215
+ }
216
+ return;
217
+ }
218
+ // Handle legacy single-skill format
219
+ if (deployData.version !== selectedSkill.version) {
220
+ throw new Error(`Version mismatch: config has ${selectedSkill.version}, deploy.json has ${deployData.version || 'undefined'}`);
221
+ }
222
+ }
223
+ /**
224
+ * Extracts the deploy data for a specific skill from the deploy.json.
225
+ *
226
+ * @param deployData - Deploy data from deploy.json
227
+ * @param skillName - Name of the skill to extract
228
+ * @returns The deploy data for the specific skill
229
+ */
230
+ export function getSkillDeployData(deployData, skillName) {
231
+ // Handle multi-skill format
232
+ if (deployData.skills && Array.isArray(deployData.skills)) {
233
+ const skillData = deployData.skills.find((s) => s.name === skillName);
234
+ if (!skillData) {
235
+ throw new Error(`Skill "${skillName}" not found in deploy.json`);
236
+ }
237
+ return skillData;
83
238
  }
239
+ // Handle legacy single-skill format
240
+ return deployData;
84
241
  }
@@ -131,6 +131,19 @@ export function createSandbox(options) {
131
131
  // };
132
132
  // Create console object (use custom console if provided, otherwise default)
133
133
  const consoleObj = customConsole || console;
134
+ // Create API service instances
135
+ const userService = new UserDataApiService(BASE_URLS.API, apiKey, agentId);
136
+ const productService = new ProductApiService(BASE_URLS.API, apiKey, agentId);
137
+ const dataService = new CustomDataApiService(BASE_URLS.API, apiKey, agentId);
138
+ const basketsService = new BasketApiService(BASE_URLS.API, apiKey, agentId);
139
+ const orderService = new OrderApiService(BASE_URLS.API, apiKey, agentId);
140
+ // Override User service methods if needed
141
+ // Example: Override the update method
142
+ // const originalUpdate = userService.update.bind(userService);
143
+ // userService.update = async (data: any) => {
144
+ // console.log('Custom update logic here');
145
+ // return await originalUpdate(data);
146
+ // };
134
147
  // Create comprehensive polyfills for browser/Node.js APIs
135
148
  const polyfills = {
136
149
  // AbortController polyfill
@@ -257,11 +270,11 @@ export function createSandbox(options) {
257
270
  null: null,
258
271
  Infinity: Infinity,
259
272
  NaN: NaN,
260
- User: new UserDataApiService(BASE_URLS.API, apiKey, agentId),
261
- Product: new ProductApiService(BASE_URLS.API, apiKey, agentId),
262
- CustomData: new CustomDataApiService(BASE_URLS.API, apiKey, agentId),
263
- Basket: new BasketApiService(BASE_URLS.API, apiKey, agentId),
264
- Order: new OrderApiService(BASE_URLS.API, apiKey, agentId),
273
+ User: userService,
274
+ Products: productService,
275
+ Data: dataService,
276
+ Baskets: basketsService,
277
+ Orders: orderService,
265
278
  // Environment variables function
266
279
  env: (key) => envVars[key]
267
280
  };