prompt-language-shell 0.4.0 → 0.4.4
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/ANSWER.md +119 -0
- package/dist/config/INTROSPECT.md +18 -13
- package/dist/config/PLAN.md +27 -6
- package/dist/handlers/answer.js +28 -0
- package/dist/handlers/command.js +38 -0
- package/dist/handlers/config.js +39 -0
- package/dist/handlers/execution.js +68 -0
- package/dist/handlers/introspect.js +28 -0
- package/dist/handlers/plan.js +82 -0
- package/dist/services/anthropic.js +18 -2
- package/dist/{types → services}/colors.js +58 -6
- package/dist/services/components.js +54 -1
- package/dist/services/queue.js +52 -0
- package/dist/services/tool-registry.js +5 -0
- package/dist/tools/answer.tool.js +18 -0
- package/dist/types/types.js +4 -0
- package/dist/ui/Answer.js +71 -0
- package/dist/ui/AnswerDisplay.js +8 -0
- package/dist/ui/Command.js +3 -1
- package/dist/ui/Component.js +23 -2
- package/dist/ui/Config.js +1 -1
- package/dist/ui/Confirm.js +4 -3
- package/dist/ui/Feedback.js +2 -2
- package/dist/ui/Introspect.js +100 -0
- package/dist/ui/Label.js +4 -2
- package/dist/ui/List.js +3 -3
- package/dist/ui/Main.js +41 -174
- package/dist/ui/Plan.js +13 -10
- package/dist/ui/Report.js +13 -0
- package/dist/ui/Separator.js +1 -1
- package/package.json +1 -1
- /package/dist/services/{config.js → configuration.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { ComponentName } from '../types/types.js';
|
|
3
|
-
import { AnthropicModel, isValidAnthropicApiKey, isValidAnthropicModel, } from './
|
|
3
|
+
import { AnthropicModel, isValidAnthropicApiKey, isValidAnthropicModel, } from './configuration.js';
|
|
4
4
|
import { getConfirmationMessage } from './messages.js';
|
|
5
5
|
import { StepType } from '../ui/Config.js';
|
|
6
6
|
export function markAsDone(component) {
|
|
@@ -125,6 +125,59 @@ export function createConfirmDefinition(onConfirmed, onCancelled) {
|
|
|
125
125
|
},
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
export function createIntrospectDefinition(tasks, service, onError, onComplete, onAborted) {
|
|
129
|
+
return {
|
|
130
|
+
id: randomUUID(),
|
|
131
|
+
name: ComponentName.Introspect,
|
|
132
|
+
state: {
|
|
133
|
+
done: false,
|
|
134
|
+
isLoading: true,
|
|
135
|
+
},
|
|
136
|
+
props: {
|
|
137
|
+
tasks,
|
|
138
|
+
service,
|
|
139
|
+
onError,
|
|
140
|
+
onComplete,
|
|
141
|
+
onAborted,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function createReportDefinition(message, capabilities) {
|
|
146
|
+
return {
|
|
147
|
+
id: randomUUID(),
|
|
148
|
+
name: ComponentName.Report,
|
|
149
|
+
props: {
|
|
150
|
+
message,
|
|
151
|
+
capabilities,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export function createAnswerDefinition(question, service, onError, onComplete, onAborted) {
|
|
156
|
+
return {
|
|
157
|
+
id: randomUUID(),
|
|
158
|
+
name: ComponentName.Answer,
|
|
159
|
+
state: {
|
|
160
|
+
done: false,
|
|
161
|
+
isLoading: true,
|
|
162
|
+
},
|
|
163
|
+
props: {
|
|
164
|
+
question,
|
|
165
|
+
service,
|
|
166
|
+
onError,
|
|
167
|
+
onComplete,
|
|
168
|
+
onAborted,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function createAnswerDisplayDefinition(answer) {
|
|
173
|
+
return {
|
|
174
|
+
id: randomUUID(),
|
|
175
|
+
name: ComponentName.AnswerDisplay,
|
|
176
|
+
props: {
|
|
177
|
+
answer,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
128
181
|
export function isStateless(component) {
|
|
129
182
|
return !('state' in component);
|
|
130
183
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FeedbackType } from '../types/types.js';
|
|
2
|
+
import { createFeedback, markAsDone } from './components.js';
|
|
3
|
+
import { FeedbackMessages } from './messages.js';
|
|
4
|
+
import { exitApp } from './process.js';
|
|
5
|
+
/**
|
|
6
|
+
* Higher-order function that wraps queue handler logic with common patterns:
|
|
7
|
+
* - Check if queue is empty
|
|
8
|
+
* - Extract first element
|
|
9
|
+
* - Optionally check component name
|
|
10
|
+
* - Execute callback with first element
|
|
11
|
+
* - Return new queue state
|
|
12
|
+
*/
|
|
13
|
+
export function withQueueHandler(componentName, callback, shouldExit = false, exitCode = 0) {
|
|
14
|
+
return (currentQueue) => {
|
|
15
|
+
if (currentQueue.length === 0)
|
|
16
|
+
return currentQueue;
|
|
17
|
+
const [first, ...rest] = currentQueue;
|
|
18
|
+
// If componentName is specified, check if it matches
|
|
19
|
+
if (componentName && first.name !== componentName) {
|
|
20
|
+
if (shouldExit) {
|
|
21
|
+
exitApp(exitCode);
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
// Execute callback with first and rest
|
|
26
|
+
const result = callback(first, rest);
|
|
27
|
+
// Exit if specified
|
|
28
|
+
if (shouldExit) {
|
|
29
|
+
exitApp(exitCode);
|
|
30
|
+
}
|
|
31
|
+
// Return result or empty queue
|
|
32
|
+
return result || [];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates a generic error handler for a component
|
|
37
|
+
*/
|
|
38
|
+
export function createErrorHandler(componentName, addToTimeline) {
|
|
39
|
+
return (error) => withQueueHandler(componentName, (first) => {
|
|
40
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, error));
|
|
41
|
+
return undefined;
|
|
42
|
+
}, true, 1);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a generic completion handler for a component
|
|
46
|
+
*/
|
|
47
|
+
export function createCompletionHandler(componentName, addToTimeline, onComplete) {
|
|
48
|
+
return withQueueHandler(componentName, (first) => {
|
|
49
|
+
onComplete(first);
|
|
50
|
+
return undefined;
|
|
51
|
+
}, true, 0);
|
|
52
|
+
}
|
|
@@ -33,6 +33,7 @@ class ToolRegistry {
|
|
|
33
33
|
// Create singleton instance
|
|
34
34
|
export const toolRegistry = new ToolRegistry();
|
|
35
35
|
// Register built-in tools
|
|
36
|
+
import { answerTool } from '../tools/answer.tool.js';
|
|
36
37
|
import { introspectTool } from '../tools/introspect.tool.js';
|
|
37
38
|
import { planTool } from '../tools/plan.tool.js';
|
|
38
39
|
toolRegistry.register('plan', {
|
|
@@ -43,3 +44,7 @@ toolRegistry.register('introspect', {
|
|
|
43
44
|
schema: introspectTool,
|
|
44
45
|
instructionsPath: 'config/INTROSPECT.md',
|
|
45
46
|
});
|
|
47
|
+
toolRegistry.register('answer', {
|
|
48
|
+
schema: answerTool,
|
|
49
|
+
instructionsPath: 'config/ANSWER.md',
|
|
50
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const answerTool = {
|
|
2
|
+
name: 'answer',
|
|
3
|
+
description: 'Answer questions and provide up-to-date information using web search. Called after PLAN has identified an answer request and user has confirmed. Searches the web for current data and provides concise, helpful responses formatted for terminal display.',
|
|
4
|
+
input_schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
question: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'The question or information request from the user. Should be a clear, complete question.',
|
|
10
|
+
},
|
|
11
|
+
answer: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'The answer to the question. Must be concise and well-formatted. Maximum 4 lines of text, each line maximum 80 characters. Use natural line breaks for readability.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
required: ['question', 'answer'],
|
|
17
|
+
},
|
|
18
|
+
};
|
package/dist/types/types.js
CHANGED
|
@@ -8,6 +8,10 @@ export var ComponentName;
|
|
|
8
8
|
ComponentName["Refinement"] = "refinement";
|
|
9
9
|
ComponentName["Feedback"] = "feedback";
|
|
10
10
|
ComponentName["Confirm"] = "confirm";
|
|
11
|
+
ComponentName["Introspect"] = "introspect";
|
|
12
|
+
ComponentName["Report"] = "report";
|
|
13
|
+
ComponentName["Answer"] = "answer";
|
|
14
|
+
ComponentName["AnswerDisplay"] = "answerDisplay";
|
|
11
15
|
})(ComponentName || (ComponentName = {}));
|
|
12
16
|
export var TaskType;
|
|
13
17
|
(function (TaskType) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getTextColor } from '../services/colors.js';
|
|
5
|
+
import { Spinner } from './Spinner.js';
|
|
6
|
+
const MIN_PROCESSING_TIME = 1000;
|
|
7
|
+
export function Answer({ question, state, service, onError, onComplete, onAborted, }) {
|
|
8
|
+
const done = state?.done ?? false;
|
|
9
|
+
const isCurrent = done === false;
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
if (key.escape && isLoading && !done) {
|
|
14
|
+
setIsLoading(false);
|
|
15
|
+
onAborted();
|
|
16
|
+
}
|
|
17
|
+
}, { isActive: isLoading && !done });
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// Skip processing if done
|
|
20
|
+
if (done) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Skip processing if no service available
|
|
24
|
+
if (!service) {
|
|
25
|
+
setError('No service available');
|
|
26
|
+
setIsLoading(false);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let mounted = true;
|
|
30
|
+
async function process(svc) {
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
try {
|
|
33
|
+
// Call answer tool
|
|
34
|
+
const result = await svc.processWithTool(question, 'answer');
|
|
35
|
+
const elapsed = Date.now() - startTime;
|
|
36
|
+
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
38
|
+
if (mounted) {
|
|
39
|
+
// Extract answer from result
|
|
40
|
+
const answer = result.answer || '';
|
|
41
|
+
setIsLoading(false);
|
|
42
|
+
onComplete?.(answer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const elapsed = Date.now() - startTime;
|
|
47
|
+
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
49
|
+
if (mounted) {
|
|
50
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
if (onError) {
|
|
53
|
+
onError(errorMessage);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
setError(errorMessage);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
process(service);
|
|
62
|
+
return () => {
|
|
63
|
+
mounted = false;
|
|
64
|
+
};
|
|
65
|
+
}, [question, done, service, onComplete, onError]);
|
|
66
|
+
// Return null when done (like Introspect)
|
|
67
|
+
if (done || (!isLoading && !error)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Finding answer. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }))] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Colors } from '../services/colors.js';
|
|
4
|
+
export function AnswerDisplay({ answer }) {
|
|
5
|
+
// Split answer into lines and display with indentation
|
|
6
|
+
const lines = answer.split('\n');
|
|
7
|
+
return (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: lines.map((line, index) => (_jsx(Text, { color: Colors.Text.Active, children: line }, index))) }));
|
|
8
|
+
}
|
package/dist/ui/Command.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getTextColor } from '../services/colors.js';
|
|
4
5
|
import { Spinner } from './Spinner.js';
|
|
5
6
|
const MIN_PROCESSING_TIME = 1000; // purely for visual effect
|
|
6
7
|
export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
|
|
@@ -58,5 +59,6 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
58
59
|
mounted = false;
|
|
59
60
|
};
|
|
60
61
|
}, [command, done, service]);
|
|
61
|
-
|
|
62
|
+
const isCurrent = done === false;
|
|
63
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: getTextColor(isCurrent), children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
|
|
62
64
|
}
|
package/dist/ui/Component.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { ComponentName } from '../types/types.js';
|
|
4
|
+
import { Answer } from './Answer.js';
|
|
5
|
+
import { AnswerDisplay } from './AnswerDisplay.js';
|
|
4
6
|
import { Command } from './Command.js';
|
|
5
7
|
import { Confirm } from './Confirm.js';
|
|
6
8
|
import { Config } from './Config.js';
|
|
7
9
|
import { Feedback } from './Feedback.js';
|
|
10
|
+
import { Introspect } from './Introspect.js';
|
|
8
11
|
import { Message } from './Message.js';
|
|
9
12
|
import { Plan } from './Plan.js';
|
|
10
13
|
import { Refinement } from './Refinement.js';
|
|
14
|
+
import { Report } from './Report.js';
|
|
11
15
|
import { Welcome } from './Welcome.js';
|
|
12
16
|
export const Component = React.memo(function Component({ def, debug, }) {
|
|
13
17
|
switch (def.name) {
|
|
@@ -23,8 +27,11 @@ export const Component = React.memo(function Component({ def, debug, }) {
|
|
|
23
27
|
const state = def.state;
|
|
24
28
|
return _jsx(Command, { ...props, state: state });
|
|
25
29
|
}
|
|
26
|
-
case ComponentName.Plan:
|
|
27
|
-
|
|
30
|
+
case ComponentName.Plan: {
|
|
31
|
+
const props = def.props;
|
|
32
|
+
const state = def.state;
|
|
33
|
+
return _jsx(Plan, { ...props, state: state, debug: debug });
|
|
34
|
+
}
|
|
28
35
|
case ComponentName.Feedback:
|
|
29
36
|
return _jsx(Feedback, { ...def.props });
|
|
30
37
|
case ComponentName.Message:
|
|
@@ -39,5 +46,19 @@ export const Component = React.memo(function Component({ def, debug, }) {
|
|
|
39
46
|
const state = def.state;
|
|
40
47
|
return _jsx(Confirm, { ...props, state: state });
|
|
41
48
|
}
|
|
49
|
+
case ComponentName.Introspect: {
|
|
50
|
+
const props = def.props;
|
|
51
|
+
const state = def.state;
|
|
52
|
+
return _jsx(Introspect, { ...props, state: state });
|
|
53
|
+
}
|
|
54
|
+
case ComponentName.Report:
|
|
55
|
+
return _jsx(Report, { ...def.props });
|
|
56
|
+
case ComponentName.Answer: {
|
|
57
|
+
const props = def.props;
|
|
58
|
+
const state = def.state;
|
|
59
|
+
return _jsx(Answer, { ...props, state: state });
|
|
60
|
+
}
|
|
61
|
+
case ComponentName.AnswerDisplay:
|
|
62
|
+
return _jsx(AnswerDisplay, { ...def.props });
|
|
42
63
|
}
|
|
43
64
|
});
|
package/dist/ui/Config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Box, Text, useFocus, useInput } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
-
import { Colors } from '../
|
|
5
|
+
import { Colors } from '../services/colors.js';
|
|
6
6
|
export var StepType;
|
|
7
7
|
(function (StepType) {
|
|
8
8
|
StepType["Text"] = "text";
|
package/dist/ui/Confirm.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { Colors } from '../
|
|
4
|
+
import { Colors } from '../services/colors.js';
|
|
5
5
|
export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
6
6
|
const done = state?.done ?? false;
|
|
7
|
+
const isCurrent = done === false;
|
|
7
8
|
const [selectedIndex, setSelectedIndex] = React.useState(0); // 0 = Yes, 1 = No
|
|
8
9
|
useInput((input, key) => {
|
|
9
10
|
if (done)
|
|
@@ -33,9 +34,9 @@ export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
|
33
34
|
];
|
|
34
35
|
if (done) {
|
|
35
36
|
// When done, show both the message and user's choice in timeline
|
|
36
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { children: _jsxs(Text, { color:
|
|
37
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsx(Box, { children: _jsxs(Text, { color: Colors.Text.Inactive, children: ["> ", options[selectedIndex].label] }) })] }));
|
|
37
38
|
}
|
|
38
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
39
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: isCurrent ? Colors.Text.Active : Colors.Text.Inactive, children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
39
40
|
const isSelected = index === selectedIndex;
|
|
40
41
|
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, bold: isSelected, children: option.label }) }, option.value));
|
|
41
42
|
}) })] })] }));
|
package/dist/ui/Feedback.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import {
|
|
3
|
+
import { getFeedbackColor } from '../services/colors.js';
|
|
4
4
|
import { FeedbackType } from '../types/types.js';
|
|
5
5
|
function getSymbol(type) {
|
|
6
6
|
return {
|
|
@@ -11,7 +11,7 @@ function getSymbol(type) {
|
|
|
11
11
|
}[type];
|
|
12
12
|
}
|
|
13
13
|
export function Feedback({ type, message }) {
|
|
14
|
-
const color =
|
|
14
|
+
const color = getFeedbackColor(type, false);
|
|
15
15
|
const symbol = getSymbol(type);
|
|
16
16
|
return (_jsx(Box, { children: _jsxs(Text, { color: color, children: [symbol, " ", message] }) }));
|
|
17
17
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getTextColor } from '../services/colors.js';
|
|
5
|
+
import { Spinner } from './Spinner.js';
|
|
6
|
+
const MIN_PROCESSING_TIME = 1000;
|
|
7
|
+
const BUILT_IN_CAPABILITIES = new Set([
|
|
8
|
+
'CONFIG',
|
|
9
|
+
'PLAN',
|
|
10
|
+
'INTROSPECT',
|
|
11
|
+
'ANSWER',
|
|
12
|
+
'EXECUTE',
|
|
13
|
+
'REPORT',
|
|
14
|
+
]);
|
|
15
|
+
function parseCapabilityFromTask(task) {
|
|
16
|
+
// Parse "NAME: Description" format from task.action
|
|
17
|
+
const colonIndex = task.action.indexOf(':');
|
|
18
|
+
if (colonIndex === -1) {
|
|
19
|
+
return {
|
|
20
|
+
name: task.action,
|
|
21
|
+
description: '',
|
|
22
|
+
isBuiltIn: BUILT_IN_CAPABILITIES.has(task.action.toUpperCase()),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const name = task.action.substring(0, colonIndex).trim();
|
|
26
|
+
const description = task.action.substring(colonIndex + 1).trim();
|
|
27
|
+
const isBuiltIn = BUILT_IN_CAPABILITIES.has(name.toUpperCase());
|
|
28
|
+
return {
|
|
29
|
+
name,
|
|
30
|
+
description,
|
|
31
|
+
isBuiltIn,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function Introspect({ tasks, state, service, children, onError, onComplete, onAborted, }) {
|
|
35
|
+
const done = state?.done ?? false;
|
|
36
|
+
const isCurrent = done === false;
|
|
37
|
+
const [error, setError] = useState(null);
|
|
38
|
+
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
39
|
+
useInput((input, key) => {
|
|
40
|
+
if (key.escape && isLoading && !done) {
|
|
41
|
+
setIsLoading(false);
|
|
42
|
+
onAborted();
|
|
43
|
+
}
|
|
44
|
+
}, { isActive: isLoading && !done });
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
// Skip processing if done
|
|
47
|
+
if (done) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Skip processing if no service available
|
|
51
|
+
if (!service) {
|
|
52
|
+
setError('No service available');
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let mounted = true;
|
|
57
|
+
async function process(svc) {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
try {
|
|
60
|
+
// Get the introspect task action (first task should have the intro message)
|
|
61
|
+
const introspectAction = tasks[0]?.action || 'list capabilities';
|
|
62
|
+
// Call introspect tool
|
|
63
|
+
const result = await svc.processWithTool(introspectAction, 'introspect');
|
|
64
|
+
const elapsed = Date.now() - startTime;
|
|
65
|
+
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
67
|
+
if (mounted) {
|
|
68
|
+
// Parse capabilities from returned tasks
|
|
69
|
+
const capabilities = result.tasks.map(parseCapabilityFromTask);
|
|
70
|
+
setIsLoading(false);
|
|
71
|
+
onComplete?.(result.message, capabilities);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const elapsed = Date.now() - startTime;
|
|
76
|
+
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
78
|
+
if (mounted) {
|
|
79
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
80
|
+
setIsLoading(false);
|
|
81
|
+
if (onError) {
|
|
82
|
+
onError(errorMessage);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
setError(errorMessage);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
process(service);
|
|
91
|
+
return () => {
|
|
92
|
+
mounted = false;
|
|
93
|
+
};
|
|
94
|
+
}, [tasks, done, service]);
|
|
95
|
+
// Don't render wrapper when done and nothing to show
|
|
96
|
+
if (!isLoading && !error && !children) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
|
|
100
|
+
}
|
package/dist/ui/Label.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { getTaskColors } from '../services/colors.js';
|
|
3
4
|
import { Separator } from './Separator.js';
|
|
4
|
-
export function Label({ description,
|
|
5
|
-
|
|
5
|
+
export function Label({ description, taskType, showType = false, isCurrent = false, }) {
|
|
6
|
+
const colors = getTaskColors(taskType, isCurrent);
|
|
7
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.description, children: description }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: colors.type, children: taskType })] }))] }));
|
|
6
8
|
}
|
package/dist/ui/List.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import {
|
|
3
|
+
import { Separator } from './Separator.js';
|
|
4
4
|
export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, showType = false, }) => {
|
|
5
5
|
const marginLeft = level > 0 ? 4 : 0;
|
|
6
6
|
return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => {
|
|
@@ -21,6 +21,6 @@ export const List = ({ items, level = 0, highlightedIndex = null, highlightedPar
|
|
|
21
21
|
(isHighlighted && item.type.highlightedColor
|
|
22
22
|
? item.type.highlightedColor
|
|
23
23
|
: 'whiteBright');
|
|
24
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(Text, { color: descriptionColor, children: item.description.text }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: item.type.text })] }))] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }))] }, index));
|
|
25
25
|
}) }));
|
|
26
26
|
};
|