prompt-language-shell 0.8.4 → 0.8.8

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.
Files changed (41) hide show
  1. package/dist/configuration/io.js +85 -0
  2. package/dist/configuration/messages.js +30 -0
  3. package/dist/configuration/schema.js +167 -0
  4. package/dist/configuration/transformation.js +55 -0
  5. package/dist/configuration/types.js +30 -0
  6. package/dist/configuration/validation.js +52 -0
  7. package/dist/execution/handlers.js +135 -0
  8. package/dist/execution/processing.js +36 -0
  9. package/dist/execution/reducer.js +148 -0
  10. package/dist/execution/types.js +12 -0
  11. package/dist/execution/validation.js +12 -0
  12. package/dist/index.js +1 -1
  13. package/dist/services/anthropic.js +2 -1
  14. package/dist/services/colors.js +22 -12
  15. package/dist/services/components.js +35 -11
  16. package/dist/services/config-labels.js +15 -15
  17. package/dist/services/logger.js +2 -1
  18. package/dist/services/messages.js +53 -1
  19. package/dist/services/refinement.js +11 -6
  20. package/dist/services/router.js +92 -52
  21. package/dist/skills/execute.md +79 -9
  22. package/dist/skills/schedule.md +121 -29
  23. package/dist/tools/execute.tool.js +4 -0
  24. package/dist/types/schemas.js +1 -0
  25. package/dist/ui/Answer.js +36 -15
  26. package/dist/ui/Command.js +43 -23
  27. package/dist/ui/Component.js +147 -33
  28. package/dist/ui/Config.js +73 -79
  29. package/dist/ui/Confirm.js +34 -21
  30. package/dist/ui/Execute.js +129 -329
  31. package/dist/ui/Feedback.js +2 -1
  32. package/dist/ui/Introspect.js +51 -24
  33. package/dist/ui/Label.js +4 -3
  34. package/dist/ui/List.js +3 -2
  35. package/dist/ui/Main.js +5 -1
  36. package/dist/ui/Refinement.js +8 -1
  37. package/dist/ui/Schedule.js +89 -61
  38. package/dist/ui/Validate.js +75 -77
  39. package/dist/ui/Workflow.js +47 -123
  40. package/package.json +1 -1
  41. package/dist/services/configuration.js +0 -409
@@ -1,23 +1,35 @@
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
+ import { ComponentStatus, } from '../types/components.js';
5
5
  import { TaskType } from '../types/types.js';
6
+ import { saveConfig } from '../configuration/io.js';
7
+ import { unflattenConfig } from '../configuration/transformation.js';
6
8
  import { Colors, getTextColor } from '../services/colors.js';
7
- import { createConfigStepsFromSchema } from '../services/components.js';
8
- import { DebugLevel, saveConfig, unflattenConfig, } from '../services/configuration.js';
9
+ import { createConfigDefinitionWithKeys, createMessage, } from '../services/components.js';
9
10
  import { saveConfigLabels } from '../services/config-labels.js';
10
11
  import { useInput } from '../services/keyboard.js';
11
- import { formatErrorMessage } from '../services/messages.js';
12
+ import { formatErrorMessage, getUnresolvedPlaceholdersMessage, } from '../services/messages.js';
12
13
  import { ensureMinimumTime } from '../services/timing.js';
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, stateHandlers, lifecycleHandlers, workflowHandlers, }) {
16
+ export const ValidateView = ({ state, status }) => {
17
17
  const isActive = status === ComponentStatus.Active;
18
- const [error, setError] = useState(state?.error ?? null);
19
- const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
20
- const [configRequirements, setConfigRequirements] = useState(state?.configRequirements ?? null);
18
+ const { error, completionMessage } = state;
19
+ // Don't render when not active and nothing to show
20
+ if (!isActive && !completionMessage && !error) {
21
+ return null;
22
+ }
23
+ 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] }) }))] }));
24
+ };
25
+ /**
26
+ * Validate controller: Validates missing config
27
+ */
28
+ export function Validate({ missingConfig, userRequest, status, service, onError, requestHandlers, onValidationComplete, onAborted, lifecycleHandlers, workflowHandlers, }) {
29
+ const isActive = status === ComponentStatus.Active;
30
+ const [error, setError] = useState(null);
31
+ const [completionMessage, setCompletionMessage] = useState(null);
32
+ const [configRequirements, setConfigRequirements] = useState([]);
21
33
  useInput((_, key) => {
22
34
  if (key.escape && isActive) {
23
35
  onAborted('validation');
@@ -40,7 +52,7 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
40
52
  if (mounted) {
41
53
  // Add debug components to timeline if present
42
54
  if (result.debug?.length) {
43
- workflowHandlers?.addToTimeline(...result.debug);
55
+ workflowHandlers.addToTimeline(...result.debug);
44
56
  }
45
57
  // Extract CONFIG tasks with descriptions from result
46
58
  const configTasks = result.tasks.filter((task) => task.type === TaskType.Config);
@@ -57,25 +69,50 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
57
69
  };
58
70
  });
59
71
  // Build completion message showing which config properties are needed
60
- const count = withDescriptions.length;
61
- const propertyWord = count === 1 ? 'property' : 'properties';
62
- // Shuffle between different message variations
63
- const messages = [
64
- `Additional configuration ${propertyWord} required.`,
65
- `Configuration ${propertyWord} needed.`,
66
- `Missing configuration ${propertyWord} detected.`,
67
- `Setup requires configuration ${propertyWord}.`,
68
- ];
69
- const message = messages[Math.floor(Math.random() * messages.length)];
72
+ const message = getUnresolvedPlaceholdersMessage(withDescriptions.length);
70
73
  setCompletionMessage(message);
71
74
  setConfigRequirements(withDescriptions);
72
- // Save state after validation completes
73
- stateHandlers?.updateState({
75
+ // Add validation message to timeline before Config component
76
+ workflowHandlers.addToTimeline(createMessage(message));
77
+ // Create Config component and add to queue
78
+ const keys = withDescriptions.map((req) => req.path);
79
+ const configDef = createConfigDefinitionWithKeys(keys, (config) => {
80
+ // Convert flat dotted keys to nested structure grouped by section
81
+ const configBySection = unflattenConfig(config);
82
+ // Extract and save labels to cache
83
+ const labels = {};
84
+ for (const req of withDescriptions) {
85
+ if (req.description) {
86
+ labels[req.path] = req.description;
87
+ }
88
+ }
89
+ saveConfigLabels(labels);
90
+ // Save each section
91
+ for (const [section, sectionConfig] of Object.entries(configBySection)) {
92
+ saveConfig(section, sectionConfig);
93
+ }
94
+ // After config is saved, invoke callback to add Execute component to queue
95
+ onValidationComplete(withDescriptions);
96
+ }, (operation) => {
97
+ onAborted(operation);
98
+ });
99
+ // Override descriptions with LLM-generated ones
100
+ if ('props' in configDef && 'steps' in configDef.props) {
101
+ configDef.props.steps = configDef.props.steps.map((step, index) => ({
102
+ ...step,
103
+ description: withDescriptions[index].description ||
104
+ withDescriptions[index].path,
105
+ }));
106
+ }
107
+ workflowHandlers.addToQueue(configDef);
108
+ lifecycleHandlers.completeActive();
109
+ const finalState = {
110
+ error: null,
74
111
  completionMessage: message,
75
112
  configRequirements: withDescriptions,
76
113
  validated: true,
77
- error: null,
78
- });
114
+ };
115
+ requestHandlers.onCompleted(finalState);
79
116
  }
80
117
  }
81
118
  catch (err) {
@@ -83,13 +120,13 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
83
120
  if (mounted) {
84
121
  const errorMessage = formatErrorMessage(err);
85
122
  setError(errorMessage);
86
- // Save error state
87
- stateHandlers?.updateState({
123
+ const finalState = {
88
124
  error: errorMessage,
89
125
  completionMessage: null,
90
- configRequirements: null,
126
+ configRequirements: [],
91
127
  validated: false,
92
- });
128
+ };
129
+ requestHandlers.onCompleted(finalState);
93
130
  onError(errorMessage);
94
131
  }
95
132
  }
@@ -103,59 +140,20 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
103
140
  userRequest,
104
141
  isActive,
105
142
  service,
106
- onComplete,
143
+ requestHandlers,
107
144
  onError,
108
145
  onAborted,
146
+ onValidationComplete,
147
+ lifecycleHandlers,
148
+ workflowHandlers,
109
149
  ]);
110
- // Don't render when not active and nothing to show
111
- if (!isActive && !completionMessage && !error && !children) {
112
- return null;
113
- }
114
- // Create ConfigSteps from requirements using createConfigStepsFromSchema
115
- // to load current values from config file, then override descriptions
116
- const configSteps = configRequirements
117
- ? (() => {
118
- const keys = configRequirements.map((req) => req.path);
119
- const steps = createConfigStepsFromSchema(keys);
120
- // Override descriptions with LLM-generated ones
121
- return steps.map((step, index) => ({
122
- ...step,
123
- description: configRequirements[index].description ||
124
- configRequirements[index].path,
125
- }));
126
- })()
127
- : null;
128
- const handleConfigFinished = (config) => {
129
- // Convert flat dotted keys to nested structure grouped by section
130
- const configBySection = unflattenConfig(config);
131
- // Extract and save labels to cache
132
- if (configRequirements) {
133
- const labels = {};
134
- for (const req of configRequirements) {
135
- if (req.description) {
136
- labels[req.path] = req.description;
137
- }
138
- }
139
- saveConfigLabels(labels);
140
- }
141
- // Save each section
142
- for (const [section, sectionConfig] of Object.entries(configBySection)) {
143
- saveConfig(section, sectionConfig);
144
- }
145
- // Mark validation component as complete before invoking callback
146
- // This allows the workflow to proceed to execution
147
- lifecycleHandlers?.completeActive();
148
- // Invoke callback which will queue the Execute component
149
- if (configRequirements) {
150
- onComplete(configRequirements);
151
- }
152
- };
153
- const handleConfigAborted = (operation) => {
154
- // Mark validation component as complete when aborted
155
- lifecycleHandlers?.completeActive();
156
- onAborted(operation);
150
+ const state = {
151
+ error,
152
+ completionMessage,
153
+ configRequirements,
154
+ validated: error === null && completionMessage !== null,
157
155
  };
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] }));
156
+ return _jsx(ValidateView, { state: state, status: status });
159
157
  }
160
158
  /**
161
159
  * Build prompt for VALIDATE tool
@@ -3,12 +3,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { Box, Static } from 'ink';
4
4
  import { ComponentStatus, } from '../types/components.js';
5
5
  import { ComponentName, FeedbackType } from '../types/types.js';
6
- import { createFeedback, isStateless, markAsDone, } from '../services/components.js';
7
- import { DebugLevel } from '../services/configuration.js';
6
+ import { createFeedback, isSimple, markAsDone, } from '../services/components.js';
8
7
  import { getWarnings } from '../services/logger.js';
9
8
  import { getCancellationMessage } from '../services/messages.js';
10
9
  import { exitApp } from '../services/process.js';
11
- import { Component } from './Component.js';
10
+ import { SimpleComponent, ControllerComponent, TimelineComponent, } from './Component.js';
12
11
  export const Workflow = ({ initialQueue, debug }) => {
13
12
  const [timeline, setTimeline] = useState([]);
14
13
  const [current, setCurrent] = useState({ active: null, pending: null });
@@ -36,25 +35,39 @@ export const Workflow = ({ initialQueue, debug }) => {
36
35
  return { active: null, pending };
37
36
  });
38
37
  }, []);
39
- // Focused handler instances - segregated by responsibility
40
- const stateHandlers = useMemo(() => ({
41
- updateState: (newState) => {
38
+ // Request handlers - manages errors, aborts, and completions
39
+ const requestHandlers = useMemo(() => ({
40
+ onError: (error) => {
41
+ moveActiveToTimeline();
42
+ // Add feedback to queue
43
+ setQueue((queue) => [
44
+ ...queue,
45
+ createFeedback(FeedbackType.Failed, error),
46
+ ]);
47
+ },
48
+ onAborted: (operation) => {
49
+ moveActiveToTimeline();
50
+ // Clear queue and add only feedback to prevent subsequent components from executing
51
+ const message = getCancellationMessage(operation);
52
+ setQueue([createFeedback(FeedbackType.Aborted, message)]);
53
+ },
54
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
55
+ onCompleted: (finalState) => {
42
56
  setCurrent((curr) => {
43
57
  const { active, pending } = curr;
44
58
  if (!active || !('state' in active))
45
59
  return curr;
46
- const stateful = active;
60
+ // Save final state to definition
61
+ const managed = active;
47
62
  const updated = {
48
- ...stateful,
49
- state: {
50
- ...stateful.state,
51
- ...newState,
52
- },
63
+ ...managed,
64
+ state: finalState,
53
65
  };
54
66
  return { active: updated, pending };
55
67
  });
56
68
  },
57
- }), []);
69
+ }), [moveActiveToTimeline]);
70
+ // Lifecycle handlers - for components with active/pending states
58
71
  const lifecycleHandlers = useMemo(() => ({
59
72
  completeActive: (...items) => {
60
73
  moveActiveToPending();
@@ -62,33 +75,6 @@ export const Workflow = ({ initialQueue, debug }) => {
62
75
  setQueue((queue) => [...items, ...queue]);
63
76
  }
64
77
  },
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(() => ({
92
78
  completeActiveAndPending: (...items) => {
93
79
  setCurrent((curr) => {
94
80
  const { active, pending } = curr;
@@ -107,6 +93,12 @@ export const Workflow = ({ initialQueue, debug }) => {
107
93
  setQueue((queue) => [...items, ...queue]);
108
94
  }
109
95
  },
96
+ }), [moveActiveToPending]);
97
+ // Workflow handlers - manages queue and timeline
98
+ const workflowHandlers = useMemo(() => ({
99
+ addToQueue: (...items) => {
100
+ setQueue((queue) => [...queue, ...items]);
101
+ },
110
102
  addToTimeline: (...items) => {
111
103
  setTimeline((prev) => [...prev, ...items]);
112
104
  },
@@ -140,13 +132,13 @@ export const Workflow = ({ initialQueue, debug }) => {
140
132
  const { active, pending } = current;
141
133
  if (!active)
142
134
  return;
143
- if (isStateless(active)) {
144
- // Stateless components move directly to timeline
135
+ if (isSimple(active)) {
136
+ // Simple components move directly to timeline
145
137
  const doneComponent = markAsDone(active);
146
138
  setTimeline((prev) => [...prev, doneComponent]);
147
139
  setCurrent({ active: null, pending });
148
140
  }
149
- // Stateful components stay in active until handlers move them to pending
141
+ // Managed components stay in active until handlers move them to pending
150
142
  }, [current]);
151
143
  // Check for accumulated warnings and add them to timeline
152
144
  useEffect(() => {
@@ -180,86 +172,18 @@ export const Workflow = ({ initialQueue, debug }) => {
180
172
  lastItem.props.type === FeedbackType.Failed;
181
173
  exitApp(isFailed ? 1 : 0);
182
174
  }, [current, queue, timeline]);
183
- // Render active and pending components
184
- const activeComponent = useMemo(() => {
185
- const { active } = current;
186
- if (!active)
187
- return null;
188
- // For stateless components, render as-is
189
- if (isStateless(active)) {
190
- return _jsx(Component, { def: active, debug: debug }, active.id);
191
- }
192
- // For stateful components, inject focused handlers
193
- const statefulActive = active;
194
- const wrappedDef = {
195
- ...statefulActive,
196
- props: {
197
- ...statefulActive.props,
198
- stateHandlers,
199
- lifecycleHandlers,
200
- queueHandlers,
201
- errorHandlers,
202
- workflowHandlers,
203
- },
204
- };
205
- return _jsx(Component, { def: wrappedDef, debug: debug }, active.id);
206
- }, [
207
- current,
208
- debug,
209
- stateHandlers,
210
- lifecycleHandlers,
211
- queueHandlers,
212
- errorHandlers,
213
- workflowHandlers,
214
- ]);
215
- const pendingComponent = useMemo(() => {
216
- const { pending } = current;
217
- if (!pending)
175
+ // Render component with handlers (used for both active and pending)
176
+ const renderComponent = useCallback((def, status) => {
177
+ if (!def)
218
178
  return null;
219
- // For stateless components, render as-is
220
- if (isStateless(pending)) {
221
- return _jsx(Component, { def: pending, debug: debug }, pending.id);
179
+ // For simple components, render as-is
180
+ if (isSimple(def)) {
181
+ return _jsx(SimpleComponent, { def: def }, def.id);
222
182
  }
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 })] }));
183
+ // For managed components, inject handlers via ControllerComponent
184
+ return (_jsx(ControllerComponent, { def: { ...def, status }, debug: debug, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers }, def.id));
185
+ }, [debug, requestHandlers, lifecycleHandlers, workflowHandlers]);
186
+ const activeComponent = useMemo(() => renderComponent(current.active, ComponentStatus.Active), [current.active, renderComponent]);
187
+ const pendingComponent = useMemo(() => renderComponent(current.pending, ComponentStatus.Pending), [current.pending, renderComponent]);
188
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => (_jsx(Box, { marginTop: 1, children: _jsx(TimelineComponent, { def: item }) }, item.id)) }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
265
189
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-language-shell",
3
- "version": "0.8.4",
3
+ "version": "0.8.8",
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",