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 ADDED
@@ -0,0 +1,37 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024-2025 Lux AI Labs. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary and confidential property of Lux AI Labs.
7
+
8
+ RESTRICTIONS:
9
+
10
+ 1. You may NOT copy, modify, merge, publish, distribute, sublicense, and/or
11
+ sell copies of the Software without explicit written permission from
12
+ Lux AI Labs.
13
+
14
+ 2. You may NOT reverse engineer, decompile, or disassemble the Software.
15
+
16
+ 3. You may NOT use the Software to create derivative works.
17
+
18
+ 4. You may NOT remove or alter any proprietary notices, labels, or marks
19
+ on the Software.
20
+
21
+ PERMITTED USE:
22
+
23
+ You are granted a limited, non-exclusive, non-transferable license to use
24
+ the Software solely for your internal business purposes in connection with
25
+ the Lux platform, subject to the terms and conditions of your agreement
26
+ with Lux AI Labs.
27
+
28
+ DISCLAIMER:
29
+
30
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
33
+ LUX AI LABS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
34
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
35
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36
+
37
+ For licensing inquiries, contact: sam@uselux.ai
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # luxlabs
2
+
3
+ Official CLI tool for Lux - Upload and deploy interfaces from your terminal.
4
+
5
+ **Author:** Jason Henkel at Lux Labs
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g luxlabs
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # Authenticate with Lux
17
+ lux login
18
+
19
+ # Initialize a new interface project
20
+ lux interface init
21
+
22
+ # Start local development with hot reload
23
+ lux dev
24
+
25
+ # Deploy your interface
26
+ lux interface up
27
+ ```
28
+
29
+ ## Commands
30
+
31
+ ### Authentication
32
+
33
+ | Command | Description |
34
+ |---------|-------------|
35
+ | `lux login` | Authenticate with Lux (opens browser) |
36
+ | `lux login --key <api-key>` | Authenticate with an API key |
37
+ | `lux logout` | Log out from Lux |
38
+
39
+ ### Interface Management
40
+
41
+ | Command | Description |
42
+ |---------|-------------|
43
+ | `lux interface init` | Initialize a new interface project |
44
+ | `lux interface up` | Upload and deploy your interface |
45
+ | `lux interface list` | List all your interfaces |
46
+ | `lux interface link` | Link current directory to an interface |
47
+ | `lux i <subcommand>` | Shorthand alias for interface commands |
48
+
49
+ ### Development
50
+
51
+ | Command | Description |
52
+ |---------|-------------|
53
+ | `lux dev` | Start local dev server with tunnel |
54
+ | `lux dev -p <port>` | Specify custom port (default: 3000) |
55
+ | `lux dev --no-tunnel` | Disable tunnel (local only) |
56
+ | `lux servers` | List running dev servers |
57
+ | `lux logs <interface>` | View logs from a dev server |
58
+
59
+ ### Data Management
60
+
61
+ | Command | Description |
62
+ |---------|-------------|
63
+ | `lux data tables` | Manage database tables |
64
+ | `lux data kv` | Manage KV namespaces |
65
+
66
+ ### Storage
67
+
68
+ | Command | Description |
69
+ |---------|-------------|
70
+ | `lux storage ls` | List files in storage |
71
+ | `lux storage get <key>` | Download a file |
72
+ | `lux storage put <key> <file>` | Upload a file |
73
+ | `lux storage rm <key>` | Delete a file |
74
+
75
+ ### Workflows
76
+
77
+ | Command | Description |
78
+ |---------|-------------|
79
+ | `lux workflows list` | List all workflows |
80
+ | `lux workflows get <id>` | Get workflow details |
81
+ | `lux workflows create` | Create a new workflow |
82
+ | `lux workflows publish <id>` | Publish a workflow |
83
+ | `lux flow <subcommand>` | Shorthand alias |
84
+
85
+ ### Agents
86
+
87
+ | Command | Description |
88
+ |---------|-------------|
89
+ | `lux agent list` | List all agents |
90
+ | `lux agent get <id>` | Get agent details |
91
+ | `lux agent create` | Create a new agent |
92
+ | `lux agent prompt <id>` | View/edit agent prompt |
93
+
94
+ ### Knowledge Base
95
+
96
+ | Command | Description |
97
+ |---------|-------------|
98
+ | `lux knowledge list` | List knowledge bases |
99
+ | `lux knowledge upload <file>` | Upload to knowledge base |
100
+ | `lux kb <subcommand>` | Shorthand alias |
101
+
102
+ ### Voice Agents
103
+
104
+ | Command | Description |
105
+ |---------|-------------|
106
+ | `lux voice-agents list` | List voice agents |
107
+ | `lux voice-agents create` | Create a voice agent |
108
+ | `lux va <subcommand>` | Shorthand alias |
109
+
110
+ ### Secrets
111
+
112
+ | Command | Description |
113
+ |---------|-------------|
114
+ | `lux secrets list` | List organization secrets |
115
+ | `lux secrets set <key> <value>` | Set a secret |
116
+ | `lux secrets get <key>` | Get a secret value |
117
+ | `lux secrets delete <key>` | Delete a secret |
118
+
119
+ ### A/B Tests
120
+
121
+ | Command | Description |
122
+ |---------|-------------|
123
+ | `lux ab-tests list-tests` | List all A/B tests |
124
+ | `lux ab-tests get-test <id>` | Get test details |
125
+ | `lux experiments <subcommand>` | Alias for ab-tests |
126
+
127
+ ### Project Deployment
128
+
129
+ | Command | Description |
130
+ |---------|-------------|
131
+ | `lux project deploy` | Deploy project to GitHub |
132
+ | `lux proj <subcommand>` | Shorthand alias |
133
+
134
+ ### Preview & Testing
135
+
136
+ | Command | Description |
137
+ |---------|-------------|
138
+ | `lux preview <interface-id>` | Start interface preview |
139
+ | `lux screenshot <interface-id>` | Take a screenshot |
140
+ | `lux click <interface-id> <selector>` | Click an element |
141
+ | `lux type <interface-id> <selector> <text>` | Type into an element |
142
+ | `lux eval <interface-id> <code>` | Execute JavaScript |
143
+ | `lux url <interface-id>` | Get current URL |
144
+ | `lux nav <interface-id> <url>` | Navigate to URL |
145
+
146
+ ## Requirements
147
+
148
+ - Node.js >= 18.0.0
149
+ - npm or yarn
150
+
151
+ ## Support
152
+
153
+ For support and documentation, visit [uselux.ai](https://uselux.ai)
154
+
155
+ For issues, visit [GitHub Issues](https://github.com/luxAILabs/lux-studio/issues)
156
+
157
+ ## License
158
+
159
+ Proprietary - See LICENSE file for details.
160
+
161
+ Copyright (c) 2024-2025 Lux AI Labs. All Rights Reserved.
@@ -0,0 +1,437 @@
1
+ /**
2
+ * A/B Tests Commands
3
+ *
4
+ * CLI commands for reading A/B test configurations.
5
+ * These are primarily used by Claude Code to understand what variants exist
6
+ * and what they should do (via the descriptions).
7
+ *
8
+ * Commands:
9
+ * - lux ab-tests list-tests [interface] List all A/B tests
10
+ * - lux ab-tests get-test <key> [interface] Get details for a specific test
11
+ * - lux ab-tests get-variant <key> <variant> [interface] Get details for a specific variant
12
+ */
13
+
14
+ const chalk = require('chalk');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ /**
19
+ * Show help for ab-tests commands
20
+ */
21
+ function showHelp() {
22
+ console.log(chalk.cyan('\nLux A/B Tests Commands:\n'));
23
+ console.log(chalk.white(' lux ab-tests list-tests [interface]'));
24
+ console.log(chalk.dim(' List all A/B tests for an interface'));
25
+ console.log(chalk.dim(' Options: --json (JSON output)\n'));
26
+ console.log(chalk.white(' lux ab-tests get-test <test-key> [interface]'));
27
+ console.log(chalk.dim(' Get details for a specific A/B test'));
28
+ console.log(chalk.dim(' Options: --json (JSON output)\n'));
29
+ console.log(chalk.white(' lux ab-tests get-variant <test-key> <variant-key> [interface]'));
30
+ console.log(chalk.dim(' Get details for a specific variant within a test'));
31
+ console.log(chalk.dim(' Options: --json (JSON output)\n'));
32
+ }
33
+
34
+ /**
35
+ * Get the path to ab-tests.json for an interface
36
+ * Supports both interface ID and interface name
37
+ */
38
+ function getABTestsPath(interfaceIdentifier) {
39
+ const interfacesDir = path.join(process.cwd(), 'interfaces');
40
+
41
+ if (!fs.existsSync(interfacesDir)) {
42
+ return null;
43
+ }
44
+
45
+ // If no identifier provided, try to find the only interface
46
+ if (!interfaceIdentifier) {
47
+ const entries = fs.readdirSync(interfacesDir, { withFileTypes: true });
48
+ const dirs = entries.filter(e => e.isDirectory());
49
+
50
+ if (dirs.length === 0) {
51
+ return null;
52
+ }
53
+
54
+ if (dirs.length === 1) {
55
+ // Auto-select the only interface
56
+ interfaceIdentifier = dirs[0].name;
57
+ } else {
58
+ // Multiple interfaces - need to specify
59
+ console.log(chalk.yellow('Multiple interfaces found. Please specify which one:'));
60
+ for (const dir of dirs) {
61
+ const metaPath = path.join(interfacesDir, dir.name, 'metadata.json');
62
+ let name = dir.name;
63
+ if (fs.existsSync(metaPath)) {
64
+ try {
65
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
66
+ name = meta.name || dir.name;
67
+ } catch (e) { /* ignore */ }
68
+ }
69
+ console.log(chalk.dim(` - ${name} (${dir.name})`));
70
+ }
71
+ return null;
72
+ }
73
+ }
74
+
75
+ // Check if it's a direct interface ID (directory exists)
76
+ let interfaceDir = path.join(interfacesDir, interfaceIdentifier, 'repo');
77
+ if (fs.existsSync(interfaceDir)) {
78
+ return path.join(interfaceDir, '.lux', 'ab-tests.json');
79
+ }
80
+
81
+ // Try to find by name
82
+ const entries = fs.readdirSync(interfacesDir, { withFileTypes: true });
83
+ for (const entry of entries) {
84
+ if (!entry.isDirectory()) continue;
85
+
86
+ const metaPath = path.join(interfacesDir, entry.name, 'metadata.json');
87
+ if (fs.existsSync(metaPath)) {
88
+ try {
89
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
90
+ if (meta.name && meta.name.toLowerCase() === interfaceIdentifier.toLowerCase()) {
91
+ return path.join(interfacesDir, entry.name, 'repo', '.lux', 'ab-tests.json');
92
+ }
93
+ } catch (e) { /* ignore */ }
94
+ }
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * Get interface name from identifier
102
+ */
103
+ function getInterfaceName(interfaceIdentifier) {
104
+ const interfacesDir = path.join(process.cwd(), 'interfaces');
105
+
106
+ if (!interfaceIdentifier) {
107
+ const entries = fs.readdirSync(interfacesDir, { withFileTypes: true });
108
+ const dirs = entries.filter(e => e.isDirectory());
109
+ if (dirs.length === 1) {
110
+ interfaceIdentifier = dirs[0].name;
111
+ }
112
+ }
113
+
114
+ if (!interfaceIdentifier) return 'Unknown';
115
+
116
+ const metaPath = path.join(interfacesDir, interfaceIdentifier, 'metadata.json');
117
+ if (fs.existsSync(metaPath)) {
118
+ try {
119
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
120
+ return meta.name || interfaceIdentifier;
121
+ } catch (e) { /* ignore */ }
122
+ }
123
+
124
+ return interfaceIdentifier;
125
+ }
126
+
127
+ /**
128
+ * Load tests from file
129
+ */
130
+ function loadTests(interfaceId) {
131
+ const testsPath = getABTestsPath(interfaceId);
132
+
133
+ if (!testsPath) {
134
+ return { error: 'Could not find interface. Make sure you are in a Lux project directory.' };
135
+ }
136
+
137
+ if (!fs.existsSync(testsPath)) {
138
+ return { tests: [], empty: true };
139
+ }
140
+
141
+ try {
142
+ const tests = JSON.parse(fs.readFileSync(testsPath, 'utf-8'));
143
+ return { tests, testsPath };
144
+ } catch (e) {
145
+ return { error: `Failed to parse ab-tests.json: ${e.message}` };
146
+ }
147
+ }
148
+
149
+ /**
150
+ * List all A/B tests for an interface
151
+ */
152
+ async function listTests(interfaceId, options) {
153
+ const result = loadTests(interfaceId);
154
+
155
+ if (result.error) {
156
+ console.log(chalk.red(result.error));
157
+ return;
158
+ }
159
+
160
+ if (result.empty) {
161
+ if (options.json) {
162
+ console.log(JSON.stringify({ tests: [] }, null, 2));
163
+ } else {
164
+ console.log(chalk.yellow('No A/B tests found for this interface.'));
165
+ console.log(chalk.dim('Create tests in the Settings tab of your interface.'));
166
+ }
167
+ return;
168
+ }
169
+
170
+ const { tests } = result;
171
+
172
+ if (options.json) {
173
+ console.log(JSON.stringify({ tests }, null, 2));
174
+ return;
175
+ }
176
+
177
+ const interfaceName = getInterfaceName(interfaceId);
178
+ console.log(chalk.cyan(`\nA/B Tests for "${interfaceName}":`));
179
+
180
+ if (tests.length === 0) {
181
+ console.log(chalk.yellow('\nNo tests configured.'));
182
+ return;
183
+ }
184
+
185
+ for (const test of tests) {
186
+ console.log();
187
+ console.log(chalk.cyan(`📊 ${test.name}`) + chalk.dim(` (key: ${test.key})`));
188
+ console.log(chalk.dim(` Status: ${test.status}${test.posthogFlagId ? ` (PostHog flag: ${test.posthogFlagId})` : ''}`));
189
+
190
+ if (test.description) {
191
+ console.log(chalk.white(` Description: ${test.description}`));
192
+ }
193
+
194
+ // Show referenced components if any
195
+ if (test.referencedComponents && test.referencedComponents.length > 0) {
196
+ console.log(chalk.dim(` Referenced Components: ${test.referencedComponents.join(', ')}`));
197
+ }
198
+
199
+ console.log(chalk.dim('\n Variants:'));
200
+ for (const v of test.variants) {
201
+ const desc = v.description || 'No description';
202
+ console.log(chalk.white(` • ${v.key} (${v.percentage}%)`) + chalk.dim(` - ${desc}`));
203
+ }
204
+ }
205
+
206
+ console.log();
207
+ }
208
+
209
+ /**
210
+ * Get details for a specific A/B test
211
+ */
212
+ async function getTest(testKey, interfaceId, options) {
213
+ const result = loadTests(interfaceId);
214
+
215
+ if (result.error) {
216
+ console.log(chalk.red(result.error));
217
+ return;
218
+ }
219
+
220
+ if (result.empty) {
221
+ console.log(chalk.red('No A/B tests found for this interface.'));
222
+ return;
223
+ }
224
+
225
+ const { tests } = result;
226
+ const test = tests.find(t => t.key === testKey);
227
+
228
+ if (!test) {
229
+ console.log(chalk.red(`Test "${testKey}" not found.`));
230
+ console.log(chalk.dim('\nAvailable tests:'));
231
+ for (const t of tests) {
232
+ console.log(chalk.dim(` - ${t.key} (${t.name})`));
233
+ }
234
+ return;
235
+ }
236
+
237
+ if (options.json) {
238
+ console.log(JSON.stringify({ test }, null, 2));
239
+ return;
240
+ }
241
+
242
+ console.log();
243
+ console.log(chalk.cyan(`📊 ${test.key}`));
244
+ console.log(chalk.white(` Name: ${test.name}`));
245
+ console.log(chalk.dim(` Status: ${test.status}${test.posthogFlagId ? ` (PostHog flag: ${test.posthogFlagId})` : ''}`));
246
+
247
+ if (test.description) {
248
+ console.log(chalk.white(` Description: ${test.description}`));
249
+ }
250
+
251
+ // Show referenced components if any
252
+ if (test.referencedComponents && test.referencedComponents.length > 0) {
253
+ console.log(chalk.cyan('\n Referenced Components:'));
254
+ for (const comp of test.referencedComponents) {
255
+ console.log(chalk.white(` • ${comp}`));
256
+ }
257
+ }
258
+
259
+ console.log(chalk.dim(`\n Variants (${test.variants.length}):`));
260
+ for (const v of test.variants) {
261
+ console.log(chalk.white(` • ${v.key} (${v.percentage}%)`));
262
+ if (v.description) {
263
+ console.log(chalk.dim(` ${v.description}`));
264
+ }
265
+ }
266
+
267
+ // Show implementation pattern
268
+ const nonControlVariants = test.variants.filter(v => v.key !== 'current' && v.key !== 'control');
269
+ if (nonControlVariants.length > 0) {
270
+ console.log(chalk.dim('\n Implementation Pattern:'));
271
+ console.log(chalk.gray(` import { useFeatureFlagVariantKey } from 'posthog-js/react';`));
272
+ console.log(chalk.gray(` const variant = useFeatureFlagVariantKey('${test.key}');`));
273
+ for (const v of nonControlVariants) {
274
+ const comment = v.description ? ` /* ${v.description} */` : '';
275
+ console.log(chalk.gray(` if (variant === '${v.key}') {${comment} }`));
276
+ }
277
+ }
278
+
279
+ console.log();
280
+ }
281
+
282
+ /**
283
+ * Get details for a specific variant within a test
284
+ */
285
+ async function getVariant(testKey, variantKey, interfaceId, options) {
286
+ const result = loadTests(interfaceId);
287
+
288
+ if (result.error) {
289
+ console.log(chalk.red(result.error));
290
+ return;
291
+ }
292
+
293
+ if (result.empty) {
294
+ console.log(chalk.red('No A/B tests found for this interface.'));
295
+ return;
296
+ }
297
+
298
+ const { tests } = result;
299
+ const test = tests.find(t => t.key === testKey);
300
+
301
+ if (!test) {
302
+ console.log(chalk.red(`Test "${testKey}" not found.`));
303
+ console.log(chalk.dim('\nAvailable tests:'));
304
+ for (const t of tests) {
305
+ console.log(chalk.dim(` - ${t.key} (${t.name})`));
306
+ }
307
+ return;
308
+ }
309
+
310
+ const variant = test.variants.find(v => v.key === variantKey);
311
+
312
+ if (!variant) {
313
+ console.log(chalk.red(`Variant "${variantKey}" not found in test "${testKey}".`));
314
+ console.log(chalk.dim('\nAvailable variants:'));
315
+ for (const v of test.variants) {
316
+ console.log(chalk.dim(` - ${v.key} (${v.name || v.key})`));
317
+ }
318
+ return;
319
+ }
320
+
321
+ if (options.json) {
322
+ console.log(JSON.stringify({
323
+ test: { key: test.key, name: test.name, description: test.description },
324
+ variant
325
+ }, null, 2));
326
+ return;
327
+ }
328
+
329
+ console.log();
330
+ console.log(chalk.cyan(`🎯 Variant: ${variant.key}`));
331
+ console.log(chalk.dim(` Test: ${test.name} (${test.key})`));
332
+ console.log(chalk.white(` Name: ${variant.name || variant.key}`));
333
+ console.log(chalk.dim(` Traffic: ${variant.percentage}%`));
334
+
335
+ if (variant.description) {
336
+ console.log(chalk.cyan('\n Description (what this variant should do):'));
337
+ console.log(chalk.white(` ${variant.description}`));
338
+ } else {
339
+ console.log(chalk.yellow('\n No description set for this variant.'));
340
+ }
341
+
342
+ // Show referenced components for this variant if any
343
+ if (variant.referencedComponents && variant.referencedComponents.length > 0) {
344
+ console.log(chalk.cyan('\n Referenced Components:'));
345
+ for (const comp of variant.referencedComponents) {
346
+ console.log(chalk.white(` • ${comp}`));
347
+ }
348
+ }
349
+
350
+ // Show implementation hint
351
+ const isControl = variant.key === 'current' || variant.key === 'control';
352
+ if (!isControl) {
353
+ console.log(chalk.dim('\n Implementation:'));
354
+ console.log(chalk.gray(` import { useFeatureFlagVariantKey } from 'posthog-js/react';`));
355
+ console.log(chalk.gray(` const variant = useFeatureFlagVariantKey('${test.key}');`));
356
+ console.log(chalk.gray(` if (variant === '${variant.key}') {`));
357
+ if (variant.description) {
358
+ console.log(chalk.gray(` // ${variant.description}`));
359
+ }
360
+ console.log(chalk.gray(` }`));
361
+ } else {
362
+ console.log(chalk.dim('\n Note: This is the control/baseline variant (default behavior).'));
363
+ }
364
+
365
+ console.log();
366
+ }
367
+
368
+ /**
369
+ * Parse command options from args array
370
+ */
371
+ function parseOptions(args) {
372
+ const options = { json: false };
373
+ const remaining = [];
374
+
375
+ for (const arg of args) {
376
+ if (arg === '--json') {
377
+ options.json = true;
378
+ } else if (!arg.startsWith('-')) {
379
+ remaining.push(arg);
380
+ }
381
+ }
382
+
383
+ return { options, remaining };
384
+ }
385
+
386
+ /**
387
+ * Handle ab-tests commands
388
+ */
389
+ async function handleABTests(args) {
390
+ const subcommand = args[0];
391
+ const subArgs = args.slice(1);
392
+
393
+ if (!subcommand || subcommand === 'help') {
394
+ showHelp();
395
+ return;
396
+ }
397
+
398
+ const { options, remaining } = parseOptions(subArgs);
399
+
400
+ switch (subcommand) {
401
+ case 'list-tests':
402
+ case 'list':
403
+ case 'ls':
404
+ await listTests(remaining[0], options);
405
+ break;
406
+
407
+ case 'get-test':
408
+ case 'get':
409
+ if (!remaining[0]) {
410
+ console.log(chalk.red('Missing test key. Usage: lux ab-tests get-test <test-key> [interface]'));
411
+ return;
412
+ }
413
+ await getTest(remaining[0], remaining[1], options);
414
+ break;
415
+
416
+ case 'get-variant':
417
+ if (!remaining[0]) {
418
+ console.log(chalk.red('Missing test key. Usage: lux ab-tests get-variant <test-key> <variant-key> [interface]'));
419
+ return;
420
+ }
421
+ if (!remaining[1]) {
422
+ console.log(chalk.red('Missing variant key. Usage: lux ab-tests get-variant <test-key> <variant-key> [interface]'));
423
+ return;
424
+ }
425
+ await getVariant(remaining[0], remaining[1], remaining[2], options);
426
+ break;
427
+
428
+ default:
429
+ console.log(chalk.red(`Unknown subcommand: ${subcommand}`));
430
+ showHelp();
431
+ process.exit(1);
432
+ }
433
+ }
434
+
435
+ module.exports = {
436
+ handleABTests,
437
+ };