@positronic/cli 0.0.43 → 0.0.45

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/dist/src/cli.js CHANGED
@@ -377,13 +377,25 @@ export function buildCli(options) {
377
377
  var element = brainCommand.history(argv);
378
378
  render(element);
379
379
  });
380
- // --- Show Run Command ---
381
- cli = cli.command('show <run-id>', 'Show detailed information about a brain run, including any errors\n', function(yargsShow) {
382
- return yargsShow.positional('run-id', {
383
- describe: 'ID of the brain run to show',
380
+ // --- Show Brain/Run Command ---
381
+ cli = cli.command('show [brain]', 'Show information about a brain or a specific run\n', function(yargsShow) {
382
+ return yargsShow.positional('brain', {
383
+ describe: 'Brain identifier to show info for',
384
+ type: 'string'
385
+ }).option('run-id', {
386
+ describe: 'ID of a specific brain run to show',
384
387
  type: 'string',
385
- demandOption: true
386
- }).example('$0 show abc123', 'Show details for brain run abc123');
388
+ alias: 'id'
389
+ }).option('steps', {
390
+ describe: 'Show the step structure of the brain',
391
+ type: 'boolean',
392
+ default: false
393
+ }).check(function(argv) {
394
+ if (!argv.brain && !argv.runId) {
395
+ throw new Error('You must provide either a brain identifier or a --run-id.');
396
+ }
397
+ return true;
398
+ }).example('$0 show my-brain', 'Show info about my-brain').example('$0 show my-brain --steps', 'Show my-brain with step structure').example('$0 show --run-id abc123', 'Show details for a specific run');
387
399
  }, function(argv) {
388
400
  var element = brainCommand.show(argv);
389
401
  render(element);
@@ -485,12 +497,24 @@ export function buildCli(options) {
485
497
  }, function(argv) {
486
498
  var element = brainCommand.history(argv);
487
499
  render(element);
488
- }).command('show <run-id>', 'Show detailed information about a brain run, including any errors\n', function(yargsShow) {
489
- return yargsShow.positional('run-id', {
490
- describe: 'ID of the brain run to show',
500
+ }).command('show [brain]', 'Show information about a brain or a specific run\n', function(yargsShow) {
501
+ return yargsShow.positional('brain', {
502
+ describe: 'Brain identifier to show info for',
503
+ type: 'string'
504
+ }).option('run-id', {
505
+ describe: 'ID of a specific brain run to show',
491
506
  type: 'string',
492
- demandOption: true
493
- }).example('$0 brain show abc123', 'Show details for brain run abc123');
507
+ alias: 'id'
508
+ }).option('steps', {
509
+ describe: 'Show the step structure of the brain',
510
+ type: 'boolean',
511
+ default: false
512
+ }).check(function(argv) {
513
+ if (!argv.brain && !argv.runId) {
514
+ throw new Error('You must provide either a brain identifier or a --run-id.');
515
+ }
516
+ return true;
517
+ }).example('$0 brain show my-brain', 'Show info about my-brain').example('$0 brain show my-brain --steps', 'Show my-brain with step structure').example('$0 brain show --run-id abc123', 'Show details for a specific run');
494
518
  }, function(argv) {
495
519
  var element = brainCommand.show(argv);
496
520
  render(element);
@@ -22,6 +22,7 @@ import { Watch } from '../components/watch.js';
22
22
  import { BrainList } from '../components/brain-list.js';
23
23
  import { BrainHistory } from '../components/brain-history.js';
24
24
  import { RunShow } from '../components/run-show.js';
25
+ import { BrainShow } from '../components/brain-show.js';
25
26
  import { BrainRerun } from '../components/brain-rerun.js';
26
27
  import { BrainKill } from '../components/brain-kill.js';
27
28
  import { BrainRun } from '../components/brain-run.js';
@@ -58,9 +59,27 @@ export var BrainCommand = /*#__PURE__*/ function() {
58
59
  {
59
60
  key: "show",
60
61
  value: function show(param) {
61
- var runId = param.runId;
62
- return React.createElement(RunShow, {
63
- runId: runId
62
+ var brain = param.brain, runId = param.runId, steps = param.steps;
63
+ // If run ID is provided, show run info (existing behavior)
64
+ if (runId) {
65
+ return React.createElement(RunShow, {
66
+ runId: runId
67
+ });
68
+ }
69
+ // If brain identifier is provided, show brain info
70
+ if (brain) {
71
+ return React.createElement(BrainShow, {
72
+ identifier: brain,
73
+ showSteps: steps || false
74
+ });
75
+ }
76
+ // Neither provided - show error
77
+ return React.createElement(ErrorComponent, {
78
+ error: {
79
+ title: 'Missing Argument',
80
+ message: 'You must provide either a brain identifier or a run ID.',
81
+ details: 'Use: show <brain> to show brain info, or show --run-id <id> to show run info.'
82
+ }
64
83
  });
65
84
  }
66
85
  },
@@ -53,6 +53,7 @@ export var BrainKill = function(param) {
53
53
  var _useState = _sliced_to_array(useState(force), 2), confirmed = _useState[0], setConfirmed = _useState[1];
54
54
  var _useState1 = _sliced_to_array(useState(false), 2), killed = _useState1[0], setKilled = _useState1[1];
55
55
  var _useState2 = _sliced_to_array(useState(''), 2), input = _useState2[0], setInput = _useState2[1];
56
+ var inputRef = useRef(''); // Track input in ref to avoid stale closure
56
57
  var _useStdin = useStdin(), stdin = _useStdin.stdin, setRawMode = _useStdin.setRawMode;
57
58
  var exit = useApp().exit;
58
59
  var isKilling = useRef(false);
@@ -61,23 +62,43 @@ export var BrainKill = function(param) {
61
62
  if (stdin && !confirmed && !killed) {
62
63
  setRawMode(true);
63
64
  var handleData = function(data) {
64
- var char = data.toString();
65
- if (char === '\r' || char === '\n') {
66
- if (input.toLowerCase() === 'yes') {
67
- setConfirmed(true);
68
- } else {
69
- exit();
65
+ var chars = data.toString();
66
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
67
+ try {
68
+ // Process each character in the buffer (stdin may send multiple at once)
69
+ for(var _iterator = chars[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
70
+ var char = _step.value;
71
+ if (char === '\r' || char === '\n') {
72
+ if (inputRef.current.toLowerCase() === 'yes') {
73
+ setConfirmed(true);
74
+ } else {
75
+ exit();
76
+ }
77
+ return; // Stop processing after Enter
78
+ } else if (char === '\u0003') {
79
+ exit();
80
+ return;
81
+ } else if (char === '\u007F' || char === '\b') {
82
+ inputRef.current = inputRef.current.slice(0, -1);
83
+ setInput(inputRef.current);
84
+ } else {
85
+ inputRef.current = inputRef.current + char;
86
+ setInput(inputRef.current);
87
+ }
88
+ }
89
+ } catch (err) {
90
+ _didIteratorError = true;
91
+ _iteratorError = err;
92
+ } finally{
93
+ try {
94
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
95
+ _iterator.return();
96
+ }
97
+ } finally{
98
+ if (_didIteratorError) {
99
+ throw _iteratorError;
100
+ }
70
101
  }
71
- } else if (char === '\u0003') {
72
- exit();
73
- } else if (char === '\u007F' || char === '\b') {
74
- setInput(function(prev) {
75
- return prev.slice(0, -1);
76
- });
77
- } else {
78
- setInput(function(prev) {
79
- return prev + char;
80
- });
81
102
  }
82
103
  };
83
104
  stdin.on('data', handleData);
@@ -168,5 +189,6 @@ export var BrainKill = function(param) {
168
189
  dimColor: true
169
190
  }, "The brain will be cancelled and cannot be resumed.")), /*#__PURE__*/ React.createElement(Text, null, 'Type "yes" to confirm: ', input));
170
191
  }
171
- return null;
192
+ // Confirmed but still processing - show killing message
193
+ return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, null, "\uD83D\uDED1 Killing brain run..."));
172
194
  };
@@ -0,0 +1,111 @@
1
+ import React from 'react';
2
+ import { Text, Box } from 'ink';
3
+ import { useApiGet } from '../hooks/useApi.js';
4
+ import { ErrorComponent } from './error.js';
5
+ import { BrainResolver } from './brain-resolver.js';
6
+ // Component to display a labeled field
7
+ var Field = function(param) {
8
+ var label = param.label, children = param.children;
9
+ return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Box, {
10
+ width: 14
11
+ }, /*#__PURE__*/ React.createElement(Text, {
12
+ dimColor: true
13
+ }, label, ":")), /*#__PURE__*/ React.createElement(Box, {
14
+ flexGrow: 1
15
+ }, children));
16
+ };
17
+ var StepTree = function(param) {
18
+ var steps = param.steps, _param_depth = param.depth, depth = _param_depth === void 0 ? 0 : _param_depth;
19
+ return /*#__PURE__*/ React.createElement(Box, {
20
+ flexDirection: "column",
21
+ marginLeft: depth > 0 ? 2 : 0
22
+ }, steps.map(function(step, index) {
23
+ return /*#__PURE__*/ React.createElement(Box, {
24
+ key: index,
25
+ flexDirection: "column"
26
+ }, /*#__PURE__*/ React.createElement(Box, {
27
+ marginLeft: 1
28
+ }, /*#__PURE__*/ React.createElement(Text, {
29
+ color: "gray"
30
+ }, "○ ", step.title), step.type === 'loop' && /*#__PURE__*/ React.createElement(Text, {
31
+ dimColor: true
32
+ }, " (loop)")), step.innerBrain && /*#__PURE__*/ React.createElement(Box, {
33
+ marginLeft: 2,
34
+ marginTop: 0
35
+ }, /*#__PURE__*/ React.createElement(Box, {
36
+ flexDirection: "column"
37
+ }, /*#__PURE__*/ React.createElement(Text, {
38
+ dimColor: true
39
+ }, "└─ Inner Brain: ", step.innerBrain.title), /*#__PURE__*/ React.createElement(StepTree, {
40
+ steps: step.innerBrain.steps,
41
+ depth: depth + 1
42
+ }))));
43
+ }));
44
+ };
45
+ var BrainInfo = function(param) {
46
+ var brainTitle = param.brainTitle, showSteps = param.showSteps;
47
+ var _useApiGet = useApiGet("/brains/".concat(encodeURIComponent(brainTitle))), brain = _useApiGet.data, brainLoading = _useApiGet.loading, brainError = _useApiGet.error;
48
+ var _useApiGet1 = useApiGet('/brains/schedules'), schedulesData = _useApiGet1.data, schedulesLoading = _useApiGet1.loading;
49
+ if (brainLoading || schedulesLoading) {
50
+ return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, null, "Loading brain info..."));
51
+ }
52
+ if (brainError) {
53
+ return /*#__PURE__*/ React.createElement(ErrorComponent, {
54
+ error: brainError
55
+ });
56
+ }
57
+ if (!brain) {
58
+ return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
59
+ color: "red"
60
+ }, "Brain not found"));
61
+ }
62
+ // Filter schedules for this brain
63
+ var brainSchedules = (schedulesData === null || schedulesData === void 0 ? void 0 : schedulesData.schedules.filter(function(s) {
64
+ return s.brainTitle === brainTitle;
65
+ })) || [];
66
+ return /*#__PURE__*/ React.createElement(Box, {
67
+ flexDirection: "column",
68
+ gap: 1
69
+ }, /*#__PURE__*/ React.createElement(Box, {
70
+ flexDirection: "column"
71
+ }, /*#__PURE__*/ React.createElement(Field, {
72
+ label: "Brain"
73
+ }, /*#__PURE__*/ React.createElement(Text, {
74
+ bold: true
75
+ }, brain.title)), brain.description && /*#__PURE__*/ React.createElement(Field, {
76
+ label: "Description"
77
+ }, /*#__PURE__*/ React.createElement(Text, null, brain.description))), showSteps && brain.steps.length > 0 && /*#__PURE__*/ React.createElement(Box, {
78
+ flexDirection: "column"
79
+ }, /*#__PURE__*/ React.createElement(Text, {
80
+ bold: true
81
+ }, "Steps:"), /*#__PURE__*/ React.createElement(StepTree, {
82
+ steps: brain.steps
83
+ })), brainSchedules.length > 0 && /*#__PURE__*/ React.createElement(Box, {
84
+ flexDirection: "column"
85
+ }, /*#__PURE__*/ React.createElement(Text, {
86
+ bold: true
87
+ }, "Schedules:"), /*#__PURE__*/ React.createElement(Box, {
88
+ marginLeft: 2,
89
+ flexDirection: "column"
90
+ }, brainSchedules.map(function(schedule) {
91
+ return /*#__PURE__*/ React.createElement(Box, {
92
+ key: schedule.id
93
+ }, /*#__PURE__*/ React.createElement(Text, null, schedule.enabled ? '•' : '○', " ", schedule.cronExpression, !schedule.enabled && /*#__PURE__*/ React.createElement(Text, {
94
+ dimColor: true
95
+ }, " (disabled)")));
96
+ }))), brainSchedules.length === 0 && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
97
+ dimColor: true
98
+ }, "No schedules configured.")));
99
+ };
100
+ // Exported component that uses BrainResolver for fuzzy matching
101
+ export var BrainShow = function(param) {
102
+ var identifier = param.identifier, showSteps = param.showSteps;
103
+ return /*#__PURE__*/ React.createElement(BrainResolver, {
104
+ identifier: identifier
105
+ }, function(resolvedBrainTitle) {
106
+ return /*#__PURE__*/ React.createElement(BrainInfo, {
107
+ brainTitle: resolvedBrainTitle,
108
+ showSteps: showSteps
109
+ });
110
+ });
111
+ };
@@ -6,6 +6,19 @@ function _array_like_to_array(arr, len) {
6
6
  function _array_with_holes(arr) {
7
7
  if (Array.isArray(arr)) return arr;
8
8
  }
9
+ function _define_property(obj, key, value) {
10
+ if (key in obj) {
11
+ Object.defineProperty(obj, key, {
12
+ value: value,
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true
16
+ });
17
+ } else {
18
+ obj[key] = value;
19
+ }
20
+ return obj;
21
+ }
9
22
  function _iterable_to_array_limit(arr, i) {
10
23
  var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
11
24
  if (_i == null) return;
@@ -33,6 +46,45 @@ function _iterable_to_array_limit(arr, i) {
33
46
  function _non_iterable_rest() {
34
47
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
35
48
  }
49
+ function _object_spread(target) {
50
+ for(var i = 1; i < arguments.length; i++){
51
+ var source = arguments[i] != null ? arguments[i] : {};
52
+ var ownKeys = Object.keys(source);
53
+ if (typeof Object.getOwnPropertySymbols === "function") {
54
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
55
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
56
+ }));
57
+ }
58
+ ownKeys.forEach(function(key) {
59
+ _define_property(target, key, source[key]);
60
+ });
61
+ }
62
+ return target;
63
+ }
64
+ function ownKeys(object, enumerableOnly) {
65
+ var keys = Object.keys(object);
66
+ if (Object.getOwnPropertySymbols) {
67
+ var symbols = Object.getOwnPropertySymbols(object);
68
+ if (enumerableOnly) {
69
+ symbols = symbols.filter(function(sym) {
70
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
71
+ });
72
+ }
73
+ keys.push.apply(keys, symbols);
74
+ }
75
+ return keys;
76
+ }
77
+ function _object_spread_props(target, source) {
78
+ source = source != null ? source : {};
79
+ if (Object.getOwnPropertyDescriptors) {
80
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
81
+ } else {
82
+ ownKeys(Object(source)).forEach(function(key) {
83
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
84
+ });
85
+ }
86
+ return target;
87
+ }
36
88
  function _sliced_to_array(arr, i) {
37
89
  return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
38
90
  }
@@ -45,71 +97,175 @@ function _unsupported_iterable_to_array(o, minLen) {
45
97
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
46
98
  }
47
99
  import React, { useState, useEffect, useRef } from 'react';
48
- import { Text, Box } from 'ink';
100
+ import { Text, Box, useStdout } from 'ink';
49
101
  import { EventSource } from 'eventsource';
50
102
  import { BRAIN_EVENTS } from '@positronic/core';
51
103
  import { STATUS } from '@positronic/core';
52
104
  import { getApiBaseUrl, isApiLocalDevMode } from '../commands/helpers.js';
53
- var getStatusIndicator = function(status) {
105
+ import { ErrorComponent } from './error.js';
106
+ // Get the index of the currently running step (or last completed if none running)
107
+ var getCurrentStepIndex = function(steps) {
108
+ var runningIndex = steps.findIndex(function(s) {
109
+ return s.status === STATUS.RUNNING;
110
+ });
111
+ if (runningIndex >= 0) return runningIndex;
112
+ // Find the last completed step
113
+ for(var i = steps.length - 1; i >= 0; i--){
114
+ if (steps[i].status === STATUS.COMPLETE || steps[i].status === STATUS.ERROR) {
115
+ return i;
116
+ }
117
+ }
118
+ return 0;
119
+ };
120
+ // Count completed steps
121
+ var getCompletedCount = function(steps) {
122
+ return steps.filter(function(s) {
123
+ return s.status === STATUS.COMPLETE;
124
+ }).length;
125
+ };
126
+ // Get status indicator character
127
+ var getStatusChar = function(status) {
128
+ switch(status){
129
+ case STATUS.COMPLETE:
130
+ return '✓';
131
+ case STATUS.ERROR:
132
+ return '✗';
133
+ case STATUS.RUNNING:
134
+ return '•';
135
+ case STATUS.PENDING:
136
+ return '○';
137
+ default:
138
+ return '?';
139
+ }
140
+ };
141
+ // Get status color
142
+ var getStatusColor = function(status) {
54
143
  switch(status){
55
144
  case STATUS.COMPLETE:
56
- return /*#__PURE__*/ React.createElement(Text, {
57
- color: "green"
58
- }, "✔");
145
+ return 'green';
59
146
  case STATUS.ERROR:
60
- return /*#__PURE__*/ React.createElement(Text, {
61
- color: "red"
62
- }, "•");
147
+ return 'red';
63
148
  case STATUS.RUNNING:
64
- return /*#__PURE__*/ React.createElement(Text, {
65
- color: "white"
66
- }, "•");
149
+ return 'yellow';
67
150
  case STATUS.PENDING:
68
- return /*#__PURE__*/ React.createElement(Text, null, "•");
151
+ return 'gray';
69
152
  default:
70
- return /*#__PURE__*/ React.createElement(Text, null, "❓");
153
+ return 'white';
71
154
  }
72
155
  };
73
- var WatchStatus = function(param) {
74
- var steps = param.steps, brainTitle = param.brainTitle, runId = param.runId;
75
- // Maintain consistent Box wrapper for proper Ink terminal clearing
156
+ var ProgressBar = function(param) {
157
+ var completed = param.completed, total = param.total, _param_width = param.width, width = _param_width === void 0 ? 20 : _param_width;
158
+ var filledWidth = total > 0 ? Math.round(completed / total * width) : 0;
159
+ var emptyWidth = width - filledWidth;
160
+ return /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
161
+ color: "green"
162
+ }, '━'.repeat(filledWidth)), /*#__PURE__*/ React.createElement(Text, {
163
+ dimColor: true
164
+ }, '━'.repeat(emptyWidth)), /*#__PURE__*/ React.createElement(Text, {
165
+ dimColor: true
166
+ }, " ", completed, "/", total, " steps"));
167
+ };
168
+ var StepWindow = function(param) {
169
+ var steps = param.steps, _param_indent = param.indent, indent = _param_indent === void 0 ? 0 : _param_indent;
170
+ if (steps.length === 0) {
171
+ return /*#__PURE__*/ React.createElement(Box, {
172
+ marginLeft: indent
173
+ }, /*#__PURE__*/ React.createElement(Text, {
174
+ dimColor: true
175
+ }, "Waiting for steps..."));
176
+ }
177
+ var currentIndex = getCurrentStepIndex(steps);
178
+ var prevStep = currentIndex > 0 ? steps[currentIndex - 1] : null;
179
+ var currentStep = steps[currentIndex];
180
+ var nextStep = currentIndex < steps.length - 1 ? steps[currentIndex + 1] : null;
76
181
  return /*#__PURE__*/ React.createElement(Box, {
77
- flexDirection: "column"
78
- }, !steps || steps.length === 0 ? /*#__PURE__*/ React.createElement(Text, null, "Waiting for brain steps...") : /*#__PURE__*/ React.createElement(React.Fragment, null, brainTitle && /*#__PURE__*/ React.createElement(Text, {
182
+ flexDirection: "column",
183
+ marginLeft: indent
184
+ }, prevStep && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
185
+ color: getStatusColor(prevStep.status)
186
+ }, getStatusChar(prevStep.status), " ", prevStep.title)), currentStep && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
187
+ color: getStatusColor(currentStep.status),
79
188
  bold: true
80
- }, "Brain: ", brainTitle, " Run ID: ", runId), /*#__PURE__*/ React.createElement(Box, {
81
- marginTop: 1,
189
+ }, getStatusChar(currentStep.status), " ", currentStep.title)), nextStep && /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
190
+ color: getStatusColor(nextStep.status)
191
+ }, getStatusChar(nextStep.status), " ", nextStep.title)));
192
+ };
193
+ var BrainSection = function(param) {
194
+ var brain = param.brain, _param_isInner = param.isInner, isInner = _param_isInner === void 0 ? false : _param_isInner, brains = param.brains;
195
+ var indent = isInner ? 2 : 0;
196
+ var completed = getCompletedCount(brain.steps);
197
+ var total = brain.steps.length;
198
+ // Find the currently running step to check for inner brain
199
+ var currentIndex = getCurrentStepIndex(brain.steps);
200
+ var currentStep = brain.steps[currentIndex];
201
+ // Find any inner brain associated with the current step
202
+ var innerBrain = currentStep ? Array.from(brains.values()).find(function(b) {
203
+ return b.parentStepId === currentStep.id && !b.isComplete;
204
+ }) : null;
205
+ return /*#__PURE__*/ React.createElement(Box, {
206
+ flexDirection: "column",
207
+ marginLeft: indent
208
+ }, /*#__PURE__*/ React.createElement(Box, {
82
209
  marginBottom: 1
83
- }, /*#__PURE__*/ React.createElement(Text, {
210
+ }, isInner ? /*#__PURE__*/ React.createElement(Text, {
211
+ dimColor: true
212
+ }, "└─ ") : null, /*#__PURE__*/ React.createElement(Text, {
84
213
  bold: true
85
- }, "Steps:")), steps.map(function(step) {
86
- return /*#__PURE__*/ React.createElement(Box, {
87
- key: step.id,
88
- marginLeft: 1,
89
- marginBottom: 1,
90
- flexDirection: "row"
91
- }, /*#__PURE__*/ React.createElement(Box, null, /*#__PURE__*/ React.createElement(Text, {
92
- color: step.status === STATUS.COMPLETE ? 'green' : step.status === STATUS.ERROR ? 'red' : step.status === STATUS.RUNNING ? 'white' : step.status === STATUS.PENDING ? 'gray' : 'yellow'
93
- }, getStatusIndicator(step.status), " ", step.title)));
214
+ }, brain.brainTitle)), /*#__PURE__*/ React.createElement(StepWindow, {
215
+ steps: brain.steps,
216
+ indent: 1
217
+ }), /*#__PURE__*/ React.createElement(Box, {
218
+ marginTop: 1
219
+ }, /*#__PURE__*/ React.createElement(Box, {
220
+ marginLeft: 1
221
+ }, /*#__PURE__*/ React.createElement(ProgressBar, {
222
+ completed: completed,
223
+ total: total
224
+ }))), innerBrain && /*#__PURE__*/ React.createElement(Box, {
225
+ marginTop: 1
226
+ }, /*#__PURE__*/ React.createElement(BrainSection, {
227
+ brain: innerBrain,
228
+ isInner: true,
229
+ brains: brains
94
230
  })));
95
231
  };
96
232
  export var Watch = function(param) {
97
233
  var runId = param.runId;
98
- var _useState = _sliced_to_array(useState([]), 2), steps = _useState[0], setSteps = _useState[1];
99
- var _useState1 = _sliced_to_array(useState(undefined), 2), brainTitle = _useState1[0], setBrainTitle = _useState1[1];
100
- var _useState2 = _sliced_to_array(useState(undefined), 2), brainError = _useState2[0], setBrainError = _useState2[1];
101
- var _useState3 = _sliced_to_array(useState(null), 2), error = _useState3[0], setError = _useState3[1];
102
- var _useState4 = _sliced_to_array(useState(false), 2), isConnected = _useState4[0], setIsConnected = _useState4[1];
103
- var _useState5 = _sliced_to_array(useState(false), 2), isCompleted = _useState5[0], setIsCompleted = _useState5[1];
234
+ var write = useStdout().write;
235
+ // Track all brains (parent and inner) by their brainRunId
236
+ var _useState = _sliced_to_array(useState(new Map()), 2), brains = _useState[0], setBrains = _useState[1];
237
+ var _useState1 = _sliced_to_array(useState(undefined), 2), brainError = _useState1[0], setBrainError = _useState1[1];
238
+ var _useState2 = _sliced_to_array(useState(null), 2), error = _useState2[0], setError = _useState2[1];
239
+ var _useState3 = _sliced_to_array(useState(false), 2), isConnected = _useState3[0], setIsConnected = _useState3[1];
240
+ var _useState4 = _sliced_to_array(useState(false), 2), isCompleted = _useState4[0], setIsCompleted = _useState4[1];
104
241
  // Track the main brain's brainRunId to distinguish from inner brain events
105
242
  var mainBrainRunIdRef = useRef(null);
243
+ // Track the currently running step ID to associate inner brains with their parent step
244
+ var runningStepIdRef = useRef(null);
245
+ // Enter alternate screen buffer on mount, exit on unmount
246
+ // Skip in test environment to avoid interfering with test output capture
247
+ useEffect(function() {
248
+ if (process.env.NODE_ENV === 'test') {
249
+ return;
250
+ }
251
+ // Enter alternate screen buffer and clear
252
+ write('\x1B[?1049h\x1B[2J\x1B[H');
253
+ return function() {
254
+ // Exit alternate screen buffer
255
+ write('\x1B[?1049l');
256
+ };
257
+ }, [
258
+ write
259
+ ]);
106
260
  useEffect(function() {
107
261
  var baseUrl = getApiBaseUrl();
108
262
  var url = "".concat(baseUrl, "/brains/runs/").concat(runId, "/watch");
109
263
  var es = new EventSource(url);
110
264
  setIsConnected(false);
111
265
  setError(null);
266
+ setBrains(new Map());
112
267
  mainBrainRunIdRef.current = null;
268
+ runningStepIdRef.current = null;
113
269
  es.onopen = function() {
114
270
  setIsConnected(true);
115
271
  setError(null);
@@ -117,21 +273,60 @@ export var Watch = function(param) {
117
273
  es.onmessage = function(event) {
118
274
  try {
119
275
  var eventData = JSON.parse(event.data);
120
- if (eventData.type === BRAIN_EVENTS.STEP_STATUS) {
121
- setSteps(eventData.steps);
122
- }
276
+ // Handle brain start - register new brain (parent or inner)
123
277
  if (eventData.type === BRAIN_EVENTS.START || eventData.type === BRAIN_EVENTS.RESTART) {
124
278
  var startEvent = eventData;
125
- // Only set the main brain info on the first START event
126
- if (mainBrainRunIdRef.current === null) {
279
+ var isMainBrain = mainBrainRunIdRef.current === null;
280
+ if (isMainBrain) {
127
281
  mainBrainRunIdRef.current = startEvent.brainRunId;
128
- setBrainTitle(startEvent.brainTitle);
129
282
  }
283
+ setBrains(function(prev) {
284
+ var next = new Map(prev);
285
+ next.set(startEvent.brainRunId, {
286
+ brainRunId: startEvent.brainRunId,
287
+ brainTitle: startEvent.brainTitle,
288
+ steps: [],
289
+ parentStepId: isMainBrain ? null : runningStepIdRef.current,
290
+ isComplete: false
291
+ });
292
+ return next;
293
+ });
130
294
  setIsCompleted(false);
131
295
  }
132
- // Only mark complete when the main brain completes, not inner brains
296
+ // Handle step status - update steps for the specific brain only
297
+ if (eventData.type === BRAIN_EVENTS.STEP_STATUS) {
298
+ var statusEvent = eventData;
299
+ setBrains(function(prev) {
300
+ var next = new Map(prev);
301
+ var brain = next.get(statusEvent.brainRunId);
302
+ if (brain) {
303
+ next.set(statusEvent.brainRunId, _object_spread_props(_object_spread({}, brain), {
304
+ steps: statusEvent.steps
305
+ }));
306
+ }
307
+ return next;
308
+ });
309
+ }
310
+ // Handle step start - track the running step for inner brain association
311
+ if (eventData.type === BRAIN_EVENTS.STEP_START) {
312
+ var stepEvent = eventData;
313
+ runningStepIdRef.current = stepEvent.stepId;
314
+ }
315
+ // Mark brain as complete when it completes
133
316
  if (eventData.type === BRAIN_EVENTS.COMPLETE || eventData.type === BRAIN_EVENTS.ERROR) {
134
317
  var completeEvent = eventData;
318
+ // Mark this brain as complete
319
+ setBrains(function(prev) {
320
+ var next = new Map(prev);
321
+ var brain = next.get(completeEvent.brainRunId);
322
+ if (brain) {
323
+ next.set(completeEvent.brainRunId, _object_spread_props(_object_spread({}, brain), {
324
+ isComplete: true
325
+ }));
326
+ }
327
+ return next;
328
+ });
329
+ // Only mark overall as complete when the main brain completes
135
330
  if (completeEvent.brainRunId === mainBrainRunIdRef.current) {
136
331
  setIsCompleted(true);
137
332
  }
@@ -147,28 +342,24 @@ export var Watch = function(param) {
147
342
  setError(new Error("Error parsing event data: ".concat(e.message)));
148
343
  }
149
344
  };
150
- es.onerror = function(err) {
151
- // EventSource does not provide detailed error objects here, often just a generic Event
345
+ es.onerror = function() {
152
346
  var errorMessage = isApiLocalDevMode() ? "Connection to ".concat(url, " failed. Ensure the local development server is running ('positronic server' or 'px s').") : "Connection to ".concat(url, " failed. Please check your network connection and verify the project URL is correct.");
153
347
  setError(new Error(errorMessage));
154
348
  setIsConnected(false);
155
349
  es.close();
156
350
  };
157
- // Cleanup function to close EventSource when component unmounts or runId changes
158
351
  return function() {
159
352
  es.close();
160
353
  };
161
354
  }, [
162
355
  runId
163
356
  ]);
164
- // Maintain consistent Box wrapper to help Ink properly calculate
165
- // terminal clearing between renders (prevents appending instead of overwriting)
357
+ var mainBrain = mainBrainRunIdRef.current ? brains.get(mainBrainRunIdRef.current) : null;
166
358
  return /*#__PURE__*/ React.createElement(Box, {
167
359
  flexDirection: "column"
168
- }, !isConnected && steps.length === 0 ? /*#__PURE__*/ React.createElement(Text, null, "Connecting to watch service...") : /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(WatchStatus, {
169
- steps: steps,
170
- brainTitle: brainTitle,
171
- runId: runId
360
+ }, !isConnected && brains.size === 0 ? /*#__PURE__*/ React.createElement(Text, null, "Connecting to watch service...") : !mainBrain ? /*#__PURE__*/ React.createElement(Text, null, "Waiting for brain to start...") : /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(BrainSection, {
361
+ brain: mainBrain,
362
+ brains: brains
172
363
  }), isCompleted && !error && !brainError && /*#__PURE__*/ React.createElement(Box, {
173
364
  marginTop: 1,
174
365
  borderStyle: "round",
@@ -176,23 +367,17 @@ export var Watch = function(param) {
176
367
  paddingX: 1
177
368
  }, /*#__PURE__*/ React.createElement(Text, {
178
369
  color: "green"
179
- }, "Brain completed.")), error && /*#__PURE__*/ React.createElement(Box, {
180
- borderStyle: "round",
181
- borderColor: "red",
182
- padding: 1
183
- }, /*#__PURE__*/ React.createElement(Text, {
184
- color: "red"
185
- }, error.message), /*#__PURE__*/ React.createElement(Text, {
186
- color: "red"
187
- }, error.stack)), brainError && /*#__PURE__*/ React.createElement(Box, {
188
- borderStyle: "round",
189
- borderColor: "red",
190
- padding: 1
191
- }, /*#__PURE__*/ React.createElement(Text, {
192
- color: "red"
193
- }, brainError.error.name), /*#__PURE__*/ React.createElement(Text, {
194
- color: "red"
195
- }, brainError.error.message), /*#__PURE__*/ React.createElement(Text, {
196
- color: "red"
197
- }, brainError.error.stack))));
370
+ }, "Brain completed.")), error && /*#__PURE__*/ React.createElement(ErrorComponent, {
371
+ error: {
372
+ title: 'Connection Error',
373
+ message: error.message,
374
+ details: error.stack
375
+ }
376
+ }), brainError && /*#__PURE__*/ React.createElement(ErrorComponent, {
377
+ error: {
378
+ title: brainError.error.name || 'Brain Error',
379
+ message: brainError.error.message,
380
+ details: brainError.error.stack
381
+ }
382
+ })));
198
383
  };
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAK5D,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,KAAK,GAAG,CAAC;CAC9C;AAoBD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,UAAU,4BAkkC3C"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAK5D,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,KAAK,GAAG,CAAC;CAC9C;AAoBD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,UAAU,4BAomC3C"}
@@ -7,7 +7,9 @@ interface BrainHistoryArgs {
7
7
  limit: number;
8
8
  }
9
9
  interface BrainShowArgs {
10
- runId: string;
10
+ brain?: string;
11
+ runId?: string;
12
+ steps?: boolean;
11
13
  }
12
14
  interface BrainRerunArgs {
13
15
  brain: string;
@@ -31,7 +33,7 @@ interface BrainKillArgs {
31
33
  export declare class BrainCommand {
32
34
  list(argv: ArgumentsCamelCase<BrainListArgs>): React.ReactElement;
33
35
  history({ brain, limit, }: ArgumentsCamelCase<BrainHistoryArgs>): React.ReactElement;
34
- show({ runId, }: ArgumentsCamelCase<BrainShowArgs>): React.ReactElement;
36
+ show({ brain, runId, steps, }: ArgumentsCamelCase<BrainShowArgs>): React.ReactElement;
35
37
  rerun({ brain, runId, startsAt, stopsAfter, }: ArgumentsCamelCase<BrainRerunArgs>): React.ReactElement;
36
38
  run({ brain, watch, options }: ArgumentsCamelCase<BrainRunArgs>): React.ReactElement;
37
39
  watch({ runId, brain, }: ArgumentsCamelCase<BrainWatchArgs>): React.ReactElement;
@@ -1 +1 @@
1
- {"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../../src/commands/brain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,KAAK,MAAM,OAAO,CAAC;AAY1B,UAAU,aAAa;CAAG;AAC1B,UAAU,gBAAgB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AACD,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AACD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AACD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AACD,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AACD,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,qBAAa,YAAY;IACvB,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,YAAY;IAIjE,OAAO,CAAC,EACN,KAAK,EACL,KAAK,GACN,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC,YAAY;IAQ5D,IAAI,CAAC,EACH,KAAK,GACN,EAAE,kBAAkB,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,YAAY;IAIzD,KAAK,CAAC,EACJ,KAAK,EACL,KAAK,EACL,QAAQ,EACR,UAAU,GACX,EAAE,kBAAkB,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,YAAY;IAa1D,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,kBAAkB,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,YAAY;IAQpF,KAAK,CAAC,EACJ,KAAK,EACL,KAAK,GACN,EAAE,kBAAkB,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,YAAY;IAyB1D,IAAI,CAAC,EACH,KAAK,EACL,KAAK,GACN,EAAE,kBAAkB,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,YAAY;CAI1D"}
1
+ {"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../../src/commands/brain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,UAAU,aAAa;CAAG;AAC1B,UAAU,gBAAgB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AACD,UAAU,aAAa;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AACD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AACD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AACD,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AACD,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,qBAAa,YAAY;IACvB,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,YAAY;IAIjE,OAAO,CAAC,EACN,KAAK,EACL,KAAK,GACN,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC,YAAY;IAQ5D,IAAI,CAAC,EACH,KAAK,EACL,KAAK,EACL,KAAK,GACN,EAAE,kBAAkB,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,YAAY;IAqBzD,KAAK,CAAC,EACJ,KAAK,EACL,KAAK,EACL,QAAQ,EACR,UAAU,GACX,EAAE,kBAAkB,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,YAAY;IAa1D,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,kBAAkB,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,YAAY;IAQpF,KAAK,CAAC,EACJ,KAAK,EACL,KAAK,GACN,EAAE,kBAAkB,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,YAAY;IAyB1D,IAAI,CAAC,EACH,KAAK,EACL,KAAK,GACN,EAAE,kBAAkB,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,YAAY;CAI1D"}
@@ -2,6 +2,6 @@ interface BrainKillProps {
2
2
  runId: string;
3
3
  force: boolean;
4
4
  }
5
- export declare const BrainKill: ({ runId, force }: BrainKillProps) => import("react/jsx-runtime").JSX.Element | null;
5
+ export declare const BrainKill: ({ runId, force }: BrainKillProps) => import("react/jsx-runtime").JSX.Element;
6
6
  export {};
7
7
  //# sourceMappingURL=brain-kill.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"brain-kill.d.ts","sourceRoot":"","sources":["../../../src/components/brain-kill.tsx"],"names":[],"mappings":"AAKA,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,SAAS,GAAI,kBAAkB,cAAc,mDAoHzD,CAAC"}
1
+ {"version":3,"file":"brain-kill.d.ts","sourceRoot":"","sources":["../../../src/components/brain-kill.tsx"],"names":[],"mappings":"AAKA,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,SAAS,GAAI,kBAAkB,cAAc,4CAiIzD,CAAC"}
@@ -0,0 +1,7 @@
1
+ interface BrainShowProps {
2
+ identifier: string;
3
+ showSteps: boolean;
4
+ }
5
+ export declare const BrainShow: ({ identifier, showSteps }: BrainShowProps) => import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=brain-show.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-show.d.ts","sourceRoot":"","sources":["../../../src/components/brain-show.tsx"],"names":[],"mappings":"AA+JA,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AAGD,eAAO,MAAM,SAAS,GAAI,2BAA2B,cAAc,4CAQlE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../../src/components/watch.tsx"],"names":[],"mappings":"AAiEA,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,KAAK,GAAI,WAAW,UAAU,4CA+G1C,CAAC"}
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../../src/components/watch.tsx"],"names":[],"mappings":"AA8LA,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,KAAK,GAAI,WAAW,UAAU,4CAsL1C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@positronic/cli",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,9 +23,9 @@
23
23
  "clean": "rm -rf tsconfig.tsbuildinfo dist node_modules"
24
24
  },
25
25
  "dependencies": {
26
- "@positronic/core": "^0.0.43",
27
- "@positronic/spec": "^0.0.43",
28
- "@positronic/template-new-project": "^0.0.43",
26
+ "@positronic/core": "^0.0.45",
27
+ "@positronic/spec": "^0.0.45",
28
+ "@positronic/template-new-project": "^0.0.45",
29
29
  "caz": "^2.0.0",
30
30
  "chokidar": "^3.6.0",
31
31
  "dotenv": "^16.4.7",