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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Interface (App) Commands
3
+ *
4
+ * This is the main entry point for interface/app commands.
5
+ * The implementation is split into modules under ./interface/:
6
+ *
7
+ * - git-utils.js - Git command helpers (runGitCommand, cloneRepoWithRetry)
8
+ * - boilerplate.js - Template generation (getBoilerplateFiles, writeBoilerplateFiles)
9
+ * - init.js - Interface initialization (initInterface, pushBoilerplateTemplate)
10
+ * - path.js - Path resolution (getInterfacePath)
11
+ */
12
+
13
+ const chalk = require('chalk');
14
+
15
+ // Import from modular files
16
+ const { initInterface } = require('./interface/init');
17
+ const { getInterfacePath } = require('./interface/path');
18
+
19
+ /**
20
+ * Deploy interface to production
21
+ */
22
+ async function deployInterface(options) {
23
+ const { deploy } = require('./deploy');
24
+ await deploy(options);
25
+ }
26
+
27
+ /**
28
+ * List all interfaces
29
+ */
30
+ async function listInterfaces(options) {
31
+ const { list } = require('./list');
32
+ await list(options);
33
+ }
34
+
35
+ /**
36
+ * View interface logs
37
+ */
38
+ async function viewLogs(options) {
39
+ const { logs } = require('./logs');
40
+ await logs(options);
41
+ }
42
+
43
+ /**
44
+ * Parse command options from args array
45
+ */
46
+ function parseOptions(args) {
47
+ const options = {};
48
+ for (let i = 0; i < args.length; i++) {
49
+ const arg = args[i];
50
+ if (arg.startsWith('--')) {
51
+ const key = arg.slice(2);
52
+ const value = args[i + 1];
53
+ options[key] = value || true;
54
+ if (value && !value.startsWith('--')) {
55
+ i++; // Skip next arg since we used it as a value
56
+ }
57
+ } else if (arg.startsWith('-')) {
58
+ const key = arg.slice(1);
59
+ const value = args[i + 1];
60
+ options[key] = value || true;
61
+ if (value && !value.startsWith('-')) {
62
+ i++;
63
+ }
64
+ }
65
+ }
66
+ return options;
67
+ }
68
+
69
+ /**
70
+ * Show help for interface commands
71
+ */
72
+ function showHelp() {
73
+ console.log(chalk.cyan('\nLux Interface Commands:\n'));
74
+ console.log(chalk.white(' lux interface create --name <name>') + chalk.dim(' (or lux i create)'));
75
+ console.log(chalk.dim(' Create new interface'));
76
+ console.log(chalk.dim(' Required: --name <interface-name>'));
77
+ console.log(chalk.dim(' Optional: --description <desc>\n'));
78
+ console.log(chalk.white(' lux interface init') + chalk.dim(' (alias for create)\n'));
79
+ console.log(chalk.white(' lux interface list') + chalk.dim(' (or lux i list)'));
80
+ console.log(chalk.dim(' List all interfaces\n'));
81
+ console.log(chalk.white(' lux interface path <name-or-id>'));
82
+ console.log(chalk.dim(' Get local repo path for an interface\n'));
83
+ console.log(chalk.white(' lux interface deploy') + chalk.dim(' (or lux i deploy)'));
84
+ console.log(chalk.dim(' Deploy interface to production\n'));
85
+ console.log(chalk.white(' lux interface logs') + chalk.dim(' (or lux i logs)'));
86
+ console.log(chalk.dim(' View interface logs\n'));
87
+ }
88
+
89
+ /**
90
+ * Handle interface commands
91
+ */
92
+ async function handleInterface(subcommand, args) {
93
+ if (!subcommand || subcommand === 'help') {
94
+ showHelp();
95
+ return;
96
+ }
97
+
98
+ switch (subcommand) {
99
+ case 'init':
100
+ case 'create':
101
+ await initInterface(parseOptions(args));
102
+ break;
103
+ case 'deploy':
104
+ await deployInterface(parseOptions(args));
105
+ break;
106
+ case 'list':
107
+ case 'ls':
108
+ await listInterfaces(parseOptions(args));
109
+ break;
110
+ case 'logs':
111
+ await viewLogs(parseOptions(args));
112
+ break;
113
+ case 'path':
114
+ await getInterfacePath(args[0]);
115
+ break;
116
+ default:
117
+ console.log(chalk.red(`Unknown subcommand: ${subcommand}`));
118
+ showHelp();
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ module.exports = {
124
+ handleInterface,
125
+ };
@@ -0,0 +1,339 @@
1
+ const axios = require('axios');
2
+ const chalk = require('chalk');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const FormData = require('form-data');
6
+ const {
7
+ getApiUrl,
8
+ getAuthHeaders,
9
+ isAuthenticated,
10
+ } = require('../lib/config');
11
+ const {
12
+ error,
13
+ success,
14
+ info,
15
+ formatJson,
16
+ formatTable,
17
+ requireArgs,
18
+ } = require('../lib/helpers');
19
+
20
+ async function handleKnowledge(args) {
21
+ // Check authentication
22
+ if (!isAuthenticated()) {
23
+ console.log(
24
+ chalk.red('āŒ Not authenticated. Run'),
25
+ chalk.white('lux login'),
26
+ chalk.red('first.')
27
+ );
28
+ process.exit(1);
29
+ }
30
+
31
+ const command = args[0];
32
+
33
+ if (!command) {
34
+ console.log(`
35
+ ${chalk.bold('Usage:')} lux knowledge <command> [args]
36
+
37
+ ${chalk.bold('Document Commands:')}
38
+ list List all documents and folders
39
+ get <document-id> Get document details and content
40
+ url <document-id> Get public URL for a document
41
+ upload <file-path> [folder-id] Upload a file to knowledge base
42
+ delete <document-id> Delete a document
43
+
44
+ ${chalk.bold('Folder Commands:')}
45
+ folders list List all folders
46
+ folders create <name> [parent-id] Create a new folder
47
+ folders delete <folder-id> Delete a folder
48
+
49
+ ${chalk.bold('Examples:')}
50
+ lux knowledge list
51
+ lux knowledge get doc_123abc
52
+ lux knowledge upload ./data.json
53
+ lux knowledge upload ./image.png folder_xyz
54
+ lux knowledge folders create "Product Docs"
55
+ `);
56
+ process.exit(0);
57
+ }
58
+
59
+ const apiUrl = getApiUrl();
60
+
61
+ try {
62
+ // ============ DOCUMENT COMMANDS ============
63
+ if (command === 'list') {
64
+ info('Loading knowledge base...');
65
+
66
+ const [foldersRes, docsRes] = await Promise.all([
67
+ axios.get(`${apiUrl}/api/knowledge/folders`, {
68
+ headers: getAuthHeaders(),
69
+ }),
70
+ axios.get(`${apiUrl}/api/knowledge/documents`, {
71
+ headers: getAuthHeaders(),
72
+ }),
73
+ ]);
74
+
75
+ const folders = foldersRes.data.folders || [];
76
+ const documents = docsRes.data.documents || [];
77
+
78
+ if (folders.length === 0 && documents.length === 0) {
79
+ console.log('\n(No knowledge items found)\n');
80
+ return;
81
+ }
82
+
83
+ if (folders.length > 0) {
84
+ console.log(`\n${chalk.bold('Folders')} (${folders.length}):\n`);
85
+ formatTable(folders.map(f => ({
86
+ id: f.id,
87
+ name: f.name,
88
+ path: f.path,
89
+ })));
90
+ }
91
+
92
+ if (documents.length > 0) {
93
+ console.log(`\n${chalk.bold('Documents')} (${documents.length}):\n`);
94
+ formatTable(documents.map(d => ({
95
+ id: d.id,
96
+ name: d.name,
97
+ type: d.document_type || d.file_type || 'unknown',
98
+ size: formatFileSize(d.file_size),
99
+ folder: d.folder_id || '(root)',
100
+ })));
101
+ }
102
+ console.log('');
103
+ }
104
+
105
+ else if (command === 'get') {
106
+ requireArgs(args.slice(1), 1, 'lux knowledge get <document-id>');
107
+ const docId = args[1];
108
+
109
+ info(`Loading document: ${docId}`);
110
+ const { data } = await axios.get(
111
+ `${apiUrl}/api/knowledge/documents/${docId}`,
112
+ { headers: getAuthHeaders() }
113
+ );
114
+
115
+ const doc = data.document;
116
+ console.log(`\n${chalk.bold('Document Details:')}`);
117
+ console.log(` ID: ${doc.id}`);
118
+ console.log(` Name: ${doc.name}`);
119
+ console.log(` Type: ${doc.document_type || doc.file_type || 'unknown'}`);
120
+ console.log(` Size: ${formatFileSize(doc.file_size)}`);
121
+ console.log(` Folder: ${doc.folder_id || '(root)'}`);
122
+
123
+ if (doc.public_url) {
124
+ console.log(` URL: ${doc.public_url}`);
125
+ }
126
+
127
+ if (doc.image_width && doc.image_height) {
128
+ console.log(` Dimensions: ${doc.image_width}x${doc.image_height}`);
129
+ console.log(` Est. Tokens: ${doc.estimated_tokens || 'N/A'}`);
130
+ }
131
+
132
+ if (doc.content) {
133
+ console.log(`\n${chalk.bold('Content:')}`);
134
+ console.log(doc.content.substring(0, 2000));
135
+ if (doc.content.length > 2000) {
136
+ console.log(chalk.gray(`\n... (${doc.content.length - 2000} more characters)`));
137
+ }
138
+ }
139
+ console.log('');
140
+ }
141
+
142
+ else if (command === 'url') {
143
+ requireArgs(args.slice(1), 1, 'lux knowledge url <document-id>');
144
+ const docId = args[1];
145
+
146
+ info(`Getting URL for document: ${docId}`);
147
+ const { data } = await axios.get(
148
+ `${apiUrl}/api/knowledge/documents/${docId}/presigned-url`,
149
+ { headers: getAuthHeaders() }
150
+ );
151
+
152
+ console.log(`\n${chalk.bold('Public URL:')}`);
153
+ console.log(data.presignedUrl);
154
+ console.log(`\nDocument: ${data.document.name} (${formatFileSize(data.document.file_size)})`);
155
+ console.log('');
156
+ }
157
+
158
+ else if (command === 'upload') {
159
+ requireArgs(args.slice(1), 1, 'lux knowledge upload <file-path> [folder-id]');
160
+ const filePath = args[1];
161
+ const folderId = args[2] || null;
162
+
163
+ // Check file exists
164
+ if (!fs.existsSync(filePath)) {
165
+ error(`File not found: ${filePath}`);
166
+ return;
167
+ }
168
+
169
+ const fileName = path.basename(filePath);
170
+ const fileBuffer = fs.readFileSync(filePath);
171
+ const fileSize = fileBuffer.length;
172
+
173
+ // Detect MIME type
174
+ const ext = path.extname(fileName).toLowerCase();
175
+ const mimeTypes = {
176
+ '.txt': 'text/plain',
177
+ '.json': 'application/json',
178
+ '.md': 'text/markdown',
179
+ '.csv': 'text/csv',
180
+ '.html': 'text/html',
181
+ '.xml': 'text/xml',
182
+ '.pdf': 'application/pdf',
183
+ '.png': 'image/png',
184
+ '.jpg': 'image/jpeg',
185
+ '.jpeg': 'image/jpeg',
186
+ '.gif': 'image/gif',
187
+ '.webp': 'image/webp',
188
+ '.svg': 'image/svg+xml',
189
+ };
190
+ const fileType = mimeTypes[ext] || 'application/octet-stream';
191
+
192
+ info(`Uploading: ${fileName} (${formatFileSize(fileSize)})`);
193
+
194
+ // Step 1: Get presigned upload URL
195
+ const { data: uploadData } = await axios.post(
196
+ `${apiUrl}/api/knowledge/upload`,
197
+ { fileName, fileType },
198
+ { headers: getAuthHeaders() }
199
+ );
200
+
201
+ // Step 2: Upload to R2
202
+ info('Uploading to storage...');
203
+ await axios.put(uploadData.uploadUrl, fileBuffer, {
204
+ headers: {
205
+ 'Content-Type': fileType,
206
+ },
207
+ maxBodyLength: Infinity,
208
+ maxContentLength: Infinity,
209
+ });
210
+
211
+ // Step 3: Complete upload (save to database)
212
+ info('Saving document metadata...');
213
+ const { data: completeData } = await axios.put(
214
+ `${apiUrl}/api/knowledge/upload`,
215
+ {
216
+ filePath: uploadData.filePath,
217
+ fileName,
218
+ fileType,
219
+ folderId,
220
+ fileSize,
221
+ },
222
+ { headers: getAuthHeaders() }
223
+ );
224
+
225
+ success('Document uploaded!');
226
+ console.log(` ID: ${completeData.document.id}`);
227
+ console.log(` Name: ${completeData.document.name}`);
228
+ if (completeData.document.public_url) {
229
+ console.log(` URL: ${completeData.document.public_url}`);
230
+ }
231
+ console.log('');
232
+ }
233
+
234
+ else if (command === 'delete') {
235
+ requireArgs(args.slice(1), 1, 'lux knowledge delete <document-id>');
236
+ const docId = args[1];
237
+
238
+ info(`Deleting document: ${docId}`);
239
+ await axios.delete(
240
+ `${apiUrl}/api/knowledge/documents/${docId}`,
241
+ { headers: getAuthHeaders() }
242
+ );
243
+
244
+ success('Document deleted!');
245
+ }
246
+
247
+ // ============ FOLDER COMMANDS ============
248
+ else if (command === 'folders') {
249
+ const subCommand = args[1];
250
+
251
+ if (!subCommand || subCommand === 'list') {
252
+ info('Loading folders...');
253
+ const { data } = await axios.get(`${apiUrl}/api/knowledge/folders`, {
254
+ headers: getAuthHeaders(),
255
+ });
256
+
257
+ if (!data.folders || data.folders.length === 0) {
258
+ console.log('\n(No folders found)\n');
259
+ } else {
260
+ console.log(`\nFound ${data.folders.length} folder(s):\n`);
261
+ formatTable(data.folders.map(f => ({
262
+ id: f.id,
263
+ name: f.name,
264
+ path: f.path,
265
+ parent: f.parent_id || '(root)',
266
+ })));
267
+ console.log('');
268
+ }
269
+ }
270
+
271
+ else if (subCommand === 'create') {
272
+ requireArgs(args.slice(2), 1, 'lux knowledge folders create <name> [parent-id]');
273
+ const name = args[2];
274
+ const parentId = args[3] || null;
275
+
276
+ // Calculate path
277
+ let folderPath = `/${name}`;
278
+ if (parentId) {
279
+ // Fetch parent to get its path
280
+ const { data: foldersData } = await axios.get(`${apiUrl}/api/knowledge/folders`, {
281
+ headers: getAuthHeaders(),
282
+ });
283
+ const parent = (foldersData.folders || []).find(f => f.id === parentId);
284
+ if (parent) {
285
+ folderPath = `${parent.path}/${name}`;
286
+ }
287
+ }
288
+
289
+ info(`Creating folder: ${name}`);
290
+ const { data } = await axios.post(
291
+ `${apiUrl}/api/knowledge/folders`,
292
+ { name, parent_id: parentId, path: folderPath },
293
+ { headers: getAuthHeaders() }
294
+ );
295
+
296
+ success('Folder created!');
297
+ console.log(` ID: ${data.folder.id}`);
298
+ console.log(` Name: ${data.folder.name}`);
299
+ console.log(` Path: ${data.folder.path}`);
300
+ console.log('');
301
+ }
302
+
303
+ else if (subCommand === 'delete') {
304
+ requireArgs(args.slice(2), 1, 'lux knowledge folders delete <folder-id>');
305
+ const folderId = args[2];
306
+
307
+ info(`Deleting folder: ${folderId}`);
308
+ await axios.delete(
309
+ `${apiUrl}/api/knowledge/folders?id=${folderId}`,
310
+ { headers: getAuthHeaders() }
311
+ );
312
+
313
+ success('Folder deleted!');
314
+ }
315
+
316
+ else {
317
+ error(`Unknown folder subcommand: ${subCommand}`);
318
+ }
319
+ }
320
+
321
+ else {
322
+ error(`Unknown command: ${command}\n\nRun 'lux knowledge' to see available commands`);
323
+ }
324
+ } catch (err) {
325
+ const errorMessage =
326
+ err.response?.data?.error || err.message || 'Unknown error';
327
+ error(`${errorMessage}`);
328
+ }
329
+ }
330
+
331
+ function formatFileSize(bytes) {
332
+ if (!bytes || bytes === 0) return '0 B';
333
+ const k = 1024;
334
+ const sizes = ['B', 'KB', 'MB', 'GB'];
335
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
336
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
337
+ }
338
+
339
+ module.exports = { handleKnowledge };
@@ -0,0 +1,127 @@
1
+ const axios = require('axios');
2
+ const chalk = require('chalk');
3
+ const ora = require('ora');
4
+ const {
5
+ saveInterfaceConfig,
6
+ loadInterfaceConfig,
7
+ getApiUrl,
8
+ getAuthHeaders,
9
+ isAuthenticated,
10
+ } = require('../lib/config');
11
+
12
+ async function link(interfaceId) {
13
+ // Check authentication
14
+ if (!isAuthenticated()) {
15
+ console.log(
16
+ chalk.red('āŒ Not authenticated. Run'),
17
+ chalk.white('lux login'),
18
+ chalk.red('first.')
19
+ );
20
+ process.exit(1);
21
+ }
22
+
23
+ if (!interfaceId) {
24
+ console.log(chalk.red('āŒ Interface ID required.'));
25
+ console.log(chalk.dim('Usage:'), chalk.white('lux link <interface-id>'));
26
+ process.exit(1);
27
+ }
28
+
29
+ // Check if already linked
30
+ const existing = loadInterfaceConfig();
31
+ if (existing && existing.id) {
32
+ console.log(
33
+ chalk.yellow('āš ļø Already linked to:'),
34
+ existing.name,
35
+ chalk.dim(`(${existing.id})`)
36
+ );
37
+
38
+ const inquirer = require('inquirer');
39
+ const { confirm } = await inquirer.prompt([
40
+ {
41
+ type: 'confirm',
42
+ name: 'confirm',
43
+ message: 'Do you want to relink to a different interface?',
44
+ default: false,
45
+ },
46
+ ]);
47
+
48
+ if (!confirm) {
49
+ console.log(chalk.dim('Cancelled.'));
50
+ return;
51
+ }
52
+ }
53
+
54
+ const apiUrl = getApiUrl();
55
+ const spinner = ora('Fetching interface...').start();
56
+
57
+ try {
58
+ // Fetch interface details
59
+ const { data } = await axios.get(
60
+ `${apiUrl}/api/interfaces/${interfaceId}`,
61
+ {
62
+ headers: getAuthHeaders(),
63
+ }
64
+ );
65
+
66
+ const iface = data.interface;
67
+
68
+ if (!iface) {
69
+ spinner.fail('Interface not found');
70
+ process.exit(1);
71
+ }
72
+
73
+ spinner.succeed(chalk.green('āœ“ Found interface'));
74
+
75
+ // Save config
76
+ const config = {
77
+ id: iface.id,
78
+ name: iface.name,
79
+ description: iface.description,
80
+ githubRepoUrl: iface.github_repo_url,
81
+ deploymentUrl: iface.vercel_deployment_url,
82
+ linkedAt: new Date().toISOString(),
83
+ };
84
+
85
+ saveInterfaceConfig(config);
86
+
87
+ console.log(chalk.green('\nāœ“ Linked successfully!\n'));
88
+ console.log(chalk.white(iface.name));
89
+ console.log(chalk.dim(iface.description || 'No description'));
90
+
91
+ if (iface.vercel_deployment_url) {
92
+ console.log(chalk.cyan(`\n${iface.vercel_deployment_url}`));
93
+ }
94
+
95
+ console.log(chalk.dim('\nYou can now run:'));
96
+ console.log(chalk.white(' lux up'), chalk.dim('- Upload changes'));
97
+ console.log(
98
+ chalk.white(' lux deploy'),
99
+ chalk.dim('- Deploy to production')
100
+ );
101
+ console.log(chalk.white(' lux logs'), chalk.dim('- View logs\n'));
102
+ } catch (error) {
103
+ spinner.fail('Failed to link');
104
+ console.error(
105
+ chalk.red('\nāŒ Error:'),
106
+ error.response?.data?.error || error.message
107
+ );
108
+
109
+ if (error.response?.status === 401) {
110
+ console.log(
111
+ chalk.yellow('\nYour session may have expired. Try running:'),
112
+ chalk.white('lux login')
113
+ );
114
+ } else if (error.response?.status === 404) {
115
+ console.log(
116
+ chalk.yellow('\nInterface not found. Check the ID and try again.')
117
+ );
118
+ console.log(chalk.dim('List interfaces with:'), chalk.white('lux list'));
119
+ }
120
+
121
+ process.exit(1);
122
+ }
123
+ }
124
+
125
+ module.exports = {
126
+ link,
127
+ };
@@ -0,0 +1,97 @@
1
+ const axios = require('axios');
2
+ const chalk = require('chalk');
3
+ const { getApiUrl, getAuthHeaders, isAuthenticated } = require('../lib/config');
4
+
5
+ async function list(options) {
6
+ // Check authentication
7
+ if (!isAuthenticated()) {
8
+ console.log(
9
+ chalk.red('āŒ Not authenticated. Run'),
10
+ chalk.white('lux login'),
11
+ chalk.red('first.')
12
+ );
13
+ process.exit(1);
14
+ }
15
+
16
+ const apiUrl = getApiUrl();
17
+
18
+ try {
19
+ const { data } = await axios.get(`${apiUrl}/api/interfaces`, {
20
+ headers: getAuthHeaders(),
21
+ });
22
+
23
+ const interfaces = data.interfaces || [];
24
+
25
+ if (interfaces.length === 0) {
26
+ console.log(chalk.yellow('\nNo interfaces found.'));
27
+ console.log(
28
+ chalk.dim('Create one with:'),
29
+ chalk.white('lux init'),
30
+ chalk.dim('and'),
31
+ chalk.white('lux up\n')
32
+ );
33
+ return;
34
+ }
35
+
36
+ console.log(chalk.cyan(`\nšŸ“¦ Interfaces (${interfaces.length})\n`));
37
+
38
+ for (const iface of interfaces) {
39
+ const status = getStatusDisplay(iface.status);
40
+
41
+ console.log(
42
+ `šŸ“± ${chalk.white(iface.name)} ${chalk.dim(`(${iface.id})`)}`
43
+ );
44
+ console.log(
45
+ ` ${status} ${chalk.dim(iface.description || 'No description')}`
46
+ );
47
+
48
+ if (iface.vercel_deployment_url) {
49
+ console.log(` ${chalk.cyan(iface.vercel_deployment_url)}`);
50
+ }
51
+
52
+ if (iface.github_repo_url) {
53
+ console.log(` ${chalk.dim(iface.github_repo_url)}`);
54
+ }
55
+
56
+ console.log('');
57
+ }
58
+
59
+ console.log(
60
+ chalk.dim('Link to an interface: '),
61
+ chalk.white('lux link <interface-id>\n')
62
+ );
63
+ } catch (error) {
64
+ console.error(
65
+ chalk.red('\nāŒ Error:'),
66
+ error.response?.data?.error || error.message
67
+ );
68
+
69
+ if (error.response?.status === 401) {
70
+ console.log(
71
+ chalk.yellow('\nYour session may have expired. Try running:'),
72
+ chalk.white('lux login')
73
+ );
74
+ }
75
+
76
+ process.exit(1);
77
+ }
78
+ }
79
+
80
+ function getStatusDisplay(status) {
81
+ switch (status) {
82
+ case 'draft':
83
+ return chalk.gray('ā—‹ draft');
84
+ case 'building':
85
+ return chalk.yellow('◐ building');
86
+ case 'published':
87
+ return chalk.green('ā— published');
88
+ case 'failed':
89
+ return chalk.red('āœ• failed');
90
+ default:
91
+ return chalk.gray(`ā—‹ ${status}`);
92
+ }
93
+ }
94
+
95
+ module.exports = {
96
+ list,
97
+ };