prompt-language-shell 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +114 -10
  2. package/dist/config/ANSWER.md +4 -0
  3. package/dist/config/INTROSPECT.md +4 -3
  4. package/dist/config/PLAN.md +23 -0
  5. package/dist/config/VALIDATE.md +12 -11
  6. package/dist/services/components.js +54 -64
  7. package/dist/services/configuration.js +84 -0
  8. package/dist/services/messages.js +22 -0
  9. package/dist/services/queue.js +2 -2
  10. package/dist/services/refinement.js +36 -0
  11. package/dist/services/task-router.js +135 -0
  12. package/dist/types/types.js +0 -1
  13. package/dist/ui/Answer.js +18 -27
  14. package/dist/ui/Command.js +45 -27
  15. package/dist/ui/Component.js +23 -50
  16. package/dist/ui/Config.js +49 -24
  17. package/dist/ui/Confirm.js +17 -11
  18. package/dist/ui/Execute.js +66 -45
  19. package/dist/ui/Feedback.js +1 -1
  20. package/dist/ui/Introspect.js +27 -23
  21. package/dist/ui/Main.js +71 -100
  22. package/dist/ui/Message.js +1 -1
  23. package/dist/ui/Plan.js +54 -32
  24. package/dist/ui/Refinement.js +6 -7
  25. package/dist/ui/Report.js +1 -1
  26. package/dist/ui/UserQuery.js +6 -0
  27. package/dist/ui/Validate.js +49 -19
  28. package/dist/ui/Welcome.js +12 -5
  29. package/dist/ui/Workflow.js +119 -0
  30. package/package.json +1 -1
  31. package/dist/handlers/answer.js +0 -21
  32. package/dist/handlers/command.js +0 -34
  33. package/dist/handlers/config.js +0 -88
  34. package/dist/handlers/execute.js +0 -46
  35. package/dist/handlers/execution.js +0 -140
  36. package/dist/handlers/introspect.js +0 -21
  37. package/dist/handlers/plan.js +0 -79
  38. package/dist/types/handlers.js +0 -1
  39. package/dist/ui/AnswerDisplay.js +0 -8
  40. package/dist/ui/Column.js +0 -7
package/dist/ui/Config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
2
+ import { useState } from 'react';
3
3
  import { Box, Text, useFocus } from 'ink';
4
4
  import TextInput from 'ink-text-input';
5
5
  import { Colors } from '../services/colors.js';
@@ -10,8 +10,8 @@ export var StepType;
10
10
  StepType["Selection"] = "selection";
11
11
  })(StepType || (StepType = {}));
12
12
  function TextStep({ value, placeholder, validate, onChange, onSubmit, }) {
13
- const [inputValue, setInputValue] = React.useState(value);
14
- const [validationFailed, setValidationFailed] = React.useState(false);
13
+ const [inputValue, setInputValue] = useState(value);
14
+ const [validationFailed, setValidationFailed] = useState(false);
15
15
  const { isFocused } = useFocus({ autoFocus: true });
16
16
  const handleChange = (newValue) => {
17
17
  setInputValue(newValue);
@@ -57,20 +57,27 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
57
57
  return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
58
58
  }) }));
59
59
  }
60
- export function Config({ steps, state, debug, onFinished, onAborted }) {
61
- const done = state?.done ?? false;
62
- const [step, setStep] = React.useState(done ? steps.length : 0);
63
- const [values, setValues] = React.useState(() => {
60
+ export function Config({ steps, state, isActive = true, debug, handlers, onFinished, onAborted, }) {
61
+ // isActive passed as prop
62
+ const [step, setStep] = useState(!isActive ? (state?.completedStep ?? steps.length) : 0);
63
+ const [values, setValues] = useState(() => {
64
+ // If not active and we have saved state values, use those
65
+ if (!isActive && state?.values) {
66
+ return state.values;
67
+ }
68
+ // Otherwise initialize from step defaults
64
69
  const initial = {};
65
70
  steps.forEach((stepConfig) => {
71
+ // Use full path if available, otherwise use key
72
+ const configKey = stepConfig.path || stepConfig.key;
66
73
  switch (stepConfig.type) {
67
74
  case StepType.Text:
68
75
  if (stepConfig.value !== null) {
69
- initial[stepConfig.key] = stepConfig.value;
76
+ initial[configKey] = stepConfig.value;
70
77
  }
71
78
  break;
72
79
  case StepType.Selection:
73
- initial[stepConfig.key] =
80
+ initial[configKey] =
74
81
  stepConfig.options[stepConfig.defaultIndex].value;
75
82
  break;
76
83
  default: {
@@ -81,8 +88,8 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
81
88
  });
82
89
  return initial;
83
90
  });
84
- const [inputValue, setInputValue] = React.useState('');
85
- const [selectedIndex, setSelectedIndex] = React.useState(() => {
91
+ const [inputValue, setInputValue] = useState('');
92
+ const [selectedIndex, setSelectedIndex] = useState(() => {
86
93
  const firstStep = steps[0];
87
94
  return firstStep?.type === StepType.Selection ? firstStep.defaultIndex : 0;
88
95
  });
@@ -93,19 +100,20 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
93
100
  return value.replace(/\n/g, '').trim();
94
101
  };
95
102
  useInput((input, key) => {
96
- if (key.escape && !done && step < steps.length) {
103
+ if (key.escape && isActive && step < steps.length) {
97
104
  // Save current value before aborting
98
105
  const currentStepConfig = steps[step];
99
106
  if (currentStepConfig) {
107
+ const configKey = currentStepConfig.path || currentStepConfig.key;
100
108
  let currentValue = '';
101
109
  switch (currentStepConfig.type) {
102
110
  case StepType.Text:
103
- currentValue = inputValue || values[currentStepConfig.key] || '';
111
+ currentValue = inputValue || values[configKey] || '';
104
112
  break;
105
113
  case StepType.Selection:
106
114
  currentValue =
107
115
  currentStepConfig.options[selectedIndex]?.value ||
108
- values[currentStepConfig.key] ||
116
+ values[configKey] ||
109
117
  '';
110
118
  break;
111
119
  default: {
@@ -114,16 +122,16 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
114
122
  }
115
123
  }
116
124
  if (currentValue) {
117
- setValues({ ...values, [currentStepConfig.key]: currentValue });
125
+ setValues({ ...values, [configKey]: currentValue });
118
126
  }
119
127
  }
120
128
  if (onAborted) {
121
- onAborted();
129
+ onAborted('configuration');
122
130
  }
123
131
  return;
124
132
  }
125
133
  const currentStep = steps[step];
126
- if (!done && step < steps.length && currentStep) {
134
+ if (isActive && step < steps.length && currentStep) {
127
135
  switch (currentStep.type) {
128
136
  case StepType.Selection:
129
137
  if (key.tab) {
@@ -173,7 +181,9 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
173
181
  if (!finalValue) {
174
182
  return;
175
183
  }
176
- const newValues = { ...values, [currentStepConfig.key]: finalValue };
184
+ // Use full path if available, otherwise use key
185
+ const configKey = currentStepConfig.path || currentStepConfig.key;
186
+ const newValues = { ...values, [configKey]: finalValue };
177
187
  setValues(newValues);
178
188
  setInputValue('');
179
189
  if (step === steps.length - 1) {
@@ -181,9 +191,21 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
181
191
  if (onFinished) {
182
192
  onFinished(newValues);
183
193
  }
194
+ // Save state before completing
195
+ handlers?.updateState({
196
+ values: newValues,
197
+ completedStep: steps.length,
198
+ });
199
+ // Signal Workflow that config is complete
200
+ handlers?.onComplete();
184
201
  setStep(steps.length);
185
202
  }
186
203
  else {
204
+ // Save state after each step
205
+ handlers?.updateState({
206
+ values: newValues,
207
+ completedStep: step + 1,
208
+ });
187
209
  setStep(step + 1);
188
210
  // Reset selection index for next step
189
211
  const nextStep = steps[step + 1];
@@ -193,15 +215,18 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
193
215
  }
194
216
  };
195
217
  const renderStepInput = (stepConfig, isCurrentStep) => {
218
+ const configKey = stepConfig.path || stepConfig.key;
219
+ // Use state values if not active (in timeline), otherwise use local values
220
+ const displayValue = !isActive && state?.values ? state.values[configKey] : values[configKey];
196
221
  switch (stepConfig.type) {
197
222
  case StepType.Text:
198
223
  if (isCurrentStep) {
199
224
  return (_jsx(TextStep, { value: inputValue, placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: setInputValue, onSubmit: handleSubmit }));
200
225
  }
201
- return _jsx(Text, { dimColor: true, children: values[stepConfig.key] || '' });
226
+ return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
202
227
  case StepType.Selection: {
203
228
  if (!isCurrentStep) {
204
- const selectedOption = stepConfig.options.find((opt) => opt.value === values[stepConfig.key]);
229
+ const selectedOption = stepConfig.options.find((opt) => opt.value === displayValue);
205
230
  return _jsx(Text, { dimColor: true, children: selectedOption?.label || '' });
206
231
  }
207
232
  return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: isCurrentStep }));
@@ -212,14 +237,14 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
212
237
  }
213
238
  }
214
239
  };
215
- return (_jsx(Box, { flexDirection: "column", children: steps.map((stepConfig, index) => {
216
- const isCurrentStep = index === step && !done;
240
+ return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
241
+ const isCurrentStep = index === step && isActive;
217
242
  const isCompleted = index < step;
218
- const wasAborted = index === step && done;
243
+ const wasAborted = index === step && !isActive;
219
244
  const shouldShow = isCompleted || isCurrentStep || wasAborted;
220
245
  if (!shouldShow) {
221
246
  return null;
222
247
  }
223
- return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), debug && stepConfig.path && (_jsxs(Text, { color: Colors.Type.Define, children: ['{', stepConfig.path, '}'] }))] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
248
+ return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), debug && stepConfig.path && (_jsxs(Text, { color: Colors.Type.Define, children: ['{', stepConfig.path, '}'] }))] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.path || stepConfig.key));
224
249
  }) }));
225
250
  }
@@ -1,26 +1,32 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
2
+ import { useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { Colors, Palette } from '../services/colors.js';
5
5
  import { useInput } from '../services/keyboard.js';
6
- export function Confirm({ message, state, onConfirmed, onCancelled, }) {
7
- const done = state?.done ?? false;
8
- const isCurrent = done === false;
9
- const [selectedIndex, setSelectedIndex] = React.useState(0); // 0 = Yes, 1 = No
6
+ import { UserQuery } from './UserQuery.js';
7
+ export function Confirm({ message, state, isActive = true, handlers, onConfirmed, onCancelled, }) {
8
+ // isActive passed as prop
9
+ const [selectedIndex, setSelectedIndex] = useState(state?.selectedIndex ?? 0); // 0 = Yes, 1 = No
10
10
  useInput((input, key) => {
11
- if (done)
11
+ if (!isActive)
12
12
  return;
13
13
  if (key.escape) {
14
14
  // Escape: highlight "No" and cancel
15
15
  setSelectedIndex(1);
16
+ handlers?.updateState({ selectedIndex: 1 });
16
17
  onCancelled?.();
17
18
  }
18
19
  else if (key.tab) {
19
20
  // Toggle between Yes (0) and No (1)
20
- setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
21
+ setSelectedIndex((prev) => {
22
+ const newIndex = prev === 0 ? 1 : 0;
23
+ handlers?.updateState({ selectedIndex: newIndex });
24
+ return newIndex;
25
+ });
21
26
  }
22
27
  else if (key.return) {
23
28
  // Confirm selection
29
+ handlers?.updateState({ selectedIndex, confirmed: true });
24
30
  if (selectedIndex === 0) {
25
31
  onConfirmed?.();
26
32
  }
@@ -28,16 +34,16 @@ export function Confirm({ message, state, onConfirmed, onCancelled, }) {
28
34
  onCancelled?.();
29
35
  }
30
36
  }
31
- }, { isActive: !done });
37
+ }, { isActive });
32
38
  const options = [
33
39
  { label: 'yes', value: 'yes', color: Palette.BrightGreen },
34
40
  { label: 'no', value: 'no', color: Colors.Status.Error },
35
41
  ];
36
- if (done) {
42
+ if (!isActive) {
37
43
  // When done, show both the message and user's choice in timeline
38
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsx(Box, { paddingX: 1, marginX: -1, alignSelf: "flex-start", backgroundColor: Colors.Background.UserQuery, children: _jsxs(Text, { color: Colors.Text.UserQuery, children: ["> ", options[selectedIndex].label] }) })] }));
44
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsxs(UserQuery, { children: ["> ", options[selectedIndex].label] })] }));
39
45
  }
40
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: isCurrent ? Colors.Text.Active : Colors.Text.Inactive, children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
46
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: isActive ? Colors.Text.Active : Colors.Text.Inactive, children: message }) }), _jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
41
47
  const isSelected = index === selectedIndex;
42
48
  return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
43
49
  }) })] })] }));
@@ -85,41 +85,53 @@ function CommandStatusDisplay({ item, elapsed }) {
85
85
  const elapsedTime = getElapsedTime();
86
86
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[item.status] }), _jsx(Text, { color: colors.description, children: item.label || item.command.description }), elapsedTime !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: [" (", formatDuration(elapsedTime), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, children: [_jsx(Text, { color: colors.symbol, children: "\u221F " }), _jsx(Text, { color: colors.command, children: item.command.command }), item.status === ExecutionStatus.Running && (_jsxs(Text, { children: [' ', _jsx(Spinner, {})] }))] })] }));
87
87
  }
88
- export function Execute({ tasks, state, service, onError, onComplete, onAborted, }) {
89
- const done = state?.done ?? false;
90
- const isCurrent = done === false;
91
- const [error, setError] = useState(null);
92
- const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
88
+ export function Execute({ tasks, state, isActive = true, service, handlers, }) {
89
+ // isActive passed as prop
90
+ const [error, setError] = useState(state?.error ?? null);
93
91
  const [isExecuting, setIsExecuting] = useState(false);
94
- const [commandStatuses, setCommandStatuses] = useState([]);
95
- const [message, setMessage] = useState('');
92
+ const [commandStatuses, setCommandStatuses] = useState(state?.commandStatuses ?? []);
93
+ const [message, setMessage] = useState(state?.message ?? '');
96
94
  const [currentElapsed, setCurrentElapsed] = useState(0);
97
95
  const [runningIndex, setRunningIndex] = useState(null);
98
96
  const [outputs, setOutputs] = useState([]);
99
- useInput((input, key) => {
100
- if (key.escape && (isLoading || isExecuting) && !done) {
101
- setIsLoading(false);
97
+ const [hasProcessed, setHasProcessed] = useState(false);
98
+ // Derive loading state from current conditions
99
+ const isLoading = isActive &&
100
+ commandStatuses.length === 0 &&
101
+ !error &&
102
+ !isExecuting &&
103
+ !hasProcessed;
104
+ useInput((_, key) => {
105
+ if (key.escape && (isLoading || isExecuting) && isActive) {
102
106
  setIsExecuting(false);
103
107
  setRunningIndex(null);
104
108
  // Mark any running command as aborted when cancelled
105
109
  const now = Date.now();
106
- setCommandStatuses((prev) => prev.map((item) => {
107
- if (item.status === ExecutionStatus.Running) {
108
- const elapsed = item.startTime
109
- ? Math.floor((now - item.startTime) / 1000) * 1000
110
- : undefined;
111
- return {
112
- ...item,
113
- status: ExecutionStatus.Aborted,
114
- endTime: now,
115
- elapsed,
116
- };
117
- }
118
- return item;
119
- }));
120
- onAborted(calculateTotalElapsed(commandStatuses));
110
+ setCommandStatuses((prev) => {
111
+ const updated = prev.map((item) => {
112
+ if (item.status === ExecutionStatus.Running) {
113
+ const elapsed = item.startTime
114
+ ? Math.floor((now - item.startTime) / 1000) * 1000
115
+ : undefined;
116
+ return {
117
+ ...item,
118
+ status: ExecutionStatus.Aborted,
119
+ endTime: now,
120
+ elapsed,
121
+ };
122
+ }
123
+ return item;
124
+ });
125
+ // Save state after updating
126
+ handlers?.updateState({
127
+ commandStatuses: updated,
128
+ message,
129
+ });
130
+ return updated;
131
+ });
132
+ handlers?.onAborted('execution');
121
133
  }
122
- }, { isActive: (isLoading || isExecuting) && !done });
134
+ }, { isActive: (isLoading || isExecuting) && isActive });
123
135
  // Update elapsed time for running command
124
136
  useEffect(() => {
125
137
  if (runningIndex === null)
@@ -139,15 +151,20 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
139
151
  useEffect(() => {
140
152
  if (isExecuting || commandStatuses.length === 0 || !outputs.length)
141
153
  return;
142
- onComplete?.(outputs, calculateTotalElapsed(commandStatuses));
143
- }, [isExecuting, commandStatuses, outputs, onComplete]);
154
+ // Save state before completing
155
+ handlers?.updateState({
156
+ message,
157
+ commandStatuses,
158
+ error,
159
+ });
160
+ handlers?.onComplete();
161
+ }, [isExecuting, commandStatuses, outputs, handlers, message, error]);
144
162
  useEffect(() => {
145
- if (done) {
163
+ if (!isActive) {
146
164
  return;
147
165
  }
148
166
  if (!service) {
149
167
  setError('No service available');
150
- setIsLoading(false);
151
168
  return;
152
169
  }
153
170
  let mounted = true;
@@ -173,9 +190,14 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
173
190
  if (!mounted)
174
191
  return;
175
192
  if (!result.commands || result.commands.length === 0) {
176
- setIsLoading(false);
177
193
  setOutputs([]);
178
- onComplete?.([], 0);
194
+ setHasProcessed(true);
195
+ // Save state before completing
196
+ handlers?.updateState({
197
+ message: result.message,
198
+ commandStatuses: [],
199
+ });
200
+ handlers?.onComplete();
179
201
  return;
180
202
  }
181
203
  // Resolve placeholders in command strings before execution
@@ -190,7 +212,6 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
190
212
  status: ExecutionStatus.Pending,
191
213
  label: tasks[index]?.action,
192
214
  })));
193
- setIsLoading(false);
194
215
  setIsExecuting(true);
195
216
  // Execute commands sequentially
196
217
  const outputs = await executeCommands(resolvedCommands, (progress) => {
@@ -236,14 +257,14 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
236
257
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
237
258
  if (mounted) {
238
259
  const errorMessage = formatErrorMessage(err);
239
- setIsLoading(false);
240
260
  setIsExecuting(false);
241
- if (onError) {
242
- onError(errorMessage);
243
- }
244
- else {
245
- setError(errorMessage);
246
- }
261
+ setError(errorMessage);
262
+ setHasProcessed(true);
263
+ // Save error state
264
+ handlers?.updateState({
265
+ error: errorMessage,
266
+ });
267
+ handlers?.onError(errorMessage);
247
268
  }
248
269
  }
249
270
  }
@@ -251,12 +272,12 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
251
272
  return () => {
252
273
  mounted = false;
253
274
  };
254
- }, [tasks, done, service, onComplete, onError]);
275
+ }, [tasks, isActive, service, handlers]);
255
276
  // Return null only when loading completes with no commands
256
- if (done && commandStatuses.length === 0 && !error) {
277
+ if (!isActive && commandStatuses.length === 0 && !error) {
257
278
  return null;
258
279
  }
259
- // Show completed steps when done
260
- const showCompletedSteps = done && commandStatuses.length > 0;
261
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showCompletedSteps) && (_jsxs(Box, { flexDirection: "column", children: [message && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: getTextColor(isCurrent), children: message }), isExecuting && (_jsxs(Text, { children: [' ', _jsx(Spinner, {})] }))] })), commandStatuses.map((item, index) => (_jsx(Box, { marginBottom: index < commandStatuses.length - 1 ? 1 : 0, children: _jsx(CommandStatusDisplay, { item: item, elapsed: index === runningIndex ? currentElapsed : undefined }) }, index)))] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
280
+ // Show completed steps when not active
281
+ const showCompletedSteps = !isActive && commandStatuses.length > 0;
282
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showCompletedSteps) && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [message && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: message }), isExecuting && (_jsxs(Text, { children: [' ', _jsx(Spinner, {})] }))] })), commandStatuses.map((item, index) => (_jsx(Box, { marginBottom: index < commandStatuses.length - 1 ? 1 : 0, children: _jsx(CommandStatusDisplay, { item: item, elapsed: index === runningIndex ? currentElapsed : undefined }) }, index)))] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
262
283
  }
@@ -13,5 +13,5 @@ function getSymbol(type) {
13
13
  export function Feedback({ type, message }) {
14
14
  const color = getFeedbackColor(type, false);
15
15
  const symbol = getSymbol(type);
16
- return (_jsx(Box, { children: _jsxs(Text, { color: color, children: [symbol, " ", message] }) }));
16
+ return (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: color, children: [symbol, " ", message] }) }));
17
17
  }
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { Colors, getTextColor } from '../services/colors.js';
5
+ import { createReportDefinition } from '../services/components.js';
5
6
  import { useInput } from '../services/keyboard.js';
6
7
  import { formatErrorMessage } from '../services/messages.js';
7
8
  import { ensureMinimumTime } from '../services/timing.js';
@@ -41,26 +42,22 @@ function parseCapabilityFromTask(task) {
41
42
  isIndirect,
42
43
  };
43
44
  }
44
- export function Introspect({ tasks, state, service, children, debug = false, onError, onComplete, onAborted, }) {
45
- const done = state?.done ?? false;
46
- const isCurrent = done === false;
45
+ export function Introspect({ tasks, state, isActive = true, service, children, debug = false, handlers, }) {
46
+ // isActive passed as prop
47
47
  const [error, setError] = useState(null);
48
- const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
49
48
  useInput((input, key) => {
50
- if (key.escape && isLoading && !done) {
51
- setIsLoading(false);
52
- onAborted();
49
+ if (key.escape && isActive) {
50
+ handlers?.onAborted('introspection');
53
51
  }
54
- }, { isActive: isLoading && !done });
52
+ }, { isActive });
55
53
  useEffect(() => {
56
- // Skip processing if done
57
- if (done) {
54
+ // Skip processing if not active
55
+ if (!isActive) {
58
56
  return;
59
57
  }
60
58
  // Skip processing if no service available
61
59
  if (!service) {
62
60
  setError('No service available');
63
- setIsLoading(false);
64
61
  return;
65
62
  }
66
63
  let mounted = true;
@@ -78,23 +75,30 @@ export function Introspect({ tasks, state, service, children, debug = false, onE
78
75
  // Filter out internal capabilities when not in debug mode
79
76
  if (!debug) {
80
77
  capabilities = capabilities.filter((cap) => cap.name.toUpperCase() !== 'PLAN' &&
78
+ cap.name.toUpperCase() !== 'VALIDATE' &&
81
79
  cap.name.toUpperCase() !== 'REPORT');
82
80
  }
83
- setIsLoading(false);
84
- onComplete?.(result.message, capabilities);
81
+ // Save state before completing
82
+ handlers?.updateState({
83
+ capabilities,
84
+ message: result.message,
85
+ });
86
+ // Add Report component to queue
87
+ handlers?.addToQueue(createReportDefinition(result.message, capabilities));
88
+ // Signal completion
89
+ handlers?.onComplete();
85
90
  }
86
91
  }
87
92
  catch (err) {
88
93
  await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
89
94
  if (mounted) {
90
95
  const errorMessage = formatErrorMessage(err);
91
- setIsLoading(false);
92
- if (onError) {
93
- onError(errorMessage);
94
- }
95
- else {
96
- setError(errorMessage);
97
- }
96
+ setError(errorMessage);
97
+ // Save error state
98
+ handlers?.updateState({
99
+ error: errorMessage,
100
+ });
101
+ handlers?.onError(errorMessage);
98
102
  }
99
103
  }
100
104
  }
@@ -102,10 +106,10 @@ export function Introspect({ tasks, state, service, children, debug = false, onE
102
106
  return () => {
103
107
  mounted = false;
104
108
  };
105
- }, [tasks, done, service, debug, onComplete, onError]);
109
+ }, [tasks, isActive, service, debug, handlers]);
106
110
  // Don't render wrapper when done and nothing to show
107
- if (!isLoading && !error && !children) {
111
+ if (!isActive && !error && !children) {
108
112
  return null;
109
113
  }
110
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
114
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
111
115
  }