prompt-language-shell 0.8.6 → 0.9.0
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/README.md +0 -1
- package/dist/configuration/io.js +22 -1
- package/dist/configuration/types.js +3 -4
- package/dist/execution/handlers.js +20 -29
- package/dist/execution/processing.js +13 -1
- package/dist/execution/reducer.js +17 -37
- package/dist/execution/utils.js +6 -0
- package/dist/services/anthropic.js +1 -0
- package/dist/services/colors.js +21 -11
- package/dist/services/components.js +1 -2
- package/dist/services/filesystem.js +21 -1
- package/dist/services/messages.js +15 -0
- package/dist/services/process.js +7 -2
- package/dist/services/refinement.js +5 -0
- package/dist/services/router.js +136 -82
- package/dist/services/shell.js +179 -10
- package/dist/services/skills.js +2 -1
- package/dist/skills/answer.md +14 -12
- package/dist/skills/execute.md +139 -28
- package/dist/skills/introspect.md +9 -9
- package/dist/skills/schedule.md +121 -35
- package/dist/tools/execute.tool.js +4 -0
- package/dist/types/errors.js +47 -0
- package/dist/types/result.js +40 -0
- package/dist/types/schemas.js +1 -0
- package/dist/ui/Component.js +2 -2
- package/dist/ui/Config.js +6 -2
- package/dist/ui/Execute.js +146 -102
- package/dist/ui/Feedback.js +2 -1
- package/dist/ui/Label.js +3 -2
- package/dist/ui/List.js +3 -2
- package/dist/ui/Output.js +54 -0
- package/dist/ui/Schedule.js +16 -11
- package/dist/ui/Task.js +99 -10
- package/dist/ui/Workflow.js +2 -5
- package/package.json +1 -1
package/dist/ui/Execute.js
CHANGED
|
@@ -1,83 +1,118 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useReducer } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { getTextColor } from '../services/colors.js';
|
|
6
6
|
import { useInput } from '../services/keyboard.js';
|
|
7
|
-
import { formatErrorMessage } from '../services/messages.js';
|
|
7
|
+
import { formatErrorMessage, getExecutionErrorMessage, } from '../services/messages.js';
|
|
8
8
|
import { ExecutionStatus } from '../services/shell.js';
|
|
9
9
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
10
10
|
import { buildAbortedState, handleTaskCompletion, handleTaskFailure, } from '../execution/handlers.js';
|
|
11
11
|
import { processTasks } from '../execution/processing.js';
|
|
12
12
|
import { executeReducer, initialState } from '../execution/reducer.js';
|
|
13
13
|
import { ExecuteActionType } from '../execution/types.js';
|
|
14
|
+
import { createFeedback, createMessage, markAsDone, } from '../services/components.js';
|
|
15
|
+
import { FeedbackType } from '../types/types.js';
|
|
14
16
|
import { Message } from './Message.js';
|
|
15
17
|
import { Spinner } from './Spinner.js';
|
|
16
18
|
import { Task } from './Task.js';
|
|
17
19
|
const MINIMUM_PROCESSING_TIME = 400;
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Create an ExecuteState with defaults
|
|
22
|
+
*/
|
|
23
|
+
function createExecuteState(overrides = {}) {
|
|
24
|
+
return {
|
|
25
|
+
message: '',
|
|
26
|
+
summary: '',
|
|
27
|
+
tasks: [],
|
|
28
|
+
completed: 0,
|
|
29
|
+
completionMessage: null,
|
|
30
|
+
error: null,
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export const ExecuteView = ({ state, status, workdir, onOutputChange, onTaskComplete, onTaskAbort, onTaskError, }) => {
|
|
19
35
|
const isActive = status === ComponentStatus.Active;
|
|
20
|
-
const { error,
|
|
21
|
-
const hasProcessed =
|
|
36
|
+
const { error, tasks, message, completed, completionMessage } = state;
|
|
37
|
+
const hasProcessed = tasks.length > 0;
|
|
22
38
|
// Derive loading state from current conditions
|
|
23
|
-
const isLoading = isActive &&
|
|
24
|
-
const isExecuting = completed <
|
|
39
|
+
const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
|
|
40
|
+
const isExecuting = completed < tasks.length;
|
|
25
41
|
// Return null only when loading completes with no commands
|
|
26
|
-
if (!isActive &&
|
|
42
|
+
if (!isActive && tasks.length === 0 && !error) {
|
|
27
43
|
return null;
|
|
28
44
|
}
|
|
29
45
|
// Show completed steps when not active
|
|
30
|
-
const showTasks = !isActive &&
|
|
31
|
-
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showTasks) && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [message && (_jsxs(Box, { marginBottom: 1, gap: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: message }), isExecuting && _jsx(Spinner, {})] })),
|
|
46
|
+
const showTasks = !isActive && tasks.length > 0;
|
|
47
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showTasks) && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [message && (_jsxs(Box, { marginBottom: 1, gap: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: message }), isExecuting && _jsx(Spinner, {})] })), tasks.map((taskInfo, index) => {
|
|
48
|
+
// Merge workdir into active task's command
|
|
49
|
+
const taskCommand = isActive && index === completed && workdir
|
|
50
|
+
? { ...taskInfo.command, workdir }
|
|
51
|
+
: taskInfo.command;
|
|
52
|
+
return (_jsx(Box, { marginBottom: index < tasks.length - 1 ? 1 : 0, children: _jsx(Task, { label: taskInfo.label, command: taskCommand, isActive: isActive && index === completed, isFinished: index < completed, index: index, initialStatus: taskInfo.status, initialElapsed: taskInfo.elapsed, initialOutput: taskInfo.stdout || taskInfo.stderr || taskInfo.error
|
|
53
|
+
? {
|
|
54
|
+
stdout: taskInfo.stdout ?? '',
|
|
55
|
+
stderr: taskInfo.stderr ?? '',
|
|
56
|
+
error: taskInfo.error ?? '',
|
|
57
|
+
}
|
|
58
|
+
: undefined, onOutputChange: onOutputChange, onComplete: onTaskComplete, onAbort: onTaskAbort, onError: onTaskError }) }, index));
|
|
59
|
+
})] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && _jsx(Message, { text: error, status: status })] }));
|
|
32
60
|
};
|
|
33
61
|
/**
|
|
34
62
|
* Execute controller: Runs tasks sequentially
|
|
35
63
|
*/
|
|
36
|
-
export function Execute({ tasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
|
|
64
|
+
export function Execute({ tasks: inputTasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
|
|
37
65
|
const isActive = status === ComponentStatus.Active;
|
|
38
66
|
const [localState, dispatch] = useReducer(executeReducer, initialState);
|
|
39
|
-
|
|
67
|
+
// Ref to store current output for each task (avoids re-renders)
|
|
68
|
+
const taskOutputRef = useRef(new Map());
|
|
69
|
+
// Track working directory across commands (persists cd changes)
|
|
70
|
+
const workdirRef = useRef(undefined);
|
|
71
|
+
const { error, tasks, message, completed, hasProcessed, completionMessage, summary, } = localState;
|
|
40
72
|
// Derive loading state from current conditions
|
|
41
|
-
const isLoading = isActive &&
|
|
42
|
-
const isExecuting = completed <
|
|
73
|
+
const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
|
|
74
|
+
const isExecuting = completed < tasks.length;
|
|
75
|
+
// Handle output changes from Task - store in ref (no re-render)
|
|
76
|
+
const handleOutputChange = useCallback((index, taskOutput) => {
|
|
77
|
+
taskOutputRef.current.set(index, taskOutput);
|
|
78
|
+
}, []);
|
|
43
79
|
// Handle cancel with useCallback to ensure we capture latest state
|
|
44
80
|
const handleCancel = useCallback(() => {
|
|
45
81
|
dispatch({
|
|
46
82
|
type: ExecuteActionType.CancelExecution,
|
|
47
83
|
payload: { completed },
|
|
48
84
|
});
|
|
49
|
-
// Get updated task infos after cancel
|
|
50
|
-
const updatedTaskInfos =
|
|
85
|
+
// Get updated task infos after cancel, merging output from ref
|
|
86
|
+
const updatedTaskInfos = tasks.map((task, taskIndex) => {
|
|
87
|
+
const output = taskOutputRef.current.get(taskIndex);
|
|
88
|
+
const baseTask = output
|
|
89
|
+
? {
|
|
90
|
+
...task,
|
|
91
|
+
stdout: output.stdout,
|
|
92
|
+
stderr: output.stderr,
|
|
93
|
+
error: output.error,
|
|
94
|
+
}
|
|
95
|
+
: task;
|
|
51
96
|
if (taskIndex < completed) {
|
|
52
|
-
return { ...
|
|
97
|
+
return { ...baseTask, status: ExecutionStatus.Success };
|
|
53
98
|
}
|
|
54
99
|
else if (taskIndex === completed) {
|
|
55
|
-
return { ...
|
|
100
|
+
return { ...baseTask, status: ExecutionStatus.Aborted };
|
|
56
101
|
}
|
|
57
102
|
else {
|
|
58
|
-
return { ...
|
|
103
|
+
return { ...baseTask, status: ExecutionStatus.Cancelled };
|
|
59
104
|
}
|
|
60
105
|
});
|
|
61
106
|
// Expose final state
|
|
62
|
-
const finalState = {
|
|
107
|
+
const finalState = createExecuteState({
|
|
63
108
|
message,
|
|
64
109
|
summary,
|
|
65
|
-
|
|
110
|
+
tasks: updatedTaskInfos,
|
|
66
111
|
completed,
|
|
67
|
-
|
|
68
|
-
completionMessage: null,
|
|
69
|
-
error: null,
|
|
70
|
-
};
|
|
112
|
+
});
|
|
71
113
|
requestHandlers.onCompleted(finalState);
|
|
72
114
|
requestHandlers.onAborted('execution');
|
|
73
|
-
}, [
|
|
74
|
-
message,
|
|
75
|
-
summary,
|
|
76
|
-
taskInfos,
|
|
77
|
-
completed,
|
|
78
|
-
taskExecutionTimes,
|
|
79
|
-
requestHandlers,
|
|
80
|
-
]);
|
|
115
|
+
}, [message, summary, tasks, completed, requestHandlers]);
|
|
81
116
|
useInput((_, key) => {
|
|
82
117
|
if (key.escape && (isLoading || isExecuting) && isActive) {
|
|
83
118
|
handleCancel();
|
|
@@ -85,14 +120,14 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
|
|
|
85
120
|
}, { isActive: (isLoading || isExecuting) && isActive });
|
|
86
121
|
// Process tasks to get commands from AI
|
|
87
122
|
useEffect(() => {
|
|
88
|
-
if (!isActive ||
|
|
123
|
+
if (!isActive || tasks.length > 0 || hasProcessed) {
|
|
89
124
|
return;
|
|
90
125
|
}
|
|
91
126
|
let mounted = true;
|
|
92
127
|
async function process(svc) {
|
|
93
128
|
const startTime = Date.now();
|
|
94
129
|
try {
|
|
95
|
-
const result = await processTasks(
|
|
130
|
+
const result = await processTasks(inputTasks, svc);
|
|
96
131
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
97
132
|
if (!mounted)
|
|
98
133
|
return;
|
|
@@ -101,47 +136,46 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
|
|
|
101
136
|
workflowHandlers.addToTimeline(...result.debug);
|
|
102
137
|
}
|
|
103
138
|
if (result.commands.length === 0) {
|
|
139
|
+
// Check if this is an error response (has error field)
|
|
140
|
+
if (result.error) {
|
|
141
|
+
// Add error message to timeline
|
|
142
|
+
const errorMessage = getExecutionErrorMessage(result.error);
|
|
143
|
+
workflowHandlers.addToTimeline(markAsDone(createMessage(errorMessage)));
|
|
144
|
+
// Complete without error in state (message already in timeline)
|
|
145
|
+
requestHandlers.onCompleted(createExecuteState({ message: result.message }));
|
|
146
|
+
lifecycleHandlers.completeActive();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// No commands and no error - just complete
|
|
104
150
|
dispatch({
|
|
105
151
|
type: ExecuteActionType.ProcessingComplete,
|
|
106
152
|
payload: { message: result.message },
|
|
107
153
|
});
|
|
108
|
-
|
|
109
|
-
message: result.message,
|
|
110
|
-
summary: '',
|
|
111
|
-
taskInfos: [],
|
|
112
|
-
completed: 0,
|
|
113
|
-
taskExecutionTimes: [],
|
|
114
|
-
completionMessage: null,
|
|
115
|
-
error: null,
|
|
116
|
-
};
|
|
117
|
-
requestHandlers.onCompleted(finalState);
|
|
154
|
+
requestHandlers.onCompleted(createExecuteState({ message: result.message }));
|
|
118
155
|
lifecycleHandlers.completeActive();
|
|
119
156
|
return;
|
|
120
157
|
}
|
|
121
158
|
// Create task infos from commands
|
|
122
|
-
const
|
|
123
|
-
label:
|
|
159
|
+
const tasks = result.commands.map((cmd, index) => ({
|
|
160
|
+
label: inputTasks[index]?.action ?? cmd.description,
|
|
124
161
|
command: cmd,
|
|
162
|
+
status: ExecutionStatus.Pending,
|
|
163
|
+
elapsed: 0,
|
|
125
164
|
}));
|
|
126
165
|
dispatch({
|
|
127
166
|
type: ExecuteActionType.CommandsReady,
|
|
128
167
|
payload: {
|
|
129
168
|
message: result.message,
|
|
130
169
|
summary: result.summary,
|
|
131
|
-
|
|
170
|
+
tasks,
|
|
132
171
|
},
|
|
133
172
|
});
|
|
134
173
|
// Update state after AI processing
|
|
135
|
-
|
|
174
|
+
requestHandlers.onCompleted(createExecuteState({
|
|
136
175
|
message: result.message,
|
|
137
176
|
summary: result.summary,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
taskExecutionTimes: [],
|
|
141
|
-
completionMessage: null,
|
|
142
|
-
error: null,
|
|
143
|
-
};
|
|
144
|
-
requestHandlers.onCompleted(finalState);
|
|
177
|
+
tasks,
|
|
178
|
+
}));
|
|
145
179
|
}
|
|
146
180
|
catch (err) {
|
|
147
181
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
@@ -151,16 +185,7 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
|
|
|
151
185
|
type: ExecuteActionType.ProcessingError,
|
|
152
186
|
payload: { error: errorMessage },
|
|
153
187
|
});
|
|
154
|
-
|
|
155
|
-
message: '',
|
|
156
|
-
summary: '',
|
|
157
|
-
taskInfos: [],
|
|
158
|
-
completed: 0,
|
|
159
|
-
taskExecutionTimes: [],
|
|
160
|
-
completionMessage: null,
|
|
161
|
-
error: errorMessage,
|
|
162
|
-
};
|
|
163
|
-
requestHandlers.onCompleted(finalState);
|
|
188
|
+
requestHandlers.onCompleted(createExecuteState({ error: errorMessage }));
|
|
164
189
|
requestHandlers.onError(errorMessage);
|
|
165
190
|
}
|
|
166
191
|
}
|
|
@@ -170,81 +195,100 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
|
|
|
170
195
|
mounted = false;
|
|
171
196
|
};
|
|
172
197
|
}, [
|
|
173
|
-
|
|
198
|
+
inputTasks,
|
|
174
199
|
isActive,
|
|
175
200
|
service,
|
|
176
201
|
requestHandlers,
|
|
177
202
|
lifecycleHandlers,
|
|
178
203
|
workflowHandlers,
|
|
179
|
-
|
|
204
|
+
tasks.length,
|
|
180
205
|
hasProcessed,
|
|
181
206
|
]);
|
|
182
207
|
// Handle task completion - move to next task
|
|
183
|
-
const handleTaskComplete = useCallback((index,
|
|
208
|
+
const handleTaskComplete = useCallback((index, elapsed, taskOutput) => {
|
|
209
|
+
// Track working directory for subsequent commands
|
|
210
|
+
if (taskOutput.workdir) {
|
|
211
|
+
workdirRef.current = taskOutput.workdir;
|
|
212
|
+
}
|
|
213
|
+
// Update tasks with output before calling handler
|
|
214
|
+
const tasksWithOutput = tasks.map((task, i) => i === index
|
|
215
|
+
? {
|
|
216
|
+
...task,
|
|
217
|
+
stdout: taskOutput.stdout,
|
|
218
|
+
stderr: taskOutput.stderr,
|
|
219
|
+
error: taskOutput.error,
|
|
220
|
+
}
|
|
221
|
+
: task);
|
|
184
222
|
const result = handleTaskCompletion(index, elapsed, {
|
|
185
|
-
|
|
223
|
+
tasks: tasksWithOutput,
|
|
186
224
|
message,
|
|
187
225
|
summary,
|
|
188
|
-
taskExecutionTimes,
|
|
189
226
|
});
|
|
190
227
|
dispatch(result.action);
|
|
191
228
|
requestHandlers.onCompleted(result.finalState);
|
|
192
229
|
if (result.shouldComplete) {
|
|
193
230
|
lifecycleHandlers.completeActive();
|
|
194
231
|
}
|
|
195
|
-
}, [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
232
|
+
}, [tasks, message, summary, requestHandlers, lifecycleHandlers]);
|
|
233
|
+
const handleTaskError = useCallback((index, error, elapsed, taskOutput) => {
|
|
234
|
+
// Track working directory for subsequent commands (even on error)
|
|
235
|
+
if (taskOutput.workdir) {
|
|
236
|
+
workdirRef.current = taskOutput.workdir;
|
|
237
|
+
}
|
|
238
|
+
// Update tasks with output before calling handler
|
|
239
|
+
const tasksWithOutput = tasks.map((task, i) => i === index
|
|
240
|
+
? {
|
|
241
|
+
...task,
|
|
242
|
+
stdout: taskOutput.stdout,
|
|
243
|
+
stderr: taskOutput.stderr,
|
|
244
|
+
error: taskOutput.error,
|
|
245
|
+
}
|
|
246
|
+
: task);
|
|
204
247
|
const result = handleTaskFailure(index, error, elapsed, {
|
|
205
|
-
|
|
248
|
+
tasks: tasksWithOutput,
|
|
206
249
|
message,
|
|
207
250
|
summary,
|
|
208
|
-
taskExecutionTimes,
|
|
209
251
|
});
|
|
210
252
|
dispatch(result.action);
|
|
211
253
|
requestHandlers.onCompleted(result.finalState);
|
|
212
|
-
|
|
213
|
-
|
|
254
|
+
// Add error feedback to queue for critical failures
|
|
255
|
+
if (result.action.type === ExecuteActionType.TaskErrorCritical) {
|
|
256
|
+
const errorMessage = getExecutionErrorMessage(error);
|
|
257
|
+
workflowHandlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessage));
|
|
214
258
|
}
|
|
215
259
|
if (result.shouldComplete) {
|
|
216
260
|
lifecycleHandlers.completeActive();
|
|
217
261
|
}
|
|
218
262
|
}, [
|
|
219
|
-
|
|
263
|
+
tasks,
|
|
220
264
|
message,
|
|
221
265
|
summary,
|
|
222
|
-
taskExecutionTimes,
|
|
223
266
|
requestHandlers,
|
|
224
267
|
lifecycleHandlers,
|
|
268
|
+
workflowHandlers,
|
|
225
269
|
]);
|
|
226
|
-
const handleTaskAbort = useCallback((
|
|
270
|
+
const handleTaskAbort = useCallback((index, taskOutput) => {
|
|
227
271
|
// Task was aborted - execution already stopped by Escape handler
|
|
228
|
-
//
|
|
229
|
-
const
|
|
272
|
+
// Update tasks with output before building state
|
|
273
|
+
const tasksWithOutput = tasks.map((task, i) => i === index
|
|
274
|
+
? {
|
|
275
|
+
...task,
|
|
276
|
+
stdout: taskOutput.stdout,
|
|
277
|
+
stderr: taskOutput.stderr,
|
|
278
|
+
error: taskOutput.error,
|
|
279
|
+
}
|
|
280
|
+
: task);
|
|
281
|
+
const finalState = buildAbortedState(tasksWithOutput, message, summary, completed);
|
|
230
282
|
requestHandlers.onCompleted(finalState);
|
|
231
|
-
}, [
|
|
232
|
-
taskInfos,
|
|
233
|
-
message,
|
|
234
|
-
summary,
|
|
235
|
-
completed,
|
|
236
|
-
taskExecutionTimes,
|
|
237
|
-
requestHandlers,
|
|
238
|
-
]);
|
|
283
|
+
}, [tasks, message, summary, completed, requestHandlers]);
|
|
239
284
|
// Controller always renders View with current state
|
|
240
|
-
const viewState = {
|
|
285
|
+
const viewState = createExecuteState({
|
|
241
286
|
error,
|
|
242
|
-
|
|
287
|
+
tasks,
|
|
243
288
|
message,
|
|
244
289
|
summary,
|
|
245
290
|
completed,
|
|
246
|
-
taskExecutionTimes,
|
|
247
291
|
completionMessage,
|
|
248
|
-
};
|
|
249
|
-
return (_jsx(ExecuteView, {
|
|
292
|
+
});
|
|
293
|
+
return (_jsx(ExecuteView, { state: viewState, status: status, workdir: workdirRef.current, onOutputChange: handleOutputChange, onTaskComplete: handleTaskComplete, onTaskAbort: handleTaskAbort, onTaskError: handleTaskError }));
|
|
250
294
|
}
|
package/dist/ui/Feedback.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { ComponentStatus } from '../types/components.js';
|
|
3
4
|
import { FeedbackType } from '../types/types.js';
|
|
4
5
|
import { getFeedbackColor } from '../services/colors.js';
|
|
5
6
|
function getSymbol(type) {
|
|
@@ -12,7 +13,7 @@ function getSymbol(type) {
|
|
|
12
13
|
}[type];
|
|
13
14
|
}
|
|
14
15
|
export function Feedback({ type, message }) {
|
|
15
|
-
const color = getFeedbackColor(type,
|
|
16
|
+
const color = getFeedbackColor(type, ComponentStatus.Done);
|
|
16
17
|
const symbol = getSymbol(type);
|
|
17
18
|
return (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: color, children: [symbol, " ", message] }) }));
|
|
18
19
|
}
|
package/dist/ui/Label.js
CHANGED
|
@@ -3,7 +3,8 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
|
|
4
4
|
import { DebugLevel } from '../configuration/types.js';
|
|
5
5
|
import { Separator } from './Separator.js';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { ComponentStatus } from '../types/components.js';
|
|
7
|
+
export function Label({ description, taskType, showType = false, status = ComponentStatus.Done, debug = DebugLevel.None, }) {
|
|
8
|
+
const colors = getTaskColors(taskType, status);
|
|
8
9
|
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.description, children: description }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: colors.type, children: getTaskTypeLabel(taskType, debug) })] }))] }));
|
|
9
10
|
}
|
package/dist/ui/List.js
CHANGED
|
@@ -4,7 +4,8 @@ import { Palette } from '../services/colors.js';
|
|
|
4
4
|
import { Separator } from './Separator.js';
|
|
5
5
|
export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, showType = false, }) => {
|
|
6
6
|
const marginLeft = level > 0 ? 2 : 0;
|
|
7
|
-
|
|
7
|
+
const gap = level === 0 ? 1 : 0;
|
|
8
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, gap: gap, children: items.map((item, index) => {
|
|
8
9
|
// At level 0, track which parent is active for child highlighting
|
|
9
10
|
// At level > 0, only highlight if this parent is the active one
|
|
10
11
|
const shouldHighlightChildren = level === 0 ? highlightedParentIndex === index : false;
|
|
@@ -23,6 +24,6 @@ export const List = ({ items, level = 0, highlightedIndex = null, highlightedPar
|
|
|
23
24
|
(isHighlighted && item.type.highlightedColor
|
|
24
25
|
? item.type.highlightedColor
|
|
25
26
|
: Palette.White);
|
|
26
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(Text, { color: descriptionColor, children: item.description.text }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: item.type.text })] }))] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }))] }, index));
|
|
27
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(Text, { color: descriptionColor, children: item.description.text }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: item.type.text })] }))] }), item.children && item.children.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }) }))] }, index));
|
|
27
28
|
}) }));
|
|
28
29
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Palette } from '../services/colors.js';
|
|
4
|
+
import { ExecutionStatus } from '../services/shell.js';
|
|
5
|
+
const MAX_LINES = 8;
|
|
6
|
+
const MAX_WIDTH = 75;
|
|
7
|
+
const SHORT_OUTPUT_THRESHOLD = 4;
|
|
8
|
+
const MINIMAL_INFO_THRESHOLD = 2;
|
|
9
|
+
/**
|
|
10
|
+
* Get the last N lines from text, filtering out empty/whitespace-only lines
|
|
11
|
+
*/
|
|
12
|
+
export function getLastLines(text, maxLines = MAX_LINES) {
|
|
13
|
+
const lines = text
|
|
14
|
+
.trim()
|
|
15
|
+
.split(/\r?\n/)
|
|
16
|
+
.filter((line) => line.trim().length > 0);
|
|
17
|
+
return lines.length <= maxLines ? lines : lines.slice(-maxLines);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compute display configuration for output rendering.
|
|
21
|
+
* Encapsulates the logic for what to show and how to style it.
|
|
22
|
+
*/
|
|
23
|
+
export function computeDisplayConfig(stdout, stderr, status, isFinished) {
|
|
24
|
+
const hasStdout = stdout.trim().length > 0;
|
|
25
|
+
const hasStderr = stderr.trim().length > 0;
|
|
26
|
+
if (!hasStdout && !hasStderr)
|
|
27
|
+
return null;
|
|
28
|
+
const stdoutLines = hasStdout ? getLastLines(stdout) : [];
|
|
29
|
+
const stderrLines = hasStderr ? getLastLines(stderr) : [];
|
|
30
|
+
// Show stdout if no stderr, or if stderr is minimal (provides context)
|
|
31
|
+
const showStdout = hasStdout && (!hasStderr || stderrLines.length <= MINIMAL_INFO_THRESHOLD);
|
|
32
|
+
// Use word wrapping for short outputs to show more detail
|
|
33
|
+
const totalLines = stdoutLines.length + stderrLines.length;
|
|
34
|
+
const wrapMode = totalLines <= SHORT_OUTPUT_THRESHOLD ? 'wrap' : 'truncate-end';
|
|
35
|
+
// Darker colors for finished tasks
|
|
36
|
+
const baseColor = isFinished ? Palette.DarkGray : Palette.Gray;
|
|
37
|
+
const stderrColor = status === ExecutionStatus.Failed ? Palette.Yellow : baseColor;
|
|
38
|
+
return {
|
|
39
|
+
stdoutLines,
|
|
40
|
+
stderrLines,
|
|
41
|
+
showStdout,
|
|
42
|
+
wrapMode,
|
|
43
|
+
stdoutColor: baseColor,
|
|
44
|
+
stderrColor,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function Output({ stdout, stderr, status, isFinished }) {
|
|
48
|
+
const config = computeDisplayConfig(stdout, stderr, status, isFinished ?? false);
|
|
49
|
+
if (!config)
|
|
50
|
+
return null;
|
|
51
|
+
const { stdoutLines, stderrLines, showStdout, wrapMode, stdoutColor, stderrColor, } = config;
|
|
52
|
+
return (_jsxs(Box, { marginTop: 1, marginLeft: 5, flexDirection: "column", width: MAX_WIDTH, children: [showStdout &&
|
|
53
|
+
stdoutLines.map((line, index) => (_jsx(Text, { color: stdoutColor, wrap: wrapMode, children: line }, `out-${index}`))), stderrLines.map((line, index) => (_jsx(Text, { color: stderrColor, wrap: wrapMode, children: line }, `err-${index}`)))] }));
|
|
54
|
+
}
|
package/dist/ui/Schedule.js
CHANGED
|
@@ -3,17 +3,22 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Box } from 'ink';
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { TaskType } from '../types/types.js';
|
|
6
|
-
import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
|
|
6
|
+
import { getTaskColors, getTaskTypeLabel, Palette, } from '../services/colors.js';
|
|
7
7
|
import { DebugLevel } from '../configuration/types.js';
|
|
8
8
|
import { useInput } from '../services/keyboard.js';
|
|
9
9
|
import { Label } from './Label.js';
|
|
10
10
|
import { List } from './List.js';
|
|
11
|
-
function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutSelection = false,
|
|
12
|
-
const taskColors = getTaskColors(task.type,
|
|
11
|
+
export function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutSelection = false, status = ComponentStatus.Done, debug = DebugLevel.None) {
|
|
12
|
+
const taskColors = getTaskColors(task.type, status);
|
|
13
|
+
// Determine description color based on status
|
|
14
|
+
let descriptionColor = taskColors.description;
|
|
15
|
+
if (status === ComponentStatus.Pending) {
|
|
16
|
+
descriptionColor = Palette.SoftWhite;
|
|
17
|
+
}
|
|
13
18
|
const item = {
|
|
14
19
|
description: {
|
|
15
20
|
text: task.action,
|
|
16
|
-
color:
|
|
21
|
+
color: descriptionColor,
|
|
17
22
|
},
|
|
18
23
|
type: { text: getTaskTypeLabel(task.type, debug), color: taskColors.type },
|
|
19
24
|
children: [],
|
|
@@ -21,7 +26,7 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
21
26
|
// Mark define tasks with right arrow when no selection has been made
|
|
22
27
|
if (isDefineTaskWithoutSelection) {
|
|
23
28
|
item.marker = ' → ';
|
|
24
|
-
item.markerColor = getTaskColors(TaskType.Schedule,
|
|
29
|
+
item.markerColor = getTaskColors(TaskType.Schedule, status).type;
|
|
25
30
|
}
|
|
26
31
|
// Add children for Define tasks with options
|
|
27
32
|
if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
|
|
@@ -33,8 +38,8 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
33
38
|
childType =
|
|
34
39
|
index === highlightedChildIndex ? TaskType.Execute : TaskType.Discard;
|
|
35
40
|
}
|
|
36
|
-
const colors = getTaskColors(childType,
|
|
37
|
-
const planColors = getTaskColors(TaskType.Schedule,
|
|
41
|
+
const colors = getTaskColors(childType, status);
|
|
42
|
+
const planColors = getTaskColors(TaskType.Schedule, status);
|
|
38
43
|
return {
|
|
39
44
|
description: {
|
|
40
45
|
text: option,
|
|
@@ -56,11 +61,11 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
56
61
|
Array.isArray(scheduledTask.subtasks) &&
|
|
57
62
|
scheduledTask.subtasks.length > 0) {
|
|
58
63
|
item.children = scheduledTask.subtasks.map((subtask) => {
|
|
59
|
-
const subtaskColors = getTaskColors(subtask.type,
|
|
64
|
+
const subtaskColors = getTaskColors(subtask.type, status);
|
|
60
65
|
return {
|
|
61
66
|
description: {
|
|
62
67
|
text: subtask.action,
|
|
63
|
-
color:
|
|
68
|
+
color: Palette.AshGray,
|
|
64
69
|
},
|
|
65
70
|
type: {
|
|
66
71
|
text: getTaskTypeLabel(subtask.type, debug),
|
|
@@ -107,9 +112,9 @@ export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel
|
|
|
107
112
|
defineGroupIndex === currentDefineGroupIndex &&
|
|
108
113
|
highlightedIndex === null &&
|
|
109
114
|
isActive;
|
|
110
|
-
return taskToListItem(task, childIndex, isDefineWithoutSelection,
|
|
115
|
+
return taskToListItem(task, childIndex, isDefineWithoutSelection, status, debug);
|
|
111
116
|
});
|
|
112
|
-
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None,
|
|
117
|
+
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None, status: status, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None }) })] }));
|
|
113
118
|
};
|
|
114
119
|
/**
|
|
115
120
|
* Schedule controller: Manages task selection and navigation
|