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.
Files changed (45) hide show
  1. package/README.md +0 -1
  2. package/dist/configuration/io.js +22 -1
  3. package/dist/{services/config-labels.js → configuration/labels.js} +1 -1
  4. package/dist/configuration/schema.js +2 -2
  5. package/dist/configuration/steps.js +171 -0
  6. package/dist/configuration/transformation.js +17 -0
  7. package/dist/configuration/types.js +3 -4
  8. package/dist/execution/handlers.js +20 -35
  9. package/dist/execution/hooks.js +291 -0
  10. package/dist/execution/processing.js +15 -2
  11. package/dist/execution/reducer.js +30 -48
  12. package/dist/execution/runner.js +81 -0
  13. package/dist/execution/types.js +1 -0
  14. package/dist/execution/utils.js +28 -0
  15. package/dist/services/components.js +109 -395
  16. package/dist/services/filesystem.js +21 -1
  17. package/dist/services/logger.js +3 -3
  18. package/dist/services/messages.js +10 -16
  19. package/dist/services/process.js +7 -2
  20. package/dist/services/refinement.js +5 -2
  21. package/dist/services/router.js +120 -67
  22. package/dist/services/shell.js +179 -10
  23. package/dist/services/skills.js +2 -1
  24. package/dist/skills/answer.md +14 -12
  25. package/dist/skills/execute.md +98 -39
  26. package/dist/skills/introspect.md +9 -9
  27. package/dist/skills/schedule.md +0 -6
  28. package/dist/types/errors.js +47 -0
  29. package/dist/types/result.js +40 -0
  30. package/dist/ui/Command.js +11 -7
  31. package/dist/ui/Component.js +6 -3
  32. package/dist/ui/Config.js +9 -3
  33. package/dist/ui/Execute.js +249 -163
  34. package/dist/ui/Introspect.js +13 -14
  35. package/dist/ui/List.js +2 -2
  36. package/dist/ui/Main.js +14 -7
  37. package/dist/ui/Output.js +54 -0
  38. package/dist/ui/Schedule.js +3 -1
  39. package/dist/ui/Subtask.js +6 -3
  40. package/dist/ui/Task.js +10 -85
  41. package/dist/ui/Validate.js +26 -21
  42. package/dist/ui/Workflow.js +21 -4
  43. package/package.json +1 -1
  44. package/dist/parser.js +0 -13
  45. package/dist/services/config-utils.js +0 -20
package/README.md CHANGED
@@ -159,7 +159,6 @@ $ pls build test
159
159
 
160
160
  ## Roadmap
161
161
 
162
- - **0.8** - Sequential and interlaced skill execution
163
162
  - **0.9** - Learn skill, codebase refinement, complex dependency handling
164
163
  - **1.0** - Production release
165
164
 
@@ -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
- fs.writeFile(configFile, newContent);
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,6 +1,6 @@
1
1
  import { homedir } from 'os';
2
2
  import { join } from 'path';
3
- import { defaultFileSystem } from './filesystem.js';
3
+ import { defaultFileSystem } from '../services/filesystem.js';
4
4
  /**
5
5
  * Get the path to the config labels cache file
6
6
  */
@@ -1,7 +1,7 @@
1
1
  import YAML from 'yaml';
2
2
  import { AnthropicModel, ConfigDefinitionType, DebugLevel, SUPPORTED_DEBUG_LEVELS, SUPPORTED_MODELS, } from './types.js';
3
- import { flattenConfig } from '../services/config-utils.js';
4
- import { getConfigLabel } from '../services/config-labels.js';
3
+ import { flattenConfig } from './transformation.js';
4
+ import { getConfigLabel } from './labels.js';
5
5
  import { defaultFileSystem } from '../services/filesystem.js';
6
6
  import { getConfigPath, loadConfig } from './io.js';
7
7
  /**
@@ -0,0 +1,171 @@
1
+ import { parse as parseYaml } from 'yaml';
2
+ import { ConfigDefinitionType } from './types.js';
3
+ import { getConfigPath, loadConfig } from './io.js';
4
+ import { getConfigSchema } from './schema.js';
5
+ import { getConfigLabel } from './labels.js';
6
+ import { defaultFileSystem } from '../services/filesystem.js';
7
+ import { StepType } from '../ui/Config.js';
8
+ export function createConfigSteps() {
9
+ // Use schema-based config step generation for required Anthropic settings
10
+ return createConfigStepsFromSchema(['anthropic.key', 'anthropic.model']);
11
+ }
12
+ /**
13
+ * Get current config value for a dotted key path
14
+ */
15
+ function getConfigValue(config, key) {
16
+ if (!config)
17
+ return undefined;
18
+ const parts = key.split('.');
19
+ let value = config;
20
+ for (const part of parts) {
21
+ if (value && typeof value === 'object' && part in value) {
22
+ value = value[part];
23
+ }
24
+ else {
25
+ return undefined;
26
+ }
27
+ }
28
+ return value;
29
+ }
30
+ /**
31
+ * Get validation function for a config definition
32
+ */
33
+ function getValidator(definition) {
34
+ switch (definition.type) {
35
+ case ConfigDefinitionType.RegExp:
36
+ return (value) => definition.pattern.test(value);
37
+ case ConfigDefinitionType.String:
38
+ return () => true; // Strings are always valid
39
+ case ConfigDefinitionType.Enum:
40
+ return (value) => definition.values.includes(value);
41
+ case ConfigDefinitionType.Number:
42
+ return (value) => !isNaN(Number(value));
43
+ case ConfigDefinitionType.Boolean:
44
+ return (value) => value === 'true' || value === 'false';
45
+ }
46
+ }
47
+ /**
48
+ * Create config steps from schema for specified keys
49
+ */
50
+ export function createConfigStepsFromSchema(keys, fs = defaultFileSystem) {
51
+ const schema = getConfigSchema();
52
+ let currentConfig = null;
53
+ let rawConfig = null;
54
+ // Load validated config (may fail if config has validation errors)
55
+ try {
56
+ currentConfig = loadConfig(fs);
57
+ }
58
+ catch {
59
+ // Config doesn't exist or has validation errors, use defaults
60
+ }
61
+ // Load raw config separately (for discovered keys not in schema)
62
+ try {
63
+ const configFile = getConfigPath();
64
+ if (fs.exists(configFile)) {
65
+ const content = fs.readFile(configFile, 'utf-8');
66
+ rawConfig = parseYaml(content);
67
+ }
68
+ }
69
+ catch {
70
+ // Config file doesn't exist or can't be parsed
71
+ }
72
+ return keys.map((key) => {
73
+ // Check if key is in schema (system config)
74
+ if (!(key in schema)) {
75
+ // Key is not in schema - it's from a skill or discovered config
76
+ // Create a simple text step with cached label or full path as description
77
+ const keyParts = key.split('.');
78
+ const shortKey = keyParts[keyParts.length - 1];
79
+ // Load current value if it exists (use rawConfig since discovered keys aren't in validated config)
80
+ const currentValue = getConfigValue(rawConfig, key);
81
+ const value = currentValue !== undefined && typeof currentValue === 'string'
82
+ ? currentValue
83
+ : null;
84
+ // Use cached label if available, fallback to key path
85
+ const cachedLabel = getConfigLabel(key, fs);
86
+ return {
87
+ description: cachedLabel ?? key,
88
+ key: shortKey,
89
+ path: key,
90
+ type: StepType.Text,
91
+ value,
92
+ validate: () => true, // Accept any string for now
93
+ };
94
+ }
95
+ const definition = schema[key];
96
+ const currentValue = getConfigValue(currentConfig, key);
97
+ const keyParts = key.split('.');
98
+ const shortKey = keyParts[keyParts.length - 1];
99
+ // Map definition to ConfigStep based on type
100
+ switch (definition.type) {
101
+ case ConfigDefinitionType.RegExp:
102
+ case ConfigDefinitionType.String: {
103
+ const value = currentValue !== undefined && typeof currentValue === 'string'
104
+ ? currentValue
105
+ : definition.type === ConfigDefinitionType.String
106
+ ? (definition.default ?? '')
107
+ : null;
108
+ return {
109
+ description: definition.description,
110
+ key: shortKey,
111
+ path: key,
112
+ type: StepType.Text,
113
+ value,
114
+ validate: getValidator(definition),
115
+ };
116
+ }
117
+ case ConfigDefinitionType.Number: {
118
+ const value = currentValue !== undefined && typeof currentValue === 'number'
119
+ ? String(currentValue)
120
+ : definition.default !== undefined
121
+ ? String(definition.default)
122
+ : '0';
123
+ return {
124
+ description: definition.description,
125
+ key: shortKey,
126
+ path: key,
127
+ type: StepType.Text,
128
+ value,
129
+ validate: getValidator(definition),
130
+ };
131
+ }
132
+ case ConfigDefinitionType.Enum: {
133
+ const currentStr = currentValue !== undefined && typeof currentValue === 'string'
134
+ ? currentValue
135
+ : definition.default;
136
+ const defaultIndex = currentStr
137
+ ? definition.values.indexOf(currentStr)
138
+ : 0;
139
+ return {
140
+ description: definition.description,
141
+ key: shortKey,
142
+ path: key,
143
+ type: StepType.Selection,
144
+ options: definition.values.map((value) => ({
145
+ label: value,
146
+ value,
147
+ })),
148
+ defaultIndex: Math.max(0, defaultIndex),
149
+ validate: getValidator(definition),
150
+ };
151
+ }
152
+ case ConfigDefinitionType.Boolean: {
153
+ const currentBool = currentValue !== undefined && typeof currentValue === 'boolean'
154
+ ? currentValue
155
+ : undefined;
156
+ return {
157
+ description: definition.description,
158
+ key: shortKey,
159
+ path: key,
160
+ type: StepType.Selection,
161
+ options: [
162
+ { label: 'yes', value: 'true' },
163
+ { label: 'no', value: 'false' },
164
+ ],
165
+ defaultIndex: currentBool !== undefined ? (currentBool ? 0 : 1) : 0,
166
+ validate: getValidator(definition),
167
+ };
168
+ }
169
+ }
170
+ });
171
+ }
@@ -1,5 +1,22 @@
1
1
  import { ConfigDefinitionType } from './types.js';
2
2
  import { getConfigSchema } from './schema.js';
3
+ /**
4
+ * Flatten nested config object to dot notation
5
+ * Example: { a: { b: 1 } } => { 'a.b': 1 }
6
+ */
7
+ export function flattenConfig(obj, prefix = '') {
8
+ const result = {};
9
+ for (const [key, value] of Object.entries(obj)) {
10
+ const fullKey = prefix ? `${prefix}.${key}` : key;
11
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
12
+ Object.assign(result, flattenConfig(value, fullKey));
13
+ }
14
+ else {
15
+ result[fullKey] = value;
16
+ }
17
+ }
18
+ return result;
19
+ }
3
20
  /**
4
21
  * Convert string value to appropriate type based on schema definition
5
22
  */
@@ -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 Error {
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 { taskInfos, message, summary, taskExecutionTimes } = context;
9
- const updatedTimes = [...taskExecutionTimes, elapsed];
10
- const updatedTaskInfos = taskInfos.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Success, elapsed } : task);
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,7 @@ export function handleTaskCompletion(index, elapsed, context) {
18
18
  finalState: {
19
19
  message,
20
20
  summary,
21
- taskInfos: updatedTaskInfos,
22
- completed: index + 1,
23
- taskExecutionTimes: updatedTimes,
21
+ tasks: updatedTaskInfos,
24
22
  completionMessage: null,
25
23
  error: null,
26
24
  },
@@ -29,7 +27,7 @@ export function handleTaskCompletion(index, elapsed, context) {
29
27
  }
30
28
  // All tasks complete
31
29
  const summaryText = summary.trim() || 'Execution completed';
32
- const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
30
+ const totalElapsed = getTotalElapsed(updatedTaskInfos);
33
31
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
34
32
  return {
35
33
  action: {
@@ -39,9 +37,7 @@ export function handleTaskCompletion(index, elapsed, context) {
39
37
  finalState: {
40
38
  message,
41
39
  summary,
42
- taskInfos: updatedTaskInfos,
43
- completed: index + 1,
44
- taskExecutionTimes: updatedTimes,
40
+ tasks: updatedTaskInfos,
45
41
  completionMessage: completion,
46
42
  error: null,
47
43
  },
@@ -52,10 +48,10 @@ export function handleTaskCompletion(index, elapsed, context) {
52
48
  * Handles task error logic and returns the appropriate action and state.
53
49
  */
54
50
  export function handleTaskFailure(index, error, elapsed, context) {
55
- const { taskInfos, message, summary, taskExecutionTimes } = context;
56
- const task = taskInfos[index];
51
+ const { tasks, message, summary } = context;
52
+ const task = tasks[index];
57
53
  const isCritical = task.command.critical !== false; // Default to true
58
- const updatedTaskInfos = taskInfos.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Failed, elapsed } : task);
54
+ const updatedTaskInfos = tasks.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Failed, elapsed } : task);
59
55
  if (isCritical) {
60
56
  // Critical failure - stop execution
61
57
  return {
@@ -66,19 +62,15 @@ export function handleTaskFailure(index, error, elapsed, context) {
66
62
  finalState: {
67
63
  message,
68
64
  summary,
69
- taskInfos: updatedTaskInfos,
70
- completed: index + 1,
71
- taskExecutionTimes,
65
+ tasks: updatedTaskInfos,
72
66
  completionMessage: null,
73
- error,
67
+ error: null,
74
68
  },
75
69
  shouldComplete: true,
76
- shouldReportError: true,
77
70
  };
78
71
  }
79
72
  // Non-critical failure - continue to next task
80
- const updatedTimes = [...taskExecutionTimes, elapsed];
81
- if (index < taskInfos.length - 1) {
73
+ if (index < tasks.length - 1) {
82
74
  return {
83
75
  action: {
84
76
  type: ExecuteActionType.TaskErrorContinue,
@@ -87,19 +79,17 @@ export function handleTaskFailure(index, error, elapsed, context) {
87
79
  finalState: {
88
80
  message,
89
81
  summary,
90
- taskInfos: updatedTaskInfos,
91
- completed: index + 1,
92
- taskExecutionTimes: updatedTimes,
82
+ tasks: updatedTaskInfos,
93
83
  completionMessage: null,
94
84
  error: null,
95
85
  },
96
86
  shouldComplete: false,
97
- shouldReportError: false,
98
87
  };
99
88
  }
100
- // Last task, complete execution
89
+ // Last task failed (non-critical), complete execution
90
+ // Non-critical failures still show completion message with summary
101
91
  const summaryText = summary.trim() || 'Execution completed';
102
- const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
92
+ const totalElapsed = getTotalElapsed(updatedTaskInfos);
103
93
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
104
94
  return {
105
95
  action: {
@@ -109,26 +99,21 @@ export function handleTaskFailure(index, error, elapsed, context) {
109
99
  finalState: {
110
100
  message,
111
101
  summary,
112
- taskInfos: updatedTaskInfos,
113
- completed: index + 1,
114
- taskExecutionTimes: updatedTimes,
102
+ tasks: updatedTaskInfos,
115
103
  completionMessage: completion,
116
104
  error: null,
117
105
  },
118
106
  shouldComplete: true,
119
- shouldReportError: false,
120
107
  };
121
108
  }
122
109
  /**
123
110
  * Builds final state for task abortion.
124
111
  */
125
- export function buildAbortedState(taskInfos, message, summary, completed, taskExecutionTimes) {
112
+ export function buildAbortedState(tasks, message, summary) {
126
113
  return {
127
114
  message,
128
115
  summary,
129
- taskInfos,
130
- completed,
131
- taskExecutionTimes,
116
+ tasks,
132
117
  completionMessage: null,
133
118
  error: null,
134
119
  };