luxlabs 1.0.16 → 1.0.17

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.
@@ -233,6 +233,27 @@ async function wait(interfaceId, ms) {
233
233
  return result;
234
234
  }
235
235
 
236
+ /**
237
+ * Refresh the webview (soft or hard refresh)
238
+ */
239
+ async function refresh(interfaceId, hard = false) {
240
+ const id = generateId();
241
+ const result = await sendCommand({
242
+ id,
243
+ type: 'refresh',
244
+ appId: interfaceId,
245
+ payload: { hard },
246
+ });
247
+
248
+ if (result.success) {
249
+ console.log(chalk.green(hard ? 'Hard refresh completed!' : 'Refresh completed!'));
250
+ } else {
251
+ console.log(chalk.red('Refresh failed:'), result.error);
252
+ }
253
+
254
+ return result;
255
+ }
256
+
236
257
  /**
237
258
  * Start the interface preview in Lux Studio
238
259
  */
@@ -265,6 +286,7 @@ function showHelp() {
265
286
  console.log(' lux test url <interface-id> Get current URL');
266
287
  console.log(' lux test navigate <interface-id> <url> Navigate to URL');
267
288
  console.log(' lux test wait <interface-id> <ms> Wait for duration');
289
+ console.log(' lux test refresh <interface-id> [--hard] Refresh the page');
268
290
  console.log('');
269
291
  console.log(chalk.cyan('Examples:'));
270
292
  console.log(' lux test screenshot my-interface ./screenshot.png');
@@ -272,6 +294,7 @@ function showHelp() {
272
294
  console.log(' lux test type my-interface "#email" "user@example.com"');
273
295
  console.log(' lux test eval my-interface "document.title"');
274
296
  console.log(' lux test navigate my-interface "http://localhost:3000/login"');
297
+ console.log(' lux test refresh my-interface --hard');
275
298
  console.log('');
276
299
  console.log(chalk.dim('Note: <interface-id> is the interface ID or name from "lux servers"'));
277
300
  console.log('');
@@ -370,6 +393,18 @@ async function handleTest(args = []) {
370
393
  break;
371
394
  }
372
395
 
396
+ case 'refresh': {
397
+ const [interfaceId, ...flags] = rest;
398
+ if (!interfaceId) {
399
+ console.log(chalk.red('Error: interface-id is required'));
400
+ showHelp();
401
+ return;
402
+ }
403
+ const hard = flags.includes('--hard') || flags.includes('-h');
404
+ await refresh(interfaceId, hard);
405
+ break;
406
+ }
407
+
373
408
  default:
374
409
  console.log(chalk.red(`Unknown test command: ${subcommand}`));
375
410
  showHelp();
@@ -388,6 +423,7 @@ module.exports = {
388
423
  getUrl,
389
424
  navigate,
390
425
  wait,
426
+ refresh,
391
427
  startPreview,
392
428
  sendCommand,
393
429
  };
@@ -102,6 +102,11 @@ ${chalk.bold('Commands:')}
102
102
  delete <id> Delete a local workflow
103
103
  diff <id> Show local vs published differences
104
104
 
105
+ ${chalk.bold('Execution History:')}
106
+ executions <flow-id> [--limit N] List execution history for a flow
107
+ execution <flow-id> <exec-id> Get full execution details
108
+ node <flow-id> <exec-id> <node-id> Get specific node execution details
109
+
105
110
  ${chalk.bold('Sync Status:')}
106
111
  draft - Never published, local only
107
112
  synced - Matches published version
@@ -127,6 +132,12 @@ ${chalk.bold('Examples:')}
127
132
  lux workflows publish flow_123
128
133
  lux workflows diff flow_123
129
134
 
135
+ ${chalk.bold('Execution history:')}
136
+ lux flow executions my-flow-id
137
+ lux flow executions my-flow-id --limit 50
138
+ lux flow execution my-flow-id exec_abc123
139
+ lux flow node my-flow-id exec_abc123 node-1
140
+
130
141
  ${chalk.bold('Webhook workflow:')}
131
142
  lux flow webhook-url flow_123
132
143
  lux flow webhook-listen flow_123
@@ -851,6 +862,229 @@ ${chalk.bold('Examples:')}
851
862
  break;
852
863
  }
853
864
 
865
+ // ============ EXECUTION HISTORY COMMANDS ============
866
+
867
+ case 'executions': {
868
+ requireArgs(args.slice(1), 1, 'lux flow executions <flow-id> [--limit N]');
869
+ const flowId = args[1];
870
+
871
+ // Parse --limit flag
872
+ const limitIndex = args.indexOf('--limit');
873
+ const limit = limitIndex !== -1 && args[limitIndex + 1] ? parseInt(args[limitIndex + 1], 10) : 20;
874
+
875
+ info(`Fetching execution history for: ${flowId}`);
876
+
877
+ const { data } = await axios.get(
878
+ `${apiUrl}/api/flows/${flowId}/executions?limit=${limit}`,
879
+ { headers: getAuthHeaders() }
880
+ );
881
+
882
+ if (!data.executions || data.executions.length === 0) {
883
+ console.log(chalk.dim('\nNo executions found for this flow.\n'));
884
+ break;
885
+ }
886
+
887
+ console.log(`\nšŸ“Š Execution History for: ${flowId}\n`);
888
+
889
+ // Status colors
890
+ const statusColors = {
891
+ completed: chalk.green,
892
+ running: chalk.cyan,
893
+ failed: chalk.red,
894
+ cancelled: chalk.yellow,
895
+ };
896
+
897
+ const formatted = data.executions.map((exec) => {
898
+ const statusColor = statusColors[exec.status] || chalk.white;
899
+ const duration = exec.durationMs ? `${exec.durationMs}ms` : '-';
900
+ const startedAt = exec.startedAt ? new Date(exec.startedAt).toLocaleString() : '-';
901
+ const nodeCount = exec.nodeExecutions?.length || 0;
902
+
903
+ return {
904
+ id: exec.id.substring(0, 16) + '...',
905
+ status: statusColor(exec.status),
906
+ trigger: exec.triggerType || '-',
907
+ duration: duration,
908
+ nodes: nodeCount,
909
+ started: startedAt,
910
+ test: exec.isTest ? chalk.yellow('test') : '',
911
+ };
912
+ });
913
+
914
+ formatTable(formatted);
915
+
916
+ if (data.pagination?.hasMore) {
917
+ console.log(chalk.dim(`\nShowing ${data.executions.length} of ${data.pagination.total} executions.`));
918
+ console.log(chalk.dim(`Use --limit N to see more.\n`));
919
+ }
920
+
921
+ console.log(chalk.gray(`\nTo see full details: lux flow execution ${flowId} <execution-id>\n`));
922
+ break;
923
+ }
924
+
925
+ case 'execution': {
926
+ requireArgs(args.slice(1), 2, 'lux flow execution <flow-id> <execution-id>');
927
+ const flowId = args[1];
928
+ const executionId = args[2];
929
+
930
+ info(`Fetching execution: ${executionId}`);
931
+
932
+ const { data } = await axios.get(
933
+ `${apiUrl}/api/flows/${flowId}/executions/${executionId}`,
934
+ { headers: getAuthHeaders() }
935
+ );
936
+
937
+ if (!data.execution) {
938
+ error(`Execution not found: ${executionId}`);
939
+ break;
940
+ }
941
+
942
+ const exec = data.execution;
943
+ const statusColors = {
944
+ completed: chalk.green,
945
+ running: chalk.cyan,
946
+ failed: chalk.red,
947
+ cancelled: chalk.yellow,
948
+ };
949
+ const statusColor = statusColors[exec.status] || chalk.white;
950
+
951
+ console.log(`\nšŸ“‹ Execution Details\n`);
952
+ console.log(` ID: ${exec.id}`);
953
+ console.log(` Flow: ${exec.flowId}`);
954
+ console.log(` Status: ${statusColor(exec.status)}`);
955
+ console.log(` Trigger: ${exec.triggerType || '-'}`);
956
+ console.log(` Duration: ${exec.durationMs ? exec.durationMs + 'ms' : '-'}`);
957
+ console.log(` Started: ${exec.startedAt ? new Date(exec.startedAt).toLocaleString() : '-'}`);
958
+ console.log(` Completed: ${exec.completedAt ? new Date(exec.completedAt).toLocaleString() : '-'}`);
959
+ console.log(` Version: ${exec.flowVersion || '-'}`);
960
+ console.log(` Test Run: ${exec.isTest ? chalk.yellow('Yes') : 'No'}`);
961
+
962
+ if (exec.error) {
963
+ console.log(`\n${chalk.red('āŒ Error:')}`);
964
+ console.log(` ${exec.error}`);
965
+ }
966
+
967
+ // Show input data
968
+ if (exec.inputData && Object.keys(exec.inputData).length > 0) {
969
+ console.log(`\n${chalk.cyan('šŸ“„ Input Data:')}`);
970
+ console.log(formatJson(exec.inputData));
971
+ }
972
+
973
+ // Show output data
974
+ if (exec.outputData && Object.keys(exec.outputData).length > 0) {
975
+ console.log(`\n${chalk.cyan('šŸ“¤ Output Data:')}`);
976
+ console.log(formatJson(exec.outputData));
977
+ }
978
+
979
+ // Show node executions summary
980
+ if (exec.nodeExecutions && exec.nodeExecutions.length > 0) {
981
+ console.log(`\n${chalk.cyan('šŸ”— Node Executions:')} (${exec.nodeExecutions.length} nodes)\n`);
982
+
983
+ const nodeFormatted = exec.nodeExecutions.map((node) => {
984
+ const nodeStatus = node.status || 'unknown';
985
+ const nodeStatusColor = statusColors[nodeStatus] || chalk.white;
986
+ const nodeDuration = node.durationMs ? `${node.durationMs}ms` : '-';
987
+
988
+ return {
989
+ node_id: node.nodeId || node.id,
990
+ type: node.nodeType || '-',
991
+ status: nodeStatusColor(nodeStatus),
992
+ duration: nodeDuration,
993
+ error: node.error ? chalk.red('⚠') : '',
994
+ };
995
+ });
996
+
997
+ formatTable(nodeFormatted);
998
+
999
+ console.log(chalk.gray(`\nTo see node details: lux flow node ${flowId} ${executionId} <node-id>\n`));
1000
+ }
1001
+
1002
+ break;
1003
+ }
1004
+
1005
+ case 'node': {
1006
+ requireArgs(args.slice(1), 3, 'lux flow node <flow-id> <execution-id> <node-id>');
1007
+ const flowId = args[1];
1008
+ const executionId = args[2];
1009
+ const nodeId = args[3];
1010
+
1011
+ info(`Fetching node execution: ${nodeId}`);
1012
+
1013
+ const { data } = await axios.get(
1014
+ `${apiUrl}/api/flows/${flowId}/executions/${executionId}`,
1015
+ { headers: getAuthHeaders() }
1016
+ );
1017
+
1018
+ if (!data.execution) {
1019
+ error(`Execution not found: ${executionId}`);
1020
+ break;
1021
+ }
1022
+
1023
+ const exec = data.execution;
1024
+ const nodeExec = exec.nodeExecutions?.find(
1025
+ (n) => n.nodeId === nodeId || n.id === nodeId
1026
+ );
1027
+
1028
+ if (!nodeExec) {
1029
+ error(`Node not found in execution: ${nodeId}`);
1030
+ console.log(chalk.dim(`\nAvailable nodes:`));
1031
+ exec.nodeExecutions?.forEach((n) => {
1032
+ console.log(chalk.dim(` - ${n.nodeId || n.id} (${n.nodeType || 'unknown'})`));
1033
+ });
1034
+ break;
1035
+ }
1036
+
1037
+ const statusColors = {
1038
+ completed: chalk.green,
1039
+ running: chalk.cyan,
1040
+ failed: chalk.red,
1041
+ cancelled: chalk.yellow,
1042
+ skipped: chalk.gray,
1043
+ };
1044
+ const statusColor = statusColors[nodeExec.status] || chalk.white;
1045
+
1046
+ console.log(`\nšŸ”— Node Execution Details\n`);
1047
+ console.log(` Node ID: ${nodeExec.nodeId || nodeExec.id}`);
1048
+ console.log(` Type: ${nodeExec.nodeType || '-'}`);
1049
+ console.log(` Label: ${nodeExec.label || '-'}`);
1050
+ console.log(` Status: ${statusColor(nodeExec.status || 'unknown')}`);
1051
+ console.log(` Duration: ${nodeExec.durationMs ? nodeExec.durationMs + 'ms' : '-'}`);
1052
+ console.log(` Started: ${nodeExec.startedAt ? new Date(nodeExec.startedAt).toLocaleString() : '-'}`);
1053
+ console.log(` Completed: ${nodeExec.completedAt ? new Date(nodeExec.completedAt).toLocaleString() : '-'}`);
1054
+
1055
+ if (nodeExec.error) {
1056
+ console.log(`\n${chalk.red('āŒ Error:')}`);
1057
+ console.log(` ${nodeExec.error}`);
1058
+ }
1059
+
1060
+ // Show node config
1061
+ if (nodeExec.config && Object.keys(nodeExec.config).length > 0) {
1062
+ console.log(`\n${chalk.cyan('āš™ļø Node Config:')}`);
1063
+ console.log(formatJson(nodeExec.config));
1064
+ }
1065
+
1066
+ // Show input
1067
+ if (nodeExec.input !== undefined) {
1068
+ console.log(`\n${chalk.cyan('šŸ“„ Input:')}`);
1069
+ console.log(formatJson(nodeExec.input));
1070
+ }
1071
+
1072
+ // Show output
1073
+ if (nodeExec.output !== undefined) {
1074
+ console.log(`\n${chalk.cyan('šŸ“¤ Output:')}`);
1075
+ console.log(formatJson(nodeExec.output));
1076
+ }
1077
+
1078
+ // Show raw data if nothing else
1079
+ if (!nodeExec.input && !nodeExec.output && !nodeExec.config) {
1080
+ console.log(`\n${chalk.cyan('šŸ“‹ Raw Node Data:')}`);
1081
+ console.log(formatJson(nodeExec));
1082
+ }
1083
+
1084
+ console.log('');
1085
+ break;
1086
+ }
1087
+
854
1088
  default:
855
1089
  error(
856
1090
  `Unknown command: ${command}\n\nRun 'lux workflows' to see available commands`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luxlabs",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
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",