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,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 };
|
package/commands/link.js
ADDED
|
@@ -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
|
+
};
|
package/commands/list.js
ADDED
|
@@ -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
|
+
};
|