prompt-language-shell 0.4.9 → 0.5.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 +114 -10
- package/dist/config/INTROSPECT.md +9 -5
- package/dist/config/PLAN.md +39 -1
- package/dist/config/VALIDATE.md +139 -0
- package/dist/handlers/config.js +23 -6
- package/dist/handlers/execute.js +10 -2
- package/dist/handlers/execution.js +68 -1
- package/dist/services/anthropic.js +3 -2
- package/dist/services/colors.js +2 -2
- package/dist/services/components.js +33 -1
- package/dist/services/config-loader.js +67 -0
- package/dist/services/execution-validator.js +110 -0
- package/dist/services/placeholder-resolver.js +120 -0
- package/dist/services/shell.js +1 -0
- package/dist/services/skill-expander.js +91 -0
- package/dist/services/skill-parser.js +169 -0
- package/dist/services/skills.js +26 -0
- package/dist/services/timing.js +38 -0
- package/dist/services/tool-registry.js +5 -0
- package/dist/tools/validate.tool.js +43 -0
- package/dist/types/skills.js +4 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Answer.js +3 -9
- package/dist/ui/Command.js +3 -6
- package/dist/ui/Component.js +7 -1
- package/dist/ui/Config.js +2 -2
- package/dist/ui/Execute.js +59 -14
- package/dist/ui/Introspect.js +6 -7
- package/dist/ui/Validate.js +120 -0
- package/dist/ui/Welcome.js +12 -5
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const validateTool = {
|
|
2
|
+
name: 'validate',
|
|
3
|
+
description: 'Validate skill requirements and generate natural language descriptions for missing configuration values. Given skill context and missing config paths, create CONFIG tasks with helpful, contextual descriptions.',
|
|
4
|
+
input_schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
message: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'Empty string or brief message (not shown to user, can be left empty)',
|
|
10
|
+
},
|
|
11
|
+
tasks: {
|
|
12
|
+
type: 'array',
|
|
13
|
+
description: 'Array of CONFIG tasks with natural language descriptions for missing config values',
|
|
14
|
+
items: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
action: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Natural language description explaining what the config value is for, followed by the config path in curly brackets {config.path}. Example: "Path to Alpha project repository (legacy implementation) {project.alpha.repo}"',
|
|
20
|
+
},
|
|
21
|
+
type: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Must be "config" for all tasks returned by this tool',
|
|
24
|
+
},
|
|
25
|
+
params: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
description: 'Must include key field with the config path',
|
|
28
|
+
properties: {
|
|
29
|
+
key: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'The config path (e.g., "opera.gx.repo")',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ['key'],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['action', 'type', 'params'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: ['message', 'tasks'],
|
|
42
|
+
},
|
|
43
|
+
};
|
package/dist/types/types.js
CHANGED
|
@@ -13,6 +13,7 @@ export var ComponentName;
|
|
|
13
13
|
ComponentName["Answer"] = "answer";
|
|
14
14
|
ComponentName["AnswerDisplay"] = "answerDisplay";
|
|
15
15
|
ComponentName["Execute"] = "execute";
|
|
16
|
+
ComponentName["Validate"] = "validate";
|
|
16
17
|
})(ComponentName || (ComponentName = {}));
|
|
17
18
|
export var TaskType;
|
|
18
19
|
(function (TaskType) {
|
package/dist/ui/Answer.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
5
5
|
import { useInput } from '../services/keyboard.js';
|
|
6
6
|
import { formatErrorMessage } from '../services/messages.js';
|
|
7
|
+
import { withMinimumTime } from '../services/timing.js';
|
|
7
8
|
import { Spinner } from './Spinner.js';
|
|
8
9
|
const MINIMUM_PROCESSING_TIME = 400;
|
|
9
10
|
export function Answer({ question, state, service, onError, onComplete, onAborted, }) {
|
|
@@ -30,13 +31,9 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
|
|
|
30
31
|
}
|
|
31
32
|
let mounted = true;
|
|
32
33
|
async function process(svc) {
|
|
33
|
-
const startTime = Date.now();
|
|
34
34
|
try {
|
|
35
|
-
// Call answer tool
|
|
36
|
-
const result = await svc.processWithTool(question, 'answer');
|
|
37
|
-
const elapsed = Date.now() - startTime;
|
|
38
|
-
const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
|
|
39
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
35
|
+
// Call answer tool with minimum processing time for UX polish
|
|
36
|
+
const result = await withMinimumTime(() => svc.processWithTool(question, 'answer'), MINIMUM_PROCESSING_TIME);
|
|
40
37
|
if (mounted) {
|
|
41
38
|
// Extract answer from result
|
|
42
39
|
const answer = result.answer || '';
|
|
@@ -45,9 +42,6 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
|
|
|
45
42
|
}
|
|
46
43
|
}
|
|
47
44
|
catch (err) {
|
|
48
|
-
const elapsed = Date.now() - startTime;
|
|
49
|
-
const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
|
|
50
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
51
45
|
if (mounted) {
|
|
52
46
|
const errorMessage = formatErrorMessage(err);
|
|
53
47
|
setIsLoading(false);
|
package/dist/ui/Command.js
CHANGED
|
@@ -5,6 +5,7 @@ import { TaskType } from '../types/types.js';
|
|
|
5
5
|
import { Colors } from '../services/colors.js';
|
|
6
6
|
import { useInput } from '../services/keyboard.js';
|
|
7
7
|
import { formatErrorMessage } from '../services/messages.js';
|
|
8
|
+
import { ensureMinimumTime } from '../services/timing.js';
|
|
8
9
|
import { Spinner } from './Spinner.js';
|
|
9
10
|
const MIN_PROCESSING_TIME = 1000; // purely for visual effect
|
|
10
11
|
export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
|
|
@@ -42,18 +43,14 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
42
43
|
// Call CONFIG tool to get specific config keys
|
|
43
44
|
result = await svc.processWithTool(query, 'config');
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
47
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
46
|
+
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
48
47
|
if (mounted) {
|
|
49
48
|
setIsLoading(false);
|
|
50
49
|
onComplete?.(result.message, result.tasks);
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
catch (err) {
|
|
54
|
-
|
|
55
|
-
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
56
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
53
|
+
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
57
54
|
if (mounted) {
|
|
58
55
|
const errorMessage = formatErrorMessage(err);
|
|
59
56
|
setIsLoading(false);
|
package/dist/ui/Component.js
CHANGED
|
@@ -13,6 +13,7 @@ import { Message } from './Message.js';
|
|
|
13
13
|
import { Plan } from './Plan.js';
|
|
14
14
|
import { Refinement } from './Refinement.js';
|
|
15
15
|
import { Report } from './Report.js';
|
|
16
|
+
import { Validate } from './Validate.js';
|
|
16
17
|
import { Welcome } from './Welcome.js';
|
|
17
18
|
export const Component = React.memo(function Component({ def, debug, }) {
|
|
18
19
|
switch (def.name) {
|
|
@@ -21,7 +22,7 @@ export const Component = React.memo(function Component({ def, debug, }) {
|
|
|
21
22
|
case ComponentName.Config: {
|
|
22
23
|
const props = def.props;
|
|
23
24
|
const state = def.state;
|
|
24
|
-
return _jsx(Config, { ...props, state: state });
|
|
25
|
+
return _jsx(Config, { ...props, state: state, debug: debug });
|
|
25
26
|
}
|
|
26
27
|
case ComponentName.Command: {
|
|
27
28
|
const props = def.props;
|
|
@@ -66,5 +67,10 @@ export const Component = React.memo(function Component({ def, debug, }) {
|
|
|
66
67
|
const state = def.state;
|
|
67
68
|
return _jsx(Execute, { ...props, state: state });
|
|
68
69
|
}
|
|
70
|
+
case ComponentName.Validate: {
|
|
71
|
+
const props = def.props;
|
|
72
|
+
const state = def.state;
|
|
73
|
+
return _jsx(Validate, { ...props, state: state });
|
|
74
|
+
}
|
|
69
75
|
}
|
|
70
76
|
});
|
package/dist/ui/Config.js
CHANGED
|
@@ -57,7 +57,7 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
|
|
|
57
57
|
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
|
|
58
58
|
}) }));
|
|
59
59
|
}
|
|
60
|
-
export function Config({ steps, state, onFinished, onAborted }) {
|
|
60
|
+
export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
61
61
|
const done = state?.done ?? false;
|
|
62
62
|
const [step, setStep] = React.useState(done ? steps.length : 0);
|
|
63
63
|
const [values, setValues] = React.useState(() => {
|
|
@@ -220,6 +220,6 @@ export function Config({ steps, state, onFinished, onAborted }) {
|
|
|
220
220
|
if (!shouldShow) {
|
|
221
221
|
return null;
|
|
222
222
|
}
|
|
223
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [
|
|
223
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), debug && stepConfig.path && (_jsxs(Text, { color: Colors.Type.Define, children: ['{', stepConfig.path, '}'] }))] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
|
|
224
224
|
}) }));
|
|
225
225
|
}
|
package/dist/ui/Execute.js
CHANGED
|
@@ -6,6 +6,9 @@ import { useInput } from '../services/keyboard.js';
|
|
|
6
6
|
import { formatErrorMessage } from '../services/messages.js';
|
|
7
7
|
import { formatDuration } from '../services/utils.js';
|
|
8
8
|
import { ExecutionStatus, executeCommands, } from '../services/shell.js';
|
|
9
|
+
import { replacePlaceholders } from '../services/placeholder-resolver.js';
|
|
10
|
+
import { loadUserConfig } from '../services/config-loader.js';
|
|
11
|
+
import { ensureMinimumTime } from '../services/timing.js';
|
|
9
12
|
import { Spinner } from './Spinner.js';
|
|
10
13
|
const MINIMUM_PROCESSING_TIME = 400;
|
|
11
14
|
const STATUS_ICONS = {
|
|
@@ -13,7 +16,22 @@ const STATUS_ICONS = {
|
|
|
13
16
|
[ExecutionStatus.Running]: '• ',
|
|
14
17
|
[ExecutionStatus.Success]: '✓ ',
|
|
15
18
|
[ExecutionStatus.Failed]: '✗ ',
|
|
19
|
+
[ExecutionStatus.Aborted]: '⊘ ',
|
|
16
20
|
};
|
|
21
|
+
function calculateTotalElapsed(commandStatuses) {
|
|
22
|
+
return commandStatuses.reduce((sum, cmd) => {
|
|
23
|
+
if (cmd.elapsed !== undefined) {
|
|
24
|
+
return sum + cmd.elapsed;
|
|
25
|
+
}
|
|
26
|
+
if (cmd.startTime) {
|
|
27
|
+
const elapsed = cmd.endTime
|
|
28
|
+
? cmd.endTime - cmd.startTime
|
|
29
|
+
: Date.now() - cmd.startTime;
|
|
30
|
+
return sum + elapsed;
|
|
31
|
+
}
|
|
32
|
+
return sum;
|
|
33
|
+
}, 0);
|
|
34
|
+
}
|
|
17
35
|
function getStatusColors(status) {
|
|
18
36
|
switch (status) {
|
|
19
37
|
case ExecutionStatus.Pending:
|
|
@@ -44,6 +62,13 @@ function getStatusColors(status) {
|
|
|
44
62
|
command: Colors.Status.Error,
|
|
45
63
|
symbol: Palette.Gray,
|
|
46
64
|
};
|
|
65
|
+
case ExecutionStatus.Aborted:
|
|
66
|
+
return {
|
|
67
|
+
icon: Palette.DarkOrange,
|
|
68
|
+
description: getTextColor(true),
|
|
69
|
+
command: Palette.DarkOrange,
|
|
70
|
+
symbol: Palette.Gray,
|
|
71
|
+
};
|
|
47
72
|
}
|
|
48
73
|
}
|
|
49
74
|
function CommandStatusDisplay({ item, elapsed }) {
|
|
@@ -75,7 +100,24 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
|
|
|
75
100
|
if (key.escape && (isLoading || isExecuting) && !done) {
|
|
76
101
|
setIsLoading(false);
|
|
77
102
|
setIsExecuting(false);
|
|
78
|
-
|
|
103
|
+
setRunningIndex(null);
|
|
104
|
+
// Mark any running command as aborted when cancelled
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
setCommandStatuses((prev) => prev.map((item) => {
|
|
107
|
+
if (item.status === ExecutionStatus.Running) {
|
|
108
|
+
const elapsed = item.startTime
|
|
109
|
+
? Math.floor((now - item.startTime) / 1000) * 1000
|
|
110
|
+
: undefined;
|
|
111
|
+
return {
|
|
112
|
+
...item,
|
|
113
|
+
status: ExecutionStatus.Aborted,
|
|
114
|
+
endTime: now,
|
|
115
|
+
elapsed,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return item;
|
|
119
|
+
}));
|
|
120
|
+
onAborted(calculateTotalElapsed(commandStatuses));
|
|
79
121
|
}
|
|
80
122
|
}, { isActive: (isLoading || isExecuting) && !done });
|
|
81
123
|
// Update elapsed time for running command
|
|
@@ -97,9 +139,7 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
|
|
|
97
139
|
useEffect(() => {
|
|
98
140
|
if (isExecuting || commandStatuses.length === 0 || !outputs.length)
|
|
99
141
|
return;
|
|
100
|
-
|
|
101
|
-
const totalElapsed = commandStatuses.reduce((sum, cmd) => sum + (cmd.elapsed ?? 0), 0);
|
|
102
|
-
onComplete?.(outputs, totalElapsed);
|
|
142
|
+
onComplete?.(outputs, calculateTotalElapsed(commandStatuses));
|
|
103
143
|
}, [isExecuting, commandStatuses, outputs, onComplete]);
|
|
104
144
|
useEffect(() => {
|
|
105
145
|
if (done) {
|
|
@@ -114,20 +154,22 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
|
|
|
114
154
|
async function process(svc) {
|
|
115
155
|
const startTime = Date.now();
|
|
116
156
|
try {
|
|
117
|
-
//
|
|
157
|
+
// Load user config for placeholder resolution
|
|
158
|
+
const userConfig = loadUserConfig();
|
|
159
|
+
// Format tasks for the execute tool and resolve placeholders
|
|
118
160
|
const taskDescriptions = tasks
|
|
119
161
|
.map((task) => {
|
|
162
|
+
// Resolve placeholders in task action
|
|
163
|
+
const resolvedAction = replacePlaceholders(task.action, userConfig);
|
|
120
164
|
const params = task.params
|
|
121
165
|
? ` (params: ${JSON.stringify(task.params)})`
|
|
122
166
|
: '';
|
|
123
|
-
return `- ${
|
|
167
|
+
return `- ${resolvedAction}${params}`;
|
|
124
168
|
})
|
|
125
169
|
.join('\n');
|
|
126
170
|
// Call execute tool to get commands
|
|
127
171
|
const result = await svc.processWithTool(taskDescriptions, 'execute');
|
|
128
|
-
|
|
129
|
-
const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
|
|
130
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
172
|
+
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
131
173
|
if (!mounted)
|
|
132
174
|
return;
|
|
133
175
|
if (!result.commands || result.commands.length === 0) {
|
|
@@ -136,9 +178,14 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
|
|
|
136
178
|
onComplete?.([], 0);
|
|
137
179
|
return;
|
|
138
180
|
}
|
|
181
|
+
// Resolve placeholders in command strings before execution
|
|
182
|
+
const resolvedCommands = result.commands.map((cmd) => ({
|
|
183
|
+
...cmd,
|
|
184
|
+
command: replacePlaceholders(cmd.command, userConfig),
|
|
185
|
+
}));
|
|
139
186
|
// Set message and initialize command statuses
|
|
140
187
|
setMessage(result.message);
|
|
141
|
-
setCommandStatuses(
|
|
188
|
+
setCommandStatuses(resolvedCommands.map((cmd, index) => ({
|
|
142
189
|
command: cmd,
|
|
143
190
|
status: ExecutionStatus.Pending,
|
|
144
191
|
label: tasks[index]?.action,
|
|
@@ -146,7 +193,7 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
|
|
|
146
193
|
setIsLoading(false);
|
|
147
194
|
setIsExecuting(true);
|
|
148
195
|
// Execute commands sequentially
|
|
149
|
-
const outputs = await executeCommands(
|
|
196
|
+
const outputs = await executeCommands(resolvedCommands, (progress) => {
|
|
150
197
|
if (!mounted)
|
|
151
198
|
return;
|
|
152
199
|
const now = Date.now();
|
|
@@ -186,9 +233,7 @@ export function Execute({ tasks, state, service, onError, onComplete, onAborted,
|
|
|
186
233
|
}
|
|
187
234
|
}
|
|
188
235
|
catch (err) {
|
|
189
|
-
|
|
190
|
-
const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
|
|
191
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
236
|
+
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
192
237
|
if (mounted) {
|
|
193
238
|
const errorMessage = formatErrorMessage(err);
|
|
194
239
|
setIsLoading(false);
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
5
5
|
import { useInput } from '../services/keyboard.js';
|
|
6
6
|
import { formatErrorMessage } from '../services/messages.js';
|
|
7
|
+
import { ensureMinimumTime } from '../services/timing.js';
|
|
7
8
|
import { Spinner } from './Spinner.js';
|
|
8
9
|
const MIN_PROCESSING_TIME = 1000;
|
|
9
10
|
const BUILT_IN_CAPABILITIES = new Set([
|
|
@@ -12,9 +13,10 @@ const BUILT_IN_CAPABILITIES = new Set([
|
|
|
12
13
|
'INTROSPECT',
|
|
13
14
|
'ANSWER',
|
|
14
15
|
'EXECUTE',
|
|
16
|
+
'VALIDATE',
|
|
15
17
|
'REPORT',
|
|
16
18
|
]);
|
|
17
|
-
const INDIRECT_CAPABILITIES = new Set(['PLAN', 'REPORT']);
|
|
19
|
+
const INDIRECT_CAPABILITIES = new Set(['PLAN', 'VALIDATE', 'REPORT']);
|
|
18
20
|
function parseCapabilityFromTask(task) {
|
|
19
21
|
// Parse "NAME: Description" format from task.action
|
|
20
22
|
const colonIndex = task.action.indexOf(':');
|
|
@@ -69,15 +71,14 @@ export function Introspect({ tasks, state, service, children, debug = false, onE
|
|
|
69
71
|
const introspectAction = tasks[0]?.action || 'list capabilities';
|
|
70
72
|
// Call introspect tool
|
|
71
73
|
const result = await svc.processWithTool(introspectAction, 'introspect');
|
|
72
|
-
|
|
73
|
-
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
74
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
74
|
+
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
75
75
|
if (mounted) {
|
|
76
76
|
// Parse capabilities from returned tasks
|
|
77
77
|
let capabilities = result.tasks.map(parseCapabilityFromTask);
|
|
78
78
|
// Filter out internal capabilities when not in debug mode
|
|
79
79
|
if (!debug) {
|
|
80
80
|
capabilities = capabilities.filter((cap) => cap.name.toUpperCase() !== 'PLAN' &&
|
|
81
|
+
cap.name.toUpperCase() !== 'VALIDATE' &&
|
|
81
82
|
cap.name.toUpperCase() !== 'REPORT');
|
|
82
83
|
}
|
|
83
84
|
setIsLoading(false);
|
|
@@ -85,9 +86,7 @@ export function Introspect({ tasks, state, service, children, debug = false, onE
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
catch (err) {
|
|
88
|
-
|
|
89
|
-
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
90
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
89
|
+
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
91
90
|
if (mounted) {
|
|
92
91
|
const errorMessage = formatErrorMessage(err);
|
|
93
92
|
setIsLoading(false);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { TaskType } from '../types/types.js';
|
|
5
|
+
import { Colors, getTextColor } from '../services/colors.js';
|
|
6
|
+
import { useInput } from '../services/keyboard.js';
|
|
7
|
+
import { formatErrorMessage } from '../services/messages.js';
|
|
8
|
+
import { ensureMinimumTime } from '../services/timing.js';
|
|
9
|
+
import { Spinner } from './Spinner.js';
|
|
10
|
+
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;
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
16
|
+
const [completionMessage, setCompletionMessage] = useState(null);
|
|
17
|
+
useInput((input, key) => {
|
|
18
|
+
if (key.escape && isLoading && !done) {
|
|
19
|
+
setIsLoading(false);
|
|
20
|
+
onAborted();
|
|
21
|
+
}
|
|
22
|
+
}, { isActive: isLoading && !done });
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
// Skip processing if done
|
|
25
|
+
if (done) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Skip processing if no service available
|
|
29
|
+
if (!service) {
|
|
30
|
+
setError('No service available');
|
|
31
|
+
setIsLoading(false);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let mounted = true;
|
|
35
|
+
async function process(svc) {
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
try {
|
|
38
|
+
// Build prompt for VALIDATE tool
|
|
39
|
+
const prompt = buildValidatePrompt(missingConfig, userRequest);
|
|
40
|
+
// Call validate tool
|
|
41
|
+
const result = await svc.processWithTool(prompt, 'validate');
|
|
42
|
+
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
43
|
+
if (mounted) {
|
|
44
|
+
// Extract CONFIG tasks with descriptions from result
|
|
45
|
+
const configTasks = result.tasks.filter((task) => task.type === TaskType.Config);
|
|
46
|
+
// Build ConfigRequirements with descriptions
|
|
47
|
+
const withDescriptions = configTasks.map((task) => {
|
|
48
|
+
const key = typeof task.params?.key === 'string'
|
|
49
|
+
? task.params.key
|
|
50
|
+
: 'unknown';
|
|
51
|
+
const original = missingConfig.find((req) => req.path === key);
|
|
52
|
+
return {
|
|
53
|
+
path: key,
|
|
54
|
+
type: original?.type || 'string',
|
|
55
|
+
description: task.action,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
// Build completion message showing which config properties are needed
|
|
59
|
+
const count = withDescriptions.length;
|
|
60
|
+
const propertyWord = count === 1 ? 'property' : 'properties';
|
|
61
|
+
// Shuffle between different message variations
|
|
62
|
+
const messages = [
|
|
63
|
+
`Additional configuration ${propertyWord} required.`,
|
|
64
|
+
`Configuration ${propertyWord} needed.`,
|
|
65
|
+
`Missing configuration ${propertyWord} detected.`,
|
|
66
|
+
`Setup requires configuration ${propertyWord}.`,
|
|
67
|
+
];
|
|
68
|
+
const message = messages[Math.floor(Math.random() * messages.length)];
|
|
69
|
+
setCompletionMessage(message);
|
|
70
|
+
setIsLoading(false);
|
|
71
|
+
onComplete?.(withDescriptions);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
76
|
+
if (mounted) {
|
|
77
|
+
const errorMessage = formatErrorMessage(err);
|
|
78
|
+
setIsLoading(false);
|
|
79
|
+
if (onError) {
|
|
80
|
+
onError(errorMessage);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
setError(errorMessage);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
process(service);
|
|
89
|
+
return () => {
|
|
90
|
+
mounted = false;
|
|
91
|
+
};
|
|
92
|
+
}, [
|
|
93
|
+
missingConfig,
|
|
94
|
+
userRequest,
|
|
95
|
+
done,
|
|
96
|
+
service,
|
|
97
|
+
onComplete,
|
|
98
|
+
onError,
|
|
99
|
+
onAborted,
|
|
100
|
+
]);
|
|
101
|
+
// Don't render when done and nothing to show
|
|
102
|
+
if (done && !completionMessage && !error && !children) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
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] }));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build prompt for VALIDATE tool
|
|
109
|
+
*/
|
|
110
|
+
function buildValidatePrompt(missingConfig, userRequest) {
|
|
111
|
+
const configList = missingConfig
|
|
112
|
+
.map((req) => `- Config path: ${req.path}\n Type: ${req.type}`)
|
|
113
|
+
.join('\n');
|
|
114
|
+
return `User requested: "${userRequest}"
|
|
115
|
+
|
|
116
|
+
Missing configuration values:
|
|
117
|
+
${configList}
|
|
118
|
+
|
|
119
|
+
Generate natural language descriptions for these configuration values based on the skill context.`;
|
|
120
|
+
}
|
package/dist/ui/Welcome.js
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { Palette } from '../services/colors.js';
|
|
3
4
|
import { Panel } from './Panel.js';
|
|
5
|
+
export function Welcome({ app }) {
|
|
6
|
+
return (_jsx(Box, { alignSelf: "flex-start", children: _jsxs(Panel, { children: [_jsx(Header, { app: app }), _jsx(Description, { description: app.description }), _jsx(Usage, {})] }) }));
|
|
7
|
+
}
|
|
4
8
|
function Header({ app }) {
|
|
5
9
|
const words = app.name
|
|
6
10
|
.split('-')
|
|
7
11
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1));
|
|
8
|
-
return (_jsxs(Box, { marginBottom: 1, gap: 1, children: [words.map((word, index) => (_jsx(Text, { color:
|
|
12
|
+
return (_jsxs(Box, { marginBottom: 1, gap: 1, children: [words.map((word, index) => (_jsx(Text, { color: Palette.BrightGreen, bold: true, children: word }, index))), _jsxs(Text, { color: Palette.AshGray, children: ["v", app.version] }), app.isDev && _jsx(Text, { color: Palette.Yellow, children: "dev" })] }));
|
|
9
13
|
}
|
|
10
14
|
function Description({ description }) {
|
|
11
15
|
const lines = description
|
|
12
16
|
.split('. ')
|
|
13
17
|
.map((line) => line.replace(/\.$/, ''))
|
|
14
18
|
.filter(Boolean);
|
|
15
|
-
return (_jsx(_Fragment, { children: lines.map((line, index) => (_jsx(Box, { children: _jsxs(Text, { color:
|
|
19
|
+
return (_jsx(_Fragment, { children: lines.map((line, index) => (_jsx(Box, { children: _jsxs(Text, { color: Palette.White, children: [line, "."] }) }, index))) }));
|
|
16
20
|
}
|
|
17
21
|
function Usage() {
|
|
18
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(
|
|
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]" }) })] }));
|
|
19
23
|
}
|
|
20
|
-
|
|
21
|
-
return (
|
|
24
|
+
function Section({ title, children, }) {
|
|
25
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: Palette.White, children: title }), children] }));
|
|
26
|
+
}
|
|
27
|
+
function Example({ children }) {
|
|
28
|
+
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: Palette.Gray, children: ">" }), _jsx(Text, { color: Palette.BrightGreen, bold: true, children: "pls" }), _jsx(Text, { color: Palette.Yellow, children: children })] }));
|
|
22
29
|
}
|