prompt-language-shell 0.8.2 → 0.8.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.
@@ -71,7 +71,7 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
71
71
  }
72
72
  return item;
73
73
  }
74
- export function Schedule({ message, tasks, state, status, debug = DebugLevel.None, handlers, onSelectionConfirmed, }) {
74
+ export function Schedule({ message, tasks, state, status, debug = DebugLevel.None, stateHandlers, lifecycleHandlers, errorHandlers, onSelectionConfirmed, }) {
75
75
  const isActive = status === ComponentStatus.Active;
76
76
  // isActive passed as prop
77
77
  const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
@@ -95,7 +95,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
95
95
  const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
96
96
  // Complete the selection phase - it goes to timeline
97
97
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
98
- handlers?.completeActive();
98
+ lifecycleHandlers?.completeActive();
99
99
  void onSelectionConfirmed(concreteTasks);
100
100
  }
101
101
  }, [
@@ -103,7 +103,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
103
103
  defineTaskIndices.length,
104
104
  tasks,
105
105
  onSelectionConfirmed,
106
- handlers,
106
+ lifecycleHandlers,
107
107
  ]);
108
108
  useInput((input, key) => {
109
109
  // Don't handle input if not active or no define task
@@ -111,7 +111,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
111
111
  return;
112
112
  }
113
113
  if (key.escape) {
114
- handlers?.onAborted('task selection');
114
+ errorHandlers?.onAborted('task selection');
115
115
  return;
116
116
  }
117
117
  if (key.downArrow) {
@@ -170,12 +170,12 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
170
170
  if (onSelectionConfirmed) {
171
171
  // Complete the selection phase - it goes to timeline
172
172
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
173
- handlers?.completeActive();
173
+ lifecycleHandlers?.completeActive();
174
174
  void onSelectionConfirmed(refinedTasks);
175
175
  }
176
176
  else {
177
177
  // No selection callback, just complete normally
178
- handlers?.completeActive();
178
+ lifecycleHandlers?.completeActive();
179
179
  }
180
180
  }
181
181
  }
@@ -183,7 +183,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
183
183
  // Sync state back to component definition
184
184
  // This ensures timeline can render historical state and tests can validate behavior
185
185
  useEffect(() => {
186
- handlers?.updateState({
186
+ stateHandlers?.updateState({
187
187
  highlightedIndex,
188
188
  currentDefineGroupIndex,
189
189
  completedSelections,
@@ -192,7 +192,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
192
192
  highlightedIndex,
193
193
  currentDefineGroupIndex,
194
194
  completedSelections,
195
- handlers,
195
+ stateHandlers,
196
196
  ]);
197
197
  const listItems = tasks.map((task, idx) => {
198
198
  // Find which define group this task belongs to (if any)
@@ -4,7 +4,7 @@ import { Box, Text } from 'ink';
4
4
  import { ComponentStatus } from '../types/components.js';
5
5
  import { TaskType } from '../types/types.js';
6
6
  import { Colors, getTextColor } from '../services/colors.js';
7
- import { addDebugToTimeline, createConfigStepsFromSchema, } from '../services/components.js';
7
+ import { createConfigStepsFromSchema } from '../services/components.js';
8
8
  import { DebugLevel, saveConfig, unflattenConfig, } from '../services/configuration.js';
9
9
  import { saveConfigLabels } from '../services/config-labels.js';
10
10
  import { useInput } from '../services/keyboard.js';
@@ -13,7 +13,7 @@ import { ensureMinimumTime } from '../services/timing.js';
13
13
  import { Config } from './Config.js';
14
14
  import { Spinner } from './Spinner.js';
15
15
  const MIN_PROCESSING_TIME = 1000;
16
- export function Validate({ missingConfig, userRequest, state, status, service, children, debug = DebugLevel.None, onError, onComplete, onAborted, handlers, }) {
16
+ export function Validate({ missingConfig, userRequest, state, status, service, children, debug = DebugLevel.None, onError, onComplete, onAborted, stateHandlers, lifecycleHandlers, workflowHandlers, }) {
17
17
  const isActive = status === ComponentStatus.Active;
18
18
  const [error, setError] = useState(state?.error ?? null);
19
19
  const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
@@ -39,7 +39,9 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
39
39
  await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
40
40
  if (mounted) {
41
41
  // Add debug components to timeline if present
42
- addDebugToTimeline(result.debug, handlers);
42
+ if (result.debug?.length) {
43
+ workflowHandlers?.addToTimeline(...result.debug);
44
+ }
43
45
  // Extract CONFIG tasks with descriptions from result
44
46
  const configTasks = result.tasks.filter((task) => task.type === TaskType.Config);
45
47
  // Build ConfigRequirements with descriptions
@@ -68,7 +70,7 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
68
70
  setCompletionMessage(message);
69
71
  setConfigRequirements(withDescriptions);
70
72
  // Save state after validation completes
71
- handlers?.updateState({
73
+ stateHandlers?.updateState({
72
74
  completionMessage: message,
73
75
  configRequirements: withDescriptions,
74
76
  validated: true,
@@ -82,7 +84,7 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
82
84
  const errorMessage = formatErrorMessage(err);
83
85
  setError(errorMessage);
84
86
  // Save error state
85
- handlers?.updateState({
87
+ stateHandlers?.updateState({
86
88
  error: errorMessage,
87
89
  completionMessage: null,
88
90
  configRequirements: null,
@@ -142,7 +144,7 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
142
144
  }
143
145
  // Mark validation component as complete before invoking callback
144
146
  // This allows the workflow to proceed to execution
145
- handlers?.completeActive();
147
+ lifecycleHandlers?.completeActive();
146
148
  // Invoke callback which will queue the Execute component
147
149
  if (configRequirements) {
148
150
  onComplete(configRequirements);
@@ -150,10 +152,10 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
150
152
  };
151
153
  const handleConfigAborted = (operation) => {
152
154
  // Mark validation component as complete when aborted
153
- handlers?.completeActive();
155
+ lifecycleHandlers?.completeActive();
154
156
  onAborted(operation);
155
157
  };
156
- 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 && configSteps.length > 0 && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps, status: status, debug: debug, onFinished: handleConfigFinished, onAborted: handleConfigAborted, handlers: handlers }) })), configSteps && configSteps.length === 0 && !error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: Colors.Status.Error, children: "Error: No configuration steps generated. Please try again." }) })), children] }));
158
+ 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 && configSteps.length > 0 && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps, status: status, debug: debug, onFinished: handleConfigFinished, onAborted: handleConfigAborted }) })), configSteps && configSteps.length === 0 && !error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: Colors.Status.Error, children: "Error: No configuration steps generated. Please try again." }) })), children] }));
157
159
  }
158
160
  /**
159
161
  * Build prompt for VALIDATE tool
@@ -5,6 +5,7 @@ import { ComponentStatus, } from '../types/components.js';
5
5
  import { ComponentName, FeedbackType } from '../types/types.js';
6
6
  import { createFeedback, isStateless, markAsDone, } from '../services/components.js';
7
7
  import { DebugLevel } from '../services/configuration.js';
8
+ import { getWarnings } from '../services/logger.js';
8
9
  import { getCancellationMessage } from '../services/messages.js';
9
10
  import { exitApp } from '../services/process.js';
10
11
  import { Component } from './Component.js';
@@ -35,11 +36,8 @@ export const Workflow = ({ initialQueue, debug }) => {
35
36
  return { active: null, pending };
36
37
  });
37
38
  }, []);
38
- // Global handlers for all stateful components
39
- const handlers = useMemo(() => ({
40
- addToQueue: (...items) => {
41
- setQueue((queue) => [...queue, ...items]);
42
- },
39
+ // Focused handler instances - segregated by responsibility
40
+ const stateHandlers = useMemo(() => ({
43
41
  updateState: (newState) => {
44
42
  setCurrent((curr) => {
45
43
  const { active, pending } = curr;
@@ -56,12 +54,41 @@ export const Workflow = ({ initialQueue, debug }) => {
56
54
  return { active: updated, pending };
57
55
  });
58
56
  },
57
+ }), []);
58
+ const lifecycleHandlers = useMemo(() => ({
59
59
  completeActive: (...items) => {
60
60
  moveActiveToPending();
61
61
  if (items.length > 0) {
62
62
  setQueue((queue) => [...items, ...queue]);
63
63
  }
64
64
  },
65
+ }), [moveActiveToPending]);
66
+ const queueHandlers = useMemo(() => ({
67
+ addToQueue: (...items) => {
68
+ setQueue((queue) => [...queue, ...items]);
69
+ },
70
+ }), []);
71
+ const errorHandlers = useMemo(() => ({
72
+ onAborted: (operation) => {
73
+ moveActiveToTimeline();
74
+ // Add feedback to queue
75
+ const message = getCancellationMessage(operation);
76
+ setQueue((queue) => [
77
+ ...queue,
78
+ createFeedback(FeedbackType.Aborted, message),
79
+ ]);
80
+ },
81
+ onError: (error) => {
82
+ moveActiveToTimeline();
83
+ // Add feedback to queue
84
+ setQueue((queue) => [
85
+ ...queue,
86
+ createFeedback(FeedbackType.Failed, error),
87
+ ]);
88
+ },
89
+ }), [moveActiveToTimeline]);
90
+ // Workflow handlers - used for timeline/queue management
91
+ const workflowHandlers = useMemo(() => ({
65
92
  completeActiveAndPending: (...items) => {
66
93
  setCurrent((curr) => {
67
94
  const { active, pending } = curr;
@@ -83,24 +110,7 @@ export const Workflow = ({ initialQueue, debug }) => {
83
110
  addToTimeline: (...items) => {
84
111
  setTimeline((prev) => [...prev, ...items]);
85
112
  },
86
- onAborted: (operation) => {
87
- moveActiveToTimeline();
88
- // Add feedback to queue
89
- const message = getCancellationMessage(operation);
90
- setQueue((queue) => [
91
- ...queue,
92
- createFeedback(FeedbackType.Aborted, message),
93
- ]);
94
- },
95
- onError: (error) => {
96
- moveActiveToTimeline();
97
- // Add feedback to queue
98
- setQueue((queue) => [
99
- ...queue,
100
- createFeedback(FeedbackType.Failed, error),
101
- ]);
102
- },
103
- }), [moveActiveToPending, moveActiveToTimeline]);
113
+ }), []);
104
114
  // Global Esc handler removed - components handle their own Esc individually
105
115
  // Move next item from queue to active
106
116
  useEffect(() => {
@@ -138,6 +148,14 @@ export const Workflow = ({ initialQueue, debug }) => {
138
148
  }
139
149
  // Stateful components stay in active until handlers move them to pending
140
150
  }, [current]);
151
+ // Check for accumulated warnings and add them to timeline
152
+ useEffect(() => {
153
+ const warningMessages = getWarnings();
154
+ if (warningMessages.length > 0) {
155
+ const warningComponents = warningMessages.map((msg) => markAsDone(createFeedback(FeedbackType.Warning, msg)));
156
+ setTimeline((prev) => [...prev, ...warningComponents]);
157
+ }
158
+ }, [timeline, current]);
141
159
  // Move final pending to timeline and exit when all done
142
160
  useEffect(() => {
143
161
  const { active, pending } = current;
@@ -171,23 +189,77 @@ export const Workflow = ({ initialQueue, debug }) => {
171
189
  if (isStateless(active)) {
172
190
  return _jsx(Component, { def: active, debug: debug }, active.id);
173
191
  }
174
- // For stateful components, inject global handlers
192
+ // For stateful components, inject focused handlers
175
193
  const statefulActive = active;
176
194
  const wrappedDef = {
177
195
  ...statefulActive,
178
196
  props: {
179
197
  ...statefulActive.props,
180
- handlers,
198
+ stateHandlers,
199
+ lifecycleHandlers,
200
+ queueHandlers,
201
+ errorHandlers,
202
+ workflowHandlers,
181
203
  },
182
204
  };
183
205
  return _jsx(Component, { def: wrappedDef, debug: debug }, active.id);
184
- }, [current, debug, handlers]);
206
+ }, [
207
+ current,
208
+ debug,
209
+ stateHandlers,
210
+ lifecycleHandlers,
211
+ queueHandlers,
212
+ errorHandlers,
213
+ workflowHandlers,
214
+ ]);
185
215
  const pendingComponent = useMemo(() => {
186
216
  const { pending } = current;
187
217
  if (!pending)
188
218
  return null;
189
- // Pending components don't receive input
190
- return _jsx(Component, { def: pending, debug: debug }, pending.id);
191
- }, [current, debug]);
192
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => (_jsx(Box, { marginTop: 1, children: _jsx(Component, { def: item, debug: DebugLevel.None }) }, item.id)) }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
219
+ // For stateless components, render as-is
220
+ if (isStateless(pending)) {
221
+ return _jsx(Component, { def: pending, debug: debug }, pending.id);
222
+ }
223
+ // For stateful components, inject focused handlers (they may have useEffect hooks)
224
+ const statefulPending = pending;
225
+ const wrappedDef = {
226
+ ...statefulPending,
227
+ props: {
228
+ ...statefulPending.props,
229
+ stateHandlers,
230
+ lifecycleHandlers,
231
+ queueHandlers,
232
+ errorHandlers,
233
+ workflowHandlers,
234
+ },
235
+ };
236
+ return _jsx(Component, { def: wrappedDef, debug: debug }, pending.id);
237
+ }, [
238
+ current,
239
+ debug,
240
+ stateHandlers,
241
+ lifecycleHandlers,
242
+ queueHandlers,
243
+ errorHandlers,
244
+ workflowHandlers,
245
+ ]);
246
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => {
247
+ // For stateful timeline components, inject handlers (useEffect hooks may still run)
248
+ let def = item;
249
+ if (!isStateless(item)) {
250
+ const statefulItem = item;
251
+ def = {
252
+ ...statefulItem,
253
+ props: {
254
+ ...statefulItem.props,
255
+ stateHandlers,
256
+ lifecycleHandlers,
257
+ queueHandlers,
258
+ errorHandlers,
259
+ workflowHandlers,
260
+ },
261
+ };
262
+ }
263
+ return (_jsx(Box, { marginTop: 1, children: _jsx(Component, { def: def, debug: DebugLevel.None }) }, item.id));
264
+ } }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
193
265
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-language-shell",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "Your personal command-line concierge. Ask politely, and it gets things done.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -51,7 +51,8 @@
51
51
  "ink": "^6.6.0",
52
52
  "ink-text-input": "^6.0.0",
53
53
  "react": "^19.2.3",
54
- "yaml": "^2.8.2"
54
+ "yaml": "^2.8.2",
55
+ "zod": "^4.2.1"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@types/node": "^25.0.3",