lua-cli 2.2.8-alpha.1 → 2.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.
Files changed (191) hide show
  1. package/API_REFERENCE.md +1408 -0
  2. package/CLI_REFERENCE.md +818 -0
  3. package/GETTING_STARTED.md +1040 -0
  4. package/README.md +738 -424
  5. package/TEMPLATE_GUIDE.md +1398 -0
  6. package/dist/api/agent.api.service.d.ts +45 -0
  7. package/dist/api/agent.api.service.js +57 -0
  8. package/dist/api/auth.api.service.d.ts +48 -0
  9. package/dist/api/auth.api.service.js +54 -0
  10. package/dist/api/basket.api.service.d.ts +85 -0
  11. package/dist/api/basket.api.service.js +164 -0
  12. package/dist/api/chat.api.service.d.ts +21 -0
  13. package/dist/api/chat.api.service.js +24 -0
  14. package/dist/api/credentials.d.ts +24 -0
  15. package/dist/api/credentials.js +46 -0
  16. package/dist/api/custom.data.api.service.d.ts +69 -0
  17. package/dist/api/custom.data.api.service.js +125 -0
  18. package/dist/api/lazy-instances.d.ts +49 -0
  19. package/dist/api/lazy-instances.js +95 -0
  20. package/dist/api/order.api.service.d.ts +53 -0
  21. package/dist/api/order.api.service.js +95 -0
  22. package/dist/api/products.api.service.d.ts +66 -0
  23. package/dist/api/products.api.service.js +112 -0
  24. package/dist/api/skills.api.service.d.ts +77 -0
  25. package/dist/api/skills.api.service.js +88 -0
  26. package/dist/api/tool.api.service.d.ts +52 -0
  27. package/dist/api/tool.api.service.js +73 -0
  28. package/dist/api/user.data.api.service.d.ts +33 -0
  29. package/dist/api/user.data.api.service.js +59 -0
  30. package/dist/api-exports.d.ts +271 -0
  31. package/dist/api-exports.js +372 -0
  32. package/dist/cli/command-definitions.d.ts +30 -0
  33. package/dist/cli/command-definitions.js +71 -0
  34. package/dist/commands/agents.d.ts +20 -0
  35. package/dist/commands/agents.js +24 -2
  36. package/dist/commands/apiKey.d.ts +23 -0
  37. package/dist/commands/apiKey.js +23 -0
  38. package/dist/commands/compile.d.ts +24 -0
  39. package/dist/commands/compile.js +67 -759
  40. package/dist/commands/configure.d.ts +24 -0
  41. package/dist/commands/configure.js +31 -96
  42. package/dist/commands/deploy.d.ts +31 -19
  43. package/dist/commands/deploy.js +45 -74
  44. package/dist/commands/destroy.d.ts +27 -0
  45. package/dist/commands/destroy.js +27 -1
  46. package/dist/commands/dev.d.ts +25 -62
  47. package/dist/commands/dev.js +58 -873
  48. package/dist/commands/init.d.ts +27 -0
  49. package/dist/commands/init.js +98 -260
  50. package/dist/commands/push.d.ts +24 -21
  51. package/dist/commands/push.js +39 -92
  52. package/dist/commands/test.d.ts +26 -0
  53. package/dist/commands/test.js +41 -188
  54. package/dist/common/basket.instance.d.ts +78 -0
  55. package/dist/common/basket.instance.js +132 -0
  56. package/dist/common/data.entry.instance.d.ts +39 -0
  57. package/dist/common/data.entry.instance.js +76 -0
  58. package/dist/common/http.client.d.ts +64 -0
  59. package/dist/common/http.client.js +133 -0
  60. package/dist/common/order.instance.d.ts +40 -0
  61. package/dist/common/order.instance.js +79 -0
  62. package/dist/common/product.instance.d.ts +33 -0
  63. package/dist/common/product.instance.js +63 -0
  64. package/dist/common/product.pagination.instance.d.ts +43 -0
  65. package/dist/common/product.pagination.instance.js +74 -0
  66. package/dist/common/product.search.instance.d.ts +22 -0
  67. package/dist/common/product.search.instance.js +40 -0
  68. package/dist/common/user.instance.d.ts +41 -0
  69. package/dist/common/user.instance.js +84 -0
  70. package/dist/config/auth.constants.d.ts +11 -0
  71. package/dist/config/auth.constants.js +11 -0
  72. package/dist/config/compile.constants.d.ts +67 -0
  73. package/dist/config/compile.constants.js +99 -0
  74. package/dist/config/constants.d.ts +5 -0
  75. package/dist/config/constants.js +5 -0
  76. package/dist/config/dev.constants.d.ts +65 -0
  77. package/dist/config/dev.constants.js +79 -0
  78. package/dist/config/init.constants.d.ts +23 -0
  79. package/dist/config/init.constants.js +41 -0
  80. package/dist/index.d.ts +19 -3
  81. package/dist/index.js +28 -44
  82. package/dist/interfaces/admin.d.ts +101 -0
  83. package/dist/interfaces/admin.js +5 -0
  84. package/dist/interfaces/agent.d.ts +107 -0
  85. package/dist/interfaces/agent.js +5 -0
  86. package/dist/interfaces/baskets.d.ts +135 -0
  87. package/dist/interfaces/baskets.js +19 -0
  88. package/dist/interfaces/chat.d.ts +61 -0
  89. package/dist/interfaces/chat.js +5 -0
  90. package/dist/interfaces/common.d.ts +62 -0
  91. package/dist/interfaces/common.js +8 -0
  92. package/dist/interfaces/compile.d.ts +11 -0
  93. package/dist/interfaces/compile.js +4 -0
  94. package/dist/interfaces/custom.data.d.ts +82 -0
  95. package/dist/interfaces/custom.data.js +5 -0
  96. package/dist/interfaces/deploy.d.ts +29 -0
  97. package/dist/interfaces/deploy.js +4 -0
  98. package/dist/interfaces/dev.d.ts +53 -0
  99. package/dist/interfaces/dev.js +5 -0
  100. package/dist/interfaces/init.d.ts +60 -0
  101. package/dist/interfaces/init.js +4 -0
  102. package/dist/interfaces/orders.d.ts +91 -0
  103. package/dist/interfaces/orders.js +19 -0
  104. package/dist/interfaces/product.d.ts +65 -0
  105. package/dist/interfaces/product.js +5 -0
  106. package/dist/interfaces/push.d.ts +26 -0
  107. package/dist/interfaces/push.js +4 -0
  108. package/dist/interfaces/test.d.ts +36 -0
  109. package/dist/interfaces/test.js +4 -0
  110. package/dist/services/auth.d.ts +54 -99
  111. package/dist/services/auth.js +76 -12
  112. package/dist/types/api-contracts.d.ts +211 -0
  113. package/dist/types/api-contracts.js +8 -0
  114. package/dist/types/compile.types.d.ts +76 -0
  115. package/dist/types/compile.types.js +4 -0
  116. package/dist/types/index.d.ts +23 -85
  117. package/dist/types/index.js +25 -14
  118. package/dist/types/skill.d.ts +142 -0
  119. package/dist/{skill.js → types/skill.js} +66 -19
  120. package/dist/types/tool-validation.d.ts +34 -0
  121. package/dist/types/tool-validation.js +42 -0
  122. package/dist/utils/auth-flows.d.ts +26 -0
  123. package/dist/utils/auth-flows.js +141 -0
  124. package/dist/utils/bundling.d.ts +36 -0
  125. package/dist/utils/bundling.js +137 -0
  126. package/dist/utils/compile.d.ts +37 -0
  127. package/dist/utils/compile.js +242 -0
  128. package/dist/utils/deploy-api.d.ts +26 -0
  129. package/dist/utils/deploy-api.js +53 -0
  130. package/dist/utils/deploy-helpers.d.ts +46 -0
  131. package/dist/utils/deploy-helpers.js +86 -0
  132. package/dist/utils/deployment.d.ts +25 -0
  133. package/dist/utils/deployment.js +161 -0
  134. package/dist/utils/dev-api.d.ts +61 -0
  135. package/dist/utils/dev-api.js +262 -0
  136. package/dist/utils/dev-helpers.d.ts +46 -0
  137. package/dist/utils/dev-helpers.js +83 -0
  138. package/dist/utils/dev-server.d.ts +24 -0
  139. package/dist/utils/dev-server.js +555 -0
  140. package/dist/utils/dev-watcher.d.ts +31 -0
  141. package/dist/utils/dev-watcher.js +110 -0
  142. package/dist/utils/files.js +0 -5
  143. package/dist/utils/init-agent.d.ts +34 -0
  144. package/dist/utils/init-agent.js +129 -0
  145. package/dist/utils/init-helpers.d.ts +41 -0
  146. package/dist/utils/init-helpers.js +73 -0
  147. package/dist/utils/init-prompts.d.ts +47 -0
  148. package/dist/utils/init-prompts.js +168 -0
  149. package/dist/utils/push-api.d.ts +15 -0
  150. package/dist/utils/push-api.js +48 -0
  151. package/dist/utils/push-helpers.d.ts +38 -0
  152. package/dist/utils/push-helpers.js +84 -0
  153. package/dist/utils/sandbox-storage.d.ts +27 -0
  154. package/dist/utils/sandbox-storage.js +71 -0
  155. package/dist/utils/sandbox.js +78 -118
  156. package/dist/utils/skill-management.d.ts +14 -0
  157. package/dist/utils/skill-management.js +148 -0
  158. package/dist/utils/test-helpers.d.ts +40 -0
  159. package/dist/utils/test-helpers.js +92 -0
  160. package/dist/utils/test-prompts.d.ts +23 -0
  161. package/dist/utils/test-prompts.js +186 -0
  162. package/dist/utils/tool-detection.d.ts +18 -0
  163. package/dist/utils/tool-detection.js +110 -0
  164. package/dist/web/app.css +14 -9
  165. package/package.json +11 -12
  166. package/template/QUICKSTART.md +299 -144
  167. package/template/README.md +928 -349
  168. package/template/TOOL_EXAMPLES.md +655 -0
  169. package/template/package-lock.json +3781 -0
  170. package/template/package.json +1 -1
  171. package/template/src/index.ts +81 -40
  172. package/template/src/tools/BasketTool.ts +128 -0
  173. package/template/src/tools/CustomDataTool.ts +7 -13
  174. package/template/src/tools/OrderTool.ts +54 -0
  175. package/template/src/tools/PaymentTool.ts +1 -1
  176. package/template/src/tools/ProductsTool.ts +56 -118
  177. package/template/src/tools/UserDataTool.ts +4 -27
  178. package/dist/custom-data-api.d.ts +0 -72
  179. package/dist/custom-data-api.js +0 -174
  180. package/dist/product-api.d.ts +0 -197
  181. package/dist/product-api.js +0 -152
  182. package/dist/services/api.d.ts +0 -569
  183. package/dist/services/api.js +0 -625
  184. package/dist/skill.d.ts +0 -50
  185. package/dist/types.d.ts +0 -1
  186. package/dist/types.js +0 -2
  187. package/dist/user-data-api.d.ts +0 -39
  188. package/dist/user-data-api.js +0 -50
  189. package/template/API.md +0 -604
  190. package/template/DEVELOPER.md +0 -771
  191. package/template/lua.skill.yaml +0 -16
@@ -0,0 +1,555 @@
1
+ /**
2
+ * Dev Server Utilities
3
+ * Handles HTTP server, WebSocket connections, and all HTTP endpoints for dev mode
4
+ */
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { createServer } from 'http';
9
+ import { WebSocketServer } from 'ws';
10
+ import { readSkillConfig, updateSkillYamlPersona } from './files.js';
11
+ import { executeTool, createBroadcastConsole, loadEnvironmentVariables } from './sandbox.js';
12
+ import { getSandboxSkillId } from './sandbox-storage.js';
13
+ import { sendChatMessage } from './dev-api.js';
14
+ import { CORS_HEADERS, HTTP_STATUS, DEV_DEFAULTS } from '../config/dev.constants.js';
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+ /**
18
+ * Reads the deploy.json file from .lua directory
19
+ *
20
+ * @returns Parsed deploy data or null if not found
21
+ */
22
+ function readDeployJson() {
23
+ const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
24
+ if (!fs.existsSync(deployPath)) {
25
+ return null;
26
+ }
27
+ const deployContent = fs.readFileSync(deployPath, 'utf8');
28
+ return JSON.parse(deployContent);
29
+ }
30
+ /**
31
+ * Creates and starts the dev web server with chat interface and tool testing.
32
+ *
33
+ * @param apiKey - User's API key
34
+ * @param agentId - Agent ID
35
+ * @param skillId - Skill ID
36
+ * @param sandboxId - Sandbox skill ID
37
+ * @param port - Port to run server on
38
+ * @returns Server instance with WebSocket and broadcast function
39
+ */
40
+ export function createChatServer(apiKey, agentId, skillId, sandboxId, port) {
41
+ const deployData = readDeployJson();
42
+ const server = createServer((req, res) => {
43
+ // Enable CORS
44
+ Object.entries(CORS_HEADERS).forEach(([key, value]) => {
45
+ res.setHeader(key, value);
46
+ });
47
+ if (req.method === 'OPTIONS') {
48
+ res.writeHead(HTTP_STATUS.OK);
49
+ res.end();
50
+ return;
51
+ }
52
+ // Route handlers
53
+ if (req.method === 'POST' && req.url === '/chat') {
54
+ handleChatEndpoint(req, res, apiKey, agentId, skillId, sandboxId, deployData);
55
+ return;
56
+ }
57
+ if (req.method === 'GET' && req.url === '/persona') {
58
+ handleGetPersonaEndpoint(req, res);
59
+ return;
60
+ }
61
+ if (req.method === 'POST' && req.url === '/persona') {
62
+ handlePostPersonaEndpoint(req, res);
63
+ return;
64
+ }
65
+ if (req.method === 'GET' && req.url === '/api/config') {
66
+ handleGetConfigEndpoint(req, res);
67
+ return;
68
+ }
69
+ if (req.method === 'GET' && req.url === '/api/skills') {
70
+ handleGetSkillsEndpoint(req, res);
71
+ return;
72
+ }
73
+ if (req.method === 'GET' && req.url === '/env') {
74
+ handleGetEnvEndpoint(req, res);
75
+ return;
76
+ }
77
+ if (req.method === 'POST' && req.url === '/env') {
78
+ handlePostEnvEndpoint(req, res);
79
+ return;
80
+ }
81
+ if (req.method === 'GET' && req.url === '/tools') {
82
+ handleGetToolsEndpoint(req, res);
83
+ return;
84
+ }
85
+ if (req.method === 'POST' && req.url === '/tools/test') {
86
+ handleToolTestEndpoint(req, res, apiKey, agentId, wss);
87
+ return;
88
+ }
89
+ if (req.method === 'GET' && req.url === '/') {
90
+ handleIndexEndpoint(req, res, apiKey, agentId, skillId, sandboxId);
91
+ return;
92
+ }
93
+ if (req.method === 'GET' && req.url === '/app.js') {
94
+ handleStaticFile(req, res, 'app.js', 'application/javascript');
95
+ return;
96
+ }
97
+ if (req.method === 'GET' && req.url === '/app.css') {
98
+ handleStaticFile(req, res, 'app.css', 'text/css');
99
+ return;
100
+ }
101
+ if (req.method === 'GET' && req.url === '/tools-page.css') {
102
+ handleStaticFile(req, res, 'tools-page.css', 'text/css');
103
+ return;
104
+ }
105
+ res.writeHead(HTTP_STATUS.NOT_FOUND);
106
+ res.end('Not Found');
107
+ });
108
+ // Create WebSocket server for log streaming
109
+ const wss = new WebSocketServer({ server });
110
+ wss.on('connection', (ws) => {
111
+ ws.on('close', () => {
112
+ // Client disconnected
113
+ });
114
+ ws.on('error', (error) => {
115
+ console.error('WebSocket error:', error);
116
+ });
117
+ // Send initial connection message
118
+ ws.send(JSON.stringify({
119
+ type: 'log',
120
+ subType: 'info',
121
+ message: 'Connected to CLI dev server',
122
+ timestamp: new Date().toISOString()
123
+ }));
124
+ });
125
+ server.listen(port, () => {
126
+ // Server started
127
+ });
128
+ // Function to broadcast logs to all connected clients
129
+ const broadcastLog = (message, subType = 'info') => {
130
+ const logMessage = {
131
+ type: 'log',
132
+ subType,
133
+ message,
134
+ timestamp: new Date().toISOString(),
135
+ id: Date.now().toString()
136
+ };
137
+ wss.clients.forEach((client) => {
138
+ if (client.readyState === 1) { // WebSocket.OPEN
139
+ client.send(JSON.stringify(logMessage));
140
+ }
141
+ });
142
+ };
143
+ return { server, wss, broadcastLog };
144
+ }
145
+ // ============================================================================
146
+ // ENDPOINT HANDLERS
147
+ // ============================================================================
148
+ /**
149
+ * Handles POST /chat - Send chat messages
150
+ */
151
+ function handleChatEndpoint(req, res, apiKey, agentId, skillId, sandboxId, deployData) {
152
+ let body = '';
153
+ req.on('data', (chunk) => {
154
+ body += chunk.toString();
155
+ });
156
+ req.on('end', async () => {
157
+ try {
158
+ const { message, persona } = JSON.parse(body);
159
+ const response = await sendChatMessage(apiKey, agentId, skillId, sandboxId, message, persona, deployData);
160
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
161
+ res.end(JSON.stringify({
162
+ success: response !== null,
163
+ text: response || 'Error sending message',
164
+ error: response === null ? 'Failed to send message' : null
165
+ }));
166
+ }
167
+ catch (error) {
168
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
169
+ res.end(JSON.stringify({ success: false, error: 'Invalid request' }));
170
+ }
171
+ });
172
+ }
173
+ /**
174
+ * Handles GET /persona - Get current persona
175
+ */
176
+ function handleGetPersonaEndpoint(req, res) {
177
+ try {
178
+ const config = readSkillConfig();
179
+ const persona = config?.agent?.persona || '';
180
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
181
+ res.end(JSON.stringify({
182
+ success: true,
183
+ persona: persona
184
+ }));
185
+ }
186
+ catch (error) {
187
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
188
+ res.end(JSON.stringify({ success: false, error: 'Failed to read persona' }));
189
+ }
190
+ }
191
+ /**
192
+ * Handles POST /persona - Update persona
193
+ */
194
+ function handlePostPersonaEndpoint(req, res) {
195
+ let body = '';
196
+ req.on('data', (chunk) => {
197
+ body += chunk.toString();
198
+ });
199
+ req.on('end', async () => {
200
+ try {
201
+ const { persona } = JSON.parse(body);
202
+ updateSkillYamlPersona(persona);
203
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
204
+ res.end(JSON.stringify({
205
+ success: true,
206
+ message: 'Persona updated successfully'
207
+ }));
208
+ }
209
+ catch (error) {
210
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
211
+ res.end(JSON.stringify({ success: false, error: 'Failed to update persona' }));
212
+ }
213
+ });
214
+ }
215
+ /**
216
+ * Handles GET /api/config - Get agent configuration
217
+ */
218
+ function handleGetConfigEndpoint(req, res) {
219
+ try {
220
+ const config = readSkillConfig();
221
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
222
+ res.end(JSON.stringify({
223
+ agent: {
224
+ agentId: config?.agent?.agentId || '',
225
+ orgId: config?.agent?.orgId || ''
226
+ }
227
+ }));
228
+ }
229
+ catch (error) {
230
+ console.error('Error reading config:', error);
231
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
232
+ res.end(JSON.stringify({ error: 'Failed to read configuration' }));
233
+ }
234
+ }
235
+ /**
236
+ * Handles GET /api/skills - Get all skills information
237
+ */
238
+ function handleGetSkillsEndpoint(req, res) {
239
+ (async () => {
240
+ try {
241
+ const skillsData = [];
242
+ // First try to read from deploy.json in the .lua directory
243
+ const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
244
+ if (fs.existsSync(deployPath)) {
245
+ // Read from compiled deploy.json
246
+ const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
247
+ if (deployData.skills && Array.isArray(deployData.skills)) {
248
+ // New format with skills array
249
+ for (const skill of deployData.skills) {
250
+ const sandboxId = await getSandboxSkillId(skill.name);
251
+ skillsData.push({
252
+ name: skill.name || DEV_DEFAULTS.SKILL_NAME,
253
+ description: skill.description || DEV_DEFAULTS.DESCRIPTION,
254
+ context: skill.context || '',
255
+ version: skill.version || DEV_DEFAULTS.VERSION,
256
+ skillId: skill.skillId || '',
257
+ sandboxId: sandboxId || '',
258
+ tools: skill.tools ? skill.tools.map((tool) => tool.name) : []
259
+ });
260
+ }
261
+ }
262
+ else {
263
+ // Legacy format fallback
264
+ const toolNames = Object.keys(deployData.tools || {});
265
+ const sandboxId = await getSandboxSkillId(); // Legacy uses no skill name
266
+ skillsData.push({
267
+ name: deployData.name || 'Current Skill',
268
+ description: deployData.description || DEV_DEFAULTS.DESCRIPTION,
269
+ context: deployData.context || '',
270
+ version: deployData.version || DEV_DEFAULTS.VERSION,
271
+ skillId: deployData.skillId || '',
272
+ sandboxId: sandboxId || '',
273
+ tools: toolNames
274
+ });
275
+ }
276
+ }
277
+ else {
278
+ // Fallback: Read from YAML and scan tools directory
279
+ const config = readSkillConfig();
280
+ const toolsDir = path.join(process.cwd(), 'src', 'tools');
281
+ let toolNames = [];
282
+ if (fs.existsSync(toolsDir)) {
283
+ const toolFiles = fs.readdirSync(toolsDir)
284
+ .filter((file) => file.endsWith('.ts'))
285
+ .map((file) => file.replace('.ts', ''));
286
+ toolNames = toolFiles;
287
+ }
288
+ if (config?.skills && Array.isArray(config.skills)) {
289
+ // New format: multiple skills
290
+ for (const skill of config.skills) {
291
+ const sandboxId = await getSandboxSkillId(skill.name);
292
+ skillsData.push({
293
+ name: skill.name || DEV_DEFAULTS.SKILL_NAME,
294
+ description: DEV_DEFAULTS.NOT_COMPILED_DESCRIPTION,
295
+ context: DEV_DEFAULTS.NOT_COMPILED_CONTEXT,
296
+ version: skill.version || DEV_DEFAULTS.VERSION,
297
+ skillId: skill.skillId || '',
298
+ sandboxId: sandboxId || '',
299
+ tools: toolNames // All tools for now since not compiled
300
+ });
301
+ }
302
+ }
303
+ else if (config?.skill) {
304
+ // Legacy format: single skill
305
+ const sandboxId = await getSandboxSkillId(); // Legacy uses no skill name
306
+ skillsData.push({
307
+ name: config.skill.name || 'Current Skill',
308
+ description: DEV_DEFAULTS.NOT_COMPILED_DESCRIPTION,
309
+ context: DEV_DEFAULTS.NOT_COMPILED_CONTEXT,
310
+ version: config.skill.version || DEV_DEFAULTS.VERSION,
311
+ skillId: config.skill.skillId || '',
312
+ sandboxId: sandboxId || '',
313
+ tools: toolNames
314
+ });
315
+ }
316
+ }
317
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
318
+ res.end(JSON.stringify({
319
+ success: true,
320
+ skills: skillsData
321
+ }));
322
+ }
323
+ catch (error) {
324
+ console.error('Error reading skills:', error);
325
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
326
+ res.end(JSON.stringify({
327
+ success: false,
328
+ error: 'Failed to read skills configuration'
329
+ }));
330
+ }
331
+ })();
332
+ }
333
+ /**
334
+ * Handles GET /env - Get environment variables
335
+ */
336
+ function handleGetEnvEndpoint(req, res) {
337
+ try {
338
+ const envPath = path.join(process.cwd(), '.env');
339
+ let envContent = '';
340
+ if (fs.existsSync(envPath)) {
341
+ envContent = fs.readFileSync(envPath, 'utf8');
342
+ }
343
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
344
+ res.end(JSON.stringify({
345
+ success: true,
346
+ content: envContent
347
+ }));
348
+ }
349
+ catch (error) {
350
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
351
+ res.end(JSON.stringify({ success: false, error: 'Failed to read .env file' }));
352
+ }
353
+ }
354
+ /**
355
+ * Handles POST /env - Update environment variables
356
+ */
357
+ function handlePostEnvEndpoint(req, res) {
358
+ let body = '';
359
+ req.on('data', (chunk) => {
360
+ body += chunk.toString();
361
+ });
362
+ req.on('end', async () => {
363
+ try {
364
+ const { content } = JSON.parse(body);
365
+ const envPath = path.join(process.cwd(), '.env');
366
+ // Write the .env file
367
+ fs.writeFileSync(envPath, content || '', 'utf8');
368
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
369
+ res.end(JSON.stringify({
370
+ success: true,
371
+ message: 'Environment variables updated successfully'
372
+ }));
373
+ }
374
+ catch (error) {
375
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
376
+ res.end(JSON.stringify({ success: false, error: 'Failed to update .env file' }));
377
+ }
378
+ });
379
+ }
380
+ /**
381
+ * Handles GET /tools - Get all tools
382
+ */
383
+ function handleGetToolsEndpoint(req, res) {
384
+ try {
385
+ // Read the deploy.json file to get tools
386
+ const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
387
+ if (fs.existsSync(deployPath)) {
388
+ const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
389
+ // Handle both new skills array format and legacy tools format
390
+ let tools = deployData.tools || {};
391
+ if (deployData.skills && Array.isArray(deployData.skills)) {
392
+ // New format: merge all tools from all skills
393
+ tools = {};
394
+ deployData.skills.forEach((skill) => {
395
+ if (skill.tools && Array.isArray(skill.tools)) {
396
+ skill.tools.forEach((tool) => {
397
+ tools[tool.name] = tool;
398
+ });
399
+ }
400
+ });
401
+ }
402
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
403
+ res.end(JSON.stringify({
404
+ success: true,
405
+ tools: tools
406
+ }));
407
+ }
408
+ else {
409
+ res.writeHead(HTTP_STATUS.NOT_FOUND, { 'Content-Type': 'application/json' });
410
+ res.end(JSON.stringify({ success: false, error: 'deploy.json not found' }));
411
+ }
412
+ }
413
+ catch (error) {
414
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
415
+ res.end(JSON.stringify({ success: false, error: 'Failed to load tools' }));
416
+ }
417
+ }
418
+ /**
419
+ * Handles POST /tools/test - Test a tool
420
+ */
421
+ function handleToolTestEndpoint(req, res, apiKey, agentId, wss) {
422
+ let body = '';
423
+ req.on('data', (chunk) => {
424
+ body += chunk.toString();
425
+ });
426
+ req.on('end', async () => {
427
+ try {
428
+ const { toolName, inputs } = JSON.parse(body);
429
+ // Read deploy.json to get the tool's execute function
430
+ const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
431
+ if (!fs.existsSync(deployPath)) {
432
+ res.writeHead(HTTP_STATUS.NOT_FOUND, { 'Content-Type': 'application/json' });
433
+ res.end(JSON.stringify({ success: false, error: 'deploy.json not found' }));
434
+ return;
435
+ }
436
+ const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
437
+ let tool = null;
438
+ if (deployData.skills && Array.isArray(deployData.skills)) {
439
+ // New format: search through skills array
440
+ for (const skill of deployData.skills) {
441
+ if (skill.tools && Array.isArray(skill.tools)) {
442
+ tool = skill.tools.find((t) => t.name === toolName);
443
+ if (tool)
444
+ break;
445
+ }
446
+ }
447
+ }
448
+ else {
449
+ // Legacy format: search in tools object
450
+ if (deployData.tools && deployData.tools[toolName]) {
451
+ tool = {
452
+ name: toolName,
453
+ ...deployData.tools[toolName]
454
+ };
455
+ }
456
+ }
457
+ if (!tool) {
458
+ res.writeHead(HTTP_STATUS.NOT_FOUND, { 'Content-Type': 'application/json' });
459
+ res.end(JSON.stringify({ success: false, error: 'Tool not found' }));
460
+ return;
461
+ }
462
+ // Load environment variables from all sources (.env + yaml)
463
+ const envVars = loadEnvironmentVariables();
464
+ // Decompress and execute the tool
465
+ const { gunzipSync } = await import('zlib');
466
+ const { Buffer } = await import('buffer');
467
+ function decompressCode(compressedCode) {
468
+ const buffer = Buffer.from(compressedCode, 'base64');
469
+ return gunzipSync(buffer).toString('utf8');
470
+ }
471
+ const toolCode = decompressCode(tool.execute);
472
+ // Broadcast tool test start
473
+ wss.clients.forEach((client) => {
474
+ if (client.readyState === 1) {
475
+ client.send(JSON.stringify({
476
+ type: 'log',
477
+ subType: 'info',
478
+ message: `🧪 Testing tool: ${toolName}`,
479
+ timestamp: new Date().toISOString(),
480
+ id: Date.now().toString()
481
+ }));
482
+ }
483
+ });
484
+ // Create custom console that captures output and sends via WebSocket
485
+ const customConsole = createBroadcastConsole((logData) => {
486
+ wss.clients.forEach((client) => {
487
+ if (client.readyState === 1) { // WebSocket.OPEN
488
+ client.send(JSON.stringify(logData));
489
+ }
490
+ });
491
+ });
492
+ // Execute the tool using the common utility
493
+ const result = await executeTool({
494
+ toolCode,
495
+ inputs,
496
+ apiKey,
497
+ agentId,
498
+ customConsole
499
+ });
500
+ // Broadcast tool test completion
501
+ wss.clients.forEach((client) => {
502
+ if (client.readyState === 1) {
503
+ client.send(JSON.stringify({
504
+ type: 'log',
505
+ subType: 'info',
506
+ message: `✅ Tool test completed: ${toolName}`,
507
+ timestamp: new Date().toISOString(),
508
+ id: (Date.now() + 1).toString()
509
+ }));
510
+ }
511
+ });
512
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json' });
513
+ res.end(JSON.stringify({
514
+ success: true,
515
+ result: result
516
+ }));
517
+ }
518
+ catch (error) {
519
+ res.writeHead(HTTP_STATUS.SERVER_ERROR, { 'Content-Type': 'application/json' });
520
+ res.end(JSON.stringify({
521
+ success: false,
522
+ error: error.message || 'Tool execution failed'
523
+ }));
524
+ }
525
+ });
526
+ }
527
+ /**
528
+ * Handles GET / - Serve main HTML page
529
+ */
530
+ function handleIndexEndpoint(req, res, apiKey, agentId, skillId, sandboxId) {
531
+ // Read the React app HTML template from dist directory
532
+ const htmlTemplate = fs.readFileSync(path.join(__dirname, '..', 'web', 'index.html'), 'utf8');
533
+ // Replace placeholders with actual values
534
+ const html = htmlTemplate
535
+ .replace('{{API_KEY}}', apiKey)
536
+ .replace('{{AGENT_ID}}', agentId)
537
+ .replace('{{SKILL_ID}}', skillId)
538
+ .replace('{{SANDBOX_ID}}', sandboxId);
539
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'text/html' });
540
+ res.end(html);
541
+ }
542
+ /**
543
+ * Handles static file serving (JS, CSS)
544
+ */
545
+ function handleStaticFile(req, res, filename, contentType) {
546
+ try {
547
+ const content = fs.readFileSync(path.join(__dirname, '..', 'web', filename), 'utf8');
548
+ res.writeHead(HTTP_STATUS.OK, { 'Content-Type': contentType });
549
+ res.end(content);
550
+ }
551
+ catch (error) {
552
+ res.writeHead(HTTP_STATUS.NOT_FOUND, { 'Content-Type': 'text/plain' });
553
+ res.end(`${filename} not found`);
554
+ }
555
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Dev File Watcher Utilities
3
+ * Handles file system watching and automatic recompilation
4
+ */
5
+ /**
6
+ * Configuration for file watcher
7
+ */
8
+ export interface FileWatcherConfig {
9
+ apiKey: string;
10
+ agentId: string;
11
+ readDeployJson: () => any | null;
12
+ broadcastLog: (message: string, subType?: 'info' | 'warn' | 'error' | 'debug') => void;
13
+ }
14
+ /**
15
+ * Checks if a file should be ignored during watching.
16
+ *
17
+ * @param filename - The filename to check
18
+ * @returns True if file should be ignored, false otherwise
19
+ */
20
+ export declare function shouldIgnoreFile(filename: string | null): boolean;
21
+ /**
22
+ * Creates and starts a file watcher for development mode.
23
+ * Watches for file changes and automatically recompiles and pushes to sandbox.
24
+ *
25
+ * @param config - File watcher configuration
26
+ * @returns The watcher instance
27
+ */
28
+ export declare function createFileWatcher(config: FileWatcherConfig): {
29
+ watcher: import("fs").FSWatcher;
30
+ cleanup: () => void;
31
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Dev File Watcher Utilities
3
+ * Handles file system watching and automatic recompilation
4
+ */
5
+ import { watch } from 'fs';
6
+ import { compileCommand } from '../commands/compile.js';
7
+ import { pushSkillsToSandbox } from './dev-api.js';
8
+ import { writeProgress } from './cli.js';
9
+ import { FILE_WATCH_CONFIG, WATCH_IGNORE_PATTERNS, WATCH_IGNORE_EXTENSIONS, } from '../config/dev.constants.js';
10
+ /**
11
+ * Checks if a file should be ignored during watching.
12
+ *
13
+ * @param filename - The filename to check
14
+ * @returns True if file should be ignored, false otherwise
15
+ */
16
+ export function shouldIgnoreFile(filename) {
17
+ if (!filename) {
18
+ return true;
19
+ }
20
+ // Check ignore patterns
21
+ for (const pattern of WATCH_IGNORE_PATTERNS) {
22
+ if (filename.includes(pattern)) {
23
+ return true;
24
+ }
25
+ }
26
+ // Check ignore extensions
27
+ for (const ext of WATCH_IGNORE_EXTENSIONS) {
28
+ if (filename.endsWith(ext)) {
29
+ return true;
30
+ }
31
+ }
32
+ return false;
33
+ }
34
+ /**
35
+ * Creates and starts a file watcher for development mode.
36
+ * Watches for file changes and automatically recompiles and pushes to sandbox.
37
+ *
38
+ * @param config - File watcher configuration
39
+ * @returns The watcher instance
40
+ */
41
+ export function createFileWatcher(config) {
42
+ const { apiKey, agentId, readDeployJson, broadcastLog } = config;
43
+ let isCompiling = false;
44
+ let pendingCompile = false;
45
+ let compileTimeout = null;
46
+ const watcher = watch(process.cwd(), { recursive: FILE_WATCH_CONFIG.RECURSIVE }, async (eventType, filename) => {
47
+ // Ignore changes in build artifacts and dependencies
48
+ if (shouldIgnoreFile(filename)) {
49
+ return;
50
+ }
51
+ // Debounce rapid changes
52
+ if (isCompiling) {
53
+ pendingCompile = true;
54
+ return;
55
+ }
56
+ // Clear any existing timeout
57
+ if (compileTimeout) {
58
+ clearTimeout(compileTimeout);
59
+ }
60
+ // Debounce file changes with configured delay
61
+ compileTimeout = setTimeout(async () => {
62
+ if (isCompiling) {
63
+ return;
64
+ }
65
+ isCompiling = true;
66
+ pendingCompile = false;
67
+ try {
68
+ writeProgress(`🔄 File changed: ${filename} - Compiling and pushing...`);
69
+ broadcastLog(`🔄 File changed: ${filename} - Compiling and pushing...`, 'info');
70
+ // Compile the skill
71
+ await compileCommand();
72
+ broadcastLog("✅ Compilation completed", 'info');
73
+ // Read updated deploy.json
74
+ const updatedDeployData = readDeployJson();
75
+ if (!updatedDeployData) {
76
+ writeProgress("❌ Compilation failed, skipping push");
77
+ return;
78
+ }
79
+ // Verify updated deploy.json has skills
80
+ if (!updatedDeployData.skills || !Array.isArray(updatedDeployData.skills) || updatedDeployData.skills.length === 0) {
81
+ writeProgress("❌ No skills found in updated deploy.json, skipping push");
82
+ return;
83
+ }
84
+ // Push all skills to sandbox
85
+ const updatedSandboxIds = await pushSkillsToSandbox(apiKey, agentId, updatedDeployData, false);
86
+ if (Object.keys(updatedSandboxIds).length === 0) {
87
+ writeProgress("❌ Failed to push any skills to sandbox, will retry on next change");
88
+ broadcastLog("❌ Failed to push skills to sandbox, will retry on next change", 'error');
89
+ }
90
+ else {
91
+ broadcastLog(`✅ Successfully pushed ${Object.keys(updatedSandboxIds).length} skills to sandbox`, 'info');
92
+ }
93
+ }
94
+ catch (error) {
95
+ writeProgress(`❌ Error during auto-compile: ${error instanceof Error ? error.message : 'Unknown error'}`);
96
+ }
97
+ finally {
98
+ isCompiling = false;
99
+ }
100
+ }, FILE_WATCH_CONFIG.DEBOUNCE_DELAY);
101
+ });
102
+ // Handle graceful shutdown
103
+ const cleanup = () => {
104
+ if (compileTimeout) {
105
+ clearTimeout(compileTimeout);
106
+ }
107
+ watcher.close();
108
+ };
109
+ return { watcher, cleanup };
110
+ }