prompt-language-shell 0.4.4 → 0.4.8

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,66 @@
1
+ ## Overview
2
+
3
+ You are the CONFIG tool for "pls" (please), a professional command-line concierge.
4
+ Your role is to determine which configuration settings the user wants to configure
5
+ based on their query.
6
+
7
+ ## Input
8
+
9
+ You will receive:
10
+ - `configStructure`: Object mapping config keys to descriptions (e.g., {"anthropic.key": "Anthropic API key"})
11
+ - `query`: User's request (e.g., "app", "mode", "anthropic", or empty)
12
+
13
+ ## Task
14
+
15
+ Determine which config keys the user wants to configure and return them as tasks.
16
+
17
+ ## Mapping Rules
18
+
19
+ ### Query: "app" or empty/unclear
20
+ - Return all **required** config keys (those needed for the app to work)
21
+ - Also include any **optional** config keys that are marked as "(discovered)" (they exist in user's config file)
22
+ - Required keys: `anthropic.key`, `anthropic.model`
23
+
24
+ ### Query: "mode"
25
+ - Return only: `settings.debug`
26
+
27
+ ### Query: "anthropic"
28
+ - Return all keys starting with `anthropic.` (usually `anthropic.key` and `anthropic.model`)
29
+
30
+ ### Other queries
31
+ - Match the query against config key names and descriptions
32
+ - Return keys that seem relevant to the query
33
+ - If unclear, return only required keys
34
+
35
+ ## Response Format
36
+
37
+ ```json
38
+ {
39
+ "message": "Brief intro message ending with period.",
40
+ "tasks": [
41
+ {
42
+ "action": "Anthropic API key",
43
+ "type": "config",
44
+ "params": {
45
+ "key": "anthropic.key"
46
+ }
47
+ },
48
+ {
49
+ "action": "Model",
50
+ "type": "config",
51
+ "params": {
52
+ "key": "anthropic.model"
53
+ }
54
+ }
55
+ ]
56
+ }
57
+ ```
58
+
59
+ ## Important
60
+
61
+ - Use the exact config keys from `configStructure`
62
+ - Use the descriptions from `configStructure` as the action text
63
+ - Always use type "config"
64
+ - Always include the key in params
65
+ - Keep message concise (≤64 characters)
66
+ - Return at least one task (required keys if unsure)
@@ -53,21 +53,36 @@ Every response MUST include an introductory message before the capability list.
53
53
 
54
54
  ## Capabilities Structure
55
55
 
56
- Present capabilities in two categories:
56
+ **⚠️ CRITICAL ORDERING REQUIREMENT ⚠️**
57
57
 
58
- ### 1. Built-in Capabilities
58
+ You MUST present capabilities in the EXACT order specified below. This is
59
+ NON-NEGOTIABLE and applies to EVERY response.
59
60
 
60
- These are the core operations available to all users:
61
+ **DO NOT:**
62
+ - Reorder capabilities based on alphabetical sorting
63
+ - Put Plan or Report first (this is WRONG)
64
+ - Rearrange based on perceived importance
65
+ - Deviate from this order for any reason
61
66
 
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
67
+ **CORRECT ORDER - FOLLOW EXACTLY:**
69
68
 
70
- ### 2. User-Defined Skills
69
+ ### Position 1-4: Built-in Capabilities (Direct User Operations)
70
+
71
+ These MUST appear FIRST, in this EXACT sequence:
72
+
73
+ 1. **Introspect** ← ALWAYS FIRST
74
+ 2. **Config** ← ALWAYS SECOND
75
+ 3. **Answer** ← ALWAYS THIRD
76
+ 4. **Execute** ← ALWAYS FOURTH
77
+
78
+ ### Position 5-6: Indirect Workflow Capabilities
79
+
80
+ These MUST appear AFTER Execute and BEFORE user skills:
81
+
82
+ 5. **Plan** ← NEVER FIRST, ALWAYS position 5 (after Execute)
83
+ 6. **Report** ← NEVER FIRST, ALWAYS position 6 (after Plan)
84
+
85
+ ### 3. User-Defined Skills
71
86
 
72
87
  If skills are provided in the "Available Skills" section below, include them
73
88
  in the response. For each skill:
@@ -116,9 +131,9 @@ Examples:
116
131
  ### Example 1: List All Capabilities
117
132
 
118
133
  When user asks "list your skills", create an introductory message like "here
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
121
- "introspect" with an action describing the capability.
134
+ are my capabilities:" followed by tasks for built-in capabilities (Introspect,
135
+ Config, Answer, Execute), then indirect workflow capabilities (Plan, Report).
136
+ Each task uses type "introspect" with an action describing the capability.
122
137
 
123
138
  ### Example 2: Filtered Skills
124
139
 
@@ -422,6 +422,27 @@ When creating task definitions, focus on:
422
422
  Prioritize clarity and precision over brevity. Each task should be unambiguous
423
423
  and executable.
424
424
 
425
+ ## Configuration Requests
426
+
427
+ When the user wants to configure or change settings (e.g., "pls config", "pls configure", "pls change settings", "pls run settings", "pls config anthropic", "pls config mode"), create a SINGLE task with type "config".
428
+
429
+ **Task format:**
430
+ - **action**: "Configure settings" (or similar natural description)
431
+ - **type**: "config"
432
+ - **params**: Include `{ "query": "filter" }` where filter specifies which settings to configure:
433
+ - If command contains specific keywords like "anthropic", "mode", "debug" → use that keyword
434
+ - If command is just "config" or "configure" or "settings" with no specific area → use "app"
435
+ - Extract the relevant context, not the full command
436
+
437
+ **Examples:**
438
+ - User: "pls config anthropic" → `{ "action": "Configure settings", "type": "config", "params": { "query": "anthropic" } }`
439
+ - User: "pls configure" → `{ "action": "Configure settings", "type": "config", "params": { "query": "app" } }`
440
+ - User: "pls run settings" → `{ "action": "Configure settings", "type": "config", "params": { "query": "app" } }`
441
+ - User: "pls config mode" → `{ "action": "Configure settings", "type": "config", "params": { "query": "mode" } }`
442
+ - User: "pls change debug settings" → `{ "action": "Configure settings", "type": "config", "params": { "query": "mode" } }`
443
+
444
+ The CONFIG tool will handle determining which specific config keys to show based on the query.
445
+
425
446
  ## Multiple Tasks
426
447
 
427
448
  When the user provides multiple tasks separated by commas, semicolons, or the
@@ -1,7 +1,7 @@
1
1
  import { ComponentName, FeedbackType } from '../types/types.js';
2
2
  import { createAnthropicService, } from '../services/anthropic.js';
3
3
  import { createCommandDefinition, createFeedback, markAsDone, } from '../services/components.js';
4
- import { saveAnthropicConfig } from '../services/configuration.js';
4
+ import { saveAnthropicConfig, saveConfig } from '../services/configuration.js';
5
5
  import { FeedbackMessages } from '../services/messages.js';
6
6
  import { exitApp } from '../services/process.js';
7
7
  import { withQueueHandler } from '../services/queue.js';
@@ -37,3 +37,44 @@ export function createConfigAbortedHandler(handleAborted) {
37
37
  handleAborted('Configuration');
38
38
  };
39
39
  }
40
+ /**
41
+ * Creates config execution finished handler for CONFIG skill
42
+ * Saves arbitrary config keys and exits
43
+ */
44
+ export function createConfigExecutionFinishedHandler(addToTimeline, keys) {
45
+ return (config) => {
46
+ // Map short keys back to full keys and save
47
+ // Group by section (e.g., "anthropic", "settings")
48
+ const sections = {};
49
+ for (const fullKey of keys) {
50
+ const parts = fullKey.split('.');
51
+ const shortKey = parts[parts.length - 1];
52
+ const section = parts.slice(0, -1).join('.');
53
+ sections[section] = sections[section] ?? {};
54
+ if (shortKey in config) {
55
+ sections[section][shortKey] = config[shortKey];
56
+ }
57
+ }
58
+ // Save each section
59
+ for (const [section, sectionConfig] of Object.entries(sections)) {
60
+ saveConfig(section, sectionConfig);
61
+ }
62
+ return withQueueHandler(ComponentName.Config, (first, rest) => {
63
+ addToTimeline(markAsDone(first), createFeedback(FeedbackType.Succeeded, FeedbackMessages.ConfigurationComplete));
64
+ exitApp(0);
65
+ return rest;
66
+ }, false, 0);
67
+ };
68
+ }
69
+ /**
70
+ * Creates config execution aborted handler for CONFIG skill
71
+ */
72
+ export function createConfigExecutionAbortedHandler(addToTimeline) {
73
+ return () => {
74
+ return withQueueHandler(ComponentName.Config, (first, rest) => {
75
+ addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, 'Configuration cancelled.'));
76
+ exitApp(0);
77
+ return rest;
78
+ }, false, 0);
79
+ };
80
+ }
@@ -1,12 +1,13 @@
1
1
  import { ComponentName, FeedbackType, TaskType } from '../types/types.js';
2
- import { createAnswerDefinition, createFeedback, createIntrospectDefinition, markAsDone, } from '../services/components.js';
2
+ import { createAnswerDefinition, createConfigDefinitionWithKeys, createFeedback, createIntrospectDefinition, markAsDone, } from '../services/components.js';
3
+ import { createConfigExecutionAbortedHandler, createConfigExecutionFinishedHandler, } from './config.js';
3
4
  import { getCancellationMessage } from '../services/messages.js';
4
5
  import { exitApp } from '../services/process.js';
5
6
  import { withQueueHandler } from '../services/queue.js';
6
7
  /**
7
8
  * Creates execution confirmed handler
8
9
  */
9
- export function createExecutionConfirmedHandler(timelineRef, addToTimeline, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted, handleAnswerError, handleAnswerComplete, handleAnswerAborted) {
10
+ export function createExecutionConfirmedHandler(timelineRef, addToTimeline, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted, handleAnswerError, handleAnswerComplete, handleAnswerAborted, setQueue) {
10
11
  return () => withQueueHandler(ComponentName.Confirm, (first) => {
11
12
  // Find the most recent Plan in timeline to get tasks
12
13
  const currentTimeline = timelineRef.current;
@@ -22,6 +23,7 @@ export function createExecutionConfirmedHandler(timelineRef, addToTimeline, serv
22
23
  : [];
23
24
  const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
24
25
  const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
26
+ const allConfig = tasks.every((task) => task.type === TaskType.Config);
25
27
  if (allIntrospect && tasks.length > 0) {
26
28
  // Execute introspection
27
29
  addToTimeline(markAsDone(first));
@@ -37,6 +39,24 @@ export function createExecutionConfirmedHandler(timelineRef, addToTimeline, serv
37
39
  createAnswerDefinition(question, service, handleAnswerError, handleAnswerComplete, handleAnswerAborted),
38
40
  ];
39
41
  }
42
+ else if (allConfig && tasks.length > 0) {
43
+ // Execute config - extract keys from task params
44
+ const keys = tasks
45
+ .map((task) => task.params?.key)
46
+ .filter((key) => typeof key === 'string');
47
+ addToTimeline(markAsDone(first));
48
+ // Create handlers with keys for proper saving
49
+ // Wrap in setQueue to properly update queue when Config finishes
50
+ const handleConfigFinished = (config) => {
51
+ setQueue(createConfigExecutionFinishedHandler(addToTimeline, keys)(config));
52
+ };
53
+ const handleConfigAborted = () => {
54
+ setQueue(createConfigExecutionAbortedHandler(addToTimeline)());
55
+ };
56
+ return [
57
+ createConfigDefinitionWithKeys(keys, handleConfigFinished, handleConfigAborted),
58
+ ];
59
+ }
40
60
  else {
41
61
  // Regular execution - just exit for now
42
62
  addToTimeline(markAsDone(first));
@@ -1,6 +1,6 @@
1
1
  import { ComponentName, FeedbackType, TaskType } from '../types/types.js';
2
2
  import { createConfirmDefinition, createFeedback, createPlanDefinition, markAsDone, createRefinement, } from '../services/components.js';
3
- import { FeedbackMessages, getRefiningMessage } from '../services/messages.js';
3
+ import { FeedbackMessages, formatErrorMessage, getRefiningMessage, } from '../services/messages.js';
4
4
  import { exitApp } from '../services/process.js';
5
5
  /**
6
6
  * Creates plan aborted handler
@@ -66,7 +66,7 @@ export function createPlanSelectionConfirmedHandler(addToTimeline, service, hand
66
66
  setQueue([confirmDefinition]);
67
67
  }
68
68
  catch (error) {
69
- const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
69
+ const errorMessage = formatErrorMessage(error);
70
70
  // Mark refinement as done and move to timeline before showing error
71
71
  setQueue((currentQueue) => {
72
72
  if (currentQueue.length > 0 &&
@@ -1,4 +1,5 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
+ import { getAvailableConfigStructure, } from './configuration.js';
2
3
  import { formatSkillsForPrompt, loadSkills } from './skills.js';
3
4
  import { toolRegistry } from './tool-registry.js';
4
5
  export class AnthropicService {
@@ -12,16 +13,36 @@ export class AnthropicService {
12
13
  // Load tool from registry
13
14
  const tool = toolRegistry.getSchema(toolName);
14
15
  const instructions = toolRegistry.getInstructions(toolName);
15
- // Load skills and augment the instructions
16
- const skills = loadSkills();
17
- const skillsSection = formatSkillsForPrompt(skills);
18
- const systemPrompt = instructions + skillsSection;
16
+ // Build system prompt with additional context based on tool
17
+ let systemPrompt = instructions;
18
+ // Add skills section for applicable tools
19
+ if (toolName === 'plan' || toolName === 'introspect') {
20
+ const skills = loadSkills();
21
+ const skillsSection = formatSkillsForPrompt(skills);
22
+ systemPrompt += skillsSection;
23
+ }
24
+ // Add config structure for config tool only
25
+ if (toolName === 'config') {
26
+ const configStructure = getAvailableConfigStructure();
27
+ const configSection = '\n\n## Available Configuration\n\n' +
28
+ 'Config structure (key: description):\n' +
29
+ JSON.stringify(configStructure, null, 2);
30
+ systemPrompt += configSection;
31
+ }
32
+ // Build tools array - add web search for answer tool
33
+ const tools = [tool];
34
+ if (toolName === 'answer') {
35
+ tools.push({
36
+ type: 'web_search_20250305',
37
+ name: 'web_search',
38
+ });
39
+ }
19
40
  // Call API with tool
20
41
  const response = await this.client.messages.create({
21
42
  model: this.model,
22
43
  max_tokens: 1024,
23
44
  system: systemPrompt,
24
- tools: [tool],
45
+ tools,
25
46
  tool_choice: { type: 'any' },
26
47
  messages: [
27
48
  {
@@ -34,15 +55,25 @@ export class AnthropicService {
34
55
  if (response.stop_reason === 'max_tokens') {
35
56
  throw new Error('Response was truncated due to length. Please simplify your request or break it into smaller parts.');
36
57
  }
37
- // Validate response structure
38
- if (response.content.length === 0 ||
39
- response.content[0].type !== 'tool_use') {
58
+ // Find tool_use block in response (may not be first with web search)
59
+ const toolUseContent = response.content.find((block) => block.type === 'tool_use');
60
+ // For answer tool with web search, model might return text directly
61
+ if (toolName === 'answer' && !toolUseContent) {
62
+ const textContent = response.content.find((block) => block.type === 'text');
63
+ if (textContent) {
64
+ return {
65
+ message: '',
66
+ tasks: [],
67
+ answer: textContent.text,
68
+ };
69
+ }
70
+ }
71
+ if (!toolUseContent) {
40
72
  throw new Error('Expected tool_use response from Claude API');
41
73
  }
42
- const content = response.content[0];
74
+ const content = toolUseContent;
43
75
  // Extract and validate response based on tool type
44
76
  const input = content.input;
45
- const isDebug = process.env.DEBUG === 'true';
46
77
  // Handle answer tool response
47
78
  if (toolName === 'answer') {
48
79
  if (!input.question || typeof input.question !== 'string') {
@@ -55,7 +86,6 @@ export class AnthropicService {
55
86
  message: '',
56
87
  tasks: [],
57
88
  answer: input.answer,
58
- systemPrompt: isDebug ? systemPrompt : undefined,
59
89
  };
60
90
  }
61
91
  // Handle plan and introspect tool responses
@@ -74,7 +104,6 @@ export class AnthropicService {
74
104
  return {
75
105
  message: input.message,
76
106
  tasks: input.tasks,
77
- systemPrompt: isDebug ? systemPrompt : undefined,
78
107
  };
79
108
  }
80
109
  }
@@ -1,47 +1,81 @@
1
1
  import { FeedbackType, TaskType } from '../types/types.js';
2
+ /**
3
+ * Base color palette - raw color values with descriptive names.
4
+ * All colors used in the interface are defined here.
5
+ */
6
+ export const Palette = {
7
+ White: '#ffffff',
8
+ AshGray: '#d0d0d0',
9
+ PaleGreen: '#a8dcbc',
10
+ Gray: '#888888',
11
+ DarkGray: '#666666',
12
+ CharcoalGray: '#282828',
13
+ Green: '#5aaa8a',
14
+ LightGreen: '#65b595',
15
+ BrightGreen: '#22aa22',
16
+ Yellow: '#cccc5c',
17
+ LightYellow: '#d4d47a',
18
+ Orange: '#cc9c5c',
19
+ DarkOrange: '#a85c3f',
20
+ BurntOrange: '#cc7a5c',
21
+ Red: '#cc5c5c',
22
+ Cyan: '#5c9ccc',
23
+ LightCyan: '#5ccccc',
24
+ SteelBlue: '#5c8cbc',
25
+ Purple: '#9c5ccc',
26
+ };
2
27
  /**
3
28
  * Semantic color palette - colors organized by their purpose/meaning.
4
- * Prefer adding semantic names here rather than to DescriptiveColors.
29
+ * References Palette colors to maintain consistency.
5
30
  */
6
31
  export const Colors = {
7
32
  Text: {
8
- Active: '#ffffff', // white
9
- Inactive: '#d0d0d0', // ash gray
33
+ Active: Palette.White,
34
+ Inactive: Palette.AshGray,
35
+ UserQuery: Palette.White,
36
+ },
37
+ Background: {
38
+ UserQuery: Palette.CharcoalGray,
10
39
  },
11
40
  Action: {
12
- Execute: '#5aaa8a', // green
13
- Discard: '#a85c3f', // dark orange
14
- Select: '#5c8cbc', // steel blue
41
+ Execute: Palette.Green,
42
+ Discard: Palette.DarkOrange,
43
+ Select: Palette.SteelBlue,
15
44
  },
16
45
  Status: {
17
- Success: '#22aa22', // green
18
- Error: '#cc5c5c', // red
19
- Warning: '#cc9c5c', // orange
20
- Info: '#5c9ccc', // cyan
46
+ Success: Palette.BrightGreen,
47
+ Error: Palette.Red,
48
+ Warning: Palette.Orange,
49
+ Info: Palette.Cyan,
21
50
  },
22
51
  Label: {
23
- Default: null, // replaced with active or inactive
24
- Inactive: '#888888', // gray
25
- Discarded: '#666666', // dark gray
26
- Skipped: '#cccc5c', // yellow
52
+ Default: null, // calculated in runtime
53
+ Inactive: Palette.Gray,
54
+ Discarded: Palette.DarkGray,
55
+ Skipped: Palette.Yellow,
27
56
  },
28
57
  Type: {
29
- Config: '#5c9ccc', // cyan
30
- Plan: '#5ccccc', // magenta
31
- Execute: '#5aaa8a', // green
32
- Answer: '#9c5ccc', // purple
33
- Introspect: '#9c5ccc', // purple
34
- Report: '#cc9c5c', // orange
35
- Define: '#cc9c5c', // amber
36
- Ignore: '#cc7a5c', // dark orange
37
- Select: '#5c8cbc', // steel blue
38
- Discard: '#a85c3f', // dark orange
58
+ Config: Palette.Cyan,
59
+ Plan: Palette.LightCyan,
60
+ Execute: Palette.Green,
61
+ Answer: Palette.Purple,
62
+ Introspect: Palette.Purple,
63
+ Report: Palette.Orange,
64
+ Define: Palette.Orange,
65
+ Ignore: Palette.BurntOrange,
66
+ Select: Palette.SteelBlue,
67
+ Discard: Palette.DarkOrange,
68
+ },
69
+ Origin: {
70
+ BuiltIn: Palette.Cyan,
71
+ UserProvided: Palette.Green,
72
+ Indirect: Palette.Purple,
39
73
  },
40
74
  };
41
75
  /**
42
76
  * Task-specific color mappings (internal)
43
77
  */
44
- const TaskColors = {
78
+ const taskColors = {
45
79
  [TaskType.Config]: {
46
80
  description: Colors.Label.Default,
47
81
  type: Colors.Type.Config,
@@ -86,7 +120,7 @@ const TaskColors = {
86
120
  /**
87
121
  * Feedback-specific color mappings (internal)
88
122
  */
89
- const FeedbackColors = {
123
+ const feedbackColors = {
90
124
  [FeedbackType.Info]: Colors.Status.Info,
91
125
  [FeedbackType.Succeeded]: Colors.Status.Success,
92
126
  [FeedbackType.Aborted]: Colors.Status.Warning,
@@ -114,7 +148,7 @@ function processColor(color, isCurrent) {
114
148
  * - Colors.Text.Active for current items
115
149
  */
116
150
  export function getTaskColors(type, isCurrent) {
117
- const colors = TaskColors[type];
151
+ const colors = taskColors[type];
118
152
  return {
119
153
  description: processColor(colors.description, isCurrent),
120
154
  type: processColor(colors.type, isCurrent),
@@ -128,7 +162,7 @@ export function getTaskColors(type, isCurrent) {
128
162
  * - Colors.Text.Active for current items
129
163
  */
130
164
  export function getFeedbackColor(type, isCurrent) {
131
- return processColor(FeedbackColors[type], isCurrent);
165
+ return processColor(feedbackColors[type], isCurrent);
132
166
  }
133
167
  /**
134
168
  * Get text color based on current/historical state.