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.
- package/dist/config/PLAN.md +61 -4
- package/dist/index.js +10 -18
- package/dist/services/anthropic.js +25 -5
- package/dist/services/config.js +1 -15
- package/dist/services/skills.js +52 -0
- package/dist/ui/Command.js +3 -1
- package/dist/ui/Please.js +14 -5
- package/package.json +1 -1
package/dist/config/PLAN.md
CHANGED
|
@@ -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
|
-
- "
|
|
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,
|
|
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 {
|
|
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
|
-
//
|
|
30
|
+
// First-time setup: config doesn't exist
|
|
32
31
|
if (!configExists()) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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) {
|
package/dist/services/config.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
|
-
import { join
|
|
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
|
+
}
|
package/dist/ui/Command.js
CHANGED
|
@@ -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
|
|
8
|
+
export const PLS = ({ app: info, command, claudeService, showConfigSetup, onConfigComplete, }) => {
|
|
9
9
|
const [history, setHistory] = React.useState([]);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 &&
|
|
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
|
};
|