prompt-language-shell 0.8.0 → 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.
- package/dist/services/anthropic.js +50 -37
- package/dist/services/colors.js +21 -1
- package/dist/services/components.js +6 -14
- package/dist/services/config-labels.js +75 -0
- package/dist/services/config-utils.js +20 -0
- package/dist/services/configuration.js +38 -59
- package/dist/services/filesystem.js +114 -0
- package/dist/services/loader.js +8 -5
- package/dist/services/logger.js +24 -0
- package/dist/services/parser.js +3 -1
- package/dist/services/refinement.js +9 -9
- package/dist/services/registry.js +1 -1
- package/dist/services/router.js +29 -28
- package/dist/services/skills.js +15 -14
- package/dist/services/validator.js +4 -3
- package/dist/skills/introspect.md +52 -43
- package/dist/skills/schedule.md +8 -3
- package/dist/tools/introspect.tool.js +18 -9
- package/dist/types/guards.js +23 -0
- package/dist/types/handlers.js +1 -0
- package/dist/types/schemas.js +103 -0
- package/dist/types/types.js +7 -0
- package/dist/ui/Answer.js +11 -15
- package/dist/ui/Command.js +24 -20
- package/dist/ui/Config.js +44 -32
- package/dist/ui/Confirm.js +9 -9
- package/dist/ui/Execute.js +265 -75
- package/dist/ui/Feedback.js +1 -0
- package/dist/ui/Introspect.js +16 -61
- package/dist/ui/Main.js +6 -6
- package/dist/ui/Report.js +4 -8
- package/dist/ui/Schedule.js +12 -12
- package/dist/ui/Spinner.js +3 -1
- package/dist/ui/Subtask.js +1 -1
- package/dist/ui/Task.js +7 -6
- package/dist/ui/Validate.js +28 -21
- package/dist/ui/Workflow.js +102 -30
- package/package.json +3 -2
package/dist/ui/Main.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { FeedbackType } from '../types/types.js';
|
|
4
|
-
import { createAnthropicService
|
|
4
|
+
import { createAnthropicService } from '../services/anthropic.js';
|
|
5
5
|
import { createCommandDefinition, createConfigDefinitionWithKeys, createFeedback, createMessage, createWelcomeDefinition, } from '../services/components.js';
|
|
6
6
|
import { DebugLevel, getConfigurationRequiredMessage, getMissingConfigKeys, loadConfig, loadDebugSetting, saveConfig, saveDebugSetting, unflattenConfig, } from '../services/configuration.js';
|
|
7
7
|
import { registerGlobalShortcut } from '../services/keyboard.js';
|
|
8
8
|
import { initializeLogger, setDebugLevel } from '../services/logger.js';
|
|
9
9
|
import { Workflow } from './Workflow.js';
|
|
10
|
-
export const Main = ({ app, command }) => {
|
|
10
|
+
export const Main = ({ app, command, serviceFactory = createAnthropicService, }) => {
|
|
11
11
|
const [service, setService] = useState(null);
|
|
12
12
|
const [initialQueue, setInitialQueue] = useState(null);
|
|
13
13
|
const [debug, setDebugLevelState] = useState(() => loadDebugSetting());
|
|
@@ -44,7 +44,7 @@ export const Main = ({ app, command }) => {
|
|
|
44
44
|
// Config exists - create service immediately
|
|
45
45
|
try {
|
|
46
46
|
const config = loadConfig();
|
|
47
|
-
const newService =
|
|
47
|
+
const newService = serviceFactory(config.anthropic);
|
|
48
48
|
setService(newService);
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
@@ -56,7 +56,7 @@ export const Main = ({ app, command }) => {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
// If config is missing, service will be created after config completes
|
|
59
|
-
}, [service]);
|
|
59
|
+
}, [service, serviceFactory]);
|
|
60
60
|
// Initialize queue after service is ready
|
|
61
61
|
useEffect(() => {
|
|
62
62
|
// Only set initial queue once
|
|
@@ -75,7 +75,7 @@ export const Main = ({ app, command }) => {
|
|
|
75
75
|
}
|
|
76
76
|
// Load config and create service
|
|
77
77
|
const newConfig = loadConfig();
|
|
78
|
-
const newService =
|
|
78
|
+
const newService = serviceFactory(newConfig.anthropic);
|
|
79
79
|
setService(newService);
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
@@ -86,7 +86,7 @@ export const Main = ({ app, command }) => {
|
|
|
86
86
|
throw new Error(errorMessage);
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
|
-
const handleConfigAborted = (
|
|
89
|
+
const handleConfigAborted = (_operation) => {
|
|
90
90
|
// Config was cancelled
|
|
91
91
|
};
|
|
92
92
|
setInitialQueue([
|
package/dist/ui/Report.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { Colors } from '../services/colors.js';
|
|
4
|
-
function CapabilityItem({ name, description,
|
|
5
|
-
const color =
|
|
6
|
-
? Colors.Origin.Indirect
|
|
7
|
-
: isBuiltIn
|
|
8
|
-
? Colors.Origin.BuiltIn
|
|
9
|
-
: Colors.Origin.UserProvided;
|
|
3
|
+
import { Colors, getOriginColor } from '../services/colors.js';
|
|
4
|
+
function CapabilityItem({ name, description, origin, isIncomplete, }) {
|
|
5
|
+
const color = getOriginColor(origin);
|
|
10
6
|
return (_jsxs(Box, { children: [_jsx(Text, { children: "- " }), _jsx(Text, { color: color, children: name }), _jsxs(Text, { children: [" - ", description] }), isIncomplete && _jsx(Text, { color: Colors.Status.Warning, children: " (incomplete)" })] }));
|
|
11
7
|
}
|
|
12
8
|
export function Report({ message, capabilities }) {
|
|
13
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: capabilities.map((capability, index) => (_jsx(CapabilityItem, { name: capability.name, description: capability.description,
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: capabilities.map((capability, index) => (_jsx(CapabilityItem, { name: capability.name, description: capability.description, origin: capability.origin, isIncomplete: capability.isIncomplete }, index))) })] }));
|
|
14
10
|
}
|
package/dist/ui/Schedule.js
CHANGED
|
@@ -37,7 +37,7 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
37
37
|
const planColors = getTaskColors(TaskType.Schedule, isCurrent);
|
|
38
38
|
return {
|
|
39
39
|
description: {
|
|
40
|
-
text:
|
|
40
|
+
text: option,
|
|
41
41
|
color: colors.description,
|
|
42
42
|
highlightedColor: planColors.description,
|
|
43
43
|
},
|
|
@@ -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,
|
|
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,15 +95,15 @@ 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
|
-
|
|
99
|
-
onSelectionConfirmed(concreteTasks);
|
|
98
|
+
lifecycleHandlers?.completeActive();
|
|
99
|
+
void onSelectionConfirmed(concreteTasks);
|
|
100
100
|
}
|
|
101
101
|
}, [
|
|
102
102
|
isActive,
|
|
103
103
|
defineTaskIndices.length,
|
|
104
104
|
tasks,
|
|
105
105
|
onSelectionConfirmed,
|
|
106
|
-
|
|
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
|
-
|
|
114
|
+
errorHandlers?.onAborted('task selection');
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
if (key.downArrow) {
|
|
@@ -153,7 +153,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
|
|
|
153
153
|
// This is a Define task - only include the selected option
|
|
154
154
|
const options = task.params.options;
|
|
155
155
|
const selectedIndex = newCompletedSelections[defineGroupIndex];
|
|
156
|
-
const selectedOption =
|
|
156
|
+
const selectedOption = options[selectedIndex];
|
|
157
157
|
// Use Execute as default - LLM will properly classify during refinement
|
|
158
158
|
refinedTasks.push({
|
|
159
159
|
action: selectedOption,
|
|
@@ -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
|
-
|
|
174
|
-
onSelectionConfirmed(refinedTasks);
|
|
173
|
+
lifecycleHandlers?.completeActive();
|
|
174
|
+
void onSelectionConfirmed(refinedTasks);
|
|
175
175
|
}
|
|
176
176
|
else {
|
|
177
177
|
// No selection callback, just complete normally
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
+
stateHandlers,
|
|
196
196
|
]);
|
|
197
197
|
const listItems = tasks.map((task, idx) => {
|
|
198
198
|
// Find which define group this task belongs to (if any)
|
package/dist/ui/Spinner.js
CHANGED
|
@@ -17,7 +17,9 @@ export function Spinner() {
|
|
|
17
17
|
return next !== prev ? next : prev;
|
|
18
18
|
});
|
|
19
19
|
}, INTERVAL);
|
|
20
|
-
return () =>
|
|
20
|
+
return () => {
|
|
21
|
+
clearInterval(timer);
|
|
22
|
+
};
|
|
21
23
|
}, []);
|
|
22
24
|
return _jsx(Text, { color: Palette.Cyan, children: FRAMES[frame] });
|
|
23
25
|
}
|
package/dist/ui/Subtask.js
CHANGED
|
@@ -4,7 +4,7 @@ import { getStatusColors, Palette, STATUS_ICONS } from '../services/colors.js';
|
|
|
4
4
|
import { ExecutionStatus } from '../services/shell.js';
|
|
5
5
|
import { formatDuration } from '../services/utils.js';
|
|
6
6
|
import { Spinner } from './Spinner.js';
|
|
7
|
-
export function Subtask({ label, command, status, isActive, startTime, endTime, elapsed, }) {
|
|
7
|
+
export function Subtask({ label, command, status, isActive: _isActive, startTime, endTime, elapsed, }) {
|
|
8
8
|
const colors = getStatusColors(status);
|
|
9
9
|
const isCancelled = status === ExecutionStatus.Cancelled;
|
|
10
10
|
const isAborted = status === ExecutionStatus.Aborted;
|
package/dist/ui/Task.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import { ExecutionStatus, executeCommand, } from '../services/shell.js';
|
|
3
|
+
import { ExecutionResult, ExecutionStatus, executeCommand, } from '../services/shell.js';
|
|
4
4
|
import { calculateElapsed } from '../services/utils.js';
|
|
5
5
|
import { Subtask } from './Subtask.js';
|
|
6
6
|
export function Task({ label, command, isActive, index, initialStatus, initialElapsed, onComplete, onAbort, onError, }) {
|
|
@@ -19,7 +19,9 @@ export function Task({ label, command, isActive, index, initialStatus, initialEl
|
|
|
19
19
|
return next !== prev ? next : prev;
|
|
20
20
|
});
|
|
21
21
|
}, 1000);
|
|
22
|
-
return () =>
|
|
22
|
+
return () => {
|
|
23
|
+
clearInterval(interval);
|
|
24
|
+
};
|
|
23
25
|
}, [status, startTime]);
|
|
24
26
|
// Execute command when becoming active
|
|
25
27
|
useEffect(() => {
|
|
@@ -43,10 +45,10 @@ export function Task({ label, command, isActive, index, initialStatus, initialEl
|
|
|
43
45
|
setEndTime(end);
|
|
44
46
|
const taskDuration = calculateElapsed(start);
|
|
45
47
|
setElapsed(taskDuration);
|
|
46
|
-
setStatus(output.result ===
|
|
48
|
+
setStatus(output.result === ExecutionResult.Success
|
|
47
49
|
? ExecutionStatus.Success
|
|
48
50
|
: ExecutionStatus.Failed);
|
|
49
|
-
if (output.result ===
|
|
51
|
+
if (output.result === ExecutionResult.Success) {
|
|
50
52
|
onComplete?.(index, output, taskDuration);
|
|
51
53
|
}
|
|
52
54
|
else {
|
|
@@ -64,11 +66,10 @@ export function Task({ label, command, isActive, index, initialStatus, initialEl
|
|
|
64
66
|
onError?.(index, err instanceof Error ? err.message : 'Unknown error', errorDuration);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
|
-
execute();
|
|
69
|
+
void execute();
|
|
68
70
|
return () => {
|
|
69
71
|
mounted = false;
|
|
70
72
|
};
|
|
71
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
73
|
}, [isActive]);
|
|
73
74
|
// Handle abort when task becomes inactive while running
|
|
74
75
|
useEffect(() => {
|
package/dist/ui/Validate.js
CHANGED
|
@@ -4,35 +4,30 @@ 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 {
|
|
7
|
+
import { createConfigStepsFromSchema } from '../services/components.js';
|
|
8
8
|
import { DebugLevel, saveConfig, unflattenConfig, } from '../services/configuration.js';
|
|
9
|
+
import { saveConfigLabels } from '../services/config-labels.js';
|
|
9
10
|
import { useInput } from '../services/keyboard.js';
|
|
10
11
|
import { formatErrorMessage } from '../services/messages.js';
|
|
11
12
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
12
13
|
import { Config } from './Config.js';
|
|
13
14
|
import { Spinner } from './Spinner.js';
|
|
14
15
|
const MIN_PROCESSING_TIME = 1000;
|
|
15
|
-
export function Validate({ missingConfig, userRequest, state, status, service, children, debug = DebugLevel.None, onError, onComplete, onAborted,
|
|
16
|
+
export function Validate({ missingConfig, userRequest, state, status, service, children, debug = DebugLevel.None, onError, onComplete, onAborted, stateHandlers, lifecycleHandlers, workflowHandlers, }) {
|
|
16
17
|
const isActive = status === ComponentStatus.Active;
|
|
17
18
|
const [error, setError] = useState(state?.error ?? null);
|
|
18
19
|
const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
|
|
19
20
|
const [configRequirements, setConfigRequirements] = useState(state?.configRequirements ?? null);
|
|
20
|
-
const [showConfig, setShowConfig] = useState(false);
|
|
21
21
|
useInput((_, key) => {
|
|
22
|
-
if (key.escape && isActive
|
|
22
|
+
if (key.escape && isActive) {
|
|
23
23
|
onAborted('validation');
|
|
24
24
|
}
|
|
25
|
-
}, { isActive
|
|
25
|
+
}, { isActive });
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
// Skip processing if not active
|
|
28
28
|
if (!isActive) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
// Skip processing if no service available
|
|
32
|
-
if (!service) {
|
|
33
|
-
setError('No service available');
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
31
|
let mounted = true;
|
|
37
32
|
async function process(svc) {
|
|
38
33
|
const startTime = Date.now();
|
|
@@ -44,7 +39,9 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
44
39
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
45
40
|
if (mounted) {
|
|
46
41
|
// Add debug components to timeline if present
|
|
47
|
-
|
|
42
|
+
if (result.debug?.length) {
|
|
43
|
+
workflowHandlers?.addToTimeline(...result.debug);
|
|
44
|
+
}
|
|
48
45
|
// Extract CONFIG tasks with descriptions from result
|
|
49
46
|
const configTasks = result.tasks.filter((task) => task.type === TaskType.Config);
|
|
50
47
|
// Build ConfigRequirements with descriptions
|
|
@@ -73,7 +70,7 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
73
70
|
setCompletionMessage(message);
|
|
74
71
|
setConfigRequirements(withDescriptions);
|
|
75
72
|
// Save state after validation completes
|
|
76
|
-
|
|
73
|
+
stateHandlers?.updateState({
|
|
77
74
|
completionMessage: message,
|
|
78
75
|
configRequirements: withDescriptions,
|
|
79
76
|
validated: true,
|
|
@@ -87,19 +84,17 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
87
84
|
const errorMessage = formatErrorMessage(err);
|
|
88
85
|
setError(errorMessage);
|
|
89
86
|
// Save error state
|
|
90
|
-
|
|
87
|
+
stateHandlers?.updateState({
|
|
91
88
|
error: errorMessage,
|
|
92
89
|
completionMessage: null,
|
|
93
90
|
configRequirements: null,
|
|
94
91
|
validated: false,
|
|
95
92
|
});
|
|
96
|
-
|
|
97
|
-
onError(errorMessage);
|
|
98
|
-
}
|
|
93
|
+
onError(errorMessage);
|
|
99
94
|
}
|
|
100
95
|
}
|
|
101
96
|
}
|
|
102
|
-
process(service);
|
|
97
|
+
void process(service);
|
|
103
98
|
return () => {
|
|
104
99
|
mounted = false;
|
|
105
100
|
};
|
|
@@ -133,22 +128,34 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
133
128
|
const handleConfigFinished = (config) => {
|
|
134
129
|
// Convert flat dotted keys to nested structure grouped by section
|
|
135
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
|
+
}
|
|
136
141
|
// Save each section
|
|
137
142
|
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
138
143
|
saveConfig(section, sectionConfig);
|
|
139
144
|
}
|
|
140
145
|
// Mark validation component as complete before invoking callback
|
|
141
146
|
// This allows the workflow to proceed to execution
|
|
142
|
-
|
|
147
|
+
lifecycleHandlers?.completeActive();
|
|
143
148
|
// Invoke callback which will queue the Execute component
|
|
144
|
-
|
|
149
|
+
if (configRequirements) {
|
|
150
|
+
onComplete(configRequirements);
|
|
151
|
+
}
|
|
145
152
|
};
|
|
146
153
|
const handleConfigAborted = (operation) => {
|
|
147
154
|
// Mark validation component as complete when aborted
|
|
148
|
-
|
|
155
|
+
lifecycleHandlers?.completeActive();
|
|
149
156
|
onAborted(operation);
|
|
150
157
|
};
|
|
151
|
-
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
|
|
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] }));
|
|
152
159
|
}
|
|
153
160
|
/**
|
|
154
161
|
* Build prompt for VALIDATE tool
|
package/dist/ui/Workflow.js
CHANGED
|
@@ -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
|
-
//
|
|
39
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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.
|
|
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",
|