luxlabs 1.0.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.
- package/LICENSE +37 -0
- package/README.md +161 -0
- package/commands/ab-tests.js +437 -0
- package/commands/agents.js +226 -0
- package/commands/data.js +966 -0
- package/commands/deploy.js +166 -0
- package/commands/dev.js +569 -0
- package/commands/init.js +126 -0
- package/commands/interface/boilerplate.js +52 -0
- package/commands/interface/git-utils.js +85 -0
- package/commands/interface/index.js +7 -0
- package/commands/interface/init.js +375 -0
- package/commands/interface/path.js +74 -0
- package/commands/interface.js +125 -0
- package/commands/knowledge.js +339 -0
- package/commands/link.js +127 -0
- package/commands/list.js +97 -0
- package/commands/login.js +247 -0
- package/commands/logout.js +19 -0
- package/commands/logs.js +182 -0
- package/commands/pricing.js +328 -0
- package/commands/project.js +704 -0
- package/commands/secrets.js +129 -0
- package/commands/servers.js +411 -0
- package/commands/storage.js +177 -0
- package/commands/up.js +211 -0
- package/commands/validate-data-lux.js +502 -0
- package/commands/voice-agents.js +1055 -0
- package/commands/webview.js +393 -0
- package/commands/workflows.js +836 -0
- package/lib/config.js +403 -0
- package/lib/helpers.js +189 -0
- package/lib/node-helper.js +120 -0
- package/lux.js +268 -0
- package/package.json +56 -0
- package/templates/next-env.d.ts +6 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Command - Control webview for AI self-testing
|
|
3
|
+
*
|
|
4
|
+
* This command allows CLI/AI to interact with running dev servers:
|
|
5
|
+
* - Take screenshots
|
|
6
|
+
* - Click elements
|
|
7
|
+
* - Type text
|
|
8
|
+
* - Execute JavaScript
|
|
9
|
+
* - Navigate pages
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const net = require('net');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
const { LUX_STUDIO_DIR } = require('../lib/config');
|
|
17
|
+
|
|
18
|
+
// Socket path for webview control
|
|
19
|
+
const SOCKET_PATH = path.join(LUX_STUDIO_DIR, 'webview-control.sock');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Send a command to Lux Studio via Unix socket
|
|
23
|
+
*/
|
|
24
|
+
function sendCommand(command) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
if (!fs.existsSync(SOCKET_PATH)) {
|
|
27
|
+
reject(new Error('Lux Studio is not running or webview control is not available'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const client = net.createConnection(SOCKET_PATH);
|
|
32
|
+
let buffer = '';
|
|
33
|
+
|
|
34
|
+
client.on('connect', () => {
|
|
35
|
+
client.write(JSON.stringify(command) + '\n');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
client.on('data', (data) => {
|
|
39
|
+
buffer += data.toString();
|
|
40
|
+
const lines = buffer.split('\n');
|
|
41
|
+
buffer = lines.pop() || '';
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (line.trim()) {
|
|
45
|
+
try {
|
|
46
|
+
const result = JSON.parse(line);
|
|
47
|
+
client.end();
|
|
48
|
+
resolve(result);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
reject(new Error(`Failed to parse response: ${err.message}`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
client.on('error', (err) => {
|
|
57
|
+
reject(new Error(`Socket error: ${err.message}`));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
client.on('close', () => {
|
|
61
|
+
if (buffer.trim()) {
|
|
62
|
+
try {
|
|
63
|
+
resolve(JSON.parse(buffer));
|
|
64
|
+
} catch {
|
|
65
|
+
// No response
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Timeout after 10 seconds (screenshots can take longer)
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
client.destroy();
|
|
73
|
+
reject(new Error('Command timed out'));
|
|
74
|
+
}, 10000);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate a unique command ID
|
|
80
|
+
*/
|
|
81
|
+
function generateId() {
|
|
82
|
+
return `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Take a screenshot of the webview
|
|
87
|
+
*/
|
|
88
|
+
async function screenshot(interfaceId, outputPath) {
|
|
89
|
+
const id = generateId();
|
|
90
|
+
const result = await sendCommand({
|
|
91
|
+
id,
|
|
92
|
+
type: 'screenshot',
|
|
93
|
+
appId: interfaceId, // Keep appId for backwards compatibility with Lux Studio
|
|
94
|
+
payload: { outputPath },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (result.success) {
|
|
98
|
+
console.log(chalk.green('Screenshot captured!'));
|
|
99
|
+
if (result.screenshotPath) {
|
|
100
|
+
console.log(chalk.cyan(' Path:'), result.screenshotPath);
|
|
101
|
+
} else if (result.data?.path) {
|
|
102
|
+
console.log(chalk.cyan(' Path:'), result.data.path);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
console.log(chalk.red('Screenshot failed:'), result.error);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Click an element in the webview
|
|
113
|
+
*/
|
|
114
|
+
async function click(interfaceId, selector) {
|
|
115
|
+
const id = generateId();
|
|
116
|
+
const result = await sendCommand({
|
|
117
|
+
id,
|
|
118
|
+
type: 'click',
|
|
119
|
+
appId: interfaceId,
|
|
120
|
+
payload: { selector },
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (result.success) {
|
|
124
|
+
console.log(chalk.green('Clicked:'), selector);
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.red('Click failed:'), result.error);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Type text into an element
|
|
134
|
+
*/
|
|
135
|
+
async function type(interfaceId, selector, text) {
|
|
136
|
+
const id = generateId();
|
|
137
|
+
const result = await sendCommand({
|
|
138
|
+
id,
|
|
139
|
+
type: 'type',
|
|
140
|
+
appId: interfaceId,
|
|
141
|
+
payload: { selector, text },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (result.success) {
|
|
145
|
+
console.log(chalk.green('Typed text into:'), selector);
|
|
146
|
+
} else {
|
|
147
|
+
console.log(chalk.red('Type failed:'), result.error);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Execute JavaScript in the webview
|
|
155
|
+
*/
|
|
156
|
+
async function evaluate(interfaceId, code) {
|
|
157
|
+
const id = generateId();
|
|
158
|
+
const result = await sendCommand({
|
|
159
|
+
id,
|
|
160
|
+
type: 'eval',
|
|
161
|
+
appId: interfaceId,
|
|
162
|
+
payload: { code },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (result.success) {
|
|
166
|
+
console.log(chalk.green('Eval result:'), JSON.stringify(result.data, null, 2));
|
|
167
|
+
} else {
|
|
168
|
+
console.log(chalk.red('Eval failed:'), result.error);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get current URL of the webview
|
|
176
|
+
*/
|
|
177
|
+
async function getUrl(interfaceId) {
|
|
178
|
+
const id = generateId();
|
|
179
|
+
const result = await sendCommand({
|
|
180
|
+
id,
|
|
181
|
+
type: 'get-url',
|
|
182
|
+
appId: interfaceId,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (result.success) {
|
|
186
|
+
console.log(chalk.green('Current URL:'), result.data?.url);
|
|
187
|
+
} else {
|
|
188
|
+
console.log(chalk.red('Get URL failed:'), result.error);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Navigate to a URL
|
|
196
|
+
*/
|
|
197
|
+
async function navigate(interfaceId, url) {
|
|
198
|
+
const id = generateId();
|
|
199
|
+
const result = await sendCommand({
|
|
200
|
+
id,
|
|
201
|
+
type: 'navigate',
|
|
202
|
+
appId: interfaceId,
|
|
203
|
+
payload: { url },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (result.success) {
|
|
207
|
+
console.log(chalk.green('Navigated to:'), url);
|
|
208
|
+
} else {
|
|
209
|
+
console.log(chalk.red('Navigate failed:'), result.error);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Wait for a duration
|
|
217
|
+
*/
|
|
218
|
+
async function wait(interfaceId, ms) {
|
|
219
|
+
const id = generateId();
|
|
220
|
+
const result = await sendCommand({
|
|
221
|
+
id,
|
|
222
|
+
type: 'wait',
|
|
223
|
+
appId: interfaceId,
|
|
224
|
+
payload: { ms: parseInt(ms, 10) },
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (result.success) {
|
|
228
|
+
console.log(chalk.green('Waited:'), ms, 'ms');
|
|
229
|
+
} else {
|
|
230
|
+
console.log(chalk.red('Wait failed:'), result.error);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Start the interface preview in Lux Studio
|
|
238
|
+
*/
|
|
239
|
+
async function startPreview(interfaceId) {
|
|
240
|
+
const id = generateId();
|
|
241
|
+
const result = await sendCommand({
|
|
242
|
+
id,
|
|
243
|
+
type: 'start-preview',
|
|
244
|
+
appId: interfaceId,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (result.success) {
|
|
248
|
+
console.log(chalk.green('Preview started for:'), interfaceId);
|
|
249
|
+
} else {
|
|
250
|
+
console.log(chalk.red('Start preview failed:'), result.error);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Show help for test commands
|
|
258
|
+
*/
|
|
259
|
+
function showHelp() {
|
|
260
|
+
console.log(chalk.cyan('\nUsage:'));
|
|
261
|
+
console.log(' lux test screenshot <interface-id> [output-path] Take a screenshot');
|
|
262
|
+
console.log(' lux test click <interface-id> <selector> Click an element');
|
|
263
|
+
console.log(' lux test type <interface-id> <selector> <text> Type into an element');
|
|
264
|
+
console.log(' lux test eval <interface-id> <code> Execute JavaScript');
|
|
265
|
+
console.log(' lux test url <interface-id> Get current URL');
|
|
266
|
+
console.log(' lux test navigate <interface-id> <url> Navigate to URL');
|
|
267
|
+
console.log(' lux test wait <interface-id> <ms> Wait for duration');
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log(chalk.cyan('Examples:'));
|
|
270
|
+
console.log(' lux test screenshot my-interface ./screenshot.png');
|
|
271
|
+
console.log(' lux test click my-interface "button.submit"');
|
|
272
|
+
console.log(' lux test type my-interface "#email" "user@example.com"');
|
|
273
|
+
console.log(' lux test eval my-interface "document.title"');
|
|
274
|
+
console.log(' lux test navigate my-interface "http://localhost:3000/login"');
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log(chalk.dim('Note: <interface-id> is the interface ID or name from "lux servers"'));
|
|
277
|
+
console.log('');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Handle test command
|
|
282
|
+
*/
|
|
283
|
+
async function handleTest(args = []) {
|
|
284
|
+
const [subcommand, ...rest] = args;
|
|
285
|
+
|
|
286
|
+
if (!subcommand || subcommand === 'help') {
|
|
287
|
+
showHelp();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
switch (subcommand) {
|
|
293
|
+
case 'screenshot': {
|
|
294
|
+
const [interfaceId, outputPath] = rest;
|
|
295
|
+
if (!interfaceId) {
|
|
296
|
+
console.log(chalk.red('Error: interface-id is required'));
|
|
297
|
+
showHelp();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
await screenshot(interfaceId, outputPath);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
case 'click': {
|
|
305
|
+
const [interfaceId, selector] = rest;
|
|
306
|
+
if (!interfaceId || !selector) {
|
|
307
|
+
console.log(chalk.red('Error: interface-id and selector are required'));
|
|
308
|
+
showHelp();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
await click(interfaceId, selector);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
case 'type': {
|
|
316
|
+
const [interfaceId, selector, ...textParts] = rest;
|
|
317
|
+
const text = textParts.join(' ');
|
|
318
|
+
if (!interfaceId || !selector || !text) {
|
|
319
|
+
console.log(chalk.red('Error: interface-id, selector, and text are required'));
|
|
320
|
+
showHelp();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
await type(interfaceId, selector, text);
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
case 'eval': {
|
|
328
|
+
const [interfaceId, ...codeParts] = rest;
|
|
329
|
+
const code = codeParts.join(' ');
|
|
330
|
+
if (!interfaceId || !code) {
|
|
331
|
+
console.log(chalk.red('Error: interface-id and code are required'));
|
|
332
|
+
showHelp();
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
await evaluate(interfaceId, code);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
case 'url': {
|
|
340
|
+
const [interfaceId] = rest;
|
|
341
|
+
if (!interfaceId) {
|
|
342
|
+
console.log(chalk.red('Error: interface-id is required'));
|
|
343
|
+
showHelp();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
await getUrl(interfaceId);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
case 'navigate':
|
|
351
|
+
case 'nav': {
|
|
352
|
+
const [interfaceId, url] = rest;
|
|
353
|
+
if (!interfaceId || !url) {
|
|
354
|
+
console.log(chalk.red('Error: interface-id and url are required'));
|
|
355
|
+
showHelp();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
await navigate(interfaceId, url);
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
case 'wait': {
|
|
363
|
+
const [interfaceId, ms] = rest;
|
|
364
|
+
if (!interfaceId || !ms) {
|
|
365
|
+
console.log(chalk.red('Error: interface-id and duration (ms) are required'));
|
|
366
|
+
showHelp();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
await wait(interfaceId, ms);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
default:
|
|
374
|
+
console.log(chalk.red(`Unknown test command: ${subcommand}`));
|
|
375
|
+
showHelp();
|
|
376
|
+
}
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.log(chalk.red('Error:'), err.message);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = {
|
|
383
|
+
handleTest,
|
|
384
|
+
screenshot,
|
|
385
|
+
click,
|
|
386
|
+
type,
|
|
387
|
+
evaluate,
|
|
388
|
+
getUrl,
|
|
389
|
+
navigate,
|
|
390
|
+
wait,
|
|
391
|
+
startPreview,
|
|
392
|
+
sendCommand,
|
|
393
|
+
};
|