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.
Files changed (37) hide show
  1. package/README.md +16 -21
  2. package/commands/ab-tests.js +14 -11
  3. package/commands/agents.js +11 -11
  4. package/commands/data.js +19 -17
  5. package/commands/deploy.js +145 -82
  6. package/commands/flows.js +152 -133
  7. package/commands/interface/init.js +36 -35
  8. package/commands/interface.js +135 -10
  9. package/commands/knowledge.js +3 -3
  10. package/commands/list.js +6 -24
  11. package/commands/login.js +31 -26
  12. package/commands/logout.js +13 -4
  13. package/commands/logs.js +17 -66
  14. package/commands/project.js +74 -47
  15. package/commands/secrets.js +1 -1
  16. package/commands/servers.js +9 -113
  17. package/commands/storage.js +1 -1
  18. package/commands/tools.js +4 -4
  19. package/commands/validate-data-lux.js +5 -2
  20. package/commands/voice-agents.js +22 -18
  21. package/lib/config.js +235 -83
  22. package/lib/helpers.js +6 -4
  23. package/lux.js +4 -94
  24. package/package.json +6 -1
  25. package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +41 -34
  26. package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +41 -34
  27. package/templates/interface-boilerplate/components/providers/posthog-provider.tsx +41 -26
  28. package/templates/interface-boilerplate/gitignore.template +4 -0
  29. package/templates/interface-boilerplate/lib/auth.config.ts +3 -2
  30. package/templates/interface-boilerplate/lib/knowledge.ts +2 -2
  31. package/templates/interface-boilerplate/middleware.ts +14 -3
  32. package/templates/interface-boilerplate/next-env.d.ts +6 -0
  33. package/templates/interface-boilerplate/package-lock.json +432 -8
  34. package/commands/dev.js +0 -578
  35. package/commands/init.js +0 -126
  36. package/commands/link.js +0 -127
  37. package/commands/up.js +0 -211
package/lib/config.js CHANGED
@@ -3,79 +3,141 @@ const path = require('path');
3
3
  const os = require('os');
4
4
 
5
5
  // Shared storage directory (used by both Electron app and CLI)
6
+ // Must match Electron ConfigService path: ~/.lux-studio on all platforms
6
7
  const LUX_STUDIO_DIR = path.join(os.homedir(), '.lux-studio');
7
- const UNIFIED_CONFIG_FILE = path.join(LUX_STUDIO_DIR, 'config.json');
8
+
9
+ // Unified config file path
10
+ const CONFIG_FILE = path.join(LUX_STUDIO_DIR, 'config.json');
8
11
  const INTERFACE_FILE = '.lux/interface.json';
9
12
 
10
- // Cache for unified config to avoid repeated file reads
11
- let _unifiedConfigCache = null;
12
- let _unifiedConfigMtime = null;
13
+ // Short folder names (to reduce path length for Windows 260 char limit)
14
+ const FOLDER_NAMES = {
15
+ projects: 'p',
16
+ interfaces: 'i',
17
+ flows: 'f',
18
+ 'voice-agents': 'v',
19
+ data: 'd',
20
+ knowledge: 'k',
21
+ };
13
22
 
14
23
  /**
15
- * Load the unified config file (~/.lux-studio/config.json)
16
- * This is the single source of truth used by both Electron app and CLI
24
+ * Load the unified config from ~/.lux-studio/config.json
25
+ * Returns the full config object or default structure
17
26
  */
18
27
  function loadUnifiedConfig() {
19
- if (!fs.existsSync(UNIFIED_CONFIG_FILE)) {
20
- return null;
28
+ if (!fs.existsSync(CONFIG_FILE)) {
29
+ return {
30
+ currentOrg: null,
31
+ currentProject: null,
32
+ orgs: {},
33
+ settings: {},
34
+ };
21
35
  }
22
36
 
23
37
  try {
24
- // Check if file has changed since last read
25
- const stats = fs.statSync(UNIFIED_CONFIG_FILE);
26
- if (_unifiedConfigCache && _unifiedConfigMtime && stats.mtimeMs === _unifiedConfigMtime) {
27
- return _unifiedConfigCache;
28
- }
29
-
30
- const content = fs.readFileSync(UNIFIED_CONFIG_FILE, 'utf8');
31
- _unifiedConfigCache = JSON.parse(content);
32
- _unifiedConfigMtime = stats.mtimeMs;
33
- return _unifiedConfigCache;
38
+ const content = fs.readFileSync(CONFIG_FILE, 'utf8');
39
+ return JSON.parse(content);
34
40
  } catch (error) {
35
- return null;
41
+ return {
42
+ currentOrg: null,
43
+ currentProject: null,
44
+ orgs: {},
45
+ settings: {},
46
+ };
36
47
  }
37
48
  }
38
49
 
39
50
  /**
40
- * Load active org from unified config
41
- * Returns { orgId, orgName } or null
51
+ * Save the unified config to ~/.lux-studio/config.json
42
52
  */
43
- function loadActiveOrg() {
44
- const unifiedConfig = loadUnifiedConfig();
45
- if (!unifiedConfig || !unifiedConfig.currentOrg) {
46
- return null;
53
+ function saveUnifiedConfig(config) {
54
+ const dir = path.dirname(CONFIG_FILE);
55
+ if (!fs.existsSync(dir)) {
56
+ fs.mkdirSync(dir, { recursive: true });
47
57
  }
58
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
59
+ }
48
60
 
49
- const orgId = unifiedConfig.currentOrg;
50
- const orgData = unifiedConfig.orgs?.[orgId];
61
+ /**
62
+ * Register a new short ID for a full UUID with collision detection.
63
+ * This must match the Electron ConfigService.registerShortId logic exactly.
64
+ */
65
+ function registerShortId(fullId) {
66
+ if (!fullId) return fullId;
51
67
 
52
- return {
53
- orgId,
54
- orgName: orgData?.name || null,
55
- };
68
+ const config = loadUnifiedConfig();
69
+ const mappings = config.idMappings || {};
70
+
71
+ // If already registered, return existing
72
+ if (mappings[fullId]) {
73
+ return mappings[fullId];
74
+ }
75
+
76
+ // Remove hyphens from UUID
77
+ const cleanId = fullId.replace(/-/g, '');
78
+
79
+ // Get all existing short IDs for collision detection
80
+ const existingShortIds = new Set(Object.values(mappings));
81
+
82
+ // Try last 8 chars, then 9, 10, etc. until no collision
83
+ let length = 8;
84
+ let shortId = cleanId.slice(-length);
85
+
86
+ while (existingShortIds.has(shortId) && length < cleanId.length) {
87
+ length++;
88
+ shortId = cleanId.slice(-length);
89
+ }
90
+
91
+ // Store the mapping
92
+ config.idMappings = config.idMappings || {};
93
+ config.idMappings[fullId] = shortId;
94
+ saveUnifiedConfig(config);
95
+
96
+ return shortId;
56
97
  }
57
98
 
58
99
  /**
59
- * Load credentials for a specific org from unified config
60
- * Returns { apiKey, orgId, orgName } or null
100
+ * Get the short ID for a full UUID from the config mappings.
101
+ * If no mapping exists, registers a new one with collision detection.
61
102
  */
62
- function loadOrgCredentials(orgId) {
63
- const unifiedConfig = loadUnifiedConfig();
64
- if (!unifiedConfig || !unifiedConfig.orgs?.[orgId]) {
65
- return null;
103
+ function getShortId(fullId) {
104
+ if (!fullId) return fullId;
105
+
106
+ const config = loadUnifiedConfig();
107
+ const mappings = config.idMappings || {};
108
+
109
+ // Return existing mapping if found
110
+ if (mappings[fullId]) {
111
+ return mappings[fullId];
66
112
  }
67
113
 
68
- const orgData = unifiedConfig.orgs[orgId];
69
- return {
70
- apiKey: orgData.apiKey,
71
- orgId: orgId,
72
- orgName: orgData.name || null,
73
- };
114
+ // Register a new short ID with collision detection
115
+ return registerShortId(fullId);
116
+ }
117
+
118
+ /**
119
+ * Get the full UUID from a short ID.
120
+ * Returns null if not found.
121
+ */
122
+ function getFullId(shortId) {
123
+ if (!shortId) return null;
124
+
125
+ const config = loadUnifiedConfig();
126
+ const mappings = config.idMappings || {};
127
+
128
+ // Reverse lookup
129
+ for (const [fullId, mappedShortId] of Object.entries(mappings)) {
130
+ if (mappedShortId === shortId) {
131
+ return fullId;
132
+ }
133
+ }
134
+
135
+ return null;
74
136
  }
75
137
 
76
138
  /**
77
139
  * Load config for the currently active org
78
- * Reads from unified ~/.lux-studio/config.json
140
+ * Returns { apiKey, orgId, orgName } or null
79
141
  */
80
142
  function loadConfig() {
81
143
  // Check for env vars first (for CI/automation)
@@ -86,22 +148,52 @@ function loadConfig() {
86
148
  };
87
149
  }
88
150
 
89
- const unifiedConfig = loadUnifiedConfig();
90
- if (!unifiedConfig || !unifiedConfig.currentOrg) {
151
+ // Load from unified config
152
+ const config = loadUnifiedConfig();
153
+
154
+ if (!config.currentOrg) {
91
155
  return null;
92
156
  }
93
157
 
94
- const orgId = unifiedConfig.currentOrg;
95
- const orgData = unifiedConfig.orgs?.[orgId];
158
+ const orgConfig = config.orgs[config.currentOrg];
159
+ if (!orgConfig || !orgConfig.apiKey) {
160
+ return null;
161
+ }
96
162
 
97
- if (!orgData || !orgData.apiKey) {
163
+ return {
164
+ apiKey: orgConfig.apiKey,
165
+ orgId: config.currentOrg,
166
+ orgName: orgConfig.name,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * @deprecated Use loadConfig() instead - reads from unified config.json
172
+ */
173
+ function loadActiveOrg() {
174
+ const config = loadUnifiedConfig();
175
+ if (!config.currentOrg) {
98
176
  return null;
99
177
  }
178
+ return {
179
+ orgId: config.currentOrg,
180
+ orgName: config.orgs[config.currentOrg]?.name,
181
+ };
182
+ }
100
183
 
184
+ /**
185
+ * @deprecated Use loadConfig() instead - reads from unified config.json
186
+ */
187
+ function loadOrgCredentials(orgId) {
188
+ const config = loadUnifiedConfig();
189
+ const orgConfig = config.orgs[orgId];
190
+ if (!orgConfig) {
191
+ return null;
192
+ }
101
193
  return {
102
- apiKey: orgData.apiKey,
194
+ apiKey: orgConfig.apiKey,
103
195
  orgId: orgId,
104
- orgName: orgData.name || null,
196
+ orgName: orgConfig.name,
105
197
  };
106
198
  }
107
199
 
@@ -203,23 +295,28 @@ function getOrgId() {
203
295
 
204
296
  /**
205
297
  * Get the flows directory for the current org/project
206
- * Returns: ~/.lux-studio/{orgId}/projects/{projectId}/flows/
298
+ * Returns: ~/.lux-studio/{shortOrgId}/p/{shortProjectId}/f/
207
299
  */
208
300
  function getFlowsDir() {
209
301
  const orgId = getOrgId();
210
302
  const projectId = getProjectId();
211
303
  if (!orgId || !projectId) return null;
212
- return path.join(LUX_STUDIO_DIR, orgId, 'projects', projectId, 'flows');
304
+ const shortOrgId = getShortId(orgId);
305
+ const shortProjectId = getShortId(projectId);
306
+ return path.join(LUX_STUDIO_DIR, shortOrgId, FOLDER_NAMES.projects, shortProjectId, FOLDER_NAMES.flows);
213
307
  }
214
308
 
215
309
  /**
216
310
  * Load a flow from local storage
311
+ * New folder structure: flows/{shortFlowId}/draft.json
217
312
  */
218
313
  function loadLocalFlow(flowId) {
219
314
  const flowsDir = getFlowsDir();
220
315
  if (!flowsDir) return null;
221
316
 
222
- const flowPath = path.join(flowsDir, `${flowId}.json`);
317
+ // Use short ID for folder path
318
+ const shortFlowId = getShortId(flowId);
319
+ const flowPath = path.join(flowsDir, shortFlowId, 'draft.json');
223
320
  if (!fs.existsSync(flowPath)) return null;
224
321
 
225
322
  try {
@@ -231,27 +328,31 @@ function loadLocalFlow(flowId) {
231
328
 
232
329
  /**
233
330
  * Save a flow to local storage
234
- * Matches Electron storage service pattern: main file + separate .deployed.json
331
+ * New folder structure: flows/{shortFlowId}/draft.json, deployed.json
235
332
  */
236
333
  function saveLocalFlow(flowId, flowData) {
237
334
  const flowsDir = getFlowsDir();
238
335
  if (!flowsDir) return false;
239
336
 
240
- // Ensure directory exists
241
- if (!fs.existsSync(flowsDir)) {
242
- fs.mkdirSync(flowsDir, { recursive: true });
337
+ // Use short ID for folder path
338
+ const shortFlowId = getShortId(flowId);
339
+ const flowDir = path.join(flowsDir, shortFlowId);
340
+
341
+ // Ensure flow directory exists
342
+ if (!fs.existsSync(flowDir)) {
343
+ fs.mkdirSync(flowDir, { recursive: true });
243
344
  }
244
345
 
245
346
  // Extract deployed data (same pattern as Electron storage service)
246
347
  const { deployedNodes, deployedEdges, ...flowWithoutDeployed } = flowData;
247
348
 
248
- // Write main flow file (without deployed data)
249
- const flowPath = path.join(flowsDir, `${flowId}.json`);
250
- fs.writeFileSync(flowPath, JSON.stringify(flowWithoutDeployed, null, 2));
349
+ // Write draft file (without deployed data)
350
+ const draftPath = path.join(flowDir, 'draft.json');
351
+ fs.writeFileSync(draftPath, JSON.stringify(flowWithoutDeployed, null, 2));
251
352
 
252
353
  // Write deployed data to separate file (if present)
253
354
  if (deployedNodes || deployedEdges) {
254
- const deployedPath = path.join(flowsDir, `${flowId}.deployed.json`);
355
+ const deployedPath = path.join(flowDir, 'deployed.json');
255
356
  fs.writeFileSync(deployedPath, JSON.stringify({
256
357
  deployedNodes: deployedNodes || [],
257
358
  deployedEdges: deployedEdges || [],
@@ -263,17 +364,22 @@ function saveLocalFlow(flowId, flowData) {
263
364
 
264
365
  /**
265
366
  * List all local flows with sync status
367
+ * New folder structure: flows/{shortFlowId}/draft.json
266
368
  */
267
369
  function listLocalFlows() {
268
370
  const flowsDir = getFlowsDir();
269
371
  if (!flowsDir || !fs.existsSync(flowsDir)) return [];
270
372
 
271
373
  const flows = [];
272
- const files = fs.readdirSync(flowsDir).filter(f => f.endsWith('.json'));
273
-
274
- for (const file of files) {
275
- const flowId = file.replace('.json', '');
276
- const flow = loadLocalFlow(flowId);
374
+ // Now we look for directories, not .json files
375
+ const entries = fs.readdirSync(flowsDir, { withFileTypes: true });
376
+ const flowDirs = entries.filter(e => e.isDirectory());
377
+
378
+ for (const entry of flowDirs) {
379
+ const shortFlowId = entry.name;
380
+ // Get full flow ID from mapping, or use short ID as fallback
381
+ const fullFlowId = getFullId(shortFlowId) || shortFlowId;
382
+ const flow = loadLocalFlow(fullFlowId);
277
383
  if (flow) {
278
384
  // Determine sync status
279
385
  let syncStatus = 'draft';
@@ -299,14 +405,18 @@ function listLocalFlows() {
299
405
 
300
406
  /**
301
407
  * Delete a local flow
408
+ * New folder structure: flows/{shortFlowId}/
302
409
  */
303
410
  function deleteLocalFlow(flowId) {
304
411
  const flowsDir = getFlowsDir();
305
412
  if (!flowsDir) return false;
306
413
 
307
- const flowPath = path.join(flowsDir, `${flowId}.json`);
308
- if (fs.existsSync(flowPath)) {
309
- fs.unlinkSync(flowPath);
414
+ // Use short ID for folder path
415
+ const shortFlowId = getShortId(flowId);
416
+ const flowDir = path.join(flowsDir, shortFlowId);
417
+ if (fs.existsSync(flowDir)) {
418
+ // Remove the entire flow directory
419
+ fs.rmSync(flowDir, { recursive: true, force: true });
310
420
  return true;
311
421
  }
312
422
  return false;
@@ -314,13 +424,25 @@ function deleteLocalFlow(flowId) {
314
424
 
315
425
  /**
316
426
  * Get the current project ID from unified config
317
- * Defaults to 'default' if not set
318
427
  */
319
428
  function getProjectId() {
320
- const unifiedConfig = loadUnifiedConfig();
321
- if (!unifiedConfig) return 'default';
429
+ // Check env var first
430
+ if (process.env.LUX_PROJECT_ID) {
431
+ return process.env.LUX_PROJECT_ID;
432
+ }
433
+
434
+ const config = loadUnifiedConfig();
322
435
 
323
- return unifiedConfig.currentProject || 'default';
436
+ if (config.currentProject) {
437
+ return config.currentProject;
438
+ }
439
+
440
+ // Fallback: use the first project in the current org
441
+ if (config.currentOrg && config.orgs?.[config.currentOrg]?.projects?.length) {
442
+ return config.orgs[config.currentOrg].projects[0];
443
+ }
444
+
445
+ return 'default';
324
446
  }
325
447
 
326
448
  /**
@@ -336,33 +458,37 @@ function slugify(name) {
336
458
 
337
459
  /**
338
460
  * Get the interfaces directory for the current org/project
339
- * Returns: ~/.lux-studio/{orgId}/projects/{projectId}/interfaces/
461
+ * Returns: ~/.lux-studio/{shortOrgId}/p/{shortProjectId}/i/
340
462
  */
341
463
  function getInterfacesDir() {
342
464
  const orgId = getOrgId();
343
465
  if (!orgId) return null;
344
466
 
345
467
  const projectId = getProjectId();
346
- return path.join(LUX_STUDIO_DIR, orgId, 'projects', projectId, 'interfaces');
468
+ const shortOrgId = getShortId(orgId);
469
+ const shortProjectId = getShortId(projectId);
470
+ return path.join(LUX_STUDIO_DIR, shortOrgId, FOLDER_NAMES.projects, shortProjectId, FOLDER_NAMES.interfaces);
347
471
  }
348
472
 
349
473
  /**
350
- * Get the directory for a specific interface by slug
351
- * Returns: ~/.lux-studio/{orgId}/projects/{projectId}/interfaces/{slug}/
474
+ * Get the directory for a specific interface by slug or ID
475
+ * Returns: ~/.lux-studio/{shortOrgId}/p/{shortProjectId}/i/{shortInterfaceId}/
352
476
  */
353
- function getInterfaceDir(slug) {
477
+ function getInterfaceDir(slugOrId) {
354
478
  const interfacesDir = getInterfacesDir();
355
479
  if (!interfacesDir) return null;
356
480
 
357
- return path.join(interfacesDir, slug);
481
+ // If it looks like a UUID, use short ID
482
+ const shortId = getShortId(slugOrId);
483
+ return path.join(interfacesDir, shortId);
358
484
  }
359
485
 
360
486
  /**
361
487
  * Get the repo directory for a specific interface
362
- * Returns: ~/.lux-studio/{orgId}/projects/{projectId}/interfaces/{slug}/repo/
488
+ * Returns: ~/.lux-studio/{shortOrgId}/p/{shortProjectId}/i/{shortInterfaceId}/repo/
363
489
  */
364
- function getInterfaceRepoDir(slug) {
365
- const interfaceDir = getInterfaceDir(slug);
490
+ function getInterfaceRepoDir(slugOrId) {
491
+ const interfaceDir = getInterfaceDir(slugOrId);
366
492
  if (!interfaceDir) return null;
367
493
 
368
494
  return path.join(interfaceDir, 'repo');
@@ -391,6 +517,26 @@ function interfaceExists(slug) {
391
517
  return interfaceDir && fs.existsSync(interfaceDir);
392
518
  }
393
519
 
520
+ /**
521
+ * Load interface metadata by ID
522
+ * @param {string} id - The interface UUID
523
+ * @returns {object|null} The metadata object, or null if not found
524
+ */
525
+ function loadInterfaceMetadata(id) {
526
+ const interfaceDir = getInterfaceDir(id);
527
+ if (!interfaceDir) return null;
528
+
529
+ const metadataPath = path.join(interfaceDir, 'metadata.json');
530
+ if (!fs.existsSync(metadataPath)) return null;
531
+
532
+ try {
533
+ const content = fs.readFileSync(metadataPath, 'utf8');
534
+ return JSON.parse(content);
535
+ } catch (error) {
536
+ return null;
537
+ }
538
+ }
539
+
394
540
  /**
395
541
  * Save interface metadata
396
542
  * @param {string} slug - The slugified interface name (folder name)
@@ -415,6 +561,7 @@ module.exports = {
415
561
  loadActiveOrg,
416
562
  loadOrgCredentials,
417
563
  loadUnifiedConfig,
564
+ saveUnifiedConfig,
418
565
  loadInterfaceConfig,
419
566
  saveInterfaceConfig,
420
567
  getApiUrl,
@@ -434,8 +581,13 @@ module.exports = {
434
581
  getInterfaceRepoDir,
435
582
  ensureInterfaceDir,
436
583
  interfaceExists,
584
+ loadInterfaceMetadata,
437
585
  saveInterfaceMetadata,
438
586
  slugify,
587
+ getShortId,
588
+ getFullId,
589
+ registerShortId,
439
590
  INTERFACE_FILE,
440
591
  LUX_STUDIO_DIR,
592
+ FOLDER_NAMES,
441
593
  };
package/lib/helpers.js CHANGED
@@ -4,6 +4,8 @@
4
4
  * Common functions for formatting, validation, and error handling
5
5
  */
6
6
 
7
+ const chalk = require('chalk');
8
+
7
9
  /**
8
10
  * Format output as table
9
11
  */
@@ -25,14 +27,14 @@ function formatJson(data, pretty = true) {
25
27
  * Print success message
26
28
  */
27
29
  function success(message) {
28
- console.log(`✅ ${message}`);
30
+ console.log(chalk.green(`[OK] ${message}`));
29
31
  }
30
32
 
31
33
  /**
32
34
  * Print error message and exit
33
35
  */
34
36
  function error(message, exitCode = 1) {
35
- console.error(`❌ ${message}`);
37
+ console.error(chalk.red(`[ERROR] ${message}`));
36
38
  process.exit(exitCode);
37
39
  }
38
40
 
@@ -40,14 +42,14 @@ function error(message, exitCode = 1) {
40
42
  * Print warning message
41
43
  */
42
44
  function warn(message) {
43
- console.warn(`⚠️ ${message}`);
45
+ console.warn(chalk.yellow(`[WARN] ${message}`));
44
46
  }
45
47
 
46
48
  /**
47
49
  * Print info message
48
50
  */
49
51
  function info(message) {
50
- console.log(`ℹ️ ${message}`);
52
+ console.log(chalk.blue(`[INFO] ${message}`));
51
53
  }
52
54
 
53
55
  /**
package/lux.js CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ require('dotenv').config({ quiet: true });
4
+
3
5
  const { program } = require('commander');
4
6
  const chalk = require('chalk');
5
7
  const packageJson = require('./package.json');
@@ -7,7 +9,6 @@ const packageJson = require('./package.json');
7
9
  // Import commands
8
10
  const { login } = require('./commands/login');
9
11
  const { logout } = require('./commands/logout');
10
- const { dev } = require('./commands/dev');
11
12
  const { handleInterface } = require('./commands/interface');
12
13
  const { handleData } = require('./commands/data');
13
14
  const { handleStorage } = require('./commands/storage');
@@ -19,8 +20,7 @@ const { handleVoiceAgents } = require('./commands/voice-agents');
19
20
  const { handleValidateDataLux } = require('./commands/validate-data-lux');
20
21
  const { handlePricing } = require('./commands/pricing');
21
22
  const { handleProject } = require('./commands/project');
22
- const { handleServers, handleLogs } = require('./commands/servers');
23
- const { handleTest } = require('./commands/webview');
23
+ const { handleServers } = require('./commands/servers');
24
24
  const { handleABTests } = require('./commands/ab-tests');
25
25
  const { handleTools } = require('./commands/tools');
26
26
  const { handleSpec } = require('./commands/spec');
@@ -43,26 +43,11 @@ program
43
43
  .description('Log out from Lux')
44
44
  .action(logout);
45
45
 
46
- // Dev command - local development with tunnel
47
- program
48
- .command('dev')
49
- .description('Start local dev server with tunnel to Lux cloud')
50
- .option('-p, --port <port>', 'Port to run dev server on', '3000')
51
- .option('--no-tunnel', 'Disable tunnel (local only)')
52
- .option('--no-sync', 'Disable auto-sync to GitHub')
53
- .action((options) => {
54
- dev({
55
- port: parseInt(options.port, 10),
56
- noTunnel: !options.tunnel,
57
- noSync: !options.sync,
58
- });
59
- });
60
-
61
46
  // Interface commands (with short alias 'i')
62
47
  program
63
48
  .command('interface [subcommand] [args...]')
64
49
  .alias('i')
65
- .description('Interface management (up, deploy, list, link, logs)')
50
+ .description('Interface management (create, deploy, list, logs, path)')
66
51
  .allowUnknownOption()
67
52
  .action((subcommand, args) => {
68
53
  handleInterface(subcommand ? subcommand : 'help', args || []);
@@ -202,81 +187,6 @@ program
202
187
  handleServers(subcommand ? [subcommand, ...(args || [])] : []);
203
188
  });
204
189
 
205
- // Logs command - view dev server logs
206
- program
207
- .command('logs [interface]')
208
- .description('View logs from a running dev server')
209
- .option('-f, --follow', 'Follow log output (like tail -f)')
210
- .option('-n, --lines <number>', 'Number of lines to show', '50')
211
- .option('-c, --console', 'Show browser console logs instead of terminal logs')
212
- .action((interfaceArg, options) => {
213
- handleLogs(interfaceArg ? [interfaceArg] : [], {
214
- follow: options.follow,
215
- lines: parseInt(options.lines, 10),
216
- console: options.console,
217
- });
218
- });
219
-
220
- // Webview control commands (top-level)
221
- const { screenshot, click, type: typeText, evaluate, getUrl, navigate, wait } = require('./commands/webview');
222
-
223
- program
224
- .command('screenshot <interface-id>')
225
- .description('Take a screenshot of the interface preview')
226
- .action(async (interfaceId) => {
227
- await screenshot(interfaceId);
228
- });
229
-
230
- program
231
- .command('click <interface-id> <selector>')
232
- .description('Click an element in the interface preview')
233
- .action(async (interfaceId, selector) => {
234
- await click(interfaceId, selector);
235
- });
236
-
237
- program
238
- .command('type <interface-id> <selector> <text>')
239
- .description('Type text into an element in the interface preview')
240
- .action(async (interfaceId, selector, text) => {
241
- await typeText(interfaceId, selector, text);
242
- });
243
-
244
- program
245
- .command('eval <interface-id> <code>')
246
- .description('Execute JavaScript in the interface preview')
247
- .action(async (interfaceId, code) => {
248
- await evaluate(interfaceId, code);
249
- });
250
-
251
- program
252
- .command('url <interface-id>')
253
- .description('Get current URL of the interface preview')
254
- .action(async (interfaceId) => {
255
- await getUrl(interfaceId);
256
- });
257
-
258
- program
259
- .command('nav <interface-id> <url>')
260
- .description('Navigate the interface preview to a URL')
261
- .action(async (interfaceId, url) => {
262
- await navigate(interfaceId, url);
263
- });
264
-
265
- program
266
- .command('wait <interface-id> <ms>')
267
- .description('Wait for a duration (milliseconds)')
268
- .action(async (interfaceId, ms) => {
269
- await wait(interfaceId, ms);
270
- });
271
-
272
- program
273
- .command('preview <interface-id>')
274
- .description('Start the interface preview (opens in Lux Studio)')
275
- .action(async (interfaceId) => {
276
- const { startPreview } = require('./commands/webview');
277
- await startPreview(interfaceId);
278
- });
279
-
280
190
  // Project spec command
281
191
  program
282
192
  .command('spec [subcommand] [args...]')