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
@@ -1,17 +1,26 @@
1
1
  const chalk = require('chalk');
2
- const { loadConfig, saveConfig } = require('../lib/config');
2
+ const { loadConfig, loadUnifiedConfig, saveUnifiedConfig } = require('../lib/config');
3
3
 
4
4
  async function logout() {
5
5
  const config = loadConfig();
6
6
 
7
- if (!config || !config.token) {
7
+ if (!config || !config.apiKey) {
8
8
  console.log(chalk.yellow('Not currently logged in.'));
9
9
  return;
10
10
  }
11
11
 
12
- saveConfig({});
12
+ const orgId = config.orgId;
13
13
 
14
- console.log(chalk.green(' Successfully logged out'));
14
+ // Clear the current org's credentials from unified config
15
+ const unified = loadUnifiedConfig();
16
+ if (unified.orgs && unified.orgs[orgId]) {
17
+ delete unified.orgs[orgId].apiKey;
18
+ }
19
+ unified.currentOrg = null;
20
+ unified.currentProject = null;
21
+ saveUnifiedConfig(unified);
22
+
23
+ console.log(chalk.green(`Successfully logged out from ${config.orgName || orgId}`));
15
24
  }
16
25
 
17
26
  module.exports = {
package/commands/logs.js CHANGED
@@ -11,39 +11,34 @@ async function logs(options) {
11
11
  // Check authentication
12
12
  if (!isAuthenticated()) {
13
13
  console.log(
14
- chalk.red('Not authenticated. Run'),
14
+ chalk.red('Not authenticated. Run'),
15
15
  chalk.white('lux login'),
16
16
  chalk.red('first.')
17
17
  );
18
18
  process.exit(1);
19
19
  }
20
20
 
21
- // Check if initialized
22
- const interfaceConfig = loadInterfaceConfig();
23
- if (!interfaceConfig || !interfaceConfig.id) {
24
- console.log(
25
- chalk.red('❌ No interface found. Run'),
26
- chalk.white('lux up'),
27
- chalk.red('first.')
28
- );
29
- process.exit(1);
21
+ // Use provided interface ID or fall back to local config
22
+ let interfaceId = options.id;
23
+ if (!interfaceId) {
24
+ const interfaceConfig = loadInterfaceConfig();
25
+ if (!interfaceConfig || !interfaceConfig.id) {
26
+ console.log(
27
+ chalk.red('No interface found. Provide an interface ID or run from a project directory.')
28
+ );
29
+ process.exit(1);
30
+ }
31
+ interfaceId = interfaceConfig.id;
30
32
  }
31
33
 
32
34
  const apiUrl = getApiUrl();
33
- const interfaceId = interfaceConfig.id;
34
35
  const logType = options.type || 'runtime';
35
36
 
36
37
  try {
37
- if (options.follow) {
38
- // Follow mode - poll for new logs
39
- await followLogs(interfaceId, apiUrl, logType);
40
- } else {
41
- // One-time fetch
42
- await fetchLogs(interfaceId, apiUrl, logType);
43
- }
38
+ await fetchLogs(interfaceId, apiUrl, logType);
44
39
  } catch (error) {
45
40
  console.error(
46
- chalk.red('\n❌ Error:'),
41
+ chalk.red('\nError:'),
47
42
  error.response?.data?.error || error.message
48
43
  );
49
44
 
@@ -75,50 +70,6 @@ async function fetchLogs(interfaceId, apiUrl, logType) {
75
70
  }
76
71
  }
77
72
 
78
- async function followLogs(interfaceId, apiUrl, logType) {
79
- console.log(
80
- chalk.cyan(`\n📋 Following ${logType} logs... (Ctrl+C to stop)\n`)
81
- );
82
-
83
- let lastTimestamp = null;
84
-
85
- while (true) {
86
- try {
87
- const endpoint =
88
- logType === 'build'
89
- ? `/api/interfaces/${interfaceId}/build-logs`
90
- : `/api/interfaces/${interfaceId}/runtime-logs`;
91
-
92
- const params = lastTimestamp ? { since: lastTimestamp } : {};
93
-
94
- const { data } = await axios.get(`${apiUrl}${endpoint}`, {
95
- headers: getAuthHeaders(),
96
- params,
97
- });
98
-
99
- if (data.logs && data.logs.length > 0) {
100
- if (logType === 'build') {
101
- displayBuildLogs(data.logs);
102
- } else {
103
- displayRuntimeLogs(data.logs);
104
- }
105
-
106
- // Update last timestamp
107
- const lastLog = data.logs[data.logs.length - 1];
108
- if (lastLog.timestamp) {
109
- lastTimestamp = lastLog.timestamp;
110
- }
111
- }
112
-
113
- // Wait 2 seconds before next poll
114
- await new Promise((resolve) => setTimeout(resolve, 2000));
115
- } catch (error) {
116
- console.error(chalk.red('Error fetching logs:'), error.message);
117
- await new Promise((resolve) => setTimeout(resolve, 5000));
118
- }
119
- }
120
- }
121
-
122
73
  function displayBuildLogs(logs) {
123
74
  if (!logs || logs.length === 0) {
124
75
  console.log(chalk.dim('No logs available yet.'));
@@ -158,15 +109,15 @@ function displayRuntimeLogs(logs) {
158
109
 
159
110
  switch (level) {
160
111
  case 'error':
161
- prefix = '';
112
+ prefix = '[ERROR] ';
162
113
  color = chalk.red;
163
114
  break;
164
115
  case 'warn':
165
- prefix = '⚠️ ';
116
+ prefix = '[WARN] ';
166
117
  color = chalk.yellow;
167
118
  break;
168
119
  case 'info':
169
- prefix = 'ℹ️ ';
120
+ prefix = '[INFO] ';
170
121
  color = chalk.cyan;
171
122
  break;
172
123
  default:
@@ -1,6 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { execSync } = require('child_process');
3
+ const { execSync, execFileSync } = require('child_process');
4
4
  const axios = require('axios');
5
5
  const ora = require('ora');
6
6
  const chalk = require('chalk');
@@ -10,15 +10,19 @@ const {
10
10
  isAuthenticated,
11
11
  getOrgId,
12
12
  getProjectId,
13
+ getShortId,
13
14
  LUX_STUDIO_DIR,
15
+ FOLDER_NAMES,
14
16
  } = require('../lib/config');
15
17
 
16
18
  /**
17
19
  * Get the project directory path
18
- * ~/.lux-studio/{orgId}/projects/{projectId}/
20
+ * ~/.lux-studio/{shortOrgId}/p/{shortProjectId}/
19
21
  */
20
22
  function getProjectDir(orgId, projectId) {
21
- return path.join(LUX_STUDIO_DIR, orgId, 'projects', projectId);
23
+ const shortOrgId = getShortId(orgId);
24
+ const shortProjectId = getShortId(projectId);
25
+ return path.join(LUX_STUDIO_DIR, shortOrgId, FOLDER_NAMES.projects, shortProjectId);
22
26
  }
23
27
 
24
28
  /**
@@ -115,39 +119,42 @@ async function deployProject(projectId) {
115
119
  console.log(chalk.dim(`Repository: LuxUserProjects/${repoName}\n`));
116
120
 
117
121
  // Step 0: Build check - verify all interfaces build successfully (if any exist)
118
- const interfacesDir = path.join(projectDir, 'interfaces');
119
- let interfaceIds = [];
122
+ // Use short folder name for interfaces directory
123
+ const interfacesDir = path.join(projectDir, FOLDER_NAMES.interfaces);
124
+ let shortInterfaceIds = [];
120
125
 
121
126
  if (fs.existsSync(interfacesDir)) {
122
- interfaceIds = fs.readdirSync(interfacesDir).filter(f => {
127
+ // Directory entries are short IDs after migration
128
+ shortInterfaceIds = fs.readdirSync(interfacesDir).filter(f => {
123
129
  const stat = fs.statSync(path.join(interfacesDir, f));
124
130
  return stat.isDirectory();
125
131
  });
126
132
 
127
- if (interfaceIds.length > 0) {
128
- console.log(chalk.dim(`Found ${interfaceIds.length} interface(s) to build\n`));
133
+ if (shortInterfaceIds.length > 0) {
134
+ console.log(chalk.dim(`Found ${shortInterfaceIds.length} interface(s) to build\n`));
129
135
 
130
- for (const interfaceId of interfaceIds) {
131
- const repoDir = path.join(interfacesDir, interfaceId, 'repo');
136
+ for (const shortInterfaceId of shortInterfaceIds) {
137
+ // Path uses short IDs
138
+ const repoDir = path.join(interfacesDir, shortInterfaceId, 'repo');
132
139
  const packageJsonPath = path.join(repoDir, 'package.json');
133
140
 
134
141
  if (fs.existsSync(packageJsonPath)) {
135
- const buildSpinner = ora(`Building interface ${interfaceId.substring(0, 8)}...`).start();
142
+ const buildSpinner = ora(`Building interface ${shortInterfaceId}...`).start();
136
143
 
137
144
  try {
138
145
  // Install dependencies if node_modules doesn't exist
139
146
  const nodeModulesPath = path.join(repoDir, 'node_modules');
140
147
  if (!fs.existsSync(nodeModulesPath)) {
141
- buildSpinner.text = `Installing dependencies for ${interfaceId.substring(0, 8)}...`;
148
+ buildSpinner.text = `Installing dependencies for ${shortInterfaceId}...`;
142
149
  execSync('npm install', { cwd: repoDir, stdio: 'pipe', timeout: 300000 });
143
150
  }
144
151
 
145
152
  // Run build
146
- buildSpinner.text = `Building interface ${interfaceId.substring(0, 8)}...`;
153
+ buildSpinner.text = `Building interface ${shortInterfaceId}...`;
147
154
  execSync('npm run build', { cwd: repoDir, stdio: 'pipe', timeout: 300000 });
148
- buildSpinner.succeed(`Interface ${interfaceId.substring(0, 8)} built successfully`);
155
+ buildSpinner.succeed(`Interface ${shortInterfaceId} built successfully`);
149
156
  } catch (error) {
150
- buildSpinner.fail(`Interface ${interfaceId.substring(0, 8)} build failed`);
157
+ buildSpinner.fail(`Interface ${shortInterfaceId} build failed`);
151
158
 
152
159
  console.log(chalk.red('\n═══════════════════════════════════════════════════════════════'));
153
160
  console.log(chalk.red(' BUILD FAILED'));
@@ -164,7 +171,7 @@ async function deployProject(projectId) {
164
171
  }
165
172
 
166
173
  console.log(chalk.dim('\n───────────────────────────────────────────────────────────────'));
167
- console.log(chalk.cyan('\n💡 To fix this issue, please copy the build error above and'));
174
+ console.log(chalk.cyan('\nTo fix this issue, please copy the build error above and'));
168
175
  console.log(chalk.cyan(' share it with your coding agent (Claude) for assistance.\n'));
169
176
  console.log(chalk.dim(' Interface directory:'));
170
177
  console.log(chalk.dim(` ${repoDir}\n`));
@@ -230,11 +237,6 @@ async function deployProject(projectId) {
230
237
  // Create/update .gitignore
231
238
  createGitignore(projectDir);
232
239
 
233
- // Always configure git user (required for commits)
234
- // Use jason@uselux.ai so Vercel recognizes the commit author as a team member
235
- execSync('git config user.email "jason@uselux.ai"', { cwd: projectDir, stdio: 'pipe' });
236
- execSync('git config user.name "Lux Deploy"', { cwd: projectDir, stdio: 'pipe' });
237
-
238
240
  gitSpinner.succeed('Git repository ready');
239
241
  } catch (error) {
240
242
  gitSpinner.fail('Failed to prepare git repository');
@@ -273,21 +275,31 @@ async function deployProject(projectId) {
273
275
  // Check if there are staged changes
274
276
  const stagedDiff = execSync('git diff --cached --name-only', { cwd: projectDir, encoding: 'utf-8' });
275
277
 
278
+ // Use --author flag to set commit author without persisting to repo config
279
+ // Escape special characters for shell double quotes (\ " $ ` and newlines)
280
+ const escapedMessage = commitMessage
281
+ .replace(/\\/g, '\\\\')
282
+ .replace(/"/g, '\\"')
283
+ .replace(/\$/g, '\\$')
284
+ .replace(/`/g, '\\`')
285
+ .replace(/[\r\n]/g, ' ');
286
+ const authorArg = '--author="Lux Deploy <jason@uselux.ai>"';
287
+
276
288
  if (stagedDiff.trim() === '' && hasCommits) {
277
289
  // No staged changes and already has commits
278
290
  commitSpinner.succeed('No changes to commit');
279
291
  } else if (!hasCommits) {
280
292
  // First commit - use --allow-empty if needed
281
293
  try {
282
- execSync(`git commit -m "${commitMessage}"`, { cwd: projectDir, stdio: 'pipe' });
294
+ execSync(`git commit ${authorArg} -m "${escapedMessage}"`, { cwd: projectDir, stdio: 'pipe' });
283
295
  commitSpinner.succeed('Created initial commit');
284
296
  } catch {
285
- execSync(`git commit --allow-empty -m "${commitMessage}"`, { cwd: projectDir, stdio: 'pipe' });
297
+ execSync(`git commit ${authorArg} --allow-empty -m "${escapedMessage}"`, { cwd: projectDir, stdio: 'pipe' });
286
298
  commitSpinner.succeed('Created initial commit');
287
299
  }
288
300
  } else {
289
301
  // Has staged changes
290
- execSync(`git commit -m "${commitMessage}"`, { cwd: projectDir, stdio: 'pipe' });
302
+ execSync(`git commit ${authorArg} -m "${escapedMessage}"`, { cwd: projectDir, stdio: 'pipe' });
291
303
  commitSpinner.succeed('Commit created');
292
304
  }
293
305
  } catch (error) {
@@ -311,21 +323,25 @@ async function deployProject(projectId) {
311
323
  const authUrl = repoUrl.replace('https://github.com/', `https://x-access-token:${token}@github.com/`);
312
324
 
313
325
  // Set remote (or update if exists)
326
+ // Use execFileSync to avoid shell injection with URLs
314
327
  try {
315
- execSync(`git remote add origin ${authUrl}`, { cwd: projectDir, stdio: 'pipe' });
328
+ execFileSync('git', ['remote', 'add', 'origin', authUrl], { cwd: projectDir, stdio: 'pipe' });
316
329
  } catch {
317
330
  // Remote might already exist, update it
318
- execSync(`git remote set-url origin ${authUrl}`, { cwd: projectDir, stdio: 'pipe' });
331
+ execFileSync('git', ['remote', 'set-url', 'origin', authUrl], { cwd: projectDir, stdio: 'pipe' });
319
332
  }
320
333
 
321
334
  // Always ensure we're on main branch before pushing
322
335
  execSync('git branch -M main', { cwd: projectDir, stdio: 'pipe' });
323
336
 
324
- // Force push to main
325
- execSync('git push -f origin main', { cwd: projectDir, stdio: 'pipe', timeout: 120000 });
337
+ // Force push to main (use -c for one-time config to avoid persisting to repo)
338
+ // Include user config so git has valid committer identity for any merge commits
339
+ // Note: http.postBuffer only applies to HTTPS remotes (which we use with token auth)
340
+ execSync('git -c http.postBuffer=524288000 -c user.name="Lux Deploy" -c user.email="jason@uselux.ai" push -f origin main', { cwd: projectDir, stdio: 'pipe', timeout: 120000 });
326
341
 
327
342
  // Clear the token from remote URL after push (security)
328
- execSync(`git remote set-url origin ${repoUrl}`, { cwd: projectDir, stdio: 'pipe' });
343
+ // Use execFileSync to avoid shell injection with URLs
344
+ execFileSync('git', ['remote', 'set-url', 'origin', repoUrl], { cwd: projectDir, stdio: 'pipe' });
329
345
 
330
346
  pushSpinner.succeed('Pushed to GitHub');
331
347
  } catch (error) {
@@ -333,8 +349,9 @@ async function deployProject(projectId) {
333
349
  console.error(chalk.red('\nError:'), error.message);
334
350
 
335
351
  // Clear token from URL even on failure
352
+ // Use execFileSync to avoid shell injection with URLs
336
353
  try {
337
- execSync(`git remote set-url origin ${repoUrl}`, { cwd: projectDir, stdio: 'pipe' });
354
+ execFileSync('git', ['remote', 'set-url', 'origin', repoUrl], { cwd: projectDir, stdio: 'pipe' });
338
355
  } catch {
339
356
  // Ignore
340
357
  }
@@ -369,7 +386,8 @@ async function deployProject(projectId) {
369
386
  vercelSpinner.succeed(`Interface ${iface.interfaceId.substring(0, 8)} deployment triggered`);
370
387
 
371
388
  // Sync local custom domains to Vercel after successful deployment
372
- const interfaceMetadataPath = path.join(interfacesDir, iface.interfaceId, 'metadata.json');
389
+ const shortInterfaceIdForMeta = getShortId(iface.interfaceId);
390
+ const interfaceMetadataPath = path.join(interfacesDir, shortInterfaceIdForMeta, 'metadata.json');
373
391
  if (fs.existsSync(interfaceMetadataPath)) {
374
392
  try {
375
393
  const metadata = JSON.parse(fs.readFileSync(interfaceMetadataPath, 'utf-8'));
@@ -448,11 +466,11 @@ async function deployProject(projectId) {
448
466
 
449
467
  for (const result of deploymentResults) {
450
468
  if (result.success) {
451
- console.log(chalk.green(`✓ ${result.interfaceId.substring(0, 8)}`));
469
+ console.log(chalk.green(`${result.interfaceId.substring(0, 8)}`));
452
470
  console.log(chalk.dim(` URL: ${result.url}`));
453
471
  console.log(chalk.dim(` Deployment ID: ${result.deploymentId}`));
454
472
  } else {
455
- console.log(chalk.red(`✗ ${result.interfaceId.substring(0, 8)}`));
473
+ console.log(chalk.red(`${result.interfaceId.substring(0, 8)}`));
456
474
  console.log(chalk.dim(` Error: ${result.error}`));
457
475
  }
458
476
  console.log('');
@@ -460,7 +478,7 @@ async function deployProject(projectId) {
460
478
 
461
479
  if (interfaceHasErrors) {
462
480
  console.log(chalk.dim('───────────────────────────────────────────────────────────────'));
463
- console.log(chalk.cyan('\n💡 To debug failed deployments, check the Vercel dashboard:'));
481
+ console.log(chalk.cyan('\nTo debug failed deployments, check the Vercel dashboard:'));
464
482
  console.log(chalk.dim(' https://vercel.com/dashboard\n'));
465
483
  console.log(chalk.dim('───────────────────────────────────────────────────────────────\n'));
466
484
  }
@@ -475,24 +493,33 @@ async function deployProject(projectId) {
475
493
  }
476
494
 
477
495
  // Step 7: Deploy flows (if any exist)
478
- const flowsDir = path.join(projectDir, 'flows');
496
+ // Use short folder name for flows directory
497
+ const flowsDir = path.join(projectDir, FOLDER_NAMES.flows);
479
498
  if (fs.existsSync(flowsDir)) {
480
- const flowFiles = fs.readdirSync(flowsDir).filter(f => f.endsWith('.json'));
499
+ // Each flow is in its own folder: f/{shortFlowId}/draft.json
500
+ const flowDirs = fs.readdirSync(flowsDir).filter(f => {
501
+ const stat = fs.statSync(path.join(flowsDir, f));
502
+ return stat.isDirectory();
503
+ });
481
504
 
482
- if (flowFiles.length > 0) {
483
- console.log(chalk.dim(`\nDeploying ${flowFiles.length} flow(s)...\n`));
505
+ if (flowDirs.length > 0) {
506
+ console.log(chalk.dim(`\nDeploying ${flowDirs.length} flow(s)...\n`));
484
507
 
485
508
  const flowResults = [];
486
509
  let flowHasErrors = false;
487
510
 
488
- for (const flowFile of flowFiles) {
489
- const flowId = flowFile.replace('.json', '');
490
- const flowSpinner = ora(`Deploying flow ${flowId.substring(0, 8)}...`).start();
511
+ for (const shortFlowId of flowDirs) {
512
+ const flowSpinner = ora(`Deploying flow ${shortFlowId}...`).start();
491
513
 
492
514
  try {
493
- // Read flow data
494
- const flowPath = path.join(flowsDir, flowFile);
515
+ // Read flow data from draft.json in the flow's folder
516
+ const flowPath = path.join(flowsDir, shortFlowId, 'draft.json');
517
+ if (!fs.existsSync(flowPath)) {
518
+ flowSpinner.warn(`Flow ${shortFlowId} has no draft.json, skipping`);
519
+ continue;
520
+ }
495
521
  const flowData = JSON.parse(fs.readFileSync(flowPath, 'utf-8'));
522
+ const flowId = flowData.id || shortFlowId; // Use ID from flow data if available
496
523
 
497
524
  // Deploy to cloud API using /publish endpoint
498
525
  // The publish endpoint supports CLI auth and will create the flow if it doesn't exist
@@ -529,10 +556,10 @@ async function deployProject(projectId) {
529
556
  fs.writeFileSync(flowPath, JSON.stringify(flowData, null, 2));
530
557
  } catch (error) {
531
558
  flowHasErrors = true;
532
- flowSpinner.fail(`Flow ${flowId.substring(0, 8)} deployment failed`);
559
+ flowSpinner.fail(`Flow ${shortFlowId} deployment failed`);
533
560
  const errorData = error.response?.data;
534
561
  flowResults.push({
535
- flowId,
562
+ flowId: shortFlowId,
536
563
  success: false,
537
564
  error: errorData?.error || error.message,
538
565
  });
@@ -549,12 +576,12 @@ async function deployProject(projectId) {
549
576
 
550
577
  for (const result of flowResults) {
551
578
  if (result.success) {
552
- console.log(chalk.green(`✓ ${result.name || result.flowId.substring(0, 8)}`));
579
+ console.log(chalk.green(`${result.name || result.flowId.substring(0, 8)}`));
553
580
  if (result.webhookUrl) {
554
581
  console.log(chalk.dim(` Webhook: ${result.webhookUrl}`));
555
582
  }
556
583
  } else {
557
- console.log(chalk.red(`✗ ${result.flowId.substring(0, 8)}`));
584
+ console.log(chalk.red(`${result.flowId.substring(0, 8)}`));
558
585
  console.log(chalk.dim(` Error: ${result.error}`));
559
586
  }
560
587
  }
@@ -11,7 +11,7 @@ async function handleSecrets(args) {
11
11
  // Check authentication
12
12
  if (!isAuthenticated()) {
13
13
  console.log(
14
- chalk.red('Not authenticated. Run'),
14
+ chalk.red('Not authenticated. Run'),
15
15
  chalk.white('lux login'),
16
16
  chalk.red('first.')
17
17
  );
@@ -83,7 +83,7 @@ async function listServers() {
83
83
  return;
84
84
  }
85
85
 
86
- console.log(chalk.cyan('\n📡 Running Dev Servers\n'));
86
+ console.log(chalk.cyan('\nRunning Dev Servers\n'));
87
87
 
88
88
  for (const server of registry.servers) {
89
89
  console.log(chalk.white(` ${server.appName || server.appId}`));
@@ -139,9 +139,8 @@ async function getLogs(appNameOrId, options = {}) {
139
139
 
140
140
  // Read and display logs
141
141
  const lines = options.lines || 50;
142
- const follow = options.follow || false;
143
142
 
144
- console.log(chalk.cyan(`\n📝 Logs for ${server.appName || server.appId}`));
143
+ console.log(chalk.cyan(`\nLogs for ${server.appName || server.appId}`));
145
144
  console.log(chalk.dim(` Port: ${server.port} | Log file: ${server.logFile}\n`));
146
145
  console.log(chalk.dim('─'.repeat(60)));
147
146
 
@@ -155,50 +154,18 @@ async function getLogs(appNameOrId, options = {}) {
155
154
  });
156
155
 
157
156
  console.log(chalk.dim('─'.repeat(60)));
158
-
159
- if (follow) {
160
- console.log(chalk.dim('\nWatching for new logs... (Ctrl+C to stop)\n'));
161
-
162
- // Watch the file for changes
163
- let lastSize = fs.statSync(server.logFile).size;
164
-
165
- const watcher = fs.watchFile(server.logFile, { interval: 500 }, (curr, prev) => {
166
- if (curr.size > lastSize) {
167
- // Read new content
168
- const fd = fs.openSync(server.logFile, 'r');
169
- const buffer = Buffer.alloc(curr.size - lastSize);
170
- fs.readSync(fd, buffer, 0, buffer.length, lastSize);
171
- fs.closeSync(fd);
172
-
173
- const newContent = buffer.toString('utf8');
174
- newContent.split('\n').filter(l => l.trim()).forEach(line => {
175
- printLogLine(line);
176
- });
177
-
178
- lastSize = curr.size;
179
- }
180
- });
181
-
182
- // Handle Ctrl+C
183
- process.on('SIGINT', () => {
184
- fs.unwatchFile(server.logFile);
185
- console.log(chalk.dim('\n\nStopped watching logs.\n'));
186
- process.exit(0);
187
- });
188
- } else {
189
- console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use -f to follow.\n`));
190
- }
157
+ console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use --lines <number> to adjust.\n`));
191
158
  }
192
159
 
193
160
  /**
194
161
  * Print a log line with appropriate coloring
195
162
  */
196
163
  function printLogLine(line) {
197
- if (line.includes('error') || line.includes('Error') || line.includes('ERROR') || line.includes('❌')) {
164
+ if (line.includes('error') || line.includes('Error') || line.includes('ERROR')) {
198
165
  console.log(chalk.red(line));
199
- } else if (line.includes('warn') || line.includes('Warn') || line.includes('WARN') || line.includes('⚠️')) {
166
+ } else if (line.includes('warn') || line.includes('Warn') || line.includes('WARN')) {
200
167
  console.log(chalk.yellow(line));
201
- } else if (line.includes('✓') || line.includes('Ready') || line.includes('success')) {
168
+ } else if (line.includes('Ready') || line.includes('success')) {
202
169
  console.log(chalk.green(line));
203
170
  } else if (line.includes('[stderr]')) {
204
171
  console.log(chalk.yellow(line));
@@ -284,7 +251,7 @@ async function restartServer(appNameOrId) {
284
251
  const signalFile = path.join(signalDir, `${server.appId}.signal`);
285
252
  fs.writeFileSync(signalFile, Date.now().toString());
286
253
 
287
- console.log(chalk.green(`\n✓ Restart signal sent for ${server.appName || server.appId}`));
254
+ console.log(chalk.green(`\nRestart signal sent for ${server.appName || server.appId}`));
288
255
  console.log(chalk.dim(` The server will restart momentarily.\n`));
289
256
  }
290
257
 
@@ -352,9 +319,8 @@ async function getBrowserConsoleLogs(appNameOrId, options = {}) {
352
319
  }
353
320
 
354
321
  const lines = options.lines || 50;
355
- const follow = options.follow || false;
356
322
 
357
- console.log(chalk.cyan(`\n🖥️ Browser Console Logs for ${server.appName || server.appId}`));
323
+ console.log(chalk.cyan(`\nBrowser Console Logs for ${server.appName || server.appId}`));
358
324
  console.log(chalk.dim(` Port: ${server.port} | Log file: ${consoleLogFile}\n`));
359
325
  console.log(chalk.dim('─'.repeat(60)));
360
326
 
@@ -368,36 +334,7 @@ async function getBrowserConsoleLogs(appNameOrId, options = {}) {
368
334
  });
369
335
 
370
336
  console.log(chalk.dim('─'.repeat(60)));
371
-
372
- if (follow) {
373
- console.log(chalk.dim('\nWatching for new logs... (Ctrl+C to stop)\n'));
374
-
375
- let lastSize = fs.statSync(consoleLogFile).size;
376
-
377
- fs.watchFile(consoleLogFile, { interval: 500 }, (curr) => {
378
- if (curr.size > lastSize) {
379
- const fd = fs.openSync(consoleLogFile, 'r');
380
- const buffer = Buffer.alloc(curr.size - lastSize);
381
- fs.readSync(fd, buffer, 0, buffer.length, lastSize);
382
- fs.closeSync(fd);
383
-
384
- const newContent = buffer.toString('utf8');
385
- newContent.split('\n').filter(l => l.trim()).forEach(line => {
386
- printConsoleLine(line);
387
- });
388
-
389
- lastSize = curr.size;
390
- }
391
- });
392
-
393
- process.on('SIGINT', () => {
394
- fs.unwatchFile(consoleLogFile);
395
- console.log(chalk.dim('\n\nStopped watching logs.\n'));
396
- process.exit(0);
397
- });
398
- } else {
399
- console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use -f to follow.\n`));
400
- }
337
+ console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use --lines <number> to adjust.\n`));
401
338
  }
402
339
 
403
340
  /**
@@ -418,49 +355,8 @@ function printConsoleLine(line) {
418
355
  }
419
356
  }
420
357
 
421
- /**
422
- * Handle the logs command
423
- */
424
- async function handleLogs(args = [], options = {}) {
425
- if (args.length === 0) {
426
- // No app specified - show help or list servers
427
- const registry = await loadRegistry(true);
428
-
429
- if (!registry.servers || registry.servers.length === 0) {
430
- console.log(chalk.yellow('\nNo dev servers are currently running.'));
431
- return;
432
- }
433
-
434
- if (registry.servers.length === 1) {
435
- // Only one server, show its logs
436
- if (options.console) {
437
- await getBrowserConsoleLogs(registry.servers[0].appId, options);
438
- } else {
439
- await getLogs(registry.servers[0].appId, options);
440
- }
441
- return;
442
- }
443
-
444
- // Multiple servers - ask user to specify
445
- console.log(chalk.cyan('\nMultiple servers running. Specify which app:\n'));
446
- registry.servers.forEach(s => {
447
- console.log(chalk.white(` lux logs ${s.appName || s.appId}`));
448
- });
449
- console.log('');
450
- return;
451
- }
452
-
453
- const appNameOrId = args[0];
454
- if (options.console) {
455
- await getBrowserConsoleLogs(appNameOrId, options);
456
- } else {
457
- await getLogs(appNameOrId, options);
458
- }
459
- }
460
-
461
358
  module.exports = {
462
359
  handleServers,
463
- handleLogs,
464
360
  listServers,
465
361
  getLogs,
466
362
  getBrowserConsoleLogs,
@@ -21,7 +21,7 @@ async function handleStorage(args) {
21
21
  // Check authentication
22
22
  if (!isAuthenticated()) {
23
23
  console.log(
24
- chalk.red('Not authenticated. Run'),
24
+ chalk.red('Not authenticated. Run'),
25
25
  chalk.white('lux login'),
26
26
  chalk.red('first.')
27
27
  );