luxlabs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +37 -0
- package/README.md +161 -0
- package/commands/ab-tests.js +437 -0
- package/commands/agents.js +226 -0
- package/commands/data.js +966 -0
- package/commands/deploy.js +166 -0
- package/commands/dev.js +569 -0
- package/commands/init.js +126 -0
- package/commands/interface/boilerplate.js +52 -0
- package/commands/interface/git-utils.js +85 -0
- package/commands/interface/index.js +7 -0
- package/commands/interface/init.js +375 -0
- package/commands/interface/path.js +74 -0
- package/commands/interface.js +125 -0
- package/commands/knowledge.js +339 -0
- package/commands/link.js +127 -0
- package/commands/list.js +97 -0
- package/commands/login.js +247 -0
- package/commands/logout.js +19 -0
- package/commands/logs.js +182 -0
- package/commands/pricing.js +328 -0
- package/commands/project.js +704 -0
- package/commands/secrets.js +129 -0
- package/commands/servers.js +411 -0
- package/commands/storage.js +177 -0
- package/commands/up.js +211 -0
- package/commands/validate-data-lux.js +502 -0
- package/commands/voice-agents.js +1055 -0
- package/commands/webview.js +393 -0
- package/commands/workflows.js +836 -0
- package/lib/config.js +403 -0
- package/lib/helpers.js +189 -0
- package/lib/node-helper.js +120 -0
- package/lux.js +268 -0
- package/package.json +56 -0
- package/templates/next-env.d.ts +6 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const open = require('open');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const ora = require('ora');
|
|
5
|
+
const inquirer = require('inquirer');
|
|
6
|
+
const axios = require('axios');
|
|
7
|
+
const { saveConfig, getApiUrl, loadConfig } = require('../lib/config');
|
|
8
|
+
|
|
9
|
+
async function login(options) {
|
|
10
|
+
// Non-interactive mode with --key flag
|
|
11
|
+
if (options.key) {
|
|
12
|
+
return await manualLogin(options.key);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Interactive mode - ask user how they want to authenticate
|
|
16
|
+
const { method } = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'list',
|
|
19
|
+
name: 'method',
|
|
20
|
+
message: 'How do you want to authenticate?',
|
|
21
|
+
choices: [
|
|
22
|
+
{ name: 'š Browser (recommended)', value: 'browser' },
|
|
23
|
+
{ name: 'š Enter API key manually', value: 'manual' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
if (method === 'browser') {
|
|
29
|
+
return await browserLogin();
|
|
30
|
+
} else {
|
|
31
|
+
return await manualLogin();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function browserLogin() {
|
|
36
|
+
const app = express();
|
|
37
|
+
const port = 8976;
|
|
38
|
+
|
|
39
|
+
console.log(chalk.cyan('\nš Authenticating with Lux...\n'));
|
|
40
|
+
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
let tokenReceived = false;
|
|
43
|
+
|
|
44
|
+
app.get('/callback', async (req, res) => {
|
|
45
|
+
const { token, orgId } = req.query;
|
|
46
|
+
|
|
47
|
+
if (!token || !orgId) {
|
|
48
|
+
res.send(`
|
|
49
|
+
<!DOCTYPE html>
|
|
50
|
+
<html>
|
|
51
|
+
<head>
|
|
52
|
+
<title>Authentication Failed</title>
|
|
53
|
+
<style>
|
|
54
|
+
body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f7fafc; }
|
|
55
|
+
.container { text-align: center; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
56
|
+
.error { color: #e53e3e; font-size: 1.5rem; margin-bottom: 1rem; }
|
|
57
|
+
</style>
|
|
58
|
+
</head>
|
|
59
|
+
<body>
|
|
60
|
+
<div class="container">
|
|
61
|
+
<div class="error">ā Authentication Failed</div>
|
|
62
|
+
<p>Please try again from your terminal.</p>
|
|
63
|
+
</div>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
`);
|
|
67
|
+
|
|
68
|
+
server.close();
|
|
69
|
+
reject(new Error('Authentication failed - missing token or org ID'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
tokenReceived = true;
|
|
74
|
+
|
|
75
|
+
// Save token and org ID
|
|
76
|
+
const config = {
|
|
77
|
+
token,
|
|
78
|
+
orgId,
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
saveConfig(config);
|
|
83
|
+
|
|
84
|
+
res.send(`
|
|
85
|
+
<!DOCTYPE html>
|
|
86
|
+
<html>
|
|
87
|
+
<head>
|
|
88
|
+
<title>Authentication Successful</title>
|
|
89
|
+
<style>
|
|
90
|
+
body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f7fafc; }
|
|
91
|
+
.container { text-align: center; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
92
|
+
.success { color: #38a169; font-size: 1.5rem; margin-bottom: 1rem; }
|
|
93
|
+
.info { color: #718096; margin-top: 1rem; }
|
|
94
|
+
</style>
|
|
95
|
+
</head>
|
|
96
|
+
<body>
|
|
97
|
+
<div class="container">
|
|
98
|
+
<div class="success">ā Authentication Successful!</div>
|
|
99
|
+
<p>You can now close this window and return to your terminal.</p>
|
|
100
|
+
<p class="info">Org ID: ${orgId}</p>
|
|
101
|
+
</div>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
|
104
|
+
`);
|
|
105
|
+
|
|
106
|
+
server.close();
|
|
107
|
+
resolve(config);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const server = app.listen(port, async () => {
|
|
111
|
+
const apiUrl = getApiUrl();
|
|
112
|
+
const authUrl = `${apiUrl}/cli-auth?port=${port}`;
|
|
113
|
+
|
|
114
|
+
console.log(chalk.dim(`Opening browser to: ${authUrl}\n`));
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await open(authUrl);
|
|
118
|
+
console.log(chalk.yellow('ā³ Waiting for authentication...\n'));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.log(chalk.red('Could not open browser automatically.'));
|
|
121
|
+
console.log(chalk.yellow(`\nPlease open this URL in your browser:\n`));
|
|
122
|
+
console.log(chalk.cyan(` ${authUrl}\n`));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Timeout after 5 minutes
|
|
127
|
+
setTimeout(
|
|
128
|
+
() => {
|
|
129
|
+
if (!tokenReceived) {
|
|
130
|
+
server.close();
|
|
131
|
+
reject(new Error('Authentication timed out'));
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
5 * 60 * 1000
|
|
135
|
+
);
|
|
136
|
+
})
|
|
137
|
+
.then((config) => {
|
|
138
|
+
console.log(chalk.green('ā Successfully authenticated!\n'));
|
|
139
|
+
console.log(chalk.dim(`Org ID: ${config.orgId}\n`));
|
|
140
|
+
console.log(chalk.cyan('You can now use Lux CLI commands.\n'));
|
|
141
|
+
process.exit(0);
|
|
142
|
+
})
|
|
143
|
+
.catch((error) => {
|
|
144
|
+
console.error(chalk.red('\nā Authentication failed:'), error.message);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function manualLogin(providedKey) {
|
|
150
|
+
let apiKey = providedKey;
|
|
151
|
+
|
|
152
|
+
// If key not provided via flag, prompt for it
|
|
153
|
+
if (!apiKey) {
|
|
154
|
+
const { key } = await inquirer.prompt([
|
|
155
|
+
{
|
|
156
|
+
type: 'password',
|
|
157
|
+
name: 'key',
|
|
158
|
+
message: 'Enter your API key:',
|
|
159
|
+
mask: '*',
|
|
160
|
+
validate: (input) => {
|
|
161
|
+
if (!input || input.trim().length === 0) {
|
|
162
|
+
return 'API key is required';
|
|
163
|
+
}
|
|
164
|
+
if (!input.startsWith('lux_')) {
|
|
165
|
+
return 'Invalid API key format (should start with "lux_")';
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
]);
|
|
171
|
+
apiKey = key;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const spinner = ora('Validating API key...').start();
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const apiUrl = getApiUrl();
|
|
178
|
+
|
|
179
|
+
// Validate the API key by making a test request
|
|
180
|
+
const response = await axios.get(`${apiUrl}/api/workflows`, {
|
|
181
|
+
headers: {
|
|
182
|
+
Authorization: `Bearer ${apiKey}`,
|
|
183
|
+
'X-Org-Id': extractOrgIdFromKey(apiKey),
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (response.status === 200) {
|
|
188
|
+
spinner.succeed(chalk.green('ā API key validated successfully!'));
|
|
189
|
+
|
|
190
|
+
// Save config
|
|
191
|
+
const config = {
|
|
192
|
+
token: apiKey,
|
|
193
|
+
orgId: extractOrgIdFromKey(apiKey),
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
saveConfig(config);
|
|
198
|
+
|
|
199
|
+
console.log(chalk.green('\nā Successfully authenticated!\n'));
|
|
200
|
+
console.log(chalk.dim(`Org ID: ${config.orgId}\n`));
|
|
201
|
+
console.log(chalk.cyan('You can now use Lux CLI commands.\n'));
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
spinner.fail(chalk.red('API key validation failed'));
|
|
205
|
+
|
|
206
|
+
if (error.response?.status === 401 || error.response?.status === 403) {
|
|
207
|
+
console.error(
|
|
208
|
+
chalk.red('\nā Invalid or expired API key. Please check your key and try again.\n')
|
|
209
|
+
);
|
|
210
|
+
} else {
|
|
211
|
+
console.error(
|
|
212
|
+
chalk.red('\nā Error:'),
|
|
213
|
+
error.response?.data?.error || error.message
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function extractOrgIdFromKey(apiKey) {
|
|
222
|
+
// API keys are formatted as: lux_{orgId}_{randomHex}
|
|
223
|
+
const parts = apiKey.split('_');
|
|
224
|
+
if (parts.length >= 2) {
|
|
225
|
+
return parts[1];
|
|
226
|
+
}
|
|
227
|
+
throw new Error('Invalid API key format');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function logout() {
|
|
231
|
+
const config = loadConfig();
|
|
232
|
+
|
|
233
|
+
if (!config) {
|
|
234
|
+
console.log(chalk.yellow('Not currently logged in.'));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { saveConfig } = require('../lib/config');
|
|
239
|
+
saveConfig({});
|
|
240
|
+
|
|
241
|
+
console.log(chalk.green('ā Successfully logged out'));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
login,
|
|
246
|
+
logout,
|
|
247
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { loadConfig, saveConfig } = require('../lib/config');
|
|
3
|
+
|
|
4
|
+
async function logout() {
|
|
5
|
+
const config = loadConfig();
|
|
6
|
+
|
|
7
|
+
if (!config || !config.token) {
|
|
8
|
+
console.log(chalk.yellow('Not currently logged in.'));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
saveConfig({});
|
|
13
|
+
|
|
14
|
+
console.log(chalk.green('ā Successfully logged out'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
logout,
|
|
19
|
+
};
|
package/commands/logs.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const {
|
|
4
|
+
loadInterfaceConfig,
|
|
5
|
+
getApiUrl,
|
|
6
|
+
getAuthHeaders,
|
|
7
|
+
isAuthenticated,
|
|
8
|
+
} = require('../lib/config');
|
|
9
|
+
|
|
10
|
+
async function logs(options) {
|
|
11
|
+
// Check authentication
|
|
12
|
+
if (!isAuthenticated()) {
|
|
13
|
+
console.log(
|
|
14
|
+
chalk.red('ā Not authenticated. Run'),
|
|
15
|
+
chalk.white('lux login'),
|
|
16
|
+
chalk.red('first.')
|
|
17
|
+
);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
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);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const apiUrl = getApiUrl();
|
|
33
|
+
const interfaceId = interfaceConfig.id;
|
|
34
|
+
const logType = options.type || 'runtime';
|
|
35
|
+
|
|
36
|
+
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
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(
|
|
46
|
+
chalk.red('\nā Error:'),
|
|
47
|
+
error.response?.data?.error || error.message
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (error.response?.status === 401) {
|
|
51
|
+
console.log(
|
|
52
|
+
chalk.yellow('\nYour session may have expired. Try running:'),
|
|
53
|
+
chalk.white('lux login')
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function fetchLogs(interfaceId, apiUrl, logType) {
|
|
62
|
+
const endpoint =
|
|
63
|
+
logType === 'build'
|
|
64
|
+
? `/api/interfaces/${interfaceId}/build-logs`
|
|
65
|
+
: `/api/interfaces/${interfaceId}/runtime-logs`;
|
|
66
|
+
|
|
67
|
+
const { data } = await axios.get(`${apiUrl}${endpoint}`, {
|
|
68
|
+
headers: getAuthHeaders(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (logType === 'build') {
|
|
72
|
+
displayBuildLogs(data.logs);
|
|
73
|
+
} else {
|
|
74
|
+
displayRuntimeLogs(data.logs);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
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
|
+
function displayBuildLogs(logs) {
|
|
123
|
+
if (!logs || logs.length === 0) {
|
|
124
|
+
console.log(chalk.dim('No logs available yet.'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const log of logs) {
|
|
129
|
+
const timestamp = log.timestamp
|
|
130
|
+
? new Date(log.timestamp).toLocaleTimeString()
|
|
131
|
+
: '';
|
|
132
|
+
const level = log.level || 'info';
|
|
133
|
+
|
|
134
|
+
let color = chalk.white;
|
|
135
|
+
if (level === 'error') color = chalk.red;
|
|
136
|
+
else if (level === 'warn') color = chalk.yellow;
|
|
137
|
+
else if (level === 'success') color = chalk.green;
|
|
138
|
+
|
|
139
|
+
console.log(
|
|
140
|
+
color(`[${timestamp}] ${log.message || log.text || JSON.stringify(log)}`)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function displayRuntimeLogs(logs) {
|
|
146
|
+
if (!logs || logs.length === 0) {
|
|
147
|
+
console.log(chalk.dim('No logs available yet.'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const log of logs) {
|
|
152
|
+
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
|
153
|
+
const level = log.level || 'log';
|
|
154
|
+
const message = log.message || '';
|
|
155
|
+
|
|
156
|
+
let prefix = '';
|
|
157
|
+
let color = chalk.white;
|
|
158
|
+
|
|
159
|
+
switch (level) {
|
|
160
|
+
case 'error':
|
|
161
|
+
prefix = 'ā';
|
|
162
|
+
color = chalk.red;
|
|
163
|
+
break;
|
|
164
|
+
case 'warn':
|
|
165
|
+
prefix = 'ā ļø ';
|
|
166
|
+
color = chalk.yellow;
|
|
167
|
+
break;
|
|
168
|
+
case 'info':
|
|
169
|
+
prefix = 'ā¹ļø ';
|
|
170
|
+
color = chalk.cyan;
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
prefix = ' ';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log(`${chalk.dim(`[${timestamp}]`)} ${prefix}${color(message)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
logs,
|
|
182
|
+
};
|