prompt-language-shell 1.0.2 → 1.0.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/Main.js +14 -1
- package/dist/components/Component.js +6 -0
- package/dist/components/controllers/Config.js +62 -128
- package/dist/components/controllers/Confirm.js +1 -1
- package/dist/components/controllers/Execute.js +50 -10
- package/dist/components/controllers/Schedule.js +6 -14
- package/dist/components/controllers/Setting.js +66 -0
- package/dist/components/views/Config.js +9 -96
- package/dist/components/views/Confirm.js +1 -1
- package/dist/components/views/Execute.js +3 -2
- package/dist/components/views/Help.js +27 -0
- package/dist/components/views/Learn.js +1 -1
- package/dist/components/views/Output.js +50 -6
- package/dist/components/views/Setting.js +88 -0
- package/dist/components/views/StdinInput.js +19 -0
- package/dist/components/views/Welcome.js +4 -11
- package/dist/execution/runner.js +10 -5
- package/dist/services/colors.js +12 -16
- package/dist/services/components.js +4 -0
- package/dist/services/monitor.js +25 -7
- package/dist/services/shell.js +42 -6
- package/dist/skills/introspect.md +20 -18
- package/dist/tools/introspect.tool.js +3 -3
- package/dist/types/components.js +6 -0
- package/dist/types/types.js +1 -0
- package/package.json +1 -1
- package/dist/components/views/Panel.js +0 -5
package/dist/Main.js
CHANGED
|
@@ -8,10 +8,19 @@ import { getMissingConfigKeys } from './configuration/schema.js';
|
|
|
8
8
|
import { createConfigStepsFromSchema } from './configuration/steps.js';
|
|
9
9
|
import { unflattenConfig } from './configuration/transformation.js';
|
|
10
10
|
import { createAnthropicService } from './services/anthropic.js';
|
|
11
|
-
import { createCommand, createConfig, createFeedback, createMessage, createWelcome, } from './services/components.js';
|
|
11
|
+
import { createCommand, createConfig, createFeedback, createHelp, createMessage, createWelcome, } from './services/components.js';
|
|
12
12
|
import { registerGlobalShortcut } from './services/keyboard.js';
|
|
13
13
|
import { initializeLogger, setDebugLevel } from './services/logger.js';
|
|
14
14
|
import { Workflow } from './components/Workflow.js';
|
|
15
|
+
/**
|
|
16
|
+
* Check if command matches help variations (help, --help, -h)
|
|
17
|
+
*/
|
|
18
|
+
function isHelpCommand(cmd) {
|
|
19
|
+
if (!cmd)
|
|
20
|
+
return false;
|
|
21
|
+
const normalized = cmd.toLowerCase().trim();
|
|
22
|
+
return (normalized === 'help' || normalized === '--help' || normalized === '-h');
|
|
23
|
+
}
|
|
15
24
|
export const Main = ({ app, command, serviceFactory = createAnthropicService, }) => {
|
|
16
25
|
const [service, setService] = useState(null);
|
|
17
26
|
const [initialQueue, setInitialQueue] = useState(null);
|
|
@@ -106,6 +115,10 @@ export const Main = ({ app, command, serviceFactory = createAnthropicService, })
|
|
|
106
115
|
}),
|
|
107
116
|
]);
|
|
108
117
|
}
|
|
118
|
+
else if (service && isHelpCommand(command)) {
|
|
119
|
+
// Help command - show help screen directly (no LLM)
|
|
120
|
+
setInitialQueue([createHelp({ app })]);
|
|
121
|
+
}
|
|
109
122
|
else if (service && command) {
|
|
110
123
|
// Valid service exists and command provided - execute command
|
|
111
124
|
setInitialQueue([createCommand({ command, service })]);
|
|
@@ -16,6 +16,7 @@ import { Refinement, RefinementView } from './controllers/Refinement.js';
|
|
|
16
16
|
import { Report } from './views/Report.js';
|
|
17
17
|
import { Schedule, ScheduleView } from './controllers/Schedule.js';
|
|
18
18
|
import { Validate, ValidateView } from './controllers/Validate.js';
|
|
19
|
+
import { Help } from './views/Help.js';
|
|
19
20
|
import { Welcome } from './views/Welcome.js';
|
|
20
21
|
/**
|
|
21
22
|
* Render a simple component (no lifecycle management)
|
|
@@ -26,6 +27,10 @@ export const SimpleComponent = memo(function SimpleComponent({ def, }) {
|
|
|
26
27
|
const { props, status } = def;
|
|
27
28
|
return _jsx(Welcome, { ...props, status: status });
|
|
28
29
|
}
|
|
30
|
+
case ComponentName.Help: {
|
|
31
|
+
const { props, status } = def;
|
|
32
|
+
return _jsx(Help, { ...props, status: status });
|
|
33
|
+
}
|
|
29
34
|
case ComponentName.Feedback: {
|
|
30
35
|
const { props, status } = def;
|
|
31
36
|
return _jsx(Feedback, { ...props, status: status });
|
|
@@ -157,6 +162,7 @@ export const TimelineComponent = ({ def, }) => {
|
|
|
157
162
|
switch (def.name) {
|
|
158
163
|
// Simple components render as-is
|
|
159
164
|
case ComponentName.Welcome:
|
|
165
|
+
case ComponentName.Help:
|
|
160
166
|
case ComponentName.Feedback:
|
|
161
167
|
case ComponentName.Message:
|
|
162
168
|
case ComponentName.Debug:
|
|
@@ -8,9 +8,31 @@ import { createConfigStepsFromSchema } from '../../configuration/steps.js';
|
|
|
8
8
|
import { saveConfigLabels } from '../../configuration/labels.js';
|
|
9
9
|
import { DebugLevel } from '../../configuration/types.js';
|
|
10
10
|
import { useInput } from '../../services/keyboard.js';
|
|
11
|
-
import {
|
|
11
|
+
import { StepType } from '../views/Config.js';
|
|
12
|
+
import { Setting } from './Setting.js';
|
|
12
13
|
import { Spinner } from '../views/Spinner.js';
|
|
13
14
|
export { ConfigView, StepType, } from '../views/Config.js';
|
|
15
|
+
function isTextStep(step) {
|
|
16
|
+
return step.type === StepType.Text;
|
|
17
|
+
}
|
|
18
|
+
function isSelectionStep(step) {
|
|
19
|
+
return step.type === StepType.Selection;
|
|
20
|
+
}
|
|
21
|
+
function initializeStepValues(steps) {
|
|
22
|
+
const values = {};
|
|
23
|
+
for (const step of steps) {
|
|
24
|
+
const configKey = step.path || step.key;
|
|
25
|
+
if (isTextStep(step)) {
|
|
26
|
+
if (step.value !== null) {
|
|
27
|
+
values[configKey] = step.value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (isSelectionStep(step)) {
|
|
31
|
+
values[configKey] = step.options[step.defaultIndex].value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return values;
|
|
35
|
+
}
|
|
14
36
|
/**
|
|
15
37
|
* Resolve query to config steps via CONFIGURE tool
|
|
16
38
|
*/
|
|
@@ -48,61 +70,19 @@ export function Config(props) {
|
|
|
48
70
|
const isActive = status === ComponentStatus.Active;
|
|
49
71
|
const [steps, setSteps] = useState(initialSteps || []);
|
|
50
72
|
const [resolving, setResolving] = useState(!initialSteps?.length && !!query);
|
|
51
|
-
const [
|
|
52
|
-
const [values, setValues] = useState(() =>
|
|
53
|
-
const initial = {};
|
|
54
|
-
(initialSteps || []).forEach((stepConfig) => {
|
|
55
|
-
const configKey = stepConfig.path || stepConfig.key;
|
|
56
|
-
switch (stepConfig.type) {
|
|
57
|
-
case StepType.Text:
|
|
58
|
-
if (stepConfig.value !== null) {
|
|
59
|
-
initial[configKey] = stepConfig.value;
|
|
60
|
-
}
|
|
61
|
-
break;
|
|
62
|
-
case StepType.Selection:
|
|
63
|
-
initial[configKey] =
|
|
64
|
-
stepConfig.options[stepConfig.defaultIndex].value;
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
return initial;
|
|
69
|
-
});
|
|
70
|
-
const [inputValue, setInputValue] = useState('');
|
|
71
|
-
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
72
|
-
if (!initialSteps?.length)
|
|
73
|
-
return 0;
|
|
74
|
-
const first = initialSteps[0];
|
|
75
|
-
return first.type === StepType.Selection ? first.defaultIndex : 0;
|
|
76
|
-
});
|
|
77
|
-
// Resolve query to steps
|
|
73
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
74
|
+
const [values, setValues] = useState(() => initializeStepValues(initialSteps || []));
|
|
78
75
|
useEffect(() => {
|
|
79
76
|
if (!isActive || !query || !service || initialSteps?.length)
|
|
80
77
|
return;
|
|
81
78
|
resolveQueryToSteps(query, service)
|
|
82
79
|
.then((result) => {
|
|
83
|
-
// Add debug components to timeline if present
|
|
84
80
|
if (result.debug.length) {
|
|
85
81
|
workflowHandlers.addToTimeline(...result.debug);
|
|
86
82
|
}
|
|
87
83
|
setSteps(result.steps);
|
|
88
84
|
setResolving(false);
|
|
89
|
-
|
|
90
|
-
const initial = {};
|
|
91
|
-
result.steps.forEach((stepConfig) => {
|
|
92
|
-
const configKey = stepConfig.path || stepConfig.key;
|
|
93
|
-
switch (stepConfig.type) {
|
|
94
|
-
case StepType.Text:
|
|
95
|
-
if (stepConfig.value !== null) {
|
|
96
|
-
initial[configKey] = stepConfig.value;
|
|
97
|
-
}
|
|
98
|
-
break;
|
|
99
|
-
case StepType.Selection:
|
|
100
|
-
initial[configKey] =
|
|
101
|
-
stepConfig.options[stepConfig.defaultIndex].value;
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
setValues(initial);
|
|
85
|
+
setValues(initializeStepValues(result.steps));
|
|
106
86
|
})
|
|
107
87
|
.catch((err) => {
|
|
108
88
|
setResolving(false);
|
|
@@ -119,96 +99,39 @@ export function Config(props) {
|
|
|
119
99
|
lifecycleHandlers,
|
|
120
100
|
workflowHandlers,
|
|
121
101
|
]);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
102
|
+
const handleEscape = () => {
|
|
103
|
+
requestHandlers.onCompleted({
|
|
104
|
+
values,
|
|
105
|
+
completedStep: currentStep,
|
|
106
|
+
selectedIndex: 0,
|
|
107
|
+
steps,
|
|
108
|
+
});
|
|
109
|
+
if (onAborted) {
|
|
110
|
+
onAborted('configuration');
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
lifecycleHandlers.completeActive(createFeedback({
|
|
114
|
+
type: FeedbackType.Aborted,
|
|
115
|
+
message: 'Configuration cancelled.',
|
|
116
|
+
}));
|
|
131
117
|
}
|
|
132
|
-
}, [step, isActive, steps, values]);
|
|
133
|
-
const normalizeValue = (value) => {
|
|
134
|
-
if (value === null || value === undefined)
|
|
135
|
-
return '';
|
|
136
|
-
return value.replace(/\n/g, '').trim();
|
|
137
118
|
};
|
|
138
119
|
useInput((_, key) => {
|
|
139
|
-
if (!isActive ||
|
|
120
|
+
if (!isActive || currentStep >= steps.length)
|
|
140
121
|
return;
|
|
141
|
-
const currentStepConfig = steps[step];
|
|
142
122
|
if (key.escape) {
|
|
143
|
-
|
|
144
|
-
let currentValue = '';
|
|
145
|
-
switch (currentStepConfig.type) {
|
|
146
|
-
case StepType.Text:
|
|
147
|
-
currentValue = inputValue || values[configKey] || '';
|
|
148
|
-
break;
|
|
149
|
-
case StepType.Selection:
|
|
150
|
-
currentValue = values[configKey] || '';
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
const finalValues = currentValue
|
|
154
|
-
? { ...values, [configKey]: currentValue }
|
|
155
|
-
: values;
|
|
156
|
-
requestHandlers.onCompleted({
|
|
157
|
-
values: finalValues,
|
|
158
|
-
completedStep: step,
|
|
159
|
-
selectedIndex,
|
|
160
|
-
steps,
|
|
161
|
-
});
|
|
162
|
-
if (onAborted) {
|
|
163
|
-
onAborted('configuration');
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
lifecycleHandlers.completeActive(createFeedback({
|
|
167
|
-
type: FeedbackType.Aborted,
|
|
168
|
-
message: 'Configuration cancelled.',
|
|
169
|
-
}));
|
|
170
|
-
}
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (currentStepConfig.type === StepType.Selection) {
|
|
174
|
-
if (key.tab) {
|
|
175
|
-
setSelectedIndex((prev) => (prev + 1) % currentStepConfig.options.length);
|
|
176
|
-
}
|
|
177
|
-
else if (key.return) {
|
|
178
|
-
handleSubmit(currentStepConfig.options[selectedIndex].value);
|
|
179
|
-
}
|
|
123
|
+
handleEscape();
|
|
180
124
|
}
|
|
181
125
|
}, { isActive });
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
switch (currentStepConfig.type) {
|
|
186
|
-
case StepType.Selection:
|
|
187
|
-
finalValue = value;
|
|
188
|
-
break;
|
|
189
|
-
case StepType.Text: {
|
|
190
|
-
const normalizedInput = normalizeValue(value);
|
|
191
|
-
if (normalizedInput && currentStepConfig.validate(normalizedInput)) {
|
|
192
|
-
finalValue = normalizedInput;
|
|
193
|
-
}
|
|
194
|
-
else if (currentStepConfig.value &&
|
|
195
|
-
currentStepConfig.validate(currentStepConfig.value)) {
|
|
196
|
-
finalValue = currentStepConfig.value;
|
|
197
|
-
}
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (!finalValue)
|
|
202
|
-
return;
|
|
203
|
-
const configKey = currentStepConfig.path || currentStepConfig.key;
|
|
204
|
-
const newValues = { ...values, [configKey]: finalValue };
|
|
126
|
+
const handleEntrySubmit = (stepConfig, value) => {
|
|
127
|
+
const configKey = stepConfig.path || stepConfig.key;
|
|
128
|
+
const newValues = { ...values, [configKey]: value };
|
|
205
129
|
setValues(newValues);
|
|
206
|
-
|
|
207
|
-
if (step === steps.length - 1) {
|
|
130
|
+
if (currentStep === steps.length - 1) {
|
|
208
131
|
requestHandlers.onCompleted({
|
|
209
132
|
values: newValues,
|
|
210
133
|
completedStep: steps.length,
|
|
211
|
-
selectedIndex,
|
|
134
|
+
selectedIndex: 0,
|
|
212
135
|
steps,
|
|
213
136
|
});
|
|
214
137
|
try {
|
|
@@ -224,14 +147,25 @@ export function Config(props) {
|
|
|
224
147
|
message: error instanceof Error ? error.message : 'Configuration failed',
|
|
225
148
|
}));
|
|
226
149
|
}
|
|
227
|
-
|
|
150
|
+
setCurrentStep(steps.length);
|
|
228
151
|
}
|
|
229
152
|
else {
|
|
230
|
-
|
|
153
|
+
setCurrentStep(currentStep + 1);
|
|
231
154
|
}
|
|
232
155
|
};
|
|
233
156
|
if (resolving) {
|
|
234
157
|
return (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { children: "Resolving configuration... " }), _jsx(Spinner, {})] }));
|
|
235
158
|
}
|
|
236
|
-
return (_jsx(
|
|
159
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
|
|
160
|
+
const isStepActive = index === currentStep && isActive;
|
|
161
|
+
const isCompleted = index < currentStep;
|
|
162
|
+
const wasAborted = index === currentStep && !isActive;
|
|
163
|
+
const shouldShow = isCompleted || isStepActive || wasAborted;
|
|
164
|
+
if (!shouldShow)
|
|
165
|
+
return null;
|
|
166
|
+
const configKey = stepConfig.path || stepConfig.key;
|
|
167
|
+
return (_jsx(Box, { marginTop: index === 0 ? 0 : 1, children: _jsx(Setting, { step: stepConfig, initialValue: values[configKey] || '', isActive: isStepActive, debug: debug, onSubmit: (value) => {
|
|
168
|
+
handleEntrySubmit(stepConfig, value);
|
|
169
|
+
} }) }, configKey));
|
|
170
|
+
}) }));
|
|
237
171
|
}
|
|
@@ -19,7 +19,7 @@ export function Confirm({ message, status, requestHandlers, onConfirmed, onCance
|
|
|
19
19
|
requestHandlers.onCompleted(finalState);
|
|
20
20
|
onCancelled();
|
|
21
21
|
}
|
|
22
|
-
else if (key.tab) {
|
|
22
|
+
else if (key.tab || key.leftArrow || key.rightArrow) {
|
|
23
23
|
// Toggle between Yes (0) and No (1)
|
|
24
24
|
setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
|
|
25
25
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
3
|
-
import { ComponentStatus, } from '../../types/components.js';
|
|
2
|
+
import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
|
|
3
|
+
import { ComponentStatus, OutputSource, } from '../../types/components.js';
|
|
4
4
|
import { useInput } from '../../services/keyboard.js';
|
|
5
5
|
import { formatErrorMessage, getExecutionErrorMessage, } from '../../services/messages.js';
|
|
6
|
-
import { ExecutionStatus } from '../../services/shell.js';
|
|
6
|
+
import { ExecutionStatus, killCurrentProcess, writeStdin, } from '../../services/shell.js';
|
|
7
7
|
import { ELAPSED_UPDATE_INTERVAL, ensureMinimumTime, } from '../../services/timing.js';
|
|
8
8
|
import { handleTaskCompletion, handleTaskFailure, } from '../../execution/handlers.js';
|
|
9
9
|
import { processTasks } from '../../execution/processing.js';
|
|
@@ -43,6 +43,9 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
43
43
|
});
|
|
44
44
|
// Ref to track if current task execution is cancelled
|
|
45
45
|
const cancelledRef = useRef(false);
|
|
46
|
+
// Toggle stdin input visibility with / key
|
|
47
|
+
const [showStdinInput, setShowStdinInput] = useState(false);
|
|
48
|
+
const showStdinInputRef = useRef(false);
|
|
46
49
|
const { error, tasks, message, hasProcessed, completionMessage, summary } = localState;
|
|
47
50
|
// Derive current task index from tasks
|
|
48
51
|
const currentTaskIndex = getCurrentTaskIndex(tasks);
|
|
@@ -75,9 +78,17 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
75
78
|
clearInterval(interval);
|
|
76
79
|
};
|
|
77
80
|
}, [runningTask?.startTime, isExecuting, currentTaskIndex]);
|
|
78
|
-
//
|
|
81
|
+
// Hide stdin input when not executing
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!isExecuting) {
|
|
84
|
+
showStdinInputRef.current = false;
|
|
85
|
+
setShowStdinInput(false);
|
|
86
|
+
}
|
|
87
|
+
}, [isExecuting]);
|
|
88
|
+
// Handle cancel - kill the running process and update final output
|
|
79
89
|
const handleCancel = useCallback(() => {
|
|
80
90
|
cancelledRef.current = true;
|
|
91
|
+
killCurrentProcess();
|
|
81
92
|
dispatch({ type: ExecuteActionType.CancelExecution });
|
|
82
93
|
// Build final state with current output for the running task
|
|
83
94
|
const updatedTasks = tasks.map((task) => {
|
|
@@ -103,9 +114,30 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
103
114
|
requestHandlers.onCompleted(finalState);
|
|
104
115
|
requestHandlers.onAborted('execution');
|
|
105
116
|
}, [message, summary, tasks, requestHandlers]);
|
|
106
|
-
|
|
117
|
+
// Handle stdin submission: write the line to the running process
|
|
118
|
+
const handleStdinSubmit = useCallback((value) => {
|
|
119
|
+
writeStdin(value + '\n');
|
|
120
|
+
outputRef.current.chunks.push({
|
|
121
|
+
text: `\n> ${value}\n`,
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
source: OutputSource.Stdin,
|
|
124
|
+
});
|
|
125
|
+
showStdinInputRef.current = false;
|
|
126
|
+
setShowStdinInput(false);
|
|
127
|
+
}, []);
|
|
128
|
+
useInput((input, key) => {
|
|
107
129
|
if (key.escape && (isLoading || isExecuting)) {
|
|
108
|
-
|
|
130
|
+
if (showStdinInputRef.current) {
|
|
131
|
+
showStdinInputRef.current = false;
|
|
132
|
+
setShowStdinInput(false);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
handleCancel();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (input === '/' && isExecuting && !showStdinInputRef.current) {
|
|
139
|
+
showStdinInputRef.current = true;
|
|
140
|
+
setShowStdinInput(true);
|
|
109
141
|
}
|
|
110
142
|
}, { isActive: (isLoading || isExecuting) && isActive });
|
|
111
143
|
// Process tasks to get commands from AI
|
|
@@ -210,6 +242,8 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
210
242
|
});
|
|
211
243
|
// Reset output ref for new task
|
|
212
244
|
outputRef.current = { chunks: [], currentMemory: undefined };
|
|
245
|
+
showStdinInputRef.current = false;
|
|
246
|
+
setShowStdinInput(false);
|
|
213
247
|
// Merge workdir into command
|
|
214
248
|
const command = workdirRef.current
|
|
215
249
|
? { ...currentTask.command, workdir: workdirRef.current }
|
|
@@ -217,8 +251,10 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
217
251
|
void executeTask(command, currentTaskIndex, {
|
|
218
252
|
onUpdate: (output) => {
|
|
219
253
|
if (!cancelledRef.current) {
|
|
254
|
+
// Preserve stdin chunks that were added locally
|
|
255
|
+
const stdinChunks = outputRef.current.chunks.filter((c) => c.source === OutputSource.Stdin);
|
|
220
256
|
outputRef.current = {
|
|
221
|
-
chunks: output.chunks,
|
|
257
|
+
chunks: [...output.chunks, ...stdinChunks],
|
|
222
258
|
currentMemory: output.currentMemory,
|
|
223
259
|
};
|
|
224
260
|
}
|
|
@@ -230,11 +266,13 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
230
266
|
if (execOutput.workdir) {
|
|
231
267
|
workdirRef.current = execOutput.workdir;
|
|
232
268
|
}
|
|
269
|
+
// Preserve stdin chunks in final output
|
|
270
|
+
const stdinChunks = outputRef.current.chunks.filter((c) => c.source === OutputSource.Stdin);
|
|
233
271
|
const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
|
|
234
272
|
? {
|
|
235
273
|
...task,
|
|
236
274
|
output: {
|
|
237
|
-
chunks: execOutput.chunks,
|
|
275
|
+
chunks: [...execOutput.chunks, ...stdinChunks],
|
|
238
276
|
},
|
|
239
277
|
}
|
|
240
278
|
: task);
|
|
@@ -256,11 +294,13 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
256
294
|
if (execOutput.workdir) {
|
|
257
295
|
workdirRef.current = execOutput.workdir;
|
|
258
296
|
}
|
|
297
|
+
// Preserve stdin chunks in final output
|
|
298
|
+
const stdinChunks = outputRef.current.chunks.filter((c) => c.source === OutputSource.Stdin);
|
|
259
299
|
const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
|
|
260
300
|
? {
|
|
261
301
|
...task,
|
|
262
302
|
output: {
|
|
263
|
-
chunks: execOutput.chunks,
|
|
303
|
+
chunks: [...execOutput.chunks, ...stdinChunks],
|
|
264
304
|
},
|
|
265
305
|
error: execOutput.error || undefined,
|
|
266
306
|
}
|
|
@@ -287,5 +327,5 @@ export function Execute({ tasks: inputTasks, status, service, upcoming, label, r
|
|
|
287
327
|
lifecycleHandlers,
|
|
288
328
|
workflowHandlers,
|
|
289
329
|
]);
|
|
290
|
-
return (_jsx(ExecuteView, { isLoading: isLoading, isExecuting: isExecuting, isActive: isActive, error: error, message: message, tasks: tasks, completionMessage: completionMessage, showTasks: showTasks, upcoming: upcoming, label: label }));
|
|
330
|
+
return (_jsx(ExecuteView, { isLoading: isLoading, isExecuting: isExecuting, isActive: isActive, error: error, message: message, tasks: tasks, completionMessage: completionMessage, showTasks: showTasks, upcoming: upcoming, label: label, onStdinSubmit: showStdinInput ? handleStdinSubmit : undefined }));
|
|
291
331
|
}
|
|
@@ -61,21 +61,13 @@ export function Schedule({ message, tasks, status, debug = DebugLevel.None, requ
|
|
|
61
61
|
requestHandlers.onAborted('task selection');
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
if (key.downArrow) {
|
|
65
|
-
setHighlightedIndex((prev) =>
|
|
66
|
-
if (prev === null) {
|
|
67
|
-
return 0; // Select first
|
|
68
|
-
}
|
|
69
|
-
return (prev + 1) % optionsCount; // Wrap around
|
|
70
|
-
});
|
|
64
|
+
if (key.downArrow || key.rightArrow || key.tab) {
|
|
65
|
+
setHighlightedIndex((prev) => prev === null ? 0 : (prev + 1) % optionsCount);
|
|
71
66
|
}
|
|
72
|
-
else if (key.upArrow) {
|
|
73
|
-
setHighlightedIndex((prev) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
return (prev - 1 + optionsCount) % optionsCount; // Wrap around
|
|
78
|
-
});
|
|
67
|
+
else if (key.upArrow || key.leftArrow) {
|
|
68
|
+
setHighlightedIndex((prev) => prev === null
|
|
69
|
+
? optionsCount - 1
|
|
70
|
+
: (prev - 1 + optionsCount) % optionsCount);
|
|
79
71
|
}
|
|
80
72
|
else if (key.return && highlightedIndex !== null) {
|
|
81
73
|
// Record the selection for this group
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useInput } from '../../services/keyboard.js';
|
|
4
|
+
import { StepType } from '../views/Config.js';
|
|
5
|
+
import { SettingView } from '../views/Setting.js';
|
|
6
|
+
function isSelectionStep(step) {
|
|
7
|
+
return step.type === StepType.Selection;
|
|
8
|
+
}
|
|
9
|
+
function normalizeTextValue(value) {
|
|
10
|
+
if (value === null || value === undefined)
|
|
11
|
+
return '';
|
|
12
|
+
return value.replace(/\n/g, '').trim();
|
|
13
|
+
}
|
|
14
|
+
export function Setting({ step, initialValue, isActive, debug, onSubmit, }) {
|
|
15
|
+
const [inputValue, setInputValue] = useState(initialValue);
|
|
16
|
+
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
17
|
+
if (isSelectionStep(step)) {
|
|
18
|
+
const index = step.options.findIndex((opt) => opt.value === initialValue);
|
|
19
|
+
return index >= 0 ? index : step.defaultIndex;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
});
|
|
23
|
+
const handleInputChange = (value) => {
|
|
24
|
+
setInputValue(value);
|
|
25
|
+
};
|
|
26
|
+
const submitValue = (value) => {
|
|
27
|
+
setInputValue(value);
|
|
28
|
+
onSubmit(value);
|
|
29
|
+
};
|
|
30
|
+
const handleInputSubmit = (value) => {
|
|
31
|
+
if (step.type === StepType.Text) {
|
|
32
|
+
const normalizedInput = normalizeTextValue(value);
|
|
33
|
+
if (normalizedInput && step.validate(normalizedInput)) {
|
|
34
|
+
submitValue(normalizedInput);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (step.value && step.validate(step.value)) {
|
|
38
|
+
submitValue(step.value);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
submitValue(value);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleSelectionNavigation = (key) => {
|
|
47
|
+
if (!isSelectionStep(step))
|
|
48
|
+
return;
|
|
49
|
+
const len = step.options.length;
|
|
50
|
+
if (key.tab || key.rightArrow) {
|
|
51
|
+
setSelectedIndex((prev) => (prev + 1) % len);
|
|
52
|
+
}
|
|
53
|
+
else if (key.leftArrow) {
|
|
54
|
+
setSelectedIndex((prev) => (prev - 1 + len) % len);
|
|
55
|
+
}
|
|
56
|
+
else if (key.return) {
|
|
57
|
+
submitValue(step.options[selectedIndex].value);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
useInput((_, key) => {
|
|
61
|
+
if (!isActive || !isSelectionStep(step))
|
|
62
|
+
return;
|
|
63
|
+
handleSelectionNavigation(key);
|
|
64
|
+
}, { isActive: isActive && isSelectionStep(step) });
|
|
65
|
+
return (_jsx(SettingView, { step: step, value: inputValue, selectedIndex: selectedIndex, isActive: isActive, debug: debug, onInputChange: handleInputChange, onInputSubmit: handleInputSubmit }));
|
|
66
|
+
}
|
|
@@ -1,106 +1,19 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import {
|
|
3
|
-
import { Box, Text, useFocus } from 'ink';
|
|
4
|
-
import TextInput from 'ink-text-input';
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
5
3
|
import { ComponentStatus } from '../../types/components.js';
|
|
6
|
-
import { Colors } from '../../services/colors.js';
|
|
7
4
|
import { DebugLevel } from '../../configuration/types.js';
|
|
8
|
-
import {
|
|
9
|
-
/**
|
|
10
|
-
* Get postfix with debug brackets if debug is enabled
|
|
11
|
-
* Info: {key} | Verbose: {key} entry
|
|
12
|
-
*/
|
|
13
|
-
function getPostfix(text, debugLevel) {
|
|
14
|
-
if (debugLevel === DebugLevel.None || !text) {
|
|
15
|
-
return '';
|
|
16
|
-
}
|
|
17
|
-
if (debugLevel === DebugLevel.Info) {
|
|
18
|
-
return `{${text}}`;
|
|
19
|
-
}
|
|
20
|
-
return `{${text}} entry`;
|
|
21
|
-
}
|
|
5
|
+
import { SettingView } from './Setting.js';
|
|
22
6
|
export var StepType;
|
|
23
7
|
(function (StepType) {
|
|
24
8
|
StepType["Text"] = "text";
|
|
25
9
|
StepType["Selection"] = "selection";
|
|
26
10
|
})(StepType || (StepType = {}));
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Sync internal state with prop changes
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
setInputValue(value);
|
|
34
|
-
}, [value]);
|
|
35
|
-
const handleChange = (newValue) => {
|
|
36
|
-
setInputValue(newValue);
|
|
37
|
-
onChange(newValue);
|
|
38
|
-
if (validationFailed) {
|
|
39
|
-
setValidationFailed(false);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
const handleSubmit = (value) => {
|
|
43
|
-
// Use placeholder if input is empty
|
|
44
|
-
const finalValue = value || placeholder || '';
|
|
45
|
-
if (!validate(finalValue)) {
|
|
46
|
-
setValidationFailed(true);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
onSubmit(finalValue);
|
|
50
|
-
};
|
|
51
|
-
// Handle input manually when validation fails
|
|
52
|
-
useInput((input, key) => {
|
|
53
|
-
if (!validationFailed)
|
|
54
|
-
return;
|
|
55
|
-
if (key.return) {
|
|
56
|
-
handleSubmit(inputValue);
|
|
57
|
-
}
|
|
58
|
-
else if (key.backspace || key.delete) {
|
|
59
|
-
const newValue = inputValue.slice(0, -1);
|
|
60
|
-
handleChange(newValue);
|
|
61
|
-
}
|
|
62
|
-
else if (!key.ctrl && !key.meta && input) {
|
|
63
|
-
const newValue = inputValue + input;
|
|
64
|
-
handleChange(newValue);
|
|
65
|
-
}
|
|
66
|
-
}, { isActive: validationFailed });
|
|
67
|
-
// When validation fails, show colored text
|
|
68
|
-
if (validationFailed) {
|
|
69
|
-
return (_jsxs(Text, { color: Colors.Status.Error, children: [inputValue || placeholder, isFocused && _jsx(Text, { inverse: true, children: " " })] }));
|
|
70
|
-
}
|
|
71
|
-
return (_jsx(TextInput, { value: inputValue, onChange: handleChange, onSubmit: handleSubmit, placeholder: placeholder }));
|
|
72
|
-
}
|
|
73
|
-
function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
|
|
74
|
-
return (_jsx(Box, { children: options.map((option, optIndex) => {
|
|
75
|
-
const isSelected = optIndex === selectedIndex;
|
|
76
|
-
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
|
|
77
|
-
}) }));
|
|
78
|
-
}
|
|
79
|
-
export const ConfigView = ({ steps, state, status, debug = DebugLevel.None, onInputChange, onInputSubmit, }) => {
|
|
11
|
+
/**
|
|
12
|
+
* Config view: Multi-step configuration form for timeline display
|
|
13
|
+
*/
|
|
14
|
+
export const ConfigView = ({ steps, state, status, debug = DebugLevel.None, }) => {
|
|
80
15
|
const isActive = status === ComponentStatus.Active;
|
|
81
16
|
const { values, completedStep, selectedIndex } = state;
|
|
82
|
-
const renderStepInput = (stepConfig, isCurrentStep) => {
|
|
83
|
-
const configKey = stepConfig.path || stepConfig.key;
|
|
84
|
-
const displayValue = values[configKey];
|
|
85
|
-
switch (stepConfig.type) {
|
|
86
|
-
case StepType.Text:
|
|
87
|
-
if (isCurrentStep && onInputChange && onInputSubmit) {
|
|
88
|
-
return (_jsx(TextStep, { value: values[configKey] || '', placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: onInputChange, onSubmit: onInputSubmit }));
|
|
89
|
-
}
|
|
90
|
-
return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
|
|
91
|
-
case StepType.Selection: {
|
|
92
|
-
if (!isCurrentStep) {
|
|
93
|
-
const option = stepConfig.options.find((opt) => opt.value === displayValue);
|
|
94
|
-
return _jsx(Text, { dimColor: true, children: option?.label || '' });
|
|
95
|
-
}
|
|
96
|
-
return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
|
|
97
|
-
}
|
|
98
|
-
default: {
|
|
99
|
-
const _exhaustiveCheck = stepConfig;
|
|
100
|
-
throw new Error('Unsupported step type');
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
17
|
return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
|
|
105
18
|
const isCurrentStep = index === completedStep && isActive;
|
|
106
19
|
const isCompleted = index < completedStep;
|
|
@@ -109,7 +22,7 @@ export const ConfigView = ({ steps, state, status, debug = DebugLevel.None, onIn
|
|
|
109
22
|
if (!shouldShow) {
|
|
110
23
|
return null;
|
|
111
24
|
}
|
|
112
|
-
const
|
|
113
|
-
return (
|
|
25
|
+
const configKey = stepConfig.path || stepConfig.key;
|
|
26
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: _jsx(SettingView, { step: stepConfig, value: values[configKey] || '', selectedIndex: selectedIndex, isActive: isCurrentStep, debug: debug }) }, configKey));
|
|
114
27
|
}) }));
|
|
115
28
|
};
|