prompt-language-shell 0.6.0 → 0.6.4
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/config/CONFIG.md +4 -2
- package/dist/services/anthropic.js +49 -4
- package/dist/services/components.js +27 -17
- package/dist/services/configuration.js +107 -27
- package/dist/services/task-router.js +20 -13
- package/dist/types/components.js +8 -1
- package/dist/ui/Answer.js +4 -2
- package/dist/ui/Command.js +7 -6
- package/dist/ui/Component.js +14 -17
- package/dist/ui/Config.js +45 -47
- package/dist/ui/Confirm.js +6 -7
- package/dist/ui/Execute.js +5 -3
- package/dist/ui/Introspect.js +4 -2
- package/dist/ui/Plan.js +10 -7
- package/dist/ui/Refinement.js +3 -1
- package/dist/ui/Validate.js +4 -3
- package/dist/ui/Workflow.js +129 -56
- package/package.json +1 -1
- package/dist/services/queue.js +0 -52
package/dist/ui/Component.js
CHANGED
|
@@ -14,36 +14,33 @@ import { Refinement } from './Refinement.js';
|
|
|
14
14
|
import { Report } from './Report.js';
|
|
15
15
|
import { Validate } from './Validate.js';
|
|
16
16
|
import { Welcome } from './Welcome.js';
|
|
17
|
-
export const Component = memo(function Component({ def,
|
|
18
|
-
// For stateless components, always inactive
|
|
19
|
-
const isStatelessComponent = !('state' in def);
|
|
20
|
-
const componentIsActive = isStatelessComponent ? false : isActive;
|
|
17
|
+
export const Component = memo(function Component({ def, debug, }) {
|
|
21
18
|
switch (def.name) {
|
|
22
19
|
case ComponentName.Welcome:
|
|
23
|
-
return _jsx(Welcome, { ...def.props });
|
|
20
|
+
return _jsx(Welcome, { ...def.props, status: def.status });
|
|
24
21
|
case ComponentName.Config:
|
|
25
|
-
return (_jsx(Config, { ...def.props, state: def.state,
|
|
22
|
+
return (_jsx(Config, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
26
23
|
case ComponentName.Command:
|
|
27
|
-
return _jsx(Command, { ...def.props, state: def.state,
|
|
24
|
+
return _jsx(Command, { ...def.props, state: def.state, status: def.status });
|
|
28
25
|
case ComponentName.Plan:
|
|
29
|
-
return (_jsx(Plan, { ...def.props, state: def.state,
|
|
26
|
+
return (_jsx(Plan, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
30
27
|
case ComponentName.Feedback:
|
|
31
|
-
return _jsx(Feedback, { ...def.props });
|
|
28
|
+
return _jsx(Feedback, { ...def.props, status: def.status });
|
|
32
29
|
case ComponentName.Message:
|
|
33
|
-
return _jsx(Message, { ...def.props });
|
|
30
|
+
return _jsx(Message, { ...def.props, status: def.status });
|
|
34
31
|
case ComponentName.Refinement:
|
|
35
|
-
return (_jsx(Refinement, { ...def.props, state: def.state,
|
|
32
|
+
return (_jsx(Refinement, { ...def.props, state: def.state, status: def.status }));
|
|
36
33
|
case ComponentName.Confirm:
|
|
37
|
-
return _jsx(Confirm, { ...def.props, state: def.state,
|
|
34
|
+
return _jsx(Confirm, { ...def.props, state: def.state, status: def.status });
|
|
38
35
|
case ComponentName.Introspect:
|
|
39
|
-
return (_jsx(Introspect, { ...def.props, state: def.state,
|
|
36
|
+
return (_jsx(Introspect, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
40
37
|
case ComponentName.Report:
|
|
41
|
-
return _jsx(Report, { ...def.props });
|
|
38
|
+
return _jsx(Report, { ...def.props, status: def.status });
|
|
42
39
|
case ComponentName.Answer:
|
|
43
|
-
return _jsx(Answer, { ...def.props, state: def.state,
|
|
40
|
+
return _jsx(Answer, { ...def.props, state: def.state, status: def.status });
|
|
44
41
|
case ComponentName.Execute:
|
|
45
|
-
return _jsx(Execute, { ...def.props, state: def.state,
|
|
42
|
+
return _jsx(Execute, { ...def.props, state: def.state, status: def.status });
|
|
46
43
|
case ComponentName.Validate:
|
|
47
|
-
return (_jsx(Validate, { ...def.props, state: def.state,
|
|
44
|
+
return (_jsx(Validate, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
48
45
|
}
|
|
49
46
|
});
|
package/dist/ui/Config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useFocus } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
+
import { ComponentStatus } from '../types/components.js';
|
|
5
6
|
import { Colors } from '../services/colors.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
7
8
|
export var StepType;
|
|
@@ -57,8 +58,8 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
|
|
|
57
58
|
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
|
|
58
59
|
}) }));
|
|
59
60
|
}
|
|
60
|
-
export function Config({ steps, state,
|
|
61
|
-
|
|
61
|
+
export function Config({ steps, state, status, debug, handlers, onFinished, onAborted, }) {
|
|
62
|
+
const isActive = status === ComponentStatus.Active;
|
|
62
63
|
const [step, setStep] = useState(!isActive ? (state?.completedStep ?? steps.length) : 0);
|
|
63
64
|
const [values, setValues] = useState(() => {
|
|
64
65
|
// If not active and we have saved state values, use those
|
|
@@ -90,8 +91,13 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
90
91
|
});
|
|
91
92
|
const [inputValue, setInputValue] = useState('');
|
|
92
93
|
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
// Initialize selectedIndex based on current step's defaultIndex
|
|
95
|
+
if (isActive &&
|
|
96
|
+
step < steps.length &&
|
|
97
|
+
steps[step].type === StepType.Selection) {
|
|
98
|
+
return steps[step].defaultIndex;
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
95
101
|
});
|
|
96
102
|
const normalizeValue = (value) => {
|
|
97
103
|
if (value === null || value === undefined) {
|
|
@@ -99,10 +105,12 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
99
105
|
}
|
|
100
106
|
return value.replace(/\n/g, '').trim();
|
|
101
107
|
};
|
|
102
|
-
useInput((
|
|
103
|
-
if (
|
|
108
|
+
useInput((_, key) => {
|
|
109
|
+
if (!isActive || step >= steps.length)
|
|
110
|
+
return;
|
|
111
|
+
const currentStepConfig = steps[step];
|
|
112
|
+
if (key.escape) {
|
|
104
113
|
// Save current value before aborting
|
|
105
|
-
const currentStepConfig = steps[step];
|
|
106
114
|
if (currentStepConfig) {
|
|
107
115
|
const configKey = currentStepConfig.path || currentStepConfig.key;
|
|
108
116
|
let currentValue = '';
|
|
@@ -111,10 +119,7 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
111
119
|
currentValue = inputValue || values[configKey] || '';
|
|
112
120
|
break;
|
|
113
121
|
case StepType.Selection:
|
|
114
|
-
currentValue =
|
|
115
|
-
currentStepConfig.options[selectedIndex]?.value ||
|
|
116
|
-
values[configKey] ||
|
|
117
|
-
'';
|
|
122
|
+
currentValue = values[configKey] || '';
|
|
118
123
|
break;
|
|
119
124
|
default: {
|
|
120
125
|
const exhaustiveCheck = currentStepConfig;
|
|
@@ -130,24 +135,13 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
130
135
|
}
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
else if (key.return) {
|
|
141
|
-
handleSubmit(currentStep.options[selectedIndex].value);
|
|
142
|
-
}
|
|
143
|
-
break;
|
|
144
|
-
case StepType.Text:
|
|
145
|
-
// Text input handled by TextInput component
|
|
146
|
-
break;
|
|
147
|
-
default: {
|
|
148
|
-
const exhaustiveCheck = currentStep;
|
|
149
|
-
throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
|
|
150
|
-
}
|
|
138
|
+
// Handle selection step navigation
|
|
139
|
+
if (currentStepConfig.type === StepType.Selection) {
|
|
140
|
+
if (key.tab) {
|
|
141
|
+
setSelectedIndex((prev) => (prev + 1) % currentStepConfig.options.length);
|
|
142
|
+
}
|
|
143
|
+
else if (key.return) {
|
|
144
|
+
handleSubmit(currentStepConfig.options[selectedIndex].value);
|
|
151
145
|
}
|
|
152
146
|
}
|
|
153
147
|
});
|
|
@@ -188,35 +182,38 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
188
182
|
setInputValue('');
|
|
189
183
|
if (step === steps.length - 1) {
|
|
190
184
|
// Last step completed
|
|
185
|
+
// IMPORTANT: Update state BEFORE calling onFinished
|
|
186
|
+
// onFinished may call handlers.completeActive(), so state must be saved first
|
|
187
|
+
const stateUpdate = {
|
|
188
|
+
values: newValues,
|
|
189
|
+
completedStep: steps.length,
|
|
190
|
+
};
|
|
191
|
+
handlers?.updateState(stateUpdate);
|
|
192
|
+
// Now call onFinished - this may trigger completeActive()
|
|
191
193
|
if (onFinished) {
|
|
192
194
|
onFinished(newValues);
|
|
193
195
|
}
|
|
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();
|
|
201
196
|
setStep(steps.length);
|
|
202
197
|
}
|
|
203
198
|
else {
|
|
204
199
|
// Save state after each step
|
|
205
|
-
|
|
200
|
+
const stateUpdate = {
|
|
206
201
|
values: newValues,
|
|
207
202
|
completedStep: step + 1,
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
203
|
+
};
|
|
204
|
+
handlers?.updateState(stateUpdate);
|
|
205
|
+
const nextStep = step + 1;
|
|
206
|
+
setStep(nextStep);
|
|
207
|
+
// Reset selectedIndex for next step
|
|
208
|
+
if (nextStep < steps.length &&
|
|
209
|
+
steps[nextStep].type === StepType.Selection) {
|
|
210
|
+
setSelectedIndex(steps[nextStep].defaultIndex);
|
|
214
211
|
}
|
|
215
212
|
}
|
|
216
213
|
};
|
|
217
214
|
const renderStepInput = (stepConfig, isCurrentStep) => {
|
|
218
215
|
const configKey = stepConfig.path || stepConfig.key;
|
|
219
|
-
// Use state values
|
|
216
|
+
// Use state values when inactive, local values when active
|
|
220
217
|
const displayValue = !isActive && state?.values ? state.values[configKey] : values[configKey];
|
|
221
218
|
switch (stepConfig.type) {
|
|
222
219
|
case StepType.Text:
|
|
@@ -226,10 +223,11 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
226
223
|
return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
|
|
227
224
|
case StepType.Selection: {
|
|
228
225
|
if (!isCurrentStep) {
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
// Find the option that matches the saved/current value
|
|
227
|
+
const option = stepConfig.options.find((opt) => opt.value === displayValue);
|
|
228
|
+
return _jsx(Text, { dimColor: true, children: option?.label || '' });
|
|
231
229
|
}
|
|
232
|
-
return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep:
|
|
230
|
+
return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
|
|
233
231
|
}
|
|
234
232
|
default: {
|
|
235
233
|
const exhaustiveCheck = stepConfig;
|
package/dist/ui/Confirm.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
4
5
|
import { Colors, Palette } from '../services/colors.js';
|
|
5
6
|
import { useInput } from '../services/keyboard.js';
|
|
6
7
|
import { UserQuery } from './UserQuery.js';
|
|
7
|
-
export function Confirm({ message, state,
|
|
8
|
-
|
|
8
|
+
export function Confirm({ message, state, status, handlers, onConfirmed, onCancelled, }) {
|
|
9
|
+
const isActive = status === ComponentStatus.Active;
|
|
9
10
|
const [selectedIndex, setSelectedIndex] = useState(state?.selectedIndex ?? 0); // 0 = Yes, 1 = No
|
|
10
11
|
useInput((input, key) => {
|
|
11
12
|
if (!isActive)
|
|
@@ -18,11 +19,9 @@ export function Confirm({ message, state, isActive = true, handlers, onConfirmed
|
|
|
18
19
|
}
|
|
19
20
|
else if (key.tab) {
|
|
20
21
|
// Toggle between Yes (0) and No (1)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return newIndex;
|
|
25
|
-
});
|
|
22
|
+
const newIndex = selectedIndex === 0 ? 1 : 0;
|
|
23
|
+
setSelectedIndex(newIndex);
|
|
24
|
+
handlers?.updateState({ selectedIndex: newIndex });
|
|
26
25
|
}
|
|
27
26
|
else if (key.return) {
|
|
28
27
|
// Confirm selection
|
package/dist/ui/Execute.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { ComponentStatus } from '../types/components.js';
|
|
4
5
|
import { Colors, getTextColor, Palette } from '../services/colors.js';
|
|
5
6
|
import { useInput } from '../services/keyboard.js';
|
|
6
7
|
import { formatErrorMessage } from '../services/messages.js';
|
|
@@ -85,7 +86,8 @@ function CommandStatusDisplay({ item, elapsed }) {
|
|
|
85
86
|
const elapsedTime = getElapsedTime();
|
|
86
87
|
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
88
|
}
|
|
88
|
-
export function Execute({ tasks, state,
|
|
89
|
+
export function Execute({ tasks, state, status, service, handlers, }) {
|
|
90
|
+
const isActive = status === ComponentStatus.Active;
|
|
89
91
|
// isActive passed as prop
|
|
90
92
|
const [error, setError] = useState(state?.error ?? null);
|
|
91
93
|
const [isExecuting, setIsExecuting] = useState(false);
|
|
@@ -157,7 +159,7 @@ export function Execute({ tasks, state, isActive = true, service, handlers, }) {
|
|
|
157
159
|
commandStatuses,
|
|
158
160
|
error,
|
|
159
161
|
});
|
|
160
|
-
handlers?.
|
|
162
|
+
handlers?.completeActive();
|
|
161
163
|
}, [isExecuting, commandStatuses, outputs, handlers, message, error]);
|
|
162
164
|
useEffect(() => {
|
|
163
165
|
if (!isActive) {
|
|
@@ -197,7 +199,7 @@ export function Execute({ tasks, state, isActive = true, service, handlers, }) {
|
|
|
197
199
|
message: result.message,
|
|
198
200
|
commandStatuses: [],
|
|
199
201
|
});
|
|
200
|
-
handlers?.
|
|
202
|
+
handlers?.completeActive();
|
|
201
203
|
return;
|
|
202
204
|
}
|
|
203
205
|
// Resolve placeholders in command strings before execution
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
4
5
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
5
6
|
import { createReportDefinition } from '../services/components.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
@@ -42,7 +43,8 @@ function parseCapabilityFromTask(task) {
|
|
|
42
43
|
isIndirect,
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
|
-
export function Introspect({ tasks, state,
|
|
46
|
+
export function Introspect({ tasks, state, status, service, children, debug = false, handlers, }) {
|
|
47
|
+
const isActive = status === ComponentStatus.Active;
|
|
46
48
|
// isActive passed as prop
|
|
47
49
|
const [error, setError] = useState(null);
|
|
48
50
|
useInput((input, key) => {
|
|
@@ -86,7 +88,7 @@ export function Introspect({ tasks, state, isActive = true, service, children, d
|
|
|
86
88
|
// Add Report component to queue
|
|
87
89
|
handlers?.addToQueue(createReportDefinition(result.message, capabilities));
|
|
88
90
|
// Signal completion
|
|
89
|
-
handlers?.
|
|
91
|
+
handlers?.completeActive();
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
catch (err) {
|
package/dist/ui/Plan.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box } from 'ink';
|
|
4
|
+
import { ComponentStatus } from '../types/components.js';
|
|
4
5
|
import { TaskType } from '../types/types.js';
|
|
5
6
|
import { getTaskColors } from '../services/colors.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
@@ -49,7 +50,8 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
49
50
|
}
|
|
50
51
|
return item;
|
|
51
52
|
}
|
|
52
|
-
export function Plan({ message, tasks, state,
|
|
53
|
+
export function Plan({ message, tasks, state, status, debug = false, handlers, onSelectionConfirmed, }) {
|
|
54
|
+
const isActive = status === ComponentStatus.Active;
|
|
53
55
|
// isActive passed as prop
|
|
54
56
|
const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
|
|
55
57
|
const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(state?.currentDefineGroupIndex ?? 0);
|
|
@@ -70,9 +72,10 @@ export function Plan({ message, tasks, state, isActive = true, debug = false, ha
|
|
|
70
72
|
if (isActive && defineTaskIndices.length === 0 && onSelectionConfirmed) {
|
|
71
73
|
// No selection needed - all tasks are concrete
|
|
72
74
|
const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
75
|
+
// Complete the selection phase - it goes to timeline
|
|
76
|
+
// Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
|
|
77
|
+
handlers?.completeActive();
|
|
73
78
|
onSelectionConfirmed(concreteTasks);
|
|
74
|
-
// Signal Plan completion after adding Confirm to queue
|
|
75
|
-
handlers?.onComplete();
|
|
76
79
|
}
|
|
77
80
|
}, [
|
|
78
81
|
isActive,
|
|
@@ -143,14 +146,14 @@ export function Plan({ message, tasks, state, isActive = true, debug = false, ha
|
|
|
143
146
|
}
|
|
144
147
|
});
|
|
145
148
|
if (onSelectionConfirmed) {
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
handlers?.
|
|
149
|
+
// Complete the selection phase - it goes to timeline
|
|
150
|
+
// Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
|
|
151
|
+
handlers?.completeActive();
|
|
149
152
|
onSelectionConfirmed(refinedTasks);
|
|
150
153
|
}
|
|
151
154
|
else {
|
|
152
155
|
// No selection callback, just complete normally
|
|
153
|
-
handlers?.
|
|
156
|
+
handlers?.completeActive();
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
}
|
package/dist/ui/Refinement.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box } from 'ink';
|
|
3
|
+
import { ComponentStatus } from '../types/components.js';
|
|
3
4
|
import { useInput } from '../services/keyboard.js';
|
|
4
5
|
import { Message } from './Message.js';
|
|
5
6
|
import { Spinner } from './Spinner.js';
|
|
6
|
-
export const Refinement = ({ text,
|
|
7
|
+
export const Refinement = ({ text, status, onAborted }) => {
|
|
8
|
+
const isActive = status === ComponentStatus.Active;
|
|
7
9
|
useInput((_, key) => {
|
|
8
10
|
if (key.escape && isActive) {
|
|
9
11
|
onAborted('plan refinement');
|
package/dist/ui/Validate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
+
import { ComponentStatus } from '../types/components.js';
|
|
4
5
|
import { TaskType } from '../types/types.js';
|
|
5
6
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
@@ -10,8 +11,8 @@ import { saveConfig, unflattenConfig } from '../services/configuration.js';
|
|
|
10
11
|
import { Config, StepType } from './Config.js';
|
|
11
12
|
import { Spinner } from './Spinner.js';
|
|
12
13
|
const MIN_PROCESSING_TIME = 1000;
|
|
13
|
-
export function Validate({ missingConfig, userRequest, state,
|
|
14
|
-
|
|
14
|
+
export function Validate({ missingConfig, userRequest, state, status, service, children, debug, onError, onComplete, onAborted, handlers, }) {
|
|
15
|
+
const isActive = status === ComponentStatus.Active;
|
|
15
16
|
const [error, setError] = useState(null);
|
|
16
17
|
const [completionMessage, setCompletionMessage] = useState(null);
|
|
17
18
|
const [configRequirements, setConfigRequirements] = useState(null);
|
|
@@ -132,7 +133,7 @@ export function Validate({ missingConfig, userRequest, state, isActive = true, s
|
|
|
132
133
|
const handleConfigAborted = (operation) => {
|
|
133
134
|
onAborted(operation);
|
|
134
135
|
};
|
|
135
|
-
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !completionMessage && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: getTextColor(isActive), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), configSteps && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps,
|
|
136
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !completionMessage && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: getTextColor(isActive), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), configSteps && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps, status: status, debug: debug, onFinished: handleConfigFinished, onAborted: handleConfigAborted, handlers: handlers }) })), children] }));
|
|
136
137
|
}
|
|
137
138
|
/**
|
|
138
139
|
* Build prompt for VALIDATE tool
|
package/dist/ui/Workflow.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { Box, Static } from 'ink';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
4
5
|
import { ComponentName, FeedbackType } from '../types/types.js';
|
|
5
6
|
import { createFeedback, isStateless, markAsDone, } from '../services/components.js';
|
|
6
7
|
import { exitApp } from '../services/process.js';
|
|
@@ -8,26 +9,82 @@ import { getCancellationMessage } from '../services/messages.js';
|
|
|
8
9
|
import { Component } from './Component.js';
|
|
9
10
|
export const Workflow = ({ initialQueue, debug }) => {
|
|
10
11
|
const [timeline, setTimeline] = useState([]);
|
|
11
|
-
const [
|
|
12
|
+
const [current, setCurrent] = useState({ active: null, pending: null });
|
|
12
13
|
const [queue, setQueue] = useState(initialQueue);
|
|
13
|
-
// Function to move active component
|
|
14
|
+
// Function to move active to pending (component just completed)
|
|
15
|
+
const moveActiveToPending = useCallback(() => {
|
|
16
|
+
setCurrent((curr) => {
|
|
17
|
+
const { active } = curr;
|
|
18
|
+
if (!active)
|
|
19
|
+
return curr;
|
|
20
|
+
// Move active to pending without marking as done
|
|
21
|
+
const pendingComponent = { ...active, status: ComponentStatus.Pending };
|
|
22
|
+
return { active: null, pending: pendingComponent };
|
|
23
|
+
});
|
|
24
|
+
}, []);
|
|
25
|
+
// Function to move active directly to timeline (error/abort)
|
|
14
26
|
const moveActiveToTimeline = useCallback(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
setCurrent((curr) => {
|
|
28
|
+
const { active, pending } = curr;
|
|
29
|
+
if (!active)
|
|
30
|
+
return curr;
|
|
31
|
+
// Mark as done and add to timeline
|
|
32
|
+
const doneComponent = markAsDone(active);
|
|
19
33
|
setTimeline((prev) => [...prev, doneComponent]);
|
|
20
|
-
return null;
|
|
34
|
+
return { active: null, pending };
|
|
21
35
|
});
|
|
22
36
|
}, []);
|
|
23
37
|
// Global handlers for all stateful components
|
|
24
38
|
const handlers = useMemo(() => ({
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
addToQueue: (...items) => {
|
|
40
|
+
setQueue((queue) => [...queue, ...items]);
|
|
41
|
+
},
|
|
42
|
+
updateState: (newState) => {
|
|
43
|
+
setCurrent((curr) => {
|
|
44
|
+
const { active, pending } = curr;
|
|
45
|
+
if (!active || !('state' in active))
|
|
46
|
+
return curr;
|
|
47
|
+
const stateful = active;
|
|
48
|
+
const updated = {
|
|
49
|
+
...stateful,
|
|
50
|
+
state: {
|
|
51
|
+
...stateful.state,
|
|
52
|
+
...newState,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
return { active: updated, pending };
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
completeActive: (...items) => {
|
|
59
|
+
moveActiveToPending();
|
|
60
|
+
if (items.length > 0) {
|
|
61
|
+
setQueue((queue) => [...items, ...queue]);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
completeActiveAndPending: (...items) => {
|
|
65
|
+
setCurrent((curr) => {
|
|
66
|
+
const { active, pending } = curr;
|
|
67
|
+
// Move both to timeline - pending first (Plan), then active (Confirm)
|
|
68
|
+
if (pending) {
|
|
69
|
+
const donePending = markAsDone(pending);
|
|
70
|
+
setTimeline((prev) => [...prev, donePending]);
|
|
71
|
+
}
|
|
72
|
+
if (active) {
|
|
73
|
+
const doneActive = markAsDone(active);
|
|
74
|
+
setTimeline((prev) => [...prev, doneActive]);
|
|
75
|
+
}
|
|
76
|
+
return { active: null, pending: null };
|
|
77
|
+
});
|
|
78
|
+
if (items.length > 0) {
|
|
79
|
+
setQueue((queue) => [...items, ...queue]);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
addToTimeline: (...items) => {
|
|
83
|
+
setTimeline((prev) => [...prev, ...items]);
|
|
27
84
|
},
|
|
28
85
|
onAborted: (operation) => {
|
|
29
86
|
moveActiveToTimeline();
|
|
30
|
-
// Add feedback to queue
|
|
87
|
+
// Add feedback to queue
|
|
31
88
|
const message = getCancellationMessage(operation);
|
|
32
89
|
setQueue((queue) => [
|
|
33
90
|
...queue,
|
|
@@ -36,73 +93,82 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
36
93
|
},
|
|
37
94
|
onError: (error) => {
|
|
38
95
|
moveActiveToTimeline();
|
|
39
|
-
// Add feedback to queue
|
|
96
|
+
// Add feedback to queue
|
|
40
97
|
setQueue((queue) => [
|
|
41
98
|
...queue,
|
|
42
99
|
createFeedback(FeedbackType.Failed, error),
|
|
43
100
|
]);
|
|
44
101
|
},
|
|
45
|
-
|
|
46
|
-
setQueue((queue) => [...queue, ...items]);
|
|
47
|
-
},
|
|
48
|
-
addToTimeline: (...items) => {
|
|
49
|
-
setTimeline((prev) => [...prev, ...items]);
|
|
50
|
-
},
|
|
51
|
-
completeActive: () => {
|
|
52
|
-
moveActiveToTimeline();
|
|
53
|
-
},
|
|
54
|
-
updateState: (newState) => {
|
|
55
|
-
setActive((curr) => {
|
|
56
|
-
if (!curr || !('state' in curr))
|
|
57
|
-
return curr;
|
|
58
|
-
const stateful = curr;
|
|
59
|
-
return {
|
|
60
|
-
...stateful,
|
|
61
|
-
state: {
|
|
62
|
-
...stateful.state,
|
|
63
|
-
...newState,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
},
|
|
68
|
-
}), [moveActiveToTimeline]);
|
|
102
|
+
}), [moveActiveToPending, moveActiveToTimeline]);
|
|
69
103
|
// Global Esc handler removed - components handle their own Esc individually
|
|
70
104
|
// Move next item from queue to active
|
|
71
105
|
useEffect(() => {
|
|
72
|
-
|
|
73
|
-
|
|
106
|
+
const { active, pending } = current;
|
|
107
|
+
// Early return: not ready to activate next
|
|
108
|
+
if (queue.length === 0 || active !== null) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const [first, ...rest] = queue;
|
|
112
|
+
const activeComponent = { ...first, status: ComponentStatus.Active };
|
|
113
|
+
// Confirm - keep pending visible (Plan showing what will execute)
|
|
114
|
+
if (first.name === ComponentName.Confirm) {
|
|
74
115
|
setQueue(rest);
|
|
75
|
-
|
|
116
|
+
setCurrent({ active: activeComponent, pending });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Other components - move pending to timeline first, then activate
|
|
120
|
+
if (pending) {
|
|
121
|
+
const donePending = markAsDone(pending);
|
|
122
|
+
setTimeline((prev) => [...prev, donePending]);
|
|
76
123
|
}
|
|
77
|
-
|
|
124
|
+
setQueue(rest);
|
|
125
|
+
setCurrent({ active: activeComponent, pending: null });
|
|
126
|
+
}, [queue, current]);
|
|
78
127
|
// Process active component - stateless components auto-move to timeline
|
|
79
128
|
useEffect(() => {
|
|
129
|
+
const { active, pending } = current;
|
|
80
130
|
if (!active)
|
|
81
131
|
return;
|
|
82
132
|
if (isStateless(active)) {
|
|
133
|
+
// Stateless components move directly to timeline
|
|
83
134
|
const doneComponent = markAsDone(active);
|
|
84
135
|
setTimeline((prev) => [...prev, doneComponent]);
|
|
85
|
-
|
|
136
|
+
setCurrent({ active: null, pending });
|
|
86
137
|
}
|
|
87
|
-
// Stateful components stay in active until handlers move them to
|
|
88
|
-
}, [
|
|
89
|
-
//
|
|
138
|
+
// Stateful components stay in active until handlers move them to pending
|
|
139
|
+
}, [current]);
|
|
140
|
+
// Move final pending to timeline and exit when all done
|
|
90
141
|
useEffect(() => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
142
|
+
const { active, pending } = current;
|
|
143
|
+
// Early return: not ready to finish
|
|
144
|
+
if (active !== null || queue.length > 0) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Handle pending component
|
|
148
|
+
if (pending) {
|
|
149
|
+
const donePending = markAsDone(pending);
|
|
150
|
+
setTimeline((prev) => [...prev, donePending]);
|
|
151
|
+
setCurrent({ active: null, pending: null });
|
|
152
|
+
return;
|
|
97
153
|
}
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
// Early return: nothing to exit with
|
|
155
|
+
if (timeline.length === 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Everything is done, exit
|
|
159
|
+
const lastItem = timeline[timeline.length - 1];
|
|
160
|
+
const isFailed = lastItem.name === ComponentName.Feedback &&
|
|
161
|
+
lastItem.props.type === FeedbackType.Failed;
|
|
162
|
+
exitApp(isFailed ? 1 : 0);
|
|
163
|
+
}, [current, queue, timeline]);
|
|
164
|
+
// Render active and pending components
|
|
100
165
|
const activeComponent = useMemo(() => {
|
|
166
|
+
const { active } = current;
|
|
101
167
|
if (!active)
|
|
102
168
|
return null;
|
|
103
|
-
// For stateless components, render as-is
|
|
169
|
+
// For stateless components, render as-is
|
|
104
170
|
if (isStateless(active)) {
|
|
105
|
-
return
|
|
171
|
+
return _jsx(Component, { def: active, debug: debug }, active.id);
|
|
106
172
|
}
|
|
107
173
|
// For stateful components, inject global handlers
|
|
108
174
|
const statefulActive = active;
|
|
@@ -113,7 +179,14 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
113
179
|
handlers,
|
|
114
180
|
},
|
|
115
181
|
};
|
|
116
|
-
return
|
|
117
|
-
}, [
|
|
118
|
-
|
|
182
|
+
return _jsx(Component, { def: wrappedDef, debug: debug }, active.id);
|
|
183
|
+
}, [current, debug, handlers]);
|
|
184
|
+
const pendingComponent = useMemo(() => {
|
|
185
|
+
const { pending } = current;
|
|
186
|
+
if (!pending)
|
|
187
|
+
return null;
|
|
188
|
+
// Pending components don't receive input
|
|
189
|
+
return _jsx(Component, { def: pending, debug: debug }, pending.id);
|
|
190
|
+
}, [current, debug]);
|
|
191
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => (_jsx(Box, { marginTop: 1, children: _jsx(Component, { def: item, debug: false }) }, item.id)) }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
|
|
119
192
|
};
|