prompt-language-shell 0.8.4 → 0.8.6

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/ui/Config.js CHANGED
@@ -6,7 +6,7 @@ import { ComponentStatus } from '../types/components.js';
6
6
  import { FeedbackType } from '../types/types.js';
7
7
  import { Colors } from '../services/colors.js';
8
8
  import { createFeedback } from '../services/components.js';
9
- import { DebugLevel } from '../services/configuration.js';
9
+ import { DebugLevel } from '../configuration/types.js';
10
10
  import { useInput } from '../services/keyboard.js';
11
11
  /**
12
12
  * Get postfix with debug brackets if debug is enabled
@@ -78,16 +78,52 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
78
78
  return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
79
79
  }) }));
80
80
  }
81
+ export const ConfigView = ({ steps, state, status, debug = DebugLevel.None, onInputChange, onInputSubmit, }) => {
82
+ const isActive = status === ComponentStatus.Active;
83
+ const { values, completedStep, selectedIndex } = state;
84
+ const renderStepInput = (stepConfig, isCurrentStep) => {
85
+ const configKey = stepConfig.path || stepConfig.key;
86
+ const displayValue = values[configKey];
87
+ switch (stepConfig.type) {
88
+ case StepType.Text:
89
+ if (isCurrentStep && onInputChange && onInputSubmit) {
90
+ return (_jsx(TextStep, { value: values[configKey] || '', placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: onInputChange, onSubmit: onInputSubmit }));
91
+ }
92
+ return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
93
+ case StepType.Selection: {
94
+ if (!isCurrentStep) {
95
+ const option = stepConfig.options.find((opt) => opt.value === displayValue);
96
+ return _jsx(Text, { dimColor: true, children: option?.label || '' });
97
+ }
98
+ return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
99
+ }
100
+ default: {
101
+ const _exhaustiveCheck = stepConfig;
102
+ throw new Error('Unsupported step type');
103
+ }
104
+ }
105
+ };
106
+ return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
107
+ const isCurrentStep = index === completedStep && isActive;
108
+ const isCompleted = index < completedStep;
109
+ const wasAborted = index === completedStep && !isActive;
110
+ const shouldShow = isCompleted || isCurrentStep || wasAborted;
111
+ if (!shouldShow) {
112
+ return null;
113
+ }
114
+ const postfix = getPostfix(stepConfig.path, debug);
115
+ return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), postfix && _jsx(Text, { color: Colors.Type.Config, children: postfix })] }), _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));
116
+ }) }));
117
+ };
118
+ /**
119
+ * Config controller: Multi-step wizard logic
120
+ */
81
121
  export function Config(props) {
82
- const { steps, state, status, debug = DebugLevel.None, stateHandlers, lifecycleHandlers, onFinished, onAborted, } = props;
122
+ const { steps, status, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, onFinished, onAborted, } = props;
83
123
  const isActive = status === ComponentStatus.Active;
84
- const [step, setStep] = useState(!isActive ? (state?.completedStep ?? steps.length) : 0);
124
+ const [step, setStep] = useState(0);
85
125
  const [values, setValues] = useState(() => {
86
- // If not active and we have saved state values, use those
87
- if (!isActive && state?.values) {
88
- return state.values;
89
- }
90
- // Otherwise initialize from step defaults
126
+ // Initialize from step defaults
91
127
  const initial = {};
92
128
  steps.forEach((stepConfig) => {
93
129
  // Use full path if available, otherwise use key
@@ -112,26 +148,14 @@ export function Config(props) {
112
148
  });
113
149
  const [inputValue, setInputValue] = useState(() => {
114
150
  // Initialize with the current step's value if available
115
- if (isActive && step < steps.length) {
151
+ if (step < steps.length) {
116
152
  const stepConfig = steps[step];
117
153
  const configKey = stepConfig.path || stepConfig.key;
118
154
  return values[configKey] || '';
119
155
  }
120
156
  return '';
121
157
  });
122
- const [selectedIndex, setSelectedIndex] = useState(() => {
123
- // If not active, use saved state
124
- if (!isActive && state?.selectedIndex !== undefined) {
125
- return state.selectedIndex;
126
- }
127
- // Initialize selectedIndex based on current step's defaultIndex
128
- if (isActive &&
129
- step < steps.length &&
130
- steps[step].type === StepType.Selection) {
131
- return steps[step].defaultIndex;
132
- }
133
- return 0;
134
- });
158
+ const [selectedIndex, setSelectedIndex] = useState(0);
135
159
  const normalizeValue = (value) => {
136
160
  if (value === null || value === undefined) {
137
161
  return '';
@@ -167,20 +191,21 @@ export function Config(props) {
167
191
  throw new Error('Unsupported step type');
168
192
  }
169
193
  }
170
- if (currentValue) {
171
- setValues({ ...values, [configKey]: currentValue });
172
- }
173
- // Save state before aborting
174
- stateHandlers?.updateState({
175
- values,
194
+ const finalValues = currentValue
195
+ ? { ...values, [configKey]: currentValue }
196
+ : values;
197
+ // Expose final state
198
+ const finalState = {
199
+ values: finalValues,
176
200
  completedStep: step,
177
201
  selectedIndex,
178
- });
202
+ };
203
+ requestHandlers.onCompleted(finalState);
179
204
  if (onAborted) {
180
205
  onAborted('configuration');
181
206
  }
182
207
  // Complete with abort feedback
183
- lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Aborted, 'Configuration cancelled.'));
208
+ lifecycleHandlers.completeActive(createFeedback(FeedbackType.Aborted, 'Configuration cancelled.'));
184
209
  return;
185
210
  }
186
211
  // Handle selection step navigation
@@ -192,7 +217,7 @@ export function Config(props) {
192
217
  handleSubmit(currentStepConfig.options[selectedIndex].value);
193
218
  }
194
219
  }
195
- });
220
+ }, { isActive });
196
221
  const handleSubmit = (value) => {
197
222
  const currentStepConfig = steps[step];
198
223
  let finalValue = '';
@@ -230,37 +255,29 @@ export function Config(props) {
230
255
  setInputValue('');
231
256
  if (step === steps.length - 1) {
232
257
  // Last step completed
233
- // IMPORTANT: Update state BEFORE calling onFinished
234
- // onFinished may call handlers.completeActive(), so state must be saved first
235
- const stateUpdate = {
258
+ // Expose final state
259
+ const finalState = {
236
260
  values: newValues,
237
261
  completedStep: steps.length,
238
262
  selectedIndex,
239
263
  };
240
- stateHandlers?.updateState(stateUpdate);
264
+ requestHandlers.onCompleted(finalState);
241
265
  // Call onFinished callback and handle result
242
266
  try {
243
267
  if (onFinished) {
244
268
  onFinished(newValues);
245
269
  }
246
270
  // Success - complete with success feedback
247
- lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Succeeded, 'Configuration saved successfully.'));
271
+ lifecycleHandlers.completeActive(createFeedback(FeedbackType.Succeeded, 'Configuration saved successfully.'));
248
272
  }
249
273
  catch (error) {
250
274
  // Failure - complete with error feedback
251
275
  const errorMessage = error instanceof Error ? error.message : 'Configuration failed';
252
- lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Failed, errorMessage));
276
+ lifecycleHandlers.completeActive(createFeedback(FeedbackType.Failed, errorMessage));
253
277
  }
254
278
  setStep(steps.length);
255
279
  }
256
280
  else {
257
- // Save state after each step
258
- const stateUpdate = {
259
- values: newValues,
260
- completedStep: step + 1,
261
- selectedIndex,
262
- };
263
- stateHandlers?.updateState(stateUpdate);
264
281
  const nextStep = step + 1;
265
282
  setStep(nextStep);
266
283
  // Reset selectedIndex for next step
@@ -270,39 +287,12 @@ export function Config(props) {
270
287
  }
271
288
  }
272
289
  };
273
- const renderStepInput = (stepConfig, isCurrentStep) => {
274
- const configKey = stepConfig.path || stepConfig.key;
275
- // Use state values when inactive, local values when active
276
- const displayValue = !isActive && state?.values ? state.values[configKey] : values[configKey];
277
- switch (stepConfig.type) {
278
- case StepType.Text:
279
- if (isCurrentStep) {
280
- return (_jsx(TextStep, { value: inputValue, placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: setInputValue, onSubmit: handleSubmit }));
281
- }
282
- return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
283
- case StepType.Selection: {
284
- if (!isCurrentStep) {
285
- // Find the option that matches the saved/current value
286
- const option = stepConfig.options.find((opt) => opt.value === displayValue);
287
- return _jsx(Text, { dimColor: true, children: option?.label || '' });
288
- }
289
- return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
290
- }
291
- default: {
292
- const _exhaustiveCheck = stepConfig;
293
- throw new Error('Unsupported step type');
294
- }
295
- }
290
+ // Build current state for View
291
+ // Controller always renders View, passing current state and callbacks
292
+ const state = {
293
+ values,
294
+ completedStep: step,
295
+ selectedIndex,
296
296
  };
297
- return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
298
- const isCurrentStep = index === step && isActive;
299
- const isCompleted = index < step;
300
- const wasAborted = index === step && !isActive;
301
- const shouldShow = isCompleted || isCurrentStep || wasAborted;
302
- if (!shouldShow) {
303
- return null;
304
- }
305
- const postfix = getPostfix(stepConfig.path, debug);
306
- return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), postfix && _jsx(Text, { color: Colors.Type.Config, children: postfix })] }), _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));
307
- }) }));
297
+ return (_jsx(ConfigView, { steps: steps, state: state, status: status, debug: debug, onInputChange: setInputValue, onInputSubmit: handleSubmit }));
308
298
  }
@@ -1,31 +1,53 @@
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
+ import { ComponentStatus, } from '../types/components.js';
5
5
  import { Colors, getTextColor, Palette } from '../services/colors.js';
6
6
  import { useInput } from '../services/keyboard.js';
7
7
  import { UserQuery } from './UserQuery.js';
8
- export function Confirm({ message, state, status, stateHandlers, onConfirmed, onCancelled, }) {
8
+ export const ConfirmView = ({ message, state, status }) => {
9
9
  const isActive = status === ComponentStatus.Active;
10
- const [selectedIndex, setSelectedIndex] = useState(state?.selectedIndex ?? 0); // 0 = Yes, 1 = No
10
+ const { selectedIndex } = state;
11
+ const options = [
12
+ { label: 'yes', value: 'yes', color: Palette.BrightGreen },
13
+ { label: 'no', value: 'no', color: Colors.Status.Error },
14
+ ];
15
+ // Timeline rendering (Done status)
16
+ if (status === ComponentStatus.Done) {
17
+ 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] })] }));
18
+ }
19
+ // Active/Pending rendering
20
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: message }) }), _jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
21
+ const isSelected = index === selectedIndex;
22
+ return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
23
+ }) })] })] }));
24
+ };
25
+ /**
26
+ * Confirm controller: Manages yes/no selection
27
+ */
28
+ export function Confirm({ message, status, requestHandlers, onConfirmed, onCancelled, }) {
29
+ const isActive = status === ComponentStatus.Active;
30
+ const [selectedIndex, setSelectedIndex] = useState(0); // 0 = Yes, 1 = No
11
31
  useInput((input, key) => {
12
32
  if (!isActive)
13
33
  return;
14
34
  if (key.escape) {
15
35
  // Escape: highlight "No" and cancel
16
- setSelectedIndex(1);
17
- stateHandlers?.updateState({ selectedIndex: 1 });
36
+ const finalState = { selectedIndex: 1, confirmed: false };
37
+ requestHandlers.onCompleted(finalState);
18
38
  onCancelled();
19
39
  }
20
40
  else if (key.tab) {
21
41
  // Toggle between Yes (0) and No (1)
22
- const newIndex = selectedIndex === 0 ? 1 : 0;
23
- setSelectedIndex(newIndex);
24
- stateHandlers?.updateState({ selectedIndex: newIndex });
42
+ setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
25
43
  }
26
44
  else if (key.return) {
27
45
  // Confirm selection
28
- stateHandlers?.updateState({ selectedIndex, confirmed: true });
46
+ const finalState = {
47
+ selectedIndex,
48
+ confirmed: true,
49
+ };
50
+ requestHandlers.onCompleted(finalState);
29
51
  if (selectedIndex === 0) {
30
52
  onConfirmed();
31
53
  }
@@ -34,16 +56,7 @@ export function Confirm({ message, state, status, stateHandlers, onConfirmed, on
34
56
  }
35
57
  }
36
58
  }, { isActive });
37
- const options = [
38
- { label: 'yes', value: 'yes', color: Palette.BrightGreen },
39
- { label: 'no', value: 'no', color: Colors.Status.Error },
40
- ];
41
- if (!isActive) {
42
- // When done, show both the message and user's choice in timeline
43
- 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] })] }));
44
- }
45
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: message }) }), _jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
46
- const isSelected = index === selectedIndex;
47
- return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
48
- }) })] })] }));
59
+ // Controller always renders View, passing current state
60
+ const state = { selectedIndex, confirmed: false };
61
+ return _jsx(ConfirmView, { message: message, state: state, status: status });
49
62
  }