prompt-language-shell 0.3.2 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,9 +18,8 @@ Your task is to create structured task definitions that:
18
18
  Each task should be precise and unambiguous, ready to be executed by the
19
19
  appropriate handler.
20
20
 
21
- **IMPORTANT**: While the primary use case involves building specific
22
- software products, all instructions and examples in this document are
23
- intentionally generic. This ensures the planning algorithm is not biased
21
+ **IMPORTANT**: All instructions and examples in this document are
22
+ intentionally generic to ensure the planning algorithm is not biased
24
23
  toward any particular domain and can be validated to work correctly across
25
24
  all scenarios. Do NOT assume or infer domain-specific context unless
26
25
  explicitly provided in skills or user requests.
@@ -118,7 +117,7 @@ executable operations.
118
117
  - Consult the skill's Description section for guidance on which steps are
119
118
  optional or conditional
120
119
  - Example: If description says "initialization only required for clean
121
- builds" and user says "rebuild cache", skip initialization steps
120
+ operations" and user says "regenerate cache", skip initialization steps
122
121
  - Only extract steps that align with the user's specific request
123
122
 
124
123
  5. **Create task definitions:**
@@ -136,26 +135,27 @@ executable operations.
136
135
  - NEVER create generic execute tasks for unmatched requirements
137
136
 
138
137
  Example 1 - Skill with parameter, variant specified:
139
- - Skill has {PROJECT} parameter with variants: Alpha, Beta, Gamma
140
- - Skill steps: "- Navigate to the {PROJECT} root directory. - Execute the
141
- {PROJECT} generation script. - Compile the {PROJECT}'s source code"
142
- - User: "build Alpha"
138
+ - Skill has {TARGET} parameter with variants: Alpha, Beta, Gamma
139
+ - Skill steps: "- Navigate to the {TARGET} root directory. - Execute the
140
+ {TARGET} generation script. - Run the {TARGET} processing pipeline"
141
+ - User: "process Alpha"
143
142
  - Correct: Three tasks with actions following the skill's steps, with
144
- {PROJECT} replaced by "Alpha"
145
- - WRONG: One task with action "Build Alpha"
143
+ {TARGET} replaced by "Alpha"
144
+ - WRONG: One task with action "Process Alpha"
146
145
 
147
146
  Example 2 - Skill with parameter, variant NOT specified:
148
147
  - Same skill as Example 1
149
- - User: "build"
150
- - Correct: One task with type "define", action "Clarify which project to
151
- build", params { options: ["Build Alpha", "Build Beta", "Build Gamma"] }
152
- - WRONG: Three tasks with {PROJECT} unreplaced or defaulted
148
+ - User: "process"
149
+ - Correct: One task with type "define", action "Clarify which target to
150
+ process", params { options: ["Process Alpha", "Process Beta", "Process
151
+ Gamma"] }
152
+ - WRONG: Three tasks with {TARGET} unreplaced or defaulted
153
153
 
154
154
  Example 3 - Skill without parameters:
155
- - Skill steps: "- Check prerequisites. - Run compilation. - Execute tests"
156
- - User: "run tests and generate a report"
155
+ - Skill steps: "- Check prerequisites. - Run processing. - Execute validation"
156
+ - User: "run validation and generate a report"
157
157
  - Correct: Four tasks (the three from skill + one for report generation)
158
- - WRONG: Two tasks ("run tests", "generate a report")
158
+ - WRONG: Two tasks ("run validation", "generate a report")
159
159
 
160
160
  Example 4 - NEGATIVE: Unmatched verb after matched skill:
161
161
  - ONLY skill available: "backup" (with steps: connect, export, save)
@@ -189,22 +189,29 @@ derived from available skills:
189
189
  2. For each applicable skill, extract specific, executable commands with their
190
190
  parameters
191
191
  3. Present these as concrete options, NOT generic categories
192
- 4. Each option should be something the user can directly select and execute
193
- 5. Format options WITHOUT brackets. Use commas to separate extra information
192
+ 4. Each option should represent a SINGLE atomic choice (e.g., which variant,
193
+ which environment, which product), NOT a complete sequence of steps
194
+ 5. **CRITICAL: Options must be ATOMIC choices, not sequences.** Each option
195
+ should select ONE thing (variant, environment, target), and once selected,
196
+ that choice will be expanded into individual sequential steps
197
+ 6. Format options WITHOUT brackets. Use commas to separate extra information
194
198
  instead. For example:
195
- - CORRECT: "Build project Alpha, the legacy version"
196
- - WRONG: "Build project Alpha (the legacy version)"
199
+ - CORRECT: "Process target Alpha, the legacy version"
200
+ - WRONG: "Process target Alpha (the legacy version)"
197
201
 
198
202
  Example:
199
- - Available skills: "Build Product" (variant A, variant B), "Deploy
203
+ - Available skills: "Process Product" (variant A, variant B), "Deploy
200
204
  Product" (staging, production), "Verify Product" (quick check, full
201
205
  validation)
202
206
  - User: "do something with the product"
203
- - Correct: Create "define" task with options: ["Build product variant A",
204
- "Build product variant B", "Deploy product to staging", "Deploy product
207
+ - Correct: Create "define" task with options: ["Process product variant A",
208
+ "Process product variant B", "Deploy product to staging", "Deploy product
205
209
  to production", "Run quick verification", "Run full validation"]
206
- - WRONG: Generic options like ["Build", "Deploy", "Verify"] - these
210
+ - WRONG: Generic options like ["Process", "Deploy", "Verify"] - these
207
211
  require further clarification
212
+ - WRONG: Options like ["Process A, run checks, deploy to staging", "Process
213
+ B, skip checks, deploy to production"] - these are sequences, not atomic
214
+ choices
208
215
 
209
216
  ## Evaluation of Requests
210
217
 
@@ -233,7 +240,7 @@ Examples that should be aborted as offensive:
233
240
  - "what is the current directory" → type: "answer"
234
241
 
235
242
  2. **Skill-based requests** - Use skills when verb matches a defined skill:
236
- - If "build" skill exists and user says "build" → Use the build skill
243
+ - If "process" skill exists and user says "process" → Use the process skill
237
244
  - If "deploy" skill exists and user says "deploy" → Use the deploy skill
238
245
  - Extract steps from the matching skill and create tasks for each step
239
246
 
@@ -262,32 +269,43 @@ type ONLY if there are concrete skill-based options:
262
269
  **For skill-based disambiguation:**
263
270
 
264
271
  When a skill exists but requires parameters or has multiple variants,
265
- use "define" type:
272
+ use "define" type to select ONE variant. The options should be ATOMIC choices,
273
+ not sequences of steps:
266
274
 
267
275
  1. **Skill requires parameters** - Ask which variant:
268
- - "build" + build skill with {PRODUCT} parameter (Alpha, Beta, Gamma,
269
- Delta) → Create "define" type with params { options: ["Build Alpha",
270
- "Build Beta", "Build Gamma", "Build Delta"] }
276
+ - "process" + process skill with {TARGET} parameter (Alpha, Beta, Gamma,
277
+ Delta) → Create "define" type with params { options: ["Process Alpha",
278
+ "Process Beta", "Process Gamma", "Process Delta"] }
279
+ - Each option is ONE variant choice
280
+ - Once selected, that variant will expand into its individual steps
271
281
  - User must specify which variant to execute the skill with
282
+ - **WRONG**: Options like ["Process Alpha and deploy", "Process Beta and
283
+ validate"] - these are sequences, not atomic variant choices
272
284
 
273
285
  2. **Skill has multiple distinct operations** - Ask which one:
274
286
  - "deploy" + deploy skill defining staging, production, canary
275
287
  environments → Create "define" type with params { options: ["Deploy to
276
288
  staging environment", "Deploy to production environment", "Deploy to
277
289
  canary environment"] }
290
+ - Each option selects ONE environment
291
+ - **WRONG**: Options like ["Deploy to staging, then production", "Deploy
292
+ to production only"] - these mix sequences with choices
278
293
 
279
294
  3. **Skill has single variant or user specifies variant** - Execute directly:
280
- - "build Alpha" + build skill with {PRODUCT} parameter → Replace
281
- {PRODUCT} with "Alpha" and execute skill steps
295
+ - "process Alpha" + process skill with {TARGET} parameter → Replace
296
+ {TARGET} with "Alpha" and execute skill steps as SEPARATE sequential
297
+ tasks
282
298
  - "deploy staging" + deploy skill with {ENV} parameter → Replace {ENV}
283
- with "staging" and execute that command
284
- - No disambiguation needed
299
+ with "staging" and execute each step as a SEPARATE task
300
+ - No disambiguation needed - proceed directly to breaking down into steps
285
301
 
286
302
  4. **User specifies "all"** - Spread into multiple tasks:
287
303
  - "deploy all" + deploy skill defining staging and production → Create
288
- two tasks: one for staging deployment, one for production deployment
289
- - "build all" + build skill with multiple product variants → Create four
290
- tasks: one for Alpha, one for Beta, one for Gamma, one for Delta
304
+ separate task sequences: first all staging steps, then all production
305
+ steps (as individual sequential tasks, not bundled)
306
+ - "process all" + process skill with multiple target variants Create
307
+ separate task sequences for each variant (each variant's steps as
308
+ individual sequential tasks)
291
309
 
292
310
  **For requests with no matching skills:**
293
311
 
@@ -305,8 +323,8 @@ Use "ignore" type:
305
323
 
306
324
  **Critical rules:**
307
325
 
308
- - NEVER create "define" type with generic categories like "Run tests",
309
- "Build project" unless these map to actual skill commands
326
+ - NEVER create "define" type with generic categories like "Run validation",
327
+ "Process target" unless these map to actual skill commands
310
328
  - NEVER create "define" type without a matching skill. The "define" type
311
329
  is ONLY for disambiguating between multiple variants/operations within
312
330
  an existing skill
@@ -338,14 +356,18 @@ When creating task definitions, focus on:
338
356
  - **Type**: Categorize the operation using one of these supported types:
339
357
  - `config` - Configuration changes, settings updates
340
358
  - `plan` - Planning or breaking down tasks
341
- - `execute` - Shell commands, running programs, scripts, compiling,
342
- building
359
+ - `execute` - Shell commands, running programs, scripts, processing
360
+ operations
343
361
  - `answer` - Answering questions, explaining concepts, providing
344
362
  information
345
363
  - `report` - Generating summaries, creating reports, displaying
346
364
  results
347
365
  - `define` - Presenting skill-based options when request matches
348
- multiple skill variants
366
+ multiple skill variants. **CRITICAL: Options must be ATOMIC choices
367
+ (selecting ONE variant, ONE environment, ONE target), NOT sequences of
368
+ steps. Each option represents a single selection that will later be
369
+ expanded into individual sequential steps. NEVER bundle multiple steps
370
+ into a single option like "Process X, run validation, deploy Y".**
349
371
  - `ignore` - Request is too vague and cannot be mapped to skills or
350
372
  inferred from context
351
373
 
@@ -541,7 +563,7 @@ Only split when tasks are truly distinct operations:
541
563
  dependencies" (type: execute) and "Run tests" (type: execute)
542
564
  - "create file; add content" → Two tasks with actions "Create a file" (type:
543
565
  execute) and "Add content" (type: execute)
544
- - "build project and deploy" → Two tasks with actions "Build the project"
566
+ - "process data and deploy" → Two tasks with actions "Process the data"
545
567
  (type: execute) and "Deploy" (type: execute)
546
568
 
547
569
  ### Correct Examples: Complex Questions
@@ -567,15 +589,15 @@ Split only when multiple distinct queries or operations are needed:
567
589
 
568
590
  Examples showing proper use of skills and disambiguation:
569
591
 
570
- - "build" with build skill requiring {PROJECT} parameter (Alpha, Beta, Gamma,
571
- Delta) → One task: type "define", action "Clarify which project to build",
572
- params { options: ["Build Alpha", "Build Beta", "Build Gamma", "Build
573
- Delta"] }. NOTE: If variants have descriptions, format as "Build Alpha, the
574
- legacy version" NOT "Build Alpha (the legacy version)"
575
- - "build Alpha" with same build skill → Three tasks extracted from skill
576
- steps: "Navigate to the Alpha project's root directory", "Execute the Alpha
577
- project generation script", "Compile the Alpha source code"
578
- - "build all" with same build skill → Twelve tasks (3 steps × 4 projects)
592
+ - "process" with process skill requiring {TARGET} parameter (Alpha, Beta, Gamma,
593
+ Delta) → One task: type "define", action "Clarify which target to process",
594
+ params { options: ["Process Alpha", "Process Beta", "Process Gamma", "Process
595
+ Delta"] }. NOTE: If variants have descriptions, format as "Process Alpha, the
596
+ legacy version" NOT "Process Alpha (the legacy version)"
597
+ - "process Alpha" with same process skill → Three tasks extracted from skill
598
+ steps: "Navigate to the Alpha target's root directory", "Execute the Alpha
599
+ target generation script", "Run the Alpha processing pipeline"
600
+ - "process all" with same process skill → Twelve tasks (3 steps × 4 targets)
579
601
  - "deploy" with deploy skill (staging, production, canary) → One task: type
580
602
  "define", action "Clarify which environment to deploy to", params
581
603
  { options: ["Deploy to staging environment", "Deploy to production
@@ -590,13 +612,40 @@ Examples showing proper use of skills and disambiguation:
590
612
  - "analyze data and generate report" with analyze skill but NO generate skill →
591
613
  Tasks from analyze skill + one "ignore" type for unknown "generate"
592
614
 
615
+ ### INCORRECT Examples: Sequence-Based Define Options
616
+
617
+ These examples show the WRONG way to use "define" type - bundling sequences
618
+ instead of atomic choices:
619
+
620
+ - "process alpha, verify, process beta" with process skill for targets Alpha
621
+ and Beta →
622
+ - WRONG: One task type "define" with options ["Process Alpha, run
623
+ verification, process Beta", "Process Alpha, skip verification, process
624
+ Beta"]
625
+ - CORRECT: Multiple sequential tasks: "Process Alpha", "Run verification",
626
+ "Process Beta" (no define needed - these are distinct sequential
627
+ operations)
628
+
629
+ - "deploy" with deploy skill (staging, production) →
630
+ - WRONG: One task type "define" with options ["Deploy to staging then
631
+ production", "Deploy to production only"]
632
+ - CORRECT: One task type "define" with options ["Deploy to staging", "Deploy
633
+ to production"] (atomic environment choices)
634
+
635
+ - "process and validate" with process skill ({TARGET} parameter: Alpha, Beta) →
636
+ - WRONG: One task type "define" with options ["Process Alpha and run
637
+ validation", "Process Beta and run validation"]
638
+ - CORRECT: One task type "define" to choose target ["Process Alpha",
639
+ "Process Beta"], then once selected, expand into separate sequential tasks
640
+ for process steps + validation step
641
+
593
642
  ### Correct Examples: Requests Without Matching Skills
594
643
 
595
644
  - "lint" with NO lint skill → One task: type "ignore", action "Ignore
596
645
  unknown 'lint' request"
597
646
  - "format" with NO format skill → One task: type "ignore", action "Ignore
598
647
  unknown 'format' request"
599
- - "build" with NO build skill → One task: type "ignore", action "Ignore
600
- unknown 'build' request"
648
+ - "process" with NO process skill → One task: type "ignore", action "Ignore
649
+ unknown 'process' request"
601
650
  - "do stuff" with NO skills → One task: type "ignore", action "Ignore
602
651
  unknown 'do stuff' request"
@@ -1,11 +1,24 @@
1
- import { ComponentName, } from '../types/components.js';
2
- import { StepType } from '../ui/Config.js';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ComponentName } from '../types/types.js';
3
3
  import { AnthropicModel, isValidAnthropicApiKey, isValidAnthropicModel, } from './config.js';
4
+ import { StepType } from '../ui/Config.js';
4
5
  export function markAsDone(component) {
5
6
  return { ...component, state: { ...component.state, done: true } };
6
7
  }
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
+ }
7
19
  export function createWelcomeDefinition(app) {
8
20
  return {
21
+ id: randomUUID(),
9
22
  name: ComponentName.Welcome,
10
23
  props: { app },
11
24
  };
@@ -35,6 +48,7 @@ export function createConfigSteps() {
35
48
  }
36
49
  export function createConfigDefinition(onFinished, onAborted) {
37
50
  return {
51
+ id: randomUUID(),
38
52
  name: ComponentName.Config,
39
53
  state: { done: false },
40
54
  props: {
@@ -44,8 +58,9 @@ export function createConfigDefinition(onFinished, onAborted) {
44
58
  },
45
59
  };
46
60
  }
47
- export function createCommandDefinition(command, service, onError, onComplete) {
61
+ export function createCommandDefinition(command, service, onError, onComplete, onAborted) {
48
62
  return {
63
+ id: randomUUID(),
49
64
  name: ComponentName.Command,
50
65
  state: {
51
66
  done: false,
@@ -56,20 +71,31 @@ export function createCommandDefinition(command, service, onError, onComplete) {
56
71
  service,
57
72
  onError,
58
73
  onComplete,
74
+ onAborted,
59
75
  },
60
76
  };
61
77
  }
62
- export function createPlanDefinition(message, tasks) {
78
+ export function createPlanDefinition(message, tasks, onAborted, onSelectionConfirmed) {
63
79
  return {
80
+ id: randomUUID(),
64
81
  name: ComponentName.Plan,
82
+ state: {
83
+ done: false,
84
+ highlightedIndex: null,
85
+ currentDefineGroupIndex: 0,
86
+ completedSelections: [],
87
+ },
65
88
  props: {
66
89
  message,
67
90
  tasks,
91
+ onSelectionConfirmed,
92
+ onAborted,
68
93
  },
69
94
  };
70
95
  }
71
96
  export function createFeedback(type, ...messages) {
72
97
  return {
98
+ id: randomUUID(),
73
99
  name: ComponentName.Feedback,
74
100
  props: {
75
101
  type,
@@ -79,12 +105,24 @@ export function createFeedback(type, ...messages) {
79
105
  }
80
106
  export function createMessage(text) {
81
107
  return {
108
+ id: randomUUID(),
82
109
  name: ComponentName.Message,
83
110
  props: {
84
111
  text,
85
112
  },
86
113
  };
87
114
  }
115
+ export function createRefinement(text, onAborted) {
116
+ return {
117
+ id: randomUUID(),
118
+ name: ComponentName.Refinement,
119
+ state: { done: false },
120
+ props: {
121
+ text,
122
+ onAborted,
123
+ },
124
+ };
125
+ }
88
126
  export function isStateless(component) {
89
127
  return !('state' in component);
90
128
  }
@@ -1,6 +1,6 @@
1
1
  export const planTool = {
2
2
  name: 'plan',
3
- description: 'Plan and structure tasks from a user command. Break down the request into clear, actionable steps with type information and parameters.',
3
+ description: 'Plan and structure tasks from a user command. Break down the request into clear, actionable steps with type information and parameters. When refining previously selected tasks, the input will be formatted as lowercase actions with types in brackets, e.g., "install the python development environment (type: execute), explain how virtual environments work (type: answer)".',
4
4
  input_schema: {
5
5
  type: 'object',
6
6
  properties: {
@@ -1,27 +1 @@
1
- export var ComponentName;
2
- (function (ComponentName) {
3
- ComponentName["Welcome"] = "welcome";
4
- ComponentName["Config"] = "config";
5
- ComponentName["Feedback"] = "feedback";
6
- ComponentName["Message"] = "message";
7
- ComponentName["Plan"] = "plan";
8
- ComponentName["Command"] = "command";
9
- })(ComponentName || (ComponentName = {}));
10
- export var TaskType;
11
- (function (TaskType) {
12
- TaskType["Config"] = "config";
13
- TaskType["Plan"] = "plan";
14
- TaskType["Execute"] = "execute";
15
- TaskType["Answer"] = "answer";
16
- TaskType["Report"] = "report";
17
- TaskType["Define"] = "define";
18
- TaskType["Ignore"] = "ignore";
19
- TaskType["Select"] = "select";
20
- })(TaskType || (TaskType = {}));
21
- export var FeedbackType;
22
- (function (FeedbackType) {
23
- FeedbackType["Info"] = "info";
24
- FeedbackType["Succeeded"] = "succeeded";
25
- FeedbackType["Aborted"] = "aborted";
26
- FeedbackType["Failed"] = "failed";
27
- })(FeedbackType || (FeedbackType = {}));
1
+ export {};
@@ -0,0 +1,29 @@
1
+ export var ComponentName;
2
+ (function (ComponentName) {
3
+ ComponentName["Welcome"] = "welcome";
4
+ ComponentName["Config"] = "config";
5
+ ComponentName["Message"] = "message";
6
+ ComponentName["Command"] = "command";
7
+ ComponentName["Plan"] = "plan";
8
+ ComponentName["Refinement"] = "refinement";
9
+ ComponentName["Feedback"] = "feedback";
10
+ })(ComponentName || (ComponentName = {}));
11
+ export var TaskType;
12
+ (function (TaskType) {
13
+ TaskType["Config"] = "config";
14
+ TaskType["Plan"] = "plan";
15
+ TaskType["Execute"] = "execute";
16
+ TaskType["Answer"] = "answer";
17
+ TaskType["Report"] = "report";
18
+ TaskType["Define"] = "define";
19
+ TaskType["Ignore"] = "ignore";
20
+ TaskType["Select"] = "select";
21
+ TaskType["Discard"] = "discard";
22
+ })(TaskType || (TaskType = {}));
23
+ export var FeedbackType;
24
+ (function (FeedbackType) {
25
+ FeedbackType["Info"] = "info";
26
+ FeedbackType["Succeeded"] = "succeeded";
27
+ FeedbackType["Aborted"] = "aborted";
28
+ FeedbackType["Failed"] = "failed";
29
+ })(FeedbackType || (FeedbackType = {}));
package/dist/ui/Column.js CHANGED
@@ -2,5 +2,5 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box } from 'ink';
3
3
  import { Component } from './Component.js';
4
4
  export const Column = ({ items }) => {
5
- return (_jsx(Box, { marginTop: 1, marginBottom: 1, marginLeft: 1, flexDirection: "column", gap: 1, children: items.map((item, index) => (_jsx(Box, { children: _jsx(Component, { def: item }) }, index))) }));
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
6
  };
@@ -1,12 +1,18 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { Box, Text } from 'ink';
3
+ import { Box, Text, useInput } from 'ink';
4
4
  import { Spinner } from './Spinner.js';
5
5
  const MIN_PROCESSING_TIME = 1000; // purely for visual effect
6
- export function Command({ command, state, service, children, onError, onComplete, }) {
6
+ export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
7
7
  const done = state?.done ?? false;
8
8
  const [error, setError] = useState(null);
9
9
  const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
10
+ useInput((input, key) => {
11
+ if (key.escape && isLoading && !done) {
12
+ setIsLoading(false);
13
+ onAborted();
14
+ }
15
+ }, { isActive: isLoading && !done });
10
16
  useEffect(() => {
11
17
  // Skip processing if done (showing historical/final state)
12
18
  if (done) {
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { ComponentName } from '../types/components.js';
2
+ import { ComponentName } from '../types/types.js';
3
3
  import { Command } from './Command.js';
4
4
  import { Config } from './Config.js';
5
5
  import { Feedback } from './Feedback.js';
6
6
  import { Message } from './Message.js';
7
7
  import { Plan } from './Plan.js';
8
+ import { Refinement } from './Refinement.js';
8
9
  import { Welcome } from './Welcome.js';
9
10
  export function Component({ def }) {
10
11
  switch (def.name) {
@@ -26,5 +27,10 @@ export function Component({ def }) {
26
27
  return _jsx(Feedback, { ...def.props });
27
28
  case ComponentName.Message:
28
29
  return _jsx(Message, { ...def.props });
30
+ case ComponentName.Refinement: {
31
+ const props = def.props;
32
+ const state = def.state;
33
+ return _jsx(Refinement, { ...props, state: state });
34
+ }
29
35
  }
30
36
  }
package/dist/ui/Config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { Box, Text, useInput, useFocus } from 'ink';
3
+ import { Box, Text, useFocus, useInput } from 'ink';
4
4
  import TextInput from 'ink-text-input';
5
5
  export var StepType;
6
6
  (function (StepType) {
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- import { FeedbackType } from '../types/components.js';
3
+ import { FeedbackType } from '../types/types.js';
4
4
  function getSymbol(type) {
5
5
  return {
6
6
  [FeedbackType.Info]: 'ℹ',
package/dist/ui/List.js CHANGED
@@ -1,7 +1,21 @@
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 }) => {
4
+ export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, }) => {
5
5
  const marginLeft = level > 0 ? 4 : 0;
6
- return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "whiteBright", children: ' - ' }), _jsx(Label, { description: item.description.text, descriptionColor: item.description.color, type: item.type.text, typeColor: item.type.color })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1 }))] }, index))) }));
6
+ return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => {
7
+ // At level 0, track which parent is active for child highlighting
8
+ // At level > 0, only highlight if this parent is the active one
9
+ const shouldHighlightChildren = level === 0 ? highlightedParentIndex === index : false;
10
+ const isHighlighted = item.highlighted || (level > 0 && index === highlightedIndex);
11
+ const marker = item.marker || (isHighlighted ? ' → ' : ' - ');
12
+ // Use highlighted colors if available and item is highlighted
13
+ const descriptionColor = isHighlighted && item.description.highlightedColor
14
+ ? item.description.highlightedColor
15
+ : item.description.color;
16
+ const typeColor = isHighlighted && item.type.highlightedColor
17
+ ? item.type.highlightedColor
18
+ : item.type.color;
19
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "whiteBright", children: marker }), _jsx(Label, { description: item.description.text, descriptionColor: descriptionColor, type: item.type.text, typeColor: typeColor })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null }))] }, index));
20
+ }) }));
7
21
  };
package/dist/ui/Main.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { ComponentName, } from '../types/components.js';
3
+ import { ComponentName, FeedbackType, TaskType, } from '../types/types.js';
4
4
  import { createAnthropicService, } from '../services/anthropic.js';
5
- import { FeedbackType } from '../types/components.js';
6
5
  import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, saveAnthropicConfig, } from '../services/config.js';
7
- import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createPlanDefinition, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
6
+ import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createRefinement, createPlanDefinition, createWelcomeDefinition, getRefiningMessage, isStateless, markAsDone, } from '../services/components.js';
8
7
  import { exitApp } from '../services/process.js';
9
8
  import { Column } from './Column.js';
10
9
  export const Main = ({ app, command }) => {
@@ -46,18 +45,106 @@ export const Main = ({ app, command }) => {
46
45
  return [];
47
46
  });
48
47
  }, [addToTimeline]);
48
+ const handleAborted = React.useCallback((operationName) => {
49
+ setQueue((currentQueue) => {
50
+ if (currentQueue.length === 0)
51
+ return currentQueue;
52
+ const [first] = currentQueue;
53
+ if (!isStateless(first)) {
54
+ addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, `${operationName} was aborted by user`));
55
+ }
56
+ exitApp(0);
57
+ return [];
58
+ });
59
+ }, [addToTimeline]);
60
+ const handleConfigAborted = React.useCallback(() => {
61
+ handleAborted('Configuration');
62
+ }, [handleAborted]);
63
+ const handlePlanAborted = React.useCallback(() => {
64
+ handleAborted('Task selection');
65
+ }, [handleAborted]);
66
+ const handleCommandAborted = React.useCallback(() => {
67
+ handleAborted('Request');
68
+ }, [handleAborted]);
69
+ const handleRefinementAborted = React.useCallback(() => {
70
+ handleAborted('Plan refinement');
71
+ }, [handleAborted]);
72
+ const handlePlanSelectionConfirmed = React.useCallback(async (selectedTasks) => {
73
+ // Mark current plan as done and add refinement to queue
74
+ let refinementDef = null;
75
+ refinementDef = createRefinement(getRefiningMessage(), handleRefinementAborted);
76
+ setQueue((currentQueue) => {
77
+ if (currentQueue.length === 0)
78
+ return currentQueue;
79
+ const [first] = currentQueue;
80
+ if (first.name === ComponentName.Plan) {
81
+ addToTimeline(markAsDone(first));
82
+ }
83
+ // Add refinement to queue so it becomes the active component
84
+ return [refinementDef];
85
+ });
86
+ // Process refined command in background
87
+ try {
88
+ const refinedCommand = selectedTasks
89
+ .map((task) => {
90
+ const action = task.action.toLowerCase().replace(/,/g, ' -');
91
+ const type = task.type || 'execute';
92
+ return `${action} (type: ${type})`;
93
+ })
94
+ .join(', ');
95
+ const result = await service.processWithTool(refinedCommand, 'plan');
96
+ // Mark refinement as done and move to timeline
97
+ setQueue((currentQueue) => {
98
+ if (currentQueue.length > 0 &&
99
+ currentQueue[0].id === refinementDef.id) {
100
+ addToTimeline(markAsDone(currentQueue[0]));
101
+ }
102
+ return [];
103
+ });
104
+ // Show final execution plan
105
+ const planDefinition = createPlanDefinition(result.message, result.tasks, handlePlanAborted, undefined);
106
+ addToTimeline(planDefinition);
107
+ exitApp(0);
108
+ }
109
+ catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
111
+ // Mark refinement as done and move to timeline before showing error
112
+ setQueue((currentQueue) => {
113
+ if (currentQueue.length > 0 &&
114
+ currentQueue[0].id === refinementDef.id) {
115
+ addToTimeline(markAsDone(currentQueue[0]));
116
+ }
117
+ return [];
118
+ });
119
+ addToTimeline(createFeedback(FeedbackType.Failed, 'Unexpected error occurred:', errorMessage));
120
+ exitApp(1);
121
+ }
122
+ }, [addToTimeline, service, handleRefinementAborted]);
49
123
  const handleCommandComplete = React.useCallback((message, tasks) => {
50
124
  setQueue((currentQueue) => {
51
125
  if (currentQueue.length === 0)
52
126
  return currentQueue;
53
127
  const [first] = currentQueue;
128
+ // Check if tasks contain a Define task that requires user interaction
129
+ const hasDefineTask = tasks.some((task) => task.type === TaskType.Define);
54
130
  if (first.name === ComponentName.Command) {
55
- addToTimeline(markAsDone(first), createPlanDefinition(message, tasks));
131
+ const planDefinition = createPlanDefinition(message, tasks, handlePlanAborted, hasDefineTask ? handlePlanSelectionConfirmed : undefined);
132
+ if (hasDefineTask) {
133
+ // Don't exit - keep the plan in the queue for interaction
134
+ addToTimeline(markAsDone(first));
135
+ return [planDefinition];
136
+ }
137
+ else {
138
+ // No define task - add plan to timeline and exit
139
+ addToTimeline(markAsDone(first), planDefinition);
140
+ exitApp(0);
141
+ return [];
142
+ }
56
143
  }
57
144
  exitApp(0);
58
145
  return [];
59
146
  });
60
- }, [addToTimeline]);
147
+ }, [addToTimeline, handlePlanSelectionConfirmed]);
61
148
  const handleConfigFinished = React.useCallback((config) => {
62
149
  const anthropicConfig = config;
63
150
  saveAnthropicConfig(anthropicConfig);
@@ -75,7 +162,7 @@ export const Main = ({ app, command }) => {
75
162
  if (command) {
76
163
  return [
77
164
  ...rest,
78
- createCommandDefinition(command, newService, handleCommandError, handleCommandComplete),
165
+ createCommandDefinition(command, newService, handleCommandError, handleCommandComplete, handleCommandAborted),
79
166
  ];
80
167
  }
81
168
  // No command - exit after showing completion message
@@ -83,25 +170,13 @@ export const Main = ({ app, command }) => {
83
170
  return rest;
84
171
  });
85
172
  }, [addToTimeline, command, handleCommandError, handleCommandComplete]);
86
- const handleConfigAborted = React.useCallback(() => {
87
- setQueue((currentQueue) => {
88
- if (currentQueue.length === 0)
89
- return currentQueue;
90
- const [first] = currentQueue;
91
- if (first.name === ComponentName.Config) {
92
- addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, 'Configuration aborted by user'));
93
- }
94
- exitApp(0);
95
- return [];
96
- });
97
- }, [addToTimeline]);
98
173
  // Initialize queue on mount
99
174
  React.useEffect(() => {
100
175
  const hasConfig = !!service;
101
176
  if (command && hasConfig) {
102
177
  // With command + valid config: [Command]
103
178
  setQueue([
104
- createCommandDefinition(command, service, handleCommandError, handleCommandComplete),
179
+ createCommandDefinition(command, service, handleCommandError, handleCommandComplete, handleCommandAborted),
105
180
  ]);
106
181
  }
107
182
  else if (command && !hasConfig) {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Text } from 'ink';
2
+ import { Box, Text } from 'ink';
3
3
  export const Message = ({ text }) => {
4
- return _jsx(Text, { children: text });
4
+ return (_jsx(Box, { children: _jsx(Text, { children: text }) }));
5
5
  };
package/dist/ui/Plan.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box } from 'ink';
3
- import { TaskType } from '../types/components.js';
2
+ import { useEffect, useState } from 'react';
3
+ import { Box, useInput } from 'ink';
4
+ import { TaskType } from '../types/types.js';
4
5
  import { Label } from './Label.js';
5
6
  import { List } from './List.js';
6
7
  const ColorPalette = {
@@ -36,8 +37,12 @@ const ColorPalette = {
36
37
  description: '#888888', // grey
37
38
  type: '#5c8cbc', // steel blue
38
39
  },
40
+ [TaskType.Discard]: {
41
+ description: '#666666', // dark grey
42
+ type: '#a85c3f', // dark orange
43
+ },
39
44
  };
40
- function taskToListItem(task) {
45
+ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutSelection = false) {
41
46
  const item = {
42
47
  description: {
43
48
  text: task.action,
@@ -46,22 +51,162 @@ function taskToListItem(task) {
46
51
  type: { text: task.type, color: ColorPalette[task.type].type },
47
52
  children: [],
48
53
  };
54
+ // Mark define tasks with right arrow when no selection has been made
55
+ if (isDefineTaskWithoutSelection) {
56
+ item.marker = ' → ';
57
+ }
49
58
  // Add children for Define tasks with options
50
59
  if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
51
- const selectColors = ColorPalette[TaskType.Select];
52
- item.children = task.params.options.map((option) => ({
53
- description: {
54
- text: String(option),
55
- color: selectColors.description,
56
- },
57
- type: {
58
- text: TaskType.Select,
59
- color: selectColors.type,
60
- },
61
- }));
60
+ item.children = task.params.options.map((option, index) => {
61
+ // Determine the type based on selection state
62
+ let childType = TaskType.Select;
63
+ if (highlightedChildIndex !== null) {
64
+ // A selection was made - mark others as discarded
65
+ childType =
66
+ index === highlightedChildIndex ? TaskType.Execute : TaskType.Discard;
67
+ }
68
+ const colors = ColorPalette[childType];
69
+ return {
70
+ description: {
71
+ text: String(option),
72
+ color: colors.description,
73
+ highlightedColor: ColorPalette[TaskType.Plan].description,
74
+ },
75
+ type: {
76
+ text: childType,
77
+ color: colors.type,
78
+ highlightedColor: ColorPalette[TaskType.Plan].type,
79
+ },
80
+ };
81
+ });
62
82
  }
63
83
  return item;
64
84
  }
65
- export function Plan({ message, tasks }) {
66
- return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, children: _jsx(Label, { description: message, descriptionColor: ColorPalette[TaskType.Plan].description, type: TaskType.Plan, typeColor: ColorPalette[TaskType.Plan].type }) })), _jsx(List, { items: tasks.map(taskToListItem) })] }));
85
+ export function Plan({ message, tasks, state, onSelectionConfirmed, onAborted, }) {
86
+ const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
87
+ const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(state?.currentDefineGroupIndex ?? 0);
88
+ const [completedSelections, setCompletedSelections] = useState(state?.completedSelections ?? []);
89
+ const [isDone, setIsDone] = useState(state?.done ?? false);
90
+ // Find all Define tasks
91
+ const defineTaskIndices = tasks
92
+ .map((t, idx) => (t.type === TaskType.Define ? idx : -1))
93
+ .filter((idx) => idx !== -1);
94
+ // Get the current active define task
95
+ const currentDefineTaskIndex = defineTaskIndices[currentDefineGroupIndex] ?? -1;
96
+ const defineTask = currentDefineTaskIndex >= 0 ? tasks[currentDefineTaskIndex] : null;
97
+ const optionsCount = Array.isArray(defineTask?.params?.options)
98
+ ? defineTask.params.options.length
99
+ : 0;
100
+ const hasMoreGroups = currentDefineGroupIndex < defineTaskIndices.length - 1;
101
+ useInput((input, key) => {
102
+ // Don't handle input if already done or no define task
103
+ if (isDone || !defineTask) {
104
+ return;
105
+ }
106
+ if (key.escape) {
107
+ onAborted();
108
+ return;
109
+ }
110
+ if (key.downArrow) {
111
+ setHighlightedIndex((prev) => {
112
+ if (prev === null) {
113
+ return 0; // Select first
114
+ }
115
+ return (prev + 1) % optionsCount; // Wrap around
116
+ });
117
+ }
118
+ else if (key.upArrow) {
119
+ setHighlightedIndex((prev) => {
120
+ if (prev === null) {
121
+ return optionsCount - 1; // Select last
122
+ }
123
+ return (prev - 1 + optionsCount) % optionsCount; // Wrap around
124
+ });
125
+ }
126
+ else if (key.return && highlightedIndex !== null) {
127
+ // Record the selection for this group
128
+ const newCompletedSelections = [...completedSelections];
129
+ newCompletedSelections[currentDefineGroupIndex] = highlightedIndex;
130
+ setCompletedSelections(newCompletedSelections);
131
+ if (hasMoreGroups) {
132
+ // Advance to next group
133
+ setCurrentDefineGroupIndex(currentDefineGroupIndex + 1);
134
+ setHighlightedIndex(null);
135
+ }
136
+ else {
137
+ // Last group - mark as done to show the selection
138
+ setIsDone(true);
139
+ setHighlightedIndex(null); // Clear highlight to show Execute color
140
+ if (state) {
141
+ state.done = true;
142
+ }
143
+ // Build refined task list with only selected options (no discarded or ignored ones)
144
+ const refinedTasks = [];
145
+ tasks.forEach((task, idx) => {
146
+ const defineGroupIndex = defineTaskIndices.indexOf(idx);
147
+ if (defineGroupIndex !== -1 &&
148
+ Array.isArray(task.params?.options)) {
149
+ // This is a Define task - only include the selected option
150
+ const options = task.params.options;
151
+ const selectedIndex = newCompletedSelections[defineGroupIndex];
152
+ refinedTasks.push({
153
+ action: String(options[selectedIndex]),
154
+ type: TaskType.Execute,
155
+ });
156
+ }
157
+ else if (task.type !== TaskType.Ignore &&
158
+ task.type !== TaskType.Discard) {
159
+ // Regular task - keep as is, but skip Ignore and Discard tasks
160
+ refinedTasks.push(task);
161
+ }
162
+ });
163
+ onSelectionConfirmed?.(refinedTasks);
164
+ }
165
+ }
166
+ }, { isActive: !isDone && defineTask !== null });
167
+ // Sync state back to state object
168
+ useEffect(() => {
169
+ if (state) {
170
+ state.highlightedIndex = highlightedIndex;
171
+ state.currentDefineGroupIndex = currentDefineGroupIndex;
172
+ state.completedSelections = completedSelections;
173
+ state.done = isDone;
174
+ }
175
+ }, [
176
+ highlightedIndex,
177
+ currentDefineGroupIndex,
178
+ completedSelections,
179
+ isDone,
180
+ state,
181
+ ]);
182
+ const listItems = tasks.map((task, idx) => {
183
+ // Find which define group this task belongs to (if any)
184
+ const defineGroupIndex = defineTaskIndices.indexOf(idx);
185
+ const isDefineTask = defineGroupIndex !== -1;
186
+ // Determine child selection state
187
+ let childIndex = null;
188
+ if (isDefineTask) {
189
+ if (defineGroupIndex < currentDefineGroupIndex) {
190
+ // Previously completed group - show the selection
191
+ childIndex = completedSelections[defineGroupIndex] ?? null;
192
+ }
193
+ else if (defineGroupIndex === currentDefineGroupIndex) {
194
+ // Current active group - show live navigation unless done
195
+ if (isDone) {
196
+ // If done, show the completed selection for this group too
197
+ childIndex = completedSelections[defineGroupIndex] ?? null;
198
+ }
199
+ else {
200
+ childIndex = null;
201
+ }
202
+ }
203
+ }
204
+ // Show arrow on current active define task when no child is highlighted and not done
205
+ const isDefineWithoutSelection = isDefineTask &&
206
+ defineGroupIndex === currentDefineGroupIndex &&
207
+ highlightedIndex === null &&
208
+ !isDone;
209
+ return taskToListItem(task, childIndex, isDefineWithoutSelection);
210
+ });
211
+ return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, children: _jsx(Label, { description: message, descriptionColor: ColorPalette[TaskType.Plan].description, type: TaskType.Plan, typeColor: ColorPalette[TaskType.Plan].type }) })), _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex })] }));
67
212
  }
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, useInput } from 'ink';
3
+ import { Message } from './Message.js';
4
+ import { Spinner } from './Spinner.js';
5
+ export const Refinement = ({ text, state, onAborted }) => {
6
+ const isDone = state?.done ?? false;
7
+ useInput((input, key) => {
8
+ if (key.escape && !isDone) {
9
+ onAborted();
10
+ return;
11
+ }
12
+ }, { isActive: !isDone });
13
+ return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text }), !isDone && _jsx(Spinner, {})] }));
14
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-language-shell",
3
- "version": "0.3.2",
3
+ "version": "0.3.6",
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",
@@ -12,7 +12,8 @@
12
12
  ],
13
13
  "scripts": {
14
14
  "clean": "rm -rf dist",
15
- "build": "npm run clean && tsc && chmod +x dist/index.js && mkdir -p dist/config && cp src/config/*.md dist/config/",
15
+ "typecheck": "tsc --project tsconfig.eslint.json",
16
+ "build": "npm run typecheck && npm run clean && tsc && chmod +x dist/index.js && mkdir -p dist/config && cp src/config/*.md dist/config/",
16
17
  "dev": "npm run build && tsc --watch",
17
18
  "prepare": "husky",
18
19
  "prepublishOnly": "npm run check",