luxlabs 1.0.21 ā 1.0.24
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/README.md +16 -21
- package/commands/ab-tests.js +14 -11
- package/commands/agents.js +11 -11
- package/commands/data.js +19 -17
- package/commands/deploy.js +145 -82
- package/commands/flows.js +152 -133
- package/commands/interface/init.js +36 -35
- package/commands/interface.js +135 -10
- package/commands/knowledge.js +3 -3
- package/commands/list.js +6 -24
- package/commands/login.js +31 -26
- package/commands/logout.js +13 -4
- package/commands/logs.js +17 -66
- package/commands/project.js +74 -47
- package/commands/secrets.js +1 -1
- package/commands/servers.js +9 -113
- package/commands/storage.js +1 -1
- package/commands/tools.js +4 -4
- package/commands/validate-data-lux.js +5 -2
- package/commands/voice-agents.js +22 -18
- package/lib/config.js +235 -83
- package/lib/helpers.js +6 -4
- package/lux.js +4 -94
- package/package.json +6 -1
- package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +41 -34
- package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +41 -34
- package/templates/interface-boilerplate/components/providers/posthog-provider.tsx +41 -26
- package/templates/interface-boilerplate/gitignore.template +4 -0
- package/templates/interface-boilerplate/lib/auth.config.ts +3 -2
- package/templates/interface-boilerplate/lib/knowledge.ts +2 -2
- package/templates/interface-boilerplate/middleware.ts +14 -3
- package/templates/interface-boilerplate/next-env.d.ts +6 -0
- package/templates/interface-boilerplate/package-lock.json +432 -8
- package/commands/dev.js +0 -578
- package/commands/init.js +0 -126
- package/commands/link.js +0 -127
- package/commands/up.js +0 -211
|
@@ -77,14 +77,14 @@ async function initInterface(options) {
|
|
|
77
77
|
// Check authentication
|
|
78
78
|
if (!isAuthenticated()) {
|
|
79
79
|
console.log(
|
|
80
|
-
chalk.red('
|
|
80
|
+
chalk.red('Not authenticated. Run'),
|
|
81
81
|
chalk.white('lux login'),
|
|
82
82
|
chalk.red('first.')
|
|
83
83
|
);
|
|
84
84
|
process.exit(1);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
console.log(chalk.cyan('\
|
|
87
|
+
console.log(chalk.cyan('\nInitialize New App\n'));
|
|
88
88
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
89
89
|
console.log(chalk.blue('[STEP 0/6]'), 'Validating options...');
|
|
90
90
|
|
|
@@ -93,8 +93,8 @@ async function initInterface(options) {
|
|
|
93
93
|
let description = options.description || '';
|
|
94
94
|
|
|
95
95
|
if (!name) {
|
|
96
|
-
console.log(chalk.red('
|
|
97
|
-
console.log(chalk.dim('Usage: lux i
|
|
96
|
+
console.log(chalk.red('--name flag is required'));
|
|
97
|
+
console.log(chalk.dim('Usage: lux i create --name <name> [--description <desc>]'));
|
|
98
98
|
process.exit(1);
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -105,12 +105,12 @@ async function initInterface(options) {
|
|
|
105
105
|
|
|
106
106
|
// Check if interface with this slug already exists
|
|
107
107
|
if (interfaceExists(slug)) {
|
|
108
|
-
console.log(chalk.red(
|
|
108
|
+
console.log(chalk.red(`Interface already exists: ${slug}`));
|
|
109
109
|
console.log(chalk.dim('Use a different name or delete the existing interface first.'));
|
|
110
110
|
process.exit(1);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
console.log(chalk.green('
|
|
113
|
+
console.log(chalk.green(' Options validated'));
|
|
114
114
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
115
115
|
|
|
116
116
|
try {
|
|
@@ -138,23 +138,23 @@ async function initInterface(options) {
|
|
|
138
138
|
const vercelProjectId = interfaceData.vercel_project_id;
|
|
139
139
|
const vercelProjectUrl = interfaceData.vercel_project_url;
|
|
140
140
|
|
|
141
|
-
console.log(chalk.green('
|
|
141
|
+
console.log(chalk.green(' Interface registered in database'));
|
|
142
142
|
console.log(chalk.dim(' Interface ID:'), interfaceId);
|
|
143
143
|
if (githubRepoUrl) {
|
|
144
|
-
console.log(chalk.green('
|
|
144
|
+
console.log(chalk.green(' GitHub repository created'));
|
|
145
145
|
console.log(chalk.dim(' GitHub URL:'), githubRepoUrl);
|
|
146
146
|
} else {
|
|
147
|
-
console.log(chalk.yellow('
|
|
147
|
+
console.log(chalk.yellow(' No GitHub repository URL returned'));
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
if (vercelProjectId) {
|
|
151
|
-
console.log(chalk.green('
|
|
151
|
+
console.log(chalk.green(' Vercel project created'));
|
|
152
152
|
console.log(chalk.dim(' Vercel Project ID:'), vercelProjectId);
|
|
153
153
|
if (vercelProjectUrl) {
|
|
154
154
|
console.log(chalk.dim(' Vercel Dashboard:'), vercelProjectUrl);
|
|
155
155
|
}
|
|
156
156
|
} else {
|
|
157
|
-
console.log(chalk.yellow('
|
|
157
|
+
console.log(chalk.yellow(' Vercel project not created (may need VERCEL_TOKEN)'));
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
@@ -175,15 +175,15 @@ async function initInterface(options) {
|
|
|
175
175
|
});
|
|
176
176
|
if (tokenRes.data?.token) {
|
|
177
177
|
githubToken = tokenRes.data.token;
|
|
178
|
-
console.log(chalk.green('
|
|
178
|
+
console.log(chalk.green(' GitHub token fetched from API'));
|
|
179
179
|
} else {
|
|
180
|
-
console.log(chalk.yellow('
|
|
180
|
+
console.log(chalk.yellow(' No token returned from API'));
|
|
181
181
|
}
|
|
182
182
|
} catch (tokenErr) {
|
|
183
|
-
console.log(chalk.yellow('
|
|
183
|
+
console.log(chalk.yellow(' Failed to fetch token:'), tokenErr.message);
|
|
184
184
|
}
|
|
185
185
|
} else {
|
|
186
|
-
console.log(chalk.green('
|
|
186
|
+
console.log(chalk.green(' Using local GitHub token'));
|
|
187
187
|
}
|
|
188
188
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
189
189
|
|
|
@@ -200,7 +200,7 @@ async function initInterface(options) {
|
|
|
200
200
|
console.log(chalk.dim(' Repo dir:'), repoDir);
|
|
201
201
|
|
|
202
202
|
if (fs.existsSync(repoDir)) {
|
|
203
|
-
console.log(chalk.yellow('
|
|
203
|
+
console.log(chalk.yellow(' Project directory already exists'));
|
|
204
204
|
} else {
|
|
205
205
|
fs.mkdirSync(repoDir, { recursive: true });
|
|
206
206
|
console.log(chalk.dim(' Created directory'));
|
|
@@ -217,10 +217,10 @@ async function initInterface(options) {
|
|
|
217
217
|
// List what was copied
|
|
218
218
|
const copiedFiles = fs.readdirSync(repoDir);
|
|
219
219
|
console.log(chalk.dim(' Copied files:'), copiedFiles.join(', '));
|
|
220
|
-
console.log(chalk.green('
|
|
220
|
+
console.log(chalk.green(' Template files copied successfully'));
|
|
221
221
|
}
|
|
222
222
|
} catch (copyError) {
|
|
223
|
-
console.log(chalk.red('
|
|
223
|
+
console.log(chalk.red(' Failed to copy templates:'), copyError.message);
|
|
224
224
|
throw copyError;
|
|
225
225
|
}
|
|
226
226
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
@@ -233,9 +233,9 @@ async function initInterface(options) {
|
|
|
233
233
|
const npmStart = Date.now();
|
|
234
234
|
await runNpmInstall(repoDir);
|
|
235
235
|
const npmDuration = ((Date.now() - npmStart) / 1000).toFixed(1);
|
|
236
|
-
console.log(chalk.green(`
|
|
236
|
+
console.log(chalk.green(` Dependencies installed (${npmDuration}s)`));
|
|
237
237
|
} catch (npmError) {
|
|
238
|
-
console.log(chalk.yellow(`
|
|
238
|
+
console.log(chalk.yellow(` npm install failed: ${npmError.message}`));
|
|
239
239
|
console.log(chalk.dim(' Dependencies will be installed when you start the dev server'));
|
|
240
240
|
}
|
|
241
241
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
@@ -250,34 +250,34 @@ async function initInterface(options) {
|
|
|
250
250
|
const hasNodeModules = gitignoreContent.includes('node_modules');
|
|
251
251
|
console.log(chalk.dim(' .gitignore exists:'), hasNodeModules ? 'includes node_modules' : 'WARNING: missing node_modules!');
|
|
252
252
|
} else {
|
|
253
|
-
console.log(chalk.yellow('
|
|
253
|
+
console.log(chalk.yellow(' No .gitignore found'));
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
// Initialize git repo
|
|
257
257
|
await runGitCommand(['init'], repoDir);
|
|
258
|
-
console.log(chalk.green('
|
|
258
|
+
console.log(chalk.green(' git init completed'));
|
|
259
259
|
|
|
260
260
|
// Add remote origin
|
|
261
261
|
if (githubToken) {
|
|
262
262
|
const targetUrl = githubRepoUrl.replace('https://github.com/', `https://${githubToken}@github.com/`) + '.git';
|
|
263
263
|
await runGitCommand(['remote', 'add', 'origin', targetUrl], repoDir);
|
|
264
|
-
console.log(chalk.green('
|
|
264
|
+
console.log(chalk.green(' Remote origin added'));
|
|
265
265
|
console.log(chalk.dim(' Remote URL:'), githubRepoUrl + '.git');
|
|
266
266
|
} else {
|
|
267
|
-
console.log(chalk.yellow('
|
|
267
|
+
console.log(chalk.yellow(' No GitHub token - remote not added'));
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
// Set main branch
|
|
271
271
|
await runGitCommand(['branch', '-M', 'main'], repoDir);
|
|
272
|
-
console.log(chalk.green('
|
|
272
|
+
console.log(chalk.green(' Branch set to main'));
|
|
273
273
|
|
|
274
274
|
// Initial commit (but don't push yet - that happens on first deploy)
|
|
275
275
|
await runGitCommand(['add', '.'], repoDir);
|
|
276
276
|
await runGitCommand(['commit', '-m', 'Initial commit from Lux Studio'], repoDir);
|
|
277
|
-
console.log(chalk.green('
|
|
277
|
+
console.log(chalk.green(' Initial commit created'));
|
|
278
278
|
console.log(chalk.dim(' Note: Push to GitHub will happen on first deploy'));
|
|
279
279
|
} catch (gitError) {
|
|
280
|
-
console.log(chalk.yellow(`
|
|
280
|
+
console.log(chalk.yellow(` Git setup failed: ${gitError.message}`));
|
|
281
281
|
}
|
|
282
282
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
283
283
|
|
|
@@ -289,6 +289,7 @@ async function initInterface(options) {
|
|
|
289
289
|
slug, // Keep slug for display/reference
|
|
290
290
|
name,
|
|
291
291
|
description,
|
|
292
|
+
version: 0, // Draft interfaces start at version 0
|
|
292
293
|
status: 'draft', // New interfaces are drafts until first deploy
|
|
293
294
|
githubUrl: githubRepoUrl,
|
|
294
295
|
vercelProjectId: vercelProjectId || null,
|
|
@@ -296,14 +297,14 @@ async function initInterface(options) {
|
|
|
296
297
|
createdAt: new Date().toISOString(),
|
|
297
298
|
updatedAt: new Date().toISOString(),
|
|
298
299
|
});
|
|
299
|
-
console.log(chalk.green('
|
|
300
|
+
console.log(chalk.green(' Metadata saved'));
|
|
300
301
|
console.log(chalk.dim(' ID:'), interfaceId);
|
|
301
302
|
console.log(chalk.dim(' Slug:'), slug);
|
|
302
303
|
if (vercelProjectId) {
|
|
303
304
|
console.log(chalk.dim(' Vercel:'), vercelProjectId);
|
|
304
305
|
}
|
|
305
306
|
} catch (metadataError) {
|
|
306
|
-
console.log(chalk.red('
|
|
307
|
+
console.log(chalk.red(' Failed to save metadata:'), metadataError.message);
|
|
307
308
|
}
|
|
308
309
|
console.log(chalk.dim('ā'.repeat(50)));
|
|
309
310
|
|
|
@@ -316,7 +317,7 @@ async function initInterface(options) {
|
|
|
316
317
|
console.log(`Vercel project: ${vercelProjectId}`);
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
console.log(chalk.green('\
|
|
320
|
+
console.log(chalk.green('\nInterface created successfully!\n'));
|
|
320
321
|
console.log(chalk.dim(' Created:'));
|
|
321
322
|
console.log(chalk.dim(' ⢠Registered in system.interfaces'));
|
|
322
323
|
console.log(chalk.dim(' ⢠GitHub repository created'));
|
|
@@ -336,7 +337,7 @@ async function initInterface(options) {
|
|
|
336
337
|
// No GitHub URL - output parseable format anyway (cloud-only interface)
|
|
337
338
|
console.log(`App created: ${interfaceId}`);
|
|
338
339
|
|
|
339
|
-
console.log(chalk.yellow('\
|
|
340
|
+
console.log(chalk.yellow('\nNo GitHub URL returned - skipping local setup'));
|
|
340
341
|
console.log(chalk.dim('\n Created:'));
|
|
341
342
|
console.log(chalk.dim(' ⢠Registered in system.interfaces'));
|
|
342
343
|
console.log(chalk.cyan('\n Next steps:'));
|
|
@@ -345,12 +346,12 @@ async function initInterface(options) {
|
|
|
345
346
|
|
|
346
347
|
process.exit(0);
|
|
347
348
|
} catch (error) {
|
|
348
|
-
console.log(chalk.red('\
|
|
349
|
+
console.log(chalk.red('\nFailed to initialize interface'));
|
|
349
350
|
|
|
350
351
|
// Detailed error logging
|
|
351
352
|
if (error.response) {
|
|
352
353
|
// Server responded with an error status
|
|
353
|
-
console.error(chalk.red('\
|
|
354
|
+
console.error(chalk.red('\nAPI Error:'));
|
|
354
355
|
console.error(chalk.dim(' Status:'), error.response.status);
|
|
355
356
|
console.error(chalk.dim(' Message:'), error.response.data?.error || error.response.data?.message || 'Unknown error');
|
|
356
357
|
if (error.response.data?.details) {
|
|
@@ -361,12 +362,12 @@ async function initInterface(options) {
|
|
|
361
362
|
}
|
|
362
363
|
} else if (error.request) {
|
|
363
364
|
// Request was made but no response received
|
|
364
|
-
console.error(chalk.red('\
|
|
365
|
+
console.error(chalk.red('\nNetwork Error:'));
|
|
365
366
|
console.error(chalk.dim(' No response received from server'));
|
|
366
367
|
console.error(chalk.dim(' URL:'), error.config?.url);
|
|
367
368
|
} else {
|
|
368
369
|
// Something else went wrong
|
|
369
|
-
console.error(chalk.red('\
|
|
370
|
+
console.error(chalk.red('\nError:'), error.message);
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
process.exit(1);
|
package/commands/interface.js
CHANGED
|
@@ -15,6 +15,9 @@ const chalk = require('chalk');
|
|
|
15
15
|
// Import from modular files
|
|
16
16
|
const { initInterface } = require('./interface/init');
|
|
17
17
|
const { getInterfacePath } = require('./interface/path');
|
|
18
|
+
const { screenshot, click, type: typeText, evaluate, getUrl, navigate, wait, startPreview } = require('./webview');
|
|
19
|
+
const { getLogs: getTerminalLogs, getBrowserConsoleLogs, handleServers } = require('./servers');
|
|
20
|
+
const { handleABTests } = require('./ab-tests');
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* Deploy interface to production
|
|
@@ -33,9 +36,9 @@ async function listInterfaces(options) {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
|
-
* View interface logs
|
|
39
|
+
* View deployed interface logs (build or runtime from Vercel)
|
|
37
40
|
*/
|
|
38
|
-
async function
|
|
41
|
+
async function viewDeployedLogs(options) {
|
|
39
42
|
const { logs } = require('./logs');
|
|
40
43
|
await logs(options);
|
|
41
44
|
}
|
|
@@ -75,15 +78,20 @@ function showHelp() {
|
|
|
75
78
|
console.log(chalk.dim(' Create new interface'));
|
|
76
79
|
console.log(chalk.dim(' Required: --name <interface-name>'));
|
|
77
80
|
console.log(chalk.dim(' Optional: --description <desc>\n'));
|
|
78
|
-
console.log(chalk.white(' lux interface init') + chalk.dim(' (alias for create)\n'));
|
|
79
81
|
console.log(chalk.white(' lux interface list') + chalk.dim(' (or lux i list)'));
|
|
80
82
|
console.log(chalk.dim(' List all interfaces\n'));
|
|
81
83
|
console.log(chalk.white(' lux interface path <name-or-id>'));
|
|
82
84
|
console.log(chalk.dim(' Get local repo path for an interface\n'));
|
|
83
85
|
console.log(chalk.white(' lux interface deploy') + chalk.dim(' (or lux i deploy)'));
|
|
84
86
|
console.log(chalk.dim(' Deploy interface to production\n'));
|
|
85
|
-
console.log(chalk.white(' lux interface logs') + chalk.dim(' (or lux i logs)'));
|
|
86
|
-
console.log(chalk.dim(' View interface logs
|
|
87
|
+
console.log(chalk.white(' lux interface logs <name-or-id>') + chalk.dim(' (or lux i logs)'));
|
|
88
|
+
console.log(chalk.dim(' View interface logs'));
|
|
89
|
+
console.log(chalk.dim(' Optional: --type <deployed|terminal|console> (default: terminal)'));
|
|
90
|
+
console.log(chalk.dim(' Optional: --lines <number> (default: 50)\n'));
|
|
91
|
+
console.log(chalk.white(' lux interface servers [subcommand]') + chalk.dim(' (or lux i servers)'));
|
|
92
|
+
console.log(chalk.dim(' Manage dev servers (list, restart)\n'));
|
|
93
|
+
console.log(chalk.white(' lux interface ab-tests [subcommand]') + chalk.dim(' (or lux i ab-tests)'));
|
|
94
|
+
console.log(chalk.dim(' Manage A/B tests (list-tests, get-test, get-variant)\n'));
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
/**
|
|
@@ -96,23 +104,140 @@ async function handleInterface(subcommand, args) {
|
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
switch (subcommand) {
|
|
99
|
-
case 'init':
|
|
100
107
|
case 'create':
|
|
101
108
|
await initInterface(parseOptions(args));
|
|
102
109
|
break;
|
|
103
|
-
case 'deploy':
|
|
104
|
-
|
|
110
|
+
case 'deploy': {
|
|
111
|
+
const deployOpts = parseOptions(args);
|
|
112
|
+
// First non-flag arg is the interface ID
|
|
113
|
+
const deployId = args.find(a => !a.startsWith('-'));
|
|
114
|
+
if (deployId) deployOpts.id = deployId;
|
|
115
|
+
await deployInterface(deployOpts);
|
|
105
116
|
break;
|
|
117
|
+
}
|
|
106
118
|
case 'list':
|
|
107
119
|
case 'ls':
|
|
108
120
|
await listInterfaces(parseOptions(args));
|
|
109
121
|
break;
|
|
110
|
-
case 'logs':
|
|
111
|
-
|
|
122
|
+
case 'logs': {
|
|
123
|
+
const logsId = args.find(a => !a.startsWith('-'));
|
|
124
|
+
const logsOpts = parseOptions(args);
|
|
125
|
+
const logType = logsOpts.type || 'terminal';
|
|
126
|
+
const logLines = parseInt(logsOpts.lines || '50', 10);
|
|
127
|
+
|
|
128
|
+
if (!logsId) {
|
|
129
|
+
console.log(chalk.red('Error: interface name or ID is required'));
|
|
130
|
+
console.log(chalk.dim('Usage: lux i logs <name-or-id> [--type deployed|terminal|console] [--lines <number>]'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (logType === 'deployed') {
|
|
135
|
+
await viewDeployedLogs({ id: logsId, type: 'runtime' });
|
|
136
|
+
} else if (logType === 'console') {
|
|
137
|
+
await getBrowserConsoleLogs(logsId, { lines: logLines });
|
|
138
|
+
} else {
|
|
139
|
+
// Default: terminal logs
|
|
140
|
+
await getTerminalLogs(logsId, { lines: logLines });
|
|
141
|
+
}
|
|
112
142
|
break;
|
|
143
|
+
}
|
|
113
144
|
case 'path':
|
|
114
145
|
await getInterfacePath(args[0]);
|
|
115
146
|
break;
|
|
147
|
+
case 'screenshot': {
|
|
148
|
+
const interfaceId = args[0];
|
|
149
|
+
if (!interfaceId) {
|
|
150
|
+
console.log(chalk.red('Error: interface name or ID is required'));
|
|
151
|
+
console.log(chalk.dim('Usage: lux i screenshot <name-or-id>'));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
await screenshot(interfaceId, args[1]);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'click': {
|
|
158
|
+
const interfaceId = args[0];
|
|
159
|
+
const selector = args[1];
|
|
160
|
+
if (!interfaceId || !selector) {
|
|
161
|
+
console.log(chalk.red('Error: interface name or ID and selector are required'));
|
|
162
|
+
console.log(chalk.dim('Usage: lux i click <name-or-id> <selector>'));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await click(interfaceId, selector);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case 'type': {
|
|
169
|
+
const interfaceId = args[0];
|
|
170
|
+
const selector = args[1];
|
|
171
|
+
const text = args.slice(2).join(' ');
|
|
172
|
+
if (!interfaceId || !selector || !text) {
|
|
173
|
+
console.log(chalk.red('Error: interface name or ID, selector, and text are required'));
|
|
174
|
+
console.log(chalk.dim('Usage: lux i type <name-or-id> <selector> <text>'));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
await typeText(interfaceId, selector, text);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'eval': {
|
|
181
|
+
const interfaceId = args[0];
|
|
182
|
+
const code = args.slice(1).join(' ');
|
|
183
|
+
if (!interfaceId || !code) {
|
|
184
|
+
console.log(chalk.red('Error: interface name or ID and code are required'));
|
|
185
|
+
console.log(chalk.dim('Usage: lux i eval <name-or-id> <code>'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await evaluate(interfaceId, code);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'url': {
|
|
192
|
+
const interfaceId = args[0];
|
|
193
|
+
if (!interfaceId) {
|
|
194
|
+
console.log(chalk.red('Error: interface name or ID is required'));
|
|
195
|
+
console.log(chalk.dim('Usage: lux i url <name-or-id>'));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
await getUrl(interfaceId);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case 'nav':
|
|
202
|
+
case 'navigate': {
|
|
203
|
+
const interfaceId = args[0];
|
|
204
|
+
const url = args[1];
|
|
205
|
+
if (!interfaceId || !url) {
|
|
206
|
+
console.log(chalk.red('Error: interface name or ID and URL are required'));
|
|
207
|
+
console.log(chalk.dim('Usage: lux i nav <name-or-id> <url>'));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
await navigate(interfaceId, url);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'wait': {
|
|
214
|
+
const interfaceId = args[0];
|
|
215
|
+
const ms = args[1];
|
|
216
|
+
if (!interfaceId || !ms) {
|
|
217
|
+
console.log(chalk.red('Error: interface name or ID and duration are required'));
|
|
218
|
+
console.log(chalk.dim('Usage: lux i wait <name-or-id> <ms>'));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
await wait(interfaceId, ms);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case 'preview': {
|
|
225
|
+
const interfaceId = args[0];
|
|
226
|
+
if (!interfaceId) {
|
|
227
|
+
console.log(chalk.red('Error: interface name or ID is required'));
|
|
228
|
+
console.log(chalk.dim('Usage: lux i preview <name-or-id>'));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
await startPreview(interfaceId);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case 'servers':
|
|
235
|
+
await handleServers(args);
|
|
236
|
+
break;
|
|
237
|
+
case 'ab-tests':
|
|
238
|
+
case 'experiments':
|
|
239
|
+
await handleABTests(args);
|
|
240
|
+
break;
|
|
116
241
|
default:
|
|
117
242
|
console.log(chalk.red(`Unknown subcommand: ${subcommand}`));
|
|
118
243
|
showHelp();
|
package/commands/knowledge.js
CHANGED
|
@@ -27,7 +27,7 @@ async function handleKnowledge(args) {
|
|
|
27
27
|
// Check authentication
|
|
28
28
|
if (!isAuthenticated()) {
|
|
29
29
|
console.log(
|
|
30
|
-
chalk.red('
|
|
30
|
+
chalk.red('Not authenticated. Run'),
|
|
31
31
|
chalk.white('lux login'),
|
|
32
32
|
chalk.red('first.')
|
|
33
33
|
);
|
|
@@ -97,13 +97,13 @@ ${chalk.bold('Examples:')}
|
|
|
97
97
|
function printTree(nodes, indent = '') {
|
|
98
98
|
for (const node of nodes) {
|
|
99
99
|
if (node.type === 'folder') {
|
|
100
|
-
console.log(`${indent}${chalk.blue('
|
|
100
|
+
console.log(`${indent}${chalk.blue('[dir]')} ${chalk.bold(node.name)}/`);
|
|
101
101
|
if (node.children && node.children.length > 0) {
|
|
102
102
|
printTree(node.children, indent + ' ');
|
|
103
103
|
}
|
|
104
104
|
} else {
|
|
105
105
|
const size = node.size ? ` (${formatFileSize(node.size)})` : '';
|
|
106
|
-
console.log(`${indent}${chalk.gray('
|
|
106
|
+
console.log(`${indent}${chalk.gray('-')} ${node.name}${chalk.gray(size)}`);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
}
|
package/commands/list.js
CHANGED
|
@@ -6,7 +6,7 @@ async function list(options) {
|
|
|
6
6
|
// Check authentication
|
|
7
7
|
if (!isAuthenticated()) {
|
|
8
8
|
console.log(
|
|
9
|
-
chalk.red('
|
|
9
|
+
chalk.red('Not authenticated. Run'),
|
|
10
10
|
chalk.white('lux login'),
|
|
11
11
|
chalk.red('first.')
|
|
12
12
|
);
|
|
@@ -26,43 +26,25 @@ async function list(options) {
|
|
|
26
26
|
console.log(chalk.yellow('\nNo interfaces found.'));
|
|
27
27
|
console.log(
|
|
28
28
|
chalk.dim('Create one with:'),
|
|
29
|
-
chalk.white('lux
|
|
30
|
-
chalk.dim('and'),
|
|
31
|
-
chalk.white('lux up\n')
|
|
29
|
+
chalk.white('lux i create --name <name>\n')
|
|
32
30
|
);
|
|
33
31
|
return;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
console.log(chalk.cyan(`\
|
|
34
|
+
console.log(chalk.cyan(`\nInterfaces (${interfaces.length})\n`));
|
|
37
35
|
|
|
38
36
|
for (const iface of interfaces) {
|
|
39
37
|
const status = getStatusDisplay(iface.status);
|
|
40
38
|
|
|
41
39
|
console.log(
|
|
42
|
-
|
|
40
|
+
` ${chalk.white(iface.name)} ${chalk.dim(iface.description || 'No description')} ${chalk.dim(iface.id)} ${status}`
|
|
43
41
|
);
|
|
44
|
-
console.log(
|
|
45
|
-
` ${status} ${chalk.dim(iface.description || 'No description')}`
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
if (iface.vercel_deployment_url) {
|
|
49
|
-
console.log(` ${chalk.cyan(iface.vercel_deployment_url)}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (iface.github_repo_url) {
|
|
53
|
-
console.log(` ${chalk.dim(iface.github_repo_url)}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
console.log('');
|
|
57
42
|
}
|
|
58
43
|
|
|
59
|
-
console.log(
|
|
60
|
-
chalk.dim('Link to an interface: '),
|
|
61
|
-
chalk.white('lux link <interface-id>\n')
|
|
62
|
-
);
|
|
44
|
+
console.log('');
|
|
63
45
|
} catch (error) {
|
|
64
46
|
console.error(
|
|
65
|
-
chalk.red('
|
|
47
|
+
chalk.red('Error:'),
|
|
66
48
|
error.response?.data?.error || error.message
|
|
67
49
|
);
|
|
68
50
|
|
package/commands/login.js
CHANGED
|
@@ -4,7 +4,7 @@ const chalk = require('chalk');
|
|
|
4
4
|
const ora = require('ora');
|
|
5
5
|
const inquirer = require('inquirer');
|
|
6
6
|
const axios = require('axios');
|
|
7
|
-
const {
|
|
7
|
+
const { getApiUrl, loadConfig, loadUnifiedConfig, saveUnifiedConfig } = require('../lib/config');
|
|
8
8
|
|
|
9
9
|
async function login(options) {
|
|
10
10
|
// Non-interactive mode with --key flag
|
|
@@ -19,8 +19,8 @@ async function login(options) {
|
|
|
19
19
|
name: 'method',
|
|
20
20
|
message: 'How do you want to authenticate?',
|
|
21
21
|
choices: [
|
|
22
|
-
{ name: '
|
|
23
|
-
{ name: '
|
|
22
|
+
{ name: 'Browser (recommended)', value: 'browser' },
|
|
23
|
+
{ name: 'Enter API key manually', value: 'manual' },
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
26
|
]);
|
|
@@ -36,7 +36,7 @@ async function browserLogin() {
|
|
|
36
36
|
const app = express();
|
|
37
37
|
const port = 8976;
|
|
38
38
|
|
|
39
|
-
console.log(chalk.cyan('\
|
|
39
|
+
console.log(chalk.cyan('\nAuthenticating with Lux...\n'));
|
|
40
40
|
|
|
41
41
|
return new Promise((resolve, reject) => {
|
|
42
42
|
let tokenReceived = false;
|
|
@@ -58,7 +58,7 @@ async function browserLogin() {
|
|
|
58
58
|
</head>
|
|
59
59
|
<body>
|
|
60
60
|
<div class="container">
|
|
61
|
-
<div class="error"
|
|
61
|
+
<div class="error">Authentication Failed</div>
|
|
62
62
|
<p>Please try again from your terminal.</p>
|
|
63
63
|
</div>
|
|
64
64
|
</body>
|
|
@@ -95,7 +95,7 @@ async function browserLogin() {
|
|
|
95
95
|
</head>
|
|
96
96
|
<body>
|
|
97
97
|
<div class="container">
|
|
98
|
-
<div class="success"
|
|
98
|
+
<div class="success">Authentication Successful!</div>
|
|
99
99
|
<p>You can now close this window and return to your terminal.</p>
|
|
100
100
|
<p class="info">Org ID: ${orgId}</p>
|
|
101
101
|
</div>
|
|
@@ -115,7 +115,7 @@ async function browserLogin() {
|
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
117
|
await open(authUrl);
|
|
118
|
-
console.log(chalk.yellow('
|
|
118
|
+
console.log(chalk.yellow('Waiting for authentication...\n'));
|
|
119
119
|
} catch (error) {
|
|
120
120
|
console.log(chalk.red('Could not open browser automatically.'));
|
|
121
121
|
console.log(chalk.yellow(`\nPlease open this URL in your browser:\n`));
|
|
@@ -135,13 +135,13 @@ async function browserLogin() {
|
|
|
135
135
|
);
|
|
136
136
|
})
|
|
137
137
|
.then((config) => {
|
|
138
|
-
console.log(chalk.green('
|
|
138
|
+
console.log(chalk.green('Successfully authenticated!\n'));
|
|
139
139
|
console.log(chalk.dim(`Org ID: ${config.orgId}\n`));
|
|
140
140
|
console.log(chalk.cyan('You can now use Lux CLI commands.\n'));
|
|
141
141
|
process.exit(0);
|
|
142
142
|
})
|
|
143
143
|
.catch((error) => {
|
|
144
|
-
console.error(chalk.red('\
|
|
144
|
+
console.error(chalk.red('\nAuthentication failed:'), error.message);
|
|
145
145
|
process.exit(1);
|
|
146
146
|
});
|
|
147
147
|
}
|
|
@@ -176,28 +176,33 @@ async function manualLogin(providedKey) {
|
|
|
176
176
|
try {
|
|
177
177
|
const apiUrl = getApiUrl();
|
|
178
178
|
|
|
179
|
-
// Validate the API key
|
|
180
|
-
const response = await axios.
|
|
179
|
+
// Validate the API key via the proper user-auth endpoint (SHA-256 hashed lookup)
|
|
180
|
+
const response = await axios.post(`${apiUrl}/api/user-auth/token/validate`, null, {
|
|
181
181
|
headers: {
|
|
182
182
|
Authorization: `Bearer ${apiKey}`,
|
|
183
|
-
'X-Org-Id': extractOrgIdFromKey(apiKey),
|
|
184
183
|
},
|
|
185
184
|
});
|
|
186
185
|
|
|
187
|
-
if (response.status === 200) {
|
|
188
|
-
spinner.succeed(chalk.green('
|
|
186
|
+
if (response.status === 200 && response.data?.valid) {
|
|
187
|
+
spinner.succeed(chalk.green('API key validated successfully!'));
|
|
189
188
|
|
|
190
|
-
// Save config
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
// Save credentials to unified config
|
|
190
|
+
const orgId = response.data.org_id || extractOrgIdFromKey(apiKey);
|
|
191
|
+
const unified = loadUnifiedConfig();
|
|
192
|
+
if (!unified.orgs) unified.orgs = {};
|
|
193
|
+
if (!unified.orgs[orgId]) unified.orgs[orgId] = {};
|
|
194
|
+
unified.orgs[orgId].apiKey = apiKey;
|
|
195
|
+
unified.currentOrg = orgId;
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
// Restore currentProject if the org has projects and none is set
|
|
198
|
+
if (!unified.currentProject && unified.orgs[orgId]?.projects?.length) {
|
|
199
|
+
unified.currentProject = unified.orgs[orgId].projects[0];
|
|
200
|
+
}
|
|
198
201
|
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
saveUnifiedConfig(unified);
|
|
203
|
+
|
|
204
|
+
console.log(chalk.green('\nSuccessfully authenticated!\n'));
|
|
205
|
+
console.log(chalk.dim(`Org ID: ${orgId}\n`));
|
|
201
206
|
console.log(chalk.cyan('You can now use Lux CLI commands.\n'));
|
|
202
207
|
}
|
|
203
208
|
} catch (error) {
|
|
@@ -205,11 +210,11 @@ async function manualLogin(providedKey) {
|
|
|
205
210
|
|
|
206
211
|
if (error.response?.status === 401 || error.response?.status === 403) {
|
|
207
212
|
console.error(
|
|
208
|
-
chalk.red('\
|
|
213
|
+
chalk.red('\nInvalid or expired API key. Please check your key and try again.\n')
|
|
209
214
|
);
|
|
210
215
|
} else {
|
|
211
216
|
console.error(
|
|
212
|
-
chalk.red('\
|
|
217
|
+
chalk.red('\nError:'),
|
|
213
218
|
error.response?.data?.error || error.message
|
|
214
219
|
);
|
|
215
220
|
}
|
|
@@ -238,7 +243,7 @@ async function logout() {
|
|
|
238
243
|
const { saveConfig } = require('../lib/config');
|
|
239
244
|
saveConfig({});
|
|
240
245
|
|
|
241
|
-
console.log(chalk.green('
|
|
246
|
+
console.log(chalk.green('Successfully logged out'));
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
module.exports = {
|