prompt-language-shell 0.9.8 → 1.0.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.
- package/README.md +137 -40
- package/dist/components/Component.js +15 -3
- package/dist/components/controllers/Command.js +3 -19
- package/dist/components/controllers/Config.js +118 -81
- package/dist/components/controllers/Execute.js +14 -11
- package/dist/components/controllers/Introspect.js +2 -4
- package/dist/components/controllers/Learn.js +416 -0
- package/dist/components/controllers/Validate.js +3 -2
- package/dist/components/views/Execute.js +1 -1
- package/dist/components/views/Learn.js +147 -0
- package/dist/components/views/Output.js +84 -32
- package/dist/components/views/Subtask.js +12 -7
- package/dist/components/views/Task.js +4 -5
- package/dist/components/views/Upcoming.js +1 -1
- package/dist/execution/runner.js +45 -29
- package/dist/services/anthropic.js +5 -4
- package/dist/services/colors.js +5 -0
- package/dist/services/components.js +19 -1
- package/dist/services/logger.js +37 -9
- package/dist/services/monitor.js +19 -3
- package/dist/services/refinement.js +1 -7
- package/dist/services/router.js +235 -96
- package/dist/services/shell.js +18 -3
- package/dist/services/skills.js +102 -0
- package/dist/services/utils.js +11 -0
- package/dist/skills/introspect.md +16 -15
- package/dist/skills/schedule.md +42 -3
- package/dist/tools/introspect.tool.js +2 -2
- package/dist/tools/schedule.tool.js +1 -1
- package/dist/types/components.js +14 -0
- package/dist/types/schemas.js +1 -0
- package/dist/types/types.js +2 -0
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Your personal command-line concierge. Ask politely, and it gets things done.
|
|
4
4
|
|
|
5
|
-
> **Note:** This project is in early preview. Features and APIs will change.
|
|
6
|
-
> See [roadmap](#roadmap).
|
|
7
|
-
|
|
8
5
|
## Installation
|
|
9
6
|
|
|
10
7
|
```bash
|
|
@@ -37,24 +34,23 @@ Skills are custom workflows you can define to teach `pls` about your specific
|
|
|
37
34
|
projects and commands. Once defined, you can use them naturally:
|
|
38
35
|
|
|
39
36
|
```
|
|
40
|
-
$ pls
|
|
37
|
+
$ pls convert video.mp4
|
|
41
38
|
|
|
42
39
|
Here's my plan.
|
|
43
40
|
|
|
44
|
-
-
|
|
45
|
-
- Compile source code
|
|
41
|
+
- Compress video.mp4 with H.264 codec
|
|
46
42
|
```
|
|
47
43
|
|
|
48
44
|
You can provide multiple requests at once:
|
|
49
45
|
|
|
50
46
|
```
|
|
51
|
-
$ pls
|
|
47
|
+
$ pls backup photos, compress and upload
|
|
52
48
|
|
|
53
49
|
Here's what I'll do.
|
|
54
50
|
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
51
|
+
- Copy photos to backup folder
|
|
52
|
+
- Create zip archive
|
|
53
|
+
- Upload to cloud storage
|
|
58
54
|
```
|
|
59
55
|
|
|
60
56
|
When `pls` needs clarification, it will present options to choose from:
|
|
@@ -129,6 +125,46 @@ Skills let you teach `pls` about your project-specific workflows. Create
|
|
|
129
125
|
markdown files in `~/.pls/skills/` to define custom operations that
|
|
130
126
|
`pls` can understand and execute.
|
|
131
127
|
|
|
128
|
+
### Creating Skills
|
|
129
|
+
|
|
130
|
+
The easiest way to create a new skill is with the guided walkthrough:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
$ pls learn
|
|
134
|
+
|
|
135
|
+
Creating a new skill...
|
|
136
|
+
|
|
137
|
+
Skill name (e.g., "Deploy Project"):
|
|
138
|
+
> Build Frontend
|
|
139
|
+
|
|
140
|
+
Description (min 20 characters):
|
|
141
|
+
> Build the frontend application using npm
|
|
142
|
+
|
|
143
|
+
Step 1 - What does this step do?
|
|
144
|
+
> Install dependencies
|
|
145
|
+
|
|
146
|
+
How should this step be executed?
|
|
147
|
+
> shell command
|
|
148
|
+
|
|
149
|
+
Enter the shell command:
|
|
150
|
+
> npm install
|
|
151
|
+
|
|
152
|
+
Add another step?
|
|
153
|
+
> yes
|
|
154
|
+
|
|
155
|
+
...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The walkthrough guides you through defining:
|
|
159
|
+
- **Name**: A unique name for the skill
|
|
160
|
+
- **Description**: What the skill does (min 20 characters)
|
|
161
|
+
- **Aliases**: Example commands that invoke the skill (optional)
|
|
162
|
+
- **Config**: Configuration properties needed (optional)
|
|
163
|
+
- **Steps**: Each step with either a shell command or reference to another skill
|
|
164
|
+
|
|
165
|
+
Skills are saved to `~/.pls/skills/` as markdown files. File names use
|
|
166
|
+
kebab-case (e.g., "Build Frontend" becomes `build-frontend.md`).
|
|
167
|
+
|
|
132
168
|
For complete documentation, see [docs/SKILLS.md](./docs/SKILLS.md).
|
|
133
169
|
|
|
134
170
|
### Structure
|
|
@@ -138,60 +174,121 @@ Each skill file uses a simple markdown format:
|
|
|
138
174
|
- **Name**: What you call this workflow (e.g., "Build Project")
|
|
139
175
|
- **Description**: What it does and any variants or options
|
|
140
176
|
- **Steps**: What needs to happen, in order
|
|
141
|
-
- **Execution
|
|
177
|
+
- **Execution**: The actual shell commands to run
|
|
142
178
|
|
|
143
179
|
### Example
|
|
144
180
|
|
|
145
|
-
Here's a skill
|
|
181
|
+
Here's a skill for building a product from source:
|
|
146
182
|
|
|
147
183
|
```markdown
|
|
148
184
|
### Name
|
|
149
|
-
Build
|
|
185
|
+
Build Product
|
|
150
186
|
|
|
151
187
|
### Description
|
|
152
|
-
Build a
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
-
|
|
188
|
+
Build a product from source. Handles the full compilation pipeline.
|
|
189
|
+
|
|
190
|
+
The company maintains two product lines:
|
|
191
|
+
- Stable: the flagship product
|
|
192
|
+
- Beta: the experimental product
|
|
193
|
+
|
|
194
|
+
If the user says "just compile" or "recompile", dependency installation and
|
|
195
|
+
tests MUST be skipped. Tests MUST also be skipped if the user says "without
|
|
196
|
+
tests". Deployment MUST only run if the user explicitly asks. Compile and
|
|
197
|
+
package steps are MANDATORY.
|
|
156
198
|
|
|
157
199
|
### Steps
|
|
158
200
|
- Navigate to the project directory
|
|
159
|
-
- Install dependencies
|
|
160
|
-
- Run the
|
|
161
|
-
-
|
|
201
|
+
- Install build dependencies
|
|
202
|
+
- Run the test suite
|
|
203
|
+
- Compile source code
|
|
204
|
+
- Package build artifacts
|
|
205
|
+
- Deploy to server
|
|
162
206
|
|
|
163
207
|
### Execution
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
208
|
+
- [ Navigate To Project ]
|
|
209
|
+
- ./configure && make deps
|
|
210
|
+
- make test
|
|
211
|
+
- make build
|
|
212
|
+
- make package
|
|
213
|
+
- ./scripts/deploy.sh
|
|
168
214
|
```
|
|
169
215
|
|
|
170
|
-
|
|
216
|
+
The `[ Navigate To Project ]` reference invokes another skill by name. When `pls`
|
|
217
|
+
plans this workflow, it expands the reference inline, inserting that skill's
|
|
218
|
+
execution steps at this position. This lets you compose complex workflows from
|
|
219
|
+
simpler, reusable skills. Here's what that skill might look like:
|
|
220
|
+
|
|
221
|
+
```markdown
|
|
222
|
+
### Name
|
|
223
|
+
Navigate To Project
|
|
224
|
+
|
|
225
|
+
### Description
|
|
226
|
+
The company maintains two product lines:
|
|
227
|
+
- Stable: the flagship product
|
|
228
|
+
- Beta: the experimental product
|
|
229
|
+
|
|
230
|
+
### Aliases
|
|
231
|
+
- go to project
|
|
232
|
+
- navigate to repo
|
|
233
|
+
|
|
234
|
+
### Config
|
|
235
|
+
project:
|
|
236
|
+
stable:
|
|
237
|
+
path: string
|
|
238
|
+
beta:
|
|
239
|
+
path: string
|
|
240
|
+
|
|
241
|
+
### Steps
|
|
242
|
+
- Navigate to project directory
|
|
243
|
+
|
|
244
|
+
### Execution
|
|
245
|
+
- cd {project.PRODUCT.path}
|
|
171
246
|
```
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
247
|
+
|
|
248
|
+
The `{project.PRODUCT.path}` placeholder uses config values from `~/.plsrc`. The
|
|
249
|
+
PRODUCT is matched from user intent (e.g., "build stable" resolves to
|
|
250
|
+
`project.stable.path`).
|
|
251
|
+
|
|
252
|
+
The Description tells `pls` when to skip optional steps. This lets you say:
|
|
253
|
+
|
|
175
254
|
```
|
|
176
|
-
|
|
177
|
-
Instead of remembering the exact commands and paths for each environment, just
|
|
178
|
-
tell `pls` what you want in plain English. The Execution section ensures the right commands run every time.
|
|
255
|
+
$ pls build stable
|
|
179
256
|
|
|
180
|
-
|
|
257
|
+
- Navigate to the Stable directory
|
|
258
|
+
- Install build dependencies
|
|
259
|
+
- Run the test suite
|
|
260
|
+
- Compile source code
|
|
261
|
+
- Package build artifacts
|
|
262
|
+
```
|
|
181
263
|
|
|
182
|
-
|
|
183
|
-
|
|
264
|
+
Here "stable" matches the PRODUCT, so `pls` looks up `project.stable.path` in your
|
|
265
|
+
config. All steps run except deploy (not requested). When iterating quickly:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
$ pls just recompile experimental
|
|
184
269
|
|
|
270
|
+
- Navigate to the Beta directory
|
|
271
|
+
- Compile source code
|
|
272
|
+
- Package build artifacts
|
|
185
273
|
```
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
274
|
+
|
|
275
|
+
Now "experimental" resolves to `project.beta.path`. And when you're ready to ship:
|
|
276
|
+
|
|
189
277
|
```
|
|
278
|
+
$ pls build and deploy main
|
|
190
279
|
|
|
191
|
-
|
|
280
|
+
- Navigate to the Stable directory
|
|
281
|
+
- Install build dependencies
|
|
282
|
+
- Run the test suite
|
|
283
|
+
- Compile source code
|
|
284
|
+
- Package build artifacts
|
|
285
|
+
- Deploy to server
|
|
286
|
+
```
|
|
192
287
|
|
|
193
|
-
|
|
194
|
-
|
|
288
|
+
The same skill handles all cases based on your intent, something an alias or
|
|
289
|
+
script can't do. Skills are fully dynamic: you can add new variants, change step
|
|
290
|
+
conditions, or introduce new options anytime by editing the markdown file - no
|
|
291
|
+
code changes required.
|
|
195
292
|
|
|
196
293
|
## Development
|
|
197
294
|
|
|
@@ -10,6 +10,7 @@ import { Debug } from './views/Debug.js';
|
|
|
10
10
|
import { Execute, ExecuteView, mapStateToViewProps, } from './controllers/Execute.js';
|
|
11
11
|
import { Feedback } from './views/Feedback.js';
|
|
12
12
|
import { Introspect, IntrospectView } from './controllers/Introspect.js';
|
|
13
|
+
import { Learn, LearnView } from './controllers/Learn.js';
|
|
13
14
|
import { Message } from './views/Message.js';
|
|
14
15
|
import { Refinement, RefinementView } from './controllers/Refinement.js';
|
|
15
16
|
import { Report } from './views/Report.js';
|
|
@@ -51,8 +52,8 @@ export const SimpleComponent = memo(function SimpleComponent({ def, }) {
|
|
|
51
52
|
export const ControllerComponent = memo(function ControllerComponent({ def, debug, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
|
|
52
53
|
switch (def.name) {
|
|
53
54
|
case ComponentName.Config: {
|
|
54
|
-
const { props: { steps, onFinished, onAborted }, status, } = def;
|
|
55
|
-
return (_jsx(Config, { steps: steps, onFinished: onFinished, onAborted: onAborted, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, status: status, debug: debug }));
|
|
55
|
+
const { props: { steps, query, service, onFinished, onAborted }, status, } = def;
|
|
56
|
+
return (_jsx(Config, { steps: steps, query: query, service: service, onFinished: onFinished, onAborted: onAborted, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status, debug: debug }));
|
|
56
57
|
}
|
|
57
58
|
case ComponentName.Command: {
|
|
58
59
|
const { props: { command, service, onAborted }, status, } = def;
|
|
@@ -86,6 +87,10 @@ export const ControllerComponent = memo(function ControllerComponent({ def, debu
|
|
|
86
87
|
const { props: { tasks, service, upcoming, label }, status, } = def;
|
|
87
88
|
return (_jsx(Execute, { tasks: tasks, service: service, upcoming: upcoming, label: label, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status }));
|
|
88
89
|
}
|
|
90
|
+
case ComponentName.Learn: {
|
|
91
|
+
const { props: { suggestedName, onFinished, onAborted }, status, } = def;
|
|
92
|
+
return (_jsx(Learn, { suggestedName: suggestedName, onFinished: onFinished, onAborted: onAborted, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers, status: status }));
|
|
93
|
+
}
|
|
89
94
|
default:
|
|
90
95
|
throw new Error(`Unknown managed component: ${def.name}`);
|
|
91
96
|
}
|
|
@@ -100,7 +105,9 @@ export const ViewComponent = memo(function ViewComponent({ def, }) {
|
|
|
100
105
|
return (_jsx(ConfirmView, { status: status, message: message, selectedIndex: state.selectedIndex }));
|
|
101
106
|
}
|
|
102
107
|
case ComponentName.Config: {
|
|
103
|
-
const { props: { steps }, state, status, } = def;
|
|
108
|
+
const { props: { steps: propSteps = [] }, state, status, } = def;
|
|
109
|
+
// Use resolved steps from state if available (for query-based configs)
|
|
110
|
+
const steps = state.steps ?? propSteps;
|
|
104
111
|
return _jsx(ConfigView, { steps: steps, state: state, status: status });
|
|
105
112
|
}
|
|
106
113
|
case ComponentName.Schedule: {
|
|
@@ -135,6 +142,10 @@ export const ViewComponent = memo(function ViewComponent({ def, }) {
|
|
|
135
142
|
const { props: { text }, status, } = def;
|
|
136
143
|
return _jsx(RefinementView, { text: text, status: status });
|
|
137
144
|
}
|
|
145
|
+
case ComponentName.Learn: {
|
|
146
|
+
const { state, status } = def;
|
|
147
|
+
return (_jsx(LearnView, { state: state, status: status, onInputChange: () => { }, onInputSubmit: () => { } }));
|
|
148
|
+
}
|
|
138
149
|
default:
|
|
139
150
|
throw new Error(`Unknown managed component: ${def.name}`);
|
|
140
151
|
}
|
|
@@ -161,6 +172,7 @@ export const TimelineComponent = ({ def, }) => {
|
|
|
161
172
|
case ComponentName.Execute:
|
|
162
173
|
case ComponentName.Answer:
|
|
163
174
|
case ComponentName.Introspect:
|
|
175
|
+
case ComponentName.Learn:
|
|
164
176
|
return _jsx(ViewComponent, { def: def });
|
|
165
177
|
default:
|
|
166
178
|
throw new Error('Unknown component type');
|
|
@@ -34,28 +34,12 @@ export function Command({ command, status, service, requestHandlers, lifecycleHa
|
|
|
34
34
|
async function process(svc) {
|
|
35
35
|
const startTime = Date.now();
|
|
36
36
|
try {
|
|
37
|
-
|
|
38
|
-
// Save schedule debug output before potentially delegating
|
|
39
|
-
const scheduleDebug = result.debug || [];
|
|
40
|
-
// If all tasks are configure type, delegate to CONFIGURE tool
|
|
41
|
-
const allConfig = result.tasks.length > 0 &&
|
|
42
|
-
result.tasks.every((task) => task.type === TaskType.Config);
|
|
43
|
-
if (allConfig) {
|
|
44
|
-
// Extract query from first config task params, default to 'app'
|
|
45
|
-
const query = result.tasks[0].params?.query || 'app';
|
|
46
|
-
// Call CONFIGURE tool to get specific config keys
|
|
47
|
-
result = await svc.processWithTool(query, 'configure');
|
|
48
|
-
}
|
|
37
|
+
const result = await svc.processWithTool(command, 'schedule');
|
|
49
38
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
50
39
|
if (mounted) {
|
|
51
40
|
// Add debug components to timeline if present
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const debugComponents = allConfig
|
|
55
|
-
? [...scheduleDebug, ...(result.debug || [])]
|
|
56
|
-
: scheduleDebug;
|
|
57
|
-
if (debugComponents.length > 0) {
|
|
58
|
-
workflowHandlers.addToTimeline(...debugComponents);
|
|
41
|
+
if (result.debug?.length) {
|
|
42
|
+
workflowHandlers.addToTimeline(...result.debug);
|
|
59
43
|
}
|
|
60
44
|
// Update local state
|
|
61
45
|
setMessage(result.message);
|
|
@@ -1,24 +1,57 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { ComponentStatus, } from '../../types/components.js';
|
|
5
|
+
import { FeedbackType, TaskType } from '../../types/types.js';
|
|
5
6
|
import { createFeedback } from '../../services/components.js';
|
|
7
|
+
import { createConfigStepsFromSchema } from '../../configuration/steps.js';
|
|
8
|
+
import { saveConfigLabels } from '../../configuration/labels.js';
|
|
6
9
|
import { DebugLevel } from '../../configuration/types.js';
|
|
7
10
|
import { useInput } from '../../services/keyboard.js';
|
|
8
11
|
import { ConfigView, StepType } from '../views/Config.js';
|
|
12
|
+
import { Spinner } from '../views/Spinner.js';
|
|
9
13
|
export { ConfigView, StepType, } from '../views/Config.js';
|
|
14
|
+
/**
|
|
15
|
+
* Resolve query to config steps via CONFIGURE tool
|
|
16
|
+
*/
|
|
17
|
+
async function resolveQueryToSteps(query, service) {
|
|
18
|
+
const result = await service.processWithTool(query, 'configure');
|
|
19
|
+
const configTasks = result.tasks.filter((task) => task.type === TaskType.Config && task.params?.key);
|
|
20
|
+
if (configTasks.length === 0) {
|
|
21
|
+
throw new Error('No configuration settings matched your query.');
|
|
22
|
+
}
|
|
23
|
+
const keys = configTasks.map((task) => task.params?.key);
|
|
24
|
+
const labels = {};
|
|
25
|
+
for (const task of configTasks) {
|
|
26
|
+
const key = task.params?.key;
|
|
27
|
+
if (key && task.action) {
|
|
28
|
+
labels[key] = task.action;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (Object.keys(labels).length > 0) {
|
|
32
|
+
saveConfigLabels(labels);
|
|
33
|
+
}
|
|
34
|
+
const steps = createConfigStepsFromSchema(keys);
|
|
35
|
+
return {
|
|
36
|
+
steps: steps.map((step, i) => ({
|
|
37
|
+
...step,
|
|
38
|
+
description: labels[keys[i]] || step.description,
|
|
39
|
+
})),
|
|
40
|
+
debug: result.debug || [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
10
43
|
/**
|
|
11
44
|
* Config controller: Multi-step wizard logic
|
|
12
45
|
*/
|
|
13
46
|
export function Config(props) {
|
|
14
|
-
const { steps, status, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, onFinished, onAborted, } = props;
|
|
47
|
+
const { steps: initialSteps, query, service, status, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, workflowHandlers, onFinished, onAborted, } = props;
|
|
15
48
|
const isActive = status === ComponentStatus.Active;
|
|
49
|
+
const [steps, setSteps] = useState(initialSteps || []);
|
|
50
|
+
const [resolving, setResolving] = useState(!initialSteps?.length && !!query);
|
|
16
51
|
const [step, setStep] = useState(0);
|
|
17
52
|
const [values, setValues] = useState(() => {
|
|
18
|
-
// Initialize from step defaults
|
|
19
53
|
const initial = {};
|
|
20
|
-
|
|
21
|
-
// Use full path if available, otherwise use key
|
|
54
|
+
(initialSteps || []).forEach((stepConfig) => {
|
|
22
55
|
const configKey = stepConfig.path || stepConfig.key;
|
|
23
56
|
switch (stepConfig.type) {
|
|
24
57
|
case StepType.Text:
|
|
@@ -30,45 +63,83 @@ export function Config(props) {
|
|
|
30
63
|
initial[configKey] =
|
|
31
64
|
stepConfig.options[stepConfig.defaultIndex].value;
|
|
32
65
|
break;
|
|
33
|
-
default: {
|
|
34
|
-
const _exhaustiveCheck = stepConfig;
|
|
35
|
-
throw new Error('Unsupported step type');
|
|
36
|
-
}
|
|
37
66
|
}
|
|
38
67
|
});
|
|
39
68
|
return initial;
|
|
40
69
|
});
|
|
41
|
-
const [inputValue, setInputValue] = useState(
|
|
42
|
-
|
|
43
|
-
if (
|
|
70
|
+
const [inputValue, setInputValue] = useState('');
|
|
71
|
+
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
72
|
+
if (!initialSteps?.length)
|
|
73
|
+
return 0;
|
|
74
|
+
const first = initialSteps[0];
|
|
75
|
+
return first.type === StepType.Selection ? first.defaultIndex : 0;
|
|
76
|
+
});
|
|
77
|
+
// Resolve query to steps
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!isActive || !query || !service || initialSteps?.length)
|
|
80
|
+
return;
|
|
81
|
+
resolveQueryToSteps(query, service)
|
|
82
|
+
.then((result) => {
|
|
83
|
+
// Add debug components to timeline if present
|
|
84
|
+
if (result.debug.length) {
|
|
85
|
+
workflowHandlers.addToTimeline(...result.debug);
|
|
86
|
+
}
|
|
87
|
+
setSteps(result.steps);
|
|
88
|
+
setResolving(false);
|
|
89
|
+
// Initialize values for resolved steps
|
|
90
|
+
const initial = {};
|
|
91
|
+
result.steps.forEach((stepConfig) => {
|
|
92
|
+
const configKey = stepConfig.path || stepConfig.key;
|
|
93
|
+
switch (stepConfig.type) {
|
|
94
|
+
case StepType.Text:
|
|
95
|
+
if (stepConfig.value !== null) {
|
|
96
|
+
initial[configKey] = stepConfig.value;
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case StepType.Selection:
|
|
100
|
+
initial[configKey] =
|
|
101
|
+
stepConfig.options[stepConfig.defaultIndex].value;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
setValues(initial);
|
|
106
|
+
})
|
|
107
|
+
.catch((err) => {
|
|
108
|
+
setResolving(false);
|
|
109
|
+
lifecycleHandlers.completeActive(createFeedback({
|
|
110
|
+
type: FeedbackType.Failed,
|
|
111
|
+
message: err instanceof Error ? err.message : 'Failed to resolve',
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
}, [
|
|
115
|
+
isActive,
|
|
116
|
+
query,
|
|
117
|
+
service,
|
|
118
|
+
initialSteps,
|
|
119
|
+
lifecycleHandlers,
|
|
120
|
+
workflowHandlers,
|
|
121
|
+
]);
|
|
122
|
+
// Update inputValue and selectedIndex when step changes
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (isActive && step < steps.length) {
|
|
44
125
|
const stepConfig = steps[step];
|
|
45
126
|
const configKey = stepConfig.path || stepConfig.key;
|
|
46
|
-
|
|
127
|
+
setInputValue(values[configKey] || '');
|
|
128
|
+
if (stepConfig.type === StepType.Selection) {
|
|
129
|
+
setSelectedIndex(stepConfig.defaultIndex);
|
|
130
|
+
}
|
|
47
131
|
}
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
132
|
+
}, [step, isActive, steps, values]);
|
|
51
133
|
const normalizeValue = (value) => {
|
|
52
|
-
if (value === null || value === undefined)
|
|
134
|
+
if (value === null || value === undefined)
|
|
53
135
|
return '';
|
|
54
|
-
}
|
|
55
136
|
return value.replace(/\n/g, '').trim();
|
|
56
137
|
};
|
|
57
|
-
// Update inputValue when step changes
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (isActive && step < steps.length) {
|
|
60
|
-
const stepConfig = steps[step];
|
|
61
|
-
const configKey = stepConfig.path || stepConfig.key;
|
|
62
|
-
const value = values[configKey] || '';
|
|
63
|
-
setInputValue(value);
|
|
64
|
-
}
|
|
65
|
-
}, [step, isActive, steps]);
|
|
66
138
|
useInput((_, key) => {
|
|
67
139
|
if (!isActive || step >= steps.length)
|
|
68
140
|
return;
|
|
69
141
|
const currentStepConfig = steps[step];
|
|
70
142
|
if (key.escape) {
|
|
71
|
-
// Save current value before aborting
|
|
72
143
|
const configKey = currentStepConfig.path || currentStepConfig.key;
|
|
73
144
|
let currentValue = '';
|
|
74
145
|
switch (currentStepConfig.type) {
|
|
@@ -78,28 +149,20 @@ export function Config(props) {
|
|
|
78
149
|
case StepType.Selection:
|
|
79
150
|
currentValue = values[configKey] || '';
|
|
80
151
|
break;
|
|
81
|
-
default: {
|
|
82
|
-
const _exhaustiveCheck = currentStepConfig;
|
|
83
|
-
throw new Error('Unsupported step type');
|
|
84
|
-
}
|
|
85
152
|
}
|
|
86
153
|
const finalValues = currentValue
|
|
87
154
|
? { ...values, [configKey]: currentValue }
|
|
88
155
|
: values;
|
|
89
|
-
|
|
90
|
-
const finalState = {
|
|
156
|
+
requestHandlers.onCompleted({
|
|
91
157
|
values: finalValues,
|
|
92
158
|
completedStep: step,
|
|
93
159
|
selectedIndex,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Abort configuration
|
|
160
|
+
steps,
|
|
161
|
+
});
|
|
97
162
|
if (onAborted) {
|
|
98
|
-
// Let Workflow handler complete and add feedback
|
|
99
163
|
onAborted('configuration');
|
|
100
164
|
}
|
|
101
165
|
else {
|
|
102
|
-
// Fallback: complete with abort feedback directly
|
|
103
166
|
lifecycleHandlers.completeActive(createFeedback({
|
|
104
167
|
type: FeedbackType.Aborted,
|
|
105
168
|
message: 'Configuration cancelled.',
|
|
@@ -107,7 +170,6 @@ export function Config(props) {
|
|
|
107
170
|
}
|
|
108
171
|
return;
|
|
109
172
|
}
|
|
110
|
-
// Handle selection step navigation
|
|
111
173
|
if (currentStepConfig.type === StepType.Selection) {
|
|
112
174
|
if (key.tab) {
|
|
113
175
|
setSelectedIndex((prev) => (prev + 1) % currentStepConfig.options.length);
|
|
@@ -122,13 +184,10 @@ export function Config(props) {
|
|
|
122
184
|
let finalValue = '';
|
|
123
185
|
switch (currentStepConfig.type) {
|
|
124
186
|
case StepType.Selection:
|
|
125
|
-
// For selection, value is already validated by options
|
|
126
187
|
finalValue = value;
|
|
127
188
|
break;
|
|
128
189
|
case StepType.Text: {
|
|
129
|
-
// For text input
|
|
130
190
|
const normalizedInput = normalizeValue(value);
|
|
131
|
-
// Try user input first, then fall back to default
|
|
132
191
|
if (normalizedInput && currentStepConfig.validate(normalizedInput)) {
|
|
133
192
|
finalValue = normalizedInput;
|
|
134
193
|
}
|
|
@@ -138,63 +197,41 @@ export function Config(props) {
|
|
|
138
197
|
}
|
|
139
198
|
break;
|
|
140
199
|
}
|
|
141
|
-
default: {
|
|
142
|
-
const _exhaustiveCheck = currentStepConfig;
|
|
143
|
-
throw new Error('Unsupported step type');
|
|
144
|
-
}
|
|
145
200
|
}
|
|
146
|
-
|
|
147
|
-
if (!finalValue) {
|
|
201
|
+
if (!finalValue)
|
|
148
202
|
return;
|
|
149
|
-
}
|
|
150
|
-
// Use full path if available, otherwise use key
|
|
151
203
|
const configKey = currentStepConfig.path || currentStepConfig.key;
|
|
152
204
|
const newValues = { ...values, [configKey]: finalValue };
|
|
153
205
|
setValues(newValues);
|
|
154
206
|
setInputValue('');
|
|
155
207
|
if (step === steps.length - 1) {
|
|
156
|
-
|
|
157
|
-
// Expose final state
|
|
158
|
-
const finalState = {
|
|
208
|
+
requestHandlers.onCompleted({
|
|
159
209
|
values: newValues,
|
|
160
210
|
completedStep: steps.length,
|
|
161
211
|
selectedIndex,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Call onFinished callback and handle result
|
|
212
|
+
steps,
|
|
213
|
+
});
|
|
165
214
|
try {
|
|
166
|
-
|
|
167
|
-
onFinished(newValues);
|
|
168
|
-
}
|
|
169
|
-
// Success - complete with success feedback
|
|
215
|
+
onFinished?.(newValues);
|
|
170
216
|
lifecycleHandlers.completeActive(createFeedback({
|
|
171
217
|
type: FeedbackType.Succeeded,
|
|
172
218
|
message: 'Configuration saved successfully.',
|
|
173
219
|
}));
|
|
174
220
|
}
|
|
175
221
|
catch (error) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
222
|
+
lifecycleHandlers.completeActive(createFeedback({
|
|
223
|
+
type: FeedbackType.Failed,
|
|
224
|
+
message: error instanceof Error ? error.message : 'Configuration failed',
|
|
225
|
+
}));
|
|
179
226
|
}
|
|
180
227
|
setStep(steps.length);
|
|
181
228
|
}
|
|
182
229
|
else {
|
|
183
|
-
|
|
184
|
-
setStep(nextStep);
|
|
185
|
-
// Reset selectedIndex for next step
|
|
186
|
-
if (nextStep < steps.length &&
|
|
187
|
-
steps[nextStep].type === StepType.Selection) {
|
|
188
|
-
setSelectedIndex(steps[nextStep].defaultIndex);
|
|
189
|
-
}
|
|
230
|
+
setStep(step + 1);
|
|
190
231
|
}
|
|
191
232
|
};
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
completedStep: step,
|
|
197
|
-
selectedIndex,
|
|
198
|
-
};
|
|
199
|
-
return (_jsx(ConfigView, { steps: steps, state: state, status: status, debug: debug, onInputChange: setInputValue, onInputSubmit: handleSubmit }));
|
|
233
|
+
if (resolving) {
|
|
234
|
+
return (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { children: "Resolving configuration... " }), _jsx(Spinner, {})] }));
|
|
235
|
+
}
|
|
236
|
+
return (_jsx(ConfigView, { steps: steps, state: { values, completedStep: step, selectedIndex }, status: status, debug: debug, onInputChange: setInputValue, onInputSubmit: handleSubmit }));
|
|
200
237
|
}
|