prompt-language-shell 0.3.0 → 0.3.2
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/index.js +2 -25
- package/dist/services/components.js +90 -0
- package/dist/services/config.js +63 -12
- package/dist/services/process.js +6 -0
- package/dist/types/components.js +10 -0
- package/dist/ui/Column.js +1 -1
- package/dist/ui/Command.js +2 -72
- package/dist/ui/Component.js +12 -5
- package/dist/ui/Config.js +169 -10
- package/dist/ui/Feedback.js +15 -4
- package/dist/ui/Main.js +118 -117
- package/dist/ui/Message.js +5 -0
- package/dist/ui/Plan.js +67 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -4,8 +4,6 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { render } from 'ink';
|
|
7
|
-
import { hasValidConfig, loadConfig, saveAnthropicConfig, } from './services/config.js';
|
|
8
|
-
import { createAnthropicService } from './services/anthropic.js';
|
|
9
7
|
import { Main } from './ui/Main.js';
|
|
10
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
9
|
const __dirname = dirname(__filename);
|
|
@@ -26,26 +24,5 @@ const app = {
|
|
|
26
24
|
// Get command from command-line arguments
|
|
27
25
|
const args = process.argv.slice(2);
|
|
28
26
|
const command = args.join(' ').trim() || null;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (hasValidConfig()) {
|
|
32
|
-
const config = loadConfig();
|
|
33
|
-
const service = createAnthropicService(config.anthropic);
|
|
34
|
-
render(_jsx(Main, { app: app, command: command, service: service, isReady: true }));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
// Setup: config doesn't exist or is invalid
|
|
38
|
-
const { waitUntilExit, unmount } = render(_jsx(Main, { app: app, command: command, isReady: false, onConfigured: (config) => {
|
|
39
|
-
saveAnthropicConfig(config);
|
|
40
|
-
if (command) {
|
|
41
|
-
return createAnthropicService(config);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// No command - exit after showing completion message
|
|
45
|
-
setTimeout(() => unmount(), 100);
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
} }));
|
|
49
|
-
await waitUntilExit();
|
|
50
|
-
}
|
|
51
|
-
runApp();
|
|
27
|
+
// Render application
|
|
28
|
+
render(_jsx(Main, { app: app, command: command }));
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ComponentName, } from '../types/components.js';
|
|
2
|
+
import { StepType } from '../ui/Config.js';
|
|
3
|
+
import { AnthropicModel, isValidAnthropicApiKey, isValidAnthropicModel, } from './config.js';
|
|
4
|
+
export function markAsDone(component) {
|
|
5
|
+
return { ...component, state: { ...component.state, done: true } };
|
|
6
|
+
}
|
|
7
|
+
export function createWelcomeDefinition(app) {
|
|
8
|
+
return {
|
|
9
|
+
name: ComponentName.Welcome,
|
|
10
|
+
props: { app },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function createConfigSteps() {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
description: 'Anthropic API key',
|
|
17
|
+
key: 'key',
|
|
18
|
+
type: StepType.Text,
|
|
19
|
+
value: null,
|
|
20
|
+
validate: isValidAnthropicApiKey,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
description: 'Model',
|
|
24
|
+
key: 'model',
|
|
25
|
+
type: StepType.Selection,
|
|
26
|
+
options: [
|
|
27
|
+
{ label: 'Haiku 4.5', value: AnthropicModel.Haiku },
|
|
28
|
+
{ label: 'Sonnet 4.5', value: AnthropicModel.Sonnet },
|
|
29
|
+
{ label: 'Opus 4.1', value: AnthropicModel.Opus },
|
|
30
|
+
],
|
|
31
|
+
defaultIndex: 0,
|
|
32
|
+
validate: isValidAnthropicModel,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
export function createConfigDefinition(onFinished, onAborted) {
|
|
37
|
+
return {
|
|
38
|
+
name: ComponentName.Config,
|
|
39
|
+
state: { done: false },
|
|
40
|
+
props: {
|
|
41
|
+
steps: createConfigSteps(),
|
|
42
|
+
onFinished,
|
|
43
|
+
onAborted,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function createCommandDefinition(command, service, onError, onComplete) {
|
|
48
|
+
return {
|
|
49
|
+
name: ComponentName.Command,
|
|
50
|
+
state: {
|
|
51
|
+
done: false,
|
|
52
|
+
isLoading: true,
|
|
53
|
+
},
|
|
54
|
+
props: {
|
|
55
|
+
command,
|
|
56
|
+
service,
|
|
57
|
+
onError,
|
|
58
|
+
onComplete,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function createPlanDefinition(message, tasks) {
|
|
63
|
+
return {
|
|
64
|
+
name: ComponentName.Plan,
|
|
65
|
+
props: {
|
|
66
|
+
message,
|
|
67
|
+
tasks,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function createFeedback(type, ...messages) {
|
|
72
|
+
return {
|
|
73
|
+
name: ComponentName.Feedback,
|
|
74
|
+
props: {
|
|
75
|
+
type,
|
|
76
|
+
message: messages.join('\n\n'),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function createMessage(text) {
|
|
81
|
+
return {
|
|
82
|
+
name: ComponentName.Message,
|
|
83
|
+
props: {
|
|
84
|
+
text,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function isStateless(component) {
|
|
89
|
+
return !('state' in component);
|
|
90
|
+
}
|
package/dist/services/config.js
CHANGED
|
@@ -2,6 +2,13 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
|
+
export var AnthropicModel;
|
|
6
|
+
(function (AnthropicModel) {
|
|
7
|
+
AnthropicModel["Sonnet"] = "claude-sonnet-4-5";
|
|
8
|
+
AnthropicModel["Haiku"] = "claude-haiku-4-5";
|
|
9
|
+
AnthropicModel["Opus"] = "claude-opus-4-1";
|
|
10
|
+
})(AnthropicModel || (AnthropicModel = {}));
|
|
11
|
+
export const SUPPORTED_MODELS = Object.values(AnthropicModel);
|
|
5
12
|
export class ConfigError extends Error {
|
|
6
13
|
origin;
|
|
7
14
|
constructor(message, origin) {
|
|
@@ -10,7 +17,9 @@ export class ConfigError extends Error {
|
|
|
10
17
|
this.origin = origin;
|
|
11
18
|
}
|
|
12
19
|
}
|
|
13
|
-
|
|
20
|
+
function getConfigFile() {
|
|
21
|
+
return join(homedir(), '.plsrc');
|
|
22
|
+
}
|
|
14
23
|
function parseYamlConfig(content) {
|
|
15
24
|
try {
|
|
16
25
|
return YAML.parse(content);
|
|
@@ -37,30 +46,41 @@ function validateConfig(parsed) {
|
|
|
37
46
|
key,
|
|
38
47
|
},
|
|
39
48
|
};
|
|
40
|
-
// Optional model
|
|
41
|
-
if (model && typeof model === 'string') {
|
|
49
|
+
// Optional model - only set if valid
|
|
50
|
+
if (model && typeof model === 'string' && isValidAnthropicModel(model)) {
|
|
42
51
|
validatedConfig.anthropic.model = model;
|
|
43
52
|
}
|
|
44
53
|
return validatedConfig;
|
|
45
54
|
}
|
|
46
55
|
export function loadConfig() {
|
|
47
|
-
|
|
56
|
+
const configFile = getConfigFile();
|
|
57
|
+
if (!existsSync(configFile)) {
|
|
48
58
|
throw new ConfigError('Configuration not found');
|
|
49
59
|
}
|
|
50
|
-
const content = readFileSync(
|
|
60
|
+
const content = readFileSync(configFile, 'utf-8');
|
|
51
61
|
const parsed = parseYamlConfig(content);
|
|
52
62
|
return validateConfig(parsed);
|
|
53
63
|
}
|
|
54
64
|
export function getConfigPath() {
|
|
55
|
-
return
|
|
65
|
+
return getConfigFile();
|
|
56
66
|
}
|
|
57
67
|
export function configExists() {
|
|
58
|
-
return existsSync(
|
|
68
|
+
return existsSync(getConfigFile());
|
|
69
|
+
}
|
|
70
|
+
export function isValidAnthropicApiKey(key) {
|
|
71
|
+
// Anthropic API keys format: sk-ant-api03-XXXXX (108 chars total)
|
|
72
|
+
// - Prefix: sk-ant-api03- (13 chars)
|
|
73
|
+
// - Key body: 95 characters (uppercase, lowercase, digits, hyphens, underscores)
|
|
74
|
+
const apiKeyPattern = /^sk-ant-api03-[A-Za-z0-9_-]{95}$/;
|
|
75
|
+
return apiKeyPattern.test(key);
|
|
76
|
+
}
|
|
77
|
+
export function isValidAnthropicModel(model) {
|
|
78
|
+
return SUPPORTED_MODELS.includes(model);
|
|
59
79
|
}
|
|
60
|
-
export function
|
|
80
|
+
export function hasValidAnthropicKey() {
|
|
61
81
|
try {
|
|
62
82
|
const config = loadConfig();
|
|
63
|
-
return !!config.anthropic.key;
|
|
83
|
+
return (!!config.anthropic.key && isValidAnthropicApiKey(config.anthropic.key));
|
|
64
84
|
}
|
|
65
85
|
catch {
|
|
66
86
|
return false;
|
|
@@ -86,12 +106,43 @@ export function mergeConfig(existingContent, sectionName, newValues) {
|
|
|
86
106
|
return YAML.stringify(sortedConfig);
|
|
87
107
|
}
|
|
88
108
|
export function saveConfig(section, config) {
|
|
89
|
-
const
|
|
90
|
-
|
|
109
|
+
const configFile = getConfigFile();
|
|
110
|
+
const existingContent = existsSync(configFile)
|
|
111
|
+
? readFileSync(configFile, 'utf-8')
|
|
91
112
|
: '';
|
|
92
113
|
const newContent = mergeConfig(existingContent, section, config);
|
|
93
|
-
writeFileSync(
|
|
114
|
+
writeFileSync(configFile, newContent, 'utf-8');
|
|
94
115
|
}
|
|
95
116
|
export function saveAnthropicConfig(config) {
|
|
96
117
|
saveConfig('anthropic', config);
|
|
97
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Returns a message requesting initial setup.
|
|
121
|
+
* Provides natural language variations that sound like a professional concierge
|
|
122
|
+
* preparing to serve, avoiding technical jargon.
|
|
123
|
+
*
|
|
124
|
+
* @param forFutureUse - If true, indicates setup is for future requests rather than
|
|
125
|
+
* an immediate task
|
|
126
|
+
*/
|
|
127
|
+
export function getConfigurationRequiredMessage(forFutureUse = false) {
|
|
128
|
+
if (forFutureUse) {
|
|
129
|
+
const messages = [
|
|
130
|
+
"Before I can assist with your requests, let's get a few things ready.",
|
|
131
|
+
'Let me set up a few things so I can help you in the future.',
|
|
132
|
+
"I'll need to prepare a few things before I can assist you.",
|
|
133
|
+
"Let's get everything ready so I can help with your tasks.",
|
|
134
|
+
"I need to set up a few things first, then I'll be ready to assist.",
|
|
135
|
+
'Let me prepare everything so I can help you going forward.',
|
|
136
|
+
];
|
|
137
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
138
|
+
}
|
|
139
|
+
const messages = [
|
|
140
|
+
'Before I can help, let me get a few things ready.',
|
|
141
|
+
'I need to set up a few things first.',
|
|
142
|
+
'Let me prepare everything before we begin.',
|
|
143
|
+
'Just a moment while I get ready to assist you.',
|
|
144
|
+
"I'll need to get set up before I can help with that.",
|
|
145
|
+
'Let me get everything ready for you.',
|
|
146
|
+
];
|
|
147
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
148
|
+
}
|
package/dist/types/components.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export var ComponentName;
|
|
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 = {}));
|
|
1
10
|
export var TaskType;
|
|
2
11
|
(function (TaskType) {
|
|
3
12
|
TaskType["Config"] = "config";
|
|
@@ -11,6 +20,7 @@ export var TaskType;
|
|
|
11
20
|
})(TaskType || (TaskType = {}));
|
|
12
21
|
export var FeedbackType;
|
|
13
22
|
(function (FeedbackType) {
|
|
23
|
+
FeedbackType["Info"] = "info";
|
|
14
24
|
FeedbackType["Succeeded"] = "succeeded";
|
|
15
25
|
FeedbackType["Aborted"] = "aborted";
|
|
16
26
|
FeedbackType["Failed"] = "failed";
|
package/dist/ui/Column.js
CHANGED
|
@@ -2,5 +2,5 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box } from 'ink';
|
|
3
3
|
import { Component } from './Component.js';
|
|
4
4
|
export const Column = ({ items }) => {
|
|
5
|
-
return (_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", gap: 1, children: items.map((item, index) => (_jsx(Box, { children: _jsx(Component, { def: item }) }, index))) }));
|
|
5
|
+
return (_jsx(Box, { marginTop: 1, marginBottom: 1, marginLeft: 1, flexDirection: "column", gap: 1, children: items.map((item, index) => (_jsx(Box, { children: _jsx(Component, { def: item }) }, index))) }));
|
|
6
6
|
};
|
package/dist/ui/Command.js
CHANGED
|
@@ -1,80 +1,12 @@
|
|
|
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 } from 'ink';
|
|
4
|
-
import { TaskType } from '../types/components.js';
|
|
5
|
-
import { Label } from './Label.js';
|
|
6
|
-
import { List } from './List.js';
|
|
7
4
|
import { Spinner } from './Spinner.js';
|
|
8
5
|
const MIN_PROCESSING_TIME = 1000; // purely for visual effect
|
|
9
|
-
// Color palette
|
|
10
|
-
const ColorPalette = {
|
|
11
|
-
[TaskType.Config]: {
|
|
12
|
-
description: '#ffffff', // white
|
|
13
|
-
type: '#5c9ccc', // cyan
|
|
14
|
-
},
|
|
15
|
-
[TaskType.Plan]: {
|
|
16
|
-
description: '#ffffff', // white
|
|
17
|
-
type: '#5ccccc', // magenta
|
|
18
|
-
},
|
|
19
|
-
[TaskType.Execute]: {
|
|
20
|
-
description: '#ffffff', // white
|
|
21
|
-
type: '#4a9a7a', // green
|
|
22
|
-
},
|
|
23
|
-
[TaskType.Answer]: {
|
|
24
|
-
description: '#ffffff', // white
|
|
25
|
-
type: '#9c5ccc', // purple
|
|
26
|
-
},
|
|
27
|
-
[TaskType.Report]: {
|
|
28
|
-
description: '#ffffff', // white
|
|
29
|
-
type: '#cc9c5c', // orange
|
|
30
|
-
},
|
|
31
|
-
[TaskType.Define]: {
|
|
32
|
-
description: '#ffffff', // white
|
|
33
|
-
type: '#cc9c5c', // amber
|
|
34
|
-
},
|
|
35
|
-
[TaskType.Ignore]: {
|
|
36
|
-
description: '#cccc5c', // yellow
|
|
37
|
-
type: '#cc7a5c', // orange
|
|
38
|
-
},
|
|
39
|
-
[TaskType.Select]: {
|
|
40
|
-
description: '#888888', // grey
|
|
41
|
-
type: '#5c8cbc', // steel blue
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
function taskToListItem(task) {
|
|
45
|
-
const colors = ColorPalette[task.type];
|
|
46
|
-
const item = {
|
|
47
|
-
description: {
|
|
48
|
-
text: task.action,
|
|
49
|
-
color: colors.description,
|
|
50
|
-
},
|
|
51
|
-
type: {
|
|
52
|
-
text: task.type,
|
|
53
|
-
color: colors.type,
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
// Add children for Define tasks with options
|
|
57
|
-
if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
|
|
58
|
-
const selectColors = ColorPalette[TaskType.Select];
|
|
59
|
-
item.children = task.params.options.map((option) => ({
|
|
60
|
-
description: {
|
|
61
|
-
text: String(option),
|
|
62
|
-
color: selectColors.description,
|
|
63
|
-
},
|
|
64
|
-
type: {
|
|
65
|
-
text: TaskType.Select,
|
|
66
|
-
color: selectColors.type,
|
|
67
|
-
},
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
return item;
|
|
71
|
-
}
|
|
72
6
|
export function Command({ command, state, service, children, onError, onComplete, }) {
|
|
73
7
|
const done = state?.done ?? false;
|
|
74
8
|
const [error, setError] = useState(null);
|
|
75
9
|
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
76
|
-
const [message, setMessage] = useState('');
|
|
77
|
-
const [tasks, setTasks] = useState([]);
|
|
78
10
|
useEffect(() => {
|
|
79
11
|
// Skip processing if done (showing historical/final state)
|
|
80
12
|
if (done) {
|
|
@@ -95,10 +27,8 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
95
27
|
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
96
28
|
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
97
29
|
if (mounted) {
|
|
98
|
-
setMessage(result.message);
|
|
99
|
-
setTasks(result.tasks);
|
|
100
30
|
setIsLoading(false);
|
|
101
|
-
onComplete?.();
|
|
31
|
+
onComplete?.(result.message, result.tasks);
|
|
102
32
|
}
|
|
103
33
|
}
|
|
104
34
|
catch (err) {
|
|
@@ -122,5 +52,5 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
122
52
|
mounted = false;
|
|
123
53
|
};
|
|
124
54
|
}, [command, done, service]);
|
|
125
|
-
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column",
|
|
55
|
+
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] }));
|
|
126
56
|
}
|
package/dist/ui/Component.js
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ComponentName } from '../types/components.js';
|
|
2
3
|
import { Command } from './Command.js';
|
|
3
4
|
import { Config } from './Config.js';
|
|
4
5
|
import { Feedback } from './Feedback.js';
|
|
6
|
+
import { Message } from './Message.js';
|
|
7
|
+
import { Plan } from './Plan.js';
|
|
5
8
|
import { Welcome } from './Welcome.js';
|
|
6
9
|
export function Component({ def }) {
|
|
7
10
|
switch (def.name) {
|
|
8
|
-
case
|
|
11
|
+
case ComponentName.Welcome:
|
|
9
12
|
return _jsx(Welcome, { ...def.props });
|
|
10
|
-
case
|
|
13
|
+
case ComponentName.Config: {
|
|
11
14
|
const props = def.props;
|
|
12
15
|
const state = def.state;
|
|
13
16
|
return _jsx(Config, { ...props, state: state });
|
|
14
17
|
}
|
|
15
|
-
case
|
|
16
|
-
return _jsx(Feedback, { ...def.props });
|
|
17
|
-
case 'command': {
|
|
18
|
+
case ComponentName.Command: {
|
|
18
19
|
const props = def.props;
|
|
19
20
|
const state = def.state;
|
|
20
21
|
return _jsx(Command, { ...props, state: state });
|
|
21
22
|
}
|
|
23
|
+
case ComponentName.Plan:
|
|
24
|
+
return _jsx(Plan, { ...def.props });
|
|
25
|
+
case ComponentName.Feedback:
|
|
26
|
+
return _jsx(Feedback, { ...def.props });
|
|
27
|
+
case ComponentName.Message:
|
|
28
|
+
return _jsx(Message, { ...def.props });
|
|
22
29
|
}
|
|
23
30
|
}
|
package/dist/ui/Config.js
CHANGED
|
@@ -1,20 +1,87 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { Box, Text, useInput, useFocus } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
+
export var StepType;
|
|
6
|
+
(function (StepType) {
|
|
7
|
+
StepType["Text"] = "text";
|
|
8
|
+
StepType["Selection"] = "selection";
|
|
9
|
+
})(StepType || (StepType = {}));
|
|
10
|
+
function TextStep({ value, placeholder, validate, onChange, onSubmit, }) {
|
|
11
|
+
const [inputValue, setInputValue] = React.useState(value);
|
|
12
|
+
const [validationFailed, setValidationFailed] = React.useState(false);
|
|
13
|
+
const { isFocused } = useFocus({ autoFocus: true });
|
|
14
|
+
const handleChange = (newValue) => {
|
|
15
|
+
setInputValue(newValue);
|
|
16
|
+
onChange(newValue);
|
|
17
|
+
if (validationFailed) {
|
|
18
|
+
setValidationFailed(false);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const handleSubmit = (value) => {
|
|
22
|
+
if (!validate(value)) {
|
|
23
|
+
setValidationFailed(true);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
onSubmit(value);
|
|
27
|
+
};
|
|
28
|
+
// Handle input manually when validation fails
|
|
29
|
+
useInput((input, key) => {
|
|
30
|
+
if (!validationFailed)
|
|
31
|
+
return;
|
|
32
|
+
if (key.return) {
|
|
33
|
+
handleSubmit(inputValue);
|
|
34
|
+
}
|
|
35
|
+
else if (key.backspace || key.delete) {
|
|
36
|
+
const newValue = inputValue.slice(0, -1);
|
|
37
|
+
handleChange(newValue);
|
|
38
|
+
}
|
|
39
|
+
else if (!key.ctrl && !key.meta && input) {
|
|
40
|
+
const newValue = inputValue + input;
|
|
41
|
+
handleChange(newValue);
|
|
42
|
+
}
|
|
43
|
+
}, { isActive: validationFailed });
|
|
44
|
+
// When validation fails, show colored text
|
|
45
|
+
if (validationFailed) {
|
|
46
|
+
return (_jsxs(Text, { color: "#cc5c5c", children: [inputValue || placeholder, isFocused && _jsx(Text, { inverse: true, children: " " })] }));
|
|
47
|
+
}
|
|
48
|
+
return (_jsx(TextInput, { value: inputValue, onChange: handleChange, onSubmit: handleSubmit, placeholder: placeholder }));
|
|
49
|
+
}
|
|
50
|
+
function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
|
|
51
|
+
return (_jsx(Box, { children: options.map((option, optIndex) => {
|
|
52
|
+
const isSelected = optIndex === selectedIndex;
|
|
53
|
+
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
|
|
54
|
+
}) }));
|
|
55
|
+
}
|
|
5
56
|
export function Config({ steps, state, onFinished, onAborted }) {
|
|
6
57
|
const done = state?.done ?? false;
|
|
7
58
|
const [step, setStep] = React.useState(done ? steps.length : 0);
|
|
8
59
|
const [values, setValues] = React.useState(() => {
|
|
9
60
|
const initial = {};
|
|
10
|
-
steps.forEach((
|
|
11
|
-
|
|
12
|
-
|
|
61
|
+
steps.forEach((stepConfig) => {
|
|
62
|
+
switch (stepConfig.type) {
|
|
63
|
+
case StepType.Text:
|
|
64
|
+
if (stepConfig.value !== null) {
|
|
65
|
+
initial[stepConfig.key] = stepConfig.value;
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case StepType.Selection:
|
|
69
|
+
initial[stepConfig.key] =
|
|
70
|
+
stepConfig.options[stepConfig.defaultIndex].value;
|
|
71
|
+
break;
|
|
72
|
+
default: {
|
|
73
|
+
const exhaustiveCheck = stepConfig;
|
|
74
|
+
throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
|
|
75
|
+
}
|
|
13
76
|
}
|
|
14
77
|
});
|
|
15
78
|
return initial;
|
|
16
79
|
});
|
|
17
80
|
const [inputValue, setInputValue] = React.useState('');
|
|
81
|
+
const [selectedIndex, setSelectedIndex] = React.useState(() => {
|
|
82
|
+
const firstStep = steps[0];
|
|
83
|
+
return firstStep?.type === StepType.Selection ? firstStep.defaultIndex : 0;
|
|
84
|
+
});
|
|
18
85
|
const normalizeValue = (value) => {
|
|
19
86
|
if (value === null || value === undefined) {
|
|
20
87
|
return '';
|
|
@@ -23,16 +90,83 @@ export function Config({ steps, state, onFinished, onAborted }) {
|
|
|
23
90
|
};
|
|
24
91
|
useInput((input, key) => {
|
|
25
92
|
if (key.escape && !done && step < steps.length) {
|
|
93
|
+
// Save current value before aborting
|
|
94
|
+
const currentStepConfig = steps[step];
|
|
95
|
+
if (currentStepConfig) {
|
|
96
|
+
let currentValue = '';
|
|
97
|
+
switch (currentStepConfig.type) {
|
|
98
|
+
case StepType.Text:
|
|
99
|
+
currentValue = inputValue || values[currentStepConfig.key] || '';
|
|
100
|
+
break;
|
|
101
|
+
case StepType.Selection:
|
|
102
|
+
currentValue =
|
|
103
|
+
currentStepConfig.options[selectedIndex]?.value ||
|
|
104
|
+
values[currentStepConfig.key] ||
|
|
105
|
+
'';
|
|
106
|
+
break;
|
|
107
|
+
default: {
|
|
108
|
+
const exhaustiveCheck = currentStepConfig;
|
|
109
|
+
throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (currentValue) {
|
|
113
|
+
setValues({ ...values, [currentStepConfig.key]: currentValue });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
26
116
|
if (onAborted) {
|
|
27
117
|
onAborted();
|
|
28
118
|
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const currentStep = steps[step];
|
|
122
|
+
if (!done && step < steps.length && currentStep) {
|
|
123
|
+
switch (currentStep.type) {
|
|
124
|
+
case StepType.Selection:
|
|
125
|
+
if (key.tab) {
|
|
126
|
+
setSelectedIndex((prev) => (prev + 1) % currentStep.options.length);
|
|
127
|
+
}
|
|
128
|
+
else if (key.return) {
|
|
129
|
+
handleSubmit(currentStep.options[selectedIndex].value);
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
case StepType.Text:
|
|
133
|
+
// Text input handled by TextInput component
|
|
134
|
+
break;
|
|
135
|
+
default: {
|
|
136
|
+
const exhaustiveCheck = currentStep;
|
|
137
|
+
throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
29
140
|
}
|
|
30
141
|
});
|
|
31
142
|
const handleSubmit = (value) => {
|
|
32
143
|
const currentStepConfig = steps[step];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
144
|
+
let finalValue = '';
|
|
145
|
+
switch (currentStepConfig.type) {
|
|
146
|
+
case StepType.Selection:
|
|
147
|
+
// For selection, value is already validated by options
|
|
148
|
+
finalValue = value;
|
|
149
|
+
break;
|
|
150
|
+
case StepType.Text: {
|
|
151
|
+
// For text input
|
|
152
|
+
const normalizedInput = normalizeValue(value);
|
|
153
|
+
// Try user input first, then fall back to default
|
|
154
|
+
if (normalizedInput && currentStepConfig.validate(normalizedInput)) {
|
|
155
|
+
finalValue = normalizedInput;
|
|
156
|
+
}
|
|
157
|
+
else if (currentStepConfig.value &&
|
|
158
|
+
currentStepConfig.validate(currentStepConfig.value)) {
|
|
159
|
+
finalValue = currentStepConfig.value;
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
default: {
|
|
164
|
+
const exhaustiveCheck = currentStepConfig;
|
|
165
|
+
throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Don't allow empty or invalid value
|
|
169
|
+
if (!finalValue) {
|
|
36
170
|
return;
|
|
37
171
|
}
|
|
38
172
|
const newValues = { ...values, [currentStepConfig.key]: finalValue };
|
|
@@ -47,9 +181,34 @@ export function Config({ steps, state, onFinished, onAborted }) {
|
|
|
47
181
|
}
|
|
48
182
|
else {
|
|
49
183
|
setStep(step + 1);
|
|
184
|
+
// Reset selection index for next step
|
|
185
|
+
const nextStep = steps[step + 1];
|
|
186
|
+
if (nextStep?.type === StepType.Selection) {
|
|
187
|
+
setSelectedIndex(nextStep.defaultIndex);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const renderStepInput = (stepConfig, isCurrentStep) => {
|
|
192
|
+
switch (stepConfig.type) {
|
|
193
|
+
case StepType.Text:
|
|
194
|
+
if (isCurrentStep) {
|
|
195
|
+
return (_jsx(TextStep, { value: inputValue, placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: setInputValue, onSubmit: handleSubmit }));
|
|
196
|
+
}
|
|
197
|
+
return _jsx(Text, { dimColor: true, children: values[stepConfig.key] || '' });
|
|
198
|
+
case StepType.Selection: {
|
|
199
|
+
if (!isCurrentStep) {
|
|
200
|
+
const selectedOption = stepConfig.options.find((opt) => opt.value === values[stepConfig.key]);
|
|
201
|
+
return _jsx(Text, { dimColor: true, children: selectedOption?.label || '' });
|
|
202
|
+
}
|
|
203
|
+
return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: isCurrentStep }));
|
|
204
|
+
}
|
|
205
|
+
default: {
|
|
206
|
+
const exhaustiveCheck = stepConfig;
|
|
207
|
+
throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
|
|
208
|
+
}
|
|
50
209
|
}
|
|
51
210
|
};
|
|
52
|
-
return (_jsx(Box, { flexDirection: "column",
|
|
211
|
+
return (_jsx(Box, { flexDirection: "column", children: steps.map((stepConfig, index) => {
|
|
53
212
|
const isCurrentStep = index === step && !done;
|
|
54
213
|
const isCompleted = index < step;
|
|
55
214
|
const wasAborted = index === step && done;
|
|
@@ -57,6 +216,6 @@ export function Config({ steps, state, onFinished, onAborted }) {
|
|
|
57
216
|
if (!shouldShow) {
|
|
58
217
|
return null;
|
|
59
218
|
}
|
|
60
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "#5c8cbc", dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }),
|
|
219
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "#5c8cbc", dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
|
|
61
220
|
}) }));
|
|
62
221
|
}
|
package/dist/ui/Feedback.js
CHANGED
|
@@ -3,20 +3,31 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import { FeedbackType } from '../types/components.js';
|
|
4
4
|
function getSymbol(type) {
|
|
5
5
|
return {
|
|
6
|
+
[FeedbackType.Info]: 'ℹ',
|
|
6
7
|
[FeedbackType.Succeeded]: '✓',
|
|
7
8
|
[FeedbackType.Aborted]: '⊘',
|
|
8
9
|
[FeedbackType.Failed]: '✗',
|
|
9
10
|
}[type];
|
|
10
11
|
}
|
|
11
|
-
function
|
|
12
|
+
function getSymbolColor(type) {
|
|
12
13
|
return {
|
|
14
|
+
[FeedbackType.Info]: '#5c9ccc', // cyan
|
|
13
15
|
[FeedbackType.Succeeded]: '#00aa00', // green
|
|
14
16
|
[FeedbackType.Aborted]: '#cc9c5c', // orange
|
|
15
|
-
[FeedbackType.Failed]: '#
|
|
17
|
+
[FeedbackType.Failed]: '#cc5c5c', // red
|
|
18
|
+
}[type];
|
|
19
|
+
}
|
|
20
|
+
function getMessageColor(type) {
|
|
21
|
+
return {
|
|
22
|
+
[FeedbackType.Info]: '#aaaaaa', // light grey
|
|
23
|
+
[FeedbackType.Succeeded]: '#5ccc5c', // green
|
|
24
|
+
[FeedbackType.Aborted]: '#cc9c5c', // orange
|
|
25
|
+
[FeedbackType.Failed]: '#cc5c5c', // red
|
|
16
26
|
}[type];
|
|
17
27
|
}
|
|
18
28
|
export function Feedback({ type, message }) {
|
|
19
|
-
const
|
|
29
|
+
const symbolColor = getSymbolColor(type);
|
|
30
|
+
const messageColor = getMessageColor(type);
|
|
20
31
|
const symbol = getSymbol(type);
|
|
21
|
-
return (
|
|
32
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: symbolColor, children: [symbol, " "] }), _jsx(Text, { color: messageColor, children: message })] }));
|
|
22
33
|
}
|
package/dist/ui/Main.js
CHANGED
|
@@ -1,133 +1,134 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { ComponentName, } from '../types/components.js';
|
|
4
|
+
import { createAnthropicService, } from '../services/anthropic.js';
|
|
3
5
|
import { FeedbackType } from '../types/components.js';
|
|
6
|
+
import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, saveAnthropicConfig, } from '../services/config.js';
|
|
7
|
+
import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createPlanDefinition, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
|
|
8
|
+
import { exitApp } from '../services/process.js';
|
|
4
9
|
import { Column } from './Column.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function createWelcomeDefinition(app) {
|
|
12
|
-
return {
|
|
13
|
-
name: 'welcome',
|
|
14
|
-
props: { app },
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function createConfigSteps() {
|
|
18
|
-
return [
|
|
19
|
-
{ description: 'Anthropic API key', key: 'key', value: null },
|
|
20
|
-
{
|
|
21
|
-
description: 'Model',
|
|
22
|
-
key: 'model',
|
|
23
|
-
value: 'claude-haiku-4-5-20251001',
|
|
24
|
-
},
|
|
25
|
-
];
|
|
26
|
-
}
|
|
27
|
-
function createConfigDefinition(onFinished, onAborted) {
|
|
28
|
-
return {
|
|
29
|
-
name: 'config',
|
|
30
|
-
state: { done: false },
|
|
31
|
-
props: {
|
|
32
|
-
steps: createConfigSteps(),
|
|
33
|
-
onFinished,
|
|
34
|
-
onAborted,
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
function createCommandDefinition(command, service, onError, onComplete) {
|
|
39
|
-
return {
|
|
40
|
-
name: 'command',
|
|
41
|
-
state: {
|
|
42
|
-
done: false,
|
|
43
|
-
isLoading: true,
|
|
44
|
-
},
|
|
45
|
-
props: {
|
|
46
|
-
command,
|
|
47
|
-
service,
|
|
48
|
-
onError,
|
|
49
|
-
onComplete,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function createFeedback(type, ...messages) {
|
|
54
|
-
return {
|
|
55
|
-
name: 'feedback',
|
|
56
|
-
props: {
|
|
57
|
-
type,
|
|
58
|
-
message: messages.join('\n\n'),
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
export const Main = ({ app, command, service: initialService, isReady, onConfigured, }) => {
|
|
63
|
-
const [history, setHistory] = React.useState([]);
|
|
64
|
-
const [current, setCurrent] = React.useState(null);
|
|
65
|
-
const [service, setService] = React.useState(initialService);
|
|
66
|
-
const addToHistory = React.useCallback((...items) => {
|
|
67
|
-
setHistory((history) => [...history, ...items]);
|
|
68
|
-
}, []);
|
|
69
|
-
const handleConfigFinished = React.useCallback((config) => {
|
|
70
|
-
const service = onConfigured?.(config);
|
|
71
|
-
if (service) {
|
|
72
|
-
setService(service);
|
|
10
|
+
export const Main = ({ app, command }) => {
|
|
11
|
+
// Initialize service from existing config if available
|
|
12
|
+
const [service, setService] = React.useState(() => {
|
|
13
|
+
if (hasValidAnthropicKey()) {
|
|
14
|
+
const config = loadConfig();
|
|
15
|
+
return createAnthropicService(config.anthropic);
|
|
73
16
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
17
|
+
return null;
|
|
18
|
+
});
|
|
19
|
+
const [timeline, setTimeline] = React.useState([]);
|
|
20
|
+
const [queue, setQueue] = React.useState([]);
|
|
21
|
+
const addToTimeline = React.useCallback((...items) => {
|
|
22
|
+
setTimeline((timeline) => [...timeline, ...items]);
|
|
23
|
+
}, []);
|
|
24
|
+
const processNextInQueue = React.useCallback(() => {
|
|
25
|
+
setQueue((currentQueue) => {
|
|
26
|
+
if (currentQueue.length === 0)
|
|
27
|
+
return currentQueue;
|
|
28
|
+
const [first, ...rest] = currentQueue;
|
|
29
|
+
// Stateless components auto-complete immediately
|
|
30
|
+
if (isStateless(first)) {
|
|
31
|
+
addToTimeline(first);
|
|
32
|
+
return rest;
|
|
33
|
+
}
|
|
34
|
+
return currentQueue;
|
|
89
35
|
});
|
|
90
|
-
}, [
|
|
36
|
+
}, [addToTimeline]);
|
|
91
37
|
const handleCommandError = React.useCallback((error) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
38
|
+
setQueue((currentQueue) => {
|
|
39
|
+
if (currentQueue.length === 0)
|
|
40
|
+
return currentQueue;
|
|
41
|
+
const [first] = currentQueue;
|
|
42
|
+
if (first.name === ComponentName.Command) {
|
|
43
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, 'Unexpected error occurred:', error));
|
|
44
|
+
}
|
|
45
|
+
exitApp(1);
|
|
46
|
+
return [];
|
|
47
|
+
});
|
|
48
|
+
}, [addToTimeline]);
|
|
49
|
+
const handleCommandComplete = React.useCallback((message, tasks) => {
|
|
50
|
+
setQueue((currentQueue) => {
|
|
51
|
+
if (currentQueue.length === 0)
|
|
52
|
+
return currentQueue;
|
|
53
|
+
const [first] = currentQueue;
|
|
54
|
+
if (first.name === ComponentName.Command) {
|
|
55
|
+
addToTimeline(markAsDone(first), createPlanDefinition(message, tasks));
|
|
56
|
+
}
|
|
57
|
+
exitApp(0);
|
|
58
|
+
return [];
|
|
98
59
|
});
|
|
99
|
-
}, [
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
60
|
+
}, [addToTimeline]);
|
|
61
|
+
const handleConfigFinished = React.useCallback((config) => {
|
|
62
|
+
const anthropicConfig = config;
|
|
63
|
+
saveAnthropicConfig(anthropicConfig);
|
|
64
|
+
const newService = createAnthropicService(anthropicConfig);
|
|
65
|
+
setService(newService);
|
|
66
|
+
// Complete config component and add command if present
|
|
67
|
+
setQueue((currentQueue) => {
|
|
68
|
+
if (currentQueue.length === 0)
|
|
69
|
+
return currentQueue;
|
|
70
|
+
const [first, ...rest] = currentQueue;
|
|
71
|
+
if (first.name === ComponentName.Config) {
|
|
72
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Succeeded, 'Configuration complete'));
|
|
73
|
+
}
|
|
74
|
+
// Add command to queue if we have one
|
|
75
|
+
if (command) {
|
|
76
|
+
return [
|
|
77
|
+
...rest,
|
|
78
|
+
createCommandDefinition(command, newService, handleCommandError, handleCommandComplete),
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
// No command - exit after showing completion message
|
|
82
|
+
exitApp(0);
|
|
83
|
+
return rest;
|
|
84
|
+
});
|
|
85
|
+
}, [addToTimeline, command, handleCommandError, handleCommandComplete]);
|
|
86
|
+
const handleConfigAborted = React.useCallback(() => {
|
|
87
|
+
setQueue((currentQueue) => {
|
|
88
|
+
if (currentQueue.length === 0)
|
|
89
|
+
return currentQueue;
|
|
90
|
+
const [first] = currentQueue;
|
|
91
|
+
if (first.name === ComponentName.Config) {
|
|
92
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, 'Configuration aborted by user'));
|
|
93
|
+
}
|
|
94
|
+
exitApp(0);
|
|
95
|
+
return [];
|
|
107
96
|
});
|
|
108
|
-
}, [
|
|
109
|
-
// Initialize
|
|
97
|
+
}, [addToTimeline]);
|
|
98
|
+
// Initialize queue on mount
|
|
110
99
|
React.useEffect(() => {
|
|
111
|
-
|
|
112
|
-
|
|
100
|
+
const hasConfig = !!service;
|
|
101
|
+
if (command && hasConfig) {
|
|
102
|
+
// With command + valid config: [Command]
|
|
103
|
+
setQueue([
|
|
104
|
+
createCommandDefinition(command, service, handleCommandError, handleCommandComplete),
|
|
105
|
+
]);
|
|
113
106
|
}
|
|
114
|
-
if (!
|
|
115
|
-
|
|
107
|
+
else if (command && !hasConfig) {
|
|
108
|
+
// With command + no config: [Message, Config] (Command added after config)
|
|
109
|
+
setQueue([
|
|
110
|
+
createMessage(getConfigurationRequiredMessage()),
|
|
111
|
+
createConfigDefinition(handleConfigFinished, handleConfigAborted),
|
|
112
|
+
]);
|
|
116
113
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
React.useEffect(() => {
|
|
121
|
-
if (command && service) {
|
|
122
|
-
setCurrent(createCommandDefinition(command, service, handleCommandError, handleCommandComplete));
|
|
114
|
+
else if (!command && hasConfig) {
|
|
115
|
+
// No command + valid config: [Welcome]
|
|
116
|
+
setQueue([createWelcomeDefinition(app)]);
|
|
123
117
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
else {
|
|
119
|
+
// No command + no config: [Welcome, Message, Config]
|
|
120
|
+
setQueue([
|
|
121
|
+
createWelcomeDefinition(app),
|
|
122
|
+
createMessage(getConfigurationRequiredMessage(true)),
|
|
123
|
+
createConfigDefinition(handleConfigFinished, handleConfigAborted),
|
|
124
|
+
]);
|
|
129
125
|
}
|
|
130
|
-
}, [
|
|
131
|
-
|
|
126
|
+
}, []); // Only run on mount
|
|
127
|
+
// Process queue whenever it changes
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
processNextInQueue();
|
|
130
|
+
}, [queue, processNextInQueue]);
|
|
131
|
+
const current = queue.length > 0 ? queue[0] : null;
|
|
132
|
+
const items = [...timeline, ...(current ? [current] : [])];
|
|
132
133
|
return _jsx(Column, { items: items });
|
|
133
134
|
};
|
package/dist/ui/Plan.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { TaskType } from '../types/components.js';
|
|
4
|
+
import { Label } from './Label.js';
|
|
5
|
+
import { List } from './List.js';
|
|
6
|
+
const ColorPalette = {
|
|
7
|
+
[TaskType.Config]: {
|
|
8
|
+
description: '#ffffff', // white
|
|
9
|
+
type: '#5c9ccc', // cyan
|
|
10
|
+
},
|
|
11
|
+
[TaskType.Plan]: {
|
|
12
|
+
description: '#ffffff', // white
|
|
13
|
+
type: '#5ccccc', // magenta
|
|
14
|
+
},
|
|
15
|
+
[TaskType.Execute]: {
|
|
16
|
+
description: '#ffffff', // white
|
|
17
|
+
type: '#4a9a7a', // green
|
|
18
|
+
},
|
|
19
|
+
[TaskType.Answer]: {
|
|
20
|
+
description: '#ffffff', // white
|
|
21
|
+
type: '#9c5ccc', // purple
|
|
22
|
+
},
|
|
23
|
+
[TaskType.Report]: {
|
|
24
|
+
description: '#ffffff', // white
|
|
25
|
+
type: '#cc9c5c', // orange
|
|
26
|
+
},
|
|
27
|
+
[TaskType.Define]: {
|
|
28
|
+
description: '#ffffff', // white
|
|
29
|
+
type: '#cc9c5c', // amber
|
|
30
|
+
},
|
|
31
|
+
[TaskType.Ignore]: {
|
|
32
|
+
description: '#cccc5c', // yellow
|
|
33
|
+
type: '#cc7a5c', // orange
|
|
34
|
+
},
|
|
35
|
+
[TaskType.Select]: {
|
|
36
|
+
description: '#888888', // grey
|
|
37
|
+
type: '#5c8cbc', // steel blue
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
function taskToListItem(task) {
|
|
41
|
+
const item = {
|
|
42
|
+
description: {
|
|
43
|
+
text: task.action,
|
|
44
|
+
color: ColorPalette[task.type].description,
|
|
45
|
+
},
|
|
46
|
+
type: { text: task.type, color: ColorPalette[task.type].type },
|
|
47
|
+
children: [],
|
|
48
|
+
};
|
|
49
|
+
// Add children for Define tasks with options
|
|
50
|
+
if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
|
|
51
|
+
const selectColors = ColorPalette[TaskType.Select];
|
|
52
|
+
item.children = task.params.options.map((option) => ({
|
|
53
|
+
description: {
|
|
54
|
+
text: String(option),
|
|
55
|
+
color: selectColors.description,
|
|
56
|
+
},
|
|
57
|
+
type: {
|
|
58
|
+
text: TaskType.Select,
|
|
59
|
+
color: selectColors.type,
|
|
60
|
+
},
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
return item;
|
|
64
|
+
}
|
|
65
|
+
export function Plan({ message, tasks }) {
|
|
66
|
+
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: tasks.map(taskToListItem) })] }));
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@vitest/coverage-v8": "^4.0.8",
|
|
58
58
|
"eslint": "^9.17.0",
|
|
59
59
|
"husky": "^9.1.7",
|
|
60
|
+
"ink-testing-library": "^4.0.0",
|
|
60
61
|
"prettier": "^3.6.2",
|
|
61
62
|
"typescript": "^5.3.3",
|
|
62
63
|
"typescript-eslint": "^8.19.1",
|