prompt-language-shell 0.1.2 → 0.1.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.
@@ -15,6 +15,38 @@ preserving the original intent. Apply minimal necessary changes to achieve
15
15
  optimal clarity. The refined output will be used to plan and execute real
16
16
  operations, so precision and unambiguous language are essential.
17
17
 
18
+ ## Skills Integration
19
+
20
+ If skills are provided in the "Available Skills" section below, you MUST
21
+ use them when the user's query matches a skill's domain.
22
+
23
+ When a query matches a skill:
24
+ 1. Recognize the semantic match between the user's request and the skill
25
+ description
26
+ 2. Extract the individual steps from the skill's "Steps" section
27
+ 3. Refine each step into clear, professional task descriptions that start
28
+ with a capital letter like a sentence
29
+ 4. Return each step as a separate task in a JSON array
30
+ 5. If the user's query includes additional requirements beyond the skill,
31
+ append those as additional tasks
32
+ 6. NEVER replace the skill's detailed steps with a generic restatement of
33
+ the user's request
34
+
35
+ Example 1:
36
+ - Skill has steps: "- Navigate to the project directory. - Run the build
37
+ script - Execute the test suite"
38
+ - User asks: "test the application"
39
+ - Correct output: ["Navigate to the project directory", "Run the build
40
+ script", "Execute the test suite"]
41
+ - WRONG output: ["test the application"]
42
+
43
+ Example 2:
44
+ - Skill has steps: "- Navigate to the project directory. - Run the build
45
+ script - Execute the test suite"
46
+ - User asks: "test the application and generate a report"
47
+ - Correct output: ["Navigate to the project directory", "Run the build
48
+ script", "Execute the test suite", "Generate a report"]
49
+
18
50
  ## Evaluation of Requests
19
51
 
20
52
  Before processing any request, evaluate its nature and respond appropriately:
@@ -35,18 +67,41 @@ If the request is too vague or unclear to understand what action should be
35
67
  taken, return the exact phrase "abort unclear request".
36
68
 
37
69
  Before marking a request as unclear, try to infer meaning from:
70
+ - **Available skills**: If a skill is provided that narrows down a domain,
71
+ use that context to interpret the request. Skills define the scope of what
72
+ generic terms mean in a specific context. When a user says "all X" or
73
+ "the Y", check if an available skill defines what X or Y means. For example,
74
+ if a skill defines specific deployment environments for a project, then
75
+ "deploy to all environments" should be interpreted within that skill's
76
+ context, not as a generic unclear request.
38
77
  - Common abbreviations and acronyms in technical contexts
39
78
  - Well-known product names, tools, or technologies
40
79
  - Context clues within the request itself
41
80
  - Standard industry terminology
42
81
 
43
- For example:
44
- - "test GX" "GX" possibly means Opera GX browser
82
+ For example using skills context:
83
+ - "build all applications" + build skill defining mobile, desktop, and web
84
+ applications → interpret as those three specific applications
85
+ - "deploy to all environments" + deployment skill defining staging, production,
86
+ and canary → interpret as those three specific environments
87
+ - "run all test suites" + testing skill listing unit and integration tests →
88
+ interpret as those two specific test types
89
+ - "build the package" + monorepo skill defining a single backend package →
90
+ interpret as that one specific package
91
+ - "check all services" + microservices skill listing auth, api, and database
92
+ services → interpret as those three specific services
93
+ - "run both compilers" + build skill defining TypeScript and Sass compilers →
94
+ interpret as those two specific compilers
95
+ - "start the server" + infrastructure skill defining a single Node.js server →
96
+ interpret as that one specific server
97
+
98
+ For example using common context:
45
99
  - "run TS compiler" → "TS" stands for TypeScript
46
100
  - "open VSC" → "VSC" likely means Visual Studio Code
101
+ - "run unit tests" → standard development terminology for testing
47
102
 
48
103
  Only mark as unclear if the request is truly unintelligible or lacks any
49
- discernible intent.
104
+ discernible intent, even after considering available skills and context.
50
105
 
51
106
  Examples that are too vague:
52
107
  - "do stuff"
@@ -168,7 +223,9 @@ Split into multiple tasks when:
168
223
  - Single task: Return ONLY the corrected command text
169
224
  - Multiple tasks: Return ONLY a JSON array of strings
170
225
 
171
- Do not include explanations, commentary, or any other text.
226
+ Do not include explanations, commentary, markdown formatting, code blocks, or
227
+ any other text. For JSON arrays, return the raw JSON without ```json``` or
228
+ any other wrapping.
172
229
 
173
230
  ## Final Validation Before Response
174
231
 
package/dist/index.js CHANGED
@@ -6,8 +6,7 @@ import { dirname, join } from 'path';
6
6
  import { render, Text } from 'ink';
7
7
  import { loadConfig, ConfigError, configExists, saveConfig, } from './services/config.js';
8
8
  import { createAnthropicService } from './services/anthropic.js';
9
- import { Please } from './ui/Please.js';
10
- import { ConfigThenCommand } from './ui/ConfigThenCommand.js';
9
+ import { PLS } from './ui/Please.js';
11
10
  const __filename = fileURLToPath(import.meta.url);
12
11
  const __dirname = dirname(__filename);
13
12
  // Get package info
@@ -28,33 +27,26 @@ const appInfo = {
28
27
  const args = process.argv.slice(2);
29
28
  const rawCommand = args.join(' ').trim();
30
29
  async function runApp() {
31
- // Check if config exists, if not run setup
30
+ // First-time setup: config doesn't exist
32
31
  if (!configExists()) {
33
- if (!rawCommand) {
34
- // "pls" for the first time: show welcome box and ask about config below
35
- const { waitUntilExit } = render(_jsx(Please, { app: appInfo, showConfigSetup: true, onConfigComplete: ({ apiKey, model }) => {
36
- saveConfig(apiKey, model);
37
- } }));
38
- await waitUntilExit();
39
- return;
40
- }
41
- else {
42
- // "pls do stuff" for the first time: ask about config, then continue
43
- render(_jsx(ConfigThenCommand, { command: rawCommand, onConfigSave: saveConfig }));
44
- return;
45
- }
32
+ const { waitUntilExit } = render(_jsx(PLS, { app: appInfo, command: rawCommand || null, showConfigSetup: true, onConfigComplete: ({ apiKey, model }) => {
33
+ saveConfig(apiKey, model);
34
+ return rawCommand ? createAnthropicService(apiKey, model) : undefined;
35
+ } }));
36
+ await waitUntilExit();
37
+ return;
46
38
  }
47
39
  // Try to load and validate config
48
40
  try {
49
41
  const config = loadConfig();
50
42
  if (!rawCommand) {
51
43
  // "pls" when config present: show welcome box
52
- render(_jsx(Please, { app: appInfo }));
44
+ render(_jsx(PLS, { app: appInfo, command: null }));
53
45
  }
54
46
  else {
55
47
  // "pls do stuff": fetch and show the plan
56
48
  const claudeService = createAnthropicService(config.anthropic.apiKey, config.anthropic.model);
57
- render(_jsx(Please, { app: appInfo, command: rawCommand, claudeService: claudeService }));
49
+ render(_jsx(PLS, { app: appInfo, command: rawCommand, claudeService: claudeService }));
58
50
  }
59
51
  }
60
52
  catch (error) {
@@ -2,6 +2,7 @@ import { readFileSync } from 'fs';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { dirname, join } from 'path';
4
4
  import Anthropic from '@anthropic-ai/sdk';
5
+ import { loadSkills, formatSkillsForPrompt } from './skills.js';
5
6
  const __filename = fileURLToPath(import.meta.url);
6
7
  const __dirname = dirname(__filename);
7
8
  const PLAN_PROMPT = readFileSync(join(__dirname, '../config/PLAN.md'), 'utf-8');
@@ -13,10 +14,14 @@ export class AnthropicService {
13
14
  this.model = model;
14
15
  }
15
16
  async processCommand(rawCommand) {
17
+ // Load skills and augment the planning prompt
18
+ const skills = loadSkills();
19
+ const skillsSection = formatSkillsForPrompt(skills);
20
+ const systemPrompt = PLAN_PROMPT + skillsSection;
16
21
  const response = await this.client.messages.create({
17
22
  model: this.model,
18
23
  max_tokens: 200,
19
- system: PLAN_PROMPT,
24
+ system: systemPrompt,
20
25
  messages: [
21
26
  {
22
27
  role: 'user',
@@ -29,24 +34,39 @@ export class AnthropicService {
29
34
  throw new Error('Unexpected response type from Claude API');
30
35
  }
31
36
  const text = content.text.trim();
37
+ let tasks;
32
38
  // Try to parse as JSON array
33
39
  if (text.startsWith('[') && text.endsWith(']')) {
34
40
  try {
35
41
  const parsed = JSON.parse(text);
36
- if (Array.isArray(parsed) && parsed.length > 0) {
42
+ if (Array.isArray(parsed)) {
37
43
  // Validate all items are strings
38
44
  const allStrings = parsed.every((item) => typeof item === 'string');
39
45
  if (allStrings) {
40
- return parsed.filter((item) => typeof item === 'string');
46
+ tasks = parsed.filter((item) => typeof item === 'string');
41
47
  }
48
+ else {
49
+ tasks = [text];
50
+ }
51
+ }
52
+ else {
53
+ tasks = [text];
42
54
  }
43
55
  }
44
56
  catch {
45
57
  // If JSON parsing fails, treat as single task
58
+ tasks = [text];
46
59
  }
47
60
  }
48
- // Single task
49
- return [text];
61
+ else {
62
+ // Single task
63
+ tasks = [text];
64
+ }
65
+ const isDebug = process.env.DEBUG === 'true';
66
+ return {
67
+ tasks,
68
+ systemPrompt: isDebug ? systemPrompt : undefined,
69
+ };
50
70
  }
51
71
  }
52
72
  export function createAnthropicService(apiKey, model) {
@@ -1,10 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'fs';
2
2
  import { homedir } from 'os';
3
- import { join, dirname } from 'path';
4
- import { fileURLToPath } from 'url';
3
+ import { join } from 'path';
5
4
  import YAML from 'yaml';
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
5
  export class ConfigError extends Error {
9
6
  constructor(message) {
10
7
  super(message);
@@ -52,17 +49,6 @@ function validateConfig(parsed) {
52
49
  if (anthropic.model && typeof anthropic.model === 'string') {
53
50
  validatedConfig.anthropic.model = anthropic.model;
54
51
  }
55
- // Optional UI config
56
- if (config.ui && typeof config.ui === 'object') {
57
- const ui = config.ui;
58
- validatedConfig.ui = {};
59
- if (ui.theme && typeof ui.theme === 'string') {
60
- validatedConfig.ui.theme = ui.theme;
61
- }
62
- if (typeof ui.verbose === 'boolean') {
63
- validatedConfig.ui.verbose = ui.verbose;
64
- }
65
- }
66
52
  return validatedConfig;
67
53
  }
68
54
  export function loadConfig() {
@@ -0,0 +1,52 @@
1
+ import { readFileSync, readdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ /**
5
+ * Get the path to the skills directory
6
+ */
7
+ export function getSkillsDirectory() {
8
+ return join(homedir(), '.pls', 'skills');
9
+ }
10
+ /**
11
+ * Load all skill markdown files from the skills directory
12
+ * Returns an array of skill file contents
13
+ */
14
+ export function loadSkills() {
15
+ const skillsDir = getSkillsDirectory();
16
+ // Return empty array if directory doesn't exist
17
+ if (!existsSync(skillsDir)) {
18
+ return [];
19
+ }
20
+ try {
21
+ const files = readdirSync(skillsDir);
22
+ // Filter for markdown files
23
+ const skillFiles = files.filter((file) => file.endsWith('.md') || file.endsWith('.MD'));
24
+ // Read and return contents of each skill file
25
+ return skillFiles.map((file) => {
26
+ const filePath = join(skillsDir, file);
27
+ return readFileSync(filePath, 'utf-8');
28
+ });
29
+ }
30
+ catch (error) {
31
+ // Return empty array if there's any error reading the directory
32
+ return [];
33
+ }
34
+ }
35
+ /**
36
+ * Format skills for inclusion in the planning prompt
37
+ */
38
+ export function formatSkillsForPrompt(skills) {
39
+ if (skills.length === 0) {
40
+ return '';
41
+ }
42
+ const header = `
43
+
44
+ ## Available Skills
45
+
46
+ The following skills define domain-specific workflows. When the user's
47
+ query matches a skill, incorporate the skill's steps into your plan.
48
+
49
+ `;
50
+ const skillsContent = skills.join('\n\n');
51
+ return header + skillsContent;
52
+ }
@@ -5,6 +5,7 @@ import { Spinner } from './Spinner.js';
5
5
  const MIN_PROCESSING_TIME = 2000; // purelly for visual effect
6
6
  export function Command({ rawCommand, claudeService }) {
7
7
  const [processedTasks, setProcessedTasks] = useState([]);
8
+ const [systemPrompt, setSystemPrompt] = useState();
8
9
  const [error, setError] = useState(null);
9
10
  const [isLoading, setIsLoading] = useState(true);
10
11
  useEffect(() => {
@@ -17,7 +18,8 @@ export function Command({ rawCommand, claudeService }) {
17
18
  const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
18
19
  await new Promise((resolve) => setTimeout(resolve, remainingTime));
19
20
  if (mounted) {
20
- setProcessedTasks(result);
21
+ setProcessedTasks(result.tasks);
22
+ setSystemPrompt(result.systemPrompt);
21
23
  setIsLoading(false);
22
24
  }
23
25
  }
package/dist/ui/Please.js CHANGED
@@ -5,12 +5,21 @@ import { Command } from './Command.js';
5
5
  import { Welcome } from './Welcome.js';
6
6
  import { ConfigSetup } from './ConfigSetup.js';
7
7
  import { History } from './History.js';
8
- export const Please = ({ app: info, command, claudeService, showConfigSetup, onConfigComplete, }) => {
8
+ export const PLS = ({ app: info, command, claudeService, showConfigSetup, onConfigComplete, }) => {
9
9
  const [history, setHistory] = React.useState([]);
10
- // Simple command execution
11
- if (command && claudeService) {
12
- return (_jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: [_jsx(History, { items: history }), _jsx(Command, { rawCommand: command, claudeService: claudeService })] }));
10
+ const [service, setService] = React.useState(claudeService);
11
+ const handleConfigComplete = (config) => {
12
+ if (onConfigComplete) {
13
+ const result = onConfigComplete(config);
14
+ if (result) {
15
+ setService(result);
16
+ }
17
+ }
18
+ };
19
+ // Command execution (with service from props or after config)
20
+ if (command && service) {
21
+ return (_jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: [_jsx(History, { items: history }), _jsx(Command, { rawCommand: command, claudeService: service })] }));
13
22
  }
14
23
  // Welcome screen with optional config setup
15
- return (_jsxs(Box, { flexDirection: "column", marginY: 1, gap: 1, children: [_jsx(History, { items: history }), _jsx(Welcome, { info: info }), showConfigSetup && onConfigComplete && (_jsx(ConfigSetup, { onComplete: onConfigComplete }))] }));
24
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, gap: 1, children: [_jsx(History, { items: history }), !showConfigSetup && _jsx(Welcome, { info: info }), showConfigSetup && _jsx(ConfigSetup, { onComplete: handleConfigComplete })] }));
16
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-language-shell",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",