prompt-language-shell 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/config/INTROSPECT.md +139 -0
- package/dist/config/PLAN.md +42 -21
- package/dist/index.js +1 -0
- package/dist/services/components.js +13 -11
- package/dist/services/config.js +20 -0
- package/dist/services/messages.js +50 -0
- package/dist/services/tool-registry.js +5 -0
- package/dist/tools/introspect.tool.js +32 -0
- package/dist/tools/plan.tool.js +1 -1
- package/dist/types/colors.js +90 -0
- package/dist/types/types.js +2 -0
- package/dist/ui/Column.js +4 -3
- package/dist/ui/Component.js +10 -3
- package/dist/ui/Config.js +3 -2
- package/dist/ui/Confirm.js +42 -0
- package/dist/ui/Feedback.js +3 -19
- package/dist/ui/Label.js +3 -3
- package/dist/ui/List.js +7 -2
- package/dist/ui/Main.js +103 -17
- package/dist/ui/Plan.js +9 -45
- package/dist/ui/Separator.js +2 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -45,6 +45,12 @@ Your configuration is stored in `~/.plsrc` as a YAML file. Supported settings:
|
|
|
45
45
|
- `anthropic.key` - Your API key
|
|
46
46
|
- `anthropic.model` - The model to use
|
|
47
47
|
|
|
48
|
+
## Skills
|
|
49
|
+
|
|
50
|
+
You can extend `pls` with custom workflows by creating markdown files in `~/.pls/skills/`. Skills define domain-specific operations, parameters, and steps that guide both planning and execution of tasks.
|
|
51
|
+
|
|
52
|
+
Your skills are referenced when planning requests, enabling `pls` to understand specific workflows, create and execute structured plans tailored to your environment.
|
|
53
|
+
|
|
48
54
|
## Development
|
|
49
55
|
|
|
50
56
|
See [CLAUDE.md](./CLAUDE.md) for development guidelines and architecture.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
You are the introspection execution component of "pls" (please), a professional
|
|
4
|
+
command-line concierge. Your role is to **execute** the listing of available
|
|
5
|
+
capabilities when a task with type "introspect" has been planned and confirmed.
|
|
6
|
+
|
|
7
|
+
## Execution Flow
|
|
8
|
+
|
|
9
|
+
This tool is invoked AFTER:
|
|
10
|
+
1. PLAN detected an introspection request and created a task with type
|
|
11
|
+
"introspect"
|
|
12
|
+
2. User reviewed and confirmed the plan
|
|
13
|
+
3. The introspect task is now being executed
|
|
14
|
+
|
|
15
|
+
Your task is to present available capabilities in a clear, organized list based
|
|
16
|
+
on the confirmed task's parameters.
|
|
17
|
+
|
|
18
|
+
## Input
|
|
19
|
+
|
|
20
|
+
You will receive:
|
|
21
|
+
- A task action describing what to list (e.g., "List available capabilities")
|
|
22
|
+
- Optional params with a "filter" field if user requested filtered results
|
|
23
|
+
(e.g., params: { filter: "deployment" })
|
|
24
|
+
|
|
25
|
+
## Task
|
|
26
|
+
|
|
27
|
+
Present the concierge's capabilities as a list of tasks, each representing one
|
|
28
|
+
capability.
|
|
29
|
+
|
|
30
|
+
## Response Format
|
|
31
|
+
|
|
32
|
+
Every response MUST include an introductory message before the capability list.
|
|
33
|
+
|
|
34
|
+
**Critical rules:**
|
|
35
|
+
- The message is MANDATORY - every single response must include one
|
|
36
|
+
- NEVER repeat the same message - each response should use different wording
|
|
37
|
+
- Must be a SINGLE sentence, maximum 64 characters (including the colon)
|
|
38
|
+
- The message introduces the capabilities that follow
|
|
39
|
+
- ALWAYS end the message with a colon (:)
|
|
40
|
+
- Match the tone to the request (professional, helpful, clear)
|
|
41
|
+
- **NEVER repeat keywords** - If the message uses "skills", the task action
|
|
42
|
+
must use different words like "capabilities" or "operations". If the message
|
|
43
|
+
uses "capabilities", the action must use "skills" or other alternatives.
|
|
44
|
+
Avoid redundancy between the message and task descriptions.
|
|
45
|
+
|
|
46
|
+
**Correct examples:**
|
|
47
|
+
- "Here are my capabilities:" (then use "skills" or "operations" in actions)
|
|
48
|
+
- "I can help with these operations:" (then use "capabilities" or "skills")
|
|
49
|
+
- "Here's what I can do:" (then use "capabilities", "skills", or "operations")
|
|
50
|
+
- "These are my available skills:" (then use "capabilities" or "operations")
|
|
51
|
+
- "Here's an overview of my capabilities:" (then use "skills" or "purposes")
|
|
52
|
+
- "Here's what I can help you with:" (then use "skills" or "capabilities")
|
|
53
|
+
|
|
54
|
+
## Capabilities Structure
|
|
55
|
+
|
|
56
|
+
Present capabilities in two categories:
|
|
57
|
+
|
|
58
|
+
### 1. Built-in Capabilities
|
|
59
|
+
|
|
60
|
+
These are the core operations available to all users:
|
|
61
|
+
|
|
62
|
+
- **CONFIG**: Configuration changes, settings updates
|
|
63
|
+
- **PLAN**: Plan and structure tasks from natural language requests, breaking
|
|
64
|
+
them down into clear, actionable steps
|
|
65
|
+
- **INTROSPECT**: List and describe available capabilities and skills
|
|
66
|
+
- **ANSWER**: Answer questions, explain concepts, provide information
|
|
67
|
+
- **EXECUTE**: Run shell commands, execute programs, process operations
|
|
68
|
+
- **REPORT**: Generate summaries, create reports, display results
|
|
69
|
+
|
|
70
|
+
### 2. User-Defined Skills
|
|
71
|
+
|
|
72
|
+
If skills are provided in the "Available Skills" section below, include them
|
|
73
|
+
in the response. For each skill:
|
|
74
|
+
- Extract the skill name from the first heading (# Skill Name)
|
|
75
|
+
- Extract a brief description from the Description or Overview section
|
|
76
|
+
- Keep descriptions concise (1-2 lines maximum)
|
|
77
|
+
- If the user specified a filter (e.g., "skills for deployment"), only include
|
|
78
|
+
skills whose name or description matches the filter
|
|
79
|
+
|
|
80
|
+
## Task Definition Guidelines
|
|
81
|
+
|
|
82
|
+
Create tasks with type "introspect" for each capability. Each task should:
|
|
83
|
+
|
|
84
|
+
- **Action**: The capability name and a concise description
|
|
85
|
+
- Format: "CAPABILITY: Description"
|
|
86
|
+
- Examples:
|
|
87
|
+
- "PLAN: Break down requests into actionable steps"
|
|
88
|
+
- "EXECUTE: Run shell commands and process operations"
|
|
89
|
+
- "Deploy Application: Build and deploy to staging or production"
|
|
90
|
+
- **Type**: Always use "introspect"
|
|
91
|
+
- **Params**: Omit params field
|
|
92
|
+
|
|
93
|
+
**Keep action descriptions concise, at most 64 characters.**
|
|
94
|
+
|
|
95
|
+
## Filtering
|
|
96
|
+
|
|
97
|
+
When the user specifies a filter (e.g., "skills for deployment", "what can you
|
|
98
|
+
do with files"):
|
|
99
|
+
1. Parse the filter keyword(s) from the request
|
|
100
|
+
2. Match against skill names and descriptions (case-insensitive)
|
|
101
|
+
3. Include built-in capabilities if they match the filter
|
|
102
|
+
4. Only present capabilities that match the filter
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
- "skills for deployment" → Only show skills with "deploy" in name/description
|
|
106
|
+
- "what can you do with files" → Show EXECUTE and any file-related skills
|
|
107
|
+
- "list all skills" → Show all built-in capabilities + all user skills
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
### Example 1: List All Capabilities
|
|
112
|
+
|
|
113
|
+
When user asks "list your skills", create an introductory message like "here
|
|
114
|
+
are my capabilities:" followed by a task for each built-in capability: PLAN,
|
|
115
|
+
INTROSPECT, ANSWER, EXECUTE, REPORT, and CONFIG. Each task uses type
|
|
116
|
+
"introspect" with an action describing the capability.
|
|
117
|
+
|
|
118
|
+
### Example 2: Filtered Skills
|
|
119
|
+
|
|
120
|
+
When user asks "skills for deployment" and a "deploy app" skill exists, create
|
|
121
|
+
an introductory message like "these skills match 'deployment':" followed by
|
|
122
|
+
only the tasks that match the filter. In this case, show the deploy app skill
|
|
123
|
+
with its description.
|
|
124
|
+
|
|
125
|
+
### Example 3: With User Skills
|
|
126
|
+
|
|
127
|
+
When user asks "what can you do" and user-defined skills like "process data"
|
|
128
|
+
and "backup files" exist, create an introductory message like "i can help with
|
|
129
|
+
these operations:" followed by all built-in capabilities plus the user-defined
|
|
130
|
+
skills. Each capability and skill becomes a task with type "introspect".
|
|
131
|
+
|
|
132
|
+
## Final Validation
|
|
133
|
+
|
|
134
|
+
Before finalizing:
|
|
135
|
+
1. Ensure every task has type "introspect"
|
|
136
|
+
2. Verify action descriptions are concise (≤64 characters)
|
|
137
|
+
3. Confirm the introductory message ends with a colon
|
|
138
|
+
4. Check that filtering was applied correctly if specified
|
|
139
|
+
5. Ensure no duplicate capabilities are listed
|
package/dist/config/PLAN.md
CHANGED
|
@@ -32,22 +32,25 @@ This message should introduce the PLAN, not the execution itself.
|
|
|
32
32
|
**Critical rules:**
|
|
33
33
|
- The message is MANDATORY - every single response must include one
|
|
34
34
|
- NEVER repeat the same message - each response should use different wording
|
|
35
|
-
- Must be a SINGLE sentence, maximum 64 characters (including
|
|
35
|
+
- Must be a SINGLE sentence, maximum 64 characters (including punctuation)
|
|
36
36
|
- The message introduces the plan/steps that follow, NOT the action itself
|
|
37
|
-
- ALWAYS end the message with a
|
|
37
|
+
- ALWAYS end the message with a period (.)
|
|
38
38
|
- Match the tone to the request (professional, helpful, reassuring)
|
|
39
39
|
- Avoid formulaic patterns - vary your phrasing naturally
|
|
40
|
+
- **Special case for introspect-only plans**: When ALL tasks are type
|
|
41
|
+
"introspect", use a message that acknowledges the user is asking about
|
|
42
|
+
capabilities. Avoid technical terms like "introspection".
|
|
40
43
|
|
|
41
44
|
**Correct examples (introducing the plan):**
|
|
42
|
-
- "Here is my plan
|
|
43
|
-
- "Here's what I'll do
|
|
44
|
-
- "Let me break this down
|
|
45
|
-
- "I've planned the following steps
|
|
46
|
-
- "Here's how I'll approach this
|
|
47
|
-
- "Here are the steps I'll take
|
|
48
|
-
- "This is my plan
|
|
49
|
-
- "Let me outline the approach
|
|
50
|
-
- "Here's the plan
|
|
45
|
+
- "Here is my plan."
|
|
46
|
+
- "Here's what I'll do."
|
|
47
|
+
- "Let me break this down."
|
|
48
|
+
- "I've planned the following steps."
|
|
49
|
+
- "Here's how I'll approach this."
|
|
50
|
+
- "Here are the steps I'll take."
|
|
51
|
+
- "This is my plan."
|
|
52
|
+
- "Let me outline the approach."
|
|
53
|
+
- "Here's the plan."
|
|
51
54
|
|
|
52
55
|
**DO NOT:**
|
|
53
56
|
- Use the exact same phrase repeatedly
|
|
@@ -55,10 +58,10 @@ This message should introduce the PLAN, not the execution itself.
|
|
|
55
58
|
- Include unnecessary pleasantries or apologies
|
|
56
59
|
- Use the same sentence structure every time
|
|
57
60
|
- Phrase it as if you're executing (use "plan" language, not "doing" language)
|
|
58
|
-
- Forget the
|
|
61
|
+
- Forget the period at the end
|
|
59
62
|
|
|
60
63
|
Remember: You are presenting a PLAN, not performing the action. The message
|
|
61
|
-
should naturally lead into a list of planned steps. Always end with a
|
|
64
|
+
should naturally lead into a list of planned steps. Always end with a period.
|
|
62
65
|
|
|
63
66
|
## Skills Integration
|
|
64
67
|
|
|
@@ -230,16 +233,30 @@ Examples that should be aborted as offensive:
|
|
|
230
233
|
|
|
231
234
|
**For requests with clear intent:**
|
|
232
235
|
|
|
233
|
-
1. **
|
|
236
|
+
1. **Introspection requests** - Use "introspect" type when request asks about
|
|
237
|
+
capabilities or skills:
|
|
238
|
+
- Verbs: "list skills", "show skills", "what can you do", "list
|
|
239
|
+
capabilities", "show capabilities", "what skills", "describe skills",
|
|
240
|
+
"flex", "show off"
|
|
241
|
+
- **Filtering**: If the request specifies a category, domain, or context
|
|
242
|
+
(e.g., "for deployment", "related to files", "about testing"), add a
|
|
243
|
+
params object with a filter field containing the specified context
|
|
244
|
+
- **IMPORTANT**: Introspection has HIGHER PRIORITY than "answer" for these
|
|
245
|
+
queries. If asking about capabilities/skills, use "introspect", NOT
|
|
246
|
+
"answer"
|
|
247
|
+
|
|
248
|
+
2. **Information requests** - Use "answer" type when request asks for
|
|
234
249
|
information:
|
|
235
250
|
- Verbs: "explain", "answer", "describe", "tell me", "say", "what
|
|
236
251
|
is", "how does"
|
|
237
252
|
- Examples:
|
|
238
|
-
- "explain
|
|
239
|
-
- "tell me about
|
|
253
|
+
- "explain typescript" → type: "answer"
|
|
254
|
+
- "tell me about docker" → type: "answer"
|
|
240
255
|
- "what is the current directory" → type: "answer"
|
|
256
|
+
- **Exception**: Questions about capabilities/skills should use
|
|
257
|
+
"introspect" instead
|
|
241
258
|
|
|
242
|
-
|
|
259
|
+
3. **Skill-based requests** - Use skills when verb matches a defined skill:
|
|
243
260
|
- If "process" skill exists and user says "process" → Use the process skill
|
|
244
261
|
- If "deploy" skill exists and user says "deploy" → Use the deploy skill
|
|
245
262
|
- Extract steps from the matching skill and create tasks for each step
|
|
@@ -359,7 +376,10 @@ When creating task definitions, focus on:
|
|
|
359
376
|
- `execute` - Shell commands, running programs, scripts, processing
|
|
360
377
|
operations
|
|
361
378
|
- `answer` - Answering questions, explaining concepts, providing
|
|
362
|
-
information
|
|
379
|
+
information (EXCEPT for capability/skill queries - use introspect)
|
|
380
|
+
- `introspect` - Listing available capabilities and skills when user
|
|
381
|
+
asks what the concierge can do. Include params { filter: "keyword" }
|
|
382
|
+
if user specifies a filter like "skills for deployment"
|
|
363
383
|
- `report` - Generating summaries, creating reports, displaying
|
|
364
384
|
results
|
|
365
385
|
- `define` - Presenting skill-based options when request matches
|
|
@@ -367,7 +387,8 @@ When creating task definitions, focus on:
|
|
|
367
387
|
(selecting ONE variant, ONE environment, ONE target), NOT sequences of
|
|
368
388
|
steps. Each option represents a single selection that will later be
|
|
369
389
|
expanded into individual sequential steps. NEVER bundle multiple steps
|
|
370
|
-
into a single option like "Process X, run validation, deploy Y"
|
|
390
|
+
into a single option like "Process X, run validation, deploy Y". The
|
|
391
|
+
action text must ALWAYS end with a colon (:) to introduce the options.**
|
|
371
392
|
- `ignore` - Request is too vague and cannot be mapped to skills or
|
|
372
393
|
inferred from context
|
|
373
394
|
|
|
@@ -590,7 +611,7 @@ Split only when multiple distinct queries or operations are needed:
|
|
|
590
611
|
Examples showing proper use of skills and disambiguation:
|
|
591
612
|
|
|
592
613
|
- "process" with process skill requiring {TARGET} parameter (Alpha, Beta, Gamma,
|
|
593
|
-
Delta) → One task: type "define", action "Clarify which target to process",
|
|
614
|
+
Delta) → One task: type "define", action "Clarify which target to process:",
|
|
594
615
|
params { options: ["Process Alpha", "Process Beta", "Process Gamma", "Process
|
|
595
616
|
Delta"] }. NOTE: If variants have descriptions, format as "Process Alpha, the
|
|
596
617
|
legacy version" NOT "Process Alpha (the legacy version)"
|
|
@@ -599,7 +620,7 @@ Examples showing proper use of skills and disambiguation:
|
|
|
599
620
|
target generation script", "Run the Alpha processing pipeline"
|
|
600
621
|
- "process all" with same process skill → Twelve tasks (3 steps × 4 targets)
|
|
601
622
|
- "deploy" with deploy skill (staging, production, canary) → One task: type
|
|
602
|
-
"define", action "Clarify which environment to deploy to", params
|
|
623
|
+
"define", action "Clarify which environment to deploy to:", params
|
|
603
624
|
{ options: ["Deploy to staging environment", "Deploy to production
|
|
604
625
|
environment", "Deploy to canary environment"] }
|
|
605
626
|
- "deploy all" with deploy skill (staging, production) → Two tasks: one for
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { ComponentName } from '../types/types.js';
|
|
3
3
|
import { AnthropicModel, isValidAnthropicApiKey, isValidAnthropicModel, } from './config.js';
|
|
4
|
+
import { getConfirmationMessage } from './messages.js';
|
|
4
5
|
import { StepType } from '../ui/Config.js';
|
|
5
6
|
export function markAsDone(component) {
|
|
6
7
|
return { ...component, state: { ...component.state, done: true } };
|
|
7
8
|
}
|
|
8
|
-
export function getRefiningMessage() {
|
|
9
|
-
const messages = [
|
|
10
|
-
'Let me work out the specifics for you.',
|
|
11
|
-
"I'll figure out the concrete steps.",
|
|
12
|
-
'Let me break this down into tasks.',
|
|
13
|
-
"I'll plan out the details.",
|
|
14
|
-
'Let me arrange the steps.',
|
|
15
|
-
"I'll prepare everything you need.",
|
|
16
|
-
];
|
|
17
|
-
return messages[Math.floor(Math.random() * messages.length)];
|
|
18
|
-
}
|
|
19
9
|
export function createWelcomeDefinition(app) {
|
|
20
10
|
return {
|
|
21
11
|
id: randomUUID(),
|
|
@@ -123,6 +113,18 @@ export function createRefinement(text, onAborted) {
|
|
|
123
113
|
},
|
|
124
114
|
};
|
|
125
115
|
}
|
|
116
|
+
export function createConfirmDefinition(onConfirmed, onCancelled) {
|
|
117
|
+
return {
|
|
118
|
+
id: randomUUID(),
|
|
119
|
+
name: ComponentName.Confirm,
|
|
120
|
+
state: { done: false },
|
|
121
|
+
props: {
|
|
122
|
+
message: getConfirmationMessage(),
|
|
123
|
+
onConfirmed,
|
|
124
|
+
onCancelled,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
126
128
|
export function isStateless(component) {
|
|
127
129
|
return !('state' in component);
|
|
128
130
|
}
|
package/dist/services/config.js
CHANGED
|
@@ -50,6 +50,14 @@ function validateConfig(parsed) {
|
|
|
50
50
|
if (model && typeof model === 'string' && isValidAnthropicModel(model)) {
|
|
51
51
|
validatedConfig.anthropic.model = model;
|
|
52
52
|
}
|
|
53
|
+
// Optional settings section
|
|
54
|
+
if (config.settings && typeof config.settings === 'object') {
|
|
55
|
+
const settings = config.settings;
|
|
56
|
+
validatedConfig.settings = {};
|
|
57
|
+
if ('debug' in settings && typeof settings.debug === 'boolean') {
|
|
58
|
+
validatedConfig.settings.debug = settings.debug;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
53
61
|
return validatedConfig;
|
|
54
62
|
}
|
|
55
63
|
export function loadConfig() {
|
|
@@ -116,6 +124,18 @@ export function saveConfig(section, config) {
|
|
|
116
124
|
export function saveAnthropicConfig(config) {
|
|
117
125
|
saveConfig('anthropic', config);
|
|
118
126
|
}
|
|
127
|
+
export function saveDebugSetting(debug) {
|
|
128
|
+
saveConfig('settings', { debug });
|
|
129
|
+
}
|
|
130
|
+
export function loadDebugSetting() {
|
|
131
|
+
try {
|
|
132
|
+
const config = loadConfig();
|
|
133
|
+
return config.settings?.debug ?? false;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
119
139
|
/**
|
|
120
140
|
* Returns a message requesting initial setup.
|
|
121
141
|
* Provides natural language variations that sound like a professional concierge
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a natural language confirmation message for plan execution.
|
|
3
|
+
* Randomly selects from variations to sound less robotic.
|
|
4
|
+
*/
|
|
5
|
+
export function getConfirmationMessage() {
|
|
6
|
+
const messages = [
|
|
7
|
+
'Should I execute this plan?',
|
|
8
|
+
'Do you want me to proceed with these tasks?',
|
|
9
|
+
'Ready to execute?',
|
|
10
|
+
'Shall I execute this plan?',
|
|
11
|
+
'Would you like me to run these tasks?',
|
|
12
|
+
'Execute this plan?',
|
|
13
|
+
];
|
|
14
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns a refining message shown during plan refinement.
|
|
18
|
+
* Randomly selects from variations to sound natural.
|
|
19
|
+
*/
|
|
20
|
+
export function getRefiningMessage() {
|
|
21
|
+
const messages = [
|
|
22
|
+
'Let me work out the specifics for you.',
|
|
23
|
+
"I'll figure out the concrete steps.",
|
|
24
|
+
'Let me break this down into tasks.',
|
|
25
|
+
"I'll plan out the details.",
|
|
26
|
+
'Let me arrange the steps.',
|
|
27
|
+
"I'll prepare everything you need.",
|
|
28
|
+
];
|
|
29
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns a cancellation message for the given operation.
|
|
33
|
+
* Randomly selects from variations to sound natural.
|
|
34
|
+
*/
|
|
35
|
+
export function getCancellationMessage(operation) {
|
|
36
|
+
const templates = [
|
|
37
|
+
`I've cancelled the ${operation.toLowerCase()}.`,
|
|
38
|
+
`I've aborted the ${operation.toLowerCase()}.`,
|
|
39
|
+
`The ${operation.toLowerCase()} was cancelled.`,
|
|
40
|
+
`The ${operation.toLowerCase()} has been aborted.`,
|
|
41
|
+
];
|
|
42
|
+
return templates[Math.floor(Math.random() * templates.length)];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Feedback messages for various operations
|
|
46
|
+
*/
|
|
47
|
+
export const FeedbackMessages = {
|
|
48
|
+
ConfigurationComplete: 'Configuration complete.',
|
|
49
|
+
UnexpectedError: 'Unexpected error occurred:',
|
|
50
|
+
};
|
|
@@ -33,8 +33,13 @@ class ToolRegistry {
|
|
|
33
33
|
// Create singleton instance
|
|
34
34
|
export const toolRegistry = new ToolRegistry();
|
|
35
35
|
// Register built-in tools
|
|
36
|
+
import { introspectTool } from '../tools/introspect.tool.js';
|
|
36
37
|
import { planTool } from '../tools/plan.tool.js';
|
|
37
38
|
toolRegistry.register('plan', {
|
|
38
39
|
schema: planTool,
|
|
39
40
|
instructionsPath: 'config/PLAN.md',
|
|
40
41
|
});
|
|
42
|
+
toolRegistry.register('introspect', {
|
|
43
|
+
schema: introspectTool,
|
|
44
|
+
instructionsPath: 'config/INTROSPECT.md',
|
|
45
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const introspectTool = {
|
|
2
|
+
name: 'introspect',
|
|
3
|
+
description: 'Execute a task with type "introspect" to list available capabilities and skills. Called after PLAN 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.',
|
|
4
|
+
input_schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
message: {
|
|
8
|
+
type: 'string',
|
|
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
|
+
},
|
|
11
|
+
tasks: {
|
|
12
|
+
type: 'array',
|
|
13
|
+
description: 'Array of capabilities, each with type "introspect". Include built-in capabilities (PLAN, INTROSPECT, ANSWER, EXECUTE, REPORT, CONFIG) and user-defined skills from the Available Skills section.',
|
|
14
|
+
items: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
action: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Capability name and description. Format: "NAME: Brief description". Maximum 64 characters. Examples: "PLAN: Break down requests into steps", "Deploy App: Build and deploy application".',
|
|
20
|
+
},
|
|
21
|
+
type: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Always "introspect" for capability listings.',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ['action', 'type'],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ['message', 'tasks'],
|
|
31
|
+
},
|
|
32
|
+
};
|
package/dist/tools/plan.tool.js
CHANGED
|
@@ -20,7 +20,7 @@ export const planTool = {
|
|
|
20
20
|
},
|
|
21
21
|
type: {
|
|
22
22
|
type: 'string',
|
|
23
|
-
description: 'Type of task: "config" (settings), "plan" (planning), "execute" (shell/programs/finding files), "answer" (questions), "report" (summaries), "define" (skill-based disambiguation), "ignore" (too vague)',
|
|
23
|
+
description: 'Type of task: "config" (settings), "plan" (planning), "execute" (shell/programs/finding files), "answer" (questions, NOT for capability queries), "introspect" (list capabilities/skills), "report" (summaries), "define" (skill-based disambiguation), "ignore" (too vague)',
|
|
24
24
|
},
|
|
25
25
|
params: {
|
|
26
26
|
type: 'object',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { FeedbackType, TaskType } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Semantic color palette - colors organized by their purpose/meaning.
|
|
4
|
+
* Prefer adding semantic names here rather than to DescriptiveColors.
|
|
5
|
+
*/
|
|
6
|
+
export const Colors = {
|
|
7
|
+
Action: {
|
|
8
|
+
Execute: '#5aaa8a', // green
|
|
9
|
+
Discard: '#a85c3f', // dark orange
|
|
10
|
+
Select: '#5c8cbc', // steel blue
|
|
11
|
+
},
|
|
12
|
+
Status: {
|
|
13
|
+
Success: '#22aa22', // green
|
|
14
|
+
Error: '#cc5c5c', // red
|
|
15
|
+
Warning: '#cc9c5c', // orange
|
|
16
|
+
Info: '#5c9ccc', // cyan
|
|
17
|
+
},
|
|
18
|
+
Label: {
|
|
19
|
+
Default: '#ffffff', // white
|
|
20
|
+
Inactive: '#888888', // gray
|
|
21
|
+
Discarded: '#666666', // dark gray
|
|
22
|
+
Skipped: '#cccc5c', // yellow
|
|
23
|
+
},
|
|
24
|
+
Type: {
|
|
25
|
+
Config: '#5c9ccc', // cyan
|
|
26
|
+
Plan: '#5ccccc', // magenta
|
|
27
|
+
Execute: '#5aaa8a', // green
|
|
28
|
+
Answer: '#9c5ccc', // purple
|
|
29
|
+
Introspect: '#9c5ccc', // purple
|
|
30
|
+
Report: '#cc9c5c', // orange
|
|
31
|
+
Define: '#cc9c5c', // amber
|
|
32
|
+
Ignore: '#cc7a5c', // dark orange
|
|
33
|
+
Select: '#5c8cbc', // steel blue
|
|
34
|
+
Discard: '#a85c3f', // dark orange
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Task-specific color mappings
|
|
39
|
+
*/
|
|
40
|
+
export const TaskColors = {
|
|
41
|
+
[TaskType.Config]: {
|
|
42
|
+
description: Colors.Label.Default,
|
|
43
|
+
type: Colors.Type.Config,
|
|
44
|
+
},
|
|
45
|
+
[TaskType.Plan]: {
|
|
46
|
+
description: Colors.Label.Default,
|
|
47
|
+
type: Colors.Type.Plan,
|
|
48
|
+
},
|
|
49
|
+
[TaskType.Execute]: {
|
|
50
|
+
description: Colors.Label.Default,
|
|
51
|
+
type: Colors.Type.Execute,
|
|
52
|
+
},
|
|
53
|
+
[TaskType.Answer]: {
|
|
54
|
+
description: Colors.Label.Default,
|
|
55
|
+
type: Colors.Type.Answer,
|
|
56
|
+
},
|
|
57
|
+
[TaskType.Introspect]: {
|
|
58
|
+
description: Colors.Label.Default,
|
|
59
|
+
type: Colors.Type.Introspect,
|
|
60
|
+
},
|
|
61
|
+
[TaskType.Report]: {
|
|
62
|
+
description: Colors.Label.Default,
|
|
63
|
+
type: Colors.Type.Report,
|
|
64
|
+
},
|
|
65
|
+
[TaskType.Define]: {
|
|
66
|
+
description: Colors.Label.Default,
|
|
67
|
+
type: Colors.Type.Define,
|
|
68
|
+
},
|
|
69
|
+
[TaskType.Ignore]: {
|
|
70
|
+
description: Colors.Label.Skipped,
|
|
71
|
+
type: Colors.Type.Ignore,
|
|
72
|
+
},
|
|
73
|
+
[TaskType.Select]: {
|
|
74
|
+
description: Colors.Label.Inactive,
|
|
75
|
+
type: Colors.Type.Select,
|
|
76
|
+
},
|
|
77
|
+
[TaskType.Discard]: {
|
|
78
|
+
description: Colors.Label.Discarded,
|
|
79
|
+
type: Colors.Type.Discard,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Feedback-specific color mappings
|
|
84
|
+
*/
|
|
85
|
+
export const FeedbackColors = {
|
|
86
|
+
[FeedbackType.Info]: Colors.Status.Info,
|
|
87
|
+
[FeedbackType.Succeeded]: Colors.Status.Success,
|
|
88
|
+
[FeedbackType.Aborted]: Colors.Status.Warning,
|
|
89
|
+
[FeedbackType.Failed]: Colors.Status.Error,
|
|
90
|
+
};
|
package/dist/types/types.js
CHANGED
|
@@ -7,6 +7,7 @@ export var ComponentName;
|
|
|
7
7
|
ComponentName["Plan"] = "plan";
|
|
8
8
|
ComponentName["Refinement"] = "refinement";
|
|
9
9
|
ComponentName["Feedback"] = "feedback";
|
|
10
|
+
ComponentName["Confirm"] = "confirm";
|
|
10
11
|
})(ComponentName || (ComponentName = {}));
|
|
11
12
|
export var TaskType;
|
|
12
13
|
(function (TaskType) {
|
|
@@ -14,6 +15,7 @@ export var TaskType;
|
|
|
14
15
|
TaskType["Plan"] = "plan";
|
|
15
16
|
TaskType["Execute"] = "execute";
|
|
16
17
|
TaskType["Answer"] = "answer";
|
|
18
|
+
TaskType["Introspect"] = "introspect";
|
|
17
19
|
TaskType["Report"] = "report";
|
|
18
20
|
TaskType["Define"] = "define";
|
|
19
21
|
TaskType["Ignore"] = "ignore";
|
package/dist/ui/Column.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
2
3
|
import { Box } from 'ink';
|
|
3
4
|
import { Component } from './Component.js';
|
|
4
|
-
export const Column = ({ items }) => {
|
|
5
|
-
return (_jsx(Box, { marginTop: 1, marginBottom: 1, marginLeft: 1, flexDirection: "column", gap: 1, children: items.map((item) => (_jsx(Box, { children: _jsx(Component, { def: item }) }, item.id))) }));
|
|
6
|
-
};
|
|
5
|
+
export const Column = React.memo(({ items, debug }) => {
|
|
6
|
+
return (_jsx(Box, { marginTop: 1, marginBottom: 1, marginLeft: 1, flexDirection: "column", gap: 1, children: items.map((item) => (_jsx(Box, { children: _jsx(Component, { def: item, debug: debug }) }, item.id))) }));
|
|
7
|
+
});
|
package/dist/ui/Component.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
2
3
|
import { ComponentName } from '../types/types.js';
|
|
3
4
|
import { Command } from './Command.js';
|
|
5
|
+
import { Confirm } from './Confirm.js';
|
|
4
6
|
import { Config } from './Config.js';
|
|
5
7
|
import { Feedback } from './Feedback.js';
|
|
6
8
|
import { Message } from './Message.js';
|
|
7
9
|
import { Plan } from './Plan.js';
|
|
8
10
|
import { Refinement } from './Refinement.js';
|
|
9
11
|
import { Welcome } from './Welcome.js';
|
|
10
|
-
export function Component({ def }) {
|
|
12
|
+
export const Component = React.memo(function Component({ def, debug, }) {
|
|
11
13
|
switch (def.name) {
|
|
12
14
|
case ComponentName.Welcome:
|
|
13
15
|
return _jsx(Welcome, { ...def.props });
|
|
@@ -22,7 +24,7 @@ export function Component({ def }) {
|
|
|
22
24
|
return _jsx(Command, { ...props, state: state });
|
|
23
25
|
}
|
|
24
26
|
case ComponentName.Plan:
|
|
25
|
-
return _jsx(Plan, { ...def.props });
|
|
27
|
+
return _jsx(Plan, { ...def.props, debug: debug });
|
|
26
28
|
case ComponentName.Feedback:
|
|
27
29
|
return _jsx(Feedback, { ...def.props });
|
|
28
30
|
case ComponentName.Message:
|
|
@@ -32,5 +34,10 @@ export function Component({ def }) {
|
|
|
32
34
|
const state = def.state;
|
|
33
35
|
return _jsx(Refinement, { ...props, state: state });
|
|
34
36
|
}
|
|
37
|
+
case ComponentName.Confirm: {
|
|
38
|
+
const props = def.props;
|
|
39
|
+
const state = def.state;
|
|
40
|
+
return _jsx(Confirm, { ...props, state: state });
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
|
-
}
|
|
43
|
+
});
|
package/dist/ui/Config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Box, Text, useFocus, useInput } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
+
import { Colors } from '../types/colors.js';
|
|
5
6
|
export var StepType;
|
|
6
7
|
(function (StepType) {
|
|
7
8
|
StepType["Text"] = "text";
|
|
@@ -43,7 +44,7 @@ function TextStep({ value, placeholder, validate, onChange, onSubmit, }) {
|
|
|
43
44
|
}, { isActive: validationFailed });
|
|
44
45
|
// When validation fails, show colored text
|
|
45
46
|
if (validationFailed) {
|
|
46
|
-
return (_jsxs(Text, { color:
|
|
47
|
+
return (_jsxs(Text, { color: Colors.Status.Error, children: [inputValue || placeholder, isFocused && _jsx(Text, { inverse: true, children: " " })] }));
|
|
47
48
|
}
|
|
48
49
|
return (_jsx(TextInput, { value: inputValue, onChange: handleChange, onSubmit: handleSubmit, placeholder: placeholder }));
|
|
49
50
|
}
|
|
@@ -216,6 +217,6 @@ export function Config({ steps, state, onFinished, onAborted }) {
|
|
|
216
217
|
if (!shouldShow) {
|
|
217
218
|
return null;
|
|
218
219
|
}
|
|
219
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color:
|
|
220
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
|
|
220
221
|
}) }));
|
|
221
222
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { Colors } from '../types/colors.js';
|
|
5
|
+
export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
6
|
+
const done = state?.done ?? false;
|
|
7
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0); // 0 = Yes, 1 = No
|
|
8
|
+
useInput((input, key) => {
|
|
9
|
+
if (done)
|
|
10
|
+
return;
|
|
11
|
+
if (key.escape) {
|
|
12
|
+
// Escape: highlight "No" and cancel
|
|
13
|
+
setSelectedIndex(1);
|
|
14
|
+
onCancelled?.();
|
|
15
|
+
}
|
|
16
|
+
else if (key.tab) {
|
|
17
|
+
// Toggle between Yes (0) and No (1)
|
|
18
|
+
setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
|
|
19
|
+
}
|
|
20
|
+
else if (key.return) {
|
|
21
|
+
// Confirm selection
|
|
22
|
+
if (selectedIndex === 0) {
|
|
23
|
+
onConfirmed?.();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
onCancelled?.();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, { isActive: !done });
|
|
30
|
+
const options = [
|
|
31
|
+
{ label: 'Yes', value: 'yes', color: Colors.Action.Execute },
|
|
32
|
+
{ label: 'No', value: 'no', color: Colors.Action.Discard },
|
|
33
|
+
];
|
|
34
|
+
if (done) {
|
|
35
|
+
// When done, show both the message and user's choice in timeline
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["> ", options[selectedIndex].label] }) })] }));
|
|
37
|
+
}
|
|
38
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
39
|
+
const isSelected = index === selectedIndex;
|
|
40
|
+
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, bold: isSelected, children: option.label }) }, option.value));
|
|
41
|
+
}) })] })] }));
|
|
42
|
+
}
|
package/dist/ui/Feedback.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { FeedbackColors } from '../types/colors.js';
|
|
3
4
|
import { FeedbackType } from '../types/types.js';
|
|
4
5
|
function getSymbol(type) {
|
|
5
6
|
return {
|
|
@@ -9,25 +10,8 @@ function getSymbol(type) {
|
|
|
9
10
|
[FeedbackType.Failed]: '✗',
|
|
10
11
|
}[type];
|
|
11
12
|
}
|
|
12
|
-
function getSymbolColor(type) {
|
|
13
|
-
return {
|
|
14
|
-
[FeedbackType.Info]: '#5c9ccc', // cyan
|
|
15
|
-
[FeedbackType.Succeeded]: '#00aa00', // green
|
|
16
|
-
[FeedbackType.Aborted]: '#cc9c5c', // orange
|
|
17
|
-
[FeedbackType.Failed]: '#cc5c5c', // red
|
|
18
|
-
}[type];
|
|
19
|
-
}
|
|
20
|
-
function getMessageColor(type) {
|
|
21
|
-
return {
|
|
22
|
-
[FeedbackType.Info]: '#aaaaaa', // light grey
|
|
23
|
-
[FeedbackType.Succeeded]: '#5ccc5c', // green
|
|
24
|
-
[FeedbackType.Aborted]: '#cc9c5c', // orange
|
|
25
|
-
[FeedbackType.Failed]: '#cc5c5c', // red
|
|
26
|
-
}[type];
|
|
27
|
-
}
|
|
28
13
|
export function Feedback({ type, message }) {
|
|
29
|
-
const
|
|
30
|
-
const messageColor = getMessageColor(type);
|
|
14
|
+
const color = FeedbackColors[type];
|
|
31
15
|
const symbol = getSymbol(type);
|
|
32
|
-
return (
|
|
16
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: color, children: [symbol, " ", message] }) }));
|
|
33
17
|
}
|
package/dist/ui/Label.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { Separator } from './Separator.js';
|
|
4
|
-
export function Label({ description, descriptionColor, type, typeColor, }) {
|
|
5
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: descriptionColor, children: description }), _jsx(Separator, {}), _jsx(Text, { color: typeColor, children: type })] }));
|
|
4
|
+
export function Label({ description, descriptionColor, type, typeColor, showType = false, }) {
|
|
5
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: descriptionColor, children: description }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: type })] }))] }));
|
|
6
6
|
}
|
package/dist/ui/List.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { Label } from './Label.js';
|
|
4
|
-
export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, }) => {
|
|
4
|
+
export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, showType = false, }) => {
|
|
5
5
|
const marginLeft = level > 0 ? 4 : 0;
|
|
6
6
|
return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => {
|
|
7
7
|
// At level 0, track which parent is active for child highlighting
|
|
@@ -16,6 +16,11 @@ export const List = ({ items, level = 0, highlightedIndex = null, highlightedPar
|
|
|
16
16
|
const typeColor = isHighlighted && item.type.highlightedColor
|
|
17
17
|
? item.type.highlightedColor
|
|
18
18
|
: item.type.color;
|
|
19
|
-
|
|
19
|
+
// Use highlighted type color for arrow markers when highlighted
|
|
20
|
+
const markerColor = item.markerColor ||
|
|
21
|
+
(isHighlighted && item.type.highlightedColor
|
|
22
|
+
? item.type.highlightedColor
|
|
23
|
+
: 'whiteBright');
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(Label, { description: item.description.text, descriptionColor: descriptionColor, type: item.type.text, typeColor: typeColor, showType: showType })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }))] }, index));
|
|
20
25
|
}) }));
|
|
21
26
|
};
|
package/dist/ui/Main.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useInput } from 'ink';
|
|
3
4
|
import { ComponentName, FeedbackType, TaskType, } from '../types/types.js';
|
|
4
5
|
import { createAnthropicService, } from '../services/anthropic.js';
|
|
5
|
-
import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, saveAnthropicConfig, } from '../services/config.js';
|
|
6
|
-
import {
|
|
6
|
+
import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, loadDebugSetting, saveAnthropicConfig, saveDebugSetting, } from '../services/config.js';
|
|
7
|
+
import { FeedbackMessages, getCancellationMessage, getRefiningMessage, } from '../services/messages.js';
|
|
8
|
+
import { createCommandDefinition, createConfirmDefinition, createConfigDefinition, createFeedback, createMessage, createRefinement, createPlanDefinition, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
|
|
7
9
|
import { exitApp } from '../services/process.js';
|
|
8
10
|
import { Column } from './Column.js';
|
|
9
11
|
export const Main = ({ app, command }) => {
|
|
@@ -17,6 +19,23 @@ export const Main = ({ app, command }) => {
|
|
|
17
19
|
});
|
|
18
20
|
const [timeline, setTimeline] = React.useState([]);
|
|
19
21
|
const [queue, setQueue] = React.useState([]);
|
|
22
|
+
const [isDebug, setIsDebug] = React.useState(() => loadDebugSetting());
|
|
23
|
+
// Use ref to track latest timeline for callbacks
|
|
24
|
+
const timelineRef = React.useRef(timeline);
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
timelineRef.current = timeline;
|
|
27
|
+
}, [timeline]);
|
|
28
|
+
// Top-level Shift+Tab handler for debug mode toggle
|
|
29
|
+
// Child components must ignore Shift+Tab to prevent conflicts
|
|
30
|
+
useInput((input, key) => {
|
|
31
|
+
if (key.shift && key.tab) {
|
|
32
|
+
setIsDebug((prev) => {
|
|
33
|
+
const newValue = !prev;
|
|
34
|
+
saveDebugSetting(newValue);
|
|
35
|
+
return newValue;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}, { isActive: true });
|
|
20
39
|
const addToTimeline = React.useCallback((...items) => {
|
|
21
40
|
setTimeline((timeline) => [...timeline, ...items]);
|
|
22
41
|
}, []);
|
|
@@ -39,7 +58,7 @@ export const Main = ({ app, command }) => {
|
|
|
39
58
|
return currentQueue;
|
|
40
59
|
const [first] = currentQueue;
|
|
41
60
|
if (first.name === ComponentName.Command) {
|
|
42
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed,
|
|
61
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, error));
|
|
43
62
|
}
|
|
44
63
|
exitApp(1);
|
|
45
64
|
return [];
|
|
@@ -51,7 +70,7 @@ export const Main = ({ app, command }) => {
|
|
|
51
70
|
return currentQueue;
|
|
52
71
|
const [first] = currentQueue;
|
|
53
72
|
if (!isStateless(first)) {
|
|
54
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted,
|
|
73
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, getCancellationMessage(operationName)));
|
|
55
74
|
}
|
|
56
75
|
exitApp(0);
|
|
57
76
|
return [];
|
|
@@ -63,12 +82,59 @@ export const Main = ({ app, command }) => {
|
|
|
63
82
|
const handlePlanAborted = React.useCallback(() => {
|
|
64
83
|
handleAborted('Task selection');
|
|
65
84
|
}, [handleAborted]);
|
|
85
|
+
const createPlanAbortHandler = React.useCallback((tasks) => {
|
|
86
|
+
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
87
|
+
if (allIntrospect) {
|
|
88
|
+
return () => handleAborted('Introspection');
|
|
89
|
+
}
|
|
90
|
+
return handlePlanAborted;
|
|
91
|
+
}, [handleAborted, handlePlanAborted]);
|
|
66
92
|
const handleCommandAborted = React.useCallback(() => {
|
|
67
93
|
handleAborted('Request');
|
|
68
94
|
}, [handleAborted]);
|
|
69
95
|
const handleRefinementAborted = React.useCallback(() => {
|
|
70
96
|
handleAborted('Plan refinement');
|
|
71
97
|
}, [handleAborted]);
|
|
98
|
+
const handleExecutionConfirmed = React.useCallback(() => {
|
|
99
|
+
setQueue((currentQueue) => {
|
|
100
|
+
if (currentQueue.length === 0)
|
|
101
|
+
return currentQueue;
|
|
102
|
+
const [first] = currentQueue;
|
|
103
|
+
if (first.name === ComponentName.Confirm) {
|
|
104
|
+
addToTimeline(markAsDone(first));
|
|
105
|
+
}
|
|
106
|
+
exitApp(0);
|
|
107
|
+
return [];
|
|
108
|
+
});
|
|
109
|
+
}, [addToTimeline]);
|
|
110
|
+
const handleExecutionCancelled = React.useCallback(() => {
|
|
111
|
+
setQueue((currentQueue) => {
|
|
112
|
+
if (currentQueue.length === 0)
|
|
113
|
+
return currentQueue;
|
|
114
|
+
const [first] = currentQueue;
|
|
115
|
+
if (first.name === ComponentName.Confirm) {
|
|
116
|
+
// Find the most recent Plan in timeline to check task types
|
|
117
|
+
const currentTimeline = timelineRef.current;
|
|
118
|
+
const lastPlanIndex = [...currentTimeline]
|
|
119
|
+
.reverse()
|
|
120
|
+
.findIndex((item) => item.name === ComponentName.Plan);
|
|
121
|
+
const lastPlan = lastPlanIndex >= 0
|
|
122
|
+
? currentTimeline[currentTimeline.length - 1 - lastPlanIndex]
|
|
123
|
+
: null;
|
|
124
|
+
const allIntrospect = lastPlan &&
|
|
125
|
+
lastPlan.name === ComponentName.Plan &&
|
|
126
|
+
'props' in lastPlan &&
|
|
127
|
+
lastPlan.props &&
|
|
128
|
+
'tasks' in lastPlan.props &&
|
|
129
|
+
Array.isArray(lastPlan.props.tasks) &&
|
|
130
|
+
lastPlan.props.tasks.every((task) => task.type === TaskType.Introspect);
|
|
131
|
+
const operation = allIntrospect ? 'introspection' : 'execution';
|
|
132
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, getCancellationMessage(operation)));
|
|
133
|
+
}
|
|
134
|
+
exitApp(0);
|
|
135
|
+
return [];
|
|
136
|
+
});
|
|
137
|
+
}, [addToTimeline]);
|
|
72
138
|
const handlePlanSelectionConfirmed = React.useCallback(async (selectedTasks) => {
|
|
73
139
|
// Mark current plan as done and add refinement to queue
|
|
74
140
|
let refinementDef = null;
|
|
@@ -101,10 +167,11 @@ export const Main = ({ app, command }) => {
|
|
|
101
167
|
}
|
|
102
168
|
return [];
|
|
103
169
|
});
|
|
104
|
-
// Show final execution plan
|
|
105
|
-
const planDefinition = createPlanDefinition(result.message, result.tasks,
|
|
170
|
+
// Show final execution plan with confirmation
|
|
171
|
+
const planDefinition = createPlanDefinition(result.message, result.tasks, createPlanAbortHandler(result.tasks), undefined);
|
|
172
|
+
const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
|
|
106
173
|
addToTimeline(planDefinition);
|
|
107
|
-
|
|
174
|
+
setQueue([confirmDefinition]);
|
|
108
175
|
}
|
|
109
176
|
catch (error) {
|
|
110
177
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
@@ -116,10 +183,17 @@ export const Main = ({ app, command }) => {
|
|
|
116
183
|
}
|
|
117
184
|
return [];
|
|
118
185
|
});
|
|
119
|
-
addToTimeline(createFeedback(FeedbackType.Failed,
|
|
186
|
+
addToTimeline(createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, errorMessage));
|
|
120
187
|
exitApp(1);
|
|
121
188
|
}
|
|
122
|
-
}, [
|
|
189
|
+
}, [
|
|
190
|
+
addToTimeline,
|
|
191
|
+
service,
|
|
192
|
+
handleRefinementAborted,
|
|
193
|
+
createPlanAbortHandler,
|
|
194
|
+
handleExecutionConfirmed,
|
|
195
|
+
handleExecutionCancelled,
|
|
196
|
+
]);
|
|
123
197
|
const handleCommandComplete = React.useCallback((message, tasks) => {
|
|
124
198
|
setQueue((currentQueue) => {
|
|
125
199
|
if (currentQueue.length === 0)
|
|
@@ -128,23 +202,29 @@ export const Main = ({ app, command }) => {
|
|
|
128
202
|
// Check if tasks contain a Define task that requires user interaction
|
|
129
203
|
const hasDefineTask = tasks.some((task) => task.type === TaskType.Define);
|
|
130
204
|
if (first.name === ComponentName.Command) {
|
|
131
|
-
const planDefinition = createPlanDefinition(message, tasks,
|
|
205
|
+
const planDefinition = createPlanDefinition(message, tasks, createPlanAbortHandler(tasks), hasDefineTask ? handlePlanSelectionConfirmed : undefined);
|
|
132
206
|
if (hasDefineTask) {
|
|
133
207
|
// Don't exit - keep the plan in the queue for interaction
|
|
134
208
|
addToTimeline(markAsDone(first));
|
|
135
209
|
return [planDefinition];
|
|
136
210
|
}
|
|
137
211
|
else {
|
|
138
|
-
// No define task -
|
|
212
|
+
// No define task - show plan and confirmation
|
|
213
|
+
const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
|
|
139
214
|
addToTimeline(markAsDone(first), planDefinition);
|
|
140
|
-
|
|
141
|
-
return [];
|
|
215
|
+
return [confirmDefinition];
|
|
142
216
|
}
|
|
143
217
|
}
|
|
144
218
|
exitApp(0);
|
|
145
219
|
return [];
|
|
146
220
|
});
|
|
147
|
-
}, [
|
|
221
|
+
}, [
|
|
222
|
+
addToTimeline,
|
|
223
|
+
createPlanAbortHandler,
|
|
224
|
+
handlePlanSelectionConfirmed,
|
|
225
|
+
handleExecutionConfirmed,
|
|
226
|
+
handleExecutionCancelled,
|
|
227
|
+
]);
|
|
148
228
|
const handleConfigFinished = React.useCallback((config) => {
|
|
149
229
|
const anthropicConfig = config;
|
|
150
230
|
saveAnthropicConfig(anthropicConfig);
|
|
@@ -156,7 +236,7 @@ export const Main = ({ app, command }) => {
|
|
|
156
236
|
return currentQueue;
|
|
157
237
|
const [first, ...rest] = currentQueue;
|
|
158
238
|
if (first.name === ComponentName.Config) {
|
|
159
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Succeeded,
|
|
239
|
+
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Succeeded, FeedbackMessages.ConfigurationComplete));
|
|
160
240
|
}
|
|
161
241
|
// Add command to queue if we have one
|
|
162
242
|
if (command) {
|
|
@@ -203,7 +283,13 @@ export const Main = ({ app, command }) => {
|
|
|
203
283
|
React.useEffect(() => {
|
|
204
284
|
processNextInQueue();
|
|
205
285
|
}, [queue, processNextInQueue]);
|
|
286
|
+
// Exit when queue is empty and timeline has content (all stateless components done)
|
|
287
|
+
React.useEffect(() => {
|
|
288
|
+
if (queue.length === 0 && timeline.length > 0) {
|
|
289
|
+
exitApp(0);
|
|
290
|
+
}
|
|
291
|
+
}, [queue, timeline]);
|
|
206
292
|
const current = queue.length > 0 ? queue[0] : null;
|
|
207
|
-
const items = [...timeline, ...(current ? [current] : [])];
|
|
208
|
-
return _jsx(Column, { items: items });
|
|
293
|
+
const items = React.useMemo(() => [...timeline, ...(current ? [current] : [])], [timeline, current]);
|
|
294
|
+
return _jsx(Column, { items: items, debug: isDebug });
|
|
209
295
|
};
|
package/dist/ui/Plan.js
CHANGED
|
@@ -1,59 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, useInput } from 'ink';
|
|
4
|
+
import { TaskColors } from '../types/colors.js';
|
|
4
5
|
import { TaskType } from '../types/types.js';
|
|
5
6
|
import { Label } from './Label.js';
|
|
6
7
|
import { List } from './List.js';
|
|
7
|
-
const ColorPalette = {
|
|
8
|
-
[TaskType.Config]: {
|
|
9
|
-
description: '#ffffff', // white
|
|
10
|
-
type: '#5c9ccc', // cyan
|
|
11
|
-
},
|
|
12
|
-
[TaskType.Plan]: {
|
|
13
|
-
description: '#ffffff', // white
|
|
14
|
-
type: '#5ccccc', // magenta
|
|
15
|
-
},
|
|
16
|
-
[TaskType.Execute]: {
|
|
17
|
-
description: '#ffffff', // white
|
|
18
|
-
type: '#4a9a7a', // green
|
|
19
|
-
},
|
|
20
|
-
[TaskType.Answer]: {
|
|
21
|
-
description: '#ffffff', // white
|
|
22
|
-
type: '#9c5ccc', // purple
|
|
23
|
-
},
|
|
24
|
-
[TaskType.Report]: {
|
|
25
|
-
description: '#ffffff', // white
|
|
26
|
-
type: '#cc9c5c', // orange
|
|
27
|
-
},
|
|
28
|
-
[TaskType.Define]: {
|
|
29
|
-
description: '#ffffff', // white
|
|
30
|
-
type: '#cc9c5c', // amber
|
|
31
|
-
},
|
|
32
|
-
[TaskType.Ignore]: {
|
|
33
|
-
description: '#cccc5c', // yellow
|
|
34
|
-
type: '#cc7a5c', // orange
|
|
35
|
-
},
|
|
36
|
-
[TaskType.Select]: {
|
|
37
|
-
description: '#888888', // grey
|
|
38
|
-
type: '#5c8cbc', // steel blue
|
|
39
|
-
},
|
|
40
|
-
[TaskType.Discard]: {
|
|
41
|
-
description: '#666666', // dark grey
|
|
42
|
-
type: '#a85c3f', // dark orange
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
8
|
function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutSelection = false) {
|
|
46
9
|
const item = {
|
|
47
10
|
description: {
|
|
48
11
|
text: task.action,
|
|
49
|
-
color:
|
|
12
|
+
color: TaskColors[task.type].description,
|
|
50
13
|
},
|
|
51
|
-
type: { text: task.type, color:
|
|
14
|
+
type: { text: task.type, color: TaskColors[task.type].type },
|
|
52
15
|
children: [],
|
|
53
16
|
};
|
|
54
17
|
// Mark define tasks with right arrow when no selection has been made
|
|
55
18
|
if (isDefineTaskWithoutSelection) {
|
|
56
19
|
item.marker = ' → ';
|
|
20
|
+
item.markerColor = TaskColors[TaskType.Plan].type;
|
|
57
21
|
}
|
|
58
22
|
// Add children for Define tasks with options
|
|
59
23
|
if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
|
|
@@ -65,24 +29,24 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
65
29
|
childType =
|
|
66
30
|
index === highlightedChildIndex ? TaskType.Execute : TaskType.Discard;
|
|
67
31
|
}
|
|
68
|
-
const colors =
|
|
32
|
+
const colors = TaskColors[childType];
|
|
69
33
|
return {
|
|
70
34
|
description: {
|
|
71
35
|
text: String(option),
|
|
72
36
|
color: colors.description,
|
|
73
|
-
highlightedColor:
|
|
37
|
+
highlightedColor: TaskColors[TaskType.Plan].description,
|
|
74
38
|
},
|
|
75
39
|
type: {
|
|
76
40
|
text: childType,
|
|
77
41
|
color: colors.type,
|
|
78
|
-
highlightedColor:
|
|
42
|
+
highlightedColor: TaskColors[TaskType.Plan].type,
|
|
79
43
|
},
|
|
80
44
|
};
|
|
81
45
|
});
|
|
82
46
|
}
|
|
83
47
|
return item;
|
|
84
48
|
}
|
|
85
|
-
export function Plan({ message, tasks, state, onSelectionConfirmed, onAborted, }) {
|
|
49
|
+
export function Plan({ message, tasks, state, debug = false, onSelectionConfirmed, onAborted, }) {
|
|
86
50
|
const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
|
|
87
51
|
const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(state?.currentDefineGroupIndex ?? 0);
|
|
88
52
|
const [completedSelections, setCompletedSelections] = useState(state?.completedSelections ?? []);
|
|
@@ -208,5 +172,5 @@ export function Plan({ message, tasks, state, onSelectionConfirmed, onAborted, }
|
|
|
208
172
|
!isDone;
|
|
209
173
|
return taskToListItem(task, childIndex, isDefineWithoutSelection);
|
|
210
174
|
});
|
|
211
|
-
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, children: _jsx(Label, { description: message, descriptionColor:
|
|
175
|
+
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, children: _jsx(Label, { description: message, descriptionColor: TaskColors[TaskType.Plan].description, type: TaskType.Plan, typeColor: TaskColors[TaskType.Plan].type, showType: debug }) })), _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug })] }));
|
|
212
176
|
}
|
package/dist/ui/Separator.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Text } from 'ink';
|
|
3
|
-
|
|
3
|
+
import { Colors } from '../types/colors.js';
|
|
4
|
+
export const Separator = ({ color = Colors.Label.Discarded, spaces = 1, }) => {
|
|
4
5
|
const spacing = ' '.repeat(spaces);
|
|
5
6
|
return (_jsxs(Text, { color: color, children: [spacing, "\u203A", spacing] }));
|
|
6
7
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -46,22 +46,22 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://github.com/aswitalski/pls#readme",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@anthropic-ai/sdk": "^0.
|
|
50
|
-
"ink": "^6.
|
|
49
|
+
"@anthropic-ai/sdk": "^0.69.0",
|
|
50
|
+
"ink": "^6.5.0",
|
|
51
51
|
"ink-text-input": "^6.0.0",
|
|
52
52
|
"react": "^19.2.0",
|
|
53
53
|
"yaml": "^2.8.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@types/node": "^
|
|
57
|
-
"@types/react": "^19.2.
|
|
58
|
-
"@vitest/coverage-v8": "^4.0.
|
|
59
|
-
"eslint": "^9.
|
|
56
|
+
"@types/node": "^24.10.1",
|
|
57
|
+
"@types/react": "^19.2.5",
|
|
58
|
+
"@vitest/coverage-v8": "^4.0.9",
|
|
59
|
+
"eslint": "^9.39.1",
|
|
60
60
|
"husky": "^9.1.7",
|
|
61
61
|
"ink-testing-library": "^4.0.0",
|
|
62
62
|
"prettier": "^3.6.2",
|
|
63
|
-
"typescript": "^5.
|
|
64
|
-
"typescript-eslint": "^8.
|
|
65
|
-
"vitest": "^4.0.
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"typescript-eslint": "^8.46.4",
|
|
65
|
+
"vitest": "^4.0.9"
|
|
66
66
|
}
|
|
67
67
|
}
|