prompt-language-shell 0.4.6 → 0.4.9
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/CONFIG.md +66 -0
- package/dist/config/EXECUTE.md +279 -0
- package/dist/config/INTROSPECT.md +29 -14
- package/dist/config/PLAN.md +40 -6
- package/dist/handlers/answer.js +13 -20
- package/dist/handlers/command.js +26 -30
- package/dist/handlers/config.js +44 -12
- package/dist/handlers/execute.js +38 -0
- package/dist/handlers/execution.js +66 -61
- package/dist/handlers/introspect.js +13 -20
- package/dist/handlers/plan.js +33 -36
- package/dist/services/anthropic.js +66 -12
- package/dist/services/colors.js +6 -5
- package/dist/services/components.js +159 -22
- package/dist/services/configuration.js +80 -0
- package/dist/services/keyboard.js +86 -0
- package/dist/services/messages.js +31 -0
- package/dist/services/shell.js +117 -0
- package/dist/services/tool-registry.js +10 -0
- package/dist/services/utils.js +21 -0
- package/dist/tools/config.tool.js +43 -0
- package/dist/tools/execute.tool.js +44 -0
- package/dist/types/handlers.js +1 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Answer.js +7 -5
- package/dist/ui/Command.js +15 -3
- package/dist/ui/Component.js +7 -1
- package/dist/ui/Config.js +6 -3
- package/dist/ui/Confirm.js +4 -3
- package/dist/ui/Execute.js +217 -0
- package/dist/ui/Introspect.js +20 -7
- package/dist/ui/Main.js +35 -74
- package/dist/ui/Plan.js +2 -1
- package/dist/ui/Refinement.js +2 -1
- package/dist/ui/Report.js +7 -3
- package/dist/ui/Spinner.js +10 -5
- package/package.json +7 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { ComponentName } from '../types/types.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getConfigSchema, loadConfig, } from './configuration.js';
|
|
4
4
|
import { getConfirmationMessage } from './messages.js';
|
|
5
5
|
import { StepType } from '../ui/Config.js';
|
|
6
6
|
export function markAsDone(component) {
|
|
@@ -14,27 +14,132 @@ export function createWelcomeDefinition(app) {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
export function createConfigSteps() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
17
|
+
// Use schema-based config step generation for required Anthropic settings
|
|
18
|
+
return createConfigStepsFromSchema(['anthropic.key', 'anthropic.model']);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get current config value for a dotted key path
|
|
22
|
+
*/
|
|
23
|
+
function getConfigValue(config, key) {
|
|
24
|
+
if (!config)
|
|
25
|
+
return undefined;
|
|
26
|
+
const parts = key.split('.');
|
|
27
|
+
let value = config;
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (value && typeof value === 'object' && part in value) {
|
|
30
|
+
value = value[part];
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get validation function for a config definition
|
|
40
|
+
*/
|
|
41
|
+
function getValidator(definition) {
|
|
42
|
+
switch (definition.type) {
|
|
43
|
+
case 'regexp':
|
|
44
|
+
return (value) => definition.pattern.test(value);
|
|
45
|
+
case 'string':
|
|
46
|
+
return () => true; // Strings are always valid
|
|
47
|
+
case 'enum':
|
|
48
|
+
return (value) => definition.values.includes(value);
|
|
49
|
+
case 'number':
|
|
50
|
+
return (value) => !isNaN(Number(value));
|
|
51
|
+
case 'boolean':
|
|
52
|
+
return (value) => value === 'true' || value === 'false';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create config steps from schema for specified keys
|
|
57
|
+
*/
|
|
58
|
+
export function createConfigStepsFromSchema(keys) {
|
|
59
|
+
const schema = getConfigSchema();
|
|
60
|
+
let currentConfig = null;
|
|
61
|
+
try {
|
|
62
|
+
currentConfig = loadConfig();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Config doesn't exist yet, use defaults
|
|
66
|
+
}
|
|
67
|
+
return keys.map((key) => {
|
|
68
|
+
if (!(key in schema)) {
|
|
69
|
+
throw new Error(`Unknown config key: ${key}`);
|
|
70
|
+
}
|
|
71
|
+
const definition = schema[key];
|
|
72
|
+
const currentValue = getConfigValue(currentConfig, key);
|
|
73
|
+
const keyParts = key.split('.');
|
|
74
|
+
const shortKey = keyParts[keyParts.length - 1];
|
|
75
|
+
// Map definition to ConfigStep based on type
|
|
76
|
+
switch (definition.type) {
|
|
77
|
+
case 'regexp':
|
|
78
|
+
case 'string': {
|
|
79
|
+
const value = currentValue !== undefined && typeof currentValue === 'string'
|
|
80
|
+
? currentValue
|
|
81
|
+
: definition.type === 'string'
|
|
82
|
+
? (definition.default ?? '')
|
|
83
|
+
: null;
|
|
84
|
+
return {
|
|
85
|
+
description: definition.description,
|
|
86
|
+
key: shortKey,
|
|
87
|
+
type: StepType.Text,
|
|
88
|
+
value,
|
|
89
|
+
validate: getValidator(definition),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
case 'number': {
|
|
93
|
+
const value = currentValue !== undefined && typeof currentValue === 'number'
|
|
94
|
+
? String(currentValue)
|
|
95
|
+
: definition.default !== undefined
|
|
96
|
+
? String(definition.default)
|
|
97
|
+
: '0';
|
|
98
|
+
return {
|
|
99
|
+
description: definition.description,
|
|
100
|
+
key: shortKey,
|
|
101
|
+
type: StepType.Text,
|
|
102
|
+
value,
|
|
103
|
+
validate: getValidator(definition),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
case 'enum': {
|
|
107
|
+
const currentStr = currentValue !== undefined && typeof currentValue === 'string'
|
|
108
|
+
? currentValue
|
|
109
|
+
: definition.default;
|
|
110
|
+
const defaultIndex = currentStr
|
|
111
|
+
? definition.values.indexOf(currentStr)
|
|
112
|
+
: 0;
|
|
113
|
+
return {
|
|
114
|
+
description: definition.description,
|
|
115
|
+
key: shortKey,
|
|
116
|
+
type: StepType.Selection,
|
|
117
|
+
options: definition.values.map((value) => ({
|
|
118
|
+
label: value,
|
|
119
|
+
value,
|
|
120
|
+
})),
|
|
121
|
+
defaultIndex: Math.max(0, defaultIndex),
|
|
122
|
+
validate: getValidator(definition),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
case 'boolean': {
|
|
126
|
+
const currentBool = currentValue !== undefined && typeof currentValue === 'boolean'
|
|
127
|
+
? currentValue
|
|
128
|
+
: undefined;
|
|
129
|
+
return {
|
|
130
|
+
description: definition.description,
|
|
131
|
+
key: shortKey,
|
|
132
|
+
type: StepType.Selection,
|
|
133
|
+
options: [
|
|
134
|
+
{ label: 'Yes', value: 'true' },
|
|
135
|
+
{ label: 'No', value: 'false' },
|
|
136
|
+
],
|
|
137
|
+
defaultIndex: currentBool !== undefined ? (currentBool ? 0 : 1) : 0,
|
|
138
|
+
validate: getValidator(definition),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
38
143
|
}
|
|
39
144
|
export function createConfigDefinition(onFinished, onAborted) {
|
|
40
145
|
return {
|
|
@@ -48,6 +153,21 @@ export function createConfigDefinition(onFinished, onAborted) {
|
|
|
48
153
|
},
|
|
49
154
|
};
|
|
50
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Create config definition with specific keys
|
|
158
|
+
*/
|
|
159
|
+
export function createConfigDefinitionWithKeys(keys, onFinished, onAborted) {
|
|
160
|
+
return {
|
|
161
|
+
id: randomUUID(),
|
|
162
|
+
name: ComponentName.Config,
|
|
163
|
+
state: { done: false },
|
|
164
|
+
props: {
|
|
165
|
+
steps: createConfigStepsFromSchema(keys),
|
|
166
|
+
onFinished,
|
|
167
|
+
onAborted,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
51
171
|
export function createCommandDefinition(command, service, onError, onComplete, onAborted) {
|
|
52
172
|
return {
|
|
53
173
|
id: randomUUID(),
|
|
@@ -181,3 +301,20 @@ export function createAnswerDisplayDefinition(answer) {
|
|
|
181
301
|
export function isStateless(component) {
|
|
182
302
|
return !('state' in component);
|
|
183
303
|
}
|
|
304
|
+
export function createExecuteDefinition(tasks, service, onError, onComplete, onAborted) {
|
|
305
|
+
return {
|
|
306
|
+
id: randomUUID(),
|
|
307
|
+
name: ComponentName.Execute,
|
|
308
|
+
state: {
|
|
309
|
+
done: false,
|
|
310
|
+
isLoading: true,
|
|
311
|
+
},
|
|
312
|
+
props: {
|
|
313
|
+
tasks,
|
|
314
|
+
service,
|
|
315
|
+
onError,
|
|
316
|
+
onComplete,
|
|
317
|
+
onAborted,
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
@@ -166,3 +166,83 @@ export function getConfigurationRequiredMessage(forFutureUse = false) {
|
|
|
166
166
|
];
|
|
167
167
|
return messages[Math.floor(Math.random() * messages.length)];
|
|
168
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Core configuration schema - defines structure and types for built-in settings
|
|
171
|
+
*/
|
|
172
|
+
const coreConfigSchema = {
|
|
173
|
+
'anthropic.key': {
|
|
174
|
+
type: 'regexp',
|
|
175
|
+
required: true,
|
|
176
|
+
pattern: /^sk-ant-api03-[A-Za-z0-9_-]{95}$/,
|
|
177
|
+
description: 'Anthropic API key',
|
|
178
|
+
},
|
|
179
|
+
'anthropic.model': {
|
|
180
|
+
type: 'enum',
|
|
181
|
+
required: true,
|
|
182
|
+
values: SUPPORTED_MODELS,
|
|
183
|
+
default: AnthropicModel.Haiku,
|
|
184
|
+
description: 'Anthropic model',
|
|
185
|
+
},
|
|
186
|
+
'settings.debug': {
|
|
187
|
+
type: 'boolean',
|
|
188
|
+
required: false,
|
|
189
|
+
description: 'Debug mode',
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Get complete configuration schema
|
|
194
|
+
* Currently returns core schema only
|
|
195
|
+
* Future: will merge with skill-declared schemas
|
|
196
|
+
*/
|
|
197
|
+
export function getConfigSchema() {
|
|
198
|
+
return {
|
|
199
|
+
...coreConfigSchema,
|
|
200
|
+
// Future: ...loadSkillSchemas()
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get available config structure for CONFIG tool
|
|
205
|
+
* Returns keys with descriptions only (no values for privacy)
|
|
206
|
+
*/
|
|
207
|
+
export function getAvailableConfigStructure() {
|
|
208
|
+
const schema = getConfigSchema();
|
|
209
|
+
const structure = {};
|
|
210
|
+
// Add core schema keys with descriptions
|
|
211
|
+
for (const [key, definition] of Object.entries(schema)) {
|
|
212
|
+
structure[key] = definition.description;
|
|
213
|
+
}
|
|
214
|
+
// Add discovered keys from config file (if it exists)
|
|
215
|
+
try {
|
|
216
|
+
const configFile = getConfigFile();
|
|
217
|
+
if (!existsSync(configFile)) {
|
|
218
|
+
return structure;
|
|
219
|
+
}
|
|
220
|
+
const content = readFileSync(configFile, 'utf-8');
|
|
221
|
+
const parsed = YAML.parse(content);
|
|
222
|
+
// Flatten nested config to dot notation
|
|
223
|
+
function flattenConfig(obj, prefix = '') {
|
|
224
|
+
const result = {};
|
|
225
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
226
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
227
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
228
|
+
Object.assign(result, flattenConfig(value, fullKey));
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
result[fullKey] = value;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
const flatConfig = flattenConfig(parsed);
|
|
237
|
+
// Add discovered keys that aren't in schema
|
|
238
|
+
for (const key of Object.keys(flatConfig)) {
|
|
239
|
+
if (!structure[key]) {
|
|
240
|
+
structure[key] = `${key} (discovered)`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// Config file doesn't exist or can't be read, only use schema
|
|
246
|
+
}
|
|
247
|
+
return structure;
|
|
248
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useInput as useInkInput } from 'ink';
|
|
2
|
+
const globalShortcuts = new Map();
|
|
3
|
+
/**
|
|
4
|
+
* Converts a Key object to a normalized pattern string
|
|
5
|
+
* Example: { shift: true, tab: true } → "shift+tab"
|
|
6
|
+
*/
|
|
7
|
+
function keyToPattern(key) {
|
|
8
|
+
const modifiers = [];
|
|
9
|
+
if (key.ctrl)
|
|
10
|
+
modifiers.push('ctrl');
|
|
11
|
+
if (key.meta)
|
|
12
|
+
modifiers.push('meta');
|
|
13
|
+
if (key.shift)
|
|
14
|
+
modifiers.push('shift');
|
|
15
|
+
// Get the key name
|
|
16
|
+
let keyName = '';
|
|
17
|
+
if (key.escape)
|
|
18
|
+
keyName = 'escape';
|
|
19
|
+
else if (key.tab)
|
|
20
|
+
keyName = 'tab';
|
|
21
|
+
else if (key.return)
|
|
22
|
+
keyName = 'return';
|
|
23
|
+
else if (key.upArrow)
|
|
24
|
+
keyName = 'up';
|
|
25
|
+
else if (key.downArrow)
|
|
26
|
+
keyName = 'down';
|
|
27
|
+
else if (key.leftArrow)
|
|
28
|
+
keyName = 'left';
|
|
29
|
+
else if (key.rightArrow)
|
|
30
|
+
keyName = 'right';
|
|
31
|
+
else if (key.backspace)
|
|
32
|
+
keyName = 'backspace';
|
|
33
|
+
else if (key.delete)
|
|
34
|
+
keyName = 'delete';
|
|
35
|
+
if (!keyName)
|
|
36
|
+
return null;
|
|
37
|
+
return [...modifiers, keyName].join('+');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Register a global keyboard shortcut
|
|
41
|
+
* @param pattern Pattern string like "shift+tab" or "ctrl+c"
|
|
42
|
+
* @param handler Function to call when shortcut is triggered
|
|
43
|
+
*/
|
|
44
|
+
export function registerGlobalShortcut(pattern, handler) {
|
|
45
|
+
globalShortcuts.set(pattern.toLowerCase(), handler);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if a key event matches a global shortcut
|
|
49
|
+
* If matched, calls the handler and returns true
|
|
50
|
+
* If not matched, returns false
|
|
51
|
+
* @param key The key object from ink's useInput
|
|
52
|
+
* @returns true if global shortcut was handled, false otherwise
|
|
53
|
+
*/
|
|
54
|
+
export function isGlobalShortcut(key) {
|
|
55
|
+
const pattern = keyToPattern(key);
|
|
56
|
+
if (!pattern)
|
|
57
|
+
return false;
|
|
58
|
+
const handler = globalShortcuts.get(pattern.toLowerCase());
|
|
59
|
+
if (handler) {
|
|
60
|
+
handler();
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Clear all registered global shortcuts (useful for testing)
|
|
67
|
+
*/
|
|
68
|
+
export function clearGlobalShortcuts() {
|
|
69
|
+
globalShortcuts.clear();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Custom useInput hook that automatically handles global shortcuts
|
|
73
|
+
* before passing events to the component handler
|
|
74
|
+
* @param handler Component's keyboard event handler
|
|
75
|
+
* @param options Options for useInput (isActive, etc.)
|
|
76
|
+
*/
|
|
77
|
+
export function useInput(handler, options) {
|
|
78
|
+
useInkInput((input, key) => {
|
|
79
|
+
// Check for global shortcuts first
|
|
80
|
+
if (isGlobalShortcut(key)) {
|
|
81
|
+
return; // Global shortcut handled, don't propagate to component
|
|
82
|
+
}
|
|
83
|
+
// No global shortcut matched, call component handler
|
|
84
|
+
handler(input, key);
|
|
85
|
+
}, options);
|
|
86
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { loadDebugSetting } from './configuration.js';
|
|
2
|
+
export { formatDuration } from './utils.js';
|
|
1
3
|
/**
|
|
2
4
|
* Returns a natural language confirmation message for plan execution.
|
|
3
5
|
* Randomly selects from variations to sound less robotic.
|
|
@@ -48,3 +50,32 @@ export const FeedbackMessages = {
|
|
|
48
50
|
ConfigurationComplete: 'Configuration complete.',
|
|
49
51
|
UnexpectedError: 'Unexpected error occurred:',
|
|
50
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Extracts a user-friendly error message from API errors.
|
|
55
|
+
* In debug mode, returns the full error; otherwise, returns just the message.
|
|
56
|
+
*
|
|
57
|
+
* Handles Anthropic API error format:
|
|
58
|
+
* 400 {"type":"error","error":{"type":"...","message":"..."},"request_id":"..."}
|
|
59
|
+
*/
|
|
60
|
+
export function formatErrorMessage(error) {
|
|
61
|
+
const rawMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
62
|
+
if (loadDebugSetting()) {
|
|
63
|
+
return rawMessage;
|
|
64
|
+
}
|
|
65
|
+
// Try to extract message from Anthropic API error format
|
|
66
|
+
// Format: "400 {json...}" or just "{json...}"
|
|
67
|
+
const jsonMatch = rawMessage.match(/\{.*\}/s);
|
|
68
|
+
if (jsonMatch) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
71
|
+
const message = parsed.error?.message ?? parsed.message;
|
|
72
|
+
if (message) {
|
|
73
|
+
return message;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// JSON parsing failed, return original message
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return rawMessage;
|
|
81
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export var ExecutionStatus;
|
|
2
|
+
(function (ExecutionStatus) {
|
|
3
|
+
ExecutionStatus["Pending"] = "pending";
|
|
4
|
+
ExecutionStatus["Running"] = "running";
|
|
5
|
+
ExecutionStatus["Success"] = "success";
|
|
6
|
+
ExecutionStatus["Failed"] = "failed";
|
|
7
|
+
})(ExecutionStatus || (ExecutionStatus = {}));
|
|
8
|
+
export var ExecutionResult;
|
|
9
|
+
(function (ExecutionResult) {
|
|
10
|
+
ExecutionResult["Success"] = "success";
|
|
11
|
+
ExecutionResult["Error"] = "error";
|
|
12
|
+
ExecutionResult["Aborted"] = "aborted";
|
|
13
|
+
})(ExecutionResult || (ExecutionResult = {}));
|
|
14
|
+
const DEFAULT_DELAY_GENERATOR = (index) => (Math.pow(3, index + 1) * Math.max(Math.random(), Math.random()) + 1) * 1000;
|
|
15
|
+
/**
|
|
16
|
+
* Dummy executor that simulates command execution with configurable delays.
|
|
17
|
+
* Supports mocked responses for testing different scenarios.
|
|
18
|
+
*/
|
|
19
|
+
export class DummyExecutor {
|
|
20
|
+
mockedResponses = new Map();
|
|
21
|
+
delayGenerator;
|
|
22
|
+
constructor(delayGenerator = DEFAULT_DELAY_GENERATOR) {
|
|
23
|
+
this.delayGenerator = delayGenerator;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set a mocked response for a specific command
|
|
27
|
+
*/
|
|
28
|
+
mock(command, response) {
|
|
29
|
+
this.mockedResponses.set(command, response);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Clear all mocked responses
|
|
33
|
+
*/
|
|
34
|
+
clearMocks() {
|
|
35
|
+
this.mockedResponses.clear();
|
|
36
|
+
}
|
|
37
|
+
execute(cmd, onProgress, index = 0) {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
onProgress?.(ExecutionStatus.Running);
|
|
40
|
+
const delay = this.delayGenerator(index);
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
const mocked = this.mockedResponses.get(cmd.command);
|
|
43
|
+
const commandResult = {
|
|
44
|
+
description: cmd.description,
|
|
45
|
+
command: cmd.command,
|
|
46
|
+
output: mocked?.output ?? '',
|
|
47
|
+
errors: mocked?.errors ?? '',
|
|
48
|
+
result: mocked?.result ?? ExecutionResult.Success,
|
|
49
|
+
error: mocked?.error,
|
|
50
|
+
};
|
|
51
|
+
onProgress?.(commandResult.result === ExecutionResult.Success
|
|
52
|
+
? ExecutionStatus.Success
|
|
53
|
+
: ExecutionStatus.Failed);
|
|
54
|
+
resolve(commandResult);
|
|
55
|
+
}, delay);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Default executor uses DummyExecutor for development and testing.
|
|
61
|
+
* To implement real shell execution, create a RealExecutor class that:
|
|
62
|
+
* - Spawns process with cmd.command in shell mode using child_process.spawn()
|
|
63
|
+
* - Sets working directory from cmd.workdir
|
|
64
|
+
* - Handles cmd.timeout for command timeout
|
|
65
|
+
* - Captures stdout and stderr streams
|
|
66
|
+
* - Calls onProgress with Running/Success/Failed status
|
|
67
|
+
* - Returns CommandOutput with actual stdout, stderr, exitCode
|
|
68
|
+
* - Handles errors (spawn failures, timeouts, non-zero exit codes)
|
|
69
|
+
*/
|
|
70
|
+
const executor = new DummyExecutor();
|
|
71
|
+
/**
|
|
72
|
+
* Execute a single shell command
|
|
73
|
+
*/
|
|
74
|
+
export function executeCommand(cmd, onProgress, index = 0) {
|
|
75
|
+
return executor.execute(cmd, onProgress, index);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Execute multiple commands sequentially
|
|
79
|
+
*/
|
|
80
|
+
export async function executeCommands(commands, onProgress) {
|
|
81
|
+
const results = [];
|
|
82
|
+
for (let i = 0; i < commands.length; i++) {
|
|
83
|
+
const cmd = commands[i];
|
|
84
|
+
onProgress?.({
|
|
85
|
+
currentIndex: i,
|
|
86
|
+
total: commands.length,
|
|
87
|
+
command: cmd,
|
|
88
|
+
status: ExecutionStatus.Running,
|
|
89
|
+
});
|
|
90
|
+
const output = await executeCommand(cmd, (status) => {
|
|
91
|
+
onProgress?.({
|
|
92
|
+
currentIndex: i,
|
|
93
|
+
total: commands.length,
|
|
94
|
+
command: cmd,
|
|
95
|
+
status,
|
|
96
|
+
output: status !== ExecutionStatus.Running ? results[i] : undefined,
|
|
97
|
+
});
|
|
98
|
+
}, i);
|
|
99
|
+
results.push(output);
|
|
100
|
+
// Update with final status
|
|
101
|
+
onProgress?.({
|
|
102
|
+
currentIndex: i,
|
|
103
|
+
total: commands.length,
|
|
104
|
+
command: cmd,
|
|
105
|
+
status: output.result === ExecutionResult.Success
|
|
106
|
+
? ExecutionStatus.Success
|
|
107
|
+
: ExecutionStatus.Failed,
|
|
108
|
+
output,
|
|
109
|
+
});
|
|
110
|
+
// Stop if critical command failed
|
|
111
|
+
const isCritical = cmd.critical !== false;
|
|
112
|
+
if (output.result !== ExecutionResult.Success && isCritical) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return results;
|
|
117
|
+
}
|
|
@@ -34,6 +34,8 @@ class ToolRegistry {
|
|
|
34
34
|
export const toolRegistry = new ToolRegistry();
|
|
35
35
|
// Register built-in tools
|
|
36
36
|
import { answerTool } from '../tools/answer.tool.js';
|
|
37
|
+
import { configTool } from '../tools/config.tool.js';
|
|
38
|
+
import { executeTool } from '../tools/execute.tool.js';
|
|
37
39
|
import { introspectTool } from '../tools/introspect.tool.js';
|
|
38
40
|
import { planTool } from '../tools/plan.tool.js';
|
|
39
41
|
toolRegistry.register('plan', {
|
|
@@ -48,3 +50,11 @@ toolRegistry.register('answer', {
|
|
|
48
50
|
schema: answerTool,
|
|
49
51
|
instructionsPath: 'config/ANSWER.md',
|
|
50
52
|
});
|
|
53
|
+
toolRegistry.register('config', {
|
|
54
|
+
schema: configTool,
|
|
55
|
+
instructionsPath: 'config/CONFIG.md',
|
|
56
|
+
});
|
|
57
|
+
toolRegistry.register('execute', {
|
|
58
|
+
schema: executeTool,
|
|
59
|
+
instructionsPath: 'config/EXECUTE.md',
|
|
60
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a duration in milliseconds to a human-readable string.
|
|
3
|
+
* Uses correct singular/plural forms.
|
|
4
|
+
*/
|
|
5
|
+
export function formatDuration(ms) {
|
|
6
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
7
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
8
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
9
|
+
const seconds = totalSeconds % 60;
|
|
10
|
+
const parts = [];
|
|
11
|
+
if (hours > 0) {
|
|
12
|
+
parts.push(`${String(hours)} ${hours === 1 ? 'hour' : 'hours'}`);
|
|
13
|
+
}
|
|
14
|
+
if (minutes > 0) {
|
|
15
|
+
parts.push(`${String(minutes)} ${minutes === 1 ? 'minute' : 'minutes'}`);
|
|
16
|
+
}
|
|
17
|
+
if (seconds > 0 || parts.length === 0) {
|
|
18
|
+
parts.push(`${String(seconds)} ${seconds === 1 ? 'second' : 'seconds'}`);
|
|
19
|
+
}
|
|
20
|
+
return parts.join(' ');
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const configTool = {
|
|
2
|
+
name: 'config',
|
|
3
|
+
description: 'Determine which configuration settings to show based on user query. Receives available config keys with descriptions and returns which keys the user wants to configure.',
|
|
4
|
+
input_schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
message: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'Brief message to display before config plan. Single sentence, maximum 64 characters. End with period.',
|
|
10
|
+
},
|
|
11
|
+
tasks: {
|
|
12
|
+
type: 'array',
|
|
13
|
+
description: 'Array of config settings to configure. Each task has type "config" and params with the config key.',
|
|
14
|
+
items: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
action: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Description of the config setting (from the provided descriptions). Maximum 64 characters.',
|
|
20
|
+
},
|
|
21
|
+
type: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Always "config" for configuration tasks.',
|
|
24
|
+
},
|
|
25
|
+
params: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
description: 'Parameters for the config task.',
|
|
28
|
+
properties: {
|
|
29
|
+
key: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'The config key to configure (e.g., "anthropic.key", "settings.debug").',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ['key'],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['action', 'type', 'params'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: ['message', 'tasks'],
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const executeTool = {
|
|
2
|
+
name: 'execute',
|
|
3
|
+
description: 'Execute shell commands from planned tasks. Translates task descriptions into specific shell commands that can be run in the terminal. Called after PLAN has created execute tasks and user has confirmed.',
|
|
4
|
+
input_schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
message: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'Brief status message about the execution. Must be a single sentence, maximum 64 characters, ending with a period.',
|
|
10
|
+
},
|
|
11
|
+
commands: {
|
|
12
|
+
type: 'array',
|
|
13
|
+
description: 'Array of commands to execute sequentially',
|
|
14
|
+
items: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
description: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Brief description of what this command does. Maximum 64 characters.',
|
|
20
|
+
},
|
|
21
|
+
command: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'The exact shell command to run. Must be a valid shell command.',
|
|
24
|
+
},
|
|
25
|
+
workdir: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Optional working directory for the command. Defaults to current directory if not specified.',
|
|
28
|
+
},
|
|
29
|
+
timeout: {
|
|
30
|
+
type: 'number',
|
|
31
|
+
description: 'Optional timeout in milliseconds. Defaults to 30000 (30 seconds).',
|
|
32
|
+
},
|
|
33
|
+
critical: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
description: 'Whether failure should stop execution of subsequent commands. Defaults to true.',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ['description', 'command'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ['message', 'commands'],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/types.js
CHANGED
|
@@ -12,6 +12,7 @@ export var ComponentName;
|
|
|
12
12
|
ComponentName["Report"] = "report";
|
|
13
13
|
ComponentName["Answer"] = "answer";
|
|
14
14
|
ComponentName["AnswerDisplay"] = "answerDisplay";
|
|
15
|
+
ComponentName["Execute"] = "execute";
|
|
15
16
|
})(ComponentName || (ComponentName = {}));
|
|
16
17
|
export var TaskType;
|
|
17
18
|
(function (TaskType) {
|