prompt-language-shell 0.8.0 → 0.8.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.
@@ -48,8 +48,8 @@ export function cleanAnswerText(text) {
48
48
  export class AnthropicService {
49
49
  client;
50
50
  model;
51
- constructor(key, model = 'claude-haiku-4-5-20251001') {
52
- this.client = new Anthropic({ apiKey: key });
51
+ constructor(key, model = 'claude-haiku-4-5', timeout = 30000) {
52
+ this.client = new Anthropic({ apiKey: key, timeout });
53
53
  this.model = model;
54
54
  }
55
55
  async processWithTool(command, toolName, customInstructions) {
@@ -186,7 +186,33 @@ export class AnthropicService {
186
186
  debug,
187
187
  };
188
188
  }
189
- // Handle schedule and introspect tool responses
189
+ // Handle introspect tool response
190
+ if (toolName === 'introspect') {
191
+ if (!input.message || typeof input.message !== 'string') {
192
+ throw new Error('Invalid tool response: missing or invalid message field');
193
+ }
194
+ if (!input.capabilities || !Array.isArray(input.capabilities)) {
195
+ throw new Error('Invalid tool response: missing or invalid capabilities array');
196
+ }
197
+ // Validate each capability has required fields
198
+ input.capabilities.forEach((cap, i) => {
199
+ if (!cap.name || typeof cap.name !== 'string') {
200
+ throw new Error(`Invalid capability at index ${String(i)}: missing or invalid 'name' field`);
201
+ }
202
+ if (!cap.description || typeof cap.description !== 'string') {
203
+ throw new Error(`Invalid capability at index ${String(i)}: missing or invalid 'description' field`);
204
+ }
205
+ if (typeof cap.origin !== 'string') {
206
+ throw new Error(`Invalid capability at index ${String(i)}: invalid 'origin' field`);
207
+ }
208
+ });
209
+ return {
210
+ message: input.message,
211
+ capabilities: input.capabilities,
212
+ debug,
213
+ };
214
+ }
215
+ // Handle schedule tool responses
190
216
  if (input.message === undefined || typeof input.message !== 'string') {
191
217
  throw new Error('Invalid tool response: missing or invalid message field');
192
218
  }
@@ -1,4 +1,4 @@
1
- import { FeedbackType, TaskType } from '../types/types.js';
1
+ import { FeedbackType, Origin, TaskType } from '../types/types.js';
2
2
  import { DebugLevel } from './configuration.js';
3
3
  import { ExecutionStatus } from './shell.js';
4
4
  /**
@@ -132,6 +132,14 @@ const feedbackColors = {
132
132
  [FeedbackType.Aborted]: Palette.MediumOrange,
133
133
  [FeedbackType.Failed]: Colors.Status.Error,
134
134
  };
135
+ /**
136
+ * Origin-specific color mappings (internal)
137
+ */
138
+ const originColors = {
139
+ [Origin.BuiltIn]: Colors.Origin.BuiltIn,
140
+ [Origin.UserProvided]: Colors.Origin.UserProvided,
141
+ [Origin.Indirect]: Colors.Origin.Indirect,
142
+ };
135
143
  /**
136
144
  * Process null color values based on current/historical state.
137
145
  *
@@ -170,6 +178,17 @@ export function getTaskColors(type, isCurrent) {
170
178
  export function getFeedbackColor(type, isCurrent) {
171
179
  return processColor(feedbackColors[type], isCurrent);
172
180
  }
181
+ /**
182
+ * Get color for capability origin.
183
+ *
184
+ * Returns the color associated with each origin type:
185
+ * - BuiltIn: Cyan
186
+ * - UserProvided: Green
187
+ * - Indirect: Purple
188
+ */
189
+ export function getOriginColor(origin) {
190
+ return originColors[origin];
191
+ }
173
192
  /**
174
193
  * Get text color based on current/historical state.
175
194
  *
@@ -78,7 +78,7 @@ export function createConfigStepsFromSchema(keys) {
78
78
  // Config file doesn't exist or can't be parsed
79
79
  }
80
80
  return keys.map((key) => {
81
- // Check if key is in schema (built-in config)
81
+ // Check if key is in schema (system config)
82
82
  if (!(key in schema)) {
83
83
  // Key is not in schema - it's from a skill or discovered config
84
84
  // Create a simple text step with the full path as description
@@ -0,0 +1,75 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ /**
5
+ * Get the path to the config labels cache file
6
+ */
7
+ export function getConfigLabelsCachePath() {
8
+ return join(homedir(), '.pls', 'cache', 'config.json');
9
+ }
10
+ /**
11
+ * Get the cache directory path
12
+ */
13
+ function getCacheDirectoryPath() {
14
+ return join(homedir(), '.pls', 'cache');
15
+ }
16
+ /**
17
+ * Ensure the cache directory exists
18
+ */
19
+ function ensureCacheDirectoryExists() {
20
+ const cacheDir = getCacheDirectoryPath();
21
+ if (!existsSync(cacheDir)) {
22
+ mkdirSync(cacheDir, { recursive: true });
23
+ }
24
+ }
25
+ /**
26
+ * Load config labels from cache file
27
+ * Returns empty object if file doesn't exist or is corrupted
28
+ */
29
+ export function loadConfigLabels() {
30
+ try {
31
+ const cachePath = getConfigLabelsCachePath();
32
+ if (!existsSync(cachePath)) {
33
+ return {};
34
+ }
35
+ const content = readFileSync(cachePath, 'utf-8');
36
+ const parsed = JSON.parse(content);
37
+ // Validate that parsed content is an object
38
+ if (typeof parsed !== 'object' ||
39
+ parsed === null ||
40
+ Array.isArray(parsed)) {
41
+ return {};
42
+ }
43
+ return parsed;
44
+ }
45
+ catch {
46
+ // Return empty object on any error (parse error, read error, etc.)
47
+ return {};
48
+ }
49
+ }
50
+ /**
51
+ * Save multiple config labels to cache
52
+ */
53
+ export function saveConfigLabels(labels) {
54
+ ensureCacheDirectoryExists();
55
+ // Load existing labels and merge with new ones
56
+ const existing = loadConfigLabels();
57
+ const merged = { ...existing, ...labels };
58
+ const cachePath = getConfigLabelsCachePath();
59
+ const content = JSON.stringify(merged, null, 2);
60
+ writeFileSync(cachePath, content, 'utf-8');
61
+ }
62
+ /**
63
+ * Save a single config label to cache
64
+ */
65
+ export function saveConfigLabel(key, label) {
66
+ saveConfigLabels({ [key]: label });
67
+ }
68
+ /**
69
+ * Get a config label from cache
70
+ * Returns undefined if label doesn't exist
71
+ */
72
+ export function getConfigLabel(key) {
73
+ const labels = loadConfigLabels();
74
+ return labels[key];
75
+ }
@@ -0,0 +1,20 @@
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
+ }
@@ -2,6 +2,18 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
2
2
  import { homedir } from 'os';
3
3
  import { join } from 'path';
4
4
  import YAML from 'yaml';
5
+ import { getConfigLabel } from './config-labels.js';
6
+ import { flattenConfig } from './config-utils.js';
7
+ /**
8
+ * Convert a dotted config key to a readable label
9
+ * Example: "project.alpha.repo" -> "Project Alpha Repo"
10
+ */
11
+ function keyToLabel(key) {
12
+ return key
13
+ .split('.')
14
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
15
+ .join(' ');
16
+ }
5
17
  export var AnthropicModel;
6
18
  (function (AnthropicModel) {
7
19
  AnthropicModel["Sonnet"] = "claude-sonnet-4-5";
@@ -192,7 +204,7 @@ export function getConfigurationRequiredMessage(forFutureUse = false) {
192
204
  return messages[Math.floor(Math.random() * messages.length)];
193
205
  }
194
206
  /**
195
- * Core configuration schema - defines structure and types for built-in settings
207
+ * Core configuration schema - defines structure and types for system settings
196
208
  */
197
209
  const coreConfigSchema = {
198
210
  'anthropic.key': {
@@ -301,19 +313,6 @@ export function getConfiguredKeys() {
301
313
  const content = readFileSync(configFile, 'utf-8');
302
314
  const parsed = YAML.parse(content);
303
315
  // Flatten nested config to dot notation
304
- function flattenConfig(obj, prefix = '') {
305
- const result = {};
306
- for (const [key, value] of Object.entries(obj)) {
307
- const fullKey = prefix ? `${prefix}.${key}` : key;
308
- if (value && typeof value === 'object' && !Array.isArray(value)) {
309
- Object.assign(result, flattenConfig(value, fullKey));
310
- }
311
- else {
312
- result[fullKey] = value;
313
- }
314
- }
315
- return result;
316
- }
317
316
  const flatConfig = flattenConfig(parsed);
318
317
  return Object.keys(flatConfig);
319
318
  }
@@ -337,19 +336,6 @@ export function getAvailableConfigStructure() {
337
336
  const content = readFileSync(configFile, 'utf-8');
338
337
  const parsed = YAML.parse(content);
339
338
  // Flatten nested config to dot notation
340
- function flattenConfig(obj, prefix = '') {
341
- const result = {};
342
- for (const [key, value] of Object.entries(obj)) {
343
- const fullKey = prefix ? `${prefix}.${key}` : key;
344
- if (value && typeof value === 'object' && !Array.isArray(value)) {
345
- Object.assign(result, flattenConfig(value, fullKey));
346
- }
347
- else {
348
- result[fullKey] = value;
349
- }
350
- }
351
- return result;
352
- }
353
339
  flatConfig = flattenConfig(parsed);
354
340
  }
355
341
  }
@@ -357,20 +343,13 @@ export function getAvailableConfigStructure() {
357
343
  // Config file doesn't exist or can't be read
358
344
  }
359
345
  // Add schema keys with descriptions
360
- // Mark optional keys as (optional)
361
346
  for (const [key, definition] of Object.entries(schema)) {
362
- const isOptional = !definition.required;
363
- if (isOptional) {
364
- structure[key] = `${definition.description} (optional)`;
365
- }
366
- else {
367
- structure[key] = definition.description;
368
- }
347
+ structure[key] = definition.description;
369
348
  }
370
349
  // Add discovered keys that aren't in schema
371
350
  for (const key of Object.keys(flatConfig)) {
372
351
  if (!(key in structure)) {
373
- structure[key] = `${key} (discovered)`;
352
+ structure[key] = getConfigLabel(key) || keyToLabel(key);
374
353
  }
375
354
  }
376
355
  return structure;
@@ -32,7 +32,7 @@ class ToolRegistry {
32
32
  }
33
33
  // Create singleton instance
34
34
  export const toolRegistry = new ToolRegistry();
35
- // Register built-in tools
35
+ // Register system tools
36
36
  import { answerTool } from '../tools/answer.tool.js';
37
37
  import { configureTool } from '../tools/configure.tool.js';
38
38
  import { executeTool } from '../tools/execute.tool.js';
@@ -1,3 +1,4 @@
1
+ import { asScheduledTasks } from '../types/guards.js';
1
2
  import { FeedbackType, TaskType } from '../types/types.js';
2
3
  import { createAnswerDefinition, createConfigDefinitionWithKeys, createConfirmDefinition, createExecuteDefinition, createFeedback, createIntrospectDefinition, createMessage, createScheduleDefinition, createValidateDefinition, } from './components.js';
3
4
  import { saveConfig, unflattenConfig } from './configuration.js';
@@ -66,8 +67,8 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
66
67
  function validateTaskTypes(tasks) {
67
68
  if (tasks.length === 0)
68
69
  return;
69
- // Cast to ScheduledTask to access subtasks property
70
- const scheduledTasks = tasks;
70
+ // Convert to ScheduledTask to access subtasks property
71
+ const scheduledTasks = asScheduledTasks(tasks);
71
72
  // Check each Group task's subtasks for uniform types
72
73
  for (const task of scheduledTasks) {
73
74
  if (task.type === TaskType.Group &&
@@ -96,7 +97,7 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
96
97
  handlers.onError(error instanceof Error ? error.message : String(error));
97
98
  return;
98
99
  }
99
- const scheduledTasks = tasks;
100
+ const scheduledTasks = asScheduledTasks(tasks);
100
101
  // Process tasks in order, preserving Group boundaries
101
102
  // Track consecutive standalone tasks to group them by type
102
103
  let consecutiveStandaloneTasks = [];
@@ -32,7 +32,7 @@ export function isValidSkillFilename(filename) {
32
32
  return kebabCasePattern.test(name);
33
33
  }
34
34
  /**
35
- * Check if skill key conflicts with built-in skills
35
+ * Check if skill key conflicts with system skills
36
36
  */
37
37
  export function conflictsWithBuiltIn(key) {
38
38
  return BUILT_IN_SKILLS.has(key);
@@ -46,7 +46,7 @@ export function getSkillsDirectory() {
46
46
  /**
47
47
  * Load all skill markdown files from the skills directory
48
48
  * Returns an array of objects with filename (key) and content
49
- * Filters out invalid filenames and conflicts with built-in skills
49
+ * Filters out invalid filenames and conflicts with system skills
50
50
  */
51
51
  export function loadSkills() {
52
52
  const skillsDir = getSkillsDirectory();
@@ -65,7 +65,7 @@ export function loadSkills() {
65
65
  }
66
66
  // Extract key (filename without extension, handles both .md and .MD)
67
67
  const key = file.slice(0, -3);
68
- // Must not conflict with built-in skills
68
+ // Must not conflict with system skills
69
69
  if (conflictsWithBuiltIn(key)) {
70
70
  return false;
71
71
  }
@@ -26,8 +26,8 @@ You will receive:
26
26
 
27
27
  ## Task
28
28
 
29
- Present the concierge's capabilities as a list of tasks, each representing
30
- one capability.
29
+ Present the concierge's capabilities as a list of capability objects, each
30
+ with a name, description, and origin.
31
31
 
32
32
  ## Response Format
33
33
 
@@ -77,7 +77,7 @@ NON-NEGOTIABLE and applies to EVERY response.
77
77
 
78
78
  **CORRECT ORDER - FOLLOW EXACTLY:**
79
79
 
80
- ### Position 1-4: Built-in Capabilities (Direct User Operations)
80
+ ### Position 1-4: system capabilities (origin: "system")
81
81
 
82
82
  These MUST appear FIRST, in this EXACT sequence:
83
83
 
@@ -86,47 +86,56 @@ These MUST appear FIRST, in this EXACT sequence:
86
86
  3. **Answer** ← ALWAYS THIRD
87
87
  4. **Execute** ← ALWAYS FOURTH
88
88
 
89
- ### Position 5-7: Indirect Workflow Capabilities
89
+ ### Position 5-7: meta workflow capabilities (origin: "meta")
90
90
 
91
- These MUST appear AFTER Execute and BEFORE user skills:
91
+ These MUST appear AFTER Execute and BEFORE user-provided skills:
92
92
 
93
93
  5. **Schedule** ← NEVER FIRST, ALWAYS position 5 (after Execute)
94
94
  6. **Validate** ← ALWAYS position 6 (after Schedule)
95
95
  7. **Report** ← NEVER FIRST, ALWAYS position 7 (after Validate)
96
96
 
97
- ### 3. User-Defined Skills
97
+ ### 3. user-provided skills (origin: "user")
98
98
 
99
99
  If skills are provided in the "Available Skills" section below, include
100
100
  them in the response. For each skill:
101
101
  - Extract the skill name from the first heading (# Skill Name)
102
- - If the skill name contains "(INCOMPLETE)", preserve it exactly in the
103
- task action
102
+ - Set origin to "user"
103
+ - If the skill name contains "(INCOMPLETE)", set isIncomplete to true and
104
+ remove "(INCOMPLETE)" from the name
104
105
  - Extract a brief description from the Description or Overview section
105
106
  - Keep descriptions concise (1-2 lines maximum)
106
107
  - If the user specified a filter (e.g., "skills for deployment"), only
107
108
  include skills whose name or description matches the filter
108
109
 
109
- ## Task Definition Guidelines
110
+ ## Capability Object Guidelines
110
111
 
111
- Create tasks with type "introspect" for each capability. Each task should:
112
+ Create capability objects for each capability. Each object should have:
112
113
 
113
- - **Action**: The capability name and a concise description
114
- - Format: "Capability Name: description" (note: display format will use
115
- " - " separator)
116
- - **IMPORTANT**: Use title case for capability names (e.g., "Schedule",
117
- "Execute"), NOT all uppercase (NOT "SCHEDULE", "EXECUTE")
114
+ - **name**: The capability or skill name
115
+ - Use title case (e.g., "Schedule", "Execute", "Deploy Application")
116
+ - NOT all uppercase (NOT "SCHEDULE", "EXECUTE")
117
+ - Maximum 32 characters
118
+ - Examples: "Introspect", "Execute", "Deploy Application"
119
+
120
+ - **description**: A concise description of what this capability does
121
+ - Maximum 64 characters
122
+ - Start with lowercase letter, no ending punctuation
123
+ - Focus on clarity and brevity
124
+ - Describe the core purpose in one short phrase
118
125
  - Examples:
119
- - "Schedule: break down requests into actionable steps"
120
- - "Execute: run shell commands and process operations"
121
- - "Deploy Application: build and deploy to staging or production"
122
- - **Type**: Always use "introspect"
123
- - **Params**: Omit params field
124
-
125
- **Keep action descriptions concise:**
126
- - Maximum 60 characters for the description portion (after the colon)
127
- - Focus on clarity and brevity
128
- - Describe the core purpose in one short phrase
129
- - Start descriptions with a lowercase letter (they follow a colon)
126
+ - "break down requests into actionable steps"
127
+ - "run shell commands and process operations"
128
+ - "build and deploy to staging or production"
129
+
130
+ - **origin**: The origin type of the capability
131
+ - Use "system" for system capabilities: Introspect, Configure, Answer,
132
+ Execute
133
+ - Use "meta" for meta workflow capabilities: Schedule, Validate, Report
134
+ - Use "user" for all user-provided skills
135
+
136
+ - **isIncomplete**: Optional boolean flag
137
+ - Only include if the skill is marked as incomplete
138
+ - Set to true if skill name contained "(INCOMPLETE)"
130
139
 
131
140
  ## Filtering
132
141
 
@@ -134,48 +143,48 @@ When the user specifies a filter (e.g., "skills for deployment", "what
134
143
  can you do with files"):
135
144
  1. Parse the filter keyword(s) from the request
136
145
  2. Match against skill names and descriptions (case-insensitive)
137
- 3. Include built-in capabilities if they match the filter
146
+ 3. Include system capabilities if they match the filter
138
147
  4. Only present capabilities that match the filter
139
148
 
140
149
  Examples:
141
150
  - "skills for deployment" → Only show skills with "deploy" in
142
151
  name/description
143
152
  - "what can you do with files" → Show EXECUTE and any file-related skills
144
- - "list all skills" → Show all built-in capabilities + all user skills
153
+ - "list all skills" → Show all system capabilities + all user-provided skills
145
154
 
146
155
  ## Examples
147
156
 
148
157
  ### Example 1: List All Capabilities
149
158
 
150
159
  When user asks "list your skills", create an introductory message like
151
- "here are my capabilities:" followed by tasks for built-in capabilities
152
- (Introspect, Configure, Answer, Execute), then indirect workflow capabilities
153
- (Schedule, Validate, Report).
154
-
155
- Each task uses type "introspect" with an action describing the
156
- capability.
160
+ "here are my capabilities:" followed by capability objects for system
161
+ capabilities (Introspect, Configure, Answer, Execute with origin
162
+ "system"), then meta workflow capabilities (Schedule, Validate, Report
163
+ with origin "meta").
157
164
 
158
165
  ### Example 2: Filtered Skills
159
166
 
160
167
  When user asks "skills for deployment" and a "deploy app" skill exists,
161
168
  create an introductory message like "these skills match 'deployment':"
162
- followed by only the tasks that match the filter. In this case, show the
163
- deploy app skill with its description.
169
+ followed by only the capabilities that match the filter. Show the deploy
170
+ app skill with origin "user".
164
171
 
165
172
  ### Example 3: With User Skills
166
173
 
167
- When user asks "what can you do" and user-defined skills like "process
174
+ When user asks "what can you do" and user-provided skills like "process
168
175
  data" and "backup files" exist, create an introductory message like "i can
169
- help with these operations:" followed by all built-in capabilities
170
- (Introspect, Configure, Answer, Execute, Validate, Schedule, Report) plus the
171
- user-defined skills. Each capability and skill becomes a task with type
172
- "introspect".
176
+ help with these operations:" followed by all system capabilities
177
+ (Introspect, Configure, Answer, Execute with origin "system"), meta
178
+ capabilities (Schedule, Validate, Report with origin "meta"), plus the
179
+ user-provided skills with origin "user".
173
180
 
174
181
  ## Final Validation
175
182
 
176
183
  Before finalizing:
177
- 1. Ensure every task has type "introspect"
178
- 2. Verify action descriptions are concise (≤64 characters)
184
+ 1. Ensure every capability has the correct origin value ("system",
185
+ "meta", or "user")
186
+ 2. Verify descriptions are concise (≤64 characters)
179
187
  3. Confirm the introductory message ends with a colon
180
188
  4. Check that filtering was applied correctly if specified
181
189
  5. Ensure no duplicate capabilities are listed
190
+ 6. Verify names use title case, not all uppercase
@@ -83,12 +83,17 @@ settings", type "configure", params { query: "app" }
83
83
 
84
84
  Before creating tasks, evaluate the request type:
85
85
 
86
- 1. **Information requests** (questions) - Use question keywords:
86
+ 1. **Introspection requests** - User asks about your capabilities:
87
+ - "list your skills", "what can you do", "flex", "show off", "list
88
+ capabilities", "show skills"
89
+ - Example: "flex" → introspect type
90
+
91
+ 2. **Information requests** (questions) - Use question keywords:
87
92
  - "explain", "describe", "tell me", "what is", "how does", "find",
88
93
  "search"
89
94
  - Example: "explain docker" → answer type
90
95
 
91
- 2. **Action requests** (commands) - Must match available skills:
96
+ 3. **Action requests** (commands) - Must match available skills:
92
97
  - Action verbs like "compile", "deploy", "process", "validate"
93
98
  - If verb matches a skill → extract skill steps as subtasks
94
99
  - If verb does NOT match any skill → ignore type with action
@@ -98,7 +103,7 @@ Before creating tasks, evaluate the request type:
98
103
  - Example: "validate" with no skill → action "Ignore unknown
99
104
  'validate' request"
100
105
 
101
- 3. **Vague/ambiguous requests** without clear verb:
106
+ 4. **Vague/ambiguous requests** without clear verb:
102
107
  - Phrases like "do something", "handle it" → ignore type
103
108
  - Action format: "Ignore unknown 'X' request" where X is the phrase
104
109
 
@@ -1,6 +1,6 @@
1
1
  export const introspectTool = {
2
2
  name: 'introspect',
3
- description: 'Execute a task with type "introspect" to list available capabilities and skills. Called after SCHEDULE has identified an introspection request and user has confirmed. Takes the task action and optional filter parameter to present built-in capabilities and user-defined skills.',
3
+ description: 'Execute a task with type "introspect" to list available capabilities and skills. Called after SCHEDULE has identified an introspection request and user has confirmed. Takes the task action and optional filter parameter to present system capabilities and user-provided skills.',
4
4
  input_schema: {
5
5
  type: 'object',
6
6
  properties: {
@@ -8,25 +8,34 @@ export const introspectTool = {
8
8
  type: 'string',
9
9
  description: 'Introductory reply to display before the capabilities list. Must be a single sentence, maximum 64 characters (including the colon at the end). Vary this naturally - try to use a different phrase each time. Always end with a colon.',
10
10
  },
11
- tasks: {
11
+ capabilities: {
12
12
  type: 'array',
13
- description: 'Array of capabilities, each with type "introspect". Include built-in capabilities (SCHEDULE, INTROSPECT, ANSWER, EXECUTE, REPORT, CONFIGURE) and user-defined skills from the Available Skills section.',
13
+ description: 'Array of capabilities and skills. Include system capabilities (Introspect, Configure, Answer, Execute) with origin "system", meta workflow capabilities (Schedule, Validate, Report) with origin "meta", and user-provided skills from the Available Skills section with origin "user".',
14
14
  items: {
15
15
  type: 'object',
16
16
  properties: {
17
- action: {
17
+ name: {
18
18
  type: 'string',
19
- description: 'Capability name and description. Format: "NAME: Brief description". Maximum 64 characters. Examples: "SCHEDULE: Break down requests into steps", "Deploy App: Build and deploy application".',
19
+ description: 'Capability or skill name. Use title case. Maximum 32 characters. Examples: "Execute", "Deploy Application", "Process Data".',
20
20
  },
21
- type: {
21
+ description: {
22
22
  type: 'string',
23
- description: 'Always "introspect" for capability listings.',
23
+ description: 'Brief description of what this capability does. Start with lowercase letter, no ending punctuation. Maximum 64 characters. Examples: "run shell commands and operations", "build and deploy to production".',
24
+ },
25
+ origin: {
26
+ type: 'string',
27
+ enum: ['system', 'user', 'meta'],
28
+ description: 'Origin of the capability. Use "system" for system capabilities (Introspect, Configure, Answer, Execute), "meta" for meta workflow capabilities (Schedule, Validate, Report), and "user" for user-provided skills.',
29
+ },
30
+ isIncomplete: {
31
+ type: 'boolean',
32
+ description: 'Optional. Set to true if the skill is marked as incomplete.',
24
33
  },
25
34
  },
26
- required: ['action', 'type'],
35
+ required: ['name', 'description', 'origin'],
27
36
  },
28
37
  },
29
38
  },
30
- required: ['message', 'tasks'],
39
+ required: ['message', 'capabilities'],
31
40
  },
32
41
  };
@@ -0,0 +1,25 @@
1
+ import { TaskType } from './types.js';
2
+ /**
3
+ * Type guard to check if a task is a ScheduledTask
4
+ * ScheduledTask has optional subtasks property or is a Group type
5
+ */
6
+ export function isScheduledTask(task) {
7
+ return 'subtasks' in task || task.type === TaskType.Group;
8
+ }
9
+ /**
10
+ * Type-safe conversion of Task array to ScheduledTask array
11
+ * This is safe because Tasks can be treated as ScheduledTask when checking for Groups
12
+ */
13
+ export function asScheduledTasks(tasks) {
14
+ return tasks;
15
+ }
16
+ /**
17
+ * Type guard to check if a value is a valid Task
18
+ */
19
+ export function isTask(value) {
20
+ return (typeof value === 'object' &&
21
+ value !== null &&
22
+ 'action' in value &&
23
+ typeof value.action === 'string' &&
24
+ 'type' in value);
25
+ }
@@ -29,6 +29,12 @@ export var TaskType;
29
29
  TaskType["Discard"] = "discard";
30
30
  TaskType["Group"] = "group";
31
31
  })(TaskType || (TaskType = {}));
32
+ export var Origin;
33
+ (function (Origin) {
34
+ Origin["BuiltIn"] = "system";
35
+ Origin["UserProvided"] = "user";
36
+ Origin["Indirect"] = "meta";
37
+ })(Origin || (Origin = {}));
32
38
  export var FeedbackType;
33
39
  (function (FeedbackType) {
34
40
  FeedbackType["Info"] = "info";
package/dist/ui/Answer.js CHANGED
@@ -23,11 +23,6 @@ export function Answer({ question, state, status, service, handlers, }) {
23
23
  if (!isActive) {
24
24
  return;
25
25
  }
26
- // Skip processing if no service available
27
- if (!service) {
28
- setError('No service available');
29
- return;
30
- }
31
26
  let mounted = true;
32
27
  async function process(svc) {
33
28
  try {
@@ -58,7 +53,7 @@ export function Answer({ question, state, status, service, handlers, }) {
58
53
  }
59
54
  }
60
55
  }
61
- process(service);
56
+ void process(service);
62
57
  return () => {
63
58
  mounted = false;
64
59
  };
@@ -1,7 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
- import { ComponentStatus, } from '../types/components.js';
4
+ import { ComponentStatus } from '../types/components.js';
5
5
  import { TaskType } from '../types/types.js';
6
6
  import { Colors } from '../services/colors.js';
7
7
  import { addDebugToTimeline, createScheduleDefinition, } from '../services/components.js';
@@ -27,11 +27,6 @@ export function Command({ command, state, status, service, handlers, onAborted,
27
27
  if (!isActive) {
28
28
  return;
29
29
  }
30
- // Skip processing if no service available
31
- if (!service) {
32
- setError('No service available');
33
- return;
34
- }
35
30
  let mounted = true;
36
31
  async function process(svc) {
37
32
  const startTime = Date.now();
@@ -64,6 +59,9 @@ export function Command({ command, state, status, service, handlers, onAborted,
64
59
  });
65
60
  // Check if tasks contain DEFINE type (variant selection needed)
66
61
  const hasDefineTask = result.tasks.some((task) => task.type === TaskType.Define);
62
+ if (!handlers) {
63
+ return;
64
+ }
67
65
  // Create Schedule definition
68
66
  const scheduleDefinition = createScheduleDefinition(result.message, result.tasks, hasDefineTask
69
67
  ? async (selectedTasks) => {
@@ -73,12 +71,12 @@ export function Command({ command, state, status, service, handlers, onAborted,
73
71
  : undefined);
74
72
  if (hasDefineTask) {
75
73
  // DEFINE tasks: Move Command to timeline, add Schedule to queue
76
- handlers?.completeActive();
77
- handlers?.addToQueue(scheduleDefinition);
74
+ handlers.completeActive();
75
+ handlers.addToQueue(scheduleDefinition);
78
76
  }
79
77
  else {
80
78
  // No DEFINE tasks: Complete Command, then route to Confirm flow
81
- handlers?.completeActive();
79
+ handlers.completeActive();
82
80
  routeTasksWithConfirm(result.tasks, result.message, svc, command, handlers, false);
83
81
  }
84
82
  }
@@ -95,7 +93,7 @@ export function Command({ command, state, status, service, handlers, onAborted,
95
93
  }
96
94
  }
97
95
  }
98
- process(service);
96
+ void process(service);
99
97
  return () => {
100
98
  mounted = false;
101
99
  };
package/dist/ui/Config.js CHANGED
@@ -8,6 +8,19 @@ import { Colors } from '../services/colors.js';
8
8
  import { createFeedback } from '../services/components.js';
9
9
  import { DebugLevel } from '../services/configuration.js';
10
10
  import { useInput } from '../services/keyboard.js';
11
+ /**
12
+ * Get postfix with debug brackets if debug is enabled
13
+ * Info: {key} | Verbose: {key} entry
14
+ */
15
+ function getPostfix(text, debugLevel) {
16
+ if (debugLevel === DebugLevel.None || !text) {
17
+ return '';
18
+ }
19
+ if (debugLevel === DebugLevel.Info) {
20
+ return `{${text}}`;
21
+ }
22
+ return `{${text}} entry`;
23
+ }
11
24
  export var StepType;
12
25
  (function (StepType) {
13
26
  StepType["Text"] = "text";
@@ -89,8 +102,8 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
89
102
  stepConfig.options[stepConfig.defaultIndex].value;
90
103
  break;
91
104
  default: {
92
- const exhaustiveCheck = stepConfig;
93
- throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
105
+ const _exhaustiveCheck = stepConfig;
106
+ throw new Error('Unsupported step type');
94
107
  }
95
108
  }
96
109
  });
@@ -132,7 +145,6 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
132
145
  const value = values[configKey] || '';
133
146
  setInputValue(value);
134
147
  }
135
- // eslint-disable-next-line react-hooks/exhaustive-deps
136
148
  }, [step, isActive, steps]);
137
149
  useInput((_, key) => {
138
150
  if (!isActive || step >= steps.length)
@@ -140,25 +152,23 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
140
152
  const currentStepConfig = steps[step];
141
153
  if (key.escape) {
142
154
  // Save current value before aborting
143
- if (currentStepConfig) {
144
- const configKey = currentStepConfig.path || currentStepConfig.key;
145
- let currentValue = '';
146
- switch (currentStepConfig.type) {
147
- case StepType.Text:
148
- currentValue = inputValue || values[configKey] || '';
149
- break;
150
- case StepType.Selection:
151
- currentValue = values[configKey] || '';
152
- break;
153
- default: {
154
- const exhaustiveCheck = currentStepConfig;
155
- throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
156
- }
157
- }
158
- if (currentValue) {
159
- setValues({ ...values, [configKey]: currentValue });
155
+ const configKey = currentStepConfig.path || currentStepConfig.key;
156
+ let currentValue = '';
157
+ switch (currentStepConfig.type) {
158
+ case StepType.Text:
159
+ currentValue = inputValue || values[configKey] || '';
160
+ break;
161
+ case StepType.Selection:
162
+ currentValue = values[configKey] || '';
163
+ break;
164
+ default: {
165
+ const _exhaustiveCheck = currentStepConfig;
166
+ throw new Error('Unsupported step type');
160
167
  }
161
168
  }
169
+ if (currentValue) {
170
+ setValues({ ...values, [configKey]: currentValue });
171
+ }
162
172
  // Save state before aborting
163
173
  handlers?.updateState({
164
174
  values,
@@ -204,8 +214,8 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
204
214
  break;
205
215
  }
206
216
  default: {
207
- const exhaustiveCheck = currentStepConfig;
208
- throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
217
+ const _exhaustiveCheck = currentStepConfig;
218
+ throw new Error('Unsupported step type');
209
219
  }
210
220
  }
211
221
  // Don't allow empty or invalid value
@@ -278,8 +288,8 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
278
288
  return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
279
289
  }
280
290
  default: {
281
- const exhaustiveCheck = stepConfig;
282
- throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
291
+ const _exhaustiveCheck = stepConfig;
292
+ throw new Error('Unsupported step type');
283
293
  }
284
294
  }
285
295
  };
@@ -291,6 +301,7 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
291
301
  if (!shouldShow) {
292
302
  return null;
293
303
  }
294
- return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), debug !== DebugLevel.None && stepConfig.path && (_jsxs(Text, { color: Colors.Type.Define, children: ['{', stepConfig.path, '}'] }))] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.path || stepConfig.key));
304
+ const postfix = getPostfix(stepConfig.path, debug);
305
+ return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), postfix && _jsx(Text, { color: Colors.Type.Config, children: postfix })] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.path || stepConfig.key));
295
306
  }) }));
296
307
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { ComponentStatus } from '../types/components.js';
5
- import { Colors, Palette } from '../services/colors.js';
5
+ import { Colors, getTextColor, Palette } from '../services/colors.js';
6
6
  import { useInput } from '../services/keyboard.js';
7
7
  import { UserQuery } from './UserQuery.js';
8
8
  export function Confirm({ message, state, status, handlers, onConfirmed, onCancelled, }) {
@@ -15,7 +15,7 @@ export function Confirm({ message, state, status, handlers, onConfirmed, onCance
15
15
  // Escape: highlight "No" and cancel
16
16
  setSelectedIndex(1);
17
17
  handlers?.updateState({ selectedIndex: 1 });
18
- onCancelled?.();
18
+ onCancelled();
19
19
  }
20
20
  else if (key.tab) {
21
21
  // Toggle between Yes (0) and No (1)
@@ -27,10 +27,10 @@ export function Confirm({ message, state, status, handlers, onConfirmed, onCance
27
27
  // Confirm selection
28
28
  handlers?.updateState({ selectedIndex, confirmed: true });
29
29
  if (selectedIndex === 0) {
30
- onConfirmed?.();
30
+ onConfirmed();
31
31
  }
32
32
  else {
33
- onCancelled?.();
33
+ onCancelled();
34
34
  }
35
35
  }
36
36
  }, { isActive });
@@ -42,7 +42,7 @@ export function Confirm({ message, state, status, handlers, onConfirmed, onCance
42
42
  // When done, show both the message and user's choice in timeline
43
43
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsxs(UserQuery, { children: ["> ", options[selectedIndex].label] })] }));
44
44
  }
45
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: isActive ? Colors.Text.Active : Colors.Text.Inactive, children: message }) }), _jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
45
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: message }) }), _jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
46
46
  const isSelected = index === selectedIndex;
47
47
  return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
48
48
  }) })] })] }));
@@ -69,10 +69,6 @@ export function Execute({ tasks, state, status, service, handlers, }) {
69
69
  if (!isActive || taskInfos.length > 0 || hasProcessed) {
70
70
  return;
71
71
  }
72
- if (!service) {
73
- setError('No service available');
74
- return;
75
- }
76
72
  let mounted = true;
77
73
  async function process(svc) {
78
74
  const startTime = Date.now();
@@ -156,7 +152,7 @@ export function Execute({ tasks, state, status, service, handlers, }) {
156
152
  }
157
153
  }
158
154
  }
159
- process(service);
155
+ void process(service);
160
156
  return () => {
161
157
  mounted = false;
162
158
  };
@@ -186,7 +182,7 @@ export function Execute({ tasks, state, status, service, handlers, }) {
186
182
  else {
187
183
  // All tasks complete
188
184
  const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
189
- const summaryText = summary?.trim() || 'Execution completed';
185
+ const summaryText = summary.trim() || 'Execution completed';
190
186
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
191
187
  setCompletionMessage(completion);
192
188
  handlers?.updateState({
@@ -203,7 +199,7 @@ export function Execute({ tasks, state, status, service, handlers, }) {
203
199
  }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
204
200
  const handleTaskError = useCallback((index, error, elapsed) => {
205
201
  const task = taskInfos[index];
206
- const isCritical = task?.command.critical !== false; // Default to true
202
+ const isCritical = task.command.critical !== false; // Default to true
207
203
  // Update task with elapsed time and failed status
208
204
  const updatedTaskInfos = taskInfos.map((task, i) => i === index
209
205
  ? { ...task, status: ExecutionStatus.Failed, elapsed }
@@ -242,7 +238,7 @@ export function Execute({ tasks, state, status, service, handlers, }) {
242
238
  else {
243
239
  // Last task, complete execution
244
240
  const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
245
- const summaryText = summary?.trim() || 'Execution completed';
241
+ const summaryText = summary.trim() || 'Execution completed';
246
242
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
247
243
  setCompletionMessage(completion);
248
244
  handlers?.updateState({
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
- import { ComponentStatus, } from '../types/components.js';
4
+ import { ComponentStatus } from '../types/components.js';
5
5
  import { Colors, getTextColor } from '../services/colors.js';
6
6
  import { addDebugToTimeline, createReportDefinition, } from '../services/components.js';
7
7
  import { DebugLevel } from '../services/configuration.js';
@@ -10,49 +10,7 @@ import { formatErrorMessage } from '../services/messages.js';
10
10
  import { ensureMinimumTime } from '../services/timing.js';
11
11
  import { Spinner } from './Spinner.js';
12
12
  const MIN_PROCESSING_TIME = 1000;
13
- const BUILT_IN_CAPABILITIES = new Set([
14
- 'CONFIGURE',
15
- 'SCHEDULE',
16
- 'INTROSPECT',
17
- 'ANSWER',
18
- 'EXECUTE',
19
- 'VALIDATE',
20
- 'REPORT',
21
- ]);
22
- const INDIRECT_CAPABILITIES = new Set(['SCHEDULE', 'VALIDATE', 'REPORT']);
23
- function parseCapabilityFromTask(task) {
24
- // Parse "NAME: Description" format from task.action
25
- const colonIndex = task.action.indexOf(':');
26
- if (colonIndex === -1) {
27
- const upperName = task.action.toUpperCase();
28
- // Check for status markers
29
- const isIncomplete = task.action.includes('(INCOMPLETE)');
30
- const cleanName = task.action.replace(/\s*\(INCOMPLETE\)\s*/gi, '').trim();
31
- return {
32
- name: cleanName,
33
- description: '',
34
- isBuiltIn: BUILT_IN_CAPABILITIES.has(upperName),
35
- isIndirect: INDIRECT_CAPABILITIES.has(upperName),
36
- isIncomplete,
37
- };
38
- }
39
- const name = task.action.substring(0, colonIndex).trim();
40
- const description = task.action.substring(colonIndex + 1).trim();
41
- // Check for status markers
42
- const isIncomplete = name.includes('(INCOMPLETE)');
43
- const cleanName = name.replace(/\s*\(INCOMPLETE\)\s*/gi, '').trim();
44
- const upperName = cleanName.toUpperCase();
45
- const isBuiltIn = BUILT_IN_CAPABILITIES.has(upperName);
46
- const isIndirect = INDIRECT_CAPABILITIES.has(upperName);
47
- return {
48
- name: cleanName,
49
- description,
50
- isBuiltIn,
51
- isIndirect,
52
- isIncomplete,
53
- };
54
- }
55
- export function Introspect({ tasks, state, status, service, children, debug = DebugLevel.None, handlers, }) {
13
+ export function Introspect({ tasks, state: _state, status, service, children, debug = DebugLevel.None, handlers, }) {
56
14
  const isActive = status === ComponentStatus.Active;
57
15
  // isActive passed as prop
58
16
  const [error, setError] = useState(null);
@@ -66,11 +24,6 @@ export function Introspect({ tasks, state, status, service, children, debug = De
66
24
  if (!isActive) {
67
25
  return;
68
26
  }
69
- // Skip processing if no service available
70
- if (!service) {
71
- setError('No service available');
72
- return;
73
- }
74
27
  let mounted = true;
75
28
  async function process(svc) {
76
29
  const startTime = Date.now();
@@ -83,8 +36,8 @@ export function Introspect({ tasks, state, status, service, children, debug = De
83
36
  if (mounted) {
84
37
  // Add debug components to timeline if present
85
38
  addDebugToTimeline(result.debug, handlers);
86
- // Parse capabilities from returned tasks
87
- let capabilities = result.tasks.map(parseCapabilityFromTask);
39
+ // Capabilities come directly from result - no parsing needed
40
+ let capabilities = result.capabilities;
88
41
  // Filter out internal capabilities when not in debug mode
89
42
  if (debug === DebugLevel.None) {
90
43
  capabilities = capabilities.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
@@ -115,7 +68,7 @@ export function Introspect({ tasks, state, status, service, children, debug = De
115
68
  }
116
69
  }
117
70
  }
118
- process(service);
71
+ void process(service);
119
72
  return () => {
120
73
  mounted = false;
121
74
  };
package/dist/ui/Main.js CHANGED
@@ -86,7 +86,7 @@ export const Main = ({ app, command }) => {
86
86
  throw new Error(errorMessage);
87
87
  }
88
88
  };
89
- const handleConfigAborted = (operation) => {
89
+ const handleConfigAborted = (_operation) => {
90
90
  // Config was cancelled
91
91
  };
92
92
  setInitialQueue([
package/dist/ui/Report.js CHANGED
@@ -1,14 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- import { Colors } from '../services/colors.js';
4
- function CapabilityItem({ name, description, isBuiltIn, isIndirect, isIncomplete, }) {
5
- const color = isIndirect
6
- ? Colors.Origin.Indirect
7
- : isBuiltIn
8
- ? Colors.Origin.BuiltIn
9
- : Colors.Origin.UserProvided;
3
+ import { Colors, getOriginColor } from '../services/colors.js';
4
+ function CapabilityItem({ name, description, origin, isIncomplete, }) {
5
+ const color = getOriginColor(origin);
10
6
  return (_jsxs(Box, { children: [_jsx(Text, { children: "- " }), _jsx(Text, { color: color, children: name }), _jsxs(Text, { children: [" - ", description] }), isIncomplete && _jsx(Text, { color: Colors.Status.Warning, children: " (incomplete)" })] }));
11
7
  }
12
8
  export function Report({ message, capabilities }) {
13
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: capabilities.map((capability, index) => (_jsx(CapabilityItem, { name: capability.name, description: capability.description, isBuiltIn: capability.isBuiltIn, isIndirect: capability.isIndirect, isIncomplete: capability.isIncomplete }, index))) })] }));
9
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: capabilities.map((capability, index) => (_jsx(CapabilityItem, { name: capability.name, description: capability.description, origin: capability.origin, isIncomplete: capability.isIncomplete }, index))) })] }));
14
10
  }
@@ -37,7 +37,7 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
37
37
  const planColors = getTaskColors(TaskType.Schedule, isCurrent);
38
38
  return {
39
39
  description: {
40
- text: String(option),
40
+ text: option,
41
41
  color: colors.description,
42
42
  highlightedColor: planColors.description,
43
43
  },
@@ -96,7 +96,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
96
96
  // Complete the selection phase - it goes to timeline
97
97
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
98
98
  handlers?.completeActive();
99
- onSelectionConfirmed(concreteTasks);
99
+ void onSelectionConfirmed(concreteTasks);
100
100
  }
101
101
  }, [
102
102
  isActive,
@@ -153,7 +153,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
153
153
  // This is a Define task - only include the selected option
154
154
  const options = task.params.options;
155
155
  const selectedIndex = newCompletedSelections[defineGroupIndex];
156
- const selectedOption = String(options[selectedIndex]);
156
+ const selectedOption = options[selectedIndex];
157
157
  // Use Execute as default - LLM will properly classify during refinement
158
158
  refinedTasks.push({
159
159
  action: selectedOption,
@@ -171,7 +171,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
171
171
  // Complete the selection phase - it goes to timeline
172
172
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
173
173
  handlers?.completeActive();
174
- onSelectionConfirmed(refinedTasks);
174
+ void onSelectionConfirmed(refinedTasks);
175
175
  }
176
176
  else {
177
177
  // No selection callback, just complete normally
@@ -17,7 +17,9 @@ export function Spinner() {
17
17
  return next !== prev ? next : prev;
18
18
  });
19
19
  }, INTERVAL);
20
- return () => clearInterval(timer);
20
+ return () => {
21
+ clearInterval(timer);
22
+ };
21
23
  }, []);
22
24
  return _jsx(Text, { color: Palette.Cyan, children: FRAMES[frame] });
23
25
  }
@@ -4,7 +4,7 @@ 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
- export function Subtask({ label, command, status, isActive, startTime, endTime, elapsed, }) {
7
+ export function Subtask({ label, command, status, isActive: _isActive, startTime, endTime, elapsed, }) {
8
8
  const colors = getStatusColors(status);
9
9
  const isCancelled = status === ExecutionStatus.Cancelled;
10
10
  const isAborted = status === ExecutionStatus.Aborted;
package/dist/ui/Task.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { ExecutionStatus, executeCommand, } from '../services/shell.js';
3
+ import { ExecutionResult, ExecutionStatus, executeCommand, } from '../services/shell.js';
4
4
  import { calculateElapsed } from '../services/utils.js';
5
5
  import { Subtask } from './Subtask.js';
6
6
  export function Task({ label, command, isActive, index, initialStatus, initialElapsed, onComplete, onAbort, onError, }) {
@@ -19,7 +19,9 @@ export function Task({ label, command, isActive, index, initialStatus, initialEl
19
19
  return next !== prev ? next : prev;
20
20
  });
21
21
  }, 1000);
22
- return () => clearInterval(interval);
22
+ return () => {
23
+ clearInterval(interval);
24
+ };
23
25
  }, [status, startTime]);
24
26
  // Execute command when becoming active
25
27
  useEffect(() => {
@@ -43,10 +45,10 @@ export function Task({ label, command, isActive, index, initialStatus, initialEl
43
45
  setEndTime(end);
44
46
  const taskDuration = calculateElapsed(start);
45
47
  setElapsed(taskDuration);
46
- setStatus(output.result === 'success'
48
+ setStatus(output.result === ExecutionResult.Success
47
49
  ? ExecutionStatus.Success
48
50
  : ExecutionStatus.Failed);
49
- if (output.result === 'success') {
51
+ if (output.result === ExecutionResult.Success) {
50
52
  onComplete?.(index, output, taskDuration);
51
53
  }
52
54
  else {
@@ -64,11 +66,10 @@ export function Task({ label, command, isActive, index, initialStatus, initialEl
64
66
  onError?.(index, err instanceof Error ? err.message : 'Unknown error', errorDuration);
65
67
  }
66
68
  }
67
- execute();
69
+ void execute();
68
70
  return () => {
69
71
  mounted = false;
70
72
  };
71
- // eslint-disable-next-line react-hooks/exhaustive-deps
72
73
  }, [isActive]);
73
74
  // Handle abort when task becomes inactive while running
74
75
  useEffect(() => {
@@ -6,6 +6,7 @@ import { TaskType } from '../types/types.js';
6
6
  import { Colors, getTextColor } from '../services/colors.js';
7
7
  import { addDebugToTimeline, createConfigStepsFromSchema, } from '../services/components.js';
8
8
  import { DebugLevel, saveConfig, unflattenConfig, } from '../services/configuration.js';
9
+ import { saveConfigLabels } from '../services/config-labels.js';
9
10
  import { useInput } from '../services/keyboard.js';
10
11
  import { formatErrorMessage } from '../services/messages.js';
11
12
  import { ensureMinimumTime } from '../services/timing.js';
@@ -17,22 +18,16 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
17
18
  const [error, setError] = useState(state?.error ?? null);
18
19
  const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
19
20
  const [configRequirements, setConfigRequirements] = useState(state?.configRequirements ?? null);
20
- const [showConfig, setShowConfig] = useState(false);
21
21
  useInput((_, key) => {
22
- if (key.escape && isActive && !showConfig) {
22
+ if (key.escape && isActive) {
23
23
  onAborted('validation');
24
24
  }
25
- }, { isActive: isActive && !showConfig });
25
+ }, { isActive });
26
26
  useEffect(() => {
27
27
  // Skip processing if not active
28
28
  if (!isActive) {
29
29
  return;
30
30
  }
31
- // Skip processing if no service available
32
- if (!service) {
33
- setError('No service available');
34
- return;
35
- }
36
31
  let mounted = true;
37
32
  async function process(svc) {
38
33
  const startTime = Date.now();
@@ -93,13 +88,11 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
93
88
  configRequirements: null,
94
89
  validated: false,
95
90
  });
96
- if (onError) {
97
- onError(errorMessage);
98
- }
91
+ onError(errorMessage);
99
92
  }
100
93
  }
101
94
  }
102
- process(service);
95
+ void process(service);
103
96
  return () => {
104
97
  mounted = false;
105
98
  };
@@ -133,6 +126,16 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
133
126
  const handleConfigFinished = (config) => {
134
127
  // Convert flat dotted keys to nested structure grouped by section
135
128
  const configBySection = unflattenConfig(config);
129
+ // Extract and save labels to cache
130
+ if (configRequirements) {
131
+ const labels = {};
132
+ for (const req of configRequirements) {
133
+ if (req.description) {
134
+ labels[req.path] = req.description;
135
+ }
136
+ }
137
+ saveConfigLabels(labels);
138
+ }
136
139
  // Save each section
137
140
  for (const [section, sectionConfig] of Object.entries(configBySection)) {
138
141
  saveConfig(section, sectionConfig);
@@ -141,7 +144,9 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
141
144
  // This allows the workflow to proceed to execution
142
145
  handlers?.completeActive();
143
146
  // Invoke callback which will queue the Execute component
144
- onComplete?.(configRequirements);
147
+ if (configRequirements) {
148
+ onComplete(configRequirements);
149
+ }
145
150
  };
146
151
  const handleConfigAborted = (operation) => {
147
152
  // Mark validation component as complete when aborted
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-language-shell",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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",