prompt-language-shell 0.4.2 → 0.4.6

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.
@@ -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 './config.js';
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) {
@@ -152,6 +152,32 @@ export function createReportDefinition(message, capabilities) {
152
152
  },
153
153
  };
154
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
+ }
155
181
  export function isStateless(component) {
156
182
  return !('state' in component);
157
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
+ };
@@ -10,6 +10,8 @@ export var ComponentName;
10
10
  ComponentName["Confirm"] = "confirm";
11
11
  ComponentName["Introspect"] = "introspect";
12
12
  ComponentName["Report"] = "report";
13
+ ComponentName["Answer"] = "answer";
14
+ ComponentName["AnswerDisplay"] = "answerDisplay";
13
15
  })(ComponentName || (ComponentName = {}));
14
16
  export var TaskType;
15
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 { Colors, getTextColor } from '../services/colors.js';
5
+ import { Spinner } from './Spinner.js';
6
+ const MinimumProcessingTime = 400;
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, MinimumProcessingTime - 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, MinimumProcessingTime - 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: Colors.Status.Error, 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
+ }
@@ -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 { Colors } 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
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", 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
+ const isCurrent = done === false;
63
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { paddingX: done ? 1 : 0, marginX: done ? -1 : 0, backgroundColor: done ? Colors.Background.UserQuery : undefined, children: [_jsxs(Text, { color: isCurrent ? Colors.Text.Active : Colors.Text.UserQuery, children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
62
64
  }
@@ -1,6 +1,8 @@
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';
@@ -25,8 +27,11 @@ export const Component = React.memo(function Component({ def, debug, }) {
25
27
  const state = def.state;
26
28
  return _jsx(Command, { ...props, state: state });
27
29
  }
28
- case ComponentName.Plan:
29
- return _jsx(Plan, { ...def.props, debug: debug });
30
+ case ComponentName.Plan: {
31
+ const props = def.props;
32
+ const state = def.state;
33
+ return _jsx(Plan, { ...props, state: state, debug: debug });
34
+ }
30
35
  case ComponentName.Feedback:
31
36
  return _jsx(Feedback, { ...def.props });
32
37
  case ComponentName.Message:
@@ -48,5 +53,12 @@ export const Component = React.memo(function Component({ def, debug, }) {
48
53
  }
49
54
  case ComponentName.Report:
50
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 });
51
63
  }
52
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 '../types/colors.js';
5
+ import { Colors } from '../services/colors.js';
6
6
  export var StepType;
7
7
  (function (StepType) {
8
8
  StepType["Text"] = "text";
@@ -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 '../types/colors.js';
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)
@@ -28,15 +29,15 @@ export function Confirm({ message, state, onConfirmed, onCancelled, }) {
28
29
  }
29
30
  }, { isActive: !done });
30
31
  const options = [
31
- { label: 'Yes', value: 'yes', color: Colors.Action.Execute },
32
- { label: 'No', value: 'no', color: Colors.Action.Discard },
32
+ { label: 'yes', value: 'yes', color: Colors.Action.Execute },
33
+ { label: 'no', value: 'no', color: Colors.Status.Error },
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: "gray", children: ["> ", options[selectedIndex].label] }) })] }));
37
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsx(Box, { paddingX: 1, marginX: -1, alignSelf: "flex-start", backgroundColor: Colors.Background.UserQuery, children: _jsxs(Text, { color: Colors.Text.UserQuery, 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
- return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, bold: isSelected, children: option.label }) }, option.value));
41
+ return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
41
42
  }) })] })] }));
42
43
  }
@@ -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 { FeedbackColors } from '../types/colors.js';
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 = FeedbackColors[type];
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
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
+ import { Colors, getTextColor } from '../services/colors.js';
4
5
  import { Spinner } from './Spinner.js';
5
6
  const MIN_PROCESSING_TIME = 1000;
6
7
  const BUILT_IN_CAPABILITIES = new Set([
@@ -32,6 +33,7 @@ function parseCapabilityFromTask(task) {
32
33
  }
33
34
  export function Introspect({ tasks, state, service, children, onError, onComplete, onAborted, }) {
34
35
  const done = state?.done ?? false;
36
+ const isCurrent = done === false;
35
37
  const [error, setError] = useState(null);
36
38
  const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
37
39
  useInput((input, key) => {
@@ -94,5 +96,5 @@ export function Introspect({ tasks, state, service, children, onError, onComplet
94
96
  if (!isLoading && !error && !children) {
95
97
  return null;
96
98
  }
97
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
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: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
98
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, descriptionColor, type, typeColor, showType = false, }) {
5
- return (_jsxs(Box, { children: [_jsx(Text, { color: descriptionColor, children: description }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: type })] }))] }));
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 { Label } from './Label.js';
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(Label, { description: item.description.text, descriptionColor: descriptionColor, type: item.type.text, typeColor: typeColor, showType: showType })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }))] }, index));
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
  };