prompt-language-shell 0.3.4 → 0.3.8
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 +6 -0
- package/dist/index.js +1 -0
- package/dist/services/components.js +41 -4
- package/dist/services/config.js +20 -0
- package/dist/services/messages.js +15 -0
- package/dist/tools/plan.tool.js +1 -1
- package/dist/types/components.js +1 -28
- package/dist/types/types.js +30 -0
- package/dist/ui/Column.js +2 -2
- package/dist/ui/Command.js +8 -2
- package/dist/ui/Component.js +15 -3
- package/dist/ui/Config.js +1 -1
- package/dist/ui/Confirm.js +41 -0
- package/dist/ui/Feedback.js +1 -1
- package/dist/ui/Label.js +3 -3
- package/dist/ui/List.js +7 -2
- package/dist/ui/Main.js +125 -28
- package/dist/ui/Message.js +2 -2
- package/dist/ui/Plan.js +30 -11
- package/dist/ui/Refinement.js +14 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -45,6 +45,12 @@ Your configuration is stored in `~/.plsrc` as a YAML file. Supported settings:
|
|
|
45
45
|
- `anthropic.key` - Your API key
|
|
46
46
|
- `anthropic.model` - The model to use
|
|
47
47
|
|
|
48
|
+
## Skills
|
|
49
|
+
|
|
50
|
+
You can extend `pls` with custom workflows by creating markdown files in `~/.pls/skills/`. Skills define domain-specific operations, parameters, and steps that guide both planning and execution of tasks.
|
|
51
|
+
|
|
52
|
+
Your skills are referenced when planning requests, enabling `pls` to understand specific workflows, create and execute structured plans tailored to your environment.
|
|
53
|
+
|
|
48
54
|
## Development
|
|
49
55
|
|
|
50
56
|
See [CLAUDE.md](./CLAUDE.md) for development guidelines and architecture.
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { ComponentName
|
|
3
|
-
import { StepType } from '../ui/Config.js';
|
|
2
|
+
import { ComponentName } from '../types/types.js';
|
|
4
3
|
import { AnthropicModel, isValidAnthropicApiKey, isValidAnthropicModel, } from './config.js';
|
|
4
|
+
import { getConfirmationMessage } from './messages.js';
|
|
5
|
+
import { StepType } from '../ui/Config.js';
|
|
5
6
|
export function markAsDone(component) {
|
|
6
7
|
return { ...component, state: { ...component.state, done: true } };
|
|
7
8
|
}
|
|
9
|
+
export function getRefiningMessage() {
|
|
10
|
+
const messages = [
|
|
11
|
+
'Let me work out the specifics for you.',
|
|
12
|
+
"I'll figure out the concrete steps.",
|
|
13
|
+
'Let me break this down into tasks.',
|
|
14
|
+
"I'll plan out the details.",
|
|
15
|
+
'Let me arrange the steps.',
|
|
16
|
+
"I'll prepare everything you need.",
|
|
17
|
+
];
|
|
18
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
19
|
+
}
|
|
8
20
|
export function createWelcomeDefinition(app) {
|
|
9
21
|
return {
|
|
10
22
|
id: randomUUID(),
|
|
@@ -47,7 +59,7 @@ export function createConfigDefinition(onFinished, onAborted) {
|
|
|
47
59
|
},
|
|
48
60
|
};
|
|
49
61
|
}
|
|
50
|
-
export function createCommandDefinition(command, service, onError, onComplete) {
|
|
62
|
+
export function createCommandDefinition(command, service, onError, onComplete, onAborted) {
|
|
51
63
|
return {
|
|
52
64
|
id: randomUUID(),
|
|
53
65
|
name: ComponentName.Command,
|
|
@@ -60,10 +72,11 @@ export function createCommandDefinition(command, service, onError, onComplete) {
|
|
|
60
72
|
service,
|
|
61
73
|
onError,
|
|
62
74
|
onComplete,
|
|
75
|
+
onAborted,
|
|
63
76
|
},
|
|
64
77
|
};
|
|
65
78
|
}
|
|
66
|
-
export function createPlanDefinition(message, tasks, onSelectionConfirmed) {
|
|
79
|
+
export function createPlanDefinition(message, tasks, onAborted, onSelectionConfirmed) {
|
|
67
80
|
return {
|
|
68
81
|
id: randomUUID(),
|
|
69
82
|
name: ComponentName.Plan,
|
|
@@ -77,6 +90,7 @@ export function createPlanDefinition(message, tasks, onSelectionConfirmed) {
|
|
|
77
90
|
message,
|
|
78
91
|
tasks,
|
|
79
92
|
onSelectionConfirmed,
|
|
93
|
+
onAborted,
|
|
80
94
|
},
|
|
81
95
|
};
|
|
82
96
|
}
|
|
@@ -99,6 +113,29 @@ export function createMessage(text) {
|
|
|
99
113
|
},
|
|
100
114
|
};
|
|
101
115
|
}
|
|
116
|
+
export function createRefinement(text, onAborted) {
|
|
117
|
+
return {
|
|
118
|
+
id: randomUUID(),
|
|
119
|
+
name: ComponentName.Refinement,
|
|
120
|
+
state: { done: false },
|
|
121
|
+
props: {
|
|
122
|
+
text,
|
|
123
|
+
onAborted,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function createConfirmDefinition(onConfirmed, onCancelled) {
|
|
128
|
+
return {
|
|
129
|
+
id: randomUUID(),
|
|
130
|
+
name: ComponentName.Confirm,
|
|
131
|
+
state: { done: false },
|
|
132
|
+
props: {
|
|
133
|
+
message: getConfirmationMessage(),
|
|
134
|
+
onConfirmed,
|
|
135
|
+
onCancelled,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
102
139
|
export function isStateless(component) {
|
|
103
140
|
return !('state' in component);
|
|
104
141
|
}
|
package/dist/services/config.js
CHANGED
|
@@ -50,6 +50,14 @@ function validateConfig(parsed) {
|
|
|
50
50
|
if (model && typeof model === 'string' && isValidAnthropicModel(model)) {
|
|
51
51
|
validatedConfig.anthropic.model = model;
|
|
52
52
|
}
|
|
53
|
+
// Optional settings section
|
|
54
|
+
if (config.settings && typeof config.settings === 'object') {
|
|
55
|
+
const settings = config.settings;
|
|
56
|
+
validatedConfig.settings = {};
|
|
57
|
+
if ('debug' in settings && typeof settings.debug === 'boolean') {
|
|
58
|
+
validatedConfig.settings.debug = settings.debug;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
53
61
|
return validatedConfig;
|
|
54
62
|
}
|
|
55
63
|
export function loadConfig() {
|
|
@@ -116,6 +124,18 @@ export function saveConfig(section, config) {
|
|
|
116
124
|
export function saveAnthropicConfig(config) {
|
|
117
125
|
saveConfig('anthropic', config);
|
|
118
126
|
}
|
|
127
|
+
export function saveDebugSetting(debug) {
|
|
128
|
+
saveConfig('settings', { debug });
|
|
129
|
+
}
|
|
130
|
+
export function loadDebugSetting() {
|
|
131
|
+
try {
|
|
132
|
+
const config = loadConfig();
|
|
133
|
+
return config.settings?.debug ?? false;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
119
139
|
/**
|
|
120
140
|
* Returns a message requesting initial setup.
|
|
121
141
|
* Provides natural language variations that sound like a professional concierge
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a natural language confirmation message for plan execution.
|
|
3
|
+
* Randomly selects from variations to sound less robotic.
|
|
4
|
+
*/
|
|
5
|
+
export function getConfirmationMessage() {
|
|
6
|
+
const messages = [
|
|
7
|
+
'Should I execute this plan?',
|
|
8
|
+
'Do you want me to proceed with these tasks?',
|
|
9
|
+
'Ready to execute?',
|
|
10
|
+
'Shall I execute this plan?',
|
|
11
|
+
'Would you like me to run these tasks?',
|
|
12
|
+
'Execute this plan?',
|
|
13
|
+
];
|
|
14
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
15
|
+
}
|
package/dist/tools/plan.tool.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const planTool = {
|
|
2
2
|
name: 'plan',
|
|
3
|
-
description: 'Plan and structure tasks from a user command. Break down the request into clear, actionable steps with type information and parameters.',
|
|
3
|
+
description: 'Plan and structure tasks from a user command. Break down the request into clear, actionable steps with type information and parameters. When refining previously selected tasks, the input will be formatted as lowercase actions with types in brackets, e.g., "install the python development environment (type: execute), explain how virtual environments work (type: answer)".',
|
|
4
4
|
input_schema: {
|
|
5
5
|
type: 'object',
|
|
6
6
|
properties: {
|
package/dist/types/components.js
CHANGED
|
@@ -1,28 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
(function (ComponentName) {
|
|
3
|
-
ComponentName["Welcome"] = "welcome";
|
|
4
|
-
ComponentName["Config"] = "config";
|
|
5
|
-
ComponentName["Feedback"] = "feedback";
|
|
6
|
-
ComponentName["Message"] = "message";
|
|
7
|
-
ComponentName["Plan"] = "plan";
|
|
8
|
-
ComponentName["Command"] = "command";
|
|
9
|
-
})(ComponentName || (ComponentName = {}));
|
|
10
|
-
export var TaskType;
|
|
11
|
-
(function (TaskType) {
|
|
12
|
-
TaskType["Config"] = "config";
|
|
13
|
-
TaskType["Plan"] = "plan";
|
|
14
|
-
TaskType["Execute"] = "execute";
|
|
15
|
-
TaskType["Answer"] = "answer";
|
|
16
|
-
TaskType["Report"] = "report";
|
|
17
|
-
TaskType["Define"] = "define";
|
|
18
|
-
TaskType["Ignore"] = "ignore";
|
|
19
|
-
TaskType["Select"] = "select";
|
|
20
|
-
TaskType["Discard"] = "discard";
|
|
21
|
-
})(TaskType || (TaskType = {}));
|
|
22
|
-
export var FeedbackType;
|
|
23
|
-
(function (FeedbackType) {
|
|
24
|
-
FeedbackType["Info"] = "info";
|
|
25
|
-
FeedbackType["Succeeded"] = "succeeded";
|
|
26
|
-
FeedbackType["Aborted"] = "aborted";
|
|
27
|
-
FeedbackType["Failed"] = "failed";
|
|
28
|
-
})(FeedbackType || (FeedbackType = {}));
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export var ComponentName;
|
|
2
|
+
(function (ComponentName) {
|
|
3
|
+
ComponentName["Welcome"] = "welcome";
|
|
4
|
+
ComponentName["Config"] = "config";
|
|
5
|
+
ComponentName["Message"] = "message";
|
|
6
|
+
ComponentName["Command"] = "command";
|
|
7
|
+
ComponentName["Plan"] = "plan";
|
|
8
|
+
ComponentName["Refinement"] = "refinement";
|
|
9
|
+
ComponentName["Feedback"] = "feedback";
|
|
10
|
+
ComponentName["Confirm"] = "confirm";
|
|
11
|
+
})(ComponentName || (ComponentName = {}));
|
|
12
|
+
export var TaskType;
|
|
13
|
+
(function (TaskType) {
|
|
14
|
+
TaskType["Config"] = "config";
|
|
15
|
+
TaskType["Plan"] = "plan";
|
|
16
|
+
TaskType["Execute"] = "execute";
|
|
17
|
+
TaskType["Answer"] = "answer";
|
|
18
|
+
TaskType["Report"] = "report";
|
|
19
|
+
TaskType["Define"] = "define";
|
|
20
|
+
TaskType["Ignore"] = "ignore";
|
|
21
|
+
TaskType["Select"] = "select";
|
|
22
|
+
TaskType["Discard"] = "discard";
|
|
23
|
+
})(TaskType || (TaskType = {}));
|
|
24
|
+
export var FeedbackType;
|
|
25
|
+
(function (FeedbackType) {
|
|
26
|
+
FeedbackType["Info"] = "info";
|
|
27
|
+
FeedbackType["Succeeded"] = "succeeded";
|
|
28
|
+
FeedbackType["Aborted"] = "aborted";
|
|
29
|
+
FeedbackType["Failed"] = "failed";
|
|
30
|
+
})(FeedbackType || (FeedbackType = {}));
|
package/dist/ui/Column.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box } from 'ink';
|
|
3
3
|
import { Component } from './Component.js';
|
|
4
|
-
export const Column = ({ items }) => {
|
|
5
|
-
return (_jsx(Box, { marginTop: 1, marginBottom: 1, marginLeft: 1, flexDirection: "column", gap: 1, children: items.map((item) => (_jsx(Box, { children: _jsx(Component, { def: item }) }, item.id))) }));
|
|
4
|
+
export const Column = ({ items, debug }) => {
|
|
5
|
+
return (_jsx(Box, { marginTop: 1, marginBottom: 1, marginLeft: 1, flexDirection: "column", gap: 1, children: items.map((item) => (_jsx(Box, { children: _jsx(Component, { def: item, debug: debug }) }, item.id))) }));
|
|
6
6
|
};
|
package/dist/ui/Command.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
4
|
import { Spinner } from './Spinner.js';
|
|
5
5
|
const MIN_PROCESSING_TIME = 1000; // purely for visual effect
|
|
6
|
-
export function Command({ command, state, service, children, onError, onComplete, }) {
|
|
6
|
+
export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
|
|
7
7
|
const done = state?.done ?? false;
|
|
8
8
|
const [error, setError] = useState(null);
|
|
9
9
|
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
10
|
+
useInput((input, key) => {
|
|
11
|
+
if (key.escape && isLoading && !done) {
|
|
12
|
+
setIsLoading(false);
|
|
13
|
+
onAborted();
|
|
14
|
+
}
|
|
15
|
+
}, { isActive: isLoading && !done });
|
|
10
16
|
useEffect(() => {
|
|
11
17
|
// Skip processing if done (showing historical/final state)
|
|
12
18
|
if (done) {
|
package/dist/ui/Component.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { ComponentName } from '../types/
|
|
2
|
+
import { ComponentName } from '../types/types.js';
|
|
3
3
|
import { Command } from './Command.js';
|
|
4
|
+
import { Confirm } from './Confirm.js';
|
|
4
5
|
import { Config } from './Config.js';
|
|
5
6
|
import { Feedback } from './Feedback.js';
|
|
6
7
|
import { Message } from './Message.js';
|
|
7
8
|
import { Plan } from './Plan.js';
|
|
9
|
+
import { Refinement } from './Refinement.js';
|
|
8
10
|
import { Welcome } from './Welcome.js';
|
|
9
|
-
export function Component({ def }) {
|
|
11
|
+
export function Component({ def, debug }) {
|
|
10
12
|
switch (def.name) {
|
|
11
13
|
case ComponentName.Welcome:
|
|
12
14
|
return _jsx(Welcome, { ...def.props });
|
|
@@ -21,10 +23,20 @@ export function Component({ def }) {
|
|
|
21
23
|
return _jsx(Command, { ...props, state: state });
|
|
22
24
|
}
|
|
23
25
|
case ComponentName.Plan:
|
|
24
|
-
return _jsx(Plan, { ...def.props });
|
|
26
|
+
return _jsx(Plan, { ...def.props, debug: debug });
|
|
25
27
|
case ComponentName.Feedback:
|
|
26
28
|
return _jsx(Feedback, { ...def.props });
|
|
27
29
|
case ComponentName.Message:
|
|
28
30
|
return _jsx(Message, { ...def.props });
|
|
31
|
+
case ComponentName.Refinement: {
|
|
32
|
+
const props = def.props;
|
|
33
|
+
const state = def.state;
|
|
34
|
+
return _jsx(Refinement, { ...props, state: state });
|
|
35
|
+
}
|
|
36
|
+
case ComponentName.Confirm: {
|
|
37
|
+
const props = def.props;
|
|
38
|
+
const state = def.state;
|
|
39
|
+
return _jsx(Confirm, { ...props, state: state });
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
}
|
package/dist/ui/Config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { Box, Text,
|
|
3
|
+
import { Box, Text, useFocus, useInput } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
5
|
export var StepType;
|
|
6
6
|
(function (StepType) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
5
|
+
const done = state?.done ?? false;
|
|
6
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0); // 0 = Yes, 1 = No
|
|
7
|
+
useInput((input, key) => {
|
|
8
|
+
if (done)
|
|
9
|
+
return;
|
|
10
|
+
if (key.escape) {
|
|
11
|
+
// Escape: highlight "No" and cancel
|
|
12
|
+
setSelectedIndex(1);
|
|
13
|
+
onCancelled?.();
|
|
14
|
+
}
|
|
15
|
+
else if (key.tab) {
|
|
16
|
+
// Toggle between Yes (0) and No (1)
|
|
17
|
+
setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
|
|
18
|
+
}
|
|
19
|
+
else if (key.return) {
|
|
20
|
+
// Confirm selection
|
|
21
|
+
if (selectedIndex === 0) {
|
|
22
|
+
onConfirmed?.();
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
onCancelled?.();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}, { isActive: !done });
|
|
29
|
+
const options = [
|
|
30
|
+
{ label: 'Yes', value: 'yes', color: '#4a9a7a' }, // green (execute)
|
|
31
|
+
{ label: 'No', value: 'no', color: '#a85c3f' }, // dark orange (discard)
|
|
32
|
+
];
|
|
33
|
+
if (done) {
|
|
34
|
+
// When done, show both the message and user's choice in timeline
|
|
35
|
+
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] }) })] }));
|
|
36
|
+
}
|
|
37
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: "#5c8cbc", children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
38
|
+
const isSelected = index === selectedIndex;
|
|
39
|
+
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, bold: isSelected, children: option.label }) }, option.value));
|
|
40
|
+
}) })] })] }));
|
|
41
|
+
}
|
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 { FeedbackType } from '../types/
|
|
3
|
+
import { FeedbackType } from '../types/types.js';
|
|
4
4
|
function getSymbol(type) {
|
|
5
5
|
return {
|
|
6
6
|
[FeedbackType.Info]: 'ℹ',
|
package/dist/ui/Label.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
3
|
import { Separator } from './Separator.js';
|
|
4
|
-
export function Label({ description, descriptionColor, type, typeColor, }) {
|
|
5
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: descriptionColor, children: description }), _jsx(Separator, {}), _jsx(Text, { color: typeColor, children: type })] }));
|
|
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 })] }))] }));
|
|
6
6
|
}
|
package/dist/ui/List.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { Label } from './Label.js';
|
|
4
|
-
export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, }) => {
|
|
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) => {
|
|
7
7
|
// At level 0, track which parent is active for child highlighting
|
|
@@ -16,6 +16,11 @@ export const List = ({ items, level = 0, highlightedIndex = null, highlightedPar
|
|
|
16
16
|
const typeColor = isHighlighted && item.type.highlightedColor
|
|
17
17
|
? item.type.highlightedColor
|
|
18
18
|
: item.type.color;
|
|
19
|
-
|
|
19
|
+
// Use highlighted type color for arrow markers when highlighted
|
|
20
|
+
const markerColor = item.markerColor ||
|
|
21
|
+
(isHighlighted && item.type.highlightedColor
|
|
22
|
+
? item.type.highlightedColor
|
|
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));
|
|
20
25
|
}) }));
|
|
21
26
|
};
|
package/dist/ui/Main.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { useInput } from 'ink';
|
|
4
|
+
import { ComponentName, FeedbackType, TaskType, } from '../types/types.js';
|
|
4
5
|
import { createAnthropicService, } from '../services/anthropic.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createPlanDefinition, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
|
|
6
|
+
import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, loadDebugSetting, saveAnthropicConfig, saveDebugSetting, } from '../services/config.js';
|
|
7
|
+
import { createCommandDefinition, createConfirmDefinition, createConfigDefinition, createFeedback, createMessage, createRefinement, createPlanDefinition, createWelcomeDefinition, getRefiningMessage, isStateless, markAsDone, } from '../services/components.js';
|
|
8
8
|
import { exitApp } from '../services/process.js';
|
|
9
9
|
import { Column } from './Column.js';
|
|
10
10
|
export const Main = ({ app, command }) => {
|
|
@@ -18,6 +18,18 @@ export const Main = ({ app, command }) => {
|
|
|
18
18
|
});
|
|
19
19
|
const [timeline, setTimeline] = React.useState([]);
|
|
20
20
|
const [queue, setQueue] = React.useState([]);
|
|
21
|
+
const [isDebug, setIsDebug] = React.useState(() => loadDebugSetting());
|
|
22
|
+
// Top-level Shift+Tab handler for debug mode toggle
|
|
23
|
+
// Child components must ignore Shift+Tab to prevent conflicts
|
|
24
|
+
useInput((input, key) => {
|
|
25
|
+
if (key.shift && key.tab) {
|
|
26
|
+
setIsDebug((prev) => {
|
|
27
|
+
const newValue = !prev;
|
|
28
|
+
saveDebugSetting(newValue);
|
|
29
|
+
return newValue;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, { isActive: true });
|
|
21
33
|
const addToTimeline = React.useCallback((...items) => {
|
|
22
34
|
setTimeline((timeline) => [...timeline, ...items]);
|
|
23
35
|
}, []);
|
|
@@ -46,20 +58,106 @@ export const Main = ({ app, command }) => {
|
|
|
46
58
|
return [];
|
|
47
59
|
});
|
|
48
60
|
}, [addToTimeline]);
|
|
49
|
-
const
|
|
61
|
+
const handleAborted = React.useCallback((operationName) => {
|
|
50
62
|
setQueue((currentQueue) => {
|
|
51
63
|
if (currentQueue.length === 0)
|
|
52
64
|
return currentQueue;
|
|
53
65
|
const [first] = currentQueue;
|
|
54
|
-
if (first
|
|
55
|
-
|
|
66
|
+
if (!isStateless(first)) {
|
|
67
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, `${operationName} was aborted by user`));
|
|
68
|
+
}
|
|
69
|
+
exitApp(0);
|
|
70
|
+
return [];
|
|
71
|
+
});
|
|
72
|
+
}, [addToTimeline]);
|
|
73
|
+
const handleConfigAborted = React.useCallback(() => {
|
|
74
|
+
handleAborted('Configuration');
|
|
75
|
+
}, [handleAborted]);
|
|
76
|
+
const handlePlanAborted = React.useCallback(() => {
|
|
77
|
+
handleAborted('Task selection');
|
|
78
|
+
}, [handleAborted]);
|
|
79
|
+
const handleCommandAborted = React.useCallback(() => {
|
|
80
|
+
handleAborted('Request');
|
|
81
|
+
}, [handleAborted]);
|
|
82
|
+
const handleRefinementAborted = React.useCallback(() => {
|
|
83
|
+
handleAborted('Plan refinement');
|
|
84
|
+
}, [handleAborted]);
|
|
85
|
+
const handleExecutionConfirmed = React.useCallback(() => {
|
|
86
|
+
setQueue((currentQueue) => {
|
|
87
|
+
if (currentQueue.length === 0)
|
|
88
|
+
return currentQueue;
|
|
89
|
+
const [first] = currentQueue;
|
|
90
|
+
if (first.name === ComponentName.Confirm) {
|
|
56
91
|
addToTimeline(markAsDone(first));
|
|
57
92
|
}
|
|
58
|
-
// Exit after selection is confirmed
|
|
59
93
|
exitApp(0);
|
|
60
94
|
return [];
|
|
61
95
|
});
|
|
62
96
|
}, [addToTimeline]);
|
|
97
|
+
const handleExecutionCancelled = React.useCallback(() => {
|
|
98
|
+
setQueue((currentQueue) => {
|
|
99
|
+
if (currentQueue.length === 0)
|
|
100
|
+
return currentQueue;
|
|
101
|
+
const [first] = currentQueue;
|
|
102
|
+
if (first.name === ComponentName.Confirm) {
|
|
103
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, 'Execution cancelled'));
|
|
104
|
+
}
|
|
105
|
+
exitApp(0);
|
|
106
|
+
return [];
|
|
107
|
+
});
|
|
108
|
+
}, [addToTimeline]);
|
|
109
|
+
const handlePlanSelectionConfirmed = React.useCallback(async (selectedTasks) => {
|
|
110
|
+
// Mark current plan as done and add refinement to queue
|
|
111
|
+
let refinementDef = null;
|
|
112
|
+
refinementDef = createRefinement(getRefiningMessage(), handleRefinementAborted);
|
|
113
|
+
setQueue((currentQueue) => {
|
|
114
|
+
if (currentQueue.length === 0)
|
|
115
|
+
return currentQueue;
|
|
116
|
+
const [first] = currentQueue;
|
|
117
|
+
if (first.name === ComponentName.Plan) {
|
|
118
|
+
addToTimeline(markAsDone(first));
|
|
119
|
+
}
|
|
120
|
+
// Add refinement to queue so it becomes the active component
|
|
121
|
+
return [refinementDef];
|
|
122
|
+
});
|
|
123
|
+
// Process refined command in background
|
|
124
|
+
try {
|
|
125
|
+
const refinedCommand = selectedTasks
|
|
126
|
+
.map((task) => {
|
|
127
|
+
const action = task.action.toLowerCase().replace(/,/g, ' -');
|
|
128
|
+
const type = task.type || 'execute';
|
|
129
|
+
return `${action} (type: ${type})`;
|
|
130
|
+
})
|
|
131
|
+
.join(', ');
|
|
132
|
+
const result = await service.processWithTool(refinedCommand, 'plan');
|
|
133
|
+
// Mark refinement as done and move to timeline
|
|
134
|
+
setQueue((currentQueue) => {
|
|
135
|
+
if (currentQueue.length > 0 &&
|
|
136
|
+
currentQueue[0].id === refinementDef.id) {
|
|
137
|
+
addToTimeline(markAsDone(currentQueue[0]));
|
|
138
|
+
}
|
|
139
|
+
return [];
|
|
140
|
+
});
|
|
141
|
+
// Show final execution plan with confirmation
|
|
142
|
+
const planDefinition = createPlanDefinition(result.message, result.tasks, handlePlanAborted, undefined);
|
|
143
|
+
const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
|
|
144
|
+
addToTimeline(planDefinition);
|
|
145
|
+
setQueue([confirmDefinition]);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
149
|
+
// Mark refinement as done and move to timeline before showing error
|
|
150
|
+
setQueue((currentQueue) => {
|
|
151
|
+
if (currentQueue.length > 0 &&
|
|
152
|
+
currentQueue[0].id === refinementDef.id) {
|
|
153
|
+
addToTimeline(markAsDone(currentQueue[0]));
|
|
154
|
+
}
|
|
155
|
+
return [];
|
|
156
|
+
});
|
|
157
|
+
addToTimeline(createFeedback(FeedbackType.Failed, 'Unexpected error occurred:', errorMessage));
|
|
158
|
+
exitApp(1);
|
|
159
|
+
}
|
|
160
|
+
}, [addToTimeline, service, handleRefinementAborted]);
|
|
63
161
|
const handleCommandComplete = React.useCallback((message, tasks) => {
|
|
64
162
|
setQueue((currentQueue) => {
|
|
65
163
|
if (currentQueue.length === 0)
|
|
@@ -68,23 +166,28 @@ export const Main = ({ app, command }) => {
|
|
|
68
166
|
// Check if tasks contain a Define task that requires user interaction
|
|
69
167
|
const hasDefineTask = tasks.some((task) => task.type === TaskType.Define);
|
|
70
168
|
if (first.name === ComponentName.Command) {
|
|
71
|
-
const planDefinition = createPlanDefinition(message, tasks, hasDefineTask ? handlePlanSelectionConfirmed : undefined);
|
|
169
|
+
const planDefinition = createPlanDefinition(message, tasks, handlePlanAborted, hasDefineTask ? handlePlanSelectionConfirmed : undefined);
|
|
72
170
|
if (hasDefineTask) {
|
|
73
171
|
// Don't exit - keep the plan in the queue for interaction
|
|
74
172
|
addToTimeline(markAsDone(first));
|
|
75
173
|
return [planDefinition];
|
|
76
174
|
}
|
|
77
175
|
else {
|
|
78
|
-
// No define task -
|
|
176
|
+
// No define task - show plan and confirmation
|
|
177
|
+
const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
|
|
79
178
|
addToTimeline(markAsDone(first), planDefinition);
|
|
80
|
-
|
|
81
|
-
return [];
|
|
179
|
+
return [confirmDefinition];
|
|
82
180
|
}
|
|
83
181
|
}
|
|
84
182
|
exitApp(0);
|
|
85
183
|
return [];
|
|
86
184
|
});
|
|
87
|
-
}, [
|
|
185
|
+
}, [
|
|
186
|
+
addToTimeline,
|
|
187
|
+
handlePlanSelectionConfirmed,
|
|
188
|
+
handleExecutionConfirmed,
|
|
189
|
+
handleExecutionCancelled,
|
|
190
|
+
]);
|
|
88
191
|
const handleConfigFinished = React.useCallback((config) => {
|
|
89
192
|
const anthropicConfig = config;
|
|
90
193
|
saveAnthropicConfig(anthropicConfig);
|
|
@@ -102,7 +205,7 @@ export const Main = ({ app, command }) => {
|
|
|
102
205
|
if (command) {
|
|
103
206
|
return [
|
|
104
207
|
...rest,
|
|
105
|
-
createCommandDefinition(command, newService, handleCommandError, handleCommandComplete),
|
|
208
|
+
createCommandDefinition(command, newService, handleCommandError, handleCommandComplete, handleCommandAborted),
|
|
106
209
|
];
|
|
107
210
|
}
|
|
108
211
|
// No command - exit after showing completion message
|
|
@@ -110,25 +213,13 @@ export const Main = ({ app, command }) => {
|
|
|
110
213
|
return rest;
|
|
111
214
|
});
|
|
112
215
|
}, [addToTimeline, command, handleCommandError, handleCommandComplete]);
|
|
113
|
-
const handleConfigAborted = React.useCallback(() => {
|
|
114
|
-
setQueue((currentQueue) => {
|
|
115
|
-
if (currentQueue.length === 0)
|
|
116
|
-
return currentQueue;
|
|
117
|
-
const [first] = currentQueue;
|
|
118
|
-
if (first.name === ComponentName.Config) {
|
|
119
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, 'Configuration aborted by user'));
|
|
120
|
-
}
|
|
121
|
-
exitApp(0);
|
|
122
|
-
return [];
|
|
123
|
-
});
|
|
124
|
-
}, [addToTimeline]);
|
|
125
216
|
// Initialize queue on mount
|
|
126
217
|
React.useEffect(() => {
|
|
127
218
|
const hasConfig = !!service;
|
|
128
219
|
if (command && hasConfig) {
|
|
129
220
|
// With command + valid config: [Command]
|
|
130
221
|
setQueue([
|
|
131
|
-
createCommandDefinition(command, service, handleCommandError, handleCommandComplete),
|
|
222
|
+
createCommandDefinition(command, service, handleCommandError, handleCommandComplete, handleCommandAborted),
|
|
132
223
|
]);
|
|
133
224
|
}
|
|
134
225
|
else if (command && !hasConfig) {
|
|
@@ -155,7 +246,13 @@ export const Main = ({ app, command }) => {
|
|
|
155
246
|
React.useEffect(() => {
|
|
156
247
|
processNextInQueue();
|
|
157
248
|
}, [queue, processNextInQueue]);
|
|
249
|
+
// Exit when queue is empty and timeline has content (all stateless components done)
|
|
250
|
+
React.useEffect(() => {
|
|
251
|
+
if (queue.length === 0 && timeline.length > 0) {
|
|
252
|
+
exitApp(0);
|
|
253
|
+
}
|
|
254
|
+
}, [queue, timeline]);
|
|
158
255
|
const current = queue.length > 0 ? queue[0] : null;
|
|
159
256
|
const items = [...timeline, ...(current ? [current] : [])];
|
|
160
|
-
return _jsx(Column, { items: items });
|
|
257
|
+
return _jsx(Column, { items: items, debug: isDebug });
|
|
161
258
|
};
|
package/dist/ui/Message.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Text } from 'ink';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
3
|
export const Message = ({ text }) => {
|
|
4
|
-
return _jsx(Text, { children: text });
|
|
4
|
+
return (_jsx(Box, { children: _jsx(Text, { children: text }) }));
|
|
5
5
|
};
|
package/dist/ui/Plan.js
CHANGED
|
@@ -1,7 +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, useInput } from 'ink';
|
|
4
|
-
import { TaskType } from '../types/
|
|
4
|
+
import { TaskType } from '../types/types.js';
|
|
5
5
|
import { Label } from './Label.js';
|
|
6
6
|
import { List } from './List.js';
|
|
7
7
|
const ColorPalette = {
|
|
@@ -54,6 +54,7 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
54
54
|
// Mark define tasks with right arrow when no selection has been made
|
|
55
55
|
if (isDefineTaskWithoutSelection) {
|
|
56
56
|
item.marker = ' → ';
|
|
57
|
+
item.markerColor = ColorPalette[TaskType.Plan].type;
|
|
57
58
|
}
|
|
58
59
|
// Add children for Define tasks with options
|
|
59
60
|
if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
|
|
@@ -82,7 +83,7 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
82
83
|
}
|
|
83
84
|
return item;
|
|
84
85
|
}
|
|
85
|
-
export function Plan({ message, tasks, state, onSelectionConfirmed, }) {
|
|
86
|
+
export function Plan({ message, tasks, state, debug = false, onSelectionConfirmed, onAborted, }) {
|
|
86
87
|
const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
|
|
87
88
|
const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(state?.currentDefineGroupIndex ?? 0);
|
|
88
89
|
const [completedSelections, setCompletedSelections] = useState(state?.completedSelections ?? []);
|
|
@@ -103,6 +104,10 @@ export function Plan({ message, tasks, state, onSelectionConfirmed, }) {
|
|
|
103
104
|
if (isDone || !defineTask) {
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
107
|
+
if (key.escape) {
|
|
108
|
+
onAborted();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
106
111
|
if (key.downArrow) {
|
|
107
112
|
setHighlightedIndex((prev) => {
|
|
108
113
|
if (prev === null) {
|
|
@@ -136,14 +141,27 @@ export function Plan({ message, tasks, state, onSelectionConfirmed, }) {
|
|
|
136
141
|
if (state) {
|
|
137
142
|
state.done = true;
|
|
138
143
|
}
|
|
139
|
-
//
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
// Build refined task list with only selected options (no discarded or ignored ones)
|
|
145
|
+
const refinedTasks = [];
|
|
146
|
+
tasks.forEach((task, idx) => {
|
|
147
|
+
const defineGroupIndex = defineTaskIndices.indexOf(idx);
|
|
148
|
+
if (defineGroupIndex !== -1 &&
|
|
149
|
+
Array.isArray(task.params?.options)) {
|
|
150
|
+
// This is a Define task - only include the selected option
|
|
151
|
+
const options = task.params.options;
|
|
152
|
+
const selectedIndex = newCompletedSelections[defineGroupIndex];
|
|
153
|
+
refinedTasks.push({
|
|
154
|
+
action: String(options[selectedIndex]),
|
|
155
|
+
type: TaskType.Execute,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else if (task.type !== TaskType.Ignore &&
|
|
159
|
+
task.type !== TaskType.Discard) {
|
|
160
|
+
// Regular task - keep as is, but skip Ignore and Discard tasks
|
|
161
|
+
refinedTasks.push(task);
|
|
143
162
|
}
|
|
144
|
-
return task;
|
|
145
163
|
});
|
|
146
|
-
onSelectionConfirmed?.(
|
|
164
|
+
onSelectionConfirmed?.(refinedTasks);
|
|
147
165
|
}
|
|
148
166
|
}
|
|
149
167
|
}, { isActive: !isDone && defineTask !== null });
|
|
@@ -184,11 +202,12 @@ export function Plan({ message, tasks, state, onSelectionConfirmed, }) {
|
|
|
184
202
|
}
|
|
185
203
|
}
|
|
186
204
|
}
|
|
187
|
-
// Show arrow on current active define task when no child is highlighted
|
|
205
|
+
// Show arrow on current active define task when no child is highlighted and not done
|
|
188
206
|
const isDefineWithoutSelection = isDefineTask &&
|
|
189
207
|
defineGroupIndex === currentDefineGroupIndex &&
|
|
190
|
-
highlightedIndex === null
|
|
208
|
+
highlightedIndex === null &&
|
|
209
|
+
!isDone;
|
|
191
210
|
return taskToListItem(task, childIndex, isDefineWithoutSelection);
|
|
192
211
|
});
|
|
193
|
-
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, children: _jsx(Label, { description: message, descriptionColor: ColorPalette[TaskType.Plan].description, type: TaskType.Plan, typeColor: ColorPalette[TaskType.Plan].type }) })), _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex })] }));
|
|
212
|
+
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, children: _jsx(Label, { description: message, descriptionColor: ColorPalette[TaskType.Plan].description, type: TaskType.Plan, typeColor: ColorPalette[TaskType.Plan].type, showType: debug }) })), _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug })] }));
|
|
194
213
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, useInput } from 'ink';
|
|
3
|
+
import { Message } from './Message.js';
|
|
4
|
+
import { Spinner } from './Spinner.js';
|
|
5
|
+
export const Refinement = ({ text, state, onAborted }) => {
|
|
6
|
+
const isDone = state?.done ?? false;
|
|
7
|
+
useInput((input, key) => {
|
|
8
|
+
if (key.escape && !isDone) {
|
|
9
|
+
onAborted();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
}, { isActive: !isDone });
|
|
13
|
+
return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text }), !isDone && _jsx(Spinner, {})] }));
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Your personal command-line concierge. Ask politely, and it gets things done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"clean": "rm -rf dist",
|
|
15
|
-
"
|
|
15
|
+
"typecheck": "tsc --project tsconfig.eslint.json",
|
|
16
|
+
"build": "npm run typecheck && npm run clean && tsc && chmod +x dist/index.js && mkdir -p dist/config && cp src/config/*.md dist/config/",
|
|
16
17
|
"dev": "npm run build && tsc --watch",
|
|
17
18
|
"prepare": "husky",
|
|
18
19
|
"prepublishOnly": "npm run check",
|