lua-cli 1.3.0-alpha.1 → 1.3.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 (42) hide show
  1. package/CHANGELOG.md +8 -3
  2. package/README.md +168 -14
  3. package/dist/commands/agents.js +5 -9
  4. package/dist/commands/compile.js +252 -70
  5. package/dist/commands/deploy-new.d.ts +0 -20
  6. package/dist/commands/deploy-new.js +130 -128
  7. package/dist/commands/deploy.js +15 -43
  8. package/dist/commands/dev.d.ts +63 -0
  9. package/dist/commands/dev.js +656 -0
  10. package/dist/commands/index.d.ts +1 -0
  11. package/dist/commands/index.js +1 -0
  12. package/dist/commands/init.js +230 -42
  13. package/dist/commands/push.js +25 -36
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +7 -1
  16. package/dist/services/api.d.ts +195 -0
  17. package/dist/services/api.js +209 -0
  18. package/dist/services/auth.d.ts +82 -0
  19. package/dist/services/auth.js +101 -51
  20. package/dist/user-data-api.d.ts +52 -0
  21. package/dist/user-data-api.js +151 -0
  22. package/dist/utils/files.d.ts +4 -1
  23. package/dist/utils/files.js +62 -16
  24. package/dist/web/app.css +1050 -0
  25. package/dist/web/app.js +79 -0
  26. package/dist/web/tools-page.css +377 -0
  27. package/package.json +17 -4
  28. package/template/package-lock.json +32 -3
  29. package/template/package.json +3 -1
  30. package/template/{index.ts → src/index.ts} +9 -3
  31. package/template/src/tools/UserPreferencesTool.ts +73 -0
  32. package/template/tools/UserPreferencesTool.ts +73 -0
  33. package/template/tsconfig.json +1 -1
  34. package/template/.lua/deploy.json +0 -148
  35. /package/template/{services → src/services}/ApiService.ts +0 -0
  36. /package/template/{services → src/services}/GetWeather.ts +0 -0
  37. /package/template/{services → src/services}/MathService.ts +0 -0
  38. /package/template/{tools → src/tools}/AdvancedMathTool.ts +0 -0
  39. /package/template/{tools → src/tools}/CalculatorTool.ts +0 -0
  40. /package/template/{tools → src/tools}/CreatePostTool.ts +0 -0
  41. /package/template/{tools → src/tools}/GetUserDataTool.ts +0 -0
  42. /package/template/{tools → src/tools}/GetWeatherTool.ts +0 -0
@@ -0,0 +1,656 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import inquirer from 'inquirer';
5
+ import { compileCommand } from './compile.js';
6
+ import { checkApiKey, loadApiKey } from '../services/auth.js';
7
+ import { ApiService } from '../services/api.js';
8
+ import { readSkillConfig, updateSkillYamlPersona } from '../utils/files.js';
9
+ import { withErrorHandling, clearPromptLines, 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() {
22
+ try {
23
+ return await keytar.getPassword(SANDBOX_SERVICE, SANDBOX_ACCOUNT);
24
+ }
25
+ catch (error) {
26
+ return null;
27
+ }
28
+ }
29
+ // Function to store sandbox skill ID in secure storage
30
+ async function setSandboxSkillId(skillId) {
31
+ try {
32
+ await keytar.setPassword(SANDBOX_SERVICE, SANDBOX_ACCOUNT, skillId);
33
+ }
34
+ catch (error) {
35
+ // Ignore storage errors
36
+ }
37
+ }
38
+ // Function to send chat message to API
39
+ export async function sendChatMessage(apiKey, agentId, skillId, sandboxId, message, persona) {
40
+ try {
41
+ const chatRequest = {
42
+ messages: [
43
+ {
44
+ type: "text",
45
+ text: message
46
+ }
47
+ ],
48
+ navigate: true,
49
+ skillOverride: [
50
+ {
51
+ skillId: skillId,
52
+ sandboxId: sandboxId
53
+ }
54
+ ]
55
+ };
56
+ // Add persona override if provided
57
+ if (persona) {
58
+ chatRequest.personaOverride = persona;
59
+ }
60
+ const response = await ApiService.Chat.sendMessage(agentId, chatRequest, apiKey);
61
+ if (!response.success) {
62
+ console.error(`❌ Chat API error: ${response.error?.message || 'Unknown error'}`);
63
+ return null;
64
+ }
65
+ return response.data?.text || null;
66
+ }
67
+ catch (error) {
68
+ console.error("❌ Error sending chat message:", error);
69
+ return null;
70
+ }
71
+ }
72
+ // Function to create and start web server for chat interface
73
+ function createChatServer(apiKey, agentId, skillId, sandboxId, port = 3000) {
74
+ const server = createServer((req, res) => {
75
+ // Enable CORS
76
+ res.setHeader('Access-Control-Allow-Origin', '*');
77
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
78
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
79
+ if (req.method === 'OPTIONS') {
80
+ res.writeHead(200);
81
+ res.end();
82
+ return;
83
+ }
84
+ if (req.method === 'POST' && req.url === '/chat') {
85
+ let body = '';
86
+ req.on('data', chunk => {
87
+ body += chunk.toString();
88
+ });
89
+ req.on('end', async () => {
90
+ try {
91
+ const { message, persona } = JSON.parse(body);
92
+ const response = await sendChatMessage(apiKey, agentId, skillId, sandboxId, message, persona);
93
+ res.writeHead(200, { 'Content-Type': 'application/json' });
94
+ res.end(JSON.stringify({
95
+ success: response !== null,
96
+ text: response || 'Error sending message',
97
+ error: response === null ? 'Failed to send message' : null
98
+ }));
99
+ }
100
+ catch (error) {
101
+ res.writeHead(500, { 'Content-Type': 'application/json' });
102
+ res.end(JSON.stringify({ success: false, error: 'Invalid request' }));
103
+ }
104
+ });
105
+ return;
106
+ }
107
+ // Persona management endpoints
108
+ if (req.method === 'GET' && req.url === '/persona') {
109
+ try {
110
+ const config = readSkillConfig();
111
+ const persona = config?.agent?.persona || '';
112
+ res.writeHead(200, { 'Content-Type': 'application/json' });
113
+ res.end(JSON.stringify({
114
+ success: true,
115
+ persona: persona
116
+ }));
117
+ }
118
+ catch (error) {
119
+ res.writeHead(500, { 'Content-Type': 'application/json' });
120
+ res.end(JSON.stringify({ success: false, error: 'Failed to read persona' }));
121
+ }
122
+ return;
123
+ }
124
+ if (req.method === 'POST' && req.url === '/persona') {
125
+ let body = '';
126
+ req.on('data', chunk => {
127
+ body += chunk.toString();
128
+ });
129
+ req.on('end', async () => {
130
+ try {
131
+ const { persona } = JSON.parse(body);
132
+ // Update the YAML file
133
+ updateSkillYamlPersona(persona);
134
+ res.writeHead(200, { 'Content-Type': 'application/json' });
135
+ res.end(JSON.stringify({
136
+ success: true,
137
+ message: 'Persona updated successfully'
138
+ }));
139
+ }
140
+ catch (error) {
141
+ res.writeHead(500, { 'Content-Type': 'application/json' });
142
+ res.end(JSON.stringify({ success: false, error: 'Failed to update persona' }));
143
+ }
144
+ });
145
+ return;
146
+ }
147
+ // Tools endpoints
148
+ if (req.method === 'GET' && req.url === '/tools') {
149
+ try {
150
+ // Read the deploy.json file to get tools
151
+ const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
152
+ if (fs.existsSync(deployPath)) {
153
+ const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
154
+ res.writeHead(200, { 'Content-Type': 'application/json' });
155
+ res.end(JSON.stringify({
156
+ success: true,
157
+ tools: deployData.tools || []
158
+ }));
159
+ }
160
+ else {
161
+ res.writeHead(404, { 'Content-Type': 'application/json' });
162
+ res.end(JSON.stringify({ success: false, error: 'deploy.json not found' }));
163
+ }
164
+ }
165
+ catch (error) {
166
+ res.writeHead(500, { 'Content-Type': 'application/json' });
167
+ res.end(JSON.stringify({ success: false, error: 'Failed to load tools' }));
168
+ }
169
+ return;
170
+ }
171
+ if (req.method === 'POST' && req.url === '/tools/test') {
172
+ let body = '';
173
+ req.on('data', chunk => {
174
+ body += chunk.toString();
175
+ });
176
+ req.on('end', async () => {
177
+ try {
178
+ const { toolName, inputs } = JSON.parse(body);
179
+ // Read deploy.json to get the tool's execute function
180
+ const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
181
+ if (!fs.existsSync(deployPath)) {
182
+ res.writeHead(404, { 'Content-Type': 'application/json' });
183
+ res.end(JSON.stringify({ success: false, error: 'deploy.json not found' }));
184
+ return;
185
+ }
186
+ const deployData = JSON.parse(fs.readFileSync(deployPath, 'utf8'));
187
+ const tool = deployData.tools.find((t) => t.name === toolName);
188
+ if (!tool) {
189
+ res.writeHead(404, { 'Content-Type': 'application/json' });
190
+ res.end(JSON.stringify({ success: false, error: 'Tool not found' }));
191
+ return;
192
+ }
193
+ // Decompress and execute the tool
194
+ const { gunzipSync } = await import('zlib');
195
+ const { Buffer } = await import('buffer');
196
+ const { createRequire } = await import('module');
197
+ const vm = await import('vm');
198
+ function decompressCode(compressedCode) {
199
+ const buffer = Buffer.from(compressedCode, 'base64');
200
+ return gunzipSync(buffer).toString('utf8');
201
+ }
202
+ const toolCode = decompressCode(tool.execute);
203
+ // Create a CommonJS context for execution
204
+ const require = createRequire(process.cwd() + '/package.json');
205
+ // Create a sandbox context
206
+ const sandbox = {
207
+ require,
208
+ console,
209
+ Buffer,
210
+ setTimeout,
211
+ setInterval,
212
+ clearTimeout,
213
+ clearInterval,
214
+ process,
215
+ global: globalThis,
216
+ __dirname: process.cwd(),
217
+ __filename: path.join(process.cwd(), 'index.ts'),
218
+ module: { exports: {} },
219
+ exports: {},
220
+ // Web APIs
221
+ fetch: globalThis.fetch,
222
+ URLSearchParams: globalThis.URLSearchParams,
223
+ URL: globalThis.URL,
224
+ Headers: globalThis.Headers,
225
+ Request: globalThis.Request,
226
+ Response: globalThis.Response,
227
+ // Additional globals
228
+ Object,
229
+ Array,
230
+ String,
231
+ Number,
232
+ Boolean,
233
+ Date,
234
+ Math,
235
+ JSON,
236
+ Error,
237
+ TypeError,
238
+ ReferenceError,
239
+ SyntaxError,
240
+ globalThis,
241
+ undefined: undefined,
242
+ null: null,
243
+ Infinity: Infinity,
244
+ NaN: NaN
245
+ };
246
+ // Create the CommonJS wrapper code
247
+ const commonJsWrapper = `
248
+ const executeFunction = ${toolCode};
249
+
250
+ // Export the function for testing
251
+ module.exports = async (input) => {
252
+ return await executeFunction(input);
253
+ };
254
+ `;
255
+ // Execute the code in the sandbox
256
+ const context = vm.createContext(sandbox);
257
+ vm.runInContext(commonJsWrapper, context);
258
+ // Get the exported function and execute it
259
+ const executeFunction = context.module.exports;
260
+ const result = await executeFunction(inputs);
261
+ res.writeHead(200, { 'Content-Type': 'application/json' });
262
+ res.end(JSON.stringify({
263
+ success: true,
264
+ result: result
265
+ }));
266
+ }
267
+ catch (error) {
268
+ res.writeHead(500, { 'Content-Type': 'application/json' });
269
+ res.end(JSON.stringify({
270
+ success: false,
271
+ error: error.message || 'Tool execution failed'
272
+ }));
273
+ }
274
+ });
275
+ return;
276
+ }
277
+ if (req.method === 'GET' && req.url === '/') {
278
+ // Read the React app HTML template
279
+ const htmlTemplate = fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'web', 'index.html'), 'utf8');
280
+ // Replace placeholders with actual values
281
+ const html = htmlTemplate
282
+ .replace('{{API_KEY}}', apiKey)
283
+ .replace('{{AGENT_ID}}', agentId)
284
+ .replace('{{SKILL_ID}}', skillId)
285
+ .replace('{{SANDBOX_ID}}', sandboxId);
286
+ res.writeHead(200, { 'Content-Type': 'text/html' });
287
+ res.end(html);
288
+ return;
289
+ }
290
+ // Serve the React app bundle
291
+ if (req.method === 'GET' && req.url === '/app.js') {
292
+ try {
293
+ const appJs = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'web', 'app.js'), 'utf8');
294
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
295
+ res.end(appJs);
296
+ }
297
+ catch (error) {
298
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
299
+ res.end('React app not found. Please run the build process.');
300
+ }
301
+ return;
302
+ }
303
+ // Serve the CSS files
304
+ if (req.method === 'GET' && req.url === '/app.css') {
305
+ try {
306
+ const appCss = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'web', 'app.css'), 'utf8');
307
+ res.writeHead(200, { 'Content-Type': 'text/css' });
308
+ res.end(appCss);
309
+ }
310
+ catch (error) {
311
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
312
+ res.end('CSS not found');
313
+ }
314
+ return;
315
+ }
316
+ if (req.method === 'GET' && req.url === '/tools-page.css') {
317
+ try {
318
+ const toolsPageCss = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'web', 'tools-page.css'), 'utf8');
319
+ res.writeHead(200, { 'Content-Type': 'text/css' });
320
+ res.end(toolsPageCss);
321
+ }
322
+ catch (error) {
323
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
324
+ res.end('Tools page CSS not found');
325
+ }
326
+ return;
327
+ }
328
+ res.writeHead(404);
329
+ res.end('Not Found');
330
+ });
331
+ // Create WebSocket server for live reload
332
+ const wss = new WebSocketServer({ server });
333
+ wss.on('connection', (ws) => {
334
+ console.log('WebSocket client connected for live reload');
335
+ ws.on('close', () => {
336
+ console.log('WebSocket client disconnected');
337
+ });
338
+ ws.on('error', (error) => {
339
+ console.error('WebSocket error:', error);
340
+ });
341
+ });
342
+ server.listen(port, () => {
343
+ console.log('Chat interface available at: http://localhost:' + port);
344
+ console.log('Live reload WebSocket available at: ws://localhost:' + port);
345
+ });
346
+ return { server, wss };
347
+ }
348
+ // Function to update existing sandbox version
349
+ export async function updateDevVersion(apiKey, agentId, skillId, sandboxVersionId, versionData) {
350
+ try {
351
+ const url = 'http://localhost:3022/developer/skills/' + agentId + '/' + skillId + '/version/sandbox/' + sandboxVersionId;
352
+ console.log('Calling UPDATE endpoint: ' + url);
353
+ const response = await ApiService.Skill.updateDevSkill(apiKey, agentId, skillId, sandboxVersionId, versionData);
354
+ if (response.success) {
355
+ return {
356
+ success: true,
357
+ data: response.data
358
+ };
359
+ }
360
+ else {
361
+ return {
362
+ success: false,
363
+ error: {
364
+ message: response.error?.message || 'Unknown error',
365
+ error: response.error?.message || 'Unknown error',
366
+ statusCode: response.error?.statusCode || 500
367
+ }
368
+ };
369
+ }
370
+ }
371
+ catch (error) {
372
+ console.error("Network error updating dev version:", error);
373
+ return {
374
+ success: false,
375
+ error: {
376
+ message: "Network error",
377
+ error: error instanceof Error ? error.message : "Failed to connect to server",
378
+ statusCode: 500
379
+ }
380
+ };
381
+ }
382
+ }
383
+ // Function to create new sandbox version
384
+ export async function pushDevVersion(apiKey, agentId, skillId, versionData) {
385
+ try {
386
+ const url = `http://localhost:3022/developer/skills/${agentId}/${skillId}/version/sandbox`;
387
+ console.log(`🔗 Calling CREATE endpoint: ${url}`);
388
+ const response = await ApiService.Skill.pushDevSkill(apiKey, agentId, skillId, versionData);
389
+ if (response.success) {
390
+ return {
391
+ success: true,
392
+ data: response.data
393
+ };
394
+ }
395
+ else {
396
+ return {
397
+ success: false,
398
+ error: {
399
+ message: response.error?.message || 'Unknown error',
400
+ error: response.error?.message || 'Unknown error',
401
+ statusCode: response.error?.statusCode || 500
402
+ }
403
+ };
404
+ }
405
+ }
406
+ catch (error) {
407
+ console.error("❌ Network error pushing dev version:", error);
408
+ return {
409
+ success: false,
410
+ error: {
411
+ message: "Network error",
412
+ error: error instanceof Error ? error.message : "Failed to connect to server",
413
+ statusCode: 500
414
+ }
415
+ };
416
+ }
417
+ }
418
+ function readConfigSkillName() {
419
+ const config = readSkillConfig();
420
+ return config?.skill?.name || null;
421
+ }
422
+ function readConfigVersion() {
423
+ const config = readSkillConfig();
424
+ return config?.skill?.version || null;
425
+ }
426
+ function readDeployJson() {
427
+ const deployPath = path.join(process.cwd(), '.lua', 'deploy.json');
428
+ if (!fs.existsSync(deployPath)) {
429
+ return null;
430
+ }
431
+ const deployContent = fs.readFileSync(deployPath, 'utf8');
432
+ return JSON.parse(deployContent);
433
+ }
434
+ // Function to push to sandbox (extracted for reuse)
435
+ async function pushToSandbox(apiKey, agentId, skillId, deployData, isInitial = false) {
436
+ const sandboxSkillId = await getSandboxSkillId();
437
+ // Read skill name from config and add to payload
438
+ const skillName = readConfigSkillName();
439
+ const payloadWithName = {
440
+ ...deployData,
441
+ name: skillName || deployData.name || 'Unknown Skill'
442
+ };
443
+ if (sandboxSkillId) {
444
+ // Try to update existing sandbox version
445
+ if (!isInitial) {
446
+ writeProgress("🔄 Updating existing sandbox version...");
447
+ }
448
+ const updateResult = await updateDevVersion(apiKey, agentId, skillId, sandboxSkillId, payloadWithName);
449
+ if (updateResult.success && updateResult.data) {
450
+ if (!isInitial) {
451
+ writeSuccess(`✅ Version ${updateResult.data.version} updated in sandbox successfully`);
452
+ writeSuccess(`🔑 Sandbox Skill ID: ${updateResult.data.skillId}`);
453
+ }
454
+ return true;
455
+ }
456
+ else if (updateResult.error) {
457
+ if (!isInitial) {
458
+ writeProgress("⚠️ Failed to update existing sandbox, creating new one...");
459
+ console.error(`❌ Sandbox update failed: ${updateResult.error.message}`);
460
+ if (updateResult.error.error) {
461
+ console.error(` Details: ${updateResult.error.error}`);
462
+ }
463
+ }
464
+ // Fall through to create new sandbox
465
+ }
466
+ }
467
+ // Create new sandbox version (either no existing ID or update failed)
468
+ if (!isInitial) {
469
+ writeProgress("🔄 Creating new sandbox version...");
470
+ }
471
+ const result = await pushDevVersion(apiKey, agentId, skillId, payloadWithName);
472
+ if (result.success && result.data) {
473
+ // Store the new sandbox skill ID
474
+ await setSandboxSkillId(result.data.skillId);
475
+ if (!isInitial) {
476
+ writeSuccess(`✅ Version ${result.data.version} pushed to sandbox successfully`);
477
+ writeSuccess(`🔑 Sandbox Skill ID: ${result.data.skillId}`);
478
+ }
479
+ return true;
480
+ }
481
+ else if (result.error) {
482
+ if (!isInitial) {
483
+ console.error(`❌ Sandbox creation failed: ${result.error.message}`);
484
+ if (result.error.error) {
485
+ console.error(` Details: ${result.error.error}`);
486
+ }
487
+ if (result.error.statusCode) {
488
+ console.error(` Status Code: ${result.error.statusCode}`);
489
+ }
490
+ }
491
+ return false;
492
+ }
493
+ else {
494
+ if (!isInitial) {
495
+ console.error("❌ Failed to push version to sandbox. Please try again.");
496
+ }
497
+ return false;
498
+ }
499
+ }
500
+ export async function devCommand() {
501
+ return withErrorHandling(async () => {
502
+ // Check if we're in a skill directory
503
+ const config = readSkillConfig();
504
+ if (!config) {
505
+ console.error("❌ No lua.skill.yaml found. Please run this command from a skill directory.");
506
+ process.exit(1);
507
+ }
508
+ // Read version from config
509
+ const version = config.skill?.version;
510
+ if (!version) {
511
+ console.error("❌ No version found in skill configuration");
512
+ process.exit(1);
513
+ }
514
+ // Confirm with user
515
+ const { confirmed } = await inquirer.prompt([
516
+ {
517
+ type: "confirm",
518
+ name: "confirmed",
519
+ message: `Are you sure you want to push version ${version} to sandbox?`,
520
+ default: false
521
+ }
522
+ ]);
523
+ // Clear the confirmation prompt lines
524
+ clearPromptLines(2);
525
+ if (!confirmed) {
526
+ console.log("❌ Dev push cancelled.");
527
+ process.exit(0);
528
+ }
529
+ // Load API key
530
+ const apiKey = await loadApiKey();
531
+ if (!apiKey) {
532
+ console.error("❌ No API key found. Please run 'lua auth configure' to set up your API key.");
533
+ process.exit(1);
534
+ }
535
+ // Validate API key
536
+ const userData = await checkApiKey(apiKey);
537
+ writeProgress("✅ Authenticated");
538
+ // Compile the skill first
539
+ writeProgress("🔄 Compiling skill...");
540
+ await compileCommand();
541
+ // Read deploy.json
542
+ const deployData = readDeployJson();
543
+ if (!deployData) {
544
+ console.error("❌ No deploy.json found. Compilation may have failed.");
545
+ process.exit(1);
546
+ }
547
+ // Verify version matches
548
+ if (deployData.version !== version) {
549
+ console.error(`❌ Version mismatch: config has ${version}, deploy.json has ${deployData.version}`);
550
+ process.exit(1);
551
+ }
552
+ // Extract agentId and skillId from config
553
+ const agentId = config.agent?.agentId;
554
+ const skillId = config.skill?.skillId;
555
+ if (!agentId || !skillId) {
556
+ console.error("❌ Missing agentId or skillId in skill configuration");
557
+ process.exit(1);
558
+ }
559
+ // Initial push to sandbox
560
+ writeProgress("🔄 Pushing to sandbox...");
561
+ const initialSuccess = await pushToSandbox(apiKey, agentId, skillId, deployData, true);
562
+ if (!initialSuccess) {
563
+ console.error("❌ Failed to push to sandbox. Cannot start development mode.");
564
+ process.exit(1);
565
+ }
566
+ // Get the sandbox skill ID for chat
567
+ const sandboxSkillId = await getSandboxSkillId();
568
+ if (!sandboxSkillId) {
569
+ console.error("❌ No sandbox skill ID found. Cannot start chat interface.");
570
+ process.exit(1);
571
+ }
572
+ // Start web server for chat interface
573
+ const chatPort = 3000;
574
+ const { server, wss } = createChatServer(apiKey, agentId, skillId, sandboxSkillId, chatPort);
575
+ // Open browser to chat interface
576
+ try {
577
+ await open(`http://localhost:${chatPort}`);
578
+ writeSuccess("🌐 Chat interface opened in your browser");
579
+ }
580
+ catch (error) {
581
+ writeSuccess(`🌐 Chat interface available at: http://localhost:${chatPort}`);
582
+ }
583
+ // Start file watching
584
+ writeSuccess("🔍 Watching for file changes... (Press Ctrl+C to stop)");
585
+ let isCompiling = false;
586
+ let pendingCompile = false;
587
+ const watcher = watch(process.cwd(), { recursive: true }, async (eventType, filename) => {
588
+ // Ignore changes in .lua directory and other build artifacts
589
+ if (filename && (filename.includes('.lua/') ||
590
+ filename.includes('node_modules/') ||
591
+ filename.includes('.git/') ||
592
+ filename.endsWith('.log') ||
593
+ filename.endsWith('.tmp'))) {
594
+ return;
595
+ }
596
+ // Debounce rapid changes
597
+ if (isCompiling) {
598
+ pendingCompile = true;
599
+ return;
600
+ }
601
+ isCompiling = true;
602
+ pendingCompile = false;
603
+ try {
604
+ writeProgress(`🔄 File changed: ${filename} - Compiling and pushing...`);
605
+ // Compile the skill
606
+ await compileCommand();
607
+ // Read updated deploy.json
608
+ const updatedDeployData = readDeployJson();
609
+ if (!updatedDeployData) {
610
+ writeProgress("❌ Compilation failed, skipping push");
611
+ return;
612
+ }
613
+ // Verify version matches
614
+ if (updatedDeployData.version !== version) {
615
+ writeProgress("❌ Version mismatch, skipping push");
616
+ return;
617
+ }
618
+ // Push to sandbox
619
+ const pushSuccess = await pushToSandbox(apiKey, agentId, skillId, updatedDeployData, false);
620
+ if (!pushSuccess) {
621
+ writeProgress("❌ Failed to push to sandbox, will retry on next change");
622
+ }
623
+ else {
624
+ // Broadcast reload message to all connected WebSocket clients
625
+ wss.clients.forEach((client) => {
626
+ if (client.readyState === 1) { // WebSocket.OPEN
627
+ client.send(JSON.stringify({ type: 'reload', message: 'Files changed, reloading...' }));
628
+ }
629
+ });
630
+ }
631
+ }
632
+ catch (error) {
633
+ writeProgress(`❌ Error during auto-compile: ${error instanceof Error ? error.message : 'Unknown error'}`);
634
+ }
635
+ finally {
636
+ isCompiling = false;
637
+ // Handle any pending compile
638
+ if (pendingCompile) {
639
+ setTimeout(() => {
640
+ if (!isCompiling) {
641
+ watcher.emit('change', 'pending', 'pending');
642
+ }
643
+ }, 100);
644
+ }
645
+ }
646
+ });
647
+ // Handle graceful shutdown
648
+ process.on('SIGINT', () => {
649
+ writeSuccess("\n🛑 Stopping file watcher...");
650
+ watcher.close();
651
+ process.exit(0);
652
+ });
653
+ // Keep the process alive
654
+ return new Promise(() => { });
655
+ }, "dev push");
656
+ }
@@ -6,3 +6,4 @@ export { compileCommand } from "./compile.js";
6
6
  export { testCommand } from "./test.js";
7
7
  export { pushCommand } from "./push.js";
8
8
  export { deployCommand } from "./deploy.js";
9
+ export { devCommand } from "./dev.js";
@@ -6,3 +6,4 @@ export { compileCommand } from "./compile.js";
6
6
  export { testCommand } from "./test.js";
7
7
  export { pushCommand } from "./push.js";
8
8
  export { deployCommand } from "./deploy.js";
9
+ export { devCommand } from "./dev.js";