prompt-language-shell 0.8.8 → 0.9.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/README.md +0 -1
- package/dist/configuration/io.js +22 -1
- package/dist/{services/config-labels.js → configuration/labels.js} +1 -1
- package/dist/configuration/schema.js +2 -2
- package/dist/configuration/steps.js +171 -0
- package/dist/configuration/transformation.js +17 -0
- package/dist/configuration/types.js +3 -4
- package/dist/execution/handlers.js +20 -35
- package/dist/execution/hooks.js +291 -0
- package/dist/execution/processing.js +15 -2
- package/dist/execution/reducer.js +30 -48
- package/dist/execution/runner.js +81 -0
- package/dist/execution/types.js +1 -0
- package/dist/execution/utils.js +28 -0
- package/dist/services/components.js +109 -395
- package/dist/services/filesystem.js +21 -1
- package/dist/services/logger.js +3 -3
- package/dist/services/messages.js +10 -16
- package/dist/services/process.js +7 -2
- package/dist/services/refinement.js +5 -2
- package/dist/services/router.js +120 -67
- package/dist/services/shell.js +179 -10
- package/dist/services/skills.js +2 -1
- package/dist/skills/answer.md +14 -12
- package/dist/skills/execute.md +98 -39
- package/dist/skills/introspect.md +9 -9
- package/dist/skills/schedule.md +0 -6
- package/dist/types/errors.js +47 -0
- package/dist/types/result.js +40 -0
- package/dist/ui/Command.js +11 -7
- package/dist/ui/Component.js +6 -3
- package/dist/ui/Config.js +9 -3
- package/dist/ui/Execute.js +249 -163
- package/dist/ui/Introspect.js +13 -14
- package/dist/ui/List.js +2 -2
- package/dist/ui/Main.js +14 -7
- package/dist/ui/Output.js +54 -0
- package/dist/ui/Schedule.js +3 -1
- package/dist/ui/Subtask.js +6 -3
- package/dist/ui/Task.js +10 -85
- package/dist/ui/Validate.js +26 -21
- package/dist/ui/Workflow.js +21 -4
- package/package.json +1 -1
- package/dist/parser.js +0 -13
- package/dist/services/config-utils.js +0 -20
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Palette } from '../services/colors.js';
|
|
4
|
+
import { ExecutionStatus } from '../services/shell.js';
|
|
5
|
+
const MAX_LINES = 8;
|
|
6
|
+
const MAX_WIDTH = 75;
|
|
7
|
+
const SHORT_OUTPUT_THRESHOLD = 4;
|
|
8
|
+
const MINIMAL_INFO_THRESHOLD = 2;
|
|
9
|
+
/**
|
|
10
|
+
* Get the last N lines from text, filtering out empty/whitespace-only lines
|
|
11
|
+
*/
|
|
12
|
+
export function getLastLines(text, maxLines = MAX_LINES) {
|
|
13
|
+
const lines = text
|
|
14
|
+
.trim()
|
|
15
|
+
.split(/\r?\n/)
|
|
16
|
+
.filter((line) => line.trim().length > 0);
|
|
17
|
+
return lines.length <= maxLines ? lines : lines.slice(-maxLines);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compute display configuration for output rendering.
|
|
21
|
+
* Encapsulates the logic for what to show and how to style it.
|
|
22
|
+
*/
|
|
23
|
+
export function computeDisplayConfig(stdout, stderr, status, isFinished) {
|
|
24
|
+
const hasStdout = stdout.trim().length > 0;
|
|
25
|
+
const hasStderr = stderr.trim().length > 0;
|
|
26
|
+
if (!hasStdout && !hasStderr)
|
|
27
|
+
return null;
|
|
28
|
+
const stdoutLines = hasStdout ? getLastLines(stdout) : [];
|
|
29
|
+
const stderrLines = hasStderr ? getLastLines(stderr) : [];
|
|
30
|
+
// Show stdout if no stderr, or if stderr is minimal (provides context)
|
|
31
|
+
const showStdout = hasStdout && (!hasStderr || stderrLines.length <= MINIMAL_INFO_THRESHOLD);
|
|
32
|
+
// Use word wrapping for short outputs to show more detail
|
|
33
|
+
const totalLines = stdoutLines.length + stderrLines.length;
|
|
34
|
+
const wrapMode = totalLines <= SHORT_OUTPUT_THRESHOLD ? 'wrap' : 'truncate-end';
|
|
35
|
+
// Darker colors for finished tasks
|
|
36
|
+
const baseColor = isFinished ? Palette.DarkGray : Palette.Gray;
|
|
37
|
+
const stderrColor = status === ExecutionStatus.Failed ? Palette.Yellow : baseColor;
|
|
38
|
+
return {
|
|
39
|
+
stdoutLines,
|
|
40
|
+
stderrLines,
|
|
41
|
+
showStdout,
|
|
42
|
+
wrapMode,
|
|
43
|
+
stdoutColor: baseColor,
|
|
44
|
+
stderrColor,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function Output({ stdout, stderr, status, isFinished }) {
|
|
48
|
+
const config = computeDisplayConfig(stdout, stderr, status, isFinished ?? false);
|
|
49
|
+
if (!config)
|
|
50
|
+
return null;
|
|
51
|
+
const { stdoutLines, stderrLines, showStdout, wrapMode, stdoutColor, stderrColor, } = config;
|
|
52
|
+
return (_jsxs(Box, { marginTop: 1, marginLeft: 5, flexDirection: "column", width: MAX_WIDTH, children: [showStdout &&
|
|
53
|
+
stdoutLines.map((line, index) => (_jsx(Text, { color: stdoutColor, wrap: wrapMode, children: line }, `out-${index}`))), stderrLines.map((line, index) => (_jsx(Text, { color: stderrColor, wrap: wrapMode, children: line }, `err-${index}`)))] }));
|
|
54
|
+
}
|
package/dist/ui/Schedule.js
CHANGED
|
@@ -79,6 +79,8 @@ export function taskToListItem(task, highlightedChildIndex = null, isDefineTaskW
|
|
|
79
79
|
export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel.None, }) => {
|
|
80
80
|
const isActive = status === ComponentStatus.Active;
|
|
81
81
|
const { highlightedIndex, currentDefineGroupIndex, completedSelections } = state;
|
|
82
|
+
// Use compact mode when all tasks are Config type
|
|
83
|
+
const isCompact = tasks.every((task) => task.type === TaskType.Config);
|
|
82
84
|
// Find all Define tasks
|
|
83
85
|
const defineTaskIndices = tasks
|
|
84
86
|
.map((t, idx) => (t.type === TaskType.Define ? idx : -1))
|
|
@@ -114,7 +116,7 @@ export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel
|
|
|
114
116
|
isActive;
|
|
115
117
|
return taskToListItem(task, childIndex, isDefineWithoutSelection, status, debug);
|
|
116
118
|
});
|
|
117
|
-
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None, status: status, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None }) })] }));
|
|
119
|
+
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None, status: status, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None, compact: isCompact }) })] }));
|
|
118
120
|
};
|
|
119
121
|
/**
|
|
120
122
|
* Schedule controller: Manages task selection and navigation
|
package/dist/ui/Subtask.js
CHANGED
|
@@ -4,7 +4,11 @@ import { getStatusColors, Palette, STATUS_ICONS } from '../services/colors.js';
|
|
|
4
4
|
import { ExecutionStatus } from '../services/shell.js';
|
|
5
5
|
import { formatDuration } from '../services/utils.js';
|
|
6
6
|
import { Spinner } from './Spinner.js';
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Pure display component for a single subtask.
|
|
9
|
+
* Shows label, command, status icon, and elapsed time.
|
|
10
|
+
*/
|
|
11
|
+
export function SubtaskView({ label, command, status, elapsed, }) {
|
|
8
12
|
const colors = getStatusColors(status);
|
|
9
13
|
const isCancelled = status === ExecutionStatus.Cancelled;
|
|
10
14
|
const isAborted = status === ExecutionStatus.Aborted;
|
|
@@ -12,11 +16,10 @@ export function Subtask({ label, command, status, isActive: _isActive, startTime
|
|
|
12
16
|
const isFinished = status === ExecutionStatus.Success ||
|
|
13
17
|
status === ExecutionStatus.Failed ||
|
|
14
18
|
status === ExecutionStatus.Aborted;
|
|
15
|
-
const elapsedTime = elapsed ?? (startTime && endTime ? endTime - startTime : undefined);
|
|
16
19
|
// Apply strikethrough for cancelled and aborted tasks
|
|
17
20
|
const formatText = (text) => shouldStrikethrough ? text.split('').join('\u0336') + '\u0336' : text;
|
|
18
21
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[status] }), _jsx(Text, { color: colors.description, children: shouldStrikethrough
|
|
19
22
|
? formatText(label || command.description)
|
|
20
23
|
: label || command.description }), (isFinished || status === ExecutionStatus.Running) &&
|
|
21
|
-
|
|
24
|
+
elapsed !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: ["(", formatDuration(elapsed), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, flexDirection: "row", children: [_jsx(Box, { children: _jsx(Text, { color: colors.symbol, children: "\u221F " }) }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: colors.command, children: command.command }), status === ExecutionStatus.Running && _jsx(Spinner, {})] })] })] }));
|
|
22
25
|
}
|
package/dist/ui/Task.js
CHANGED
|
@@ -1,86 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const [currentElapsed, setCurrentElapsed] = useState(0);
|
|
12
|
-
// Update elapsed time while running
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (status !== ExecutionStatus.Running || !startTime)
|
|
15
|
-
return;
|
|
16
|
-
const interval = setInterval(() => {
|
|
17
|
-
setCurrentElapsed((prev) => {
|
|
18
|
-
const next = Date.now() - startTime;
|
|
19
|
-
return next !== prev ? next : prev;
|
|
20
|
-
});
|
|
21
|
-
}, 1000);
|
|
22
|
-
return () => {
|
|
23
|
-
clearInterval(interval);
|
|
24
|
-
};
|
|
25
|
-
}, [status, startTime]);
|
|
26
|
-
// Execute command when becoming active
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
// Don't execute if task is cancelled or if not active
|
|
29
|
-
if (!isActive ||
|
|
30
|
-
status === ExecutionStatus.Cancelled ||
|
|
31
|
-
status !== ExecutionStatus.Pending) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
let mounted = true;
|
|
35
|
-
async function execute() {
|
|
36
|
-
const start = Date.now();
|
|
37
|
-
setStatus(ExecutionStatus.Running);
|
|
38
|
-
setStartTime(start);
|
|
39
|
-
setCurrentElapsed(0);
|
|
40
|
-
try {
|
|
41
|
-
const output = await executeCommand(command, undefined, index);
|
|
42
|
-
if (!mounted)
|
|
43
|
-
return;
|
|
44
|
-
const end = Date.now();
|
|
45
|
-
setEndTime(end);
|
|
46
|
-
const taskDuration = calculateElapsed(start);
|
|
47
|
-
setElapsed(taskDuration);
|
|
48
|
-
setStatus(output.result === ExecutionResult.Success
|
|
49
|
-
? ExecutionStatus.Success
|
|
50
|
-
: ExecutionStatus.Failed);
|
|
51
|
-
if (output.result === ExecutionResult.Success) {
|
|
52
|
-
onComplete?.(index, output, taskDuration);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
onError?.(index, output.errors || 'Command failed', taskDuration);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
if (!mounted)
|
|
60
|
-
return;
|
|
61
|
-
const end = Date.now();
|
|
62
|
-
setEndTime(end);
|
|
63
|
-
const errorDuration = calculateElapsed(start);
|
|
64
|
-
setElapsed(errorDuration);
|
|
65
|
-
setStatus(ExecutionStatus.Failed);
|
|
66
|
-
onError?.(index, err instanceof Error ? err.message : 'Unknown error', errorDuration);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
void execute();
|
|
70
|
-
return () => {
|
|
71
|
-
mounted = false;
|
|
72
|
-
};
|
|
73
|
-
}, [isActive]);
|
|
74
|
-
// Handle abort when task becomes inactive while running
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (!isActive && status === ExecutionStatus.Running && startTime) {
|
|
77
|
-
// Task was aborted mid-execution
|
|
78
|
-
const end = Date.now();
|
|
79
|
-
setEndTime(end);
|
|
80
|
-
setElapsed(calculateElapsed(startTime));
|
|
81
|
-
setStatus(ExecutionStatus.Aborted);
|
|
82
|
-
onAbort?.(index);
|
|
83
|
-
}
|
|
84
|
-
}, [isActive, status, startTime, index, onAbort]);
|
|
85
|
-
return (_jsx(Subtask, { label: label, command: command, status: status, isActive: isActive, startTime: startTime, endTime: endTime, elapsed: status === ExecutionStatus.Running ? currentElapsed : elapsed }));
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { Output } from './Output.js';
|
|
4
|
+
import { SubtaskView } from './Subtask.js';
|
|
5
|
+
/**
|
|
6
|
+
* Pure display component for a task.
|
|
7
|
+
* Combines SubtaskView (label/command/status) with Output (stdout/stderr).
|
|
8
|
+
*/
|
|
9
|
+
export function TaskView({ label, command, status, elapsed, stdout, stderr, isFinished, }) {
|
|
10
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SubtaskView, { label: label, command: command, status: status, elapsed: elapsed }), _jsx(Output, { stdout: stdout, stderr: stderr, isFinished: isFinished, status: status }, `${stdout.length}-${stderr.length}`)] }));
|
|
86
11
|
}
|
package/dist/ui/Validate.js
CHANGED
|
@@ -4,10 +4,11 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { TaskType } from '../types/types.js';
|
|
6
6
|
import { saveConfig } from '../configuration/io.js';
|
|
7
|
+
import { createConfigStepsFromSchema } from '../configuration/steps.js';
|
|
7
8
|
import { unflattenConfig } from '../configuration/transformation.js';
|
|
8
9
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
9
|
-
import {
|
|
10
|
-
import { saveConfigLabels } from '../
|
|
10
|
+
import { createConfig, createMessage } from '../services/components.js';
|
|
11
|
+
import { saveConfigLabels } from '../configuration/labels.js';
|
|
11
12
|
import { useInput } from '../services/keyboard.js';
|
|
12
13
|
import { formatErrorMessage, getUnresolvedPlaceholdersMessage, } from '../services/messages.js';
|
|
13
14
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
@@ -73,28 +74,32 @@ export function Validate({ missingConfig, userRequest, status, service, onError,
|
|
|
73
74
|
setCompletionMessage(message);
|
|
74
75
|
setConfigRequirements(withDescriptions);
|
|
75
76
|
// Add validation message to timeline before Config component
|
|
76
|
-
workflowHandlers.addToTimeline(createMessage(message));
|
|
77
|
+
workflowHandlers.addToTimeline(createMessage({ text: message }));
|
|
77
78
|
// Create Config component and add to queue
|
|
78
79
|
const keys = withDescriptions.map((req) => req.path);
|
|
79
|
-
const configDef =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
const configDef = createConfig({
|
|
81
|
+
steps: createConfigStepsFromSchema(keys),
|
|
82
|
+
onFinished: (config) => {
|
|
83
|
+
// Convert flat dotted keys to nested structure grouped by section
|
|
84
|
+
const configBySection = unflattenConfig(config);
|
|
85
|
+
// Extract and save labels to cache
|
|
86
|
+
const labels = {};
|
|
87
|
+
for (const req of withDescriptions) {
|
|
88
|
+
if (req.description) {
|
|
89
|
+
labels[req.path] = req.description;
|
|
90
|
+
}
|
|
87
91
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
saveConfigLabels(labels);
|
|
93
|
+
// Save each section
|
|
94
|
+
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
95
|
+
saveConfig(section, sectionConfig);
|
|
96
|
+
}
|
|
97
|
+
// After config is saved, invoke callback to add Execute component to queue
|
|
98
|
+
onValidationComplete(withDescriptions);
|
|
99
|
+
},
|
|
100
|
+
onAborted: (operation) => {
|
|
101
|
+
onAborted(operation);
|
|
102
|
+
},
|
|
98
103
|
});
|
|
99
104
|
// Override descriptions with LLM-generated ones
|
|
100
105
|
if ('props' in configDef && 'steps' in configDef.props) {
|
package/dist/ui/Workflow.js
CHANGED
|
@@ -3,11 +3,19 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { Box, Static } from 'ink';
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { ComponentName, FeedbackType } from '../types/types.js';
|
|
6
|
-
import { createFeedback
|
|
6
|
+
import { createFeedback } from '../services/components.js';
|
|
7
7
|
import { getWarnings } from '../services/logger.js';
|
|
8
8
|
import { getCancellationMessage } from '../services/messages.js';
|
|
9
9
|
import { exitApp } from '../services/process.js';
|
|
10
10
|
import { SimpleComponent, ControllerComponent, TimelineComponent, } from './Component.js';
|
|
11
|
+
/**
|
|
12
|
+
* Mark a component as done. Returns the component to be added to timeline.
|
|
13
|
+
* Components use handlers.updateState to save their state before completion,
|
|
14
|
+
* so this function sets the status to Done and returns the updated component.
|
|
15
|
+
*/
|
|
16
|
+
function markAsDone(component) {
|
|
17
|
+
return { ...component, status: ComponentStatus.Done };
|
|
18
|
+
}
|
|
11
19
|
export const Workflow = ({ initialQueue, debug }) => {
|
|
12
20
|
const [timeline, setTimeline] = useState([]);
|
|
13
21
|
const [current, setCurrent] = useState({ active: null, pending: null });
|
|
@@ -42,14 +50,14 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
42
50
|
// Add feedback to queue
|
|
43
51
|
setQueue((queue) => [
|
|
44
52
|
...queue,
|
|
45
|
-
createFeedback(FeedbackType.Failed, error),
|
|
53
|
+
createFeedback({ type: FeedbackType.Failed, message: error }),
|
|
46
54
|
]);
|
|
47
55
|
},
|
|
48
56
|
onAborted: (operation) => {
|
|
49
57
|
moveActiveToTimeline();
|
|
50
58
|
// Clear queue and add only feedback to prevent subsequent components from executing
|
|
51
59
|
const message = getCancellationMessage(operation);
|
|
52
|
-
setQueue([createFeedback(FeedbackType.Aborted, message)]);
|
|
60
|
+
setQueue([createFeedback({ type: FeedbackType.Aborted, message })]);
|
|
53
61
|
},
|
|
54
62
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
55
63
|
onCompleted: (finalState) => {
|
|
@@ -144,7 +152,7 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
144
152
|
useEffect(() => {
|
|
145
153
|
const warningMessages = getWarnings();
|
|
146
154
|
if (warningMessages.length > 0) {
|
|
147
|
-
const warningComponents = warningMessages.map((msg) =>
|
|
155
|
+
const warningComponents = warningMessages.map((msg) => createFeedback({ type: FeedbackType.Warning, message: msg }, ComponentStatus.Done));
|
|
148
156
|
setTimeline((prev) => [...prev, ...warningComponents]);
|
|
149
157
|
}
|
|
150
158
|
}, [timeline, current]);
|
|
@@ -187,3 +195,12 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
187
195
|
const pendingComponent = useMemo(() => renderComponent(current.pending, ComponentStatus.Pending), [current.pending, renderComponent]);
|
|
188
196
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => (_jsx(Box, { marginTop: 1, children: _jsx(TimelineComponent, { def: item }) }, item.id)) }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
|
|
189
197
|
};
|
|
198
|
+
/**
|
|
199
|
+
* Check if a component is stateless (simple).
|
|
200
|
+
* Stateless components are display-only and complete immediately without
|
|
201
|
+
* tracking internal state. Stateful components manage user interaction
|
|
202
|
+
* and maintain state across their lifecycle.
|
|
203
|
+
*/
|
|
204
|
+
export function isSimple(component) {
|
|
205
|
+
return !('state' in component);
|
|
206
|
+
}
|
package/package.json
CHANGED
package/dist/parser.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parses a comma-separated list of tasks from command-line prompt
|
|
3
|
-
* Strips exclamation marks and periods from each task
|
|
4
|
-
*
|
|
5
|
-
* @param prompt - Raw command-line input string
|
|
6
|
-
* @returns Array of parsed task strings
|
|
7
|
-
*/
|
|
8
|
-
export function parseCommands(prompt) {
|
|
9
|
-
return prompt
|
|
10
|
-
.split(',')
|
|
11
|
-
.map((task) => task.trim().replace(/[!.]/g, '').trim())
|
|
12
|
-
.filter((task) => task.length > 0);
|
|
13
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility functions for config manipulation
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Flatten nested config object to dot notation
|
|
6
|
-
* Example: { a: { b: 1 } } => { 'a.b': 1 }
|
|
7
|
-
*/
|
|
8
|
-
export function flattenConfig(obj, prefix = '') {
|
|
9
|
-
const result = {};
|
|
10
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
11
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
12
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
13
|
-
Object.assign(result, flattenConfig(value, fullKey));
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
result[fullKey] = value;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return result;
|
|
20
|
-
}
|