luxlabs 1.0.18 → 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 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 nextBinExists = fs.existsSync('./node_modules/.bin/next');
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 = './node_modules/.bin/next';
169
+ const nextBin = getNextBinPath();
161
170
  const nodePath = getNodePath();
162
171
  const nodeEnv = getNodeEnv();
163
172
 
@@ -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/workflows/${workflowId}/draft`,
59
+ `${apiUrl}/api/flows/${workflowId}/draft`,
60
60
  { headers: getAuthHeaders() }
61
61
  );
62
62
  return data;
@@ -68,13 +68,13 @@ async function getWorkflowDraft(workflowId) {
68
68
  async function saveWorkflowDraft(workflowId, config) {
69
69
  const apiUrl = getApiUrl();
70
70
  await axios.put(
71
- `${apiUrl}/api/workflows/${workflowId}/draft`,
71
+ `${apiUrl}/api/flows/${workflowId}/draft`,
72
72
  { config },
73
73
  { headers: getAuthHeaders() }
74
74
  );
75
75
  }
76
76
 
77
- async function handleWorkflows(args) {
77
+ async function handleFlows(args) {
78
78
  // Check authentication
79
79
  if (!isAuthenticated()) {
80
80
  console.log(
@@ -89,18 +89,18 @@ async function handleWorkflows(args) {
89
89
 
90
90
  if (!command) {
91
91
  console.log(`
92
- ${chalk.bold('Usage:')} lux workflows <command> [args]
92
+ ${chalk.bold('Usage:')} lux flows <command> [args]
93
93
 
94
94
  ${chalk.bold('Commands:')}
95
- list List all workflows (from local storage)
96
- get <id> Get workflow details with config
97
- status <id> Show sync status for a workflow
98
- create <name> [desc] [--publish] Create workflow (use --publish to publish immediately)
99
- init <name> [desc] [--publish] Initialize workflow (alias for create)
95
+ list List all flows (from local storage)
96
+ get <id> Get flow details with config
97
+ status <id> Show sync status for a flow
98
+ create <name> [desc] [--publish] Create flow (use --publish to publish immediately)
99
+ init <name> [desc] [--publish] Initialize flow (alias for create)
100
100
  save <id> <config-file> Save draft config from file (local)
101
- sync Sync published workflows from cloud
102
- publish <id> Publish workflow to cloud
103
- delete <id> Delete a local workflow
101
+ sync Sync published flows from cloud
102
+ publish <id> Publish flow to cloud
103
+ delete <id> Delete a local flow
104
104
  diff <id> Show local vs published differences
105
105
 
106
106
  ${chalk.bold('Execution History:')}
@@ -128,33 +128,33 @@ ${chalk.bold('Execution Commands:')}
128
128
  last <id> Show the last execution result
129
129
 
130
130
  ${chalk.bold('Examples:')}
131
- lux workflows list
132
- lux workflows get flow_123
133
- lux workflows status flow_123
134
- lux workflows create "My Workflow" "Description"
135
- lux workflows create "My Workflow" --publish # Create and publish immediately
136
- lux flow init "My Workflow" --publish
137
- lux workflows save flow_123 ./config.json
138
- lux workflows sync
139
- lux workflows publish flow_123
140
- lux workflows diff flow_123
131
+ lux flows list
132
+ lux flows get flow_123
133
+ lux flows status flow_123
134
+ lux flows create "My Flow" "Description"
135
+ lux flows create "My Flow" --publish # Create and publish immediately
136
+ lux flows init "My Flow" --publish
137
+ lux flows save flow_123 ./config.json
138
+ lux flows sync
139
+ lux flows publish flow_123
140
+ lux flows diff flow_123
141
141
 
142
142
  ${chalk.bold('Execution history:')}
143
- lux flow executions my-flow-id
144
- lux flow executions my-flow-id --limit 50
145
- lux flow execution my-flow-id exec_abc123
146
- lux flow node my-flow-id exec_abc123 node-1
143
+ lux flows executions my-flow-id
144
+ lux flows executions my-flow-id --limit 50
145
+ lux flows execution my-flow-id exec_abc123
146
+ lux flows node my-flow-id exec_abc123 node-1
147
147
 
148
- ${chalk.bold('Webhook workflow:')}
149
- lux flow webhook-url flow_123
150
- lux flow webhook-listen flow_123
151
- lux flow webhook-poll flow_123
152
- lux flow webhook-accept flow_123
148
+ ${chalk.bold('Webhook:')}
149
+ lux flows webhook-url flow_123
150
+ lux flows webhook-listen flow_123
151
+ lux flows webhook-poll flow_123
152
+ lux flows webhook-accept flow_123
153
153
 
154
154
  ${chalk.bold('Execution debugging:')}
155
- lux workflows executions my-flow # List recent executions
156
- lux workflows last my-flow # Show last execution result
157
- lux workflows test my-flow --data '{"name":"test"}' # Test with data
155
+ lux flows executions my-flow # List recent executions
156
+ lux flows last my-flow # Show last execution result
157
+ lux flows test my-flow --data '{"name":"test"}' # Test with data
158
158
  `);
159
159
  process.exit(0);
160
160
  }
@@ -165,14 +165,14 @@ ${chalk.bold('Examples:')}
165
165
  try {
166
166
  switch (command) {
167
167
  case 'list': {
168
- info('Loading workflows from local storage...');
168
+ info('Loading flows from local storage...');
169
169
  const localFlows = listLocalFlows();
170
170
 
171
171
  if (localFlows.length === 0) {
172
- console.log('\n(No local workflows found)');
173
- console.log(chalk.gray('Run "lux workflows sync" to sync from cloud, or "lux workflows create" to create one.\n'));
172
+ console.log('\n(No local flows found)');
173
+ console.log(chalk.gray('Run "lux flows sync" to sync from cloud, or "lux flows create" to create one.\n'));
174
174
  } else {
175
- console.log(`\nFound ${localFlows.length} workflow(s):\n`);
175
+ console.log(`\nFound ${localFlows.length} flow(s):\n`);
176
176
 
177
177
  // Color-code sync status
178
178
  const statusColors = {
@@ -203,12 +203,12 @@ ${chalk.bold('Examples:')}
203
203
  }
204
204
 
205
205
  case 'status': {
206
- requireArgs(args.slice(1), 1, 'lux workflows status <id>');
207
- const workflowId = args[1];
206
+ requireArgs(args.slice(1), 1, 'lux flows status <id>');
207
+ const flowId = args[1];
208
208
 
209
- const flow = loadLocalFlow(workflowId);
209
+ const flow = loadLocalFlow(flowId);
210
210
  if (!flow) {
211
- error(`Workflow not found locally: ${workflowId}`);
211
+ error(`Flow not found locally: ${flowId}`);
212
212
  break;
213
213
  }
214
214
 
@@ -231,8 +231,8 @@ ${chalk.bold('Examples:')}
231
231
  conflict: chalk.red,
232
232
  };
233
233
 
234
- console.log(`\n📝 Workflow: ${flow.name}`);
235
- console.log(` ID: ${workflowId}`);
234
+ console.log(`\n📝 Flow: ${flow.name}`);
235
+ console.log(` ID: ${flowId}`);
236
236
  console.log(` Sync Status: ${statusColors[syncStatus](syncStatus)}`);
237
237
  console.log(` Local Version: ${flow.localVersion || 1}`);
238
238
  console.log(` Published Version: ${flow.publishedVersion || '(never published)'}`);
@@ -243,19 +243,19 @@ ${chalk.bold('Examples:')}
243
243
  console.log(` Edges: ${flow.edges?.length || 0}`);
244
244
 
245
245
  if (syncStatus === 'conflict') {
246
- console.log(chalk.red('\n⚠️ This workflow has conflicts with the cloud version.'));
246
+ console.log(chalk.red('\n⚠️ This flow has conflicts with the cloud version.'));
247
247
  console.log(chalk.gray(' Use the Lux Studio app to resolve conflicts.\n'));
248
248
  } else if (syncStatus === 'dirty') {
249
- console.log(chalk.yellow('\n📤 This workflow has unpublished local changes.'));
250
- console.log(chalk.gray(' Run "lux workflows publish ' + workflowId + '" to publish.\n'));
249
+ console.log(chalk.yellow('\n📤 This flow has unpublished local changes.'));
250
+ console.log(chalk.gray(' Run "lux flows publish ' + flowId + '" to publish.\n'));
251
251
  }
252
252
  break;
253
253
  }
254
254
 
255
255
  case 'sync': {
256
- info('Syncing workflows from cloud...');
256
+ info('Syncing flows from cloud...');
257
257
 
258
- const { data } = await axios.get(`${apiUrl}/api/workflows?include_config=true`, {
258
+ const { data } = await axios.get(`${apiUrl}/api/flows?include_config=true`, {
259
259
  headers: getAuthHeaders(),
260
260
  });
261
261
 
@@ -357,18 +357,18 @@ ${chalk.bold('Examples:')}
357
357
  console.log(` New from cloud: ${newFromCloud}`);
358
358
  if (conflicts > 0) {
359
359
  console.log(chalk.red(` Conflicts: ${conflicts}`));
360
- console.log(chalk.gray('\n Use "lux workflows list" to see workflows with conflicts.'));
360
+ console.log(chalk.gray('\n Use "lux flows list" to see flows with conflicts.'));
361
361
  }
362
362
  break;
363
363
  }
364
364
 
365
365
  case 'delete': {
366
- requireArgs(args.slice(1), 1, 'lux workflows delete <id>');
367
- const workflowId = args[1];
366
+ requireArgs(args.slice(1), 1, 'lux flows delete <id>');
367
+ const flowId = args[1];
368
368
 
369
- const flow = loadLocalFlow(workflowId);
369
+ const flow = loadLocalFlow(flowId);
370
370
  if (!flow) {
371
- error(`Workflow not found locally: ${workflowId}`);
371
+ error(`Flow not found locally: ${flowId}`);
372
372
  break;
373
373
  }
374
374
 
@@ -377,23 +377,23 @@ ${chalk.bold('Examples:')}
377
377
  console.log(chalk.gray(' This only deletes the local copy. The published version remains in the cloud.\n'));
378
378
  }
379
379
 
380
- deleteLocalFlow(workflowId);
381
- success(`Deleted local workflow: ${flow.name}`);
380
+ deleteLocalFlow(flowId);
381
+ success(`Deleted local flow: ${flow.name}`);
382
382
  break;
383
383
  }
384
384
 
385
385
  case 'get': {
386
- requireArgs(args.slice(1), 1, 'lux workflows get <id>');
387
- const workflowId = args[1];
386
+ requireArgs(args.slice(1), 1, 'lux flows get <id>');
387
+ const flowId = args[1];
388
388
 
389
- info(`Loading workflow: ${workflowId}`);
389
+ info(`Loading flow: ${flowId}`);
390
390
  const { data } = await axios.get(
391
- `${apiUrl}/api/workflows/${workflowId}`,
391
+ `${apiUrl}/api/flows/${flowId}`,
392
392
  { headers: getAuthHeaders() }
393
393
  );
394
394
 
395
395
  if (!data.workflow) {
396
- error(`Workflow not found: ${workflowId}`);
396
+ error(`Flow not found: ${flowId}`);
397
397
  }
398
398
 
399
399
  const workflow = data.workflow;
@@ -421,7 +421,7 @@ ${chalk.bold('Examples:')}
421
421
  args.splice(publishFlagIndex, 1); // Remove flag from args
422
422
  }
423
423
 
424
- requireArgs(args.slice(1), 1, 'lux workflows create <name> [description] [--publish]');
424
+ requireArgs(args.slice(1), 1, 'lux flows create <name> [description] [--publish]');
425
425
  const name = args[1];
426
426
  const description = args[2] || '';
427
427
 
@@ -454,15 +454,15 @@ ${chalk.bold('Examples:')}
454
454
  updatedAt: now,
455
455
  };
456
456
 
457
- info(`Creating local workflow: ${name}`);
457
+ info(`Creating local flow: ${name}`);
458
458
 
459
459
  if (saveLocalFlow(flowId, newFlow)) {
460
- success(`Workflow created locally!`);
460
+ success(`Flow created locally!`);
461
461
  console.log(` ID: ${flowId}`);
462
462
 
463
463
  // If --publish flag was provided, publish immediately
464
464
  if (shouldPublish) {
465
- info(`Publishing workflow to cloud...`);
465
+ info(`Publishing flow to cloud...`);
466
466
 
467
467
  try {
468
468
  const publishConfig = {
@@ -472,9 +472,9 @@ ${chalk.bold('Examples:')}
472
472
  metadata: newFlow.metadata || {},
473
473
  };
474
474
 
475
- // Step 1: Create workflow in cloud database (this generates a cloud UUID)
475
+ // Step 1: Create flow in cloud database (this generates a cloud UUID)
476
476
  const createResponse = await axios.post(
477
- `${apiUrl}/api/workflows`,
477
+ `${apiUrl}/api/flows`,
478
478
  {
479
479
  name: newFlow.name,
480
480
  description: newFlow.description,
@@ -485,14 +485,14 @@ ${chalk.bold('Examples:')}
485
485
 
486
486
  const cloudId = createResponse.data.workflow?.id || createResponse.data.workflow?.workflow_id;
487
487
  if (!cloudId) {
488
- throw new Error('No workflow ID returned from cloud');
488
+ throw new Error('No flow ID returned from cloud');
489
489
  }
490
490
 
491
491
  info(`Created in cloud with ID: ${cloudId}`);
492
492
 
493
- // Step 2: Publish the workflow (copies draft config to published)
493
+ // Step 2: Publish the flow (copies draft config to published)
494
494
  const publishResponse = await axios.post(
495
- `${apiUrl}/api/workflows/${cloudId}/publish`,
495
+ `${apiUrl}/api/flows/${cloudId}/publish`,
496
496
  {},
497
497
  { headers: getAuthHeaders() }
498
498
  );
@@ -515,44 +515,46 @@ ${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
 
521
- success('Workflow published!');
523
+ success('Flow published!');
522
524
  console.log(` Cloud ID: ${cloudId}`);
523
525
  console.log(` Status: ${chalk.green('published')}`);
524
526
  console.log(` Version: ${newVersion}`);
525
527
  } catch (publishError) {
526
528
  error(`Failed to publish: ${publishError.response?.data?.error || publishError.message}`);
527
529
  console.log(` Status: ${chalk.gray('draft')}`);
528
- console.log(chalk.gray(`\nRun "lux workflows publish ${flowId}" to try again.`));
530
+ console.log(chalk.gray(`\nRun "lux flows publish ${flowId}" to try again.`));
529
531
  }
530
532
  } else {
531
533
  console.log(` Status: ${chalk.gray('draft')}`);
532
- console.log(chalk.gray(`\n This workflow is local only. Run "lux workflows publish ${flowId}" to publish to cloud.`));
534
+ console.log(chalk.gray(`\n This flow is local only. Run "lux flows publish ${flowId}" to publish to cloud.`));
533
535
  }
534
536
  } else {
535
- error('Failed to create workflow. Make sure you are authenticated.');
537
+ error('Failed to create flow. Make sure you are authenticated.');
536
538
  }
537
539
  break;
538
540
  }
539
541
 
540
542
  case 'save': {
541
- requireArgs(args.slice(1), 2, 'lux workflows save <id> <config-file>');
542
- const workflowId = args[1];
543
+ requireArgs(args.slice(1), 2, 'lux flows save <id> <config-file>');
544
+ const flowId = args[1];
543
545
  const configFile = args[2];
544
546
 
545
547
  // Load existing local flow
546
- const existingFlow = loadLocalFlow(workflowId);
548
+ const existingFlow = loadLocalFlow(flowId);
547
549
  if (!existingFlow) {
548
- error(`Workflow not found locally: ${workflowId}`);
549
- console.log(chalk.gray('Run "lux workflows sync" to sync from cloud first.'));
550
+ error(`Flow not found locally: ${flowId}`);
551
+ console.log(chalk.gray('Run "lux flows sync" to sync from cloud first.'));
550
552
  break;
551
553
  }
552
554
 
553
555
  info(`Reading config from: ${configFile}`);
554
556
  const configJson = readFile(configFile);
555
- const config = parseJson(configJson, 'workflow config');
557
+ const config = parseJson(configJson, 'flow config');
556
558
 
557
559
  // Update local flow with new config
558
560
  const updatedFlow = {
@@ -563,9 +565,9 @@ ${chalk.bold('Examples:')}
563
565
  updatedAt: new Date().toISOString(),
564
566
  };
565
567
 
566
- info(`Saving to local storage: ${workflowId}`);
567
- if (saveLocalFlow(workflowId, updatedFlow)) {
568
- success('Workflow saved locally!');
568
+ info(`Saving to local storage: ${flowId}`);
569
+ if (saveLocalFlow(flowId, updatedFlow)) {
570
+ success('Flow saved locally!');
569
571
  console.log(` Local Version: ${updatedFlow.localVersion}`);
570
572
 
571
573
  // Show sync status
@@ -576,22 +578,22 @@ ${chalk.bold('Examples:')}
576
578
  console.log(` Status: ${chalk.yellow('dirty')} (has unpublished changes)`);
577
579
  }
578
580
 
579
- console.log(chalk.gray(`\nRun "lux workflows publish ${workflowId}" to publish to cloud.`));
581
+ console.log(chalk.gray(`\nRun "lux flows publish ${flowId}" to publish to cloud.`));
580
582
  } else {
581
- error('Failed to save workflow locally.');
583
+ error('Failed to save flow locally.');
582
584
  }
583
585
  break;
584
586
  }
585
587
 
586
588
  case 'publish': {
587
- requireArgs(args.slice(1), 1, 'lux workflows publish <id>');
588
- const workflowId = args[1];
589
+ requireArgs(args.slice(1), 1, 'lux flows publish <id>');
590
+ const flowId = args[1];
589
591
 
590
592
  // Load local flow
591
- const localFlow = loadLocalFlow(workflowId);
593
+ const localFlow = loadLocalFlow(flowId);
592
594
  if (!localFlow) {
593
- error(`Workflow not found locally: ${workflowId}`);
594
- console.log(chalk.gray('Run "lux workflows sync" to sync from cloud first.'));
595
+ error(`Flow not found locally: ${flowId}`);
596
+ console.log(chalk.gray('Run "lux flows sync" to sync from cloud first.'));
595
597
  break;
596
598
  }
597
599
 
@@ -599,12 +601,12 @@ ${chalk.bold('Examples:')}
599
601
  if (localFlow.cloudVersion && localFlow.publishedVersion &&
600
602
  localFlow.cloudVersion > localFlow.publishedVersion &&
601
603
  localFlow.localVersion > localFlow.publishedVersion) {
602
- error('Cannot publish: workflow has conflicts with cloud version.');
604
+ error('Cannot publish: flow has conflicts with cloud version.');
603
605
  console.log(chalk.gray('Resolve conflicts in Lux Studio first.'));
604
606
  break;
605
607
  }
606
608
 
607
- info(`Publishing workflow: ${workflowId}`);
609
+ info(`Publishing flow: ${flowId}`);
608
610
 
609
611
  // Publish directly to cloud with config in request body
610
612
  const publishConfig = {
@@ -615,7 +617,7 @@ ${chalk.bold('Examples:')}
615
617
  };
616
618
 
617
619
  const { data } = await axios.post(
618
- `${apiUrl}/api/workflows/${workflowId}/publish`,
620
+ `${apiUrl}/api/flows/${flowId}/publish`,
619
621
  {
620
622
  name: localFlow.name,
621
623
  description: localFlow.description,
@@ -642,10 +644,12 @@ ${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
- saveLocalFlow(workflowId, updatedFlow);
650
+ saveLocalFlow(flowId, updatedFlow);
647
651
 
648
- success('Workflow published!');
652
+ success('Flow published!');
649
653
  console.log(` Name: ${localFlow.name}`);
650
654
  console.log(` Version: ${newVersion}`);
651
655
  console.log(` Status: ${chalk.green('synced')}`);
@@ -653,17 +657,17 @@ ${chalk.bold('Examples:')}
653
657
  }
654
658
 
655
659
  case 'diff': {
656
- requireArgs(args.slice(1), 1, 'lux workflows diff <id>');
657
- const workflowId = args[1];
660
+ requireArgs(args.slice(1), 1, 'lux flows diff <id>');
661
+ const flowId = args[1];
658
662
 
659
663
  // Load local flow
660
- const localFlow = loadLocalFlow(workflowId);
664
+ const localFlow = loadLocalFlow(flowId);
661
665
  if (!localFlow) {
662
- error(`Workflow not found locally: ${workflowId}`);
666
+ error(`Flow not found locally: ${flowId}`);
663
667
  break;
664
668
  }
665
669
 
666
- info(`Comparing local vs published for: ${workflowId}`);
670
+ info(`Comparing local vs published for: ${flowId}`);
667
671
 
668
672
  const localConfig = {
669
673
  nodes: localFlow.nodes || [],
@@ -672,12 +676,12 @@ ${chalk.bold('Examples:')}
672
676
 
673
677
  // Check if never published
674
678
  if (!localFlow.publishedVersion) {
675
- console.log('\n📝 NEW WORKFLOW (not yet published)\n');
679
+ console.log('\n📝 NEW FLOW (not yet published)\n');
676
680
  console.log(` Name: ${localFlow.name}`);
677
681
  console.log(` Local Version: ${localFlow.localVersion || 1}`);
678
682
  console.log(` Nodes: ${localConfig.nodes.length}`);
679
683
  console.log(` Edges: ${localConfig.edges.length}`);
680
- console.log(chalk.gray(`\nRun "lux workflows publish ${workflowId}" to publish.\n`));
684
+ console.log(chalk.gray(`\nRun "lux flows publish ${flowId}" to publish.\n`));
681
685
  break;
682
686
  }
683
687
 
@@ -709,17 +713,17 @@ ${chalk.bold('Examples:')}
709
713
  console.log(` Published Version: ${localFlow.publishedVersion}`);
710
714
  console.log(` Nodes: ${localConfig.nodes.length}`);
711
715
  console.log(` Edges: ${localConfig.edges.length}`);
712
- console.log(chalk.gray(`\nRun "lux workflows publish ${workflowId}" to publish changes.\n`));
716
+ console.log(chalk.gray(`\nRun "lux flows publish ${flowId}" to publish changes.\n`));
713
717
  }
714
718
  break;
715
719
  }
716
720
 
717
721
  case 'webhook-url': {
718
- requireArgs(args.slice(1), 1, 'lux flow webhook-url <flow-id>');
719
- const workflowId = args[1];
722
+ requireArgs(args.slice(1), 1, 'lux flows webhook-url <flow-id>');
723
+ const flowId = args[1];
720
724
 
721
- info(`Getting webhook URL for: ${workflowId}`);
722
- const tokenData = await getWebhookToken(workflowId);
725
+ info(`Getting webhook URL for: ${flowId}`);
726
+ const tokenData = await getWebhookToken(flowId);
723
727
 
724
728
  console.log(`\n📍 Webhook URL:\n${tokenData.webhookUrl}\n`);
725
729
  console.log(`📊 Status:`);
@@ -727,17 +731,17 @@ ${chalk.bold('Examples:')}
727
731
  console.log(` Webhook Trigger Enabled: ${tokenData.webhookTrigger ? '✅ Yes' : '❌ No'}\n`);
728
732
 
729
733
  if (!tokenData.formatConfirmed) {
730
- console.log(chalk.yellow('ℹ️ Run "lux flow webhook-listen" to capture a sample webhook'));
734
+ console.log(chalk.yellow('ℹ️ Run "lux flows webhook-listen" to capture a sample webhook'));
731
735
  }
732
736
  break;
733
737
  }
734
738
 
735
739
  case 'webhook-listen': {
736
- requireArgs(args.slice(1), 1, 'lux flow webhook-listen <flow-id>');
737
- const workflowId = args[1];
740
+ requireArgs(args.slice(1), 1, 'lux flows webhook-listen <flow-id>');
741
+ const flowId = args[1];
738
742
 
739
- info(`Starting webhook listening session for: ${workflowId}`);
740
- const tokenData = await getWebhookToken(workflowId);
743
+ info(`Starting webhook listening session for: ${flowId}`);
744
+ const tokenData = await getWebhookToken(flowId);
741
745
 
742
746
  // Start listening session
743
747
  await axios.post(`${WEBHOOK_WORKER_URL}/listening/start/${tokenData.token}`);
@@ -746,15 +750,15 @@ ${chalk.bold('Examples:')}
746
750
  console.log(`\n📡 Now listening for webhooks at:\n${tokenData.webhookUrl}\n`);
747
751
  console.log('Session expires in 5 minutes.');
748
752
  console.log(`\nSend a webhook request to the URL above, then run:`);
749
- console.log(chalk.white(` lux flow webhook-poll ${workflowId}\n`));
753
+ console.log(chalk.white(` lux flows webhook-poll ${flowId}\n`));
750
754
  break;
751
755
  }
752
756
 
753
757
  case 'webhook-poll': {
754
- requireArgs(args.slice(1), 1, 'lux flow webhook-poll <flow-id>');
755
- const workflowId = args[1];
758
+ requireArgs(args.slice(1), 1, 'lux flows webhook-poll <flow-id>');
759
+ const flowId = args[1];
756
760
 
757
- const tokenData = await getWebhookToken(workflowId);
761
+ const tokenData = await getWebhookToken(flowId);
758
762
  const pollData = await getCapturedPayload(tokenData.token);
759
763
 
760
764
  // Return raw JSON response
@@ -763,20 +767,20 @@ ${chalk.bold('Examples:')}
763
767
  }
764
768
 
765
769
  case 'webhook-accept': {
766
- requireArgs(args.slice(1), 1, 'lux flow webhook-accept <flow-id>');
767
- const workflowId = args[1];
770
+ requireArgs(args.slice(1), 1, 'lux flows webhook-accept <flow-id>');
771
+ const flowId = args[1];
768
772
 
769
- info(`Accepting webhook format for: ${workflowId}`);
773
+ info(`Accepting webhook format for: ${flowId}`);
770
774
 
771
775
  // Step 1: Get webhook token
772
- const tokenData = await getWebhookToken(workflowId);
776
+ const tokenData = await getWebhookToken(flowId);
773
777
 
774
778
  // Step 2: Poll for captured payload
775
779
  const pollData = await getCapturedPayload(tokenData.token);
776
780
 
777
781
  // Check if payload was captured
778
782
  if (pollData.waiting || !pollData.payload) {
779
- error('No webhook captured. Run "lux flow webhook-listen" first and send a test webhook.');
783
+ error('No webhook captured. Run "lux flows webhook-listen" first and send a test webhook.');
780
784
  break;
781
785
  }
782
786
 
@@ -851,11 +855,11 @@ ${chalk.bold('Examples:')}
851
855
  }
852
856
 
853
857
  case 'webhook-decline': {
854
- requireArgs(args.slice(1), 1, 'lux flow webhook-decline <flow-id>');
855
- const workflowId = args[1];
858
+ requireArgs(args.slice(1), 1, 'lux flows webhook-decline <flow-id>');
859
+ const flowId = args[1];
856
860
 
857
- info(`Declining webhook format for: ${workflowId}`);
858
- const tokenData = await getWebhookToken(workflowId);
861
+ info(`Declining webhook format for: ${flowId}`);
862
+ const tokenData = await getWebhookToken(flowId);
859
863
 
860
864
  // Clear webhook schema from database
861
865
  info('Clearing webhook schema...');
@@ -870,14 +874,14 @@ ${chalk.bold('Examples:')}
870
874
  success('Webhook format declined!');
871
875
  console.log('\n✅ Schema cleared from database');
872
876
  console.log('✅ Listening session has been reset');
873
- console.log(`\nRun "lux flow webhook-listen ${workflowId}" to try again.\n`);
877
+ console.log(`\nRun "lux flows webhook-listen ${flowId}" to try again.\n`);
874
878
  break;
875
879
  }
876
880
 
877
881
  // ============ EXECUTION HISTORY COMMANDS ============
878
882
 
879
883
  case 'executions': {
880
- requireArgs(args.slice(1), 1, 'lux workflows executions <flow-id> [--limit N]');
884
+ requireArgs(args.slice(1), 1, 'lux flows executions <flow-id> [--limit N]');
881
885
  const flowId = args[1];
882
886
 
883
887
  // Parse --limit flag
@@ -912,7 +916,7 @@ ${chalk.bold('Examples:')}
912
916
 
913
917
  if (executions.length === 0) {
914
918
  console.log('\n(No executions found for this flow)');
915
- console.log(chalk.gray('Run the flow from the interface or use "lux workflows test" to create an execution.\n'));
919
+ console.log(chalk.gray('Run the flow from the interface or use "lux flows test" to create an execution.\n'));
916
920
  break;
917
921
  }
918
922
 
@@ -954,12 +958,12 @@ ${chalk.bold('Examples:')}
954
958
  console.log(chalk.dim(`Use --limit N to see more.\n`));
955
959
  }
956
960
 
957
- console.log(chalk.gray(`\nTo see full details: lux workflows execution ${flowId} <execution-id>\n`));
961
+ console.log(chalk.gray(`\nTo see full details: lux flows execution ${flowId} <execution-id>\n`));
958
962
  break;
959
963
  }
960
964
 
961
965
  case 'execution': {
962
- requireArgs(args.slice(1), 2, 'lux workflows execution <flow-id> <execution-id>');
966
+ requireArgs(args.slice(1), 2, 'lux flows execution <flow-id> <execution-id>');
963
967
  const flowId = args[1];
964
968
  const executionId = args[2];
965
969
  const jsonOutput = args.includes('--json');
@@ -1056,14 +1060,14 @@ ${chalk.bold('Examples:')}
1056
1060
 
1057
1061
  formatTable(nodeFormatted);
1058
1062
 
1059
- console.log(chalk.gray(`\nTo see node details: lux workflows node ${flowId} ${executionId} <node-id>\n`));
1063
+ console.log(chalk.gray(`\nTo see node details: lux flows node ${flowId} ${executionId} <node-id>\n`));
1060
1064
  }
1061
1065
 
1062
1066
  break;
1063
1067
  }
1064
1068
 
1065
1069
  case 'node': {
1066
- requireArgs(args.slice(1), 3, 'lux workflows node <flow-id> <execution-id> <node-id>');
1070
+ requireArgs(args.slice(1), 3, 'lux flows node <flow-id> <execution-id> <node-id>');
1067
1071
  const flowId = args[1];
1068
1072
  const executionId = args[2];
1069
1073
  const nodeId = args[3];
@@ -1163,7 +1167,7 @@ ${chalk.bold('Examples:')}
1163
1167
  }
1164
1168
 
1165
1169
  case 'last': {
1166
- requireArgs(args.slice(1), 1, 'lux workflows last <flow-id>');
1170
+ requireArgs(args.slice(1), 1, 'lux flows last <flow-id>');
1167
1171
  const flowId = args[1];
1168
1172
  const jsonOutput = args.includes('--json');
1169
1173
 
@@ -1188,7 +1192,7 @@ ${chalk.bold('Examples:')}
1188
1192
  const executions = listData.executions || [];
1189
1193
  if (executions.length === 0) {
1190
1194
  console.log('\n(No executions found for this flow)');
1191
- console.log(chalk.gray('Run the flow from the interface or use "lux workflows test" to create an execution.\n'));
1195
+ console.log(chalk.gray('Run the flow from the interface or use "lux flows test" to create an execution.\n'));
1192
1196
  break;
1193
1197
  }
1194
1198
 
@@ -1259,7 +1263,7 @@ ${chalk.bold('Examples:')}
1259
1263
  }
1260
1264
 
1261
1265
  case 'test': {
1262
- requireArgs(args.slice(1), 1, 'lux workflows test <flow-id> [--data <json>] [--data-file <path>]');
1266
+ requireArgs(args.slice(1), 1, 'lux flows test <flow-id> [--data <json>] [--data-file <path>]');
1263
1267
  const flowId = args[1];
1264
1268
  const jsonOutput = args.includes('--json');
1265
1269
 
@@ -1285,7 +1289,7 @@ ${chalk.bold('Examples:')}
1285
1289
  const flowData = loadLocalFlow(flowId);
1286
1290
  if (!flowData) {
1287
1291
  error(`Flow not found locally: ${flowId}`);
1288
- console.log(chalk.gray('Run "lux workflows sync" to sync from cloud first.'));
1292
+ console.log(chalk.gray('Run "lux flows sync" to sync from cloud first.'));
1289
1293
  break;
1290
1294
  }
1291
1295
 
@@ -1363,7 +1367,7 @@ ${chalk.bold('Examples:')}
1363
1367
 
1364
1368
  default:
1365
1369
  error(
1366
- `Unknown command: ${command}\n\nRun 'lux workflows' to see available commands`
1370
+ `Unknown command: ${command}\n\nRun 'lux flows' to see available commands`
1367
1371
  );
1368
1372
  }
1369
1373
  } catch (err) {
@@ -1373,4 +1377,4 @@ ${chalk.bold('Examples:')}
1373
1377
  }
1374
1378
  }
1375
1379
 
1376
- module.exports = { handleWorkflows };
1380
+ module.exports = { handleFlows };
@@ -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 <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
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 <name> [parent-id] Create a new folder
47
- folders delete <folder-id> Delete a 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 doc_123abc
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 folder_xyz
54
- lux knowledge folders create "Product Docs"
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 [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
- ]);
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 folders = foldersRes.data.folders || [];
76
- const documents = docsRes.data.documents || [];
86
+ const tree = data.tree || [];
87
+ const totalFiles = data.totalFiles || 0;
77
88
 
78
- if (folders.length === 0 && documents.length === 0) {
89
+ if (tree.length === 0) {
79
90
  console.log('\n(No knowledge items found)\n');
80
91
  return;
81
92
  }
82
93
 
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
- })));
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
- 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
- }
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 <document-id>');
107
- const docId = args[1];
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: ${docId}`);
120
+ info(`Loading document: ${filePath}`);
110
121
  const { data } = await axios.get(
111
- `${apiUrl}/api/knowledge/documents/${docId}`,
122
+ `${studioApiUrl}/api/projects/${projectId}/knowledge/${filePath}`,
112
123
  { headers: getAuthHeaders() }
113
124
  );
114
125
 
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}`);
126
+ if (!data.success) {
127
+ error(data.error || 'Failed to get file');
128
+ return;
125
129
  }
126
130
 
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'}`);
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
- if (doc.content) {
148
+ // Show content preview for text files
149
+ if (data.encoding === 'utf-8' && data.content) {
133
150
  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)`));
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 <document-id>');
144
- const docId = args[1];
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: ${docId}`);
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
- `${apiUrl}/api/knowledge/documents/${docId}/presigned-url`,
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(data.presignedUrl);
154
- console.log(`\nDocument: ${data.document.name} (${formatFileSize(data.document.file_size)})`);
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-path> [folder-id]');
160
- const filePath = args[1];
161
- const folderId = args[2] || null;
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(filePath)) {
165
- error(`File not found: ${filePath}`);
195
+ if (!fs.existsSync(localFilePath)) {
196
+ error(`File not found: ${localFilePath}`);
166
197
  return;
167
198
  }
168
199
 
169
- const fileName = path.basename(filePath);
170
- const fileBuffer = fs.readFileSync(filePath);
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 fileType = mimeTypes[ext] || 'application/octet-stream';
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
- `${apiUrl}/api/knowledge/upload`,
197
- { fileName, fileType },
227
+ `${studioApiUrl}/api/projects/${projectId}/knowledge/upload-url`,
228
+ { path: fileName, contentType, folder },
198
229
  { headers: getAuthHeaders() }
199
230
  );
200
231
 
201
- // Step 2: Upload to R2
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': fileType,
241
+ 'Content-Type': contentType,
206
242
  },
207
243
  maxBodyLength: Infinity,
208
244
  maxContentLength: Infinity,
209
245
  });
210
246
 
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
- },
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
- 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}`);
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 <document-id>');
236
- const docId = args[1];
271
+ requireArgs(args.slice(1), 1, 'lux knowledge delete <file-path>');
272
+ const filePath = args[1];
237
273
 
238
- info(`Deleting document: ${docId}`);
239
- await axios.delete(
240
- `${apiUrl}/api/knowledge/documents/${docId}`,
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(`${apiUrl}/api/knowledge/folders`, {
254
- headers: getAuthHeaders(),
255
- });
294
+ const { data } = await axios.get(
295
+ `${studioApiUrl}/api/projects/${projectId}/knowledge`,
296
+ { headers: getAuthHeaders() }
297
+ );
256
298
 
257
- if (!data.folders || data.folders.length === 0) {
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 ${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
- })));
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 <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
- }
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
- info(`Creating folder: ${name}`);
336
+ // In R2, folders are virtual - they're created by the folder endpoint
290
337
  const { data } = await axios.post(
291
- `${apiUrl}/api/knowledge/folders`,
292
- { name, parent_id: parentId, path: folderPath },
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(` ID: ${data.folder.id}`);
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-id>');
305
- const folderId = args[2];
354
+ requireArgs(args.slice(2), 1, 'lux knowledge folders delete <folder-path>');
355
+ const folderPath = args[2];
306
356
 
307
- info(`Deleting folder: ${folderId}`);
308
- await axios.delete(
309
- `${apiUrl}/api/knowledge/folders?id=${folderId}`,
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
- success('Folder deleted!');
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/workflows`, {
180
+ const response = await axios.get(`${apiUrl}/api/flows`, {
181
181
  headers: {
182
182
  Authorization: `Bearer ${apiKey}`,
183
183
  'X-Org-Id': extractOrgIdFromKey(apiKey),
@@ -231,7 +231,8 @@ async function deployProject(projectId) {
231
231
  createGitignore(projectDir);
232
232
 
233
233
  // Always configure git user (required for commits)
234
- execSync('git config user.email "deploy@uselux.ai"', { cwd: projectDir, stdio: 'pipe' });
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/workflows/${flowId}/publish`,
500
+ `${apiUrl}/api/flows/${flowId}/publish`,
500
501
  {
501
502
  // Wrap flow data in config object as expected by /publish
502
503
  config: {
package/lux.js CHANGED
@@ -11,7 +11,7 @@ const { dev } = require('./commands/dev');
11
11
  const { handleInterface } = require('./commands/interface');
12
12
  const { handleData } = require('./commands/data');
13
13
  const { handleStorage } = require('./commands/storage');
14
- const { handleWorkflows } = require('./commands/workflows');
14
+ const { handleFlows } = require('./commands/flows');
15
15
  const { handleSecrets } = require('./commands/secrets');
16
16
  const { handleAgents } = require('./commands/agents');
17
17
  const { handleKnowledge } = require('./commands/knowledge');
@@ -96,14 +96,14 @@ program
96
96
  handleStorage(subcommand ? [subcommand, ...(args || [])] : []);
97
97
  });
98
98
 
99
- // Workflows commands (with short aliases 'flow' and 'f')
99
+ // Flows commands (with short alias 'f')
100
100
  program
101
- .command('workflows [subcommand] [args...]')
102
- .aliases(['flow', 'f'])
103
- .description('Workflow management (list, get, create/init, save, publish, diff)')
101
+ .command('flows [subcommand] [args...]')
102
+ .alias('f')
103
+ .description('Flow management (list, get, create/init, save, publish, diff)')
104
104
  .allowUnknownOption()
105
105
  .action((subcommand, args) => {
106
- handleWorkflows(subcommand ? [subcommand, ...(args || [])] : []);
106
+ handleFlows(subcommand ? [subcommand, ...(args || [])] : []);
107
107
  });
108
108
 
109
109
  // Secrets commands
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luxlabs",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "CLI tool for Lux - Upload and deploy interfaces from your terminal",
5
5
  "author": "Jason Henkel <jason@uselux.ai>",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -24,7 +24,7 @@
24
24
  "deploy",
25
25
  "interfaces",
26
26
  "ai",
27
- "workflows",
27
+ "flows",
28
28
  "agents"
29
29
  ],
30
30
  "repository": {