prompt-language-shell 0.8.2 → 0.8.6

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 (45) 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 +35 -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 +43 -56
  14. package/dist/services/colors.js +2 -1
  15. package/dist/services/components.js +40 -24
  16. package/dist/services/config-labels.js +15 -15
  17. package/dist/services/filesystem.js +114 -0
  18. package/dist/services/loader.js +8 -5
  19. package/dist/services/logger.js +26 -1
  20. package/dist/services/messages.js +32 -1
  21. package/dist/services/parser.js +3 -1
  22. package/dist/services/refinement.js +10 -10
  23. package/dist/services/router.js +43 -27
  24. package/dist/services/skills.js +12 -11
  25. package/dist/services/validator.js +4 -3
  26. package/dist/types/guards.js +4 -6
  27. package/dist/types/handlers.js +1 -0
  28. package/dist/types/schemas.js +103 -0
  29. package/dist/types/types.js +1 -0
  30. package/dist/ui/Answer.js +38 -16
  31. package/dist/ui/Command.js +48 -22
  32. package/dist/ui/Component.js +147 -33
  33. package/dist/ui/Config.js +69 -78
  34. package/dist/ui/Confirm.js +34 -21
  35. package/dist/ui/Execute.js +151 -178
  36. package/dist/ui/Feedback.js +1 -0
  37. package/dist/ui/Introspect.js +54 -25
  38. package/dist/ui/Label.js +1 -1
  39. package/dist/ui/Main.js +10 -6
  40. package/dist/ui/Refinement.js +8 -1
  41. package/dist/ui/Schedule.js +76 -53
  42. package/dist/ui/Validate.js +77 -77
  43. package/dist/ui/Workflow.js +60 -61
  44. package/package.json +3 -2
  45. package/dist/services/configuration.js +0 -409
@@ -1,54 +1,65 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useReducer } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { ComponentStatus, } from '../types/components.js';
5
- import { Colors, getTextColor } from '../services/colors.js';
6
- import { addDebugToTimeline } from '../services/components.js';
5
+ import { getTextColor } from '../services/colors.js';
7
6
  import { useInput } from '../services/keyboard.js';
8
- import { loadUserConfig } from '../services/loader.js';
9
7
  import { formatErrorMessage } from '../services/messages.js';
10
- import { replacePlaceholders } from '../services/resolver.js';
11
8
  import { ExecutionStatus } from '../services/shell.js';
12
9
  import { ensureMinimumTime } from '../services/timing.js';
13
- import { formatDuration } from '../services/utils.js';
10
+ import { buildAbortedState, handleTaskCompletion, handleTaskFailure, } from '../execution/handlers.js';
11
+ import { processTasks } from '../execution/processing.js';
12
+ import { executeReducer, initialState } from '../execution/reducer.js';
13
+ import { ExecuteActionType } from '../execution/types.js';
14
+ import { Message } from './Message.js';
14
15
  import { Spinner } from './Spinner.js';
15
16
  import { Task } from './Task.js';
16
17
  const MINIMUM_PROCESSING_TIME = 400;
17
- export function Execute({ tasks, state, status, service, handlers, }) {
18
+ export const ExecuteView = ({ state, status, onTaskComplete, onTaskAbort, onTaskError, }) => {
18
19
  const isActive = status === ComponentStatus.Active;
19
- const [error, setError] = useState(state?.error ?? null);
20
- const [taskInfos, setTaskInfos] = useState(state?.taskInfos ?? []);
21
- const [message, setMessage] = useState(state?.message ?? '');
22
- const [completed, setCompleted] = useState(state?.completed ?? 0);
23
- const [hasProcessed, setHasProcessed] = useState(false);
24
- const [taskExecutionTimes, setTaskExecutionTimes] = useState(state?.taskExecutionTimes ?? []);
25
- const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
26
- const [summary, setSummary] = useState(state?.summary ?? '');
20
+ const { error, taskInfos, message, completed, completionMessage } = state;
21
+ const hasProcessed = taskInfos.length > 0;
22
+ // Derive loading state from current conditions
23
+ const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
24
+ const isExecuting = completed < taskInfos.length;
25
+ // Return null only when loading completes with no commands
26
+ if (!isActive && taskInfos.length === 0 && !error) {
27
+ return null;
28
+ }
29
+ // Show completed steps when not active
30
+ const showTasks = !isActive && taskInfos.length > 0;
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, {})] })), taskInfos.map((taskInfo, index) => (_jsx(Box, { marginBottom: index < taskInfos.length - 1 ? 1 : 0, children: _jsx(Task, { label: taskInfo.label, command: taskInfo.command, isActive: isActive && index === completed, index: index, initialStatus: taskInfo.status, initialElapsed: taskInfo.elapsed, onComplete: onTaskComplete, onAbort: onTaskAbort, onError: onTaskError }) }, index)))] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && _jsx(Message, { text: error, status: status })] }));
32
+ };
33
+ /**
34
+ * Execute controller: Runs tasks sequentially
35
+ */
36
+ export function Execute({ tasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
37
+ const isActive = status === ComponentStatus.Active;
38
+ const [localState, dispatch] = useReducer(executeReducer, initialState);
39
+ const { error, taskInfos, message, completed, hasProcessed, taskExecutionTimes, completionMessage, summary, } = localState;
27
40
  // Derive loading state from current conditions
28
41
  const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
29
42
  const isExecuting = completed < taskInfos.length;
30
43
  // Handle cancel with useCallback to ensure we capture latest state
31
44
  const handleCancel = useCallback(() => {
32
- // Mark tasks based on their status relative to completed:
33
- // - Before completed: finished (Success)
34
- // - At completed: interrupted (Aborted)
35
- // - After completed: never started (Cancelled)
45
+ dispatch({
46
+ type: ExecuteActionType.CancelExecution,
47
+ payload: { completed },
48
+ });
49
+ // Get updated task infos after cancel
36
50
  const updatedTaskInfos = taskInfos.map((task, taskIndex) => {
37
51
  if (taskIndex < completed) {
38
- // Tasks that completed before interruption
39
52
  return { ...task, status: ExecutionStatus.Success };
40
53
  }
41
54
  else if (taskIndex === completed) {
42
- // Task that was running when interrupted
43
55
  return { ...task, status: ExecutionStatus.Aborted };
44
56
  }
45
57
  else {
46
- // Tasks that haven't started yet
47
58
  return { ...task, status: ExecutionStatus.Cancelled };
48
59
  }
49
60
  });
50
- setTaskInfos(updatedTaskInfos);
51
- handlers?.updateState({
61
+ // Expose final state
62
+ const finalState = {
52
63
  message,
53
64
  summary,
54
65
  taskInfos: updatedTaskInfos,
@@ -56,9 +67,17 @@ export function Execute({ tasks, state, status, service, handlers, }) {
56
67
  taskExecutionTimes,
57
68
  completionMessage: null,
58
69
  error: null,
59
- });
60
- handlers?.onAborted('execution');
61
- }, [message, summary, taskInfos, completed, taskExecutionTimes, handlers]);
70
+ };
71
+ requestHandlers.onCompleted(finalState);
72
+ requestHandlers.onAborted('execution');
73
+ }, [
74
+ message,
75
+ summary,
76
+ taskInfos,
77
+ completed,
78
+ taskExecutionTimes,
79
+ requestHandlers,
80
+ ]);
62
81
  useInput((_, key) => {
63
82
  if (key.escape && (isLoading || isExecuting) && isActive) {
64
83
  handleCancel();
@@ -73,28 +92,20 @@ export function Execute({ tasks, state, status, service, handlers, }) {
73
92
  async function process(svc) {
74
93
  const startTime = Date.now();
75
94
  try {
76
- // Load user config for placeholder resolution
77
- const userConfig = loadUserConfig();
78
- // Format tasks for the execute tool and resolve placeholders
79
- const taskDescriptions = tasks
80
- .map((task) => {
81
- const resolvedAction = replacePlaceholders(task.action, userConfig);
82
- const params = task.params
83
- ? ` (params: ${JSON.stringify(task.params)})`
84
- : '';
85
- return `- ${resolvedAction}${params}`;
86
- })
87
- .join('\n');
88
- // Call execute tool to get commands
89
- const result = await svc.processWithTool(taskDescriptions, 'execute');
95
+ const result = await processTasks(tasks, svc);
90
96
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
91
97
  if (!mounted)
92
98
  return;
93
99
  // Add debug components to timeline if present
94
- addDebugToTimeline(result.debug, handlers);
95
- if (!result.commands || result.commands.length === 0) {
96
- setHasProcessed(true);
97
- handlers?.updateState({
100
+ if (result.debug?.length) {
101
+ workflowHandlers.addToTimeline(...result.debug);
102
+ }
103
+ if (result.commands.length === 0) {
104
+ dispatch({
105
+ type: ExecuteActionType.ProcessingComplete,
106
+ payload: { message: result.message },
107
+ });
108
+ const finalState = {
98
109
  message: result.message,
99
110
  summary: '',
100
111
  taskInfos: [],
@@ -102,44 +113,45 @@ export function Execute({ tasks, state, status, service, handlers, }) {
102
113
  taskExecutionTimes: [],
103
114
  completionMessage: null,
104
115
  error: null,
105
- });
106
- handlers?.completeActive();
116
+ };
117
+ requestHandlers.onCompleted(finalState);
118
+ lifecycleHandlers.completeActive();
107
119
  return;
108
120
  }
109
- // Resolve placeholders in command strings
110
- const resolvedCommands = result.commands.map((cmd) => ({
111
- ...cmd,
112
- command: replacePlaceholders(cmd.command, userConfig),
113
- }));
114
- // Set message, summary, and create task infos
115
- const newMessage = result.message;
116
- const newSummary = result.summary || '';
117
- const infos = resolvedCommands.map((cmd, index) => ({
121
+ // Create task infos from commands
122
+ const infos = result.commands.map((cmd, index) => ({
118
123
  label: tasks[index]?.action,
119
124
  command: cmd,
120
125
  }));
121
- setMessage(newMessage);
122
- setSummary(newSummary);
123
- setTaskInfos(infos);
124
- setCompleted(0); // Start with first task
126
+ dispatch({
127
+ type: ExecuteActionType.CommandsReady,
128
+ payload: {
129
+ message: result.message,
130
+ summary: result.summary,
131
+ taskInfos: infos,
132
+ },
133
+ });
125
134
  // Update state after AI processing
126
- handlers?.updateState({
127
- message: newMessage,
128
- summary: newSummary,
135
+ const finalState = {
136
+ message: result.message,
137
+ summary: result.summary,
129
138
  taskInfos: infos,
130
139
  completed: 0,
131
140
  taskExecutionTimes: [],
132
141
  completionMessage: null,
133
142
  error: null,
134
- });
143
+ };
144
+ requestHandlers.onCompleted(finalState);
135
145
  }
136
146
  catch (err) {
137
147
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
138
148
  if (mounted) {
139
149
  const errorMessage = formatErrorMessage(err);
140
- setError(errorMessage);
141
- setHasProcessed(true);
142
- handlers?.updateState({
150
+ dispatch({
151
+ type: ExecuteActionType.ProcessingError,
152
+ payload: { error: errorMessage },
153
+ });
154
+ const finalState = {
143
155
  message: '',
144
156
  summary: '',
145
157
  taskInfos: [],
@@ -147,8 +159,9 @@ export function Execute({ tasks, state, status, service, handlers, }) {
147
159
  taskExecutionTimes: [],
148
160
  completionMessage: null,
149
161
  error: errorMessage,
150
- });
151
- handlers?.onError(errorMessage);
162
+ };
163
+ requestHandlers.onCompleted(finalState);
164
+ requestHandlers.onError(errorMessage);
152
165
  }
153
166
  }
154
167
  }
@@ -156,122 +169,82 @@ export function Execute({ tasks, state, status, service, handlers, }) {
156
169
  return () => {
157
170
  mounted = false;
158
171
  };
159
- }, [tasks, isActive, service, handlers, taskInfos.length, hasProcessed]);
172
+ }, [
173
+ tasks,
174
+ isActive,
175
+ service,
176
+ requestHandlers,
177
+ lifecycleHandlers,
178
+ workflowHandlers,
179
+ taskInfos.length,
180
+ hasProcessed,
181
+ ]);
160
182
  // Handle task completion - move to next task
161
183
  const handleTaskComplete = useCallback((index, _output, elapsed) => {
162
- const updatedTimes = [...taskExecutionTimes, elapsed];
163
- setTaskExecutionTimes(updatedTimes);
164
- // Update task with elapsed time and success status
165
- const updatedTaskInfos = taskInfos.map((task, i) => i === index
166
- ? { ...task, status: ExecutionStatus.Success, elapsed }
167
- : task);
168
- setTaskInfos(updatedTaskInfos);
169
- if (index < taskInfos.length - 1) {
170
- // More tasks to execute
171
- setCompleted(index + 1);
172
- handlers?.updateState({
173
- message,
174
- summary,
175
- taskInfos: updatedTaskInfos,
176
- completed: index + 1,
177
- taskExecutionTimes: updatedTimes,
178
- completionMessage: null,
179
- error: null,
180
- });
181
- }
182
- else {
183
- // All tasks complete
184
- const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
185
- const summaryText = summary.trim() || 'Execution completed';
186
- const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
187
- setCompletionMessage(completion);
188
- handlers?.updateState({
189
- message,
190
- summary,
191
- taskInfos: updatedTaskInfos,
192
- completed: index + 1,
193
- taskExecutionTimes: updatedTimes,
194
- completionMessage: completion,
195
- error: null,
196
- });
197
- handlers?.completeActive();
184
+ const result = handleTaskCompletion(index, elapsed, {
185
+ taskInfos,
186
+ message,
187
+ summary,
188
+ taskExecutionTimes,
189
+ });
190
+ dispatch(result.action);
191
+ requestHandlers.onCompleted(result.finalState);
192
+ if (result.shouldComplete) {
193
+ lifecycleHandlers.completeActive();
198
194
  }
199
- }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
195
+ }, [
196
+ taskInfos,
197
+ message,
198
+ summary,
199
+ taskExecutionTimes,
200
+ requestHandlers,
201
+ lifecycleHandlers,
202
+ ]);
200
203
  const handleTaskError = useCallback((index, error, elapsed) => {
201
- const task = taskInfos[index];
202
- const isCritical = task.command.critical !== false; // Default to true
203
- // Update task with elapsed time and failed status
204
- const updatedTaskInfos = taskInfos.map((task, i) => i === index
205
- ? { ...task, status: ExecutionStatus.Failed, elapsed }
206
- : task);
207
- setTaskInfos(updatedTaskInfos);
208
- if (isCritical) {
209
- // Critical failure - stop execution
210
- setError(error);
211
- handlers?.updateState({
212
- message,
213
- summary,
214
- taskInfos: updatedTaskInfos,
215
- completed: index + 1,
216
- taskExecutionTimes,
217
- completionMessage: null,
218
- error,
219
- });
220
- handlers?.onError(error);
204
+ const result = handleTaskFailure(index, error, elapsed, {
205
+ taskInfos,
206
+ message,
207
+ summary,
208
+ taskExecutionTimes,
209
+ });
210
+ dispatch(result.action);
211
+ requestHandlers.onCompleted(result.finalState);
212
+ if (result.shouldReportError) {
213
+ requestHandlers.onError(error);
221
214
  }
222
- else {
223
- // Non-critical failure - continue to next task
224
- const updatedTimes = [...taskExecutionTimes, elapsed];
225
- setTaskExecutionTimes(updatedTimes);
226
- if (index < taskInfos.length - 1) {
227
- setCompleted(index + 1);
228
- handlers?.updateState({
229
- message,
230
- summary,
231
- taskInfos: updatedTaskInfos,
232
- completed: index + 1,
233
- taskExecutionTimes: updatedTimes,
234
- completionMessage: null,
235
- error: null,
236
- });
237
- }
238
- else {
239
- // Last task, complete execution
240
- const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
241
- const summaryText = summary.trim() || 'Execution completed';
242
- const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
243
- setCompletionMessage(completion);
244
- handlers?.updateState({
245
- message,
246
- summary,
247
- taskInfos: updatedTaskInfos,
248
- completed: index + 1,
249
- taskExecutionTimes: updatedTimes,
250
- completionMessage: completion,
251
- error: null,
252
- });
253
- handlers?.completeActive();
254
- }
215
+ if (result.shouldComplete) {
216
+ lifecycleHandlers.completeActive();
255
217
  }
256
- }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
218
+ }, [
219
+ taskInfos,
220
+ message,
221
+ summary,
222
+ taskExecutionTimes,
223
+ requestHandlers,
224
+ lifecycleHandlers,
225
+ ]);
257
226
  const handleTaskAbort = useCallback((_index) => {
258
227
  // Task was aborted - execution already stopped by Escape handler
259
228
  // Just update state, don't call onAborted (already called at Execute level)
260
- handlers?.updateState({
261
- message,
262
- summary,
263
- taskInfos,
264
- completed,
265
- taskExecutionTimes,
266
- completionMessage: null,
267
- error: null,
268
- });
269
- }, [taskInfos, message, summary, completed, taskExecutionTimes, handlers]);
270
- // Return null only when loading completes with no commands
271
- if (!isActive && taskInfos.length === 0 && !error) {
272
- return null;
273
- }
274
- // Show completed steps when not active
275
- const showTasks = !isActive && taskInfos.length > 0;
276
- 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, {})] })), taskInfos.map((taskInfo, index) => (_jsx(Box, { marginBottom: index < taskInfos.length - 1 ? 1 : 0, children: _jsx(Task, { label: taskInfo.label, command: taskInfo.command, isActive: isActive && index === completed, index: index, initialStatus: taskInfo.status, initialElapsed: taskInfo.elapsed, onComplete: handleTaskComplete, onAbort: handleTaskAbort, onError: handleTaskError }) }, index)))] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
229
+ const finalState = buildAbortedState(taskInfos, message, summary, completed, taskExecutionTimes);
230
+ requestHandlers.onCompleted(finalState);
231
+ }, [
232
+ taskInfos,
233
+ message,
234
+ summary,
235
+ completed,
236
+ taskExecutionTimes,
237
+ requestHandlers,
238
+ ]);
239
+ // Controller always renders View with current state
240
+ const viewState = {
241
+ error,
242
+ taskInfos,
243
+ message,
244
+ summary,
245
+ completed,
246
+ taskExecutionTimes,
247
+ completionMessage,
248
+ };
249
+ return (_jsx(ExecuteView, { tasks: tasks, state: viewState, status: status, onTaskComplete: handleTaskComplete, onTaskAbort: handleTaskAbort, onTaskError: handleTaskError }));
277
250
  }
@@ -5,6 +5,7 @@ import { getFeedbackColor } from '../services/colors.js';
5
5
  function getSymbol(type) {
6
6
  return {
7
7
  [FeedbackType.Info]: 'ℹ',
8
+ [FeedbackType.Warning]: '⚠',
8
9
  [FeedbackType.Succeeded]: '✓',
9
10
  [FeedbackType.Aborted]: '⊘',
10
11
  [FeedbackType.Failed]: '✗',
@@ -1,22 +1,35 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } 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 { Colors, getTextColor } from '../services/colors.js';
6
- import { addDebugToTimeline, createReportDefinition, } from '../services/components.js';
7
- import { DebugLevel } from '../services/configuration.js';
6
+ import { createReportDefinition } from '../services/components.js';
7
+ import { DebugLevel } from '../configuration/types.js';
8
8
  import { useInput } from '../services/keyboard.js';
9
9
  import { formatErrorMessage } from '../services/messages.js';
10
10
  import { ensureMinimumTime } from '../services/timing.js';
11
11
  import { Spinner } from './Spinner.js';
12
12
  const MIN_PROCESSING_TIME = 1000;
13
- export function Introspect({ tasks, state: _state, status, service, children, debug = DebugLevel.None, handlers, }) {
13
+ export const IntrospectView = ({ state, status, children, }) => {
14
+ const isActive = status === ComponentStatus.Active;
15
+ const { error } = state;
16
+ // Don't render wrapper when done and nothing to show
17
+ if (!isActive && !error && !children) {
18
+ return null;
19
+ }
20
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
21
+ };
22
+ /**
23
+ * Introspect controller: Lists capabilities via LLM
24
+ */
25
+ export function Introspect({ tasks, status, service, children, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
14
26
  const isActive = status === ComponentStatus.Active;
15
- // isActive passed as prop
16
27
  const [error, setError] = useState(null);
28
+ const [capabilities, setCapabilities] = useState(null);
29
+ const [message, setMessage] = useState(null);
17
30
  useInput((input, key) => {
18
31
  if (key.escape && isActive) {
19
- handlers?.onAborted('introspection');
32
+ requestHandlers.onAborted('introspection');
20
33
  }
21
34
  }, { isActive });
22
35
  useEffect(() => {
@@ -35,24 +48,29 @@ export function Introspect({ tasks, state: _state, status, service, children, de
35
48
  await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
36
49
  if (mounted) {
37
50
  // Add debug components to timeline if present
38
- addDebugToTimeline(result.debug, handlers);
51
+ if (result.debug?.length) {
52
+ workflowHandlers.addToTimeline(...result.debug);
53
+ }
39
54
  // Capabilities come directly from result - no parsing needed
40
- let capabilities = result.capabilities;
55
+ let caps = result.capabilities;
41
56
  // Filter out internal capabilities when not in debug mode
42
57
  if (debug === DebugLevel.None) {
43
- capabilities = capabilities.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
58
+ caps = caps.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
44
59
  cap.name.toUpperCase() !== 'VALIDATE' &&
45
60
  cap.name.toUpperCase() !== 'REPORT');
46
61
  }
47
- // Save state before completing
48
- handlers?.updateState({
49
- capabilities,
62
+ setCapabilities(caps);
63
+ setMessage(result.message);
64
+ const finalState = {
65
+ error: null,
66
+ capabilities: caps,
50
67
  message: result.message,
51
- });
68
+ };
69
+ requestHandlers.onCompleted(finalState);
52
70
  // Add Report component to queue
53
- handlers?.addToQueue(createReportDefinition(result.message, capabilities));
71
+ workflowHandlers.addToQueue(createReportDefinition(result.message, caps));
54
72
  // Signal completion
55
- handlers?.completeActive();
73
+ lifecycleHandlers.completeActive();
56
74
  }
57
75
  }
58
76
  catch (err) {
@@ -60,11 +78,13 @@ export function Introspect({ tasks, state: _state, status, service, children, de
60
78
  if (mounted) {
61
79
  const errorMessage = formatErrorMessage(err);
62
80
  setError(errorMessage);
63
- // Save error state
64
- handlers?.updateState({
81
+ const finalState = {
65
82
  error: errorMessage,
66
- });
67
- handlers?.onError(errorMessage);
83
+ capabilities: [],
84
+ message: null,
85
+ };
86
+ requestHandlers.onCompleted(finalState);
87
+ requestHandlers.onError(errorMessage);
68
88
  }
69
89
  }
70
90
  }
@@ -72,10 +92,19 @@ export function Introspect({ tasks, state: _state, status, service, children, de
72
92
  return () => {
73
93
  mounted = false;
74
94
  };
75
- }, [tasks, isActive, service, debug, handlers]);
76
- // Don't render wrapper when done and nothing to show
77
- if (!isActive && !error && !children) {
78
- return null;
79
- }
80
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
95
+ }, [
96
+ tasks,
97
+ isActive,
98
+ service,
99
+ debug,
100
+ requestHandlers,
101
+ lifecycleHandlers,
102
+ workflowHandlers,
103
+ ]);
104
+ const state = {
105
+ error,
106
+ capabilities: capabilities || [],
107
+ message,
108
+ };
109
+ return (_jsx(IntrospectView, { state: state, status: status, children: children }));
81
110
  }
package/dist/ui/Label.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
4
- import { DebugLevel } from '../services/configuration.js';
4
+ import { DebugLevel } from '../configuration/types.js';
5
5
  import { Separator } from './Separator.js';
6
6
  export function Label({ description, taskType, showType = false, isCurrent = false, debug = DebugLevel.None, }) {
7
7
  const colors = getTaskColors(taskType, isCurrent);
package/dist/ui/Main.js CHANGED
@@ -1,13 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
+ import { DebugLevel } from '../configuration/types.js';
3
4
  import { FeedbackType } from '../types/types.js';
4
- import { createAnthropicService, } from '../services/anthropic.js';
5
+ import { loadConfig, loadDebugSetting, saveConfig, saveDebugSetting, } from '../configuration/io.js';
6
+ import { getConfigurationRequiredMessage } from '../configuration/messages.js';
7
+ import { getMissingConfigKeys } from '../configuration/schema.js';
8
+ import { unflattenConfig } from '../configuration/transformation.js';
9
+ import { createAnthropicService } from '../services/anthropic.js';
5
10
  import { createCommandDefinition, createConfigDefinitionWithKeys, createFeedback, createMessage, createWelcomeDefinition, } from '../services/components.js';
6
- import { DebugLevel, getConfigurationRequiredMessage, getMissingConfigKeys, loadConfig, loadDebugSetting, saveConfig, saveDebugSetting, unflattenConfig, } from '../services/configuration.js';
7
11
  import { registerGlobalShortcut } from '../services/keyboard.js';
8
12
  import { initializeLogger, setDebugLevel } from '../services/logger.js';
9
13
  import { Workflow } from './Workflow.js';
10
- export const Main = ({ app, command }) => {
14
+ export const Main = ({ app, command, serviceFactory = createAnthropicService, }) => {
11
15
  const [service, setService] = useState(null);
12
16
  const [initialQueue, setInitialQueue] = useState(null);
13
17
  const [debug, setDebugLevelState] = useState(() => loadDebugSetting());
@@ -44,7 +48,7 @@ export const Main = ({ app, command }) => {
44
48
  // Config exists - create service immediately
45
49
  try {
46
50
  const config = loadConfig();
47
- const newService = createAnthropicService(config.anthropic);
51
+ const newService = serviceFactory(config.anthropic);
48
52
  setService(newService);
49
53
  }
50
54
  catch (error) {
@@ -56,7 +60,7 @@ export const Main = ({ app, command }) => {
56
60
  }
57
61
  }
58
62
  // If config is missing, service will be created after config completes
59
- }, [service]);
63
+ }, [service, serviceFactory]);
60
64
  // Initialize queue after service is ready
61
65
  useEffect(() => {
62
66
  // Only set initial queue once
@@ -75,7 +79,7 @@ export const Main = ({ app, command }) => {
75
79
  }
76
80
  // Load config and create service
77
81
  const newConfig = loadConfig();
78
- const newService = createAnthropicService(newConfig.anthropic);
82
+ const newService = serviceFactory(newConfig.anthropic);
79
83
  setService(newService);
80
84
  }
81
85
  catch (error) {
@@ -4,6 +4,13 @@ import { ComponentStatus } from '../types/components.js';
4
4
  import { useInput } from '../services/keyboard.js';
5
5
  import { Message } from './Message.js';
6
6
  import { Spinner } from './Spinner.js';
7
+ export const RefinementView = ({ text, status }) => {
8
+ const isActive = status === ComponentStatus.Active;
9
+ return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text, status: status }), isActive && _jsx(Spinner, {})] }));
10
+ };
11
+ /**
12
+ * Refinement controller: Handles abort input
13
+ */
7
14
  export const Refinement = ({ text, status, onAborted }) => {
8
15
  const isActive = status === ComponentStatus.Active;
9
16
  useInput((_, key) => {
@@ -12,5 +19,5 @@ export const Refinement = ({ text, status, onAborted }) => {
12
19
  return;
13
20
  }
14
21
  }, { isActive });
15
- return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text }), isActive && _jsx(Spinner, {})] }));
22
+ return _jsx(RefinementView, { text: text, status: status });
16
23
  };