prompt-language-shell 0.8.4 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/configuration/io.js +85 -0
  2. package/dist/configuration/messages.js +30 -0
  3. package/dist/configuration/schema.js +167 -0
  4. package/dist/configuration/transformation.js +55 -0
  5. package/dist/configuration/types.js +30 -0
  6. package/dist/configuration/validation.js +52 -0
  7. package/dist/execution/handlers.js +135 -0
  8. package/dist/execution/processing.js +36 -0
  9. package/dist/execution/reducer.js +148 -0
  10. package/dist/execution/types.js +12 -0
  11. package/dist/execution/validation.js +12 -0
  12. package/dist/index.js +1 -1
  13. package/dist/services/anthropic.js +2 -1
  14. package/dist/services/colors.js +22 -12
  15. package/dist/services/components.js +35 -11
  16. package/dist/services/config-labels.js +15 -15
  17. package/dist/services/logger.js +2 -1
  18. package/dist/services/messages.js +53 -1
  19. package/dist/services/refinement.js +11 -6
  20. package/dist/services/router.js +92 -52
  21. package/dist/skills/execute.md +79 -9
  22. package/dist/skills/schedule.md +121 -29
  23. package/dist/tools/execute.tool.js +4 -0
  24. package/dist/types/schemas.js +1 -0
  25. package/dist/ui/Answer.js +36 -15
  26. package/dist/ui/Command.js +43 -23
  27. package/dist/ui/Component.js +147 -33
  28. package/dist/ui/Config.js +73 -79
  29. package/dist/ui/Confirm.js +34 -21
  30. package/dist/ui/Execute.js +129 -329
  31. package/dist/ui/Feedback.js +2 -1
  32. package/dist/ui/Introspect.js +51 -24
  33. package/dist/ui/Label.js +4 -3
  34. package/dist/ui/List.js +3 -2
  35. package/dist/ui/Main.js +5 -1
  36. package/dist/ui/Refinement.js +8 -1
  37. package/dist/ui/Schedule.js +89 -61
  38. package/dist/ui/Validate.js +75 -77
  39. package/dist/ui/Workflow.js +47 -123
  40. package/package.json +1 -1
  41. package/dist/services/configuration.js +0 -409
package/dist/ui/Answer.js CHANGED
@@ -1,20 +1,29 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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
6
  import { useInput } from '../services/keyboard.js';
7
7
  import { formatErrorMessage } from '../services/messages.js';
8
8
  import { withMinimumTime } from '../services/timing.js';
9
9
  import { Spinner } from './Spinner.js';
10
10
  const MINIMUM_PROCESSING_TIME = 400;
11
- export function Answer({ question, state, status, service, stateHandlers, lifecycleHandlers, errorHandlers, workflowHandlers, }) {
11
+ export const AnswerView = ({ question, state, status }) => {
12
+ const isActive = status === ComponentStatus.Active;
13
+ const { error, answer } = state;
14
+ const lines = answer ? answer.split('\n') : [];
15
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !answer && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Finding answer. " }), _jsx(Spinner, {})] })), answer && (_jsxs(_Fragment, { children: [_jsx(Box, { marginLeft: 1, marginBottom: 1, children: _jsx(Text, { color: getTextColor(isActive), children: question }) }), _jsx(Box, { flexDirection: "column", paddingLeft: 3, children: lines.map((line, index) => (_jsx(Text, { color: getTextColor(isActive), children: line }, index))) })] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
16
+ };
17
+ /**
18
+ * Answer controller: Fetches answer from LLM
19
+ */
20
+ export function Answer({ question, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
12
21
  const isActive = status === ComponentStatus.Active;
13
22
  const [error, setError] = useState(null);
14
- const [answer, setAnswer] = useState(state?.answer ?? null);
23
+ const [answer, setAnswer] = useState(null);
15
24
  useInput((input, key) => {
16
25
  if (key.escape && isActive) {
17
- errorHandlers?.onAborted('answer');
26
+ requestHandlers.onAborted('answer');
18
27
  }
19
28
  }, { isActive });
20
29
  useEffect(() => {
@@ -30,27 +39,32 @@ export function Answer({ question, state, status, service, stateHandlers, lifecy
30
39
  if (mounted) {
31
40
  // Add debug components to timeline if present
32
41
  if (result.debug?.length) {
33
- workflowHandlers?.addToTimeline(...result.debug);
42
+ workflowHandlers.addToTimeline(...result.debug);
34
43
  }
35
44
  // Extract answer from result
36
45
  const answerText = result.answer || '';
37
46
  setAnswer(answerText);
38
- // Update component state so answer persists in timeline
39
- stateHandlers?.updateState({
47
+ // Expose final state
48
+ const finalState = {
40
49
  answer: answerText,
41
- });
50
+ error: null,
51
+ };
52
+ requestHandlers.onCompleted(finalState);
42
53
  // Signal completion
43
- lifecycleHandlers?.completeActive();
54
+ lifecycleHandlers.completeActive();
44
55
  }
45
56
  }
46
57
  catch (err) {
47
58
  if (mounted) {
48
59
  const errorMessage = formatErrorMessage(err);
49
60
  setError(errorMessage);
50
- stateHandlers?.updateState({
61
+ // Expose final state with error
62
+ const finalState = {
51
63
  error: errorMessage,
52
- });
53
- errorHandlers?.onError(errorMessage);
64
+ answer: null,
65
+ };
66
+ requestHandlers.onCompleted(finalState);
67
+ requestHandlers.onError(errorMessage);
54
68
  }
55
69
  }
56
70
  }
@@ -58,7 +72,14 @@ export function Answer({ question, state, status, service, stateHandlers, lifecy
58
72
  return () => {
59
73
  mounted = false;
60
74
  };
61
- }, [question, isActive, service]);
62
- const lines = answer ? answer.split('\n') : [];
63
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !answer && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Finding answer. " }), _jsx(Spinner, {})] })), answer && (_jsxs(_Fragment, { children: [_jsx(Box, { marginLeft: 1, marginBottom: 1, children: _jsx(Text, { color: getTextColor(isActive), children: question }) }), _jsx(Box, { flexDirection: "column", paddingLeft: 3, children: lines.map((line, index) => (_jsx(Text, { color: getTextColor(isActive), children: line }, index))) })] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
75
+ }, [
76
+ question,
77
+ isActive,
78
+ service,
79
+ requestHandlers,
80
+ lifecycleHandlers,
81
+ workflowHandlers,
82
+ ]);
83
+ const state = { error, answer };
84
+ return _jsx(AnswerView, { question: question, state: state, status: status });
64
85
  }
@@ -1,7 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
- import { ComponentStatus } from '../types/components.js';
4
+ import { ComponentStatus, } from '../types/components.js';
5
5
  import { TaskType } from '../types/types.js';
6
6
  import { Colors } from '../services/colors.js';
7
7
  import { createScheduleDefinition } from '../services/components.js';
@@ -13,12 +13,22 @@ import { ensureMinimumTime } from '../services/timing.js';
13
13
  import { Spinner } from './Spinner.js';
14
14
  import { UserQuery } from './UserQuery.js';
15
15
  const MIN_PROCESSING_TIME = 400; // purely for visual effect
16
- export function Command({ command, state, status, service, stateHandlers, lifecycleHandlers, queueHandlers, errorHandlers, workflowHandlers, onAborted, }) {
16
+ export const CommandView = ({ command, state, status }) => {
17
17
  const isActive = status === ComponentStatus.Active;
18
- const [error, setError] = useState(state?.error ?? null);
18
+ const { error } = state;
19
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [!isActive ? (_jsxs(UserQuery, { children: ["> pls ", command] })) : (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: Colors.Text.Active, children: ["> pls ", command] }), _jsx(Text, { children: " " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
20
+ };
21
+ /**
22
+ * Command controller: Processes and routes command
23
+ */
24
+ export function Command({ command, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, onAborted, }) {
25
+ const isActive = status === ComponentStatus.Active;
26
+ const [error, setError] = useState(null);
27
+ const [message, setMessage] = useState(null);
28
+ const [tasks, setTasks] = useState([]);
19
29
  useInput((_, key) => {
20
30
  if (key.escape && isActive) {
21
- errorHandlers?.onAborted('request');
31
+ requestHandlers.onAborted('request');
22
32
  onAborted?.('request');
23
33
  }
24
34
  }, { isActive });
@@ -52,38 +62,36 @@ export function Command({ command, state, status, service, stateHandlers, lifecy
52
62
  ? [...scheduleDebug, ...(result.debug || [])]
53
63
  : scheduleDebug;
54
64
  if (debugComponents.length > 0) {
55
- workflowHandlers?.addToTimeline(...debugComponents);
65
+ workflowHandlers.addToTimeline(...debugComponents);
56
66
  }
57
- // Save result to state for timeline display
58
- stateHandlers?.updateState({
67
+ // Update local state
68
+ setMessage(result.message);
69
+ setTasks(result.tasks);
70
+ // Expose final state
71
+ const finalState = {
72
+ error: null,
59
73
  message: result.message,
60
74
  tasks: result.tasks,
61
- });
75
+ };
76
+ requestHandlers.onCompleted(finalState);
62
77
  // Check if tasks contain DEFINE type (variant selection needed)
63
78
  const hasDefineTask = result.tasks.some((task) => task.type === TaskType.Define);
64
- // Guard: ensure all required handlers are present
65
- if (!queueHandlers ||
66
- !lifecycleHandlers ||
67
- !workflowHandlers ||
68
- !errorHandlers) {
69
- return;
70
- }
71
79
  // Create Schedule definition
72
80
  const scheduleDefinition = createScheduleDefinition(result.message, result.tasks, hasDefineTask
73
81
  ? async (selectedTasks) => {
74
82
  // Refinement flow for DEFINE tasks
75
- await handleRefinement(selectedTasks, svc, command, queueHandlers, lifecycleHandlers, workflowHandlers, errorHandlers);
83
+ await handleRefinement(selectedTasks, svc, command, lifecycleHandlers, workflowHandlers, requestHandlers);
76
84
  }
77
85
  : undefined);
78
86
  if (hasDefineTask) {
79
87
  // DEFINE tasks: Move Command to timeline, add Schedule to queue
80
88
  lifecycleHandlers.completeActive();
81
- queueHandlers.addToQueue(scheduleDefinition);
89
+ workflowHandlers.addToQueue(scheduleDefinition);
82
90
  }
83
91
  else {
84
92
  // No DEFINE tasks: Complete Command, then route to Confirm flow
85
93
  lifecycleHandlers.completeActive();
86
- routeTasksWithConfirm(result.tasks, result.message, svc, command, queueHandlers, workflowHandlers, errorHandlers, false);
94
+ routeTasksWithConfirm(result.tasks, result.message, svc, command, lifecycleHandlers, workflowHandlers, requestHandlers, false);
87
95
  }
88
96
  }
89
97
  }
@@ -92,10 +100,14 @@ export function Command({ command, state, status, service, stateHandlers, lifecy
92
100
  if (mounted) {
93
101
  const errorMessage = formatErrorMessage(err);
94
102
  setError(errorMessage);
95
- stateHandlers?.updateState({
103
+ // Expose final state with error
104
+ const finalState = {
96
105
  error: errorMessage,
97
- });
98
- errorHandlers?.onError(errorMessage);
106
+ message: null,
107
+ tasks: [],
108
+ };
109
+ requestHandlers.onCompleted(finalState);
110
+ requestHandlers.onError(errorMessage);
99
111
  }
100
112
  }
101
113
  }
@@ -103,6 +115,14 @@ export function Command({ command, state, status, service, stateHandlers, lifecy
103
115
  return () => {
104
116
  mounted = false;
105
117
  };
106
- }, [command, isActive, service]);
107
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [!isActive ? (_jsxs(UserQuery, { children: ["> pls ", command] })) : (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: Colors.Text.Active, children: ["> pls ", command] }), _jsx(Text, { children: " " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
118
+ }, [
119
+ command,
120
+ isActive,
121
+ service,
122
+ requestHandlers,
123
+ lifecycleHandlers,
124
+ workflowHandlers,
125
+ ]);
126
+ const state = { error, message, tasks };
127
+ return _jsx(CommandView, { command: command, state: state, status: status });
108
128
  }
@@ -1,49 +1,163 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { memo } from 'react';
3
3
  import { ComponentName } from '../types/types.js';
4
- import { Answer } from './Answer.js';
5
- import { Command } from './Command.js';
6
- import { Config } from './Config.js';
7
- import { Confirm } from './Confirm.js';
4
+ import { Answer, AnswerView } from './Answer.js';
5
+ import { Command, CommandView } from './Command.js';
6
+ import { Config, ConfigView } from './Config.js';
7
+ import { Confirm, ConfirmView } from './Confirm.js';
8
8
  import { Debug } from './Debug.js';
9
- import { Execute } from './Execute.js';
9
+ import { Execute, ExecuteView } from './Execute.js';
10
10
  import { Feedback } from './Feedback.js';
11
- import { Introspect } from './Introspect.js';
11
+ import { Introspect, IntrospectView } from './Introspect.js';
12
12
  import { Message } from './Message.js';
13
- import { Refinement } from './Refinement.js';
13
+ import { Refinement, RefinementView } from './Refinement.js';
14
14
  import { Report } from './Report.js';
15
- import { Schedule } from './Schedule.js';
16
- import { Validate } from './Validate.js';
15
+ import { Schedule, ScheduleView } from './Schedule.js';
16
+ import { Validate, ValidateView } from './Validate.js';
17
17
  import { Welcome } from './Welcome.js';
18
- export const Component = memo(function Component({ def, debug, }) {
18
+ /**
19
+ * Render a simple component (no lifecycle management)
20
+ */
21
+ export const SimpleComponent = memo(function SimpleComponent({ def, }) {
19
22
  switch (def.name) {
23
+ case ComponentName.Welcome: {
24
+ const { props, status } = def;
25
+ return _jsx(Welcome, { ...props, status: status });
26
+ }
27
+ case ComponentName.Feedback: {
28
+ const { props, status } = def;
29
+ return _jsx(Feedback, { ...props, status: status });
30
+ }
31
+ case ComponentName.Message: {
32
+ const { props, status } = def;
33
+ return _jsx(Message, { ...props, status: status });
34
+ }
35
+ case ComponentName.Debug: {
36
+ const { props, status } = def;
37
+ return _jsx(Debug, { ...props, status: status });
38
+ }
39
+ case ComponentName.Report: {
40
+ const { props, status } = def;
41
+ return _jsx(Report, { ...props, status: status });
42
+ }
43
+ default:
44
+ throw new Error(`Unknown simple component: ${def.name}`);
45
+ }
46
+ });
47
+ /**
48
+ * Render a managed component (controller with lifecycle management)
49
+ */
50
+ export const ControllerComponent = memo(function ControllerComponent({ def, debug, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
51
+ switch (def.name) {
52
+ case ComponentName.Config: {
53
+ const { props: { steps, onFinished, onAborted }, status, } = def;
54
+ return (_jsx(Config, { steps: steps, onFinished: onFinished, onAborted: onAborted, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, status: status, debug: debug }));
55
+ }
56
+ case ComponentName.Command: {
57
+ const { props: { command, service, onAborted }, status, } = def;
58
+ return (_jsx(Command, { command: command, service: service, onAborted: onAborted, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status }));
59
+ }
60
+ case ComponentName.Schedule: {
61
+ const { props: { message, tasks, onSelectionConfirmed }, status, } = def;
62
+ return (_jsx(Schedule, { message: message, tasks: tasks, onSelectionConfirmed: onSelectionConfirmed, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, status: status, debug: debug }));
63
+ }
64
+ case ComponentName.Refinement: {
65
+ const { props: { text, onAborted }, status, } = def;
66
+ return (_jsx(Refinement, { text: text, onAborted: onAborted, requestHandlers: requestHandlers, status: status }));
67
+ }
68
+ case ComponentName.Confirm: {
69
+ const { props: { message, onConfirmed, onCancelled }, status, } = def;
70
+ return (_jsx(Confirm, { message: message, onConfirmed: onConfirmed, onCancelled: onCancelled, requestHandlers: requestHandlers, status: status }));
71
+ }
72
+ case ComponentName.Introspect: {
73
+ const { props: { tasks, service, children }, status, } = def;
74
+ return (_jsx(Introspect, { tasks: tasks, service: service, children: children, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status, debug: debug }));
75
+ }
76
+ case ComponentName.Answer: {
77
+ const { props: { question, service }, status, } = def;
78
+ return (_jsx(Answer, { question: question, service: service, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status }));
79
+ }
80
+ case ComponentName.Validate: {
81
+ const { props: { missingConfig, userRequest, service, onError, onValidationComplete, onAborted, }, status, } = def;
82
+ return (_jsx(Validate, { missingConfig: missingConfig, userRequest: userRequest, service: service, onError: onError, onValidationComplete: onValidationComplete, onAborted: onAborted, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status }));
83
+ }
84
+ case ComponentName.Execute: {
85
+ const { props: { tasks, service }, status, } = def;
86
+ return (_jsx(Execute, { tasks: tasks, service: service, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status }));
87
+ }
88
+ default:
89
+ throw new Error(`Unknown managed component: ${def.name}`);
90
+ }
91
+ });
92
+ /**
93
+ * Render a managed component as View only (no Controller logic)
94
+ */
95
+ export const ViewComponent = memo(function ViewComponent({ def, }) {
96
+ switch (def.name) {
97
+ case ComponentName.Confirm: {
98
+ const { props: { message }, state, status, } = def;
99
+ return _jsx(ConfirmView, { message: message, state: state, status: status });
100
+ }
101
+ case ComponentName.Config: {
102
+ const { props: { steps }, state, status, } = def;
103
+ return _jsx(ConfigView, { steps: steps, state: state, status: status });
104
+ }
105
+ case ComponentName.Schedule: {
106
+ const { props: { message, tasks }, state, status, } = def;
107
+ return (_jsx(ScheduleView, { message: message, tasks: tasks, state: state, status: status }));
108
+ }
109
+ case ComponentName.Execute: {
110
+ const { props: { tasks }, state, status, } = def;
111
+ return _jsx(ExecuteView, { tasks: tasks, state: state, status: status });
112
+ }
113
+ case ComponentName.Answer: {
114
+ const { props: { question }, state, status, } = def;
115
+ return _jsx(AnswerView, { question: question, state: state, status: status });
116
+ }
117
+ case ComponentName.Command: {
118
+ const { props: { command }, state, status, } = def;
119
+ return _jsx(CommandView, { command: command, state: state, status: status });
120
+ }
121
+ case ComponentName.Introspect: {
122
+ const { props: { children }, state, status, } = def;
123
+ return (_jsx(IntrospectView, { state: state, status: status, children: children }));
124
+ }
125
+ case ComponentName.Validate: {
126
+ const { state, status } = def;
127
+ return _jsx(ValidateView, { state: state, status: status });
128
+ }
129
+ case ComponentName.Refinement: {
130
+ const { props: { text }, status, } = def;
131
+ return _jsx(RefinementView, { text: text, status: status });
132
+ }
133
+ default:
134
+ throw new Error(`Unknown managed component: ${def.name}`);
135
+ }
136
+ });
137
+ /**
138
+ * Render a component in the timeline (Views only for managed, as-is for simple)
139
+ */
140
+ export const TimelineComponent = ({ def, }) => {
141
+ switch (def.name) {
142
+ // Simple components render as-is
20
143
  case ComponentName.Welcome:
21
- return _jsx(Welcome, { ...def.props, status: def.status });
22
- case ComponentName.Config:
23
- return (_jsx(Config, { ...def.props, state: def.state, status: def.status, debug: debug }));
24
- case ComponentName.Command:
25
- return _jsx(Command, { ...def.props, state: def.state, status: def.status });
26
- case ComponentName.Schedule:
27
- return (_jsx(Schedule, { ...def.props, state: def.state, status: def.status, debug: debug }));
28
144
  case ComponentName.Feedback:
29
- return _jsx(Feedback, { ...def.props, status: def.status });
30
145
  case ComponentName.Message:
31
- return _jsx(Message, { ...def.props, status: def.status });
32
146
  case ComponentName.Debug:
33
- return _jsx(Debug, { ...def.props, status: def.status });
34
- case ComponentName.Refinement:
35
- return (_jsx(Refinement, { ...def.props, state: def.state, status: def.status }));
36
- case ComponentName.Confirm:
37
- return _jsx(Confirm, { ...def.props, state: def.state, status: def.status });
38
- case ComponentName.Introspect:
39
- return (_jsx(Introspect, { ...def.props, state: def.state, status: def.status, debug: debug }));
40
147
  case ComponentName.Report:
41
- return _jsx(Report, { ...def.props, status: def.status });
42
- case ComponentName.Answer:
43
- return _jsx(Answer, { ...def.props, state: def.state, status: def.status });
44
- case ComponentName.Execute:
45
- return _jsx(Execute, { ...def.props, state: def.state, status: def.status });
148
+ return _jsx(SimpleComponent, { def: def });
149
+ // Managed components render as Views
150
+ case ComponentName.Config:
151
+ case ComponentName.Command:
152
+ case ComponentName.Confirm:
153
+ case ComponentName.Schedule:
154
+ case ComponentName.Refinement:
46
155
  case ComponentName.Validate:
47
- return (_jsx(Validate, { ...def.props, state: def.state, status: def.status, debug: debug }));
156
+ case ComponentName.Execute:
157
+ case ComponentName.Answer:
158
+ case ComponentName.Introspect:
159
+ return _jsx(ViewComponent, { def: def });
160
+ default:
161
+ throw new Error('Unknown component type');
48
162
  }
49
- });
163
+ };
package/dist/ui/Config.js CHANGED
@@ -6,7 +6,7 @@ import { ComponentStatus } from '../types/components.js';
6
6
  import { FeedbackType } from '../types/types.js';
7
7
  import { Colors } from '../services/colors.js';
8
8
  import { createFeedback } from '../services/components.js';
9
- import { DebugLevel } from '../services/configuration.js';
9
+ import { DebugLevel } from '../configuration/types.js';
10
10
  import { useInput } from '../services/keyboard.js';
11
11
  /**
12
12
  * Get postfix with debug brackets if debug is enabled
@@ -78,16 +78,52 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
78
78
  return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
79
79
  }) }));
80
80
  }
81
+ export const ConfigView = ({ steps, state, status, debug = DebugLevel.None, onInputChange, onInputSubmit, }) => {
82
+ const isActive = status === ComponentStatus.Active;
83
+ const { values, completedStep, selectedIndex } = state;
84
+ const renderStepInput = (stepConfig, isCurrentStep) => {
85
+ const configKey = stepConfig.path || stepConfig.key;
86
+ const displayValue = values[configKey];
87
+ switch (stepConfig.type) {
88
+ case StepType.Text:
89
+ if (isCurrentStep && onInputChange && onInputSubmit) {
90
+ return (_jsx(TextStep, { value: values[configKey] || '', placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: onInputChange, onSubmit: onInputSubmit }));
91
+ }
92
+ return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
93
+ case StepType.Selection: {
94
+ if (!isCurrentStep) {
95
+ const option = stepConfig.options.find((opt) => opt.value === displayValue);
96
+ return _jsx(Text, { dimColor: true, children: option?.label || '' });
97
+ }
98
+ return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
99
+ }
100
+ default: {
101
+ const _exhaustiveCheck = stepConfig;
102
+ throw new Error('Unsupported step type');
103
+ }
104
+ }
105
+ };
106
+ return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
107
+ const isCurrentStep = index === completedStep && isActive;
108
+ const isCompleted = index < completedStep;
109
+ const wasAborted = index === completedStep && !isActive;
110
+ const shouldShow = isCompleted || isCurrentStep || wasAborted;
111
+ if (!shouldShow) {
112
+ return null;
113
+ }
114
+ const postfix = getPostfix(stepConfig.path, debug);
115
+ return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), postfix && _jsx(Text, { color: Colors.Type.Config, children: postfix })] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.path || stepConfig.key));
116
+ }) }));
117
+ };
118
+ /**
119
+ * Config controller: Multi-step wizard logic
120
+ */
81
121
  export function Config(props) {
82
- const { steps, state, status, debug = DebugLevel.None, stateHandlers, lifecycleHandlers, onFinished, onAborted, } = props;
122
+ const { steps, status, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, onFinished, onAborted, } = props;
83
123
  const isActive = status === ComponentStatus.Active;
84
- const [step, setStep] = useState(!isActive ? (state?.completedStep ?? steps.length) : 0);
124
+ const [step, setStep] = useState(0);
85
125
  const [values, setValues] = useState(() => {
86
- // If not active and we have saved state values, use those
87
- if (!isActive && state?.values) {
88
- return state.values;
89
- }
90
- // Otherwise initialize from step defaults
126
+ // Initialize from step defaults
91
127
  const initial = {};
92
128
  steps.forEach((stepConfig) => {
93
129
  // Use full path if available, otherwise use key
@@ -112,26 +148,14 @@ export function Config(props) {
112
148
  });
113
149
  const [inputValue, setInputValue] = useState(() => {
114
150
  // Initialize with the current step's value if available
115
- if (isActive && step < steps.length) {
151
+ if (step < steps.length) {
116
152
  const stepConfig = steps[step];
117
153
  const configKey = stepConfig.path || stepConfig.key;
118
154
  return values[configKey] || '';
119
155
  }
120
156
  return '';
121
157
  });
122
- const [selectedIndex, setSelectedIndex] = useState(() => {
123
- // If not active, use saved state
124
- if (!isActive && state?.selectedIndex !== undefined) {
125
- return state.selectedIndex;
126
- }
127
- // Initialize selectedIndex based on current step's defaultIndex
128
- if (isActive &&
129
- step < steps.length &&
130
- steps[step].type === StepType.Selection) {
131
- return steps[step].defaultIndex;
132
- }
133
- return 0;
134
- });
158
+ const [selectedIndex, setSelectedIndex] = useState(0);
135
159
  const normalizeValue = (value) => {
136
160
  if (value === null || value === undefined) {
137
161
  return '';
@@ -167,20 +191,25 @@ export function Config(props) {
167
191
  throw new Error('Unsupported step type');
168
192
  }
169
193
  }
170
- if (currentValue) {
171
- setValues({ ...values, [configKey]: currentValue });
172
- }
173
- // Save state before aborting
174
- stateHandlers?.updateState({
175
- values,
194
+ const finalValues = currentValue
195
+ ? { ...values, [configKey]: currentValue }
196
+ : values;
197
+ // Expose final state
198
+ const finalState = {
199
+ values: finalValues,
176
200
  completedStep: step,
177
201
  selectedIndex,
178
- });
202
+ };
203
+ requestHandlers.onCompleted(finalState);
204
+ // Abort configuration
179
205
  if (onAborted) {
206
+ // Let Workflow handler complete and add feedback
180
207
  onAborted('configuration');
181
208
  }
182
- // Complete with abort feedback
183
- lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Aborted, 'Configuration cancelled.'));
209
+ else {
210
+ // Fallback: complete with abort feedback directly
211
+ lifecycleHandlers.completeActive(createFeedback(FeedbackType.Aborted, 'Configuration cancelled.'));
212
+ }
184
213
  return;
185
214
  }
186
215
  // Handle selection step navigation
@@ -192,7 +221,7 @@ export function Config(props) {
192
221
  handleSubmit(currentStepConfig.options[selectedIndex].value);
193
222
  }
194
223
  }
195
- });
224
+ }, { isActive });
196
225
  const handleSubmit = (value) => {
197
226
  const currentStepConfig = steps[step];
198
227
  let finalValue = '';
@@ -230,37 +259,29 @@ export function Config(props) {
230
259
  setInputValue('');
231
260
  if (step === steps.length - 1) {
232
261
  // Last step completed
233
- // IMPORTANT: Update state BEFORE calling onFinished
234
- // onFinished may call handlers.completeActive(), so state must be saved first
235
- const stateUpdate = {
262
+ // Expose final state
263
+ const finalState = {
236
264
  values: newValues,
237
265
  completedStep: steps.length,
238
266
  selectedIndex,
239
267
  };
240
- stateHandlers?.updateState(stateUpdate);
268
+ requestHandlers.onCompleted(finalState);
241
269
  // Call onFinished callback and handle result
242
270
  try {
243
271
  if (onFinished) {
244
272
  onFinished(newValues);
245
273
  }
246
274
  // Success - complete with success feedback
247
- lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Succeeded, 'Configuration saved successfully.'));
275
+ lifecycleHandlers.completeActive(createFeedback(FeedbackType.Succeeded, 'Configuration saved successfully.'));
248
276
  }
249
277
  catch (error) {
250
278
  // Failure - complete with error feedback
251
279
  const errorMessage = error instanceof Error ? error.message : 'Configuration failed';
252
- lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Failed, errorMessage));
280
+ lifecycleHandlers.completeActive(createFeedback(FeedbackType.Failed, errorMessage));
253
281
  }
254
282
  setStep(steps.length);
255
283
  }
256
284
  else {
257
- // Save state after each step
258
- const stateUpdate = {
259
- values: newValues,
260
- completedStep: step + 1,
261
- selectedIndex,
262
- };
263
- stateHandlers?.updateState(stateUpdate);
264
285
  const nextStep = step + 1;
265
286
  setStep(nextStep);
266
287
  // Reset selectedIndex for next step
@@ -270,39 +291,12 @@ export function Config(props) {
270
291
  }
271
292
  }
272
293
  };
273
- const renderStepInput = (stepConfig, isCurrentStep) => {
274
- const configKey = stepConfig.path || stepConfig.key;
275
- // Use state values when inactive, local values when active
276
- const displayValue = !isActive && state?.values ? state.values[configKey] : values[configKey];
277
- switch (stepConfig.type) {
278
- case StepType.Text:
279
- if (isCurrentStep) {
280
- return (_jsx(TextStep, { value: inputValue, placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: setInputValue, onSubmit: handleSubmit }));
281
- }
282
- return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
283
- case StepType.Selection: {
284
- if (!isCurrentStep) {
285
- // Find the option that matches the saved/current value
286
- const option = stepConfig.options.find((opt) => opt.value === displayValue);
287
- return _jsx(Text, { dimColor: true, children: option?.label || '' });
288
- }
289
- return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
290
- }
291
- default: {
292
- const _exhaustiveCheck = stepConfig;
293
- throw new Error('Unsupported step type');
294
- }
295
- }
294
+ // Build current state for View
295
+ // Controller always renders View, passing current state and callbacks
296
+ const state = {
297
+ values,
298
+ completedStep: step,
299
+ selectedIndex,
296
300
  };
297
- return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
298
- const isCurrentStep = index === step && isActive;
299
- const isCompleted = index < step;
300
- const wasAborted = index === step && !isActive;
301
- const shouldShow = isCompleted || isCurrentStep || wasAborted;
302
- if (!shouldShow) {
303
- return null;
304
- }
305
- const postfix = getPostfix(stepConfig.path, debug);
306
- return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), postfix && _jsx(Text, { color: Colors.Type.Config, children: postfix })] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.path || stepConfig.key));
307
- }) }));
301
+ return (_jsx(ConfigView, { steps: steps, state: state, status: status, debug: debug, onInputChange: setInputValue, onInputSubmit: handleSubmit }));
308
302
  }