prompt-language-shell 0.8.4 → 0.8.6

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.
@@ -0,0 +1,148 @@
1
+ import { ExecutionStatus } from '../services/shell.js';
2
+ import { formatDuration } from '../services/utils.js';
3
+ import { ExecuteActionType, } from './types.js';
4
+ export const initialState = {
5
+ error: null,
6
+ taskInfos: [],
7
+ message: '',
8
+ completed: 0,
9
+ hasProcessed: false,
10
+ taskExecutionTimes: [],
11
+ completionMessage: null,
12
+ summary: '',
13
+ };
14
+ export function executeReducer(state, action) {
15
+ switch (action.type) {
16
+ case ExecuteActionType.ProcessingComplete:
17
+ return {
18
+ ...state,
19
+ message: action.payload.message,
20
+ hasProcessed: true,
21
+ };
22
+ case ExecuteActionType.CommandsReady:
23
+ return {
24
+ ...state,
25
+ message: action.payload.message,
26
+ summary: action.payload.summary,
27
+ taskInfos: action.payload.taskInfos,
28
+ completed: 0,
29
+ };
30
+ case ExecuteActionType.ProcessingError:
31
+ return {
32
+ ...state,
33
+ error: action.payload.error,
34
+ hasProcessed: true,
35
+ };
36
+ case ExecuteActionType.TaskComplete: {
37
+ const updatedTimes = [
38
+ ...state.taskExecutionTimes,
39
+ action.payload.elapsed,
40
+ ];
41
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
42
+ ? {
43
+ ...task,
44
+ status: ExecutionStatus.Success,
45
+ elapsed: action.payload.elapsed,
46
+ }
47
+ : task);
48
+ return {
49
+ ...state,
50
+ taskInfos: updatedTaskInfos,
51
+ taskExecutionTimes: updatedTimes,
52
+ completed: action.payload.index + 1,
53
+ };
54
+ }
55
+ case ExecuteActionType.AllTasksComplete: {
56
+ const updatedTimes = [
57
+ ...state.taskExecutionTimes,
58
+ action.payload.elapsed,
59
+ ];
60
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
61
+ ? {
62
+ ...task,
63
+ status: ExecutionStatus.Success,
64
+ elapsed: action.payload.elapsed,
65
+ }
66
+ : task);
67
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
68
+ const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
69
+ return {
70
+ ...state,
71
+ taskInfos: updatedTaskInfos,
72
+ taskExecutionTimes: updatedTimes,
73
+ completed: action.payload.index + 1,
74
+ completionMessage: completion,
75
+ };
76
+ }
77
+ case ExecuteActionType.TaskErrorCritical: {
78
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
79
+ ? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
80
+ : task);
81
+ return {
82
+ ...state,
83
+ taskInfos: updatedTaskInfos,
84
+ error: action.payload.error,
85
+ };
86
+ }
87
+ case ExecuteActionType.TaskErrorContinue: {
88
+ const updatedTimes = [
89
+ ...state.taskExecutionTimes,
90
+ action.payload.elapsed,
91
+ ];
92
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
93
+ ? {
94
+ ...task,
95
+ status: ExecutionStatus.Failed,
96
+ elapsed: action.payload.elapsed,
97
+ }
98
+ : task);
99
+ return {
100
+ ...state,
101
+ taskInfos: updatedTaskInfos,
102
+ taskExecutionTimes: updatedTimes,
103
+ completed: action.payload.index + 1,
104
+ };
105
+ }
106
+ case ExecuteActionType.LastTaskError: {
107
+ const updatedTimes = [
108
+ ...state.taskExecutionTimes,
109
+ action.payload.elapsed,
110
+ ];
111
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
112
+ ? {
113
+ ...task,
114
+ status: ExecutionStatus.Failed,
115
+ elapsed: action.payload.elapsed,
116
+ }
117
+ : task);
118
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
119
+ const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
120
+ return {
121
+ ...state,
122
+ taskInfos: updatedTaskInfos,
123
+ taskExecutionTimes: updatedTimes,
124
+ completed: action.payload.index + 1,
125
+ completionMessage: completion,
126
+ };
127
+ }
128
+ case ExecuteActionType.CancelExecution: {
129
+ const updatedTaskInfos = state.taskInfos.map((task, taskIndex) => {
130
+ if (taskIndex < action.payload.completed) {
131
+ return { ...task, status: ExecutionStatus.Success };
132
+ }
133
+ else if (taskIndex === action.payload.completed) {
134
+ return { ...task, status: ExecutionStatus.Aborted };
135
+ }
136
+ else {
137
+ return { ...task, status: ExecutionStatus.Cancelled };
138
+ }
139
+ });
140
+ return {
141
+ ...state,
142
+ taskInfos: updatedTaskInfos,
143
+ };
144
+ }
145
+ default:
146
+ return state;
147
+ }
148
+ }
@@ -0,0 +1,12 @@
1
+ export var ExecuteActionType;
2
+ (function (ExecuteActionType) {
3
+ ExecuteActionType["ProcessingComplete"] = "PROCESSING_COMPLETE";
4
+ ExecuteActionType["CommandsReady"] = "COMMANDS_READY";
5
+ ExecuteActionType["ProcessingError"] = "PROCESSING_ERROR";
6
+ ExecuteActionType["TaskComplete"] = "TASK_COMPLETE";
7
+ ExecuteActionType["AllTasksComplete"] = "ALL_TASKS_COMPLETE";
8
+ ExecuteActionType["TaskErrorCritical"] = "TASK_ERROR_CRITICAL";
9
+ ExecuteActionType["TaskErrorContinue"] = "TASK_ERROR_CONTINUE";
10
+ ExecuteActionType["LastTaskError"] = "LAST_TASK_ERROR";
11
+ ExecuteActionType["CancelExecution"] = "CANCEL_EXECUTION";
12
+ })(ExecuteActionType || (ExecuteActionType = {}));
@@ -0,0 +1,12 @@
1
+ import { getUnresolvedPlaceholdersMessage } from '../services/messages.js';
2
+ /**
3
+ * Validates that all placeholders in a command have been resolved.
4
+ * Throws an error if unresolved placeholders are found.
5
+ */
6
+ export function validatePlaceholderResolution(command) {
7
+ const unresolvedPattern = /\{[^}]+\}/g;
8
+ const matches = command.match(unresolvedPattern);
9
+ if (matches && matches.length > 0) {
10
+ throw new Error(getUnresolvedPlaceholdersMessage(matches.length));
11
+ }
12
+ }
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ 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 { DebugLevel } from './services/configuration.js';
7
+ import { DebugLevel } from './configuration/types.js';
8
8
  import { Main } from './ui/Main.js';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
@@ -1,5 +1,5 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
- import { getAvailableConfigStructure, getConfiguredKeys, } from './configuration.js';
2
+ import { getAvailableConfigStructure, getConfiguredKeys, } from '../configuration/schema.js';
3
3
  import { logPrompt, logResponse } from './logger.js';
4
4
  import { formatSkillsForPrompt, loadSkillsWithValidation } from './skills.js';
5
5
  import { toolRegistry } from './registry.js';
@@ -1,5 +1,5 @@
1
+ import { DebugLevel } from '../configuration/types.js';
1
2
  import { FeedbackType, Origin, TaskType } from '../types/types.js';
2
- import { DebugLevel } from './configuration.js';
3
3
  import { ExecutionStatus } from './shell.js';
4
4
  /**
5
5
  * Base color palette - raw color values with descriptive names.
@@ -1,8 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { parse as parseYaml } from 'yaml';
3
+ import { ConfigDefinitionType, } from '../configuration/types.js';
3
4
  import { ComponentStatus, } from '../types/components.js';
4
5
  import { ComponentName } from '../types/types.js';
5
- import { ConfigDefinitionType, getConfigPath, getConfigSchema, loadConfig, } from './configuration.js';
6
+ import { getConfigPath, loadConfig } from '../configuration/io.js';
7
+ import { getConfigSchema } from '../configuration/schema.js';
8
+ import { getConfigLabel } from './config-labels.js';
6
9
  import { defaultFileSystem } from './filesystem.js';
7
10
  import { getConfirmationMessage } from './messages.js';
8
11
  import { StepType } from '../ui/Config.js';
@@ -11,6 +14,7 @@ export function createWelcomeDefinition(app) {
11
14
  id: randomUUID(),
12
15
  name: ComponentName.Welcome,
13
16
  props: { app },
17
+ status: ComponentStatus.Awaiting,
14
18
  };
15
19
  }
16
20
  export function createConfigSteps() {
@@ -81,7 +85,7 @@ export function createConfigStepsFromSchema(keys, fs = defaultFileSystem) {
81
85
  // Check if key is in schema (system config)
82
86
  if (!(key in schema)) {
83
87
  // Key is not in schema - it's from a skill or discovered config
84
- // Create a simple text step with the full path as description
88
+ // Create a simple text step with cached label or full path as description
85
89
  const keyParts = key.split('.');
86
90
  const shortKey = keyParts[keyParts.length - 1];
87
91
  // Load current value if it exists (use rawConfig since discovered keys aren't in validated config)
@@ -89,8 +93,10 @@ export function createConfigStepsFromSchema(keys, fs = defaultFileSystem) {
89
93
  const value = currentValue !== undefined && typeof currentValue === 'string'
90
94
  ? currentValue
91
95
  : null;
96
+ // Use cached label if available, fallback to key path
97
+ const cachedLabel = getConfigLabel(key, fs);
92
98
  return {
93
- description: key,
99
+ description: cachedLabel ?? key,
94
100
  key: shortKey,
95
101
  path: key,
96
102
  type: StepType.Text,
@@ -217,7 +223,11 @@ export function createCommandDefinition(command, service) {
217
223
  id: randomUUID(),
218
224
  name: ComponentName.Command,
219
225
  status: ComponentStatus.Awaiting,
220
- state: {},
226
+ state: {
227
+ error: null,
228
+ message: null,
229
+ tasks: [],
230
+ },
221
231
  props: {
222
232
  command,
223
233
  service,
@@ -249,6 +259,7 @@ export function createFeedback(type, ...messages) {
249
259
  type,
250
260
  message: messages.join('\n\n'),
251
261
  },
262
+ status: ComponentStatus.Awaiting,
252
263
  };
253
264
  }
254
265
  export function createMessage(text) {
@@ -258,6 +269,7 @@ export function createMessage(text) {
258
269
  props: {
259
270
  text,
260
271
  },
272
+ status: ComponentStatus.Awaiting,
261
273
  };
262
274
  }
263
275
  export function createDebugDefinition(title, content, color) {
@@ -269,6 +281,7 @@ export function createDebugDefinition(title, content, color) {
269
281
  content,
270
282
  color,
271
283
  },
284
+ status: ComponentStatus.Awaiting,
272
285
  };
273
286
  }
274
287
  export function createRefinement(text, onAborted) {
@@ -288,7 +301,10 @@ export function createConfirmDefinition(onConfirmed, onCancelled) {
288
301
  id: randomUUID(),
289
302
  name: ComponentName.Confirm,
290
303
  status: ComponentStatus.Awaiting,
291
- state: {},
304
+ state: {
305
+ confirmed: false,
306
+ selectedIndex: 0,
307
+ },
292
308
  props: {
293
309
  message: getConfirmationMessage(),
294
310
  onConfirmed,
@@ -301,7 +317,11 @@ export function createIntrospectDefinition(tasks, service) {
301
317
  id: randomUUID(),
302
318
  name: ComponentName.Introspect,
303
319
  status: ComponentStatus.Awaiting,
304
- state: {},
320
+ state: {
321
+ error: null,
322
+ capabilities: [],
323
+ message: null,
324
+ },
305
325
  props: {
306
326
  tasks,
307
327
  service,
@@ -316,6 +336,7 @@ export function createReportDefinition(message, capabilities) {
316
336
  message,
317
337
  capabilities,
318
338
  },
339
+ status: ComponentStatus.Awaiting,
319
340
  };
320
341
  }
321
342
  export function createAnswerDefinition(question, service) {
@@ -323,14 +344,17 @@ export function createAnswerDefinition(question, service) {
323
344
  id: randomUUID(),
324
345
  name: ComponentName.Answer,
325
346
  status: ComponentStatus.Awaiting,
326
- state: {},
347
+ state: {
348
+ error: null,
349
+ answer: null,
350
+ },
327
351
  props: {
328
352
  question,
329
353
  service,
330
354
  },
331
355
  };
332
356
  }
333
- export function isStateless(component) {
357
+ export function isSimple(component) {
334
358
  return !('state' in component);
335
359
  }
336
360
  /**
@@ -361,7 +385,7 @@ export function createExecuteDefinition(tasks, service) {
361
385
  },
362
386
  };
363
387
  }
364
- export function createValidateDefinition(missingConfig, userRequest, service, onError, onComplete, onAborted) {
388
+ export function createValidateDefinition(missingConfig, userRequest, service, onError, onValidationComplete, onAborted) {
365
389
  return {
366
390
  id: randomUUID(),
367
391
  name: ComponentName.Validate,
@@ -369,7 +393,7 @@ export function createValidateDefinition(missingConfig, userRequest, service, on
369
393
  state: {
370
394
  error: null,
371
395
  completionMessage: null,
372
- configRequirements: null,
396
+ configRequirements: [],
373
397
  validated: false,
374
398
  },
375
399
  props: {
@@ -377,7 +401,7 @@ export function createValidateDefinition(missingConfig, userRequest, service, on
377
401
  userRequest,
378
402
  service,
379
403
  onError,
380
- onComplete,
404
+ onValidationComplete,
381
405
  onAborted,
382
406
  },
383
407
  };
@@ -1,6 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
1
  import { homedir } from 'os';
3
2
  import { join } from 'path';
3
+ import { defaultFileSystem } from './filesystem.js';
4
4
  /**
5
5
  * Get the path to the config labels cache file
6
6
  */
@@ -16,23 +16,23 @@ function getCacheDirectoryPath() {
16
16
  /**
17
17
  * Ensure the cache directory exists
18
18
  */
19
- function ensureCacheDirectoryExists() {
19
+ function ensureCacheDirectoryExists(fs = defaultFileSystem) {
20
20
  const cacheDir = getCacheDirectoryPath();
21
- if (!existsSync(cacheDir)) {
22
- mkdirSync(cacheDir, { recursive: true });
21
+ if (!fs.exists(cacheDir)) {
22
+ fs.createDirectory(cacheDir, { recursive: true });
23
23
  }
24
24
  }
25
25
  /**
26
26
  * Load config labels from cache file
27
27
  * Returns empty object if file doesn't exist or is corrupted
28
28
  */
29
- export function loadConfigLabels() {
29
+ export function loadConfigLabels(fs = defaultFileSystem) {
30
30
  try {
31
31
  const cachePath = getConfigLabelsCachePath();
32
- if (!existsSync(cachePath)) {
32
+ if (!fs.exists(cachePath)) {
33
33
  return {};
34
34
  }
35
- const content = readFileSync(cachePath, 'utf-8');
35
+ const content = fs.readFile(cachePath, 'utf-8');
36
36
  const parsed = JSON.parse(content);
37
37
  // Validate that parsed content is an object
38
38
  if (typeof parsed !== 'object' ||
@@ -50,26 +50,26 @@ export function loadConfigLabels() {
50
50
  /**
51
51
  * Save multiple config labels to cache
52
52
  */
53
- export function saveConfigLabels(labels) {
54
- ensureCacheDirectoryExists();
53
+ export function saveConfigLabels(labels, fs = defaultFileSystem) {
54
+ ensureCacheDirectoryExists(fs);
55
55
  // Load existing labels and merge with new ones
56
- const existing = loadConfigLabels();
56
+ const existing = loadConfigLabels(fs);
57
57
  const merged = { ...existing, ...labels };
58
58
  const cachePath = getConfigLabelsCachePath();
59
59
  const content = JSON.stringify(merged, null, 2);
60
- writeFileSync(cachePath, content, 'utf-8');
60
+ fs.writeFile(cachePath, content);
61
61
  }
62
62
  /**
63
63
  * Save a single config label to cache
64
64
  */
65
- export function saveConfigLabel(key, label) {
66
- saveConfigLabels({ [key]: label });
65
+ export function saveConfigLabel(key, label, fs = defaultFileSystem) {
66
+ saveConfigLabels({ [key]: label }, fs);
67
67
  }
68
68
  /**
69
69
  * Get a config label from cache
70
70
  * Returns undefined if label doesn't exist
71
71
  */
72
- export function getConfigLabel(key) {
73
- const labels = loadConfigLabels();
72
+ export function getConfigLabel(key, fs = defaultFileSystem) {
73
+ const labels = loadConfigLabels(fs);
74
74
  return labels[key];
75
75
  }
@@ -1,5 +1,6 @@
1
+ import { DebugLevel } from '../configuration/types.js';
1
2
  import { createDebugDefinition } from './components.js';
2
- import { DebugLevel, loadDebugSetting } from './configuration.js';
3
+ import { loadDebugSetting } from '../configuration/io.js';
3
4
  import { Palette } from './colors.js';
4
5
  /**
5
6
  * Debug logger for the application
@@ -1,4 +1,5 @@
1
- import { DebugLevel, loadDebugSetting } from './configuration.js';
1
+ import { DebugLevel } from '../configuration/types.js';
2
+ import { loadDebugSetting } from '../configuration/io.js';
2
3
  export { formatDuration } from './utils.js';
3
4
  /**
4
5
  * Returns a natural language confirmation message for plan execution.
@@ -80,6 +81,36 @@ export function getMixedTaskTypesError(types) {
80
81
  const typeList = types.join(', ');
81
82
  return `Mixed task types are not supported. Found: ${typeList}. All tasks in a plan must have the same type.`;
82
83
  }
84
+ /**
85
+ * Returns a message for unresolved placeholders/missing configuration.
86
+ * Each message has two sentences: what's missing + what will be done.
87
+ * Both sentences are randomly selected independently for variety.
88
+ * Supports singular and plural forms.
89
+ */
90
+ export function getUnresolvedPlaceholdersMessage(count) {
91
+ const plural = count === 1 ? '' : 's';
92
+ const it = count === 1 ? 'it' : 'them';
93
+ const valueWord = count === 1 ? 'value' : 'values';
94
+ // First sentence: what's missing
95
+ const firstSentences = [
96
+ `Missing configuration ${valueWord} detected.`,
97
+ `Configuration ${valueWord} needed.`,
98
+ `Found unresolved placeholder${plural}.`,
99
+ `Additional configuration ${valueWord} required.`,
100
+ `Setup requires configuration ${valueWord}.`,
101
+ ];
102
+ // Second sentence: what will be done
103
+ const secondSentences = [
104
+ `Let me gather ${it} now.`,
105
+ `I'll set ${it} up for you.`,
106
+ `Let me configure ${it} first.`,
107
+ `I'll help you provide ${it}.`,
108
+ `Let me collect ${it} from you.`,
109
+ ];
110
+ const first = firstSentences[Math.floor(Math.random() * firstSentences.length)];
111
+ const second = secondSentences[Math.floor(Math.random() * secondSentences.length)];
112
+ return `${first} ${second}`;
113
+ }
83
114
  /**
84
115
  * Feedback messages for various operations
85
116
  */
@@ -5,12 +5,12 @@ import { routeTasksWithConfirm } from './router.js';
5
5
  * Handle refinement flow for DEFINE tasks
6
6
  * Called when user selects options from a plan with DEFINE tasks
7
7
  */
8
- export async function handleRefinement(selectedTasks, service, originalCommand, queueHandlers, lifecycleHandlers, workflowHandlers, errorHandlers) {
8
+ export async function handleRefinement(selectedTasks, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers) {
9
9
  // Create and add refinement component to queue
10
10
  const refinementDef = createRefinement(getRefiningMessage(), (operation) => {
11
- errorHandlers.onAborted(operation);
11
+ requestHandlers.onAborted(operation);
12
12
  });
13
- queueHandlers.addToQueue(refinementDef);
13
+ workflowHandlers.addToQueue(refinementDef);
14
14
  try {
15
15
  // Build refined command from selected tasks
16
16
  const refinedCommand = selectedTasks
@@ -22,19 +22,19 @@ export async function handleRefinement(selectedTasks, service, originalCommand,
22
22
  .join(', ');
23
23
  // Call LLM to refine plan with selected tasks
24
24
  const refinedResult = await service.processWithTool(refinedCommand, 'schedule');
25
- // Complete the Refinement component
25
+ // Complete the Refinement component with success state
26
26
  lifecycleHandlers.completeActive();
27
27
  // Add debug components to timeline if present
28
28
  if (refinedResult.debug?.length) {
29
29
  workflowHandlers.addToTimeline(...refinedResult.debug);
30
30
  }
31
31
  // Route refined tasks to appropriate components
32
- routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, queueHandlers, workflowHandlers, errorHandlers, false // No DEFINE tasks in refined result
32
+ routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers, false // No DEFINE tasks in refined result
33
33
  );
34
34
  }
35
35
  catch (err) {
36
36
  lifecycleHandlers.completeActive();
37
37
  const errorMessage = formatErrorMessage(err);
38
- errorHandlers.onError(errorMessage);
38
+ requestHandlers.onError(errorMessage);
39
39
  }
40
40
  }