prompt-language-shell 0.5.2 → 0.6.2

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 (40) hide show
  1. package/dist/config/ANSWER.md +4 -0
  2. package/dist/config/CONFIG.md +4 -2
  3. package/dist/config/PLAN.md +23 -0
  4. package/dist/config/VALIDATE.md +12 -11
  5. package/dist/services/anthropic.js +49 -4
  6. package/dist/services/components.js +56 -66
  7. package/dist/services/configuration.js +169 -13
  8. package/dist/services/messages.js +22 -0
  9. package/dist/services/queue.js +2 -2
  10. package/dist/services/refinement.js +37 -0
  11. package/dist/services/task-router.js +141 -0
  12. package/dist/types/types.js +0 -1
  13. package/dist/ui/Answer.js +18 -27
  14. package/dist/ui/Command.js +44 -27
  15. package/dist/ui/Component.js +23 -50
  16. package/dist/ui/Config.js +77 -55
  17. package/dist/ui/Confirm.js +17 -11
  18. package/dist/ui/Execute.js +66 -45
  19. package/dist/ui/Feedback.js +1 -1
  20. package/dist/ui/Introspect.js +26 -23
  21. package/dist/ui/Main.js +71 -100
  22. package/dist/ui/Message.js +1 -1
  23. package/dist/ui/Plan.js +54 -32
  24. package/dist/ui/Refinement.js +6 -7
  25. package/dist/ui/Report.js +1 -1
  26. package/dist/ui/UserQuery.js +6 -0
  27. package/dist/ui/Validate.js +49 -19
  28. package/dist/ui/Welcome.js +1 -1
  29. package/dist/ui/Workflow.js +132 -0
  30. package/package.json +1 -1
  31. package/dist/handlers/answer.js +0 -21
  32. package/dist/handlers/command.js +0 -34
  33. package/dist/handlers/config.js +0 -88
  34. package/dist/handlers/execute.js +0 -46
  35. package/dist/handlers/execution.js +0 -140
  36. package/dist/handlers/introspect.js +0 -21
  37. package/dist/handlers/plan.js +0 -79
  38. package/dist/types/handlers.js +0 -1
  39. package/dist/ui/AnswerDisplay.js +0 -8
  40. package/dist/ui/Column.js +0 -7
@@ -2,6 +2,7 @@ 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
4
  import { Colors, getTextColor } from '../services/colors.js';
5
+ import { createReportDefinition } from '../services/components.js';
5
6
  import { useInput } from '../services/keyboard.js';
6
7
  import { formatErrorMessage } from '../services/messages.js';
7
8
  import { ensureMinimumTime } from '../services/timing.js';
@@ -41,26 +42,22 @@ function parseCapabilityFromTask(task) {
41
42
  isIndirect,
42
43
  };
43
44
  }
44
- export function Introspect({ tasks, state, service, children, debug = false, onError, onComplete, onAborted, }) {
45
- const done = state?.done ?? false;
46
- const isCurrent = done === false;
45
+ export function Introspect({ tasks, state, isActive = true, service, children, debug = false, handlers, }) {
46
+ // isActive passed as prop
47
47
  const [error, setError] = useState(null);
48
- const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
49
48
  useInput((input, key) => {
50
- if (key.escape && isLoading && !done) {
51
- setIsLoading(false);
52
- onAborted();
49
+ if (key.escape && isActive) {
50
+ handlers?.onAborted('introspection');
53
51
  }
54
- }, { isActive: isLoading && !done });
52
+ }, { isActive });
55
53
  useEffect(() => {
56
- // Skip processing if done
57
- if (done) {
54
+ // Skip processing if not active
55
+ if (!isActive) {
58
56
  return;
59
57
  }
60
58
  // Skip processing if no service available
61
59
  if (!service) {
62
60
  setError('No service available');
63
- setIsLoading(false);
64
61
  return;
65
62
  }
66
63
  let mounted = true;
@@ -81,21 +78,27 @@ export function Introspect({ tasks, state, service, children, debug = false, onE
81
78
  cap.name.toUpperCase() !== 'VALIDATE' &&
82
79
  cap.name.toUpperCase() !== 'REPORT');
83
80
  }
84
- setIsLoading(false);
85
- onComplete?.(result.message, capabilities);
81
+ // Save state before completing
82
+ handlers?.updateState({
83
+ capabilities,
84
+ message: result.message,
85
+ });
86
+ // Add Report component to queue
87
+ handlers?.addToQueue(createReportDefinition(result.message, capabilities));
88
+ // Signal completion
89
+ handlers?.completeActive();
86
90
  }
87
91
  }
88
92
  catch (err) {
89
93
  await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
90
94
  if (mounted) {
91
95
  const errorMessage = formatErrorMessage(err);
92
- setIsLoading(false);
93
- if (onError) {
94
- onError(errorMessage);
95
- }
96
- else {
97
- setError(errorMessage);
98
- }
96
+ setError(errorMessage);
97
+ // Save error state
98
+ handlers?.updateState({
99
+ error: errorMessage,
100
+ });
101
+ handlers?.onError(errorMessage);
99
102
  }
100
103
  }
101
104
  }
@@ -103,10 +106,10 @@ export function Introspect({ tasks, state, service, children, debug = false, onE
103
106
  return () => {
104
107
  mounted = false;
105
108
  };
106
- }, [tasks, done, service, debug, onComplete, onError]);
109
+ }, [tasks, isActive, service, debug, handlers]);
107
110
  // Don't render wrapper when done and nothing to show
108
- if (!isLoading && !error && !children) {
111
+ if (!isActive && !error && !children) {
109
112
  return null;
110
113
  }
111
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
114
+ 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] }));
112
115
  }
package/dist/ui/Main.js CHANGED
@@ -1,33 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React from 'react';
2
+ import { useEffect, useState } from 'react';
3
3
  import { FeedbackType } from '../types/types.js';
4
4
  import { createAnthropicService, } from '../services/anthropic.js';
5
- import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
6
- import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, loadDebugSetting, saveDebugSetting, } from '../services/configuration.js';
5
+ import { createCommandDefinition, createConfigDefinitionWithKeys, createFeedback, createMessage, createWelcomeDefinition, } from '../services/components.js';
6
+ import { getConfigurationRequiredMessage, getMissingConfigKeys, loadConfig, loadDebugSetting, saveAnthropicConfig, saveDebugSetting, } from '../services/configuration.js';
7
7
  import { registerGlobalShortcut } from '../services/keyboard.js';
8
- import { getCancellationMessage } from '../services/messages.js';
9
- import { exitApp } from '../services/process.js';
10
- import { createAnswerHandlers } from '../handlers/answer.js';
11
- import { createCommandHandlers } from '../handlers/command.js';
12
- import { createConfigHandlers } from '../handlers/config.js';
13
- import { createExecuteHandlers } from '../handlers/execute.js';
14
- import { createExecutionHandlers } from '../handlers/execution.js';
15
- import { createIntrospectHandlers } from '../handlers/introspect.js';
16
- import { createPlanHandlers } from '../handlers/plan.js';
17
- import { Column } from './Column.js';
8
+ import { Workflow } from './Workflow.js';
18
9
  export const Main = ({ app, command }) => {
19
- const [service, setService] = React.useState(() => {
20
- if (hasValidAnthropicKey()) {
21
- const config = loadConfig();
22
- return createAnthropicService(config.anthropic);
23
- }
24
- return null;
25
- });
26
- const [timeline, setTimeline] = React.useState([]);
27
- const [queue, setQueue] = React.useState([]);
28
- const [isDebug, setIsDebug] = React.useState(() => loadDebugSetting());
10
+ const [service, setService] = useState(null);
11
+ const [initialQueue, setInitialQueue] = useState(null);
12
+ const [isDebug, setIsDebug] = useState(() => loadDebugSetting());
29
13
  // Register global keyboard shortcuts
30
- React.useEffect(() => {
14
+ useEffect(() => {
31
15
  registerGlobalShortcut('shift+tab', () => {
32
16
  setIsDebug((prev) => {
33
17
  const newValue = !prev;
@@ -36,88 +20,75 @@ export const Main = ({ app, command }) => {
36
20
  });
37
21
  });
38
22
  }, []);
39
- const addToTimeline = React.useCallback((...items) => {
40
- setTimeline((timeline) => [...timeline, ...items]);
41
- }, []);
42
- const processNextInQueue = React.useCallback(() => {
43
- setQueue((currentQueue) => {
44
- if (currentQueue.length === 0)
45
- return currentQueue;
46
- const [first, ...rest] = currentQueue;
47
- if (isStateless(first)) {
48
- addToTimeline(first);
49
- return rest;
23
+ // Initialize service on mount
24
+ useEffect(() => {
25
+ if (service !== null) {
26
+ return;
27
+ }
28
+ const missingKeys = getMissingConfigKeys();
29
+ if (missingKeys.length === 0) {
30
+ // Config exists - create service immediately
31
+ try {
32
+ const config = loadConfig();
33
+ const newService = createAnthropicService(config.anthropic);
34
+ setService(newService);
50
35
  }
51
- return currentQueue;
52
- });
53
- }, [addToTimeline]);
54
- // Core abort handler
55
- const handleAborted = React.useCallback((operationName) => {
56
- setQueue((currentQueue) => {
57
- if (currentQueue.length === 0)
58
- return currentQueue;
59
- const [first] = currentQueue;
60
- if (!isStateless(first)) {
61
- addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, getCancellationMessage(operationName)));
36
+ catch (error) {
37
+ // Service creation failed - show error and exit
38
+ const errorMessage = error instanceof Error
39
+ ? error.message
40
+ : 'Failed to initialize service';
41
+ setInitialQueue([createFeedback(FeedbackType.Failed, errorMessage)]);
62
42
  }
63
- exitApp(0);
64
- return [];
65
- });
66
- }, [addToTimeline]);
67
- // Create operations object
68
- const ops = React.useMemo(() => ({
69
- addToTimeline,
70
- setQueue,
71
- service,
72
- }), [addToTimeline, service]);
73
- // Create handlers in dependency order
74
- const introspectHandlers = React.useMemo(() => createIntrospectHandlers(ops, handleAborted), [ops, handleAborted]);
75
- const answerHandlers = React.useMemo(() => createAnswerHandlers(ops, handleAborted), [ops, handleAborted]);
76
- const executeHandlers = React.useMemo(() => createExecuteHandlers(ops, handleAborted), [ops, handleAborted]);
77
- const executionHandlers = React.useMemo(() => createExecutionHandlers(ops, {
78
- introspect: introspectHandlers,
79
- answer: answerHandlers,
80
- execute: executeHandlers,
81
- }), [ops, introspectHandlers, answerHandlers, executeHandlers]);
82
- const planHandlers = React.useMemo(() => createPlanHandlers(ops, handleAborted, executionHandlers), [ops, handleAborted, executionHandlers]);
83
- const commandHandlers = React.useMemo(() => createCommandHandlers(ops, handleAborted, planHandlers, executionHandlers), [ops, handleAborted, planHandlers, executionHandlers]);
84
- const configHandlers = React.useMemo(() => createConfigHandlers(ops, handleAborted, command, commandHandlers, setService), [ops, handleAborted, command, commandHandlers]);
85
- // Initialize queue on mount
86
- React.useEffect(() => {
87
- const hasConfig = !!service;
88
- if (command && hasConfig) {
89
- setQueue([
90
- createCommandDefinition(command, service, commandHandlers.onError, commandHandlers.onComplete, commandHandlers.onAborted),
91
- ]);
92
43
  }
93
- else if (command && !hasConfig) {
94
- setQueue([
95
- createMessage(getConfigurationRequiredMessage()),
96
- createConfigDefinition(configHandlers.onFinished, configHandlers.onAborted),
97
- ]);
44
+ // If config is missing, service will be created after config completes
45
+ }, [service]);
46
+ // Initialize queue after service is ready
47
+ useEffect(() => {
48
+ // Only set initial queue once
49
+ if (initialQueue !== null) {
50
+ return;
98
51
  }
99
- else if (!command && hasConfig) {
100
- setQueue([createWelcomeDefinition(app)]);
101
- }
102
- else {
103
- setQueue([
52
+ const missingKeys = getMissingConfigKeys();
53
+ if (missingKeys.length > 0) {
54
+ // Missing config - show initial configuration flow
55
+ const handleConfigFinished = (config) => {
56
+ // Save config and create service
57
+ try {
58
+ const newConfig = saveAnthropicConfig(config);
59
+ const newService = createAnthropicService(newConfig.anthropic);
60
+ setService(newService);
61
+ }
62
+ catch (error) {
63
+ // Config creation failed - show error
64
+ const errorMessage = error instanceof Error
65
+ ? error.message
66
+ : 'Failed to save configuration';
67
+ setInitialQueue([createFeedback(FeedbackType.Failed, errorMessage)]);
68
+ }
69
+ };
70
+ const handleConfigAborted = (operation) => {
71
+ // Config was cancelled - just exit
72
+ };
73
+ setInitialQueue([
104
74
  createWelcomeDefinition(app),
105
- createMessage(getConfigurationRequiredMessage(true)),
106
- createConfigDefinition(configHandlers.onFinished, configHandlers.onAborted),
75
+ createMessage(getConfigurationRequiredMessage()),
76
+ createConfigDefinitionWithKeys(missingKeys, handleConfigFinished, handleConfigAborted),
107
77
  ]);
108
78
  }
109
- }, []);
110
- // Process queue whenever it changes
111
- React.useEffect(() => {
112
- processNextInQueue();
113
- }, [queue, processNextInQueue]);
114
- // Exit when queue is empty and timeline has content
115
- React.useEffect(() => {
116
- if (queue.length === 0 && timeline.length > 0) {
117
- exitApp(0);
79
+ else if (service && command) {
80
+ // Valid service exists and command provided - execute command
81
+ setInitialQueue([createCommandDefinition(command, service)]);
82
+ }
83
+ else if (service && !command) {
84
+ // Valid service exists, no command - show welcome
85
+ setInitialQueue([createWelcomeDefinition(app)]);
118
86
  }
119
- }, [queue, timeline]);
120
- const current = queue.length > 0 ? queue[0] : null;
121
- const items = React.useMemo(() => [...timeline, ...(current ? [current] : [])], [timeline, current]);
122
- return _jsx(Column, { items: items, debug: isDebug });
87
+ // Wait for service to be initialized before setting queue
88
+ }, [app, command, service, initialQueue]);
89
+ // Don't render until initial queue is ready
90
+ if (initialQueue === null) {
91
+ return null;
92
+ }
93
+ return _jsx(Workflow, { initialQueue: initialQueue, debug: isDebug });
123
94
  };
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  export const Message = ({ text }) => {
4
- return (_jsx(Box, { children: _jsx(Text, { children: text }) }));
4
+ return (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: text }) }));
5
5
  };
package/dist/ui/Plan.js CHANGED
@@ -49,11 +49,11 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
49
49
  }
50
50
  return item;
51
51
  }
52
- export function Plan({ message, tasks, state, debug = false, onSelectionConfirmed, onAborted, }) {
52
+ export function Plan({ message, tasks, state, isActive = true, debug = false, handlers, onSelectionConfirmed, }) {
53
+ // isActive passed as prop
53
54
  const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
54
55
  const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(state?.currentDefineGroupIndex ?? 0);
55
56
  const [completedSelections, setCompletedSelections] = useState(state?.completedSelections ?? []);
56
- const [isDone, setIsDone] = useState(state?.done ?? false);
57
57
  // Find all Define tasks
58
58
  const defineTaskIndices = tasks
59
59
  .map((t, idx) => (t.type === TaskType.Define ? idx : -1))
@@ -65,13 +65,29 @@ export function Plan({ message, tasks, state, debug = false, onSelectionConfirme
65
65
  ? defineTask.params.options.length
66
66
  : 0;
67
67
  const hasMoreGroups = currentDefineGroupIndex < defineTaskIndices.length - 1;
68
+ // If no DEFINE tasks, immediately confirm with all tasks
69
+ useEffect(() => {
70
+ if (isActive && defineTaskIndices.length === 0 && onSelectionConfirmed) {
71
+ // No selection needed - all tasks are concrete
72
+ const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
73
+ onSelectionConfirmed(concreteTasks);
74
+ // Signal Plan completion after adding Confirm to queue
75
+ handlers?.completeActive();
76
+ }
77
+ }, [
78
+ isActive,
79
+ defineTaskIndices.length,
80
+ tasks,
81
+ onSelectionConfirmed,
82
+ handlers,
83
+ ]);
68
84
  useInput((input, key) => {
69
- // Don't handle input if already done or no define task
70
- if (isDone || !defineTask) {
85
+ // Don't handle input if not active or no define task
86
+ if (!isActive || !defineTask) {
71
87
  return;
72
88
  }
73
89
  if (key.escape) {
74
- onAborted();
90
+ handlers?.onAborted('task selection');
75
91
  return;
76
92
  }
77
93
  if (key.downArrow) {
@@ -97,16 +113,13 @@ export function Plan({ message, tasks, state, debug = false, onSelectionConfirme
97
113
  setCompletedSelections(newCompletedSelections);
98
114
  if (hasMoreGroups) {
99
115
  // Advance to next group
100
- setCurrentDefineGroupIndex(currentDefineGroupIndex + 1);
116
+ const newGroupIndex = currentDefineGroupIndex + 1;
117
+ setCurrentDefineGroupIndex(newGroupIndex);
101
118
  setHighlightedIndex(null);
102
119
  }
103
120
  else {
104
- // Last group - mark as done to show the selection
105
- setIsDone(true);
106
- setHighlightedIndex(null); // Clear highlight to show Execute color
107
- if (state) {
108
- state.done = true;
109
- }
121
+ // Clear highlight to show Execute color
122
+ setHighlightedIndex(null);
110
123
  // Build refined task list with only selected options (no discarded or ignored ones)
111
124
  const refinedTasks = [];
112
125
  tasks.forEach((task, idx) => {
@@ -116,8 +129,10 @@ export function Plan({ message, tasks, state, debug = false, onSelectionConfirme
116
129
  // This is a Define task - only include the selected option
117
130
  const options = task.params.options;
118
131
  const selectedIndex = newCompletedSelections[defineGroupIndex];
132
+ const selectedOption = String(options[selectedIndex]);
133
+ // Use Execute as default - LLM will properly classify during refinement
119
134
  refinedTasks.push({
120
- action: String(options[selectedIndex]),
135
+ action: selectedOption,
121
136
  type: TaskType.Execute,
122
137
  });
123
138
  }
@@ -127,26 +142,33 @@ export function Plan({ message, tasks, state, debug = false, onSelectionConfirme
127
142
  refinedTasks.push(task);
128
143
  }
129
144
  });
130
- onSelectionConfirmed?.(refinedTasks);
145
+ if (onSelectionConfirmed) {
146
+ // Callback will handle the entire flow (Refinement, refined Plan, Confirm)
147
+ // So we need to complete the Plan first
148
+ handlers?.completeActive();
149
+ onSelectionConfirmed(refinedTasks);
150
+ }
151
+ else {
152
+ // No selection callback, just complete normally
153
+ handlers?.completeActive();
154
+ }
131
155
  }
132
156
  }
133
- }, { isActive: !isDone && defineTask !== null });
134
- // Sync state back to state object
157
+ }, { isActive: isActive && defineTask !== null });
158
+ // Sync state back to component definition
159
+ // This ensures timeline can render historical state and tests can validate behavior
135
160
  useEffect(() => {
136
- if (state) {
137
- state.highlightedIndex = highlightedIndex;
138
- state.currentDefineGroupIndex = currentDefineGroupIndex;
139
- state.completedSelections = completedSelections;
140
- state.done = isDone;
141
- }
161
+ handlers?.updateState({
162
+ highlightedIndex,
163
+ currentDefineGroupIndex,
164
+ completedSelections,
165
+ });
142
166
  }, [
143
167
  highlightedIndex,
144
168
  currentDefineGroupIndex,
145
169
  completedSelections,
146
- isDone,
147
- state,
170
+ handlers,
148
171
  ]);
149
- const isCurrent = isDone === false;
150
172
  const listItems = tasks.map((task, idx) => {
151
173
  // Find which define group this task belongs to (if any)
152
174
  const defineGroupIndex = defineTaskIndices.indexOf(idx);
@@ -159,9 +181,9 @@ export function Plan({ message, tasks, state, debug = false, onSelectionConfirme
159
181
  childIndex = completedSelections[defineGroupIndex] ?? null;
160
182
  }
161
183
  else if (defineGroupIndex === currentDefineGroupIndex) {
162
- // Current active group - show live navigation unless done
163
- if (isDone) {
164
- // If done, show the completed selection for this group too
184
+ // Current active group - show live navigation unless not active
185
+ if (!isActive) {
186
+ // If not active, show the completed selection for this group too
165
187
  childIndex = completedSelections[defineGroupIndex] ?? null;
166
188
  }
167
189
  else {
@@ -169,12 +191,12 @@ export function Plan({ message, tasks, state, debug = false, onSelectionConfirme
169
191
  }
170
192
  }
171
193
  }
172
- // Show arrow on current active define task when no child is highlighted and not done
194
+ // Show arrow on current active define task when no child is highlighted and is active
173
195
  const isDefineWithoutSelection = isDefineTask &&
174
196
  defineGroupIndex === currentDefineGroupIndex &&
175
197
  highlightedIndex === null &&
176
- !isDone;
177
- return taskToListItem(task, childIndex, isDefineWithoutSelection, isCurrent);
198
+ isActive;
199
+ return taskToListItem(task, childIndex, isDefineWithoutSelection, isActive);
178
200
  });
179
- return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, children: _jsx(Label, { description: message, taskType: TaskType.Plan, showType: debug, isCurrent: isCurrent }) })), _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug })] }));
201
+ return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Plan, showType: debug, isCurrent: isActive }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug }) })] }));
180
202
  }
@@ -3,13 +3,12 @@ import { Box } from 'ink';
3
3
  import { useInput } from '../services/keyboard.js';
4
4
  import { Message } from './Message.js';
5
5
  import { Spinner } from './Spinner.js';
6
- export const Refinement = ({ text, state, onAborted }) => {
7
- const isDone = state?.done ?? false;
8
- useInput((input, key) => {
9
- if (key.escape && !isDone) {
10
- onAborted();
6
+ export const Refinement = ({ text, isActive = true, onAborted, }) => {
7
+ useInput((_, key) => {
8
+ if (key.escape && isActive) {
9
+ onAborted('plan refinement');
11
10
  return;
12
11
  }
13
- }, { isActive: !isDone });
14
- return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text }), !isDone && _jsx(Spinner, {})] }));
12
+ }, { isActive });
13
+ return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text }), isActive && _jsx(Spinner, {})] }));
15
14
  };
package/dist/ui/Report.js CHANGED
@@ -10,5 +10,5 @@ function CapabilityItem({ name, description, isBuiltIn, isIndirect, }) {
10
10
  return (_jsxs(Box, { children: [_jsx(Text, { children: "- " }), _jsx(Text, { color: color, children: name }), _jsxs(Text, { children: [" - ", description] })] }));
11
11
  }
12
12
  export function Report({ message, capabilities }) {
13
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: message }), _jsx(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: capabilities.map((capability, index) => (_jsx(CapabilityItem, { name: capability.name, description: capability.description, isBuiltIn: capability.isBuiltIn, isIndirect: capability.isIndirect }, index))) })] }));
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, isBuiltIn: capability.isBuiltIn, isIndirect: capability.isIndirect }, index))) })] }));
14
14
  }
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { Colors } from '../services/colors.js';
4
+ export function UserQuery({ children }) {
5
+ return (_jsx(Box, { paddingX: 1, alignSelf: "flex-start", backgroundColor: Colors.Background.UserQuery, children: _jsx(Text, { color: Colors.Text.UserQuery, children: children }) }));
6
+ }
@@ -6,29 +6,29 @@ import { Colors, getTextColor } from '../services/colors.js';
6
6
  import { useInput } from '../services/keyboard.js';
7
7
  import { formatErrorMessage } from '../services/messages.js';
8
8
  import { ensureMinimumTime } from '../services/timing.js';
9
+ import { saveConfig, unflattenConfig } from '../services/configuration.js';
10
+ import { Config, StepType } from './Config.js';
9
11
  import { Spinner } from './Spinner.js';
10
12
  const MIN_PROCESSING_TIME = 1000;
11
- export function Validate({ missingConfig, userRequest, state, service, children, onError, onComplete, onAborted, }) {
12
- const done = state?.done ?? false;
13
- const isCurrent = done === false;
13
+ export function Validate({ missingConfig, userRequest, state, isActive = true, service, children, debug, onError, onComplete, onAborted, handlers, }) {
14
+ // isActive passed as prop
14
15
  const [error, setError] = useState(null);
15
- const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
16
16
  const [completionMessage, setCompletionMessage] = useState(null);
17
- useInput((input, key) => {
18
- if (key.escape && isLoading && !done) {
19
- setIsLoading(false);
20
- onAborted();
17
+ const [configRequirements, setConfigRequirements] = useState(null);
18
+ const [showConfig, setShowConfig] = useState(false);
19
+ useInput((_, key) => {
20
+ if (key.escape && isActive && !showConfig) {
21
+ onAborted('validation');
21
22
  }
22
- }, { isActive: isLoading && !done });
23
+ }, { isActive: isActive && !showConfig });
23
24
  useEffect(() => {
24
- // Skip processing if done
25
- if (done) {
25
+ // Skip processing if not active
26
+ if (!isActive) {
26
27
  return;
27
28
  }
28
29
  // Skip processing if no service available
29
30
  if (!service) {
30
31
  setError('No service available');
31
- setIsLoading(false);
32
32
  return;
33
33
  }
34
34
  let mounted = true;
@@ -67,15 +67,22 @@ export function Validate({ missingConfig, userRequest, state, service, children,
67
67
  ];
68
68
  const message = messages[Math.floor(Math.random() * messages.length)];
69
69
  setCompletionMessage(message);
70
- setIsLoading(false);
71
- onComplete?.(withDescriptions);
70
+ setConfigRequirements(withDescriptions);
71
+ // Save state after validation completes
72
+ handlers?.updateState({
73
+ configRequirements: withDescriptions,
74
+ validated: true,
75
+ });
72
76
  }
73
77
  }
74
78
  catch (err) {
75
79
  await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
76
80
  if (mounted) {
77
81
  const errorMessage = formatErrorMessage(err);
78
- setIsLoading(false);
82
+ // Save error state
83
+ handlers?.updateState({
84
+ error: errorMessage,
85
+ });
79
86
  if (onError) {
80
87
  onError(errorMessage);
81
88
  }
@@ -92,17 +99,40 @@ export function Validate({ missingConfig, userRequest, state, service, children,
92
99
  }, [
93
100
  missingConfig,
94
101
  userRequest,
95
- done,
102
+ isActive,
96
103
  service,
97
104
  onComplete,
98
105
  onError,
99
106
  onAborted,
100
107
  ]);
101
- // Don't render when done and nothing to show
102
- if (done && !completionMessage && !error && !children) {
108
+ // Don't render when not active and nothing to show
109
+ if (!isActive && !completionMessage && !error && !children) {
103
110
  return null;
104
111
  }
105
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsxs(Text, { color: getTextColor(isCurrent), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && !isLoading && (_jsx(Box, { children: _jsx(Text, { color: getTextColor(isCurrent), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
112
+ // Create ConfigSteps from requirements
113
+ const configSteps = configRequirements
114
+ ? configRequirements.map((req) => ({
115
+ description: req.description || req.path,
116
+ key: req.path,
117
+ path: req.path,
118
+ type: StepType.Text,
119
+ value: null,
120
+ validate: () => true,
121
+ }))
122
+ : null;
123
+ const handleConfigFinished = (config) => {
124
+ // Convert flat dotted keys to nested structure grouped by section
125
+ const configBySection = unflattenConfig(config);
126
+ // Save each section
127
+ for (const [section, sectionConfig] of Object.entries(configBySection)) {
128
+ saveConfig(section, sectionConfig);
129
+ }
130
+ onComplete?.(configRequirements);
131
+ };
132
+ const handleConfigAborted = (operation) => {
133
+ onAborted(operation);
134
+ };
135
+ 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 && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps, isActive: isActive, debug: debug, onFinished: handleConfigFinished, onAborted: handleConfigAborted, handlers: handlers }) })), children] }));
106
136
  }
107
137
  /**
108
138
  * Build prompt for VALIDATE tool
@@ -21,7 +21,7 @@ function Description({ description }) {
21
21
  function Usage() {
22
22
  return (_jsxs(Box, { flexDirection: "column", marginTop: 1, gap: 1, children: [_jsx(Section, { title: "Get started:", children: _jsx(Example, { children: "list skills" }) }), _jsx(Section, { title: "Usage:", children: _jsx(Example, { children: "[describe your request]" }) })] }));
23
23
  }
24
- function Section({ title, children, }) {
24
+ function Section({ title, children }) {
25
25
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: Palette.White, children: title }), children] }));
26
26
  }
27
27
  function Example({ children }) {