lua-cli 2.2.8-alpha.2 → 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 (193) 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 +33 -6
  7. package/dist/api/agent.api.service.js +27 -0
  8. package/dist/api/auth.api.service.d.ts +31 -2
  9. package/dist/api/auth.api.service.js +29 -0
  10. package/dist/api/basket.api.service.d.ts +53 -11
  11. package/dist/api/basket.api.service.js +63 -14
  12. package/dist/api/chat.api.service.d.ts +15 -3
  13. package/dist/api/chat.api.service.js +12 -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 +45 -9
  17. package/dist/api/custom.data.api.service.js +43 -9
  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 +34 -4
  21. package/dist/api/order.api.service.js +41 -3
  22. package/dist/api/products.api.service.d.ts +39 -9
  23. package/dist/api/products.api.service.js +43 -5
  24. package/dist/api/skills.api.service.d.ts +49 -2
  25. package/dist/api/skills.api.service.js +47 -1
  26. package/dist/api/tool.api.service.d.ts +39 -1
  27. package/dist/api/tool.api.service.js +38 -0
  28. package/dist/api/user.data.api.service.d.ts +23 -1
  29. package/dist/api/user.data.api.service.js +22 -0
  30. package/dist/api-exports.d.ts +236 -5
  31. package/dist/api-exports.js +264 -81
  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 -878
  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 +54 -3
  55. package/dist/common/basket.instance.js +56 -3
  56. package/dist/common/data.entry.instance.d.ts +25 -2
  57. package/dist/common/data.entry.instance.js +24 -0
  58. package/dist/common/http.client.d.ts +51 -1
  59. package/dist/common/http.client.js +50 -0
  60. package/dist/common/order.instance.d.ts +22 -0
  61. package/dist/common/order.instance.js +31 -4
  62. package/dist/common/product.instance.d.ts +22 -1
  63. package/dist/common/product.instance.js +24 -6
  64. package/dist/common/product.pagination.instance.d.ts +22 -2
  65. package/dist/common/product.pagination.instance.js +22 -1
  66. package/dist/common/product.search.instance.d.ts +13 -3
  67. package/dist/common/product.search.instance.js +12 -1
  68. package/dist/common/user.instance.d.ts +27 -3
  69. package/dist/common/user.instance.js +28 -7
  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 +56 -50
  83. package/dist/interfaces/admin.js +4 -0
  84. package/dist/interfaces/agent.d.ts +21 -0
  85. package/dist/interfaces/agent.js +4 -0
  86. package/dist/interfaces/baskets.d.ts +60 -0
  87. package/dist/interfaces/baskets.js +12 -0
  88. package/dist/interfaces/chat.d.ts +48 -4
  89. package/dist/interfaces/chat.js +4 -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 +49 -19
  95. package/dist/interfaces/custom.data.js +4 -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 +37 -0
  103. package/dist/interfaces/orders.js +12 -0
  104. package/dist/interfaces/product.d.ts +38 -10
  105. package/dist/interfaces/product.js +4 -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 -121
  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 -17
  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 -114
  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 +7 -4
  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 +5 -5
  170. package/template/package.json +1 -1
  171. package/template/src/index.ts +147 -207
  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 -26
  178. package/dist/common/config.d.ts +0 -5
  179. package/dist/common/config.js +0 -5
  180. package/dist/custom-data-api.d.ts +0 -72
  181. package/dist/custom-data-api.js +0 -174
  182. package/dist/product-api.d.ts +0 -189
  183. package/dist/product-api.js +0 -141
  184. package/dist/services/api.d.ts +0 -549
  185. package/dist/services/api.js +0 -596
  186. package/dist/skill.d.ts +0 -50
  187. package/dist/types.d.ts +0 -1
  188. package/dist/types.js +0 -2
  189. package/dist/user-data-api.d.ts +0 -39
  190. package/dist/user-data-api.js +0 -50
  191. package/template/API.md +0 -604
  192. package/template/DEVELOPER.md +0 -771
  193. package/template/lua.skill.yaml +0 -7
@@ -1,826 +1,75 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
1
+ /**
2
+ * Dev Command
3
+ * Orchestrates the development mode with live reloading, chat interface, and tool testing
4
+ */
5
+ import open from 'open';
4
6
  import { compileCommand } from './compile.js';
5
7
  import { checkApiKey, loadApiKey } from '../services/auth.js';
6
- import { ApiService } from '../services/api.js';
7
- import { executeTool, createBroadcastConsole, loadEnvironmentVariables } from '../utils/sandbox.js';
8
- import { readSkillConfig, updateSkillYamlPersona } from '../utils/files.js';
8
+ import { readSkillConfig } from '../utils/files.js';
9
9
  import { withErrorHandling, writeProgress, writeSuccess } from '../utils/cli.js';
10
- import keytar from 'keytar';
11
- import { watch } from 'fs';
12
- import { createServer } from 'http';
13
- import { WebSocketServer } from 'ws';
14
- import open from 'open';
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = path.dirname(__filename);
17
- // Constants for sandbox skill ID storage
18
- const SANDBOX_SERVICE = "lua-cli-sandbox";
19
- const SANDBOX_ACCOUNT = "sandbox-skill-id";
20
- // Function to get sandbox skill ID from secure storage
21
- async function getSandboxSkillId(skillName) {
22
- try {
23
- const account = skillName ? `${SANDBOX_ACCOUNT}_${skillName}` : SANDBOX_ACCOUNT;
24
- return await keytar.getPassword(SANDBOX_SERVICE, account);
25
- }
26
- catch (error) {
27
- return null;
28
- }
29
- }
30
- // Function to store sandbox skill ID in secure storage
31
- async function setSandboxSkillId(sandboxId, skillName) {
32
- try {
33
- const account = skillName ? `${SANDBOX_ACCOUNT}_${skillName}` : SANDBOX_ACCOUNT;
34
- await keytar.setPassword(SANDBOX_SERVICE, account, sandboxId);
35
- }
36
- catch (error) {
37
- // Ignore storage errors
38
- }
39
- }
40
- // Function to get all sandbox skill IDs for all skills
41
- async function getAllSandboxSkillIds() {
42
- const skillOverrides = [];
43
- try {
44
- // Read deploy.json to get all skills
45
- const deployData = readDeployJson();
46
- if (!deployData || !deployData.skills || !Array.isArray(deployData.skills)) {
47
- return skillOverrides;
48
- }
49
- // For each skill, get its sandbox ID
50
- for (const skill of deployData.skills) {
51
- if (skill.skillId && skill.name) {
52
- const sandboxId = await getSandboxSkillId(skill.name);
53
- if (sandboxId) {
54
- skillOverrides.push({
55
- skillId: skill.skillId,
56
- sandboxId: sandboxId
57
- });
58
- }
59
- }
60
- }
61
- }
62
- catch (error) {
63
- console.error('Error getting sandbox skill IDs:', error);
64
- }
65
- return skillOverrides;
66
- }
67
- // Function to send chat message to API
68
- export async function sendChatMessage(apiKey, agentId, skillId, sandboxId, message, persona) {
69
- try {
70
- // Get all sandbox skill IDs for skill override
71
- const allSkillOverrides = await getAllSandboxSkillIds();
72
- // If no skills found, fallback to the provided skillId and sandboxId
73
- const skillOverride = allSkillOverrides.length > 0 ? allSkillOverrides : [
74
- {
75
- skillId: skillId,
76
- sandboxId: sandboxId
77
- }
78
- ];
79
- const chatRequest = {
80
- messages: [
81
- {
82
- type: "text",
83
- text: message
84
- }
85
- ],
86
- navigate: true,
87
- skillOverride: skillOverride
88
- };
89
- // Add persona override if provided
90
- if (persona) {
91
- chatRequest.personaOverride = persona;
92
- }
93
- const response = await ApiService.Chat.sendMessage(agentId, chatRequest, apiKey);
94
- if (!response.success) {
95
- console.error(`❌ Chat API error: ${response.error?.message || 'Unknown error'}`);
96
- return null;
97
- }
98
- return response.data?.text || null;
99
- }
100
- catch (error) {
101
- console.error("❌ Error sending chat message:", error);
102
- return null;
103
- }
104
- }
105
- // Function to create and start web server for chat interface
106
- function createChatServer(apiKey, agentId, skillId, sandboxId, port = 3000) {
107
- const server = createServer((req, res) => {
108
- // Enable CORS
109
- res.setHeader('Access-Control-Allow-Origin', '*');
110
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
111
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
112
- if (req.method === 'OPTIONS') {
113
- res.writeHead(200);
114
- res.end();
115
- return;
116
- }
117
- if (req.method === 'POST' && req.url === '/chat') {
118
- let body = '';
119
- req.on('data', chunk => {
120
- body += chunk.toString();
121
- });
122
- req.on('end', async () => {
123
- try {
124
- const { message, persona } = JSON.parse(body);
125
- const response = await sendChatMessage(apiKey, agentId, skillId, sandboxId, message, persona);
126
- res.writeHead(200, { 'Content-Type': 'application/json' });
127
- res.end(JSON.stringify({
128
- success: response !== null,
129
- text: response || 'Error sending message',
130
- error: response === null ? 'Failed to send message' : null
131
- }));
132
- }
133
- catch (error) {
134
- res.writeHead(500, { 'Content-Type': 'application/json' });
135
- res.end(JSON.stringify({ success: false, error: 'Invalid request' }));
136
- }
137
- });
138
- return;
139
- }
140
- // Persona management endpoints
141
- if (req.method === 'GET' && req.url === '/persona') {
142
- try {
143
- const config = readSkillConfig();
144
- const persona = config?.agent?.persona || '';
145
- res.writeHead(200, { 'Content-Type': 'application/json' });
146
- res.end(JSON.stringify({
147
- success: true,
148
- persona: persona
149
- }));
150
- }
151
- catch (error) {
152
- res.writeHead(500, { 'Content-Type': 'application/json' });
153
- res.end(JSON.stringify({ success: false, error: 'Failed to read persona' }));
154
- }
155
- return;
156
- }
157
- if (req.method === 'GET' && req.url === '/api/config') {
158
- try {
159
- const config = readSkillConfig();
160
- res.writeHead(200, { 'Content-Type': 'application/json' });
161
- res.end(JSON.stringify({
162
- agent: {
163
- agentId: config?.agent?.agentId || '',
164
- orgId: config?.agent?.orgId || ''
165
- }
166
- }));
167
- }
168
- catch (error) {
169
- console.error('Error reading config:', error);
170
- res.writeHead(500, { 'Content-Type': 'application/json' });
171
- res.end(JSON.stringify({ error: 'Failed to read configuration' }));
172
- }
173
- return;
174
- }
175
- if (req.method === 'GET' && req.url === '/api/skills') {
176
- (async () => {
177
- try {
178
- const skillsData = [];
179
- // First try to read from deploy.json in the .lua directory
180
- const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
181
- if (fs.existsSync(deployPath)) {
182
- // Read from compiled deploy.json
183
- const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
184
- if (deployData.skills && Array.isArray(deployData.skills)) {
185
- // New format with skills array
186
- for (const skill of deployData.skills) {
187
- const sandboxId = await getSandboxSkillId(skill.name);
188
- skillsData.push({
189
- name: skill.name || 'Unnamed Skill',
190
- description: skill.description || 'A Lua skill for AI automation',
191
- context: skill.context || '',
192
- version: skill.version || '1.0.0',
193
- skillId: skill.skillId || '',
194
- sandboxId: sandboxId || '',
195
- tools: skill.tools ? skill.tools.map((tool) => tool.name) : []
196
- });
197
- }
198
- }
199
- else {
200
- // Legacy format fallback
201
- const toolNames = Object.keys(deployData.tools || {});
202
- const sandboxId = await getSandboxSkillId(); // Legacy uses no skill name
203
- skillsData.push({
204
- name: deployData.name || 'Current Skill',
205
- description: deployData.description || 'A Lua skill for AI automation',
206
- context: deployData.context || '',
207
- version: deployData.version || '1.0.0',
208
- skillId: deployData.skillId || '',
209
- sandboxId: sandboxId || '',
210
- tools: toolNames
211
- });
212
- }
213
- }
214
- else {
215
- // Fallback: Read from YAML and scan tools directory
216
- const config = readSkillConfig();
217
- const toolsDir = path.join(process.cwd(), 'src', 'tools');
218
- let toolNames = [];
219
- if (fs.existsSync(toolsDir)) {
220
- const toolFiles = fs.readdirSync(toolsDir)
221
- .filter((file) => file.endsWith('.ts'))
222
- .map((file) => file.replace('.ts', ''));
223
- toolNames = toolFiles;
224
- }
225
- if (config?.skills && Array.isArray(config.skills)) {
226
- // New format: multiple skills
227
- for (const skill of config.skills) {
228
- const sandboxId = await getSandboxSkillId(skill.name);
229
- skillsData.push({
230
- name: skill.name || 'Unnamed Skill',
231
- description: 'A Lua skill for AI automation (not compiled)',
232
- context: 'This skill has not been compiled yet. Run "lua compile" to see full context information.',
233
- version: skill.version || '1.0.0',
234
- skillId: skill.skillId || '',
235
- sandboxId: sandboxId || '',
236
- tools: toolNames // All tools for now since not compiled
237
- });
238
- }
239
- }
240
- else if (config?.skill) {
241
- // Legacy format: single skill
242
- const sandboxId = await getSandboxSkillId(); // Legacy uses no skill name
243
- skillsData.push({
244
- name: config.skill.name || 'Current Skill',
245
- description: 'A Lua skill for AI automation (not compiled)',
246
- context: 'This skill has not been compiled yet. Run "lua compile" to see full context information.',
247
- version: config.skill.version || '1.0.0',
248
- skillId: config.skill.skillId || '',
249
- sandboxId: sandboxId || '',
250
- tools: toolNames
251
- });
252
- }
253
- }
254
- res.writeHead(200, { 'Content-Type': 'application/json' });
255
- res.end(JSON.stringify({
256
- success: true,
257
- skills: skillsData
258
- }));
259
- }
260
- catch (error) {
261
- console.error('Error reading skills:', error);
262
- res.writeHead(500, { 'Content-Type': 'application/json' });
263
- res.end(JSON.stringify({
264
- success: false,
265
- error: 'Failed to read skills configuration'
266
- }));
267
- }
268
- })();
269
- return;
270
- }
271
- if (req.method === 'POST' && req.url === '/persona') {
272
- let body = '';
273
- req.on('data', chunk => {
274
- body += chunk.toString();
275
- });
276
- req.on('end', async () => {
277
- try {
278
- const { persona } = JSON.parse(body);
279
- // Update the YAML file
280
- updateSkillYamlPersona(persona);
281
- res.writeHead(200, { 'Content-Type': 'application/json' });
282
- res.end(JSON.stringify({
283
- success: true,
284
- message: 'Persona updated successfully'
285
- }));
286
- }
287
- catch (error) {
288
- res.writeHead(500, { 'Content-Type': 'application/json' });
289
- res.end(JSON.stringify({ success: false, error: 'Failed to update persona' }));
290
- }
291
- });
292
- return;
293
- }
294
- // Environment variables endpoints
295
- if (req.method === 'GET' && req.url === '/env') {
296
- try {
297
- const envPath = path.join(process.cwd(), '.env');
298
- let envContent = '';
299
- if (fs.existsSync(envPath)) {
300
- envContent = fs.readFileSync(envPath, 'utf8');
301
- }
302
- res.writeHead(200, { 'Content-Type': 'application/json' });
303
- res.end(JSON.stringify({
304
- success: true,
305
- content: envContent
306
- }));
307
- }
308
- catch (error) {
309
- res.writeHead(500, { 'Content-Type': 'application/json' });
310
- res.end(JSON.stringify({ success: false, error: 'Failed to read .env file' }));
311
- }
312
- return;
313
- }
314
- if (req.method === 'POST' && req.url === '/env') {
315
- let body = '';
316
- req.on('data', chunk => {
317
- body += chunk.toString();
318
- });
319
- req.on('end', async () => {
320
- try {
321
- const { content } = JSON.parse(body);
322
- const envPath = path.join(process.cwd(), '.env');
323
- // Write the .env file
324
- fs.writeFileSync(envPath, content || '', 'utf8');
325
- res.writeHead(200, { 'Content-Type': 'application/json' });
326
- res.end(JSON.stringify({
327
- success: true,
328
- message: 'Environment variables updated successfully'
329
- }));
330
- }
331
- catch (error) {
332
- res.writeHead(500, { 'Content-Type': 'application/json' });
333
- res.end(JSON.stringify({ success: false, error: 'Failed to update .env file' }));
334
- }
335
- });
336
- return;
337
- }
338
- // Tools endpoints
339
- if (req.method === 'GET' && req.url === '/tools') {
340
- try {
341
- // Read the deploy.json file to get tools
342
- const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
343
- if (fs.existsSync(deployPath)) {
344
- const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
345
- // Handle both new skills array format and legacy tools format
346
- let tools = deployData.tools || {};
347
- if (deployData.skills && Array.isArray(deployData.skills)) {
348
- // New format: merge all tools from all skills
349
- tools = {};
350
- deployData.skills.forEach((skill) => {
351
- if (skill.tools && Array.isArray(skill.tools)) {
352
- skill.tools.forEach((tool) => {
353
- tools[tool.name] = tool;
354
- });
355
- }
356
- });
357
- }
358
- res.writeHead(200, { 'Content-Type': 'application/json' });
359
- res.end(JSON.stringify({
360
- success: true,
361
- tools: tools
362
- }));
363
- }
364
- else {
365
- res.writeHead(404, { 'Content-Type': 'application/json' });
366
- res.end(JSON.stringify({ success: false, error: 'deploy.json not found' }));
367
- }
368
- }
369
- catch (error) {
370
- res.writeHead(500, { 'Content-Type': 'application/json' });
371
- res.end(JSON.stringify({ success: false, error: 'Failed to load tools' }));
372
- }
373
- return;
374
- }
375
- if (req.method === 'POST' && req.url === '/tools/test') {
376
- let body = '';
377
- req.on('data', chunk => {
378
- body += chunk.toString();
379
- });
380
- req.on('end', async () => {
381
- try {
382
- const { toolName, inputs } = JSON.parse(body);
383
- // Read deploy.json to get the tool's execute function
384
- const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
385
- if (!fs.existsSync(deployPath)) {
386
- res.writeHead(404, { 'Content-Type': 'application/json' });
387
- res.end(JSON.stringify({ success: false, error: 'deploy.json not found' }));
388
- return;
389
- }
390
- const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
391
- let tool = null;
392
- if (deployData.skills && Array.isArray(deployData.skills)) {
393
- // New format: search through skills array
394
- for (const skill of deployData.skills) {
395
- if (skill.tools && Array.isArray(skill.tools)) {
396
- tool = skill.tools.find((t) => t.name === toolName);
397
- if (tool)
398
- break;
399
- }
400
- }
401
- }
402
- else {
403
- // Legacy format: search in tools object
404
- if (deployData.tools && deployData.tools[toolName]) {
405
- tool = {
406
- name: toolName,
407
- ...deployData.tools[toolName]
408
- };
409
- }
410
- }
411
- if (!tool) {
412
- res.writeHead(404, { 'Content-Type': 'application/json' });
413
- res.end(JSON.stringify({ success: false, error: 'Tool not found' }));
414
- return;
415
- }
416
- // Load environment variables from all sources (.env + yaml)
417
- const envVars = loadEnvironmentVariables();
418
- // Decompress and execute the tool
419
- const { gunzipSync } = await import('zlib');
420
- const { Buffer } = await import('buffer');
421
- const { createRequire } = await import('module');
422
- const vm = await import('vm');
423
- function decompressCode(compressedCode) {
424
- const buffer = Buffer.from(compressedCode, 'base64');
425
- return gunzipSync(buffer).toString('utf8');
426
- }
427
- const toolCode = decompressCode(tool.execute);
428
- // Broadcast tool test start
429
- wss.clients.forEach((client) => {
430
- if (client.readyState === 1) {
431
- client.send(JSON.stringify({
432
- type: 'log',
433
- subType: 'info',
434
- message: `🧪 Testing tool: ${toolName}`,
435
- timestamp: new Date().toISOString(),
436
- id: Date.now().toString()
437
- }));
438
- }
439
- });
440
- // Create custom console that captures output and sends via WebSocket
441
- const customConsole = createBroadcastConsole((logData) => {
442
- console.log('Broadcasting tool log:', logData);
443
- wss.clients.forEach((client) => {
444
- if (client.readyState === 1) { // WebSocket.OPEN
445
- client.send(JSON.stringify(logData));
446
- }
447
- });
448
- });
449
- // Execute the tool using the common utility
450
- const result = await executeTool({
451
- toolCode,
452
- inputs,
453
- apiKey,
454
- agentId,
455
- customConsole
456
- });
457
- // Broadcast tool test completion
458
- wss.clients.forEach((client) => {
459
- if (client.readyState === 1) {
460
- client.send(JSON.stringify({
461
- type: 'log',
462
- subType: 'info',
463
- message: `✅ Tool test completed: ${toolName}`,
464
- timestamp: new Date().toISOString(),
465
- id: (Date.now() + 1).toString()
466
- }));
467
- }
468
- });
469
- res.writeHead(200, { 'Content-Type': 'application/json' });
470
- res.end(JSON.stringify({
471
- success: true,
472
- result: result
473
- }));
474
- }
475
- catch (error) {
476
- res.writeHead(500, { 'Content-Type': 'application/json' });
477
- res.end(JSON.stringify({
478
- success: false,
479
- error: error.message || 'Tool execution failed'
480
- }));
481
- }
482
- });
483
- return;
484
- }
485
- if (req.method === 'GET' && req.url === '/') {
486
- // Read the React app HTML template from dist directory
487
- const htmlTemplate = fs.readFileSync(path.join(__dirname, '..', 'web', 'index.html'), 'utf8');
488
- // Replace placeholders with actual values
489
- const html = htmlTemplate
490
- .replace('{{API_KEY}}', apiKey)
491
- .replace('{{AGENT_ID}}', agentId)
492
- .replace('{{SKILL_ID}}', skillId)
493
- .replace('{{SANDBOX_ID}}', sandboxId);
494
- res.writeHead(200, { 'Content-Type': 'text/html' });
495
- res.end(html);
496
- return;
497
- }
498
- // Serve the React app bundle
499
- if (req.method === 'GET' && req.url === '/app.js') {
500
- try {
501
- const appJs = fs.readFileSync(path.join(__dirname, '..', 'web', 'app.js'), 'utf8');
502
- res.writeHead(200, { 'Content-Type': 'application/javascript' });
503
- res.end(appJs);
504
- }
505
- catch (error) {
506
- res.writeHead(404, { 'Content-Type': 'text/plain' });
507
- res.end('React app not found. Please run the build process.');
508
- }
509
- return;
510
- }
511
- // Serve the CSS files
512
- if (req.method === 'GET' && req.url === '/app.css') {
513
- try {
514
- const appCss = fs.readFileSync(path.join(__dirname, '..', 'web', 'app.css'), 'utf8');
515
- res.writeHead(200, { 'Content-Type': 'text/css' });
516
- res.end(appCss);
517
- }
518
- catch (error) {
519
- res.writeHead(404, { 'Content-Type': 'text/plain' });
520
- res.end('CSS not found');
521
- }
522
- return;
523
- }
524
- if (req.method === 'GET' && req.url === '/tools-page.css') {
525
- try {
526
- const toolsPageCss = fs.readFileSync(path.join(__dirname, '..', 'web', 'tools-page.css'), 'utf8');
527
- res.writeHead(200, { 'Content-Type': 'text/css' });
528
- res.end(toolsPageCss);
529
- }
530
- catch (error) {
531
- res.writeHead(404, { 'Content-Type': 'text/plain' });
532
- res.end('Tools page CSS not found');
533
- }
534
- return;
535
- }
536
- res.writeHead(404);
537
- res.end('Not Found');
538
- });
539
- // Create WebSocket server for log streaming
540
- const wss = new WebSocketServer({ server });
541
- wss.on('connection', (ws) => {
542
- console.log('WebSocket client connected for log streaming');
543
- ws.on('close', () => {
544
- console.log('WebSocket client disconnected');
545
- });
546
- ws.on('error', (error) => {
547
- console.error('WebSocket error:', error);
548
- });
549
- // Send initial connection message
550
- ws.send(JSON.stringify({
551
- type: 'log',
552
- subType: 'info',
553
- message: 'Connected to CLI dev server',
554
- timestamp: new Date().toISOString()
555
- }));
556
- });
557
- server.listen(port, () => {
558
- console.log('Chat interface available at: http://localhost:' + port);
559
- console.log('Log streaming WebSocket available at: ws://localhost:' + port);
560
- });
561
- // Function to broadcast logs to all connected clients
562
- const broadcastLog = (message, subType = 'info') => {
563
- const logMessage = {
564
- type: 'log',
565
- subType,
566
- message,
567
- timestamp: new Date().toISOString(),
568
- id: Date.now().toString()
569
- };
570
- wss.clients.forEach((client) => {
571
- if (client.readyState === 1) { // WebSocket.OPEN
572
- client.send(JSON.stringify(logMessage));
573
- }
574
- });
575
- };
576
- return { server, wss, broadcastLog };
577
- }
578
- // Function to update existing sandbox version
579
- export async function updateDevVersion(apiKey, agentId, skillId, sandboxVersionId, versionData) {
580
- try {
581
- // console.log('Calling UPDATE endpoint for sandbox version');
582
- const response = await ApiService.Skill.updateDevSkill(apiKey, agentId, skillId, sandboxVersionId, versionData);
583
- if (response.success) {
584
- return {
585
- success: true,
586
- data: response.data
587
- };
588
- }
589
- else {
590
- return {
591
- success: false,
592
- error: {
593
- message: response.error?.message || 'Unknown error',
594
- error: response.error?.message || 'Unknown error',
595
- statusCode: response.error?.statusCode || 500
596
- }
597
- };
598
- }
599
- }
600
- catch (error) {
601
- console.error("Network error updating dev version:", error);
602
- return {
603
- success: false,
604
- error: {
605
- message: "Network error",
606
- error: error instanceof Error ? error.message : "Failed to connect to server",
607
- statusCode: 500
608
- }
609
- };
610
- }
611
- }
612
- // Function to create new sandbox version
613
- export async function pushDevVersion(apiKey, agentId, skillId, versionData) {
614
- try {
615
- // console.log(`🔗 Calling CREATE endpoint for sandbox version`);
616
- const response = await ApiService.Skill.pushDevSkill(apiKey, agentId, skillId, versionData);
617
- if (response.success) {
618
- return {
619
- success: true,
620
- data: response.data
621
- };
622
- }
623
- else {
624
- return {
625
- success: false,
626
- error: {
627
- message: response.error?.message || 'Unknown error',
628
- error: response.error?.message || 'Unknown error',
629
- statusCode: response.error?.statusCode || 500
630
- }
631
- };
632
- }
633
- }
634
- catch (error) {
635
- console.error("❌ Network error pushing dev version:", error);
636
- return {
637
- success: false,
638
- error: {
639
- message: "Network error",
640
- error: error instanceof Error ? error.message : "Failed to connect to server",
641
- statusCode: 500
642
- }
643
- };
644
- }
645
- }
646
- function readConfigVersion() {
647
- const config = readSkillConfig();
648
- return config?.skill?.version || null;
649
- }
650
- function readDeployJson() {
651
- const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
652
- if (!fs.existsSync(deployPath)) {
653
- return null;
654
- }
655
- const deployContent = fs.readFileSync(deployPath, 'utf8');
656
- return JSON.parse(deployContent);
657
- }
658
- // Function to push multiple skills to sandbox
659
- async function pushSkillsToSandbox(apiKey, agentId, deployData, isInitial = false) {
660
- const sandboxIds = {};
661
- if (!deployData.skills || !Array.isArray(deployData.skills)) {
662
- throw new Error("No skills found in deploy data");
663
- }
664
- for (const skill of deployData.skills) {
665
- if (!skill.skillId) {
666
- console.warn(`⚠️ Skipping skill ${skill.name} - no skillId found`);
667
- continue;
668
- }
669
- try {
670
- const success = await pushSingleSkillToSandbox(apiKey, agentId, skill.skillId, skill, isInitial);
671
- if (success) {
672
- // Get the sandbox ID for this skill (stored in .lua directory)
673
- const sandboxId = await getSandboxSkillId(skill.name);
674
- if (sandboxId) {
675
- sandboxIds[skill.name] = sandboxId;
676
- }
677
- }
678
- }
679
- catch (error) {
680
- console.error(`❌ Failed to push skill ${skill.name}:`, error);
681
- }
682
- }
683
- return sandboxIds;
684
- }
685
- // Function to push a single skill to sandbox (extracted for reuse)
686
- async function pushSingleSkillToSandbox(apiKey, agentId, skillId, skillData, isInitial = false) {
687
- const skillName = skillData.name || 'Unknown Skill';
688
- const sandboxSkillId = await getSandboxSkillId(skillName);
689
- // Load environment variables
690
- const envVars = loadEnvironmentVariables();
691
- // console.log(skillData);
692
- const payloadWithNameAndEnv = {
693
- ...skillData,
694
- name: skillName,
695
- env: envVars
696
- };
697
- if (sandboxSkillId) {
698
- // Try to update existing sandbox version
699
- if (!isInitial) {
700
- writeProgress("🔄 Updating existing sandbox version...");
701
- }
702
- const updateResult = await updateDevVersion(apiKey, agentId, skillId, sandboxSkillId, payloadWithNameAndEnv);
703
- if (updateResult.success && updateResult.data) {
704
- if (!isInitial) {
705
- // writeSuccess(`✅ Version ${updateResult.data.version} updated in sandbox successfully`);
706
- // writeSuccess(`🔑 Sandbox Skill ID: ${updateResult.data.skillId}`);
707
- }
708
- return true;
709
- }
710
- else if (updateResult.error) {
711
- writeProgress("⚠️ Failed to update existing sandbox, creating new one...");
712
- console.error(`❌ Sandbox update failed: ${updateResult.error.message}`);
713
- if (updateResult.error.error) {
714
- console.error(` Details: ${updateResult.error.error}`);
715
- }
716
- // If skill not found during update, give helpful guidance before trying to create
717
- if (updateResult.error.message.includes('Skill not found') || updateResult.error.statusCode === 400 || updateResult.error.statusCode === 404) {
718
- console.error('\n💡 The skill doesn\'t exist on the server.');
719
- console.error(' Please run "lua push" first to deploy your skill, then try "lua dev" again.\n');
720
- }
721
- // Fall through to create new sandbox
722
- }
723
- }
724
- // Create new sandbox version (either no existing ID or update failed)
725
- if (!isInitial) {
726
- writeProgress("🔄 Creating new sandbox version...");
727
- }
728
- const result = await pushDevVersion(apiKey, agentId, skillId, payloadWithNameAndEnv);
729
- if (result.success && result.data) {
730
- // Store the new sandbox skill ID
731
- await setSandboxSkillId(result.data.skillId, skillName);
732
- if (!isInitial) {
733
- writeSuccess(`✅ Version ${result.data.version} pushed to sandbox successfully`);
734
- writeSuccess(`🔑 Sandbox Skill ID: ${result.data.skillId}`);
735
- }
736
- return true;
737
- }
738
- else if (result.error) {
739
- console.error(`❌ Sandbox creation failed: ${result.error.message}`);
740
- if (result.error.error) {
741
- console.error(` Details: ${result.error.error}`);
742
- }
743
- if (result.error.statusCode) {
744
- console.error(` Status Code: ${result.error.statusCode}`);
745
- }
746
- // If skill not found, give helpful guidance
747
- if (result.error.message.includes('Skill not found') || result.error.statusCode === 400 || result.error.statusCode === 404) {
748
- console.error('\n💡 The skill hasn\'t been deployed to the server yet.');
749
- console.error(' Please run "lua push" first to deploy your skill, then try "lua dev" again.');
750
- }
751
- return false;
752
- }
753
- else {
754
- console.error("❌ Failed to push version to sandbox. Please try again.");
755
- return false;
756
- }
757
- }
10
+ import { pushSkillsToSandbox } from '../utils/dev-api.js';
11
+ import { createChatServer } from '../utils/dev-server.js';
12
+ import { createFileWatcher } from '../utils/dev-watcher.js';
13
+ import { readConfigVersion, readDeployJson, extractSkillId, validateConfig, validateDeployData, validateAgentConfig, } from '../utils/dev-helpers.js';
14
+ import { DEV_SERVER_PORT } from '../config/dev.constants.js';
15
+ /**
16
+ * Main dev command - starts development mode with live reloading.
17
+ *
18
+ * This command performs the following steps:
19
+ * 1. Validates configuration and authentication
20
+ * 2. Compiles the skill
21
+ * 3. Pushes all skills to sandbox environment
22
+ * 4. Starts web server with chat interface and tool testing
23
+ * 5. Opens browser to chat interface
24
+ * 6. Watches for file changes and auto-recompiles
25
+ *
26
+ * Features:
27
+ * - Live chat interface for testing skills
28
+ * - Real-time tool testing with WebSocket log streaming
29
+ * - Automatic recompilation and push on file changes
30
+ * - Environment variable management
31
+ * - Persona configuration
32
+ * - Multi-skill support
33
+ *
34
+ * @returns Promise that resolves when dev mode is stopped (never resolves normally)
35
+ */
758
36
  export async function devCommand() {
759
37
  return withErrorHandling(async () => {
760
- // Check if we're in a skill directory
38
+ // Step 1: Validate configuration
761
39
  const config = readSkillConfig();
762
- if (!config) {
763
- console.error("❌ No lua.skill.yaml found. Please run this command from a skill directory.");
764
- process.exit(1);
765
- }
766
- // Read version from config (handle both old and new formats)
767
- let version = config.skill?.version; // Legacy format
768
- if (!version && config.skills && config.skills.length > 0) {
769
- version = config.skills[0].version; // New format - use first skill's version
770
- }
771
- if (!version) {
772
- version = "1.0.0"; // Default version
773
- }
774
- // Load API key
40
+ validateConfig(config);
41
+ const version = readConfigVersion(config);
42
+ // Step 2: Authenticate
775
43
  const apiKey = await loadApiKey();
776
44
  if (!apiKey) {
777
45
  console.error("❌ No API key found. Please run 'lua auth configure' to set up your API key.");
778
46
  process.exit(1);
779
47
  }
780
- // Validate API key
781
48
  const userData = await checkApiKey(apiKey);
782
49
  writeProgress("✅ Authenticated");
783
- // Compile the skill first
50
+ // Step 3: Compile the skill
784
51
  writeProgress("🔄 Compiling skill...");
785
52
  await compileCommand();
786
- // Re-read config after compilation to get updated skillIds
53
+ // Step 4: Read and validate compiled output
787
54
  const updatedConfig = readSkillConfig();
788
55
  if (!updatedConfig) {
789
56
  console.error("❌ Failed to read updated skill configuration after compilation.");
790
57
  process.exit(1);
791
58
  }
792
- // Read deploy.json
793
59
  const deployData = readDeployJson();
794
- if (!deployData) {
795
- console.error("❌ No deploy.json found. Compilation may have failed.");
796
- process.exit(1);
797
- }
798
- // Verify deploy.json has skills
799
- if (!deployData.skills || !Array.isArray(deployData.skills) || deployData.skills.length === 0) {
800
- console.error("❌ No skills found in deploy.json. Compilation may have failed.");
801
- process.exit(1);
802
- }
803
- // Extract agentId from updated config
804
- const agentId = updatedConfig.agent?.agentId;
805
- if (!agentId) {
806
- console.error("❌ Missing agentId in skill configuration");
807
- process.exit(1);
808
- }
809
- // For multi-skill projects, we'll use the first skill's ID for sandbox
810
- let skillId = updatedConfig.skill?.skillId; // Legacy format
811
- if (!skillId && updatedConfig.skills && updatedConfig.skills.length > 0) {
812
- skillId = updatedConfig.skills[0].skillId; // New format - use first skill
813
- }
60
+ validateDeployData(deployData);
61
+ // Step 5: Extract configuration values
62
+ validateAgentConfig(updatedConfig);
63
+ const agentId = updatedConfig.agent.agentId;
64
+ const skillId = extractSkillId(updatedConfig);
814
65
  if (!skillId) {
815
66
  console.error("❌ No skillId found after compilation. Skill creation may have failed.");
816
67
  console.error("Available skills:", updatedConfig.skills);
817
68
  process.exit(1);
818
69
  }
819
- // Initial push to sandbox
70
+ // Step 6: Push skills to sandbox
820
71
  writeProgress("🔄 Pushing skills to sandbox...");
821
- console.log('deployData', deployData);
822
72
  const sandboxIds = await pushSkillsToSandbox(apiKey, agentId, deployData, true);
823
- // console.log(sandboxIds);
824
73
  if (Object.keys(sandboxIds).length === 0) {
825
74
  console.error("❌ Failed to push any skills to sandbox. Cannot start development mode.");
826
75
  process.exit(1);
@@ -833,100 +82,31 @@ export async function devCommand() {
833
82
  console.error("❌ No sandbox skill ID found. Cannot start chat interface.");
834
83
  process.exit(1);
835
84
  }
836
- // Start web server for chat interface
837
- const chatPort = 3000;
838
- const { server, wss, broadcastLog } = createChatServer(apiKey, agentId, skillId, sandboxSkillId, chatPort);
839
- // Open browser to chat interface
85
+ // Step 7: Start web server for chat interface
86
+ const { server, wss, broadcastLog } = createChatServer(apiKey, agentId, skillId, sandboxSkillId, DEV_SERVER_PORT);
87
+ // Step 8: Open browser to chat interface
840
88
  try {
841
- await open(`http://localhost:${chatPort}`);
89
+ await open(`http://localhost:${DEV_SERVER_PORT}`);
842
90
  writeSuccess("🌐 Chat interface opened in your browser");
843
91
  broadcastLog("🌐 Chat interface opened in your browser", 'info');
844
92
  }
845
93
  catch (error) {
846
- writeSuccess(`🌐 Chat interface available at: http://localhost:${chatPort}`);
847
- broadcastLog(`🌐 Chat interface available at: http://localhost:${chatPort}`, 'info');
94
+ writeSuccess(`🌐 Chat interface available at: http://localhost:${DEV_SERVER_PORT}`);
95
+ broadcastLog(`🌐 Chat interface available at: http://localhost:${DEV_SERVER_PORT}`, 'info');
848
96
  }
849
- // Start file watching
97
+ // Step 9: Start file watching
850
98
  writeSuccess("🔍 Watching for file changes... (Press Ctrl+C to stop)");
851
99
  broadcastLog("🔍 Watching for file changes... (Press Ctrl+C to stop)", 'info');
852
- let isCompiling = false;
853
- let pendingCompile = false;
854
- let compileTimeout = null;
855
- const watcher = watch(process.cwd(), { recursive: true }, async (eventType, filename) => {
856
- // Ignore changes in .lua directory and other build artifacts
857
- if (filename && (filename.includes('.lua/') ||
858
- filename.includes('.lua\\') ||
859
- filename.includes('node_modules/') ||
860
- filename.includes('node_modules\\') ||
861
- filename.includes('.git/') ||
862
- filename.includes('.git\\') ||
863
- filename.endsWith('.log') ||
864
- filename.endsWith('.tmp') ||
865
- filename.endsWith('.js') ||
866
- filename.endsWith('.map') ||
867
- filename.includes('dist/') ||
868
- filename.includes('dist\\'))) {
869
- return;
870
- }
871
- // Debounce rapid changes
872
- if (isCompiling) {
873
- pendingCompile = true;
874
- return;
875
- }
876
- // Clear any existing timeout
877
- if (compileTimeout) {
878
- clearTimeout(compileTimeout);
879
- }
880
- // Debounce file changes with a 500ms delay
881
- compileTimeout = setTimeout(async () => {
882
- if (isCompiling) {
883
- return;
884
- }
885
- isCompiling = true;
886
- pendingCompile = false;
887
- try {
888
- writeProgress(`🔄 File changed: ${filename} - Compiling and pushing...`);
889
- broadcastLog(`🔄 File changed: ${filename} - Compiling and pushing...`, 'info');
890
- // Compile the skill
891
- await compileCommand();
892
- broadcastLog("✅ Compilation completed", 'info');
893
- // Read updated deploy.json
894
- const updatedDeployData = readDeployJson();
895
- if (!updatedDeployData) {
896
- writeProgress("❌ Compilation failed, skipping push");
897
- return;
898
- }
899
- // Verify updated deploy.json has skills
900
- if (!updatedDeployData.skills || !Array.isArray(updatedDeployData.skills) || updatedDeployData.skills.length === 0) {
901
- writeProgress("❌ No skills found in updated deploy.json, skipping push");
902
- return;
903
- }
904
- // Push all skills to sandbox
905
- const updatedSandboxIds = await pushSkillsToSandbox(apiKey, agentId, updatedDeployData, false);
906
- if (Object.keys(updatedSandboxIds).length === 0) {
907
- writeProgress("❌ Failed to push any skills to sandbox, will retry on next change");
908
- broadcastLog("❌ Failed to push skills to sandbox, will retry on next change", 'error');
909
- }
910
- else {
911
- // writeProgress(`✅ Successfully pushed ${Object.keys(updatedSandboxIds).length} skills to sandbox`);
912
- broadcastLog(`✅ Successfully pushed ${Object.keys(updatedSandboxIds).length} skills to sandbox`, 'info');
913
- }
914
- }
915
- catch (error) {
916
- writeProgress(`❌ Error during auto-compile: ${error instanceof Error ? error.message : 'Unknown error'}`);
917
- }
918
- finally {
919
- isCompiling = false;
920
- }
921
- }, 500); // 500ms debounce delay
100
+ const { watcher, cleanup } = createFileWatcher({
101
+ apiKey,
102
+ agentId,
103
+ readDeployJson,
104
+ broadcastLog,
922
105
  });
923
- // Handle graceful shutdown
106
+ // Step 10: Handle graceful shutdown
924
107
  process.on('SIGINT', () => {
925
108
  writeSuccess("\n🛑 Stopping file watcher...");
926
- if (compileTimeout) {
927
- clearTimeout(compileTimeout);
928
- }
929
- watcher.close();
109
+ cleanup();
930
110
  process.exit(0);
931
111
  });
932
112
  // Keep the process alive