prompt-language-shell 0.3.0 → 0.3.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.
@@ -1,80 +1,12 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
- import { TaskType } from '../types/components.js';
5
- import { Label } from './Label.js';
6
- import { List } from './List.js';
7
4
  import { Spinner } from './Spinner.js';
8
5
  const MIN_PROCESSING_TIME = 1000; // purely for visual effect
9
- // Color palette
10
- const ColorPalette = {
11
- [TaskType.Config]: {
12
- description: '#ffffff', // white
13
- type: '#5c9ccc', // cyan
14
- },
15
- [TaskType.Plan]: {
16
- description: '#ffffff', // white
17
- type: '#5ccccc', // magenta
18
- },
19
- [TaskType.Execute]: {
20
- description: '#ffffff', // white
21
- type: '#4a9a7a', // green
22
- },
23
- [TaskType.Answer]: {
24
- description: '#ffffff', // white
25
- type: '#9c5ccc', // purple
26
- },
27
- [TaskType.Report]: {
28
- description: '#ffffff', // white
29
- type: '#cc9c5c', // orange
30
- },
31
- [TaskType.Define]: {
32
- description: '#ffffff', // white
33
- type: '#cc9c5c', // amber
34
- },
35
- [TaskType.Ignore]: {
36
- description: '#cccc5c', // yellow
37
- type: '#cc7a5c', // orange
38
- },
39
- [TaskType.Select]: {
40
- description: '#888888', // grey
41
- type: '#5c8cbc', // steel blue
42
- },
43
- };
44
- function taskToListItem(task) {
45
- const colors = ColorPalette[task.type];
46
- const item = {
47
- description: {
48
- text: task.action,
49
- color: colors.description,
50
- },
51
- type: {
52
- text: task.type,
53
- color: colors.type,
54
- },
55
- };
56
- // Add children for Define tasks with options
57
- if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
58
- const selectColors = ColorPalette[TaskType.Select];
59
- item.children = task.params.options.map((option) => ({
60
- description: {
61
- text: String(option),
62
- color: selectColors.description,
63
- },
64
- type: {
65
- text: TaskType.Select,
66
- color: selectColors.type,
67
- },
68
- }));
69
- }
70
- return item;
71
- }
72
6
  export function Command({ command, state, service, children, onError, onComplete, }) {
73
7
  const done = state?.done ?? false;
74
8
  const [error, setError] = useState(null);
75
9
  const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
76
- const [message, setMessage] = useState('');
77
- const [tasks, setTasks] = useState([]);
78
10
  useEffect(() => {
79
11
  // Skip processing if done (showing historical/final state)
80
12
  if (done) {
@@ -95,10 +27,8 @@ export function Command({ command, state, service, children, onError, onComplete
95
27
  const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
96
28
  await new Promise((resolve) => setTimeout(resolve, remainingTime));
97
29
  if (mounted) {
98
- setMessage(result.message);
99
- setTasks(result.tasks);
100
30
  setIsLoading(false);
101
- onComplete?.();
31
+ onComplete?.(result.message, result.tasks);
102
32
  }
103
33
  }
104
34
  catch (err) {
@@ -122,5 +52,5 @@ export function Command({ command, state, service, children, onError, onComplete
122
52
  mounted = false;
123
53
  };
124
54
  }, [command, done, service]);
125
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", marginLeft: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), !isLoading && tasks.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [message && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { children: " " }), _jsx(Label, { description: message, descriptionColor: ColorPalette[TaskType.Plan].description, type: TaskType.Plan, typeColor: ColorPalette[TaskType.Plan].type })] })), _jsx(List, { items: tasks.map(taskToListItem) })] })), children] }));
55
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
126
56
  }
@@ -1,23 +1,30 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ComponentName } from '../types/components.js';
2
3
  import { Command } from './Command.js';
3
4
  import { Config } from './Config.js';
4
5
  import { Feedback } from './Feedback.js';
6
+ import { Message } from './Message.js';
7
+ import { Plan } from './Plan.js';
5
8
  import { Welcome } from './Welcome.js';
6
9
  export function Component({ def }) {
7
10
  switch (def.name) {
8
- case 'welcome':
11
+ case ComponentName.Welcome:
9
12
  return _jsx(Welcome, { ...def.props });
10
- case 'config': {
13
+ case ComponentName.Config: {
11
14
  const props = def.props;
12
15
  const state = def.state;
13
16
  return _jsx(Config, { ...props, state: state });
14
17
  }
15
- case 'feedback':
16
- return _jsx(Feedback, { ...def.props });
17
- case 'command': {
18
+ case ComponentName.Command: {
18
19
  const props = def.props;
19
20
  const state = def.state;
20
21
  return _jsx(Command, { ...props, state: state });
21
22
  }
23
+ case ComponentName.Plan:
24
+ return _jsx(Plan, { ...def.props });
25
+ case ComponentName.Feedback:
26
+ return _jsx(Feedback, { ...def.props });
27
+ case ComponentName.Message:
28
+ return _jsx(Message, { ...def.props });
22
29
  }
23
30
  }
package/dist/ui/Config.js CHANGED
@@ -1,20 +1,87 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { Box, Text, useInput } from 'ink';
3
+ import { Box, Text, useInput, useFocus } from 'ink';
4
4
  import TextInput from 'ink-text-input';
5
+ export var StepType;
6
+ (function (StepType) {
7
+ StepType["Text"] = "text";
8
+ StepType["Selection"] = "selection";
9
+ })(StepType || (StepType = {}));
10
+ function TextStep({ value, placeholder, validate, onChange, onSubmit, }) {
11
+ const [inputValue, setInputValue] = React.useState(value);
12
+ const [validationFailed, setValidationFailed] = React.useState(false);
13
+ const { isFocused } = useFocus({ autoFocus: true });
14
+ const handleChange = (newValue) => {
15
+ setInputValue(newValue);
16
+ onChange(newValue);
17
+ if (validationFailed) {
18
+ setValidationFailed(false);
19
+ }
20
+ };
21
+ const handleSubmit = (value) => {
22
+ if (!validate(value)) {
23
+ setValidationFailed(true);
24
+ return;
25
+ }
26
+ onSubmit(value);
27
+ };
28
+ // Handle input manually when validation fails
29
+ useInput((input, key) => {
30
+ if (!validationFailed)
31
+ return;
32
+ if (key.return) {
33
+ handleSubmit(inputValue);
34
+ }
35
+ else if (key.backspace || key.delete) {
36
+ const newValue = inputValue.slice(0, -1);
37
+ handleChange(newValue);
38
+ }
39
+ else if (!key.ctrl && !key.meta && input) {
40
+ const newValue = inputValue + input;
41
+ handleChange(newValue);
42
+ }
43
+ }, { isActive: validationFailed });
44
+ // When validation fails, show colored text
45
+ if (validationFailed) {
46
+ return (_jsxs(Text, { color: "#cc5c5c", children: [inputValue || placeholder, isFocused && _jsx(Text, { inverse: true, children: " " })] }));
47
+ }
48
+ return (_jsx(TextInput, { value: inputValue, onChange: handleChange, onSubmit: handleSubmit, placeholder: placeholder }));
49
+ }
50
+ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
51
+ return (_jsx(Box, { children: options.map((option, optIndex) => {
52
+ const isSelected = optIndex === selectedIndex;
53
+ return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
54
+ }) }));
55
+ }
5
56
  export function Config({ steps, state, onFinished, onAborted }) {
6
57
  const done = state?.done ?? false;
7
58
  const [step, setStep] = React.useState(done ? steps.length : 0);
8
59
  const [values, setValues] = React.useState(() => {
9
60
  const initial = {};
10
- steps.forEach((step) => {
11
- if (step.value !== null) {
12
- initial[step.key] = step.value;
61
+ steps.forEach((stepConfig) => {
62
+ switch (stepConfig.type) {
63
+ case StepType.Text:
64
+ if (stepConfig.value !== null) {
65
+ initial[stepConfig.key] = stepConfig.value;
66
+ }
67
+ break;
68
+ case StepType.Selection:
69
+ initial[stepConfig.key] =
70
+ stepConfig.options[stepConfig.defaultIndex].value;
71
+ break;
72
+ default: {
73
+ const exhaustiveCheck = stepConfig;
74
+ throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
75
+ }
13
76
  }
14
77
  });
15
78
  return initial;
16
79
  });
17
80
  const [inputValue, setInputValue] = React.useState('');
81
+ const [selectedIndex, setSelectedIndex] = React.useState(() => {
82
+ const firstStep = steps[0];
83
+ return firstStep?.type === StepType.Selection ? firstStep.defaultIndex : 0;
84
+ });
18
85
  const normalizeValue = (value) => {
19
86
  if (value === null || value === undefined) {
20
87
  return '';
@@ -23,16 +90,83 @@ export function Config({ steps, state, onFinished, onAborted }) {
23
90
  };
24
91
  useInput((input, key) => {
25
92
  if (key.escape && !done && step < steps.length) {
93
+ // Save current value before aborting
94
+ const currentStepConfig = steps[step];
95
+ if (currentStepConfig) {
96
+ let currentValue = '';
97
+ switch (currentStepConfig.type) {
98
+ case StepType.Text:
99
+ currentValue = inputValue || values[currentStepConfig.key] || '';
100
+ break;
101
+ case StepType.Selection:
102
+ currentValue =
103
+ currentStepConfig.options[selectedIndex]?.value ||
104
+ values[currentStepConfig.key] ||
105
+ '';
106
+ break;
107
+ default: {
108
+ const exhaustiveCheck = currentStepConfig;
109
+ throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
110
+ }
111
+ }
112
+ if (currentValue) {
113
+ setValues({ ...values, [currentStepConfig.key]: currentValue });
114
+ }
115
+ }
26
116
  if (onAborted) {
27
117
  onAborted();
28
118
  }
119
+ return;
120
+ }
121
+ const currentStep = steps[step];
122
+ if (!done && step < steps.length && currentStep) {
123
+ switch (currentStep.type) {
124
+ case StepType.Selection:
125
+ if (key.tab) {
126
+ setSelectedIndex((prev) => (prev + 1) % currentStep.options.length);
127
+ }
128
+ else if (key.return) {
129
+ handleSubmit(currentStep.options[selectedIndex].value);
130
+ }
131
+ break;
132
+ case StepType.Text:
133
+ // Text input handled by TextInput component
134
+ break;
135
+ default: {
136
+ const exhaustiveCheck = currentStep;
137
+ throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
138
+ }
139
+ }
29
140
  }
30
141
  });
31
142
  const handleSubmit = (value) => {
32
143
  const currentStepConfig = steps[step];
33
- const finalValue = normalizeValue(value) || normalizeValue(currentStepConfig.value);
34
- // Don't allow empty value if step has no default (mandatory field)
35
- if (!finalValue && !currentStepConfig.value) {
144
+ let finalValue = '';
145
+ switch (currentStepConfig.type) {
146
+ case StepType.Selection:
147
+ // For selection, value is already validated by options
148
+ finalValue = value;
149
+ break;
150
+ case StepType.Text: {
151
+ // For text input
152
+ const normalizedInput = normalizeValue(value);
153
+ // Try user input first, then fall back to default
154
+ if (normalizedInput && currentStepConfig.validate(normalizedInput)) {
155
+ finalValue = normalizedInput;
156
+ }
157
+ else if (currentStepConfig.value &&
158
+ currentStepConfig.validate(currentStepConfig.value)) {
159
+ finalValue = currentStepConfig.value;
160
+ }
161
+ break;
162
+ }
163
+ default: {
164
+ const exhaustiveCheck = currentStepConfig;
165
+ throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
166
+ }
167
+ }
168
+ // Don't allow empty or invalid value
169
+ if (!finalValue) {
36
170
  return;
37
171
  }
38
172
  const newValues = { ...values, [currentStepConfig.key]: finalValue };
@@ -47,9 +181,34 @@ export function Config({ steps, state, onFinished, onAborted }) {
47
181
  }
48
182
  else {
49
183
  setStep(step + 1);
184
+ // Reset selection index for next step
185
+ const nextStep = steps[step + 1];
186
+ if (nextStep?.type === StepType.Selection) {
187
+ setSelectedIndex(nextStep.defaultIndex);
188
+ }
189
+ }
190
+ };
191
+ const renderStepInput = (stepConfig, isCurrentStep) => {
192
+ switch (stepConfig.type) {
193
+ case StepType.Text:
194
+ if (isCurrentStep) {
195
+ return (_jsx(TextStep, { value: inputValue, placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: setInputValue, onSubmit: handleSubmit }));
196
+ }
197
+ return _jsx(Text, { dimColor: true, children: values[stepConfig.key] || '' });
198
+ case StepType.Selection: {
199
+ if (!isCurrentStep) {
200
+ const selectedOption = stepConfig.options.find((opt) => opt.value === values[stepConfig.key]);
201
+ return _jsx(Text, { dimColor: true, children: selectedOption?.label || '' });
202
+ }
203
+ return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: isCurrentStep }));
204
+ }
205
+ default: {
206
+ const exhaustiveCheck = stepConfig;
207
+ throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
208
+ }
50
209
  }
51
210
  };
52
- return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
211
+ return (_jsx(Box, { flexDirection: "column", children: steps.map((stepConfig, index) => {
53
212
  const isCurrentStep = index === step && !done;
54
213
  const isCompleted = index < step;
55
214
  const wasAborted = index === step && done;
@@ -57,6 +216,6 @@ export function Config({ steps, state, onFinished, onAborted }) {
57
216
  if (!shouldShow) {
58
217
  return null;
59
218
  }
60
- return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "#5c8cbc", dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), isCurrentStep ? (_jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit, placeholder: stepConfig.value || undefined })) : (_jsx(Text, { dimColor: true, children: values[stepConfig.key] || '' }))] })] }, stepConfig.key));
219
+ return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "#5c8cbc", dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
61
220
  }) }));
62
221
  }
@@ -3,20 +3,31 @@ import { Box, Text } from 'ink';
3
3
  import { FeedbackType } from '../types/components.js';
4
4
  function getSymbol(type) {
5
5
  return {
6
+ [FeedbackType.Info]: 'ℹ',
6
7
  [FeedbackType.Succeeded]: '✓',
7
8
  [FeedbackType.Aborted]: '⊘',
8
9
  [FeedbackType.Failed]: '✗',
9
10
  }[type];
10
11
  }
11
- function getColor(type) {
12
+ function getSymbolColor(type) {
12
13
  return {
14
+ [FeedbackType.Info]: '#5c9ccc', // cyan
13
15
  [FeedbackType.Succeeded]: '#00aa00', // green
14
16
  [FeedbackType.Aborted]: '#cc9c5c', // orange
15
- [FeedbackType.Failed]: '#aa0000', // red
17
+ [FeedbackType.Failed]: '#cc5c5c', // red
18
+ }[type];
19
+ }
20
+ function getMessageColor(type) {
21
+ return {
22
+ [FeedbackType.Info]: '#aaaaaa', // light grey
23
+ [FeedbackType.Succeeded]: '#5ccc5c', // green
24
+ [FeedbackType.Aborted]: '#cc9c5c', // orange
25
+ [FeedbackType.Failed]: '#cc5c5c', // red
16
26
  }[type];
17
27
  }
18
28
  export function Feedback({ type, message }) {
19
- const color = getColor(type);
29
+ const symbolColor = getSymbolColor(type);
30
+ const messageColor = getMessageColor(type);
20
31
  const symbol = getSymbol(type);
21
- return (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: color, children: [symbol, " ", message] }) }));
32
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: symbolColor, children: [symbol, " "] }), _jsx(Text, { color: messageColor, children: message })] }));
22
33
  }
package/dist/ui/List.js CHANGED
@@ -1,7 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { Label } from './Label.js';
4
- export const List = ({ items, level = 0 }) => {
4
+ export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, }) => {
5
5
  const marginLeft = level > 0 ? 4 : 0;
6
- return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "whiteBright", children: ' - ' }), _jsx(Label, { description: item.description.text, descriptionColor: item.description.color, type: item.type.text, typeColor: item.type.color })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1 }))] }, index))) }));
6
+ return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => {
7
+ // At level 0, track which parent is active for child highlighting
8
+ // At level > 0, only highlight if this parent is the active one
9
+ const shouldHighlightChildren = level === 0 ? highlightedParentIndex === index : false;
10
+ const isHighlighted = item.highlighted || (level > 0 && index === highlightedIndex);
11
+ const marker = item.marker || (isHighlighted ? ' → ' : ' - ');
12
+ // Use highlighted colors if available and item is highlighted
13
+ const descriptionColor = isHighlighted && item.description.highlightedColor
14
+ ? item.description.highlightedColor
15
+ : item.description.color;
16
+ const typeColor = isHighlighted && item.type.highlightedColor
17
+ ? item.type.highlightedColor
18
+ : item.type.color;
19
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "whiteBright", children: marker }), _jsx(Label, { description: item.description.text, descriptionColor: descriptionColor, type: item.type.text, typeColor: typeColor })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null }))] }, index));
20
+ }) }));
7
21
  };