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.
- package/dist/services/anthropic.js +29 -3
- package/dist/services/colors.js +20 -1
- package/dist/services/components.js +1 -1
- package/dist/services/config-labels.js +75 -0
- package/dist/services/config-utils.js +20 -0
- package/dist/services/configuration.js +15 -36
- package/dist/services/registry.js +1 -1
- package/dist/services/router.js +4 -3
- package/dist/services/skills.js +3 -3
- package/dist/skills/introspect.md +52 -43
- package/dist/skills/schedule.md +8 -3
- package/dist/tools/introspect.tool.js +18 -9
- package/dist/types/guards.js +25 -0
- package/dist/types/types.js +6 -0
- package/dist/ui/Answer.js +1 -6
- package/dist/ui/Command.js +8 -10
- package/dist/ui/Config.js +36 -25
- package/dist/ui/Confirm.js +5 -5
- package/dist/ui/Execute.js +4 -8
- package/dist/ui/Introspect.js +5 -52
- package/dist/ui/Main.js +1 -1
- package/dist/ui/Report.js +4 -8
- package/dist/ui/Schedule.js +4 -4
- package/dist/ui/Spinner.js +3 -1
- package/dist/ui/Subtask.js +1 -1
- package/dist/ui/Task.js +7 -6
- package/dist/ui/Validate.js +18 -13
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
}
|
package/dist/services/colors.js
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
-
|
|
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] =
|
|
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
|
|
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';
|
package/dist/services/router.js
CHANGED
|
@@ -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
|
-
//
|
|
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 = [];
|
package/dist/services/skills.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
30
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
-
|
|
103
|
-
|
|
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
|
-
##
|
|
110
|
+
## Capability Object Guidelines
|
|
110
111
|
|
|
111
|
-
Create
|
|
112
|
+
Create capability objects for each capability. Each object should have:
|
|
112
113
|
|
|
113
|
-
- **
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
|
|
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
|
-
- "
|
|
120
|
-
- "
|
|
121
|
-
- "
|
|
122
|
-
|
|
123
|
-
- **
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
-
|
|
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
|
|
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
|
|
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
|
|
152
|
-
(Introspect, Configure, Answer, Execute
|
|
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
|
|
163
|
-
|
|
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-
|
|
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
|
|
170
|
-
(Introspect, Configure, Answer, Execute
|
|
171
|
-
|
|
172
|
-
"
|
|
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
|
|
178
|
-
|
|
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
|
package/dist/skills/schedule.md
CHANGED
|
@@ -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. **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
11
|
+
capabilities: {
|
|
12
12
|
type: 'array',
|
|
13
|
-
description: 'Array of capabilities,
|
|
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
|
-
|
|
17
|
+
name: {
|
|
18
18
|
type: 'string',
|
|
19
|
-
description: 'Capability
|
|
19
|
+
description: 'Capability or skill name. Use title case. Maximum 32 characters. Examples: "Execute", "Deploy Application", "Process Data".',
|
|
20
20
|
},
|
|
21
|
-
|
|
21
|
+
description: {
|
|
22
22
|
type: 'string',
|
|
23
|
-
description: '
|
|
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: ['
|
|
35
|
+
required: ['name', 'description', 'origin'],
|
|
27
36
|
},
|
|
28
37
|
},
|
|
29
38
|
},
|
|
30
|
-
required: ['message', '
|
|
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
|
+
}
|
package/dist/types/types.js
CHANGED
|
@@ -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
|
};
|
package/dist/ui/Command.js
CHANGED
|
@@ -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
|
|
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
|
|
77
|
-
handlers
|
|
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
|
|
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
|
|
93
|
-
throw new Error(
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
208
|
-
throw new Error(
|
|
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
|
|
282
|
-
throw new Error(
|
|
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
|
-
|
|
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
|
}
|
package/dist/ui/Confirm.js
CHANGED
|
@@ -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
|
|
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
|
}) })] })] }));
|
package/dist/ui/Execute.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
241
|
+
const summaryText = summary.trim() || 'Execution completed';
|
|
246
242
|
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
247
243
|
setCompletionMessage(completion);
|
|
248
244
|
handlers?.updateState({
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
//
|
|
87
|
-
let capabilities = result.
|
|
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
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,
|
|
5
|
-
const color =
|
|
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,
|
|
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
|
}
|
package/dist/ui/Schedule.js
CHANGED
|
@@ -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:
|
|
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 =
|
|
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
|
package/dist/ui/Spinner.js
CHANGED
|
@@ -17,7 +17,9 @@ export function Spinner() {
|
|
|
17
17
|
return next !== prev ? next : prev;
|
|
18
18
|
});
|
|
19
19
|
}, INTERVAL);
|
|
20
|
-
return () =>
|
|
20
|
+
return () => {
|
|
21
|
+
clearInterval(timer);
|
|
22
|
+
};
|
|
21
23
|
}, []);
|
|
22
24
|
return _jsx(Text, { color: Palette.Cyan, children: FRAMES[frame] });
|
|
23
25
|
}
|
package/dist/ui/Subtask.js
CHANGED
|
@@ -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 () =>
|
|
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 ===
|
|
48
|
+
setStatus(output.result === ExecutionResult.Success
|
|
47
49
|
? ExecutionStatus.Success
|
|
48
50
|
: ExecutionStatus.Failed);
|
|
49
|
-
if (output.result ===
|
|
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(() => {
|
package/dist/ui/Validate.js
CHANGED
|
@@ -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
|
|
22
|
+
if (key.escape && isActive) {
|
|
23
23
|
onAborted('validation');
|
|
24
24
|
}
|
|
25
|
-
}, { isActive
|
|
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
|
-
|
|
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
|
-
|
|
147
|
+
if (configRequirements) {
|
|
148
|
+
onComplete(configRequirements);
|
|
149
|
+
}
|
|
145
150
|
};
|
|
146
151
|
const handleConfigAborted = (operation) => {
|
|
147
152
|
// Mark validation component as complete when aborted
|