luxlabs 1.0.21 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -21
- package/commands/ab-tests.js +14 -11
- package/commands/agents.js +11 -11
- package/commands/data.js +19 -17
- package/commands/deploy.js +145 -82
- package/commands/flows.js +152 -133
- package/commands/interface/init.js +36 -35
- package/commands/interface.js +135 -10
- package/commands/knowledge.js +3 -3
- package/commands/list.js +6 -24
- package/commands/login.js +31 -26
- package/commands/logout.js +13 -4
- package/commands/logs.js +17 -66
- package/commands/project.js +74 -47
- package/commands/secrets.js +1 -1
- package/commands/servers.js +9 -113
- package/commands/storage.js +1 -1
- package/commands/tools.js +4 -4
- package/commands/validate-data-lux.js +5 -2
- package/commands/voice-agents.js +22 -18
- package/lib/config.js +235 -83
- package/lib/helpers.js +6 -4
- package/lux.js +4 -94
- package/package.json +6 -1
- package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +41 -34
- package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +41 -34
- package/templates/interface-boilerplate/components/providers/posthog-provider.tsx +41 -26
- package/templates/interface-boilerplate/gitignore.template +4 -0
- package/templates/interface-boilerplate/lib/auth.config.ts +3 -2
- package/templates/interface-boilerplate/lib/knowledge.ts +2 -2
- package/templates/interface-boilerplate/middleware.ts +14 -3
- package/templates/interface-boilerplate/next-env.d.ts +6 -0
- package/templates/interface-boilerplate/package-lock.json +432 -8
- package/commands/dev.js +0 -578
- package/commands/init.js +0 -126
- package/commands/link.js +0 -127
- package/commands/up.js +0 -211
package/README.md
CHANGED
|
@@ -16,14 +16,11 @@ npm install -g luxlabs
|
|
|
16
16
|
# Authenticate with Lux
|
|
17
17
|
lux login
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
lux
|
|
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
|
|
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
|
|
44
|
-
| `lux
|
|
45
|
-
| `lux
|
|
46
|
-
| `lux
|
|
47
|
-
| `lux i <
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
|
52
|
-
|
|
53
|
-
| `lux
|
|
54
|
-
| `lux
|
|
55
|
-
| `lux
|
|
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
|
|
package/commands/ab-tests.js
CHANGED
|
@@ -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/{
|
|
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
|
-
|
|
116
|
+
// Use short folder name for interfaces
|
|
117
|
+
const interfacesDir = path.join(process.cwd(), FOLDER_NAMES.interfaces);
|
|
117
118
|
|
|
118
119
|
if (!interfaceIdentifier) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(),
|
|
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());
|
package/commands/agents.js
CHANGED
|
@@ -19,7 +19,7 @@ async function handleAgents(args) {
|
|
|
19
19
|
// Check authentication
|
|
20
20
|
if (!isAuthenticated()) {
|
|
21
21
|
console.log(
|
|
22
|
-
chalk.red('
|
|
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(`\
|
|
98
|
-
console.log(
|
|
99
|
-
console.log(
|
|
100
|
-
console.log(
|
|
101
|
-
console.log(
|
|
102
|
-
console.log(
|
|
103
|
-
console.log(
|
|
104
|
-
console.log(
|
|
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(`\
|
|
114
|
+
console.log(`\nPrompt:\n`);
|
|
115
115
|
console.log(promptData.content);
|
|
116
116
|
} else {
|
|
117
|
-
console.log(`\
|
|
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
|
-
|
|
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('
|
|
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(`
|
|
284
|
+
console.log(chalk.dim(` ${tableName}`));
|
|
281
285
|
synced++;
|
|
282
286
|
}
|
|
283
287
|
} catch (err) {
|
|
284
|
-
console.log(chalk.yellow(`
|
|
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('
|
|
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(`\
|
|
413
|
-
console.log(
|
|
414
|
-
console.log(`\
|
|
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${
|
|
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
|
|
922
|
-
const kvDir = path.join(os.homedir(), '.lux-studio',
|
|
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(`
|
|
1016
|
+
console.log(` ${ns.name}: ${keys.length} keys`);
|
|
1014
1017
|
} catch (err) {
|
|
1015
|
-
console.error(`
|
|
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
|
|
1032
|
-
const nsDir = path.join(os.homedir(), '.lux-studio',
|
|
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');
|
package/commands/deploy.js
CHANGED
|
@@ -1,164 +1,227 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
-
const ora = require('ora');
|
|
3
2
|
const chalk = require('chalk');
|
|
4
|
-
const
|
|
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
|
-
|
|
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('
|
|
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
|
-
//
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
27
|
-
console.log(
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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/${
|
|
132
|
+
`${apiUrl}/api/interfaces/${metadata.id}/deploy`,
|
|
69
133
|
{},
|
|
70
134
|
{
|
|
71
135
|
headers: getAuthHeaders(),
|
|
72
136
|
}
|
|
73
137
|
);
|
|
74
138
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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('\
|
|
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/${
|
|
170
|
+
`${apiUrl}/api/interfaces/${interfaceUuid}`,
|
|
109
171
|
{ headers: getAuthHeaders() }
|
|
110
172
|
);
|
|
111
173
|
|
|
112
|
-
const status = data.interface
|
|
174
|
+
const status = data.interface?.status;
|
|
113
175
|
|
|
114
176
|
if (status === 'published') {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
console.log(
|
|
159
|
-
|
|
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 = {
|