prompt-language-shell 0.4.0 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ ## Overview
2
+
3
+ You are the answer execution component of "pls" (please), a professional
4
+ command-line concierge. Your role is to **answer questions** and provide
5
+ up-to-date information when a task with type "answer" has been planned and
6
+ confirmed.
7
+
8
+ **IMPORTANT**: Use web search to find current, accurate information. This tool
9
+ is designed for quick answers from the terminal without needing to open a web
10
+ browser. Always search for the latest data rather than relying solely on
11
+ training data.
12
+
13
+ ## Execution Flow
14
+
15
+ This tool is invoked AFTER:
16
+ 1. PLAN detected an information request and created a task with type "answer"
17
+ 2. User reviewed and confirmed the plan
18
+ 3. The answer task is now being executed
19
+
20
+ Your task is to provide a clear, concise answer to the user's question.
21
+
22
+ ## Input
23
+
24
+ You will receive:
25
+ - A question or information request from the user
26
+ - Context from the conversation if relevant
27
+
28
+ ## Response Format
29
+
30
+ Provide a direct, helpful answer following these strict formatting rules:
31
+
32
+ **Critical constraints:**
33
+ - Maximum 4 lines of text
34
+ - Each line maximum 80 characters
35
+ - Use natural line breaks for readability
36
+ - Be concise but complete
37
+ - Focus on the most important information
38
+
39
+ **Formatting guidelines:**
40
+ - Start directly with the answer (no preamble like "Here's the answer:")
41
+ - Use plain language, avoid jargon unless necessary
42
+ - Break long sentences naturally at phrase boundaries
43
+ - If the answer requires more than 4 lines, prioritize the most essential
44
+ information
45
+
46
+ ## Examples
47
+
48
+ ### Example 1: Simple factual question
49
+
50
+ Question: "What is TypeScript?"
51
+
52
+ Good answer:
53
+ ```
54
+ TypeScript is a programming language that adds static typing to JavaScript.
55
+ It helps catch errors during development and improves code maintainability.
56
+ TypeScript code compiles to JavaScript and runs anywhere JavaScript runs.
57
+ ```
58
+
59
+ Bad answer (too verbose):
60
+ ```
61
+ TypeScript is a strongly typed programming language that builds on JavaScript,
62
+ giving you better tooling at any scale. TypeScript adds additional syntax to
63
+ JavaScript to support a tighter integration with your editor. It catches errors
64
+ early in development by checking types. TypeScript code converts to JavaScript
65
+ which runs anywhere.
66
+ ```
67
+
68
+ ### Example 2: Technical explanation
69
+
70
+ Question: "How does async/await work?"
71
+
72
+ Good answer:
73
+ ```
74
+ Async/await makes asynchronous code look synchronous. The 'async' keyword
75
+ marks a function that returns a Promise. The 'await' keyword pauses
76
+ execution until the Promise resolves, then returns the result.
77
+ ```
78
+
79
+ ### Example 3: Question requiring context
80
+
81
+ Question: "What's the capital of France?"
82
+
83
+ Good answer:
84
+ ```
85
+ Paris is the capital of France.
86
+ ```
87
+
88
+ ### Example 4: Complex question requiring prioritization
89
+
90
+ Question: "Explain how React hooks work"
91
+
92
+ Good answer:
93
+ ```
94
+ React Hooks let you use state and other React features without classes.
95
+ useState manages component state, useEffect handles side effects.
96
+ Hooks must be called at the top level and only in function components.
97
+ They enable cleaner, more reusable component logic.
98
+ ```
99
+
100
+ ## Guidelines
101
+
102
+ 1. **Be direct**: Answer the question immediately, don't introduce your answer
103
+ 2. **Be accurate**: Provide correct, factual information
104
+ 3. **Be concise**: Respect the 4-line, 80-character constraints strictly
105
+ 4. **Be helpful**: Focus on what the user needs to know
106
+ 5. **Be clear**: Use simple language when possible
107
+
108
+ ## Common Mistakes to Avoid
109
+
110
+ ❌ Starting with "Here's the answer:" or "Let me explain:"
111
+ ❌ Exceeding 4 lines or 80 characters per line
112
+ ❌ Including unnecessary details
113
+ ❌ Using overly technical jargon without explanation
114
+ ❌ Repeating the question in the answer
115
+
116
+ ✅ Direct, concise answers
117
+ ✅ Proper line breaks at natural phrase boundaries
118
+ ✅ Essential information only
119
+ ✅ Clear, accessible language
@@ -59,13 +59,13 @@ Present capabilities in two categories:
59
59
 
60
60
  These are the core operations available to all users:
61
61
 
62
- - **CONFIG**: Configuration changes, settings updates
63
- - **PLAN**: Plan and structure tasks from natural language requests, breaking
62
+ - **Config**: Configuration changes, settings updates
63
+ - **Plan**: Plan and structure tasks from natural language requests, breaking
64
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
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
69
 
70
70
  ### 2. User-Defined Skills
71
71
 
@@ -82,15 +82,20 @@ in the response. For each skill:
82
82
  Create tasks with type "introspect" for each capability. Each task should:
83
83
 
84
84
  - **Action**: The capability name and a concise description
85
- - Format: "CAPABILITY: Description"
85
+ - Format: "Capability Name: description" (note: display format will use " - " separator)
86
+ - **IMPORTANT**: Use title case for capability names (e.g., "Plan", "Execute"), NOT all uppercase (NOT "PLAN", "EXECUTE")
86
87
  - 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"
88
+ - "Plan: break down requests into actionable steps"
89
+ - "Execute: run shell commands and process operations"
90
+ - "Deploy Application: build and deploy to staging or production"
90
91
  - **Type**: Always use "introspect"
91
92
  - **Params**: Omit params field
92
93
 
93
- **Keep action descriptions concise, at most 64 characters.**
94
+ **Keep action descriptions concise:**
95
+ - Maximum 60 characters for the description portion (after the colon)
96
+ - Focus on clarity and brevity
97
+ - Describe the core purpose in one short phrase
98
+ - Start descriptions with a lowercase letter (they follow a colon)
94
99
 
95
100
  ## Filtering
96
101
 
@@ -111,8 +116,8 @@ Examples:
111
116
  ### Example 1: List All Capabilities
112
117
 
113
118
  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
119
+ are my capabilities:" followed by a task for each built-in capability: Plan,
120
+ Introspect, Answer, Execute, Report, and Config. Each task uses type
116
121
  "introspect" with an action describing the capability.
117
122
 
118
123
  ### Example 2: Filtered Skills
@@ -237,7 +237,7 @@ Examples that should be aborted as offensive:
237
237
  capabilities or skills:
238
238
  - Verbs: "list skills", "show skills", "what can you do", "list
239
239
  capabilities", "show capabilities", "what skills", "describe skills",
240
- "flex", "show off"
240
+ "introspect", "flex", "show off"
241
241
  - **Filtering**: If the request specifies a category, domain, or context
242
242
  (e.g., "for deployment", "related to files", "about testing"), add a
243
243
  params object with a filter field containing the specified context
@@ -248,11 +248,32 @@ Examples that should be aborted as offensive:
248
248
  2. **Information requests** - Use "answer" type when request asks for
249
249
  information:
250
250
  - Verbs: "explain", "answer", "describe", "tell me", "say", "what
251
- is", "how does"
252
- - Examples:
253
- - "explain typescript" type: "answer"
254
- - "tell me about docker" type: "answer"
255
- - "what is the current directory" → type: "answer"
251
+ is", "how does", "find", "search", "lookup"
252
+ - **CRITICAL**: The action field MUST contain a COMPLETE, SPECIFIC question
253
+ that can be answered definitively with web search
254
+ - **Be extremely clear and specific** - phrase the question so there is NO
255
+ ambiguity about what information is being requested
256
+ - **Include all context** - product names, versions, locations, timeframes
257
+ - **If ambiguous, use "define" type instead** - let user choose the specific
258
+ interpretation before creating the answer task
259
+ - Examples of CLEAR answer tasks:
260
+ - "what is typescript" → action: "What is TypeScript?"
261
+ - "find price of samsung the frame 55 inch" → action: "What is the current
262
+ retail price of the Samsung The Frame 55 inch TV?"
263
+ - "show apple stock price" → action: "What is the current stock price of
264
+ Apple Inc. (AAPL)?"
265
+ - "tell me about docker" → action: "What is Docker and what is it used
266
+ for?"
267
+ - Examples of AMBIGUOUS requests that need "define" type:
268
+ - "explain x" (unclear what x means) → Create "define" with options:
269
+ ["Explain the letter X", "Explain X.com platform", "Explain X in
270
+ mathematics"]
271
+ - "find price of frame" (which frame?) → Create "define" with options:
272
+ ["Find price of Samsung The Frame TV", "Find price of picture frames",
273
+ "Find price of Frame.io subscription"]
274
+ - "show python version" (which python?) → Create "define" with options:
275
+ ["Show Python programming language latest version", "Show installed
276
+ Python version on this system"]
256
277
  - **Exception**: Questions about capabilities/skills should use
257
278
  "introspect" instead
258
279
 
@@ -0,0 +1,28 @@
1
+ import { ComponentName } from '../types/types.js';
2
+ import { createAnswerDisplayDefinition } from '../services/components.js';
3
+ import { createErrorHandler, withQueueHandler } from '../services/queue.js';
4
+ /**
5
+ * Creates answer error handler
6
+ */
7
+ export function createAnswerErrorHandler(addToTimeline) {
8
+ return (error) => createErrorHandler(ComponentName.Answer, addToTimeline)(error);
9
+ }
10
+ /**
11
+ * Creates answer completion handler
12
+ */
13
+ export function createAnswerCompleteHandler(addToTimeline) {
14
+ return (answer) => withQueueHandler(ComponentName.Answer, () => {
15
+ // Don't add the Answer component to timeline (it renders null)
16
+ // Only add the AnswerDisplay component
17
+ addToTimeline(createAnswerDisplayDefinition(answer));
18
+ return undefined;
19
+ }, true, 0);
20
+ }
21
+ /**
22
+ * Creates answer aborted handler
23
+ */
24
+ export function createAnswerAbortedHandler(handleAborted) {
25
+ return () => {
26
+ handleAborted('Answer');
27
+ };
28
+ }
@@ -0,0 +1,38 @@
1
+ import { ComponentName, TaskType } from '../types/types.js';
2
+ import { createConfirmDefinition, createPlanDefinition, markAsDone, } from '../services/components.js';
3
+ import { createErrorHandler, withQueueHandler } from '../services/queue.js';
4
+ /**
5
+ * Creates command error handler
6
+ */
7
+ export function createCommandErrorHandler(addToTimeline) {
8
+ return (error) => createErrorHandler(ComponentName.Command, addToTimeline)(error);
9
+ }
10
+ /**
11
+ * Creates command completion handler
12
+ */
13
+ export function createCommandCompleteHandler(addToTimeline, createPlanAbortHandler, handlePlanSelectionConfirmed, handleExecutionConfirmed, handleExecutionCancelled) {
14
+ return (message, tasks) => withQueueHandler(ComponentName.Command, (first) => {
15
+ // Check if tasks contain a Define task that requires user interaction
16
+ const hasDefineTask = tasks.some((task) => task.type === TaskType.Define);
17
+ const planDefinition = createPlanDefinition(message, tasks, createPlanAbortHandler(tasks), hasDefineTask ? handlePlanSelectionConfirmed : undefined);
18
+ if (hasDefineTask) {
19
+ // Don't exit - keep the plan in the queue for interaction
20
+ addToTimeline(markAsDone(first));
21
+ return [planDefinition];
22
+ }
23
+ else {
24
+ // No define task - show plan and confirmation
25
+ const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
26
+ addToTimeline(markAsDone(first), planDefinition);
27
+ return [confirmDefinition];
28
+ }
29
+ }, false, 0);
30
+ }
31
+ /**
32
+ * Creates command aborted handler
33
+ */
34
+ export function createCommandAbortedHandler(handleAborted) {
35
+ return () => {
36
+ handleAborted('Request');
37
+ };
38
+ }
@@ -0,0 +1,39 @@
1
+ import { ComponentName, FeedbackType } from '../types/types.js';
2
+ import { createAnthropicService, } from '../services/anthropic.js';
3
+ import { createCommandDefinition, createFeedback, markAsDone, } from '../services/components.js';
4
+ import { saveAnthropicConfig } from '../services/configuration.js';
5
+ import { FeedbackMessages } from '../services/messages.js';
6
+ import { exitApp } from '../services/process.js';
7
+ import { withQueueHandler } from '../services/queue.js';
8
+ /**
9
+ * Creates config finished handler
10
+ */
11
+ export function createConfigFinishedHandler(addToTimeline, command, handleCommandError, handleCommandComplete, handleCommandAborted, setService) {
12
+ return (config) => {
13
+ const anthropicConfig = config;
14
+ saveAnthropicConfig(anthropicConfig);
15
+ const newService = createAnthropicService(anthropicConfig);
16
+ setService(newService);
17
+ return withQueueHandler(ComponentName.Config, (first, rest) => {
18
+ addToTimeline(markAsDone(first), createFeedback(FeedbackType.Succeeded, FeedbackMessages.ConfigurationComplete));
19
+ // Add command to queue if we have one
20
+ if (command) {
21
+ return [
22
+ ...rest,
23
+ createCommandDefinition(command, newService, handleCommandError, handleCommandComplete, handleCommandAborted),
24
+ ];
25
+ }
26
+ // No command - exit after showing completion message
27
+ exitApp(0);
28
+ return rest;
29
+ }, false, 0);
30
+ };
31
+ }
32
+ /**
33
+ * Creates config aborted handler
34
+ */
35
+ export function createConfigAbortedHandler(handleAborted) {
36
+ return () => {
37
+ handleAborted('Configuration');
38
+ };
39
+ }
@@ -0,0 +1,68 @@
1
+ import { ComponentName, FeedbackType, TaskType } from '../types/types.js';
2
+ import { createAnswerDefinition, createFeedback, createIntrospectDefinition, markAsDone, } from '../services/components.js';
3
+ import { getCancellationMessage } from '../services/messages.js';
4
+ import { exitApp } from '../services/process.js';
5
+ import { withQueueHandler } from '../services/queue.js';
6
+ /**
7
+ * Creates execution confirmed handler
8
+ */
9
+ export function createExecutionConfirmedHandler(timelineRef, addToTimeline, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted, handleAnswerError, handleAnswerComplete, handleAnswerAborted) {
10
+ return () => withQueueHandler(ComponentName.Confirm, (first) => {
11
+ // Find the most recent Plan in timeline to get tasks
12
+ const currentTimeline = timelineRef.current;
13
+ const lastPlanIndex = [...currentTimeline]
14
+ .reverse()
15
+ .findIndex((item) => item.name === ComponentName.Plan);
16
+ const lastPlan = lastPlanIndex >= 0
17
+ ? currentTimeline[currentTimeline.length - 1 - lastPlanIndex]
18
+ : null;
19
+ const tasks = lastPlan?.name === ComponentName.Plan &&
20
+ Array.isArray(lastPlan.props.tasks)
21
+ ? lastPlan.props.tasks
22
+ : [];
23
+ const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
24
+ const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
25
+ if (allIntrospect && tasks.length > 0) {
26
+ // Execute introspection
27
+ addToTimeline(markAsDone(first));
28
+ return [
29
+ createIntrospectDefinition(tasks, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted),
30
+ ];
31
+ }
32
+ else if (allAnswer && tasks.length > 0) {
33
+ // Execute answer - extract question from first task
34
+ const question = tasks[0].action;
35
+ addToTimeline(markAsDone(first));
36
+ return [
37
+ createAnswerDefinition(question, service, handleAnswerError, handleAnswerComplete, handleAnswerAborted),
38
+ ];
39
+ }
40
+ else {
41
+ // Regular execution - just exit for now
42
+ addToTimeline(markAsDone(first));
43
+ exitApp(0);
44
+ return [];
45
+ }
46
+ });
47
+ }
48
+ /**
49
+ * Creates execution cancelled handler
50
+ */
51
+ export function createExecutionCancelledHandler(timelineRef, addToTimeline) {
52
+ return () => withQueueHandler(ComponentName.Confirm, (first) => {
53
+ // Find the most recent Plan in timeline to check task types
54
+ const currentTimeline = timelineRef.current;
55
+ const lastPlanIndex = [...currentTimeline]
56
+ .reverse()
57
+ .findIndex((item) => item.name === ComponentName.Plan);
58
+ const lastPlan = lastPlanIndex >= 0
59
+ ? currentTimeline[currentTimeline.length - 1 - lastPlanIndex]
60
+ : null;
61
+ const allIntrospect = lastPlan?.name === ComponentName.Plan &&
62
+ Array.isArray(lastPlan.props.tasks) &&
63
+ lastPlan.props.tasks.every((task) => task.type === TaskType.Introspect);
64
+ const operation = allIntrospect ? 'introspection' : 'execution';
65
+ addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, getCancellationMessage(operation)));
66
+ return undefined;
67
+ }, true, 0);
68
+ }
@@ -0,0 +1,28 @@
1
+ import { ComponentName } from '../types/types.js';
2
+ import { createReportDefinition } from '../services/components.js';
3
+ import { createErrorHandler, withQueueHandler } from '../services/queue.js';
4
+ /**
5
+ * Creates introspect error handler
6
+ */
7
+ export function createIntrospectErrorHandler(addToTimeline) {
8
+ return (error) => createErrorHandler(ComponentName.Introspect, addToTimeline)(error);
9
+ }
10
+ /**
11
+ * Creates introspect completion handler
12
+ */
13
+ export function createIntrospectCompleteHandler(addToTimeline) {
14
+ return (message, capabilities) => withQueueHandler(ComponentName.Introspect, () => {
15
+ // Don't add the Introspect component to timeline (it renders null)
16
+ // Only add the Report component
17
+ addToTimeline(createReportDefinition(message, capabilities));
18
+ return undefined;
19
+ }, true, 0);
20
+ }
21
+ /**
22
+ * Creates introspect aborted handler
23
+ */
24
+ export function createIntrospectAbortedHandler(handleAborted) {
25
+ return () => {
26
+ handleAborted('Introspection');
27
+ };
28
+ }
@@ -0,0 +1,82 @@
1
+ import { ComponentName, FeedbackType, TaskType } from '../types/types.js';
2
+ import { createConfirmDefinition, createFeedback, createPlanDefinition, markAsDone, createRefinement, } from '../services/components.js';
3
+ import { FeedbackMessages, getRefiningMessage } from '../services/messages.js';
4
+ import { exitApp } from '../services/process.js';
5
+ /**
6
+ * Creates plan aborted handler
7
+ */
8
+ export function createPlanAbortedHandler(handleAborted) {
9
+ return () => {
10
+ handleAborted('Task selection');
11
+ };
12
+ }
13
+ /**
14
+ * Creates plan abort handler factory
15
+ */
16
+ export function createPlanAbortHandlerFactory(handleAborted, handlePlanAborted) {
17
+ return (tasks) => {
18
+ const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
19
+ if (allIntrospect) {
20
+ return () => {
21
+ handleAborted('Introspection');
22
+ };
23
+ }
24
+ return handlePlanAborted;
25
+ };
26
+ }
27
+ /**
28
+ * Creates plan selection confirmed handler
29
+ */
30
+ export function createPlanSelectionConfirmedHandler(addToTimeline, service, handleRefinementAborted, createPlanAbortHandler, handleExecutionConfirmed, handleExecutionCancelled, setQueue) {
31
+ return async (selectedTasks) => {
32
+ // Mark current plan as done and add refinement to queue
33
+ const refinementDef = createRefinement(getRefiningMessage(), handleRefinementAborted);
34
+ setQueue((currentQueue) => {
35
+ if (currentQueue.length === 0)
36
+ return currentQueue;
37
+ const [first] = currentQueue;
38
+ if (first.name === ComponentName.Plan) {
39
+ addToTimeline(markAsDone(first));
40
+ }
41
+ // Add refinement to queue so it becomes the active component
42
+ return [refinementDef];
43
+ });
44
+ // Process refined command in background
45
+ try {
46
+ const refinedCommand = selectedTasks
47
+ .map((task) => {
48
+ const action = task.action.toLowerCase().replace(/,/g, ' -');
49
+ const type = task.type;
50
+ return `${action} (type: ${type})`;
51
+ })
52
+ .join(', ');
53
+ const result = await service.processWithTool(refinedCommand, 'plan');
54
+ // Mark refinement as done and move to timeline
55
+ setQueue((currentQueue) => {
56
+ if (currentQueue.length > 0 &&
57
+ currentQueue[0].id === refinementDef.id) {
58
+ addToTimeline(markAsDone(currentQueue[0]));
59
+ }
60
+ return [];
61
+ });
62
+ // Show final execution plan with confirmation
63
+ const planDefinition = createPlanDefinition(result.message, result.tasks, createPlanAbortHandler(result.tasks), undefined);
64
+ const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
65
+ addToTimeline(planDefinition);
66
+ setQueue([confirmDefinition]);
67
+ }
68
+ catch (error) {
69
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
70
+ // Mark refinement as done and move to timeline before showing error
71
+ setQueue((currentQueue) => {
72
+ if (currentQueue.length > 0 &&
73
+ currentQueue[0].id === refinementDef.id) {
74
+ addToTimeline(markAsDone(currentQueue[0]));
75
+ }
76
+ return [];
77
+ });
78
+ addToTimeline(createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, errorMessage));
79
+ exitApp(1);
80
+ }
81
+ };
82
+ }
@@ -40,8 +40,25 @@ export class AnthropicService {
40
40
  throw new Error('Expected tool_use response from Claude API');
41
41
  }
42
42
  const content = response.content[0];
43
- // Extract and validate message and tasks
43
+ // Extract and validate response based on tool type
44
44
  const input = content.input;
45
+ const isDebug = process.env.DEBUG === 'true';
46
+ // Handle answer tool response
47
+ if (toolName === 'answer') {
48
+ if (!input.question || typeof input.question !== 'string') {
49
+ throw new Error('Invalid tool response: missing or invalid question field');
50
+ }
51
+ if (!input.answer || typeof input.answer !== 'string') {
52
+ throw new Error('Invalid tool response: missing or invalid answer field');
53
+ }
54
+ return {
55
+ message: '',
56
+ tasks: [],
57
+ answer: input.answer,
58
+ systemPrompt: isDebug ? systemPrompt : undefined,
59
+ };
60
+ }
61
+ // Handle plan and introspect tool responses
45
62
  if (!input.message || typeof input.message !== 'string') {
46
63
  throw new Error('Invalid tool response: missing or invalid message field');
47
64
  }
@@ -54,7 +71,6 @@ export class AnthropicService {
54
71
  throw new Error(`Invalid task at index ${String(i)}: missing or invalid 'action' field`);
55
72
  }
56
73
  });
57
- const isDebug = process.env.DEBUG === 'true';
58
74
  return {
59
75
  message: input.message,
60
76
  tasks: input.tasks,
@@ -1,9 +1,13 @@
1
- import { FeedbackType, TaskType } from './types.js';
1
+ import { FeedbackType, TaskType } from '../types/types.js';
2
2
  /**
3
3
  * Semantic color palette - colors organized by their purpose/meaning.
4
4
  * Prefer adding semantic names here rather than to DescriptiveColors.
5
5
  */
6
6
  export const Colors = {
7
+ Text: {
8
+ Active: '#ffffff', // white
9
+ Inactive: '#d0d0d0', // ash gray
10
+ },
7
11
  Action: {
8
12
  Execute: '#5aaa8a', // green
9
13
  Discard: '#a85c3f', // dark orange
@@ -16,7 +20,7 @@ export const Colors = {
16
20
  Info: '#5c9ccc', // cyan
17
21
  },
18
22
  Label: {
19
- Default: '#ffffff', // white
23
+ Default: null, // replaced with active or inactive
20
24
  Inactive: '#888888', // gray
21
25
  Discarded: '#666666', // dark gray
22
26
  Skipped: '#cccc5c', // yellow
@@ -35,9 +39,9 @@ export const Colors = {
35
39
  },
36
40
  };
37
41
  /**
38
- * Task-specific color mappings
42
+ * Task-specific color mappings (internal)
39
43
  */
40
- export const TaskColors = {
44
+ const TaskColors = {
41
45
  [TaskType.Config]: {
42
46
  description: Colors.Label.Default,
43
47
  type: Colors.Type.Config,
@@ -80,11 +84,59 @@ export const TaskColors = {
80
84
  },
81
85
  };
82
86
  /**
83
- * Feedback-specific color mappings
87
+ * Feedback-specific color mappings (internal)
84
88
  */
85
- export const FeedbackColors = {
89
+ const FeedbackColors = {
86
90
  [FeedbackType.Info]: Colors.Status.Info,
87
91
  [FeedbackType.Succeeded]: Colors.Status.Success,
88
92
  [FeedbackType.Aborted]: Colors.Status.Warning,
89
93
  [FeedbackType.Failed]: Colors.Status.Error,
90
94
  };
95
+ /**
96
+ * Process null color values based on current/historical state.
97
+ *
98
+ * Replaces null with:
99
+ * - Colors.Text.Active for current items
100
+ * - Colors.Text.Inactive (undefined) for historical items
101
+ */
102
+ function processColor(color, isCurrent) {
103
+ return color === null
104
+ ? isCurrent
105
+ ? Colors.Text.Active
106
+ : Colors.Text.Inactive
107
+ : color;
108
+ }
109
+ /**
110
+ * Get task colors with current/historical state handling.
111
+ *
112
+ * Processes null color values (terminal default) and replaces them with:
113
+ * - Colors.Text.Inactive (undefined) for historical items
114
+ * - Colors.Text.Active for current items
115
+ */
116
+ export function getTaskColors(type, isCurrent) {
117
+ const colors = TaskColors[type];
118
+ return {
119
+ description: processColor(colors.description, isCurrent),
120
+ type: processColor(colors.type, isCurrent),
121
+ };
122
+ }
123
+ /**
124
+ * Get feedback color with current/historical state handling.
125
+ *
126
+ * Processes null color values (terminal default) and replaces them with:
127
+ * - Colors.Text.Inactive (undefined) for historical items
128
+ * - Colors.Text.Active for current items
129
+ */
130
+ export function getFeedbackColor(type, isCurrent) {
131
+ return processColor(FeedbackColors[type], isCurrent);
132
+ }
133
+ /**
134
+ * Get text color based on current/historical state.
135
+ *
136
+ * Returns:
137
+ * - Colors.Text.Active for current items
138
+ * - Colors.Text.Inactive for historical items
139
+ */
140
+ export function getTextColor(isCurrent) {
141
+ return isCurrent ? Colors.Text.Active : Colors.Text.Inactive;
142
+ }