prompt-language-shell 0.4.8 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/EXECUTE.md +279 -0
- package/dist/config/PLAN.md +19 -6
- package/dist/handlers/answer.js +13 -20
- package/dist/handlers/command.js +26 -30
- package/dist/handlers/config.js +9 -18
- package/dist/handlers/execute.js +38 -0
- package/dist/handlers/execution.js +66 -81
- package/dist/handlers/introspect.js +13 -20
- package/dist/handlers/plan.js +31 -34
- package/dist/services/anthropic.js +26 -1
- package/dist/services/colors.js +1 -1
- package/dist/services/components.js +17 -0
- package/dist/services/messages.js +1 -0
- package/dist/services/shell.js +117 -0
- package/dist/services/tool-registry.js +5 -0
- package/dist/services/utils.js +21 -0
- package/dist/tools/execute.tool.js +44 -0
- package/dist/types/handlers.js +1 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Component.js +6 -0
- package/dist/ui/Confirm.js +2 -2
- package/dist/ui/Execute.js +217 -0
- package/dist/ui/Main.js +30 -69
- package/dist/ui/Spinner.js +10 -5
- package/package.json +7 -7
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { Colors, getTextColor, Palette } from '../services/colors.js';
|
|
5
|
+
import { useInput } from '../services/keyboard.js';
|
|
6
|
+
import { formatErrorMessage } from '../services/messages.js';
|
|
7
|
+
import { formatDuration } from '../services/utils.js';
|
|
8
|
+
import { ExecutionStatus, executeCommands, } from '../services/shell.js';
|
|
9
|
+
import { Spinner } from './Spinner.js';
|
|
10
|
+
const MINIMUM_PROCESSING_TIME = 400;
|
|
11
|
+
const STATUS_ICONS = {
|
|
12
|
+
[ExecutionStatus.Pending]: '- ',
|
|
13
|
+
[ExecutionStatus.Running]: '• ',
|
|
14
|
+
[ExecutionStatus.Success]: '✓ ',
|
|
15
|
+
[ExecutionStatus.Failed]: '✗ ',
|
|
16
|
+
};
|
|
17
|
+
function getStatusColors(status) {
|
|
18
|
+
switch (status) {
|
|
19
|
+
case ExecutionStatus.Pending:
|
|
20
|
+
return {
|
|
21
|
+
icon: Palette.Gray,
|
|
22
|
+
description: Palette.Gray,
|
|
23
|
+
command: Palette.DarkGray,
|
|
24
|
+
symbol: Palette.DarkGray,
|
|
25
|
+
};
|
|
26
|
+
case ExecutionStatus.Running:
|
|
27
|
+
return {
|
|
28
|
+
icon: Palette.Gray,
|
|
29
|
+
description: getTextColor(true),
|
|
30
|
+
command: Palette.LightGreen,
|
|
31
|
+
symbol: Palette.AshGray,
|
|
32
|
+
};
|
|
33
|
+
case ExecutionStatus.Success:
|
|
34
|
+
return {
|
|
35
|
+
icon: Colors.Status.Success,
|
|
36
|
+
description: getTextColor(true),
|
|
37
|
+
command: Palette.Gray,
|
|
38
|
+
symbol: Palette.Gray,
|
|
39
|
+
};
|
|
40
|
+
case ExecutionStatus.Failed:
|
|
41
|
+
return {
|
|
42
|
+
icon: Colors.Status.Error,
|
|
43
|
+
description: Colors.Status.Error,
|
|
44
|
+
command: Colors.Status.Error,
|
|
45
|
+
symbol: Palette.Gray,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function CommandStatusDisplay({ item, elapsed }) {
|
|
50
|
+
const colors = getStatusColors(item.status);
|
|
51
|
+
const getElapsedTime = () => {
|
|
52
|
+
if (item.status === ExecutionStatus.Running && elapsed !== undefined) {
|
|
53
|
+
return elapsed;
|
|
54
|
+
}
|
|
55
|
+
else if (item.startTime && item.endTime) {
|
|
56
|
+
return item.endTime - item.startTime;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
};
|
|
60
|
+
const elapsedTime = getElapsedTime();
|
|
61
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[item.status] }), _jsx(Text, { color: colors.description, children: item.label || item.command.description }), elapsedTime !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: [" (", formatDuration(elapsedTime), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, children: [_jsx(Text, { color: colors.symbol, children: "\u221F " }), _jsx(Text, { color: colors.command, children: item.command.command }), item.status === ExecutionStatus.Running && (_jsxs(Text, { children: [' ', _jsx(Spinner, {})] }))] })] }));
|
|
62
|
+
}
|
|
63
|
+
export function Execute({ tasks, state, service, onError, onComplete, onAborted, }) {
|
|
64
|
+
const done = state?.done ?? false;
|
|
65
|
+
const isCurrent = done === false;
|
|
66
|
+
const [error, setError] = useState(null);
|
|
67
|
+
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
68
|
+
const [isExecuting, setIsExecuting] = useState(false);
|
|
69
|
+
const [commandStatuses, setCommandStatuses] = useState([]);
|
|
70
|
+
const [message, setMessage] = useState('');
|
|
71
|
+
const [currentElapsed, setCurrentElapsed] = useState(0);
|
|
72
|
+
const [runningIndex, setRunningIndex] = useState(null);
|
|
73
|
+
const [outputs, setOutputs] = useState([]);
|
|
74
|
+
useInput((input, key) => {
|
|
75
|
+
if (key.escape && (isLoading || isExecuting) && !done) {
|
|
76
|
+
setIsLoading(false);
|
|
77
|
+
setIsExecuting(false);
|
|
78
|
+
onAborted();
|
|
79
|
+
}
|
|
80
|
+
}, { isActive: (isLoading || isExecuting) && !done });
|
|
81
|
+
// Update elapsed time for running command
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (runningIndex === null)
|
|
84
|
+
return;
|
|
85
|
+
const item = commandStatuses[runningIndex];
|
|
86
|
+
if (!item?.startTime)
|
|
87
|
+
return;
|
|
88
|
+
const interval = setInterval(() => {
|
|
89
|
+
setCurrentElapsed((prev) => {
|
|
90
|
+
const next = Date.now() - item.startTime;
|
|
91
|
+
return next !== prev ? next : prev;
|
|
92
|
+
});
|
|
93
|
+
}, 1000);
|
|
94
|
+
return () => clearInterval(interval);
|
|
95
|
+
}, [runningIndex, commandStatuses]);
|
|
96
|
+
// Handle completion callback when execution finishes
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (isExecuting || commandStatuses.length === 0 || !outputs.length)
|
|
99
|
+
return;
|
|
100
|
+
// Sum up elapsed times from all commands
|
|
101
|
+
const totalElapsed = commandStatuses.reduce((sum, cmd) => sum + (cmd.elapsed ?? 0), 0);
|
|
102
|
+
onComplete?.(outputs, totalElapsed);
|
|
103
|
+
}, [isExecuting, commandStatuses, outputs, onComplete]);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (done) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!service) {
|
|
109
|
+
setError('No service available');
|
|
110
|
+
setIsLoading(false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
let mounted = true;
|
|
114
|
+
async function process(svc) {
|
|
115
|
+
const startTime = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
// Format tasks for the execute tool
|
|
118
|
+
const taskDescriptions = tasks
|
|
119
|
+
.map((task) => {
|
|
120
|
+
const params = task.params
|
|
121
|
+
? ` (params: ${JSON.stringify(task.params)})`
|
|
122
|
+
: '';
|
|
123
|
+
return `- ${task.action}${params}`;
|
|
124
|
+
})
|
|
125
|
+
.join('\n');
|
|
126
|
+
// Call execute tool to get commands
|
|
127
|
+
const result = await svc.processWithTool(taskDescriptions, 'execute');
|
|
128
|
+
const elapsed = Date.now() - startTime;
|
|
129
|
+
const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
131
|
+
if (!mounted)
|
|
132
|
+
return;
|
|
133
|
+
if (!result.commands || result.commands.length === 0) {
|
|
134
|
+
setIsLoading(false);
|
|
135
|
+
setOutputs([]);
|
|
136
|
+
onComplete?.([], 0);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Set message and initialize command statuses
|
|
140
|
+
setMessage(result.message);
|
|
141
|
+
setCommandStatuses(result.commands.map((cmd, index) => ({
|
|
142
|
+
command: cmd,
|
|
143
|
+
status: ExecutionStatus.Pending,
|
|
144
|
+
label: tasks[index]?.action,
|
|
145
|
+
})));
|
|
146
|
+
setIsLoading(false);
|
|
147
|
+
setIsExecuting(true);
|
|
148
|
+
// Execute commands sequentially
|
|
149
|
+
const outputs = await executeCommands(result.commands, (progress) => {
|
|
150
|
+
if (!mounted)
|
|
151
|
+
return;
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
setCommandStatuses((prev) => prev.map((item, idx) => {
|
|
154
|
+
if (idx === progress.currentIndex) {
|
|
155
|
+
const isStarting = progress.status === ExecutionStatus.Running &&
|
|
156
|
+
!item.startTime;
|
|
157
|
+
const isEnding = progress.status !== ExecutionStatus.Running &&
|
|
158
|
+
progress.status !== ExecutionStatus.Pending;
|
|
159
|
+
const endTime = isEnding ? now : item.endTime;
|
|
160
|
+
const elapsed = isEnding && item.startTime
|
|
161
|
+
? Math.floor((now - item.startTime) / 1000) * 1000
|
|
162
|
+
: item.elapsed;
|
|
163
|
+
return {
|
|
164
|
+
...item,
|
|
165
|
+
status: progress.status,
|
|
166
|
+
output: progress.output,
|
|
167
|
+
startTime: isStarting ? now : item.startTime,
|
|
168
|
+
endTime,
|
|
169
|
+
elapsed,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return item;
|
|
173
|
+
}));
|
|
174
|
+
if (progress.status === ExecutionStatus.Running) {
|
|
175
|
+
setRunningIndex((prev) => prev !== progress.currentIndex ? progress.currentIndex : prev);
|
|
176
|
+
setCurrentElapsed((prev) => (prev !== 0 ? 0 : prev));
|
|
177
|
+
}
|
|
178
|
+
else if (progress.status === ExecutionStatus.Success ||
|
|
179
|
+
progress.status === ExecutionStatus.Failed) {
|
|
180
|
+
setRunningIndex((prev) => (prev !== null ? null : prev));
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
if (mounted) {
|
|
184
|
+
setOutputs(outputs);
|
|
185
|
+
setIsExecuting(false);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
const elapsed = Date.now() - startTime;
|
|
190
|
+
const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
|
|
191
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
192
|
+
if (mounted) {
|
|
193
|
+
const errorMessage = formatErrorMessage(err);
|
|
194
|
+
setIsLoading(false);
|
|
195
|
+
setIsExecuting(false);
|
|
196
|
+
if (onError) {
|
|
197
|
+
onError(errorMessage);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
setError(errorMessage);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
process(service);
|
|
206
|
+
return () => {
|
|
207
|
+
mounted = false;
|
|
208
|
+
};
|
|
209
|
+
}, [tasks, done, service, onComplete, onError]);
|
|
210
|
+
// Return null only when loading completes with no commands
|
|
211
|
+
if (done && commandStatuses.length === 0 && !error) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
// Show completed steps when done
|
|
215
|
+
const showCompletedSteps = done && commandStatuses.length > 0;
|
|
216
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showCompletedSteps) && (_jsxs(Box, { flexDirection: "column", children: [message && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: getTextColor(isCurrent), children: message }), isExecuting && (_jsxs(Text, { children: [' ', _jsx(Spinner, {})] }))] })), commandStatuses.map((item, index) => (_jsx(Box, { marginBottom: index < commandStatuses.length - 1 ? 1 : 0, children: _jsx(CommandStatusDisplay, { item: item, elapsed: index === runningIndex ? currentElapsed : undefined }) }, index)))] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
|
|
217
|
+
}
|
package/dist/ui/Main.js
CHANGED
|
@@ -2,20 +2,20 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { FeedbackType } from '../types/types.js';
|
|
4
4
|
import { createAnthropicService, } from '../services/anthropic.js';
|
|
5
|
+
import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
|
|
5
6
|
import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, loadDebugSetting, saveDebugSetting, } from '../services/configuration.js';
|
|
6
7
|
import { registerGlobalShortcut } from '../services/keyboard.js';
|
|
7
8
|
import { getCancellationMessage } from '../services/messages.js';
|
|
8
|
-
import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
|
|
9
9
|
import { exitApp } from '../services/process.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
10
|
+
import { createAnswerHandlers } from '../handlers/answer.js';
|
|
11
|
+
import { createCommandHandlers } from '../handlers/command.js';
|
|
12
|
+
import { createConfigHandlers } from '../handlers/config.js';
|
|
13
|
+
import { createExecuteHandlers } from '../handlers/execute.js';
|
|
14
|
+
import { createExecutionHandlers } from '../handlers/execution.js';
|
|
15
|
+
import { createIntrospectHandlers } from '../handlers/introspect.js';
|
|
16
|
+
import { createPlanHandlers } from '../handlers/plan.js';
|
|
16
17
|
import { Column } from './Column.js';
|
|
17
18
|
export const Main = ({ app, command }) => {
|
|
18
|
-
// Initialize service from existing config if available
|
|
19
19
|
const [service, setService] = React.useState(() => {
|
|
20
20
|
if (hasValidAnthropicKey()) {
|
|
21
21
|
const config = loadConfig();
|
|
@@ -26,14 +26,8 @@ export const Main = ({ app, command }) => {
|
|
|
26
26
|
const [timeline, setTimeline] = React.useState([]);
|
|
27
27
|
const [queue, setQueue] = React.useState([]);
|
|
28
28
|
const [isDebug, setIsDebug] = React.useState(() => loadDebugSetting());
|
|
29
|
-
// Use ref to track latest timeline for callbacks
|
|
30
|
-
const timelineRef = React.useRef(timeline);
|
|
31
|
-
React.useEffect(() => {
|
|
32
|
-
timelineRef.current = timeline;
|
|
33
|
-
}, [timeline]);
|
|
34
29
|
// Register global keyboard shortcuts
|
|
35
30
|
React.useEffect(() => {
|
|
36
|
-
// Shift+Tab: Toggle debug mode
|
|
37
31
|
registerGlobalShortcut('shift+tab', () => {
|
|
38
32
|
setIsDebug((prev) => {
|
|
39
33
|
const newValue = !prev;
|
|
@@ -50,7 +44,6 @@ export const Main = ({ app, command }) => {
|
|
|
50
44
|
if (currentQueue.length === 0)
|
|
51
45
|
return currentQueue;
|
|
52
46
|
const [first, ...rest] = currentQueue;
|
|
53
|
-
// Stateless components auto-complete immediately
|
|
54
47
|
if (isStateless(first)) {
|
|
55
48
|
addToTimeline(first);
|
|
56
49
|
return rest;
|
|
@@ -58,7 +51,7 @@ export const Main = ({ app, command }) => {
|
|
|
58
51
|
return currentQueue;
|
|
59
52
|
});
|
|
60
53
|
}, [addToTimeline]);
|
|
61
|
-
|
|
54
|
+
// Core abort handler
|
|
62
55
|
const handleAborted = React.useCallback((operationName) => {
|
|
63
56
|
setQueue((currentQueue) => {
|
|
64
57
|
if (currentQueue.length === 0)
|
|
@@ -71,86 +64,54 @@ export const Main = ({ app, command }) => {
|
|
|
71
64
|
return [];
|
|
72
65
|
});
|
|
73
66
|
}, [addToTimeline]);
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const createPlanAbortHandler = React.useCallback(createPlanAbortHandlerFactory(handleAborted, handlePlanAborted), [handleAborted, handlePlanAborted]);
|
|
77
|
-
const handleCommandAborted = React.useCallback(createCommandAbortedHandler(handleAborted), [handleAborted]);
|
|
78
|
-
const handleRefinementAborted = React.useCallback(() => {
|
|
79
|
-
handleAborted('Plan refinement');
|
|
80
|
-
}, [handleAborted]);
|
|
81
|
-
const handleIntrospectAborted = React.useCallback(createIntrospectAbortedHandler(handleAborted), [handleAborted]);
|
|
82
|
-
const handleIntrospectError = React.useCallback((error) => setQueue(createIntrospectErrorHandler(addToTimeline)(error)), [addToTimeline]);
|
|
83
|
-
const handleIntrospectComplete = React.useCallback((message, capabilities) => setQueue(createIntrospectCompleteHandler(addToTimeline)(message, capabilities)), [addToTimeline]);
|
|
84
|
-
const handleAnswerAborted = React.useCallback(createAnswerAbortedHandler(handleAborted), [handleAborted]);
|
|
85
|
-
const handleAnswerError = React.useCallback((error) => setQueue(createAnswerErrorHandler(addToTimeline)(error)), [addToTimeline]);
|
|
86
|
-
const handleAnswerComplete = React.useCallback((answer) => setQueue(createAnswerCompleteHandler(addToTimeline)(answer)), [addToTimeline]);
|
|
87
|
-
const handleExecutionConfirmed = React.useCallback(() => setQueue(createExecutionConfirmedHandler(timelineRef, addToTimeline, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted, handleAnswerError, handleAnswerComplete, handleAnswerAborted, setQueue)()), [
|
|
88
|
-
addToTimeline,
|
|
89
|
-
service,
|
|
90
|
-
handleIntrospectError,
|
|
91
|
-
handleIntrospectComplete,
|
|
92
|
-
handleIntrospectAborted,
|
|
93
|
-
handleAnswerError,
|
|
94
|
-
handleAnswerComplete,
|
|
95
|
-
handleAnswerAborted,
|
|
96
|
-
]);
|
|
97
|
-
const handleExecutionCancelled = React.useCallback(() => setQueue(createExecutionCancelledHandler(timelineRef, addToTimeline)()), [addToTimeline]);
|
|
98
|
-
const handlePlanSelectionConfirmed = React.useCallback(createPlanSelectionConfirmedHandler(addToTimeline, service, handleRefinementAborted, createPlanAbortHandler, handleExecutionConfirmed, handleExecutionCancelled, setQueue), [
|
|
67
|
+
// Create operations object
|
|
68
|
+
const ops = React.useMemo(() => ({
|
|
99
69
|
addToTimeline,
|
|
70
|
+
setQueue,
|
|
100
71
|
service,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
]);
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
]);
|
|
113
|
-
const
|
|
114
|
-
addToTimeline,
|
|
115
|
-
command,
|
|
116
|
-
handleCommandError,
|
|
117
|
-
handleCommandComplete,
|
|
118
|
-
handleCommandAborted,
|
|
119
|
-
]);
|
|
72
|
+
}), [addToTimeline, service]);
|
|
73
|
+
// Create handlers in dependency order
|
|
74
|
+
const introspectHandlers = React.useMemo(() => createIntrospectHandlers(ops, handleAborted), [ops, handleAborted]);
|
|
75
|
+
const answerHandlers = React.useMemo(() => createAnswerHandlers(ops, handleAborted), [ops, handleAborted]);
|
|
76
|
+
const executeHandlers = React.useMemo(() => createExecuteHandlers(ops, handleAborted), [ops, handleAborted]);
|
|
77
|
+
const executionHandlers = React.useMemo(() => createExecutionHandlers(ops, {
|
|
78
|
+
introspect: introspectHandlers,
|
|
79
|
+
answer: answerHandlers,
|
|
80
|
+
execute: executeHandlers,
|
|
81
|
+
}), [ops, introspectHandlers, answerHandlers, executeHandlers]);
|
|
82
|
+
const planHandlers = React.useMemo(() => createPlanHandlers(ops, handleAborted, executionHandlers), [ops, handleAborted, executionHandlers]);
|
|
83
|
+
const commandHandlers = React.useMemo(() => createCommandHandlers(ops, handleAborted, planHandlers, executionHandlers), [ops, handleAborted, planHandlers, executionHandlers]);
|
|
84
|
+
const configHandlers = React.useMemo(() => createConfigHandlers(ops, handleAborted, command, commandHandlers, setService), [ops, handleAborted, command, commandHandlers]);
|
|
120
85
|
// Initialize queue on mount
|
|
121
86
|
React.useEffect(() => {
|
|
122
87
|
const hasConfig = !!service;
|
|
123
88
|
if (command && hasConfig) {
|
|
124
|
-
// With command + valid config: [Command]
|
|
125
89
|
setQueue([
|
|
126
|
-
createCommandDefinition(command, service,
|
|
90
|
+
createCommandDefinition(command, service, commandHandlers.onError, commandHandlers.onComplete, commandHandlers.onAborted),
|
|
127
91
|
]);
|
|
128
92
|
}
|
|
129
93
|
else if (command && !hasConfig) {
|
|
130
|
-
// With command + no config: [Message, Config] (Command added after config)
|
|
131
94
|
setQueue([
|
|
132
95
|
createMessage(getConfigurationRequiredMessage()),
|
|
133
|
-
createConfigDefinition(
|
|
96
|
+
createConfigDefinition(configHandlers.onFinished, configHandlers.onAborted),
|
|
134
97
|
]);
|
|
135
98
|
}
|
|
136
99
|
else if (!command && hasConfig) {
|
|
137
|
-
// No command + valid config: [Welcome]
|
|
138
100
|
setQueue([createWelcomeDefinition(app)]);
|
|
139
101
|
}
|
|
140
102
|
else {
|
|
141
|
-
// No command + no config: [Welcome, Message, Config]
|
|
142
103
|
setQueue([
|
|
143
104
|
createWelcomeDefinition(app),
|
|
144
105
|
createMessage(getConfigurationRequiredMessage(true)),
|
|
145
|
-
createConfigDefinition(
|
|
106
|
+
createConfigDefinition(configHandlers.onFinished, configHandlers.onAborted),
|
|
146
107
|
]);
|
|
147
108
|
}
|
|
148
|
-
}, []);
|
|
109
|
+
}, []);
|
|
149
110
|
// Process queue whenever it changes
|
|
150
111
|
React.useEffect(() => {
|
|
151
112
|
processNextInQueue();
|
|
152
113
|
}, [queue, processNextInQueue]);
|
|
153
|
-
// Exit when queue is empty and timeline has content
|
|
114
|
+
// Exit when queue is empty and timeline has content
|
|
154
115
|
React.useEffect(() => {
|
|
155
116
|
if (queue.length === 0 && timeline.length > 0) {
|
|
156
117
|
exitApp(0);
|
package/dist/ui/Spinner.js
CHANGED
|
@@ -3,15 +3,20 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Text } from 'ink';
|
|
4
4
|
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
5
5
|
const INTERVAL = 80;
|
|
6
|
+
const CYCLE = FRAMES.length * INTERVAL;
|
|
7
|
+
function getFrame() {
|
|
8
|
+
return Math.floor((Date.now() % CYCLE) / INTERVAL);
|
|
9
|
+
}
|
|
6
10
|
export function Spinner() {
|
|
7
|
-
const [frame, setFrame] = useState(
|
|
11
|
+
const [frame, setFrame] = useState(getFrame);
|
|
8
12
|
useEffect(() => {
|
|
9
13
|
const timer = setInterval(() => {
|
|
10
|
-
setFrame((prev) =>
|
|
14
|
+
setFrame((prev) => {
|
|
15
|
+
const next = getFrame();
|
|
16
|
+
return next !== prev ? next : prev;
|
|
17
|
+
});
|
|
11
18
|
}, INTERVAL);
|
|
12
|
-
return () =>
|
|
13
|
-
clearInterval(timer);
|
|
14
|
-
};
|
|
19
|
+
return () => clearInterval(timer);
|
|
15
20
|
}, []);
|
|
16
21
|
return _jsx(Text, { color: "blueBright", children: FRAMES[frame] });
|
|
17
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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",
|
|
@@ -46,22 +46,22 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://github.com/aswitalski/pls#readme",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@anthropic-ai/sdk": "^0.
|
|
50
|
-
"ink": "^6.5.
|
|
49
|
+
"@anthropic-ai/sdk": "^0.70.1",
|
|
50
|
+
"ink": "^6.5.1",
|
|
51
51
|
"ink-text-input": "^6.0.0",
|
|
52
52
|
"react": "^19.2.0",
|
|
53
53
|
"yaml": "^2.8.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "^24.10.1",
|
|
57
|
-
"@types/react": "^19.2.
|
|
58
|
-
"@vitest/coverage-v8": "^4.0.
|
|
57
|
+
"@types/react": "^19.2.6",
|
|
58
|
+
"@vitest/coverage-v8": "^4.0.12",
|
|
59
59
|
"eslint": "^9.39.1",
|
|
60
60
|
"husky": "^9.1.7",
|
|
61
61
|
"ink-testing-library": "^4.0.0",
|
|
62
62
|
"prettier": "^3.6.2",
|
|
63
63
|
"typescript": "^5.9.3",
|
|
64
|
-
"typescript-eslint": "^8.
|
|
65
|
-
"vitest": "^4.0.
|
|
64
|
+
"typescript-eslint": "^8.47.0",
|
|
65
|
+
"vitest": "^4.0.12"
|
|
66
66
|
}
|
|
67
67
|
}
|