prompt-language-shell 0.4.9 → 0.5.0
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/dist/config/INTROSPECT.md +9 -6
- 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 +5 -7
- package/dist/ui/Validate.js +120 -0
- package/package.json +1 -1
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,9 +71,7 @@ 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);
|
|
@@ -85,9 +85,7 @@ export function Introspect({ tasks, state, service, children, debug = false, onE
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
catch (err) {
|
|
88
|
-
|
|
89
|
-
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
90
|
-
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
88
|
+
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
91
89
|
if (mounted) {
|
|
92
90
|
const errorMessage = formatErrorMessage(err);
|
|
93
91
|
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
|
+
}
|