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/README.md CHANGED
@@ -16,14 +16,11 @@ npm install -g luxlabs
16
16
  # Authenticate with Lux
17
17
  lux login
18
18
 
19
- # Initialize a new interface project
20
- lux interface init
21
-
22
- # Start local development with hot reload
23
- lux dev
19
+ # Create a new interface
20
+ lux i create --name my-app
24
21
 
25
22
  # Deploy your interface
26
- lux interface up
23
+ lux i deploy <id>
27
24
  ```
28
25
 
29
26
  ## Commands
@@ -40,21 +37,19 @@ lux interface up
40
37
 
41
38
  | Command | Description |
42
39
  |---------|-------------|
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 |
40
+ | `lux i create --name <name>` | Create a new interface |
41
+ | `lux i list` | List all your interfaces |
42
+ | `lux i deploy <id>` | Deploy interface to production |
43
+ | `lux i logs <name-or-id>` | View interface logs (--type terminal\|console\|deployed, --lines N) |
44
+ | `lux i path <name-or-id>` | Get local repo path |
45
+ | `lux i screenshot <name-or-id>` | Take a screenshot |
46
+ | `lux i click <name-or-id> <selector>` | Click an element |
47
+ | `lux i type <name-or-id> <selector> <text>` | Type into an element |
48
+ | `lux i eval <name-or-id> <code>` | Execute JavaScript |
49
+ | `lux i url <name-or-id>` | Get current URL |
50
+ | `lux i nav <name-or-id> <url>` | Navigate to URL |
51
+ | `lux i wait <name-or-id> <ms>` | Wait for duration |
52
+ | `lux i preview <name-or-id>` | Start interface preview |
58
53
 
59
54
  ### Data Management
60
55
 
@@ -20,7 +20,7 @@ const chalk = require('chalk');
20
20
  const fs = require('fs');
21
21
  const path = require('path');
22
22
  const ora = require('ora');
23
- const { loadConfig, getProjectId, getStudioApiUrl, getInterfaceRepoDir, getInterfacesDir } = require('../lib/config');
23
+ const { loadConfig, getProjectId, getStudioApiUrl, getInterfaceRepoDir, getInterfacesDir, FOLDER_NAMES } = require('../lib/config');
24
24
 
25
25
  /**
26
26
  * Show help for ab-tests commands
@@ -52,7 +52,7 @@ function showHelp() {
52
52
 
53
53
  /**
54
54
  * Get the path to ab-tests.json for an interface
55
- * Uses ~/.lux-studio/{orgId}/projects/{projectId}/interfaces/{interfaceId}/repo/.lux/ab-tests.json
55
+ * Uses ~/.lux-studio/{shortOrgId}/p/{shortProjectId}/i/{shortInterfaceId}/repo/.lux/ab-tests.json
56
56
  */
57
57
  function getABTestsPath(interfaceIdentifier) {
58
58
  // First try the config-based path (preferred - used by Electron app)
@@ -113,13 +113,16 @@ function getABTestsPath(interfaceIdentifier) {
113
113
  * Get interface name from identifier
114
114
  */
115
115
  function getInterfaceName(interfaceIdentifier) {
116
- const interfacesDir = path.join(process.cwd(), 'interfaces');
116
+ // Use short folder name for interfaces
117
+ const interfacesDir = path.join(process.cwd(), FOLDER_NAMES.interfaces);
117
118
 
118
119
  if (!interfaceIdentifier) {
119
- const entries = fs.readdirSync(interfacesDir, { withFileTypes: true });
120
- const dirs = entries.filter(e => e.isDirectory());
121
- if (dirs.length === 1) {
122
- interfaceIdentifier = dirs[0].name;
120
+ if (fs.existsSync(interfacesDir)) {
121
+ const entries = fs.readdirSync(interfacesDir, { withFileTypes: true });
122
+ const dirs = entries.filter(e => e.isDirectory());
123
+ if (dirs.length === 1) {
124
+ interfaceIdentifier = dirs[0].name;
125
+ }
123
126
  }
124
127
  }
125
128
 
@@ -196,7 +199,7 @@ async function listTests(interfaceId, options) {
196
199
 
197
200
  for (const test of tests) {
198
201
  console.log();
199
- console.log(chalk.cyan(`📊 ${test.name}`) + chalk.dim(` (key: ${test.key})`));
202
+ console.log(chalk.cyan(`${test.name}`) + chalk.dim(` (key: ${test.key})`));
200
203
  console.log(chalk.dim(` Status: ${test.status}${test.posthogFlagId ? ` (PostHog flag: ${test.posthogFlagId})` : ''}`));
201
204
 
202
205
  if (test.description) {
@@ -252,7 +255,7 @@ async function getTest(testKey, interfaceId, options) {
252
255
  }
253
256
 
254
257
  console.log();
255
- console.log(chalk.cyan(`📊 ${test.key}`));
258
+ console.log(chalk.cyan(`${test.key}`));
256
259
  console.log(chalk.white(` Name: ${test.name}`));
257
260
  console.log(chalk.dim(` Status: ${test.status}${test.posthogFlagId ? ` (PostHog flag: ${test.posthogFlagId})` : ''}`));
258
261
 
@@ -339,7 +342,7 @@ async function getVariant(testKey, variantKey, interfaceId, options) {
339
342
  }
340
343
 
341
344
  console.log();
342
- console.log(chalk.cyan(`🎯 Variant: ${variant.key}`));
345
+ console.log(chalk.cyan(`Variant: ${variant.key}`));
343
346
  console.log(chalk.dim(` Test: ${test.name} (${test.key})`));
344
347
  console.log(chalk.white(` Name: ${variant.name || variant.key}`));
345
348
  console.log(chalk.dim(` Traffic: ${variant.percentage}%`));
@@ -446,7 +449,7 @@ function getInterfaceId(options) {
446
449
  }
447
450
 
448
451
  // Try to find from interfaces directory structure
449
- const interfacesDir = path.join(process.cwd(), 'interfaces');
452
+ const interfacesDir = path.join(process.cwd(), FOLDER_NAMES.interfaces);
450
453
  if (fs.existsSync(interfacesDir)) {
451
454
  const entries = fs.readdirSync(interfacesDir, { withFileTypes: true });
452
455
  const dirs = entries.filter(e => e.isDirectory());
@@ -19,7 +19,7 @@ async function handleAgents(args) {
19
19
  // Check authentication
20
20
  if (!isAuthenticated()) {
21
21
  console.log(
22
- chalk.red('Not authenticated. Run'),
22
+ chalk.red('Not authenticated. Run'),
23
23
  chalk.white('lux login'),
24
24
  chalk.red('first.')
25
25
  );
@@ -94,14 +94,14 @@ ${chalk.bold('Examples:')}
94
94
 
95
95
  const agent = agentData.agent;
96
96
 
97
- console.log(`\n🤖 ID: ${agent.id}`);
98
- console.log(`📝 Name: ${agent.name}`);
99
- console.log(`📄 Description: ${agent.description || '(none)'}`);
100
- console.log(`🏷️ Type: ${agent.type}`);
101
- console.log(`📂 Category: ${agent.category}`);
102
- console.log(`🎯 Purpose: ${agent.purpose || '(none)'}`);
103
- console.log(`📅 Created: ${agent.created_at}`);
104
- console.log(`📅 Updated: ${agent.updated_at}`);
97
+ console.log(`\nID: ${agent.id}`);
98
+ console.log(`Name: ${agent.name}`);
99
+ console.log(`Description: ${agent.description || '(none)'}`);
100
+ console.log(`Type: ${agent.type}`);
101
+ console.log(`Category: ${agent.category}`);
102
+ console.log(`Purpose: ${agent.purpose || '(none)'}`);
103
+ console.log(`Created: ${agent.created_at}`);
104
+ console.log(`Updated: ${agent.updated_at}`);
105
105
 
106
106
  // Fetch prompt content
107
107
  info('Loading agent prompt...');
@@ -111,10 +111,10 @@ ${chalk.bold('Examples:')}
111
111
  );
112
112
 
113
113
  if (promptData.content) {
114
- console.log(`\n📋 Prompt:\n`);
114
+ console.log(`\nPrompt:\n`);
115
115
  console.log(promptData.content);
116
116
  } else {
117
- console.log(`\n📋 Prompt: (empty)`);
117
+ console.log(`\nPrompt: (empty)`);
118
118
  }
119
119
 
120
120
  break;
package/commands/data.js CHANGED
@@ -11,6 +11,8 @@ const {
11
11
  getOrgId,
12
12
  getProjectId,
13
13
  loadConfig,
14
+ getShortId,
15
+ FOLDER_NAMES,
14
16
  } = require('../lib/config');
15
17
  const {
16
18
  error,
@@ -33,7 +35,9 @@ function getTablesDir() {
33
35
  const orgId = getOrgId();
34
36
  const projectId = getProjectId();
35
37
  if (!orgId) return null;
36
- return path.join(LUX_STUDIO_DIR, orgId, 'projects', projectId, 'data', 'tables');
38
+ const shortOrgId = getShortId(orgId);
39
+ const shortProjectId = getShortId(projectId);
40
+ return path.join(LUX_STUDIO_DIR, shortOrgId, FOLDER_NAMES.projects, shortProjectId, FOLDER_NAMES.data, 'tables');
37
41
  }
38
42
 
39
43
  /**
@@ -156,7 +160,7 @@ async function handleData(args) {
156
160
  // Check authentication
157
161
  if (!isAuthenticated()) {
158
162
  console.log(
159
- chalk.red('Not authenticated. Run'),
163
+ chalk.red('Not authenticated. Run'),
160
164
  chalk.white('lux login'),
161
165
  chalk.red('first.')
162
166
  );
@@ -277,11 +281,11 @@ ${chalk.bold('Examples:')}
277
281
  const safeName = tableName.replace(/[<>:"/\\|?*]/g, '_');
278
282
  const tablePath = path.join(tablesDir, `${safeName}.json`);
279
283
  fs.writeFileSync(tablePath, JSON.stringify(tableSchema, null, 2));
280
- console.log(chalk.dim(` ${tableName}`));
284
+ console.log(chalk.dim(` ${tableName}`));
281
285
  synced++;
282
286
  }
283
287
  } catch (err) {
284
- console.log(chalk.yellow(` ${tableName}: ${err.message}`));
288
+ console.log(chalk.yellow(` ${tableName}: ${err.message}`));
285
289
  }
286
290
  }
287
291
 
@@ -336,7 +340,7 @@ ${chalk.bold('Examples:')}
336
340
  schema = parseJson(schemaArg, 'schema');
337
341
  } else {
338
342
  // Interactive mode: prompt for schema
339
- console.log('\n' + chalk.yellow('📝 Define your table schema in JSON format:'));
343
+ console.log('\n' + chalk.yellow('Define your table schema in JSON format:'));
340
344
  console.log(chalk.gray('Example:'));
341
345
  console.log(chalk.gray(JSON.stringify({
342
346
  columns: [
@@ -409,9 +413,9 @@ ${chalk.bold('Examples:')}
409
413
  { headers: getStudioAuthHeaders() }
410
414
  );
411
415
 
412
- console.log(`\n📝 Table: ${data.table.name}`);
413
- console.log(`📊 Columns: ${data.table.columnCount}`);
414
- console.log(`\n📋 Schema:\n`);
416
+ console.log(`\nTable: ${data.table.name}`);
417
+ console.log(`Columns: ${data.table.columnCount}`);
418
+ console.log(`\nSchema:\n`);
415
419
  formatTable(data.table.columns.map(col => ({
416
420
  name: col.name,
417
421
  type: col.type,
@@ -458,7 +462,7 @@ ${chalk.bold('Examples:')}
458
462
  } else {
459
463
  console.log('');
460
464
  formatTable(data.rows);
461
- console.log(`\n${chalk.green('✓')} ${data.rows.length} row(s) returned (total: ${data.pagination.total})\n`);
465
+ console.log(`\n${data.rows.length} row(s) returned (total: ${data.pagination.total})\n`);
462
466
  }
463
467
  break;
464
468
  }
@@ -916,10 +920,9 @@ ${chalk.bold('Examples:')}
916
920
  const namespaceId = args[2]; // Optional - if not provided, sync all
917
921
 
918
922
  // Get org ID and storage path
919
- const { getOrgId } = require('../lib/config');
920
923
  const orgId = getOrgId();
921
- const os = require('os');
922
- const kvDir = path.join(os.homedir(), '.lux-studio', orgId, 'data', 'kv');
924
+ const shortOrgId = getShortId(orgId);
925
+ const kvDir = path.join(os.homedir(), '.lux-studio', shortOrgId, FOLDER_NAMES.data, 'kv');
923
926
 
924
927
  if (!fs.existsSync(kvDir)) {
925
928
  fs.mkdirSync(kvDir, { recursive: true });
@@ -1010,9 +1013,9 @@ ${chalk.bold('Examples:')}
1010
1013
  );
1011
1014
 
1012
1015
  totalKeys += keys.length;
1013
- console.log(` ${ns.name}: ${keys.length} keys`);
1016
+ console.log(` ${ns.name}: ${keys.length} keys`);
1014
1017
  } catch (err) {
1015
- console.error(` ${ns.name}: ${err.message}`);
1018
+ console.error(` ${ns.name}: ${err.message}`);
1016
1019
  }
1017
1020
  }
1018
1021
 
@@ -1026,10 +1029,9 @@ ${chalk.bold('Examples:')}
1026
1029
  requireArgs(args.slice(2), 1, 'lux data kv local <namespace-id>');
1027
1030
  const namespaceId = args[2];
1028
1031
 
1029
- const { getOrgId } = require('../lib/config');
1030
1032
  const orgId = getOrgId();
1031
- const os = require('os');
1032
- const nsDir = path.join(os.homedir(), '.lux-studio', orgId, 'data', 'kv', namespaceId);
1033
+ const shortOrgId = getShortId(orgId);
1034
+ const nsDir = path.join(os.homedir(), '.lux-studio', shortOrgId, FOLDER_NAMES.data, 'kv', namespaceId);
1033
1035
 
1034
1036
  if (!fs.existsSync(nsDir)) {
1035
1037
  console.log('\n(No local data found. Run "lux data kv sync" first.)\n');
@@ -1,164 +1,227 @@
1
1
  const axios = require('axios');
2
- const ora = require('ora');
3
2
  const chalk = require('chalk');
4
- const { execSync } = require('child_process');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
5
  const {
6
- loadInterfaceConfig,
7
- saveInterfaceConfig,
8
6
  getApiUrl,
9
7
  getAuthHeaders,
10
8
  isAuthenticated,
9
+ loadInterfaceMetadata,
10
+ saveInterfaceMetadata,
11
+ getInterfaceRepoDir,
12
+ loadConfig,
11
13
  } = require('../lib/config');
14
+ const { runGitCommand } = require('./interface/git-utils');
15
+
16
+ /**
17
+ * Push local repo to GitHub
18
+ */
19
+ async function pushToGitHub(repoDir, githubRepoUrl, githubToken) {
20
+ const targetUrl = githubRepoUrl.replace('https://github.com/', `https://${githubToken}@github.com/`) + '.git';
21
+ const gitDir = path.join(repoDir, '.git');
22
+ const needsInit = !fs.existsSync(gitDir);
23
+
24
+ if (needsInit) {
25
+ await runGitCommand(['init'], repoDir);
26
+ await runGitCommand(['remote', 'add', 'origin', targetUrl], repoDir);
27
+ await runGitCommand(['branch', '-M', 'main'], repoDir);
28
+ }
29
+
30
+ await runGitCommand(['add', '.'], repoDir);
31
+
32
+ // Try to commit - if nothing changed, just push current state
33
+ try {
34
+ await runGitCommand(['commit', '-m', 'Deploy from Lux CLI'], repoDir);
35
+ } catch {
36
+ // No changes to commit, that's fine - we'll push what's there
37
+ console.log(chalk.dim(' No new changes to commit, pushing current state'));
38
+ }
39
+
40
+ await runGitCommand(['push', '-u', 'origin', 'main', '--force'], repoDir);
12
41
 
13
- async function deploy() {
42
+ // Clean up .git directory after push (we don't keep nested git repos)
43
+ if (needsInit && fs.existsSync(gitDir)) {
44
+ fs.rmSync(gitDir, { recursive: true, force: true });
45
+ }
46
+ }
47
+
48
+ async function deploy(options = {}) {
14
49
  // Check authentication
15
50
  if (!isAuthenticated()) {
16
51
  console.log(
17
- chalk.red('Not authenticated. Run'),
52
+ chalk.red('Not authenticated. Run'),
18
53
  chalk.white('lux login'),
19
54
  chalk.red('first.')
20
55
  );
21
56
  process.exit(1);
22
57
  }
23
58
 
24
- // Check if initialized
25
- const interfaceConfig = loadInterfaceConfig();
26
- if (!interfaceConfig || !interfaceConfig.id) {
27
- console.log(
28
- chalk.red(' No interface found. Run'),
29
- chalk.white('lux up'),
30
- chalk.red('first.')
31
- );
59
+ // Require interface ID
60
+ const interfaceId = options.id;
61
+ if (!interfaceId) {
62
+ console.log(chalk.red('Interface ID is required.'));
63
+ console.log(chalk.dim('Usage: lux i deploy <id>'));
64
+ console.log(chalk.dim('Run'), chalk.white('lux i list'), chalk.dim('to see available interfaces.'));
32
65
  process.exit(1);
33
66
  }
34
67
 
68
+ // Load interface metadata
69
+ const metadata = loadInterfaceMetadata(interfaceId);
70
+ if (!metadata) {
71
+ console.log(chalk.red(`Interface not found locally: ${interfaceId}`));
72
+ console.log(chalk.dim('Run'), chalk.white('lux i list'), chalk.dim('to see available interfaces.'));
73
+ process.exit(1);
74
+ }
75
+
76
+ console.log(chalk.cyan('\nDeploy Interface\n'));
77
+ console.log(chalk.dim(' Name:'), metadata.name);
78
+ console.log(chalk.dim(' ID:'), metadata.id);
79
+ console.log('');
80
+
35
81
  const apiUrl = getApiUrl();
36
- const interfaceId = interfaceConfig.id;
37
82
 
38
- // Pre-deploy accessibility check
39
- const spinner = ora('Checking accessibility...').start();
40
- try {
41
- execSync('npx eslint . --ext .tsx,.jsx --format stylish', {
42
- cwd: process.cwd(),
43
- stdio: 'pipe',
44
- });
45
- spinner.succeed(chalk.green('✓ Accessibility check passed'));
46
- } catch (error) {
47
- const output = error.stdout?.toString() || error.stderr?.toString() || '';
48
- // Only fail on jsx-a11y errors, warn on others
49
- if (output.includes('jsx-a11y')) {
50
- spinner.fail('Accessibility issues found');
51
- console.error(chalk.red('\n❌ Fix accessibility issues before deploying:\n'));
52
- console.error(output);
53
- process.exit(1);
54
- } else if (output.trim()) {
55
- spinner.warn(chalk.yellow('Linting warnings (non-blocking)'));
56
- console.log(chalk.dim(output));
57
- } else {
58
- spinner.succeed(chalk.green('✓ Accessibility check passed'));
83
+ // Step 1: Push to GitHub
84
+ const githubRepoUrl = metadata.githubUrl;
85
+ if (!githubRepoUrl) {
86
+ console.log(chalk.red('No GitHub repository URL in metadata.'));
87
+ console.log(chalk.dim('This interface may not have been set up with GitHub.'));
88
+ process.exit(1);
89
+ }
90
+
91
+ const repoDir = getInterfaceRepoDir(interfaceId);
92
+ if (!repoDir || !fs.existsSync(repoDir)) {
93
+ console.log(chalk.red('Local repository not found.'));
94
+ console.log(chalk.dim('Expected at:'), repoDir);
95
+ process.exit(1);
96
+ }
97
+
98
+ // Get GitHub token
99
+ const config = loadConfig();
100
+ let githubToken = config?.githubPat;
101
+ if (!githubToken) {
102
+ try {
103
+ const tokenRes = await axios.get(`${apiUrl}/api/studio/github-token`, {
104
+ headers: getAuthHeaders(),
105
+ });
106
+ githubToken = tokenRes.data?.token;
107
+ } catch {
108
+ // ignore
59
109
  }
60
110
  }
61
111
 
62
- // Proceed with deploy
63
- spinner.start('Deploying to production...');
112
+ if (!githubToken) {
113
+ console.log(chalk.red('No GitHub token available.'));
114
+ console.log(chalk.dim('Cannot push to GitHub without a token.'));
115
+ process.exit(1);
116
+ }
117
+
118
+ console.log(chalk.blue('[1/3]'), 'Pushing to GitHub...');
119
+ try {
120
+ await pushToGitHub(repoDir, githubRepoUrl, githubToken);
121
+ console.log(chalk.green(' Code pushed to GitHub'));
122
+ console.log(chalk.dim(' Repo:'), githubRepoUrl);
123
+ } catch (error) {
124
+ console.log(chalk.red(' Failed to push to GitHub:'), error.message);
125
+ process.exit(1);
126
+ }
64
127
 
128
+ // Step 2: Trigger deployment via API
129
+ console.log(chalk.blue('[2/3]'), 'Triggering deployment...');
65
130
  try {
66
- // Trigger deployment
67
131
  const { data } = await axios.post(
68
- `${apiUrl}/api/interfaces/${interfaceId}/deploy`,
132
+ `${apiUrl}/api/interfaces/${metadata.id}/deploy`,
69
133
  {},
70
134
  {
71
135
  headers: getAuthHeaders(),
72
136
  }
73
137
  );
74
138
 
75
- spinner.succeed(chalk.green('Deployment started!'));
76
-
77
- console.log(chalk.dim(`\nDeployment ID: ${data.deploymentId}`));
78
- console.log(chalk.dim(`Status: ${data.status}\n`));
79
-
80
- // Poll for deployment status
81
- await pollDeploymentStatus(interfaceId, apiUrl);
139
+ console.log(chalk.green(' Deployment started'));
140
+ if (data.deploymentId) {
141
+ console.log(chalk.dim(' Deployment ID:'), data.deploymentId);
142
+ }
82
143
  } catch (error) {
83
- spinner.fail('Deployment failed');
84
- console.error(
85
- chalk.red('\n❌ Error:'),
144
+ console.log(chalk.red(' Failed to trigger deployment:'),
86
145
  error.response?.data?.error || error.message
87
146
  );
88
147
 
89
148
  if (error.response?.status === 401) {
90
149
  console.log(
91
- chalk.yellow('\nYour session may have expired. Try running:'),
150
+ chalk.yellow('\nSession may have expired. Run:'),
92
151
  chalk.white('lux login')
93
152
  );
94
153
  }
95
154
 
96
155
  process.exit(1);
97
156
  }
157
+
158
+ // Step 3: Poll for deployment status
159
+ console.log(chalk.blue('[3/3]'), 'Building...');
160
+ await pollDeploymentStatus(metadata.id, interfaceId, apiUrl);
98
161
  }
99
162
 
100
- async function pollDeploymentStatus(interfaceId, apiUrl) {
101
- const spinner = ora('Building...').start();
163
+ async function pollDeploymentStatus(interfaceUuid, interfaceId, apiUrl) {
102
164
  const maxAttempts = 60; // 5 minutes max
103
165
  let attempts = 0;
104
166
 
105
167
  while (attempts < maxAttempts) {
106
168
  try {
107
169
  const { data } = await axios.get(
108
- `${apiUrl}/api/interfaces/${interfaceId}`,
170
+ `${apiUrl}/api/interfaces/${interfaceUuid}`,
109
171
  { headers: getAuthHeaders() }
110
172
  );
111
173
 
112
- const status = data.interface.status;
174
+ const status = data.interface?.status;
113
175
 
114
176
  if (status === 'published') {
115
- spinner.succeed(chalk.green('Deployment successful!'));
116
-
117
- const interfaceConfig = loadInterfaceConfig();
118
- interfaceConfig.deploymentUrl = data.interface.vercel_deployment_url;
119
- interfaceConfig.deploymentId = data.interface.vercel_deployment_id;
120
- saveInterfaceConfig(interfaceConfig);
121
-
122
- console.log(chalk.cyan('\n🎉 Your interface is live!\n'));
123
- console.log(chalk.white(` ${data.interface.vercel_deployment_url}\n`));
177
+ console.log(chalk.green(' Deployment successful!'));
178
+
179
+ // Update local metadata
180
+ const metadata = loadInterfaceMetadata(interfaceId);
181
+ if (metadata) {
182
+ metadata.status = 'published';
183
+ if (data.interface.vercel_deployment_url) {
184
+ metadata.deploymentUrl = data.interface.vercel_deployment_url;
185
+ }
186
+ if (data.interface.vercel_deployment_id) {
187
+ metadata.deploymentId = data.interface.vercel_deployment_id;
188
+ }
189
+ metadata.lastDeployedAt = new Date().toISOString();
190
+ saveInterfaceMetadata(interfaceId, metadata);
191
+ }
124
192
 
193
+ console.log(chalk.green('\nYour interface is live!\n'));
194
+ if (data.interface.vercel_deployment_url) {
195
+ console.log(chalk.white(` ${data.interface.vercel_deployment_url}\n`));
196
+ }
125
197
  if (data.interface.github_repo_url) {
126
- console.log(chalk.dim(`GitHub: ${data.interface.github_repo_url}`));
198
+ console.log(chalk.dim(` GitHub: ${data.interface.github_repo_url}`));
127
199
  }
128
200
 
129
201
  return;
130
202
  } else if (status === 'failed') {
131
- spinner.fail('Deployment failed');
132
-
133
- console.log(
134
- chalk.red('\n❌ Build failed. View logs with:'),
135
- chalk.white('lux logs --type build')
136
- );
137
-
203
+ console.log(chalk.red(' Deployment failed'));
138
204
  if (data.interface.error_message) {
139
- console.log(chalk.dim(`\nError: ${data.interface.error_message}`));
205
+ console.log(chalk.dim(` Error: ${data.interface.error_message}`));
140
206
  }
141
-
207
+ console.log(chalk.dim(' View logs with:'), chalk.white(`lux i logs ${interfaceId}`));
142
208
  process.exit(1);
143
209
  } else if (status === 'building') {
144
- spinner.text = 'Building... (this may take a few minutes)';
210
+ process.stdout.write(chalk.dim('.'));
145
211
  }
146
212
 
147
213
  // Wait 5 seconds before next poll
148
214
  await new Promise((resolve) => setTimeout(resolve, 5000));
149
215
  attempts++;
150
216
  } catch (error) {
151
- spinner.fail('Failed to check deployment status');
152
- console.error(chalk.red('\n❌ Error:'), error.message);
217
+ console.log(chalk.red('\n Failed to check status:'), error.message);
153
218
  process.exit(1);
154
219
  }
155
220
  }
156
221
 
157
- spinner.fail('Deployment timed out');
158
- console.log(
159
- chalk.yellow('\n⚠️ Deployment is still in progress. Check status with:'),
160
- chalk.white('lux logs')
161
- );
222
+ console.log(chalk.yellow('\n Deployment timed out (5 minutes).'));
223
+ console.log(chalk.dim(' Deployment may still be in progress.'));
224
+ console.log(chalk.dim(' Check status with:'), chalk.white(`lux i logs ${interfaceId}`));
162
225
  }
163
226
 
164
227
  module.exports = {