prompt-language-shell 0.8.8 → 0.9.0
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/configuration/types.js +3 -4
- package/dist/execution/handlers.js +20 -29
- package/dist/execution/processing.js +12 -1
- package/dist/execution/reducer.js +17 -37
- package/dist/execution/utils.js +6 -0
- package/dist/services/components.js +1 -2
- package/dist/services/filesystem.js +21 -1
- package/dist/services/messages.js +10 -16
- package/dist/services/process.js +7 -2
- package/dist/services/router.js +87 -57
- 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 +70 -29
- 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/Component.js +2 -2
- package/dist/ui/Execute.js +135 -112
- package/dist/ui/Output.js +54 -0
- package/dist/ui/Task.js +99 -10
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/configuration/io.js
CHANGED
|
@@ -61,11 +61,32 @@ export function mergeConfig(existingContent, sectionName, newValues) {
|
|
|
61
61
|
}
|
|
62
62
|
export function saveConfig(section, config, fs = defaultFileSystem) {
|
|
63
63
|
const configFile = getConfigFile();
|
|
64
|
+
const tempFile = `${configFile}.tmp`;
|
|
64
65
|
const existingContent = fs.exists(configFile)
|
|
65
66
|
? fs.readFile(configFile, 'utf-8')
|
|
66
67
|
: '';
|
|
67
68
|
const newContent = mergeConfig(existingContent, section, config);
|
|
68
|
-
|
|
69
|
+
try {
|
|
70
|
+
// Write to temp file first
|
|
71
|
+
fs.writeFile(tempFile, newContent);
|
|
72
|
+
// Validate the temp file can be parsed
|
|
73
|
+
const tempContent = fs.readFile(tempFile, 'utf-8');
|
|
74
|
+
parseYamlConfig(tempContent);
|
|
75
|
+
// Atomic rename (on POSIX systems)
|
|
76
|
+
fs.rename(tempFile, configFile);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// Clean up temp file if it exists
|
|
80
|
+
if (fs.exists(tempFile)) {
|
|
81
|
+
try {
|
|
82
|
+
fs.remove(tempFile);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Ignore cleanup errors
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
69
90
|
}
|
|
70
91
|
export function saveAnthropicConfig(config, fs = defaultFileSystem) {
|
|
71
92
|
saveConfig('anthropic', config, fs);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AppError, ErrorCode } from '../types/errors.js';
|
|
1
2
|
export var AnthropicModel;
|
|
2
3
|
(function (AnthropicModel) {
|
|
3
4
|
AnthropicModel["Sonnet"] = "claude-sonnet-4-5";
|
|
@@ -20,11 +21,9 @@ export var ConfigDefinitionType;
|
|
|
20
21
|
ConfigDefinitionType["Number"] = "number";
|
|
21
22
|
ConfigDefinitionType["Boolean"] = "boolean";
|
|
22
23
|
})(ConfigDefinitionType || (ConfigDefinitionType = {}));
|
|
23
|
-
export class ConfigError extends
|
|
24
|
-
origin;
|
|
24
|
+
export class ConfigError extends AppError {
|
|
25
25
|
constructor(message, origin) {
|
|
26
|
-
super(message);
|
|
26
|
+
super(message, ErrorCode.MissingConfig, origin);
|
|
27
27
|
this.name = 'ConfigError';
|
|
28
|
-
this.origin = origin;
|
|
29
28
|
}
|
|
30
29
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ExecutionStatus } from '../services/shell.js';
|
|
2
2
|
import { formatDuration } from '../services/utils.js';
|
|
3
3
|
import { ExecuteActionType, } from './types.js';
|
|
4
|
+
import { getTotalElapsed } from './utils.js';
|
|
4
5
|
/**
|
|
5
6
|
* Handles task completion logic and returns the appropriate action and state.
|
|
6
7
|
*/
|
|
7
8
|
export function handleTaskCompletion(index, elapsed, context) {
|
|
8
|
-
const {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
if (index < taskInfos.length - 1) {
|
|
9
|
+
const { tasks, message, summary } = context;
|
|
10
|
+
const updatedTaskInfos = tasks.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Success, elapsed } : task);
|
|
11
|
+
if (index < tasks.length - 1) {
|
|
12
12
|
// More tasks to execute
|
|
13
13
|
return {
|
|
14
14
|
action: {
|
|
@@ -18,9 +18,8 @@ export function handleTaskCompletion(index, elapsed, context) {
|
|
|
18
18
|
finalState: {
|
|
19
19
|
message,
|
|
20
20
|
summary,
|
|
21
|
-
|
|
21
|
+
tasks: updatedTaskInfos,
|
|
22
22
|
completed: index + 1,
|
|
23
|
-
taskExecutionTimes: updatedTimes,
|
|
24
23
|
completionMessage: null,
|
|
25
24
|
error: null,
|
|
26
25
|
},
|
|
@@ -29,7 +28,7 @@ export function handleTaskCompletion(index, elapsed, context) {
|
|
|
29
28
|
}
|
|
30
29
|
// All tasks complete
|
|
31
30
|
const summaryText = summary.trim() || 'Execution completed';
|
|
32
|
-
const totalElapsed =
|
|
31
|
+
const totalElapsed = getTotalElapsed(updatedTaskInfos);
|
|
33
32
|
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
34
33
|
return {
|
|
35
34
|
action: {
|
|
@@ -39,9 +38,8 @@ export function handleTaskCompletion(index, elapsed, context) {
|
|
|
39
38
|
finalState: {
|
|
40
39
|
message,
|
|
41
40
|
summary,
|
|
42
|
-
|
|
41
|
+
tasks: updatedTaskInfos,
|
|
43
42
|
completed: index + 1,
|
|
44
|
-
taskExecutionTimes: updatedTimes,
|
|
45
43
|
completionMessage: completion,
|
|
46
44
|
error: null,
|
|
47
45
|
},
|
|
@@ -52,10 +50,10 @@ export function handleTaskCompletion(index, elapsed, context) {
|
|
|
52
50
|
* Handles task error logic and returns the appropriate action and state.
|
|
53
51
|
*/
|
|
54
52
|
export function handleTaskFailure(index, error, elapsed, context) {
|
|
55
|
-
const {
|
|
56
|
-
const task =
|
|
53
|
+
const { tasks, message, summary } = context;
|
|
54
|
+
const task = tasks[index];
|
|
57
55
|
const isCritical = task.command.critical !== false; // Default to true
|
|
58
|
-
const updatedTaskInfos =
|
|
56
|
+
const updatedTaskInfos = tasks.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Failed, elapsed } : task);
|
|
59
57
|
if (isCritical) {
|
|
60
58
|
// Critical failure - stop execution
|
|
61
59
|
return {
|
|
@@ -66,19 +64,16 @@ export function handleTaskFailure(index, error, elapsed, context) {
|
|
|
66
64
|
finalState: {
|
|
67
65
|
message,
|
|
68
66
|
summary,
|
|
69
|
-
|
|
67
|
+
tasks: updatedTaskInfos,
|
|
70
68
|
completed: index + 1,
|
|
71
|
-
taskExecutionTimes,
|
|
72
69
|
completionMessage: null,
|
|
73
|
-
error,
|
|
70
|
+
error: null,
|
|
74
71
|
},
|
|
75
72
|
shouldComplete: true,
|
|
76
|
-
shouldReportError: true,
|
|
77
73
|
};
|
|
78
74
|
}
|
|
79
75
|
// Non-critical failure - continue to next task
|
|
80
|
-
|
|
81
|
-
if (index < taskInfos.length - 1) {
|
|
76
|
+
if (index < tasks.length - 1) {
|
|
82
77
|
return {
|
|
83
78
|
action: {
|
|
84
79
|
type: ExecuteActionType.TaskErrorContinue,
|
|
@@ -87,19 +82,18 @@ export function handleTaskFailure(index, error, elapsed, context) {
|
|
|
87
82
|
finalState: {
|
|
88
83
|
message,
|
|
89
84
|
summary,
|
|
90
|
-
|
|
85
|
+
tasks: updatedTaskInfos,
|
|
91
86
|
completed: index + 1,
|
|
92
|
-
taskExecutionTimes: updatedTimes,
|
|
93
87
|
completionMessage: null,
|
|
94
88
|
error: null,
|
|
95
89
|
},
|
|
96
90
|
shouldComplete: false,
|
|
97
|
-
shouldReportError: false,
|
|
98
91
|
};
|
|
99
92
|
}
|
|
100
|
-
// Last task, complete execution
|
|
93
|
+
// Last task failed (non-critical), complete execution
|
|
94
|
+
// Non-critical failures still show completion message with summary
|
|
101
95
|
const summaryText = summary.trim() || 'Execution completed';
|
|
102
|
-
const totalElapsed =
|
|
96
|
+
const totalElapsed = getTotalElapsed(updatedTaskInfos);
|
|
103
97
|
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
104
98
|
return {
|
|
105
99
|
action: {
|
|
@@ -109,26 +103,23 @@ export function handleTaskFailure(index, error, elapsed, context) {
|
|
|
109
103
|
finalState: {
|
|
110
104
|
message,
|
|
111
105
|
summary,
|
|
112
|
-
|
|
106
|
+
tasks: updatedTaskInfos,
|
|
113
107
|
completed: index + 1,
|
|
114
|
-
taskExecutionTimes: updatedTimes,
|
|
115
108
|
completionMessage: completion,
|
|
116
109
|
error: null,
|
|
117
110
|
},
|
|
118
111
|
shouldComplete: true,
|
|
119
|
-
shouldReportError: false,
|
|
120
112
|
};
|
|
121
113
|
}
|
|
122
114
|
/**
|
|
123
115
|
* Builds final state for task abortion.
|
|
124
116
|
*/
|
|
125
|
-
export function buildAbortedState(
|
|
117
|
+
export function buildAbortedState(tasks, message, summary, completed) {
|
|
126
118
|
return {
|
|
127
119
|
message,
|
|
128
120
|
summary,
|
|
129
|
-
|
|
121
|
+
tasks,
|
|
130
122
|
completed,
|
|
131
|
-
taskExecutionTimes,
|
|
132
123
|
completionMessage: null,
|
|
133
124
|
error: null,
|
|
134
125
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { loadUserConfig } from '../services/loader.js';
|
|
2
2
|
import { replacePlaceholders } from '../services/resolver.js';
|
|
3
3
|
import { validatePlaceholderResolution } from './validation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fix escaped quotes in commands
|
|
6
|
+
* JSON parsing removes backslashes before quotes in patterns like key="value"
|
|
7
|
+
* This restores them: key="value" -> key=\"value\"
|
|
8
|
+
*/
|
|
9
|
+
export function fixEscapedQuotes(command) {
|
|
10
|
+
// Replace ="value" with =\"value\"
|
|
11
|
+
return command.replace(/="([^"]*)"/g, '=\\"$1\\"');
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* Processes tasks through the AI service to generate executable commands.
|
|
6
15
|
* Resolves placeholders in task descriptions and validates the results.
|
|
@@ -22,7 +31,9 @@ export async function processTasks(tasks, service) {
|
|
|
22
31
|
const result = await service.processWithTool(taskDescriptions, 'execute');
|
|
23
32
|
// Resolve placeholders in command strings
|
|
24
33
|
const resolvedCommands = (result.commands || []).map((cmd) => {
|
|
25
|
-
|
|
34
|
+
// Fix escaped quotes lost in JSON parsing
|
|
35
|
+
const fixed = fixEscapedQuotes(cmd.command);
|
|
36
|
+
const resolved = replacePlaceholders(fixed, userConfig);
|
|
26
37
|
validatePlaceholderResolution(resolved);
|
|
27
38
|
return { ...cmd, command: resolved };
|
|
28
39
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ExecutionStatus } from '../services/shell.js';
|
|
2
2
|
import { formatDuration } from '../services/utils.js';
|
|
3
3
|
import { ExecuteActionType, } from './types.js';
|
|
4
|
+
import { getTotalElapsed } from './utils.js';
|
|
4
5
|
export const initialState = {
|
|
5
6
|
error: null,
|
|
6
|
-
|
|
7
|
+
tasks: [],
|
|
7
8
|
message: '',
|
|
8
9
|
completed: 0,
|
|
9
10
|
hasProcessed: false,
|
|
10
|
-
taskExecutionTimes: [],
|
|
11
11
|
completionMessage: null,
|
|
12
12
|
summary: '',
|
|
13
13
|
};
|
|
@@ -24,7 +24,7 @@ export function executeReducer(state, action) {
|
|
|
24
24
|
...state,
|
|
25
25
|
message: action.payload.message,
|
|
26
26
|
summary: action.payload.summary,
|
|
27
|
-
|
|
27
|
+
tasks: action.payload.tasks,
|
|
28
28
|
completed: 0,
|
|
29
29
|
};
|
|
30
30
|
case ExecuteActionType.ProcessingError:
|
|
@@ -34,11 +34,7 @@ export function executeReducer(state, action) {
|
|
|
34
34
|
hasProcessed: true,
|
|
35
35
|
};
|
|
36
36
|
case ExecuteActionType.TaskComplete: {
|
|
37
|
-
const
|
|
38
|
-
...state.taskExecutionTimes,
|
|
39
|
-
action.payload.elapsed,
|
|
40
|
-
];
|
|
41
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
37
|
+
const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
|
|
42
38
|
? {
|
|
43
39
|
...task,
|
|
44
40
|
status: ExecutionStatus.Success,
|
|
@@ -47,49 +43,39 @@ export function executeReducer(state, action) {
|
|
|
47
43
|
: task);
|
|
48
44
|
return {
|
|
49
45
|
...state,
|
|
50
|
-
|
|
51
|
-
taskExecutionTimes: updatedTimes,
|
|
46
|
+
tasks: updatedTaskInfos,
|
|
52
47
|
completed: action.payload.index + 1,
|
|
53
48
|
};
|
|
54
49
|
}
|
|
55
50
|
case ExecuteActionType.AllTasksComplete: {
|
|
56
|
-
const
|
|
57
|
-
...state.taskExecutionTimes,
|
|
58
|
-
action.payload.elapsed,
|
|
59
|
-
];
|
|
60
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
51
|
+
const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
|
|
61
52
|
? {
|
|
62
53
|
...task,
|
|
63
54
|
status: ExecutionStatus.Success,
|
|
64
55
|
elapsed: action.payload.elapsed,
|
|
65
56
|
}
|
|
66
57
|
: task);
|
|
67
|
-
const totalElapsed =
|
|
58
|
+
const totalElapsed = getTotalElapsed(updatedTaskInfos);
|
|
68
59
|
const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
69
60
|
return {
|
|
70
61
|
...state,
|
|
71
|
-
|
|
72
|
-
taskExecutionTimes: updatedTimes,
|
|
62
|
+
tasks: updatedTaskInfos,
|
|
73
63
|
completed: action.payload.index + 1,
|
|
74
64
|
completionMessage: completion,
|
|
75
65
|
};
|
|
76
66
|
}
|
|
77
67
|
case ExecuteActionType.TaskErrorCritical: {
|
|
78
|
-
const updatedTaskInfos = state.
|
|
68
|
+
const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
|
|
79
69
|
? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
|
|
80
70
|
: task);
|
|
81
71
|
return {
|
|
82
72
|
...state,
|
|
83
|
-
|
|
73
|
+
tasks: updatedTaskInfos,
|
|
84
74
|
error: action.payload.error,
|
|
85
75
|
};
|
|
86
76
|
}
|
|
87
77
|
case ExecuteActionType.TaskErrorContinue: {
|
|
88
|
-
const
|
|
89
|
-
...state.taskExecutionTimes,
|
|
90
|
-
action.payload.elapsed,
|
|
91
|
-
];
|
|
92
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
78
|
+
const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
|
|
93
79
|
? {
|
|
94
80
|
...task,
|
|
95
81
|
status: ExecutionStatus.Failed,
|
|
@@ -98,35 +84,29 @@ export function executeReducer(state, action) {
|
|
|
98
84
|
: task);
|
|
99
85
|
return {
|
|
100
86
|
...state,
|
|
101
|
-
|
|
102
|
-
taskExecutionTimes: updatedTimes,
|
|
87
|
+
tasks: updatedTaskInfos,
|
|
103
88
|
completed: action.payload.index + 1,
|
|
104
89
|
};
|
|
105
90
|
}
|
|
106
91
|
case ExecuteActionType.LastTaskError: {
|
|
107
|
-
const
|
|
108
|
-
...state.taskExecutionTimes,
|
|
109
|
-
action.payload.elapsed,
|
|
110
|
-
];
|
|
111
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
92
|
+
const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
|
|
112
93
|
? {
|
|
113
94
|
...task,
|
|
114
95
|
status: ExecutionStatus.Failed,
|
|
115
96
|
elapsed: action.payload.elapsed,
|
|
116
97
|
}
|
|
117
98
|
: task);
|
|
118
|
-
const totalElapsed =
|
|
99
|
+
const totalElapsed = getTotalElapsed(updatedTaskInfos);
|
|
119
100
|
const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
120
101
|
return {
|
|
121
102
|
...state,
|
|
122
|
-
|
|
123
|
-
taskExecutionTimes: updatedTimes,
|
|
103
|
+
tasks: updatedTaskInfos,
|
|
124
104
|
completed: action.payload.index + 1,
|
|
125
105
|
completionMessage: completion,
|
|
126
106
|
};
|
|
127
107
|
}
|
|
128
108
|
case ExecuteActionType.CancelExecution: {
|
|
129
|
-
const updatedTaskInfos = state.
|
|
109
|
+
const updatedTaskInfos = state.tasks.map((task, taskIndex) => {
|
|
130
110
|
if (taskIndex < action.payload.completed) {
|
|
131
111
|
return { ...task, status: ExecutionStatus.Success };
|
|
132
112
|
}
|
|
@@ -139,7 +119,7 @@ export function executeReducer(state, action) {
|
|
|
139
119
|
});
|
|
140
120
|
return {
|
|
141
121
|
...state,
|
|
142
|
-
|
|
122
|
+
tasks: updatedTaskInfos,
|
|
143
123
|
};
|
|
144
124
|
}
|
|
145
125
|
default:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'fs';
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'fs';
|
|
2
2
|
import { dirname } from 'path';
|
|
3
3
|
/**
|
|
4
4
|
* Real filesystem implementation using Node's fs module
|
|
@@ -19,6 +19,12 @@ export class RealFileSystem {
|
|
|
19
19
|
createDirectory(path, options) {
|
|
20
20
|
mkdirSync(path, options);
|
|
21
21
|
}
|
|
22
|
+
rename(oldPath, newPath) {
|
|
23
|
+
renameSync(oldPath, newPath);
|
|
24
|
+
}
|
|
25
|
+
remove(path) {
|
|
26
|
+
unlinkSync(path);
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
30
|
* In-memory filesystem implementation for testing
|
|
@@ -93,6 +99,20 @@ export class MemoryFileSystem {
|
|
|
93
99
|
this.directories.add(path);
|
|
94
100
|
}
|
|
95
101
|
}
|
|
102
|
+
rename(oldPath, newPath) {
|
|
103
|
+
const content = this.files.get(oldPath);
|
|
104
|
+
if (content === undefined) {
|
|
105
|
+
throw new Error(`ENOENT: no such file or directory, rename '${oldPath}'`);
|
|
106
|
+
}
|
|
107
|
+
this.files.delete(oldPath);
|
|
108
|
+
this.files.set(newPath, content);
|
|
109
|
+
}
|
|
110
|
+
remove(path) {
|
|
111
|
+
if (!this.files.has(path)) {
|
|
112
|
+
throw new Error(`ENOENT: no such file or directory, unlink '${path}'`);
|
|
113
|
+
}
|
|
114
|
+
this.files.delete(path);
|
|
115
|
+
}
|
|
96
116
|
/**
|
|
97
117
|
* Clear all files and directories (useful for test cleanup)
|
|
98
118
|
*/
|
|
@@ -149,22 +149,16 @@ export function formatErrorMessage(error) {
|
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
151
151
|
* Returns an execution error message with varied phrasing.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
152
|
+
* Error details are shown in the task output, so this is just a summary.
|
|
153
|
+
* Randomly selects from variations to sound natural.
|
|
154
154
|
*/
|
|
155
|
-
export function getExecutionErrorMessage(
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
'
|
|
155
|
+
export function getExecutionErrorMessage(_error) {
|
|
156
|
+
const messages = [
|
|
157
|
+
'The execution failed.',
|
|
158
|
+
'Execution has failed.',
|
|
159
|
+
'The execution was not successful.',
|
|
160
|
+
'Execution did not succeed.',
|
|
161
|
+
'The execution encountered an error.',
|
|
162
162
|
];
|
|
163
|
-
|
|
164
|
-
// Capitalize first letter of error
|
|
165
|
-
const capitalizedError = error.charAt(0).toUpperCase() + error.slice(1);
|
|
166
|
-
const errorWithPeriod = capitalizedError.endsWith('.')
|
|
167
|
-
? capitalizedError
|
|
168
|
-
: `${capitalizedError}.`;
|
|
169
|
-
return `${prefix}. ${errorWithPeriod}`;
|
|
163
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
170
164
|
}
|
package/dist/services/process.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
export const defaultProcessControl = {
|
|
2
|
+
exit: (code) => process.exit(code),
|
|
3
|
+
};
|
|
1
4
|
/**
|
|
2
5
|
* Exit application after brief delay to allow UI to render
|
|
3
6
|
*/
|
|
4
|
-
export function exitApp(code) {
|
|
5
|
-
setTimeout(() =>
|
|
7
|
+
export function exitApp(code, processControl = defaultProcessControl) {
|
|
8
|
+
setTimeout(() => {
|
|
9
|
+
processControl.exit(code);
|
|
10
|
+
}, 100);
|
|
6
11
|
}
|