luxlabs 1.0.19 → 1.0.20
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/commands/dev.js +11 -2
- package/commands/flows.js +13 -9
- package/commands/knowledge.js +193 -137
- package/commands/login.js +1 -1
- package/commands/project.js +3 -2
- package/package.json +1 -1
package/commands/dev.js
CHANGED
|
@@ -124,12 +124,21 @@ async function dev(options = {}) {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Get the correct next binary path for the current platform
|
|
129
|
+
* Points directly to Next.js CLI JS file to bypass .bin shell script wrappers (cross-platform)
|
|
130
|
+
*/
|
|
131
|
+
function getNextBinPath() {
|
|
132
|
+
return './node_modules/next/dist/bin/next';
|
|
133
|
+
}
|
|
134
|
+
|
|
127
135
|
/**
|
|
128
136
|
* Check and install npm dependencies if needed
|
|
129
137
|
*/
|
|
130
138
|
async function checkDependencies() {
|
|
131
139
|
const nodeModulesExists = fs.existsSync('node_modules');
|
|
132
|
-
const
|
|
140
|
+
const nextBinPath = getNextBinPath();
|
|
141
|
+
const nextBinExists = fs.existsSync(nextBinPath);
|
|
133
142
|
|
|
134
143
|
if (!nodeModulesExists || !nextBinExists) {
|
|
135
144
|
console.log(chalk.yellow('📦 Installing dependencies...'));
|
|
@@ -157,7 +166,7 @@ async function startDevServer(port) {
|
|
|
157
166
|
return new Promise((resolve, reject) => {
|
|
158
167
|
console.log(chalk.dim(`Starting Next.js on port ${port}...`));
|
|
159
168
|
|
|
160
|
-
const nextBin =
|
|
169
|
+
const nextBin = getNextBinPath();
|
|
161
170
|
const nodePath = getNodePath();
|
|
162
171
|
const nodeEnv = getNodeEnv();
|
|
163
172
|
|
package/commands/flows.js
CHANGED
|
@@ -56,7 +56,7 @@ async function getCapturedPayload(token) {
|
|
|
56
56
|
async function getWorkflowDraft(workflowId) {
|
|
57
57
|
const apiUrl = getApiUrl();
|
|
58
58
|
const { data } = await axios.get(
|
|
59
|
-
`${apiUrl}/api/
|
|
59
|
+
`${apiUrl}/api/flows/${workflowId}/draft`,
|
|
60
60
|
{ headers: getAuthHeaders() }
|
|
61
61
|
);
|
|
62
62
|
return data;
|
|
@@ -68,7 +68,7 @@ async function getWorkflowDraft(workflowId) {
|
|
|
68
68
|
async function saveWorkflowDraft(workflowId, config) {
|
|
69
69
|
const apiUrl = getApiUrl();
|
|
70
70
|
await axios.put(
|
|
71
|
-
`${apiUrl}/api/
|
|
71
|
+
`${apiUrl}/api/flows/${workflowId}/draft`,
|
|
72
72
|
{ config },
|
|
73
73
|
{ headers: getAuthHeaders() }
|
|
74
74
|
);
|
|
@@ -255,7 +255,7 @@ ${chalk.bold('Examples:')}
|
|
|
255
255
|
case 'sync': {
|
|
256
256
|
info('Syncing flows from cloud...');
|
|
257
257
|
|
|
258
|
-
const { data } = await axios.get(`${apiUrl}/api/
|
|
258
|
+
const { data } = await axios.get(`${apiUrl}/api/flows?include_config=true`, {
|
|
259
259
|
headers: getAuthHeaders(),
|
|
260
260
|
});
|
|
261
261
|
|
|
@@ -388,7 +388,7 @@ ${chalk.bold('Examples:')}
|
|
|
388
388
|
|
|
389
389
|
info(`Loading flow: ${flowId}`);
|
|
390
390
|
const { data } = await axios.get(
|
|
391
|
-
`${apiUrl}/api/
|
|
391
|
+
`${apiUrl}/api/flows/${flowId}`,
|
|
392
392
|
{ headers: getAuthHeaders() }
|
|
393
393
|
);
|
|
394
394
|
|
|
@@ -472,9 +472,9 @@ ${chalk.bold('Examples:')}
|
|
|
472
472
|
metadata: newFlow.metadata || {},
|
|
473
473
|
};
|
|
474
474
|
|
|
475
|
-
// Step 1: Create
|
|
475
|
+
// Step 1: Create flow in cloud database (this generates a cloud UUID)
|
|
476
476
|
const createResponse = await axios.post(
|
|
477
|
-
`${apiUrl}/api/
|
|
477
|
+
`${apiUrl}/api/flows`,
|
|
478
478
|
{
|
|
479
479
|
name: newFlow.name,
|
|
480
480
|
description: newFlow.description,
|
|
@@ -490,9 +490,9 @@ ${chalk.bold('Examples:')}
|
|
|
490
490
|
|
|
491
491
|
info(`Created in cloud with ID: ${cloudId}`);
|
|
492
492
|
|
|
493
|
-
// Step 2: Publish the
|
|
493
|
+
// Step 2: Publish the flow (copies draft config to published)
|
|
494
494
|
const publishResponse = await axios.post(
|
|
495
|
-
`${apiUrl}/api/
|
|
495
|
+
`${apiUrl}/api/flows/${cloudId}/publish`,
|
|
496
496
|
{},
|
|
497
497
|
{ headers: getAuthHeaders() }
|
|
498
498
|
);
|
|
@@ -515,6 +515,8 @@ ${chalk.bold('Examples:')}
|
|
|
515
515
|
// Cloud sync tracking - matches markFlowPublished()
|
|
516
516
|
cloudPublishedAt: now,
|
|
517
517
|
cloudStatus: 'published',
|
|
518
|
+
// Track who deployed (for teammate detection)
|
|
519
|
+
lastDeployedBy: publishResponse.data.triggeredBy || null,
|
|
518
520
|
};
|
|
519
521
|
saveLocalFlow(flowId, updatedFlow);
|
|
520
522
|
|
|
@@ -615,7 +617,7 @@ ${chalk.bold('Examples:')}
|
|
|
615
617
|
};
|
|
616
618
|
|
|
617
619
|
const { data } = await axios.post(
|
|
618
|
-
`${apiUrl}/api/
|
|
620
|
+
`${apiUrl}/api/flows/${flowId}/publish`,
|
|
619
621
|
{
|
|
620
622
|
name: localFlow.name,
|
|
621
623
|
description: localFlow.description,
|
|
@@ -642,6 +644,8 @@ ${chalk.bold('Examples:')}
|
|
|
642
644
|
// Cloud sync tracking - matches markFlowPublished()
|
|
643
645
|
cloudPublishedAt: now,
|
|
644
646
|
cloudStatus: 'published',
|
|
647
|
+
// Track who deployed (for teammate detection)
|
|
648
|
+
lastDeployedBy: data.triggeredBy || null,
|
|
645
649
|
};
|
|
646
650
|
saveLocalFlow(flowId, updatedFlow);
|
|
647
651
|
|
package/commands/knowledge.js
CHANGED
|
@@ -5,9 +5,15 @@ const path = require('path');
|
|
|
5
5
|
const FormData = require('form-data');
|
|
6
6
|
const {
|
|
7
7
|
getApiUrl,
|
|
8
|
+
getStudioApiUrl,
|
|
8
9
|
getAuthHeaders,
|
|
9
10
|
isAuthenticated,
|
|
11
|
+
getProjectId,
|
|
12
|
+
getOrgId,
|
|
10
13
|
} = require('../lib/config');
|
|
14
|
+
|
|
15
|
+
// Public R2 URL for the lux-knowledge bucket
|
|
16
|
+
const R2_PUBLIC_URL = 'https://pub-12ee5415430243b7bfc167df804f52e2.r2.dev';
|
|
11
17
|
const {
|
|
12
18
|
error,
|
|
13
19
|
success,
|
|
@@ -36,138 +42,163 @@ ${chalk.bold('Usage:')} lux knowledge <command> [args]
|
|
|
36
42
|
|
|
37
43
|
${chalk.bold('Document Commands:')}
|
|
38
44
|
list List all documents and folders
|
|
39
|
-
get <
|
|
40
|
-
url <
|
|
41
|
-
upload <file
|
|
42
|
-
delete <
|
|
45
|
+
get <file-path> Get document details and content
|
|
46
|
+
url <file-path> Get public URL for a document
|
|
47
|
+
upload <local-file> [folder] Upload a file to knowledge base
|
|
48
|
+
delete <file-path> Delete a document
|
|
43
49
|
|
|
44
50
|
${chalk.bold('Folder Commands:')}
|
|
45
51
|
folders list List all folders
|
|
46
|
-
folders create <
|
|
47
|
-
folders delete <folder-
|
|
52
|
+
folders create <folder-path> Create a new folder
|
|
53
|
+
folders delete <folder-path> Delete a folder and all contents
|
|
48
54
|
|
|
49
55
|
${chalk.bold('Examples:')}
|
|
50
56
|
lux knowledge list
|
|
51
|
-
lux knowledge get
|
|
57
|
+
lux knowledge get custom-designs/blanks/blank-white-front.png
|
|
58
|
+
lux knowledge url custom-designs/blanks/blank-white-front.png
|
|
52
59
|
lux knowledge upload ./data.json
|
|
53
|
-
lux knowledge upload ./image.png
|
|
54
|
-
lux knowledge folders create
|
|
60
|
+
lux knowledge upload ./image.png custom-designs/blanks
|
|
61
|
+
lux knowledge folders create product-docs
|
|
62
|
+
lux knowledge folders delete product-docs
|
|
55
63
|
`);
|
|
56
64
|
process.exit(0);
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
const apiUrl = getApiUrl();
|
|
68
|
+
const studioApiUrl = getStudioApiUrl();
|
|
69
|
+
const projectId = getProjectId();
|
|
60
70
|
|
|
61
71
|
try {
|
|
62
72
|
// ============ DOCUMENT COMMANDS ============
|
|
63
73
|
if (command === 'list') {
|
|
64
74
|
info('Loading knowledge base...');
|
|
65
75
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
const { data } = await axios.get(
|
|
77
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge`,
|
|
78
|
+
{ headers: getAuthHeaders() }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!data.success) {
|
|
82
|
+
error(data.error || 'Failed to list knowledge');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
74
85
|
|
|
75
|
-
const
|
|
76
|
-
const
|
|
86
|
+
const tree = data.tree || [];
|
|
87
|
+
const totalFiles = data.totalFiles || 0;
|
|
77
88
|
|
|
78
|
-
if (
|
|
89
|
+
if (tree.length === 0) {
|
|
79
90
|
console.log('\n(No knowledge items found)\n');
|
|
80
91
|
return;
|
|
81
92
|
}
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
console.log(`\n${chalk.bold('Knowledge Base')} (${totalFiles} files):\n`);
|
|
95
|
+
|
|
96
|
+
// Print tree structure
|
|
97
|
+
function printTree(nodes, indent = '') {
|
|
98
|
+
for (const node of nodes) {
|
|
99
|
+
if (node.type === 'folder') {
|
|
100
|
+
console.log(`${indent}${chalk.blue('📁')} ${chalk.bold(node.name)}/`);
|
|
101
|
+
if (node.children && node.children.length > 0) {
|
|
102
|
+
printTree(node.children, indent + ' ');
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
const size = node.size ? ` (${formatFileSize(node.size)})` : '';
|
|
106
|
+
console.log(`${indent}${chalk.gray('📄')} ${node.name}${chalk.gray(size)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
90
109
|
}
|
|
91
110
|
|
|
92
|
-
|
|
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
|
-
}
|
|
111
|
+
printTree(tree);
|
|
102
112
|
console.log('');
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
else if (command === 'get') {
|
|
106
|
-
requireArgs(args.slice(1), 1, 'lux knowledge get <
|
|
107
|
-
const
|
|
116
|
+
requireArgs(args.slice(1), 1, 'lux knowledge get <file-path>');
|
|
117
|
+
const filePath = args[1];
|
|
118
|
+
const orgId = getOrgId();
|
|
108
119
|
|
|
109
|
-
info(`Loading document: ${
|
|
120
|
+
info(`Loading document: ${filePath}`);
|
|
110
121
|
const { data } = await axios.get(
|
|
111
|
-
`${
|
|
122
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge/${filePath}`,
|
|
112
123
|
{ headers: getAuthHeaders() }
|
|
113
124
|
);
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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}`);
|
|
126
|
+
if (!data.success) {
|
|
127
|
+
error(data.error || 'Failed to get file');
|
|
128
|
+
return;
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
// Extract filename from path
|
|
132
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
133
|
+
const ext = fileName.split('.').pop()?.toLowerCase() || '';
|
|
134
|
+
|
|
135
|
+
console.log(`\n${chalk.bold('Document Details:')}`);
|
|
136
|
+
console.log(` Path: ${filePath}`);
|
|
137
|
+
console.log(` Name: ${fileName}`);
|
|
138
|
+
console.log(` Size: ${formatFileSize(data.size)}`);
|
|
139
|
+
console.log(` Encoding: ${data.encoding}`);
|
|
140
|
+
console.log(` Modified: ${data.modifiedAt || 'N/A'}`);
|
|
141
|
+
|
|
142
|
+
// Show public URL for binary files
|
|
143
|
+
if (data.encoding === 'base64') {
|
|
144
|
+
const publicUrl = `${R2_PUBLIC_URL}/${orgId}/${projectId}/knowledge/${filePath}`;
|
|
145
|
+
console.log(` URL: ${publicUrl}`);
|
|
130
146
|
}
|
|
131
147
|
|
|
132
|
-
|
|
148
|
+
// Show content preview for text files
|
|
149
|
+
if (data.encoding === 'utf-8' && data.content) {
|
|
133
150
|
console.log(`\n${chalk.bold('Content:')}`);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
const preview = data.content.substring(0, 2000);
|
|
152
|
+
console.log(preview);
|
|
153
|
+
if (data.content.length > 2000) {
|
|
154
|
+
console.log(chalk.gray(`\n... (${data.content.length - 2000} more characters)`));
|
|
137
155
|
}
|
|
138
156
|
}
|
|
139
157
|
console.log('');
|
|
140
158
|
}
|
|
141
159
|
|
|
142
160
|
else if (command === 'url') {
|
|
143
|
-
requireArgs(args.slice(1), 1, 'lux knowledge url <
|
|
144
|
-
const
|
|
161
|
+
requireArgs(args.slice(1), 1, 'lux knowledge url <file-path>');
|
|
162
|
+
const filePath = args[1];
|
|
163
|
+
const orgId = getOrgId();
|
|
145
164
|
|
|
146
|
-
info(`Getting URL for document: ${
|
|
165
|
+
info(`Getting URL for document: ${filePath}`);
|
|
166
|
+
|
|
167
|
+
// Verify the file exists by fetching from the project-based knowledge API
|
|
147
168
|
const { data } = await axios.get(
|
|
148
|
-
`${
|
|
169
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge/${filePath}`,
|
|
149
170
|
{ headers: getAuthHeaders() }
|
|
150
171
|
);
|
|
151
172
|
|
|
173
|
+
if (!data.success) {
|
|
174
|
+
error(data.error || 'Failed to get file');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Construct the public R2 URL
|
|
179
|
+
// Format: https://pub-{bucket-id}.r2.dev/{orgId}/{projectId}/knowledge/{filePath}
|
|
180
|
+
const publicUrl = `${R2_PUBLIC_URL}/${orgId}/${projectId}/knowledge/${filePath}`;
|
|
181
|
+
|
|
152
182
|
console.log(`\n${chalk.bold('Public URL:')}`);
|
|
153
|
-
console.log(
|
|
154
|
-
console.log(`\
|
|
183
|
+
console.log(publicUrl);
|
|
184
|
+
console.log(`\nFile: ${filePath} (${formatFileSize(data.size)})`);
|
|
155
185
|
console.log('');
|
|
156
186
|
}
|
|
157
187
|
|
|
158
188
|
else if (command === 'upload') {
|
|
159
|
-
requireArgs(args.slice(1), 1, 'lux knowledge upload <file
|
|
160
|
-
const
|
|
161
|
-
const
|
|
189
|
+
requireArgs(args.slice(1), 1, 'lux knowledge upload <local-file> [folder]');
|
|
190
|
+
const localFilePath = args[1];
|
|
191
|
+
const folder = args[2] || '';
|
|
192
|
+
const orgId = getOrgId();
|
|
162
193
|
|
|
163
194
|
// Check file exists
|
|
164
|
-
if (!fs.existsSync(
|
|
165
|
-
error(`File not found: ${
|
|
195
|
+
if (!fs.existsSync(localFilePath)) {
|
|
196
|
+
error(`File not found: ${localFilePath}`);
|
|
166
197
|
return;
|
|
167
198
|
}
|
|
168
199
|
|
|
169
|
-
const fileName = path.basename(
|
|
170
|
-
const fileBuffer = fs.readFileSync(
|
|
200
|
+
const fileName = path.basename(localFilePath);
|
|
201
|
+
const fileBuffer = fs.readFileSync(localFilePath);
|
|
171
202
|
const fileSize = fileBuffer.length;
|
|
172
203
|
|
|
173
204
|
// Detect MIME type
|
|
@@ -187,60 +218,70 @@ ${chalk.bold('Examples:')}
|
|
|
187
218
|
'.webp': 'image/webp',
|
|
188
219
|
'.svg': 'image/svg+xml',
|
|
189
220
|
};
|
|
190
|
-
const
|
|
221
|
+
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
191
222
|
|
|
192
223
|
info(`Uploading: ${fileName} (${formatFileSize(fileSize)})`);
|
|
193
224
|
|
|
194
|
-
// Step 1: Get presigned upload URL
|
|
225
|
+
// Step 1: Get presigned upload URL from the project-based API
|
|
195
226
|
const { data: uploadData } = await axios.post(
|
|
196
|
-
`${
|
|
197
|
-
{ fileName,
|
|
227
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge/upload-url`,
|
|
228
|
+
{ path: fileName, contentType, folder },
|
|
198
229
|
{ headers: getAuthHeaders() }
|
|
199
230
|
);
|
|
200
231
|
|
|
201
|
-
|
|
232
|
+
if (!uploadData.success) {
|
|
233
|
+
error(uploadData.error || 'Failed to get upload URL');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Step 2: Upload directly to R2 using presigned URL
|
|
202
238
|
info('Uploading to storage...');
|
|
203
239
|
await axios.put(uploadData.uploadUrl, fileBuffer, {
|
|
204
240
|
headers: {
|
|
205
|
-
'Content-Type':
|
|
241
|
+
'Content-Type': contentType,
|
|
206
242
|
},
|
|
207
243
|
maxBodyLength: Infinity,
|
|
208
244
|
maxContentLength: Infinity,
|
|
209
245
|
});
|
|
210
246
|
|
|
211
|
-
// Step 3:
|
|
212
|
-
info('
|
|
213
|
-
const { data:
|
|
214
|
-
`${
|
|
215
|
-
{
|
|
216
|
-
filePath: uploadData.filePath,
|
|
217
|
-
fileName,
|
|
218
|
-
fileType,
|
|
219
|
-
folderId,
|
|
220
|
-
fileSize,
|
|
221
|
-
},
|
|
247
|
+
// Step 3: Confirm upload completed
|
|
248
|
+
info('Confirming upload...');
|
|
249
|
+
const { data: confirmData } = await axios.post(
|
|
250
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge/confirm-upload`,
|
|
251
|
+
{ path: uploadData.path, size: fileSize },
|
|
222
252
|
{ headers: getAuthHeaders() }
|
|
223
253
|
);
|
|
224
254
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (completeData.document.public_url) {
|
|
229
|
-
console.log(` URL: ${completeData.document.public_url}`);
|
|
255
|
+
if (!confirmData.success) {
|
|
256
|
+
error(confirmData.error || 'Failed to confirm upload');
|
|
257
|
+
return;
|
|
230
258
|
}
|
|
259
|
+
|
|
260
|
+
// Construct public URL
|
|
261
|
+
const publicUrl = `${R2_PUBLIC_URL}/${orgId}/${projectId}/knowledge/${uploadData.path}`;
|
|
262
|
+
|
|
263
|
+
success('Document uploaded!');
|
|
264
|
+
console.log(` Path: ${uploadData.path}`);
|
|
265
|
+
console.log(` Size: ${formatFileSize(fileSize)}`);
|
|
266
|
+
console.log(` URL: ${publicUrl}`);
|
|
231
267
|
console.log('');
|
|
232
268
|
}
|
|
233
269
|
|
|
234
270
|
else if (command === 'delete') {
|
|
235
|
-
requireArgs(args.slice(1), 1, 'lux knowledge delete <
|
|
236
|
-
const
|
|
271
|
+
requireArgs(args.slice(1), 1, 'lux knowledge delete <file-path>');
|
|
272
|
+
const filePath = args[1];
|
|
237
273
|
|
|
238
|
-
info(`Deleting
|
|
239
|
-
await axios.delete(
|
|
240
|
-
`${
|
|
274
|
+
info(`Deleting: ${filePath}`);
|
|
275
|
+
const { data } = await axios.delete(
|
|
276
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge/${filePath}`,
|
|
241
277
|
{ headers: getAuthHeaders() }
|
|
242
278
|
);
|
|
243
279
|
|
|
280
|
+
if (!data.success) {
|
|
281
|
+
error(data.error || 'Failed to delete file');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
244
285
|
success('Document deleted!');
|
|
245
286
|
}
|
|
246
287
|
|
|
@@ -250,67 +291,82 @@ ${chalk.bold('Examples:')}
|
|
|
250
291
|
|
|
251
292
|
if (!subCommand || subCommand === 'list') {
|
|
252
293
|
info('Loading folders...');
|
|
253
|
-
const { data } = await axios.get(
|
|
254
|
-
|
|
255
|
-
|
|
294
|
+
const { data } = await axios.get(
|
|
295
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge`,
|
|
296
|
+
{ headers: getAuthHeaders() }
|
|
297
|
+
);
|
|
256
298
|
|
|
257
|
-
if (!data.
|
|
299
|
+
if (!data.success) {
|
|
300
|
+
error(data.error || 'Failed to list knowledge');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Extract folders from tree
|
|
305
|
+
const folders = [];
|
|
306
|
+
function collectFolders(nodes, parentPath = '') {
|
|
307
|
+
for (const node of nodes) {
|
|
308
|
+
if (node.type === 'folder') {
|
|
309
|
+
folders.push({
|
|
310
|
+
name: node.name,
|
|
311
|
+
path: node.path,
|
|
312
|
+
});
|
|
313
|
+
if (node.children && node.children.length > 0) {
|
|
314
|
+
collectFolders(node.children, node.path);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
collectFolders(data.tree || []);
|
|
320
|
+
|
|
321
|
+
if (folders.length === 0) {
|
|
258
322
|
console.log('\n(No folders found)\n');
|
|
259
323
|
} else {
|
|
260
|
-
console.log(`\nFound ${
|
|
261
|
-
formatTable(
|
|
262
|
-
id: f.id,
|
|
263
|
-
name: f.name,
|
|
264
|
-
path: f.path,
|
|
265
|
-
parent: f.parent_id || '(root)',
|
|
266
|
-
})));
|
|
324
|
+
console.log(`\nFound ${folders.length} folder(s):\n`);
|
|
325
|
+
formatTable(folders);
|
|
267
326
|
console.log('');
|
|
268
327
|
}
|
|
269
328
|
}
|
|
270
329
|
|
|
271
330
|
else if (subCommand === 'create') {
|
|
272
|
-
requireArgs(args.slice(2), 1, 'lux knowledge folders create <
|
|
273
|
-
const
|
|
274
|
-
|
|
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
|
-
}
|
|
331
|
+
requireArgs(args.slice(2), 1, 'lux knowledge folders create <folder-path>');
|
|
332
|
+
const folderPath = args[2];
|
|
333
|
+
|
|
334
|
+
info(`Creating folder: ${folderPath}`);
|
|
288
335
|
|
|
289
|
-
|
|
336
|
+
// In R2, folders are virtual - they're created by the folder endpoint
|
|
290
337
|
const { data } = await axios.post(
|
|
291
|
-
`${
|
|
292
|
-
{
|
|
338
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge/folder/${folderPath}`,
|
|
339
|
+
{},
|
|
293
340
|
{ headers: getAuthHeaders() }
|
|
294
341
|
);
|
|
295
342
|
|
|
343
|
+
if (!data.success) {
|
|
344
|
+
error(data.error || 'Failed to create folder');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
296
348
|
success('Folder created!');
|
|
297
|
-
console.log(`
|
|
298
|
-
console.log(` Name: ${data.folder.name}`);
|
|
299
|
-
console.log(` Path: ${data.folder.path}`);
|
|
349
|
+
console.log(` Path: ${data.path}`);
|
|
300
350
|
console.log('');
|
|
301
351
|
}
|
|
302
352
|
|
|
303
353
|
else if (subCommand === 'delete') {
|
|
304
|
-
requireArgs(args.slice(2), 1, 'lux knowledge folders delete <folder-
|
|
305
|
-
const
|
|
354
|
+
requireArgs(args.slice(2), 1, 'lux knowledge folders delete <folder-path>');
|
|
355
|
+
const folderPath = args[2];
|
|
306
356
|
|
|
307
|
-
info(`Deleting folder: ${
|
|
308
|
-
await axios.delete(
|
|
309
|
-
`${
|
|
357
|
+
info(`Deleting folder: ${folderPath}`);
|
|
358
|
+
const { data } = await axios.delete(
|
|
359
|
+
`${studioApiUrl}/api/projects/${projectId}/knowledge/${folderPath}`,
|
|
310
360
|
{ headers: getAuthHeaders() }
|
|
311
361
|
);
|
|
312
362
|
|
|
313
|
-
|
|
363
|
+
if (!data.success) {
|
|
364
|
+
error(data.error || 'Failed to delete folder');
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const deletedCount = data.deletedCount || 1;
|
|
369
|
+
success(`Folder deleted! (${deletedCount} item${deletedCount > 1 ? 's' : ''} removed)`);
|
|
314
370
|
}
|
|
315
371
|
|
|
316
372
|
else {
|
package/commands/login.js
CHANGED
|
@@ -177,7 +177,7 @@ async function manualLogin(providedKey) {
|
|
|
177
177
|
const apiUrl = getApiUrl();
|
|
178
178
|
|
|
179
179
|
// Validate the API key by making a test request
|
|
180
|
-
const response = await axios.get(`${apiUrl}/api/
|
|
180
|
+
const response = await axios.get(`${apiUrl}/api/flows`, {
|
|
181
181
|
headers: {
|
|
182
182
|
Authorization: `Bearer ${apiKey}`,
|
|
183
183
|
'X-Org-Id': extractOrgIdFromKey(apiKey),
|
package/commands/project.js
CHANGED
|
@@ -231,7 +231,8 @@ async function deployProject(projectId) {
|
|
|
231
231
|
createGitignore(projectDir);
|
|
232
232
|
|
|
233
233
|
// Always configure git user (required for commits)
|
|
234
|
-
|
|
234
|
+
// Use jason@uselux.ai so Vercel recognizes the commit author as a team member
|
|
235
|
+
execSync('git config user.email "jason@uselux.ai"', { cwd: projectDir, stdio: 'pipe' });
|
|
235
236
|
execSync('git config user.name "Lux Deploy"', { cwd: projectDir, stdio: 'pipe' });
|
|
236
237
|
|
|
237
238
|
gitSpinner.succeed('Git repository ready');
|
|
@@ -496,7 +497,7 @@ async function deployProject(projectId) {
|
|
|
496
497
|
// Deploy to cloud API using /publish endpoint
|
|
497
498
|
// The publish endpoint supports CLI auth and will create the flow if it doesn't exist
|
|
498
499
|
const { data: deployData } = await axios.post(
|
|
499
|
-
`${apiUrl}/api/
|
|
500
|
+
`${apiUrl}/api/flows/${flowId}/publish`,
|
|
500
501
|
{
|
|
501
502
|
// Wrap flow data in config object as expected by /publish
|
|
502
503
|
config: {
|
package/package.json
CHANGED