prompt-language-shell 0.0.5 → 0.1.2
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/README.md +54 -2
- package/dist/config/PLAN.md +300 -0
- package/dist/config/SYSTEM.md +127 -12
- package/dist/index.js +46 -2
- package/dist/services/anthropic.js +5 -5
- package/dist/services/config.js +94 -36
- package/dist/services/config.test.js +82 -0
- package/dist/ui/Command.js +2 -2
- package/dist/ui/ConfigSetup.js +20 -0
- package/dist/ui/ConfigThenCommand.js +16 -0
- package/dist/ui/History.js +8 -0
- package/dist/ui/Please.js +14 -3
- package/dist/ui/Welcome.js +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -8,12 +8,64 @@ Your personal command-line concierge. Ask politely, and it gets things done.
|
|
|
8
8
|
npm install -g prompt-language-shell
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
Before using `pls`, you need to configure your Claude API key:
|
|
14
|
+
|
|
15
|
+
1. Get your API key from [Anthropic Console](https://console.anthropic.com/)
|
|
16
|
+
2. Create the configuration directory and file:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
mkdir -p ~/.pls
|
|
20
|
+
echo "CLAUDE_API_KEY=sk-ant-your-api-key-here" > ~/.pls/.env
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Replace `sk-ant-your-api-key-here` with your actual API key.
|
|
24
|
+
|
|
11
25
|
## Usage
|
|
12
26
|
|
|
27
|
+
Simply type `pls` followed by your request in natural language:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pls change dir to ~
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The tool will:
|
|
34
|
+
|
|
35
|
+
- Display your original command
|
|
36
|
+
- Process it to grammatically correct and clarify it
|
|
37
|
+
- Show the interpreted task
|
|
38
|
+
|
|
39
|
+
Example output:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
> pls change dir to ~
|
|
43
|
+
- change directory to the home folder
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can provide multiple tasks separated by commas (`,`), semicolons (`;`), or the word "and":
|
|
47
|
+
|
|
13
48
|
```bash
|
|
14
|
-
pls
|
|
49
|
+
pls install deps, run tests and deploy
|
|
15
50
|
```
|
|
16
51
|
|
|
52
|
+
Example output:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
> pls install deps, run tests and deploy
|
|
56
|
+
- install dependencies
|
|
57
|
+
- run tests
|
|
58
|
+
- deploy to server
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Run `pls` without arguments to see the welcome screen.
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
Configuration is stored in `~/.pls/.env`. Currently supported:
|
|
66
|
+
|
|
67
|
+
- `CLAUDE_API_KEY` - Your Anthropic API key (required)
|
|
68
|
+
|
|
17
69
|
## Development
|
|
18
70
|
|
|
19
|
-
|
|
71
|
+
See [CLAUDE.md](./CLAUDE.md) for development guidelines and architecture.
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
You are the planning component of "pls" (please), a professional command-line
|
|
4
|
+
concierge that users trust to execute their tasks reliably. Your role is the
|
|
5
|
+
critical first step: transforming natural language requests into well-formed,
|
|
6
|
+
executable task descriptions.
|
|
7
|
+
|
|
8
|
+
The concierge handles diverse operations including filesystem manipulation,
|
|
9
|
+
resource fetching, system commands, information queries, and multi-step
|
|
10
|
+
workflows. Users expect tasks to be planned logically, sequentially, and
|
|
11
|
+
atomically so they execute exactly as intended.
|
|
12
|
+
|
|
13
|
+
Your task is to refine the user's command into clear, professional English while
|
|
14
|
+
preserving the original intent. Apply minimal necessary changes to achieve
|
|
15
|
+
optimal clarity. The refined output will be used to plan and execute real
|
|
16
|
+
operations, so precision and unambiguous language are essential.
|
|
17
|
+
|
|
18
|
+
## Evaluation of Requests
|
|
19
|
+
|
|
20
|
+
Before processing any request, evaluate its nature and respond appropriately:
|
|
21
|
+
|
|
22
|
+
**For harmful or offensive requests:**
|
|
23
|
+
If the request is clearly harmful, malicious, unethical, or offensive, return
|
|
24
|
+
the exact phrase "abort offensive request".
|
|
25
|
+
|
|
26
|
+
Examples that should be aborted as offensive:
|
|
27
|
+
- Requests to harm systems, delete critical data without authorization, or
|
|
28
|
+
perform malicious attacks
|
|
29
|
+
- Requests involving unethical surveillance or privacy violations
|
|
30
|
+
- Requests to create malware or exploit vulnerabilities
|
|
31
|
+
- Requests with offensive, discriminatory, or abusive language
|
|
32
|
+
|
|
33
|
+
**For vague or unclear requests:**
|
|
34
|
+
If the request is too vague or unclear to understand what action should be
|
|
35
|
+
taken, return the exact phrase "abort unclear request".
|
|
36
|
+
|
|
37
|
+
Before marking a request as unclear, try to infer meaning from:
|
|
38
|
+
- Common abbreviations and acronyms in technical contexts
|
|
39
|
+
- Well-known product names, tools, or technologies
|
|
40
|
+
- Context clues within the request itself
|
|
41
|
+
- Standard industry terminology
|
|
42
|
+
|
|
43
|
+
For example:
|
|
44
|
+
- "test GX" → "GX" possibly means Opera GX browser
|
|
45
|
+
- "run TS compiler" → "TS" stands for TypeScript
|
|
46
|
+
- "open VSC" → "VSC" likely means Visual Studio Code
|
|
47
|
+
|
|
48
|
+
Only mark as unclear if the request is truly unintelligible or lacks any
|
|
49
|
+
discernible intent.
|
|
50
|
+
|
|
51
|
+
Examples that are too vague:
|
|
52
|
+
- "do stuff"
|
|
53
|
+
- "handle it"
|
|
54
|
+
|
|
55
|
+
**For legitimate requests:**
|
|
56
|
+
If the request is clear enough to understand the intent, even if informal or
|
|
57
|
+
playful, process it normally. Refine casual language into professional task
|
|
58
|
+
descriptions.
|
|
59
|
+
|
|
60
|
+
## Refinement Guidelines
|
|
61
|
+
|
|
62
|
+
Focus on these elements when refining commands:
|
|
63
|
+
|
|
64
|
+
- Correct grammar and sentence structure
|
|
65
|
+
- Replace words with more precise or contextually appropriate alternatives,
|
|
66
|
+
even when the original word is grammatically correct
|
|
67
|
+
- Use professional, clear terminology suitable for technical documentation
|
|
68
|
+
- Maintain natural, fluent English phrasing
|
|
69
|
+
- Preserve the original intent and meaning
|
|
70
|
+
- Be concise and unambiguous
|
|
71
|
+
|
|
72
|
+
Prioritize clarity and precision over brevity. Choose the most appropriate word
|
|
73
|
+
for the context, not just an acceptable one.
|
|
74
|
+
|
|
75
|
+
## Multiple Tasks
|
|
76
|
+
|
|
77
|
+
When the user provides multiple tasks separated by commas, semicolons, or the
|
|
78
|
+
word "and", or when the user asks a complex question that requires multiple
|
|
79
|
+
steps to answer:
|
|
80
|
+
|
|
81
|
+
1. Identify each individual task or step
|
|
82
|
+
2. Break complex questions into separate, simpler tasks
|
|
83
|
+
3. Return a JSON array of corrected tasks
|
|
84
|
+
4. Use this exact format: ["task 1", "task 2", "task 3"]
|
|
85
|
+
|
|
86
|
+
When breaking down complex questions:
|
|
87
|
+
|
|
88
|
+
- Split compound questions into individual queries
|
|
89
|
+
- Separate conditional checks into distinct tasks
|
|
90
|
+
- Keep each task simple and focused on one operation
|
|
91
|
+
|
|
92
|
+
Before returning a JSON array, perform strict validation:
|
|
93
|
+
|
|
94
|
+
1. Each task is semantically unique (no duplicates with different words)
|
|
95
|
+
2. Each task provides distinct value
|
|
96
|
+
3. Overlapping tasks are merged or removed
|
|
97
|
+
4. When uncertain whether to split, default to a single task
|
|
98
|
+
5. Executing the tasks will not result in duplicate work
|
|
99
|
+
|
|
100
|
+
Critical validation check: After creating the array, examine each pair of
|
|
101
|
+
tasks and ask "Would these perform the same operation?" If yes, they are
|
|
102
|
+
duplicates and must be merged or removed. Pay special attention to synonym
|
|
103
|
+
verbs (delete, remove, erase) and equivalent noun phrases (unused apps,
|
|
104
|
+
applications not used).
|
|
105
|
+
|
|
106
|
+
## Avoiding Duplicates
|
|
107
|
+
|
|
108
|
+
Each task in an array must be semantically unique and provide distinct value.
|
|
109
|
+
Before returning multiple tasks, verify there are no duplicates.
|
|
110
|
+
|
|
111
|
+
Rules for preventing duplicates:
|
|
112
|
+
|
|
113
|
+
1. Modifiers are not separate tasks. Adverbs and adjectives that modify how
|
|
114
|
+
to perform a task are part of the task description, not separate tasks.
|
|
115
|
+
- "explain X in simple terms" = ONE task (not "explain X" + "use simple
|
|
116
|
+
terms")
|
|
117
|
+
- "describe X in detail" = ONE task (not "describe X" + "make it
|
|
118
|
+
detailed")
|
|
119
|
+
- "list X completely" = ONE task (not "list X" + "be complete")
|
|
120
|
+
|
|
121
|
+
2. Synonymous verbs are duplicates. Different verbs meaning the same thing
|
|
122
|
+
with the same object are duplicates. Keep only one or merge them.
|
|
123
|
+
- "explain X" + "describe X" = DUPLICATE (choose one)
|
|
124
|
+
- "show X" + "display X" = DUPLICATE (choose one)
|
|
125
|
+
- "check X" + "verify X" = DUPLICATE (choose one)
|
|
126
|
+
- "list X" + "enumerate X" = DUPLICATE (choose one)
|
|
127
|
+
- "delete X" + "remove X" = DUPLICATE (choose one)
|
|
128
|
+
- "erase X" + "remove X" = DUPLICATE (choose one)
|
|
129
|
+
- "create X" + "make X" = DUPLICATE (choose one)
|
|
130
|
+
- "find X" + "locate X" = DUPLICATE (choose one)
|
|
131
|
+
|
|
132
|
+
3. Tautological patterns stay single. When a request uses a phrase that
|
|
133
|
+
already describes how to do something, do not split it.
|
|
134
|
+
- "explain Lehman's terms in Lehman's terms" = ONE task (the phrase
|
|
135
|
+
already means "in simple language")
|
|
136
|
+
- "describe it simply in simple words" = ONE task (redundant modifiers)
|
|
137
|
+
- "show clearly and display obviously" = ONE task (redundant verbs)
|
|
138
|
+
|
|
139
|
+
4. Redundant operations are duplicates. If two alleged tasks would perform
|
|
140
|
+
the same operation, they are duplicates.
|
|
141
|
+
- "install and set up dependencies" = ONE task (setup is part of install)
|
|
142
|
+
- "check and verify disk space" = ONE task (verify means check)
|
|
143
|
+
- "list and show all files" = ONE task (list and show are the same)
|
|
144
|
+
|
|
145
|
+
## When to Split and When NOT to Split
|
|
146
|
+
|
|
147
|
+
Keep as a single task when:
|
|
148
|
+
|
|
149
|
+
- Single operation with modifiers: "explain X in detail" (one action)
|
|
150
|
+
- Tautological phrasing: "do X in terms of X" (one action)
|
|
151
|
+
- Redundant verb pairs: "check and verify X" (same operation)
|
|
152
|
+
- Compound modifiers: "quickly and efficiently process X" (one action)
|
|
153
|
+
- Implicit single operation: "install dependencies" even if it involves
|
|
154
|
+
multiple steps internally
|
|
155
|
+
|
|
156
|
+
Split into multiple tasks when:
|
|
157
|
+
|
|
158
|
+
- Distinct sequential operations: "install deps, run tests" (two separate
|
|
159
|
+
commands)
|
|
160
|
+
- Action with conditional: "check disk space and warn if below 10%" (check,
|
|
161
|
+
then conditional action)
|
|
162
|
+
- Different subjects: "explain X and demonstrate Y" (two different things)
|
|
163
|
+
- Truly separate steps: "create file and add content to it" (two distinct
|
|
164
|
+
operations)
|
|
165
|
+
|
|
166
|
+
## Response Format
|
|
167
|
+
|
|
168
|
+
- Single task: Return ONLY the corrected command text
|
|
169
|
+
- Multiple tasks: Return ONLY a JSON array of strings
|
|
170
|
+
|
|
171
|
+
Do not include explanations, commentary, or any other text.
|
|
172
|
+
|
|
173
|
+
## Final Validation Before Response
|
|
174
|
+
|
|
175
|
+
Before returning any JSON array, perform this final check:
|
|
176
|
+
|
|
177
|
+
1. Compare each task against every other task in the array
|
|
178
|
+
2. Ask for each pair: "Do these describe the same operation using different
|
|
179
|
+
words?"
|
|
180
|
+
3. Check specifically for:
|
|
181
|
+
- Synonym verbs (delete/remove, show/display, create/make, find/locate)
|
|
182
|
+
- Equivalent noun phrases (apps/applications, unused/not used,
|
|
183
|
+
files/documents)
|
|
184
|
+
- Same operation with different modifiers
|
|
185
|
+
4. If any pair is semantically identical, merge them or keep only one
|
|
186
|
+
5. If in doubt about whether tasks are duplicates, they probably are - merge
|
|
187
|
+
them
|
|
188
|
+
|
|
189
|
+
Only return the array after confirming no semantic duplicates exist.
|
|
190
|
+
|
|
191
|
+
## Examples
|
|
192
|
+
|
|
193
|
+
### Incorrect Examples: Duplicate Tasks
|
|
194
|
+
|
|
195
|
+
These examples show common mistakes that create semantic duplicates:
|
|
196
|
+
|
|
197
|
+
- "explain Lehman's terms in Lehman's terms" →
|
|
198
|
+
- wrong:
|
|
199
|
+
[
|
|
200
|
+
"explain what Lehman's terms are in simple language",
|
|
201
|
+
"describe Lehman's terms using easy-to-understand words",
|
|
202
|
+
]
|
|
203
|
+
- correct: explain Lehman's terms in simple language
|
|
204
|
+
|
|
205
|
+
- "show and display files" →
|
|
206
|
+
- wrong:
|
|
207
|
+
[
|
|
208
|
+
"show the files",
|
|
209
|
+
"display the files",
|
|
210
|
+
]
|
|
211
|
+
- correct: "show the files"
|
|
212
|
+
|
|
213
|
+
- "check and verify disk space" →
|
|
214
|
+
- wrong:
|
|
215
|
+
[
|
|
216
|
+
"check the disk space",
|
|
217
|
+
"verify the disk space",
|
|
218
|
+
]
|
|
219
|
+
- correct: "check the disk space"
|
|
220
|
+
|
|
221
|
+
- "list directory contents completely" →
|
|
222
|
+
- wrong:
|
|
223
|
+
[
|
|
224
|
+
"list the directory contents",
|
|
225
|
+
"show all items",
|
|
226
|
+
]
|
|
227
|
+
- correct: "list all directory contents"
|
|
228
|
+
|
|
229
|
+
- "install and set up dependencies" →
|
|
230
|
+
- wrong:
|
|
231
|
+
[
|
|
232
|
+
"install dependencies",
|
|
233
|
+
"set up dependencies",
|
|
234
|
+
]
|
|
235
|
+
- correct: "install dependencies"
|
|
236
|
+
|
|
237
|
+
- "delete apps and remove all apps unused in a year" →
|
|
238
|
+
- wrong:
|
|
239
|
+
[
|
|
240
|
+
"delete unused applications",
|
|
241
|
+
"remove apps not used in the past year",
|
|
242
|
+
]
|
|
243
|
+
- correct: "delete all applications unused in the past year"
|
|
244
|
+
|
|
245
|
+
### Correct Examples: Single Task
|
|
246
|
+
|
|
247
|
+
Simple requests should remain as single tasks:
|
|
248
|
+
|
|
249
|
+
- "change dir to ~" → "change directory to the home folder"
|
|
250
|
+
- "install deps" → "install dependencies"
|
|
251
|
+
- "make new file called test.txt" → "create a new file called test.txt"
|
|
252
|
+
- "show me files here" → "show the files in the current directory"
|
|
253
|
+
- "explain quantum physics simply" → "explain quantum physics in simple terms"
|
|
254
|
+
- "describe the process in detail" → "describe the process in detail"
|
|
255
|
+
- "check disk space thoroughly" → "check the disk space thoroughly"
|
|
256
|
+
|
|
257
|
+
### Correct Examples: Multiple Tasks
|
|
258
|
+
|
|
259
|
+
Only split when tasks are truly distinct operations:
|
|
260
|
+
|
|
261
|
+
- "install deps, run tests" →
|
|
262
|
+
[
|
|
263
|
+
"install dependencies",
|
|
264
|
+
"run tests",
|
|
265
|
+
]
|
|
266
|
+
- "create file; add content" →
|
|
267
|
+
[
|
|
268
|
+
"create a file",
|
|
269
|
+
"add content",
|
|
270
|
+
]
|
|
271
|
+
- "build project and deploy" →
|
|
272
|
+
[
|
|
273
|
+
"build the project",
|
|
274
|
+
"deploy",
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
### Correct Examples: Complex Questions
|
|
278
|
+
|
|
279
|
+
Split only when multiple distinct queries or operations are needed:
|
|
280
|
+
|
|
281
|
+
- "tell me weather in Wro, is it over 70 deg" →
|
|
282
|
+
[
|
|
283
|
+
"show the weather in Wrocław",
|
|
284
|
+
"check if the temperature is above 70 degrees",
|
|
285
|
+
]
|
|
286
|
+
- "pls what is 7th prime and how many are to 1000" →
|
|
287
|
+
[
|
|
288
|
+
"find the 7th prime number",
|
|
289
|
+
"count how many prime numbers are below 1000",
|
|
290
|
+
]
|
|
291
|
+
- "check disk space and warn if below 10%" →
|
|
292
|
+
[
|
|
293
|
+
"check the disk space",
|
|
294
|
+
"show a warning if it is below 10%",
|
|
295
|
+
]
|
|
296
|
+
- "find config file and show its contents" →
|
|
297
|
+
[
|
|
298
|
+
"find the config file",
|
|
299
|
+
"show its contents",
|
|
300
|
+
]
|
package/dist/config/SYSTEM.md
CHANGED
|
@@ -1,22 +1,97 @@
|
|
|
1
1
|
You are a command-line assistant for a CLI tool called "pls" (please) that
|
|
2
2
|
helps users perform filesystem and system operations using natural language.
|
|
3
3
|
|
|
4
|
-
Your task is to
|
|
5
|
-
|
|
4
|
+
Your task is to refine the user's command into clear, professional English while
|
|
5
|
+
preserving the original intent. Apply minimal necessary changes to achieve
|
|
6
|
+
optimal clarity. Focus on:
|
|
6
7
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
-
|
|
8
|
+
- Correcting grammar and sentence structure
|
|
9
|
+
- Replacing words with more precise or contextually appropriate alternatives,
|
|
10
|
+
even when the original word is grammatically correct
|
|
11
|
+
- Using professional, clear terminology suitable for technical documentation
|
|
12
|
+
- Maintaining natural, fluent English phrasing
|
|
13
|
+
- Preserving the original intent and meaning
|
|
14
|
+
- Being concise and unambiguous
|
|
15
|
+
|
|
16
|
+
Prioritize clarity and precision over brevity. Choose the most appropriate word
|
|
17
|
+
for the context, not just an acceptable one.
|
|
11
18
|
|
|
12
19
|
## Multiple Tasks
|
|
13
20
|
|
|
14
21
|
If the user provides multiple tasks separated by commas (,), semicolons (;), or
|
|
15
|
-
the word "and",
|
|
22
|
+
the word "and", OR if the user asks a complex question that requires multiple
|
|
23
|
+
steps to answer, you must:
|
|
24
|
+
|
|
25
|
+
1. Identify each individual task or step
|
|
26
|
+
2. Break complex questions into separate, simpler tasks
|
|
27
|
+
3. Return a JSON array of corrected tasks
|
|
28
|
+
4. Use this exact format: ["task 1", "task 2", "task 3"]
|
|
29
|
+
|
|
30
|
+
When breaking down complex questions:
|
|
31
|
+
- Split compound questions into individual queries
|
|
32
|
+
- Separate conditional checks into distinct tasks
|
|
33
|
+
- Keep each task simple and focused on one operation
|
|
34
|
+
|
|
35
|
+
**IMPORTANT: Before returning a JSON array, perform this validation:**
|
|
36
|
+
1. Check each task for semantic duplicates (same meaning, different words)
|
|
37
|
+
2. Verify each task provides unique, distinct value
|
|
38
|
+
3. If tasks overlap, merge them or keep only one
|
|
39
|
+
4. If unsure whether to split, default to a single task
|
|
40
|
+
5. Ask yourself: "Would executing these tasks result in duplicate work?"
|
|
41
|
+
|
|
42
|
+
## Avoiding Duplicates
|
|
43
|
+
|
|
44
|
+
CRITICAL: Each task in an array must be semantically unique and provide distinct
|
|
45
|
+
value. Before returning multiple tasks, verify there are no duplicates.
|
|
46
|
+
|
|
47
|
+
Rules for preventing duplicates:
|
|
48
|
+
|
|
49
|
+
1. **Modifiers are not separate tasks**: Adverbs and adjectives that modify how
|
|
50
|
+
to perform a task are part of the task description, not separate tasks.
|
|
51
|
+
- "explain X in simple terms" = ONE task (not "explain X" + "use simple terms")
|
|
52
|
+
- "describe X in detail" = ONE task (not "describe X" + "make it detailed")
|
|
53
|
+
- "list X completely" = ONE task (not "list X" + "be complete")
|
|
54
|
+
|
|
55
|
+
2. **Synonymous verbs = duplicate**: Different verbs meaning the same thing with
|
|
56
|
+
the same object are duplicates. Keep only one or merge them.
|
|
57
|
+
- "explain X" + "describe X" = DUPLICATE (choose one)
|
|
58
|
+
- "show X" + "display X" = DUPLICATE (choose one)
|
|
59
|
+
- "check X" + "verify X" = DUPLICATE (choose one)
|
|
60
|
+
- "list X" + "enumerate X" = DUPLICATE (choose one)
|
|
61
|
+
|
|
62
|
+
3. **Tautological patterns stay single**: When a request uses a phrase that
|
|
63
|
+
already describes how to do something, don't split it.
|
|
64
|
+
- "explain Lehman's terms in Lehman's terms" = ONE task (the phrase already
|
|
65
|
+
means "in simple language")
|
|
66
|
+
- "describe it simply in simple words" = ONE task (redundant modifiers)
|
|
67
|
+
- "show clearly and display obviously" = ONE task (redundant verbs)
|
|
68
|
+
|
|
69
|
+
4. **Redundant operations**: If two alleged tasks would perform the same
|
|
70
|
+
operation, they're duplicates.
|
|
71
|
+
- "install and set up dependencies" = ONE task (setup is part of install)
|
|
72
|
+
- "check and verify disk space" = ONE task (verify means check)
|
|
73
|
+
- "list and show all files" = ONE task (list and show are the same)
|
|
74
|
+
|
|
75
|
+
## When NOT to Split
|
|
76
|
+
|
|
77
|
+
Keep as a single task when:
|
|
78
|
+
|
|
79
|
+
- **Single operation with modifiers**: "explain X in detail" (one action)
|
|
80
|
+
- **Tautological phrasing**: "do X in terms of X" (one action)
|
|
81
|
+
- **Redundant verb pairs**: "check and verify X" (same operation)
|
|
82
|
+
- **Compound modifiers**: "quickly and efficiently process X" (one action)
|
|
83
|
+
- **Implicit single operation**: "install dependencies" even if it involves
|
|
84
|
+
multiple steps internally
|
|
16
85
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
86
|
+
DO split when:
|
|
87
|
+
|
|
88
|
+
- **Distinct sequential operations**: "install deps, run tests" (two separate
|
|
89
|
+
commands)
|
|
90
|
+
- **Action + conditional**: "check disk space and warn if below 10%" (check,
|
|
91
|
+
then conditional action)
|
|
92
|
+
- **Different subjects**: "explain X and demonstrate Y" (two different things)
|
|
93
|
+
- **Truly separate steps**: "create file and add content to it" (two distinct
|
|
94
|
+
operations)
|
|
20
95
|
|
|
21
96
|
## Response Format
|
|
22
97
|
|
|
@@ -27,15 +102,55 @@ Do not include explanations, commentary, or any other text.
|
|
|
27
102
|
|
|
28
103
|
## Examples
|
|
29
104
|
|
|
30
|
-
|
|
105
|
+
### ❌ INCORRECT: Duplicate Tasks (What NOT to do)
|
|
106
|
+
|
|
107
|
+
These examples show common mistakes that create semantic duplicates:
|
|
108
|
+
|
|
109
|
+
- "explain Lehman's terms in Lehman's terms" →
|
|
110
|
+
❌ WRONG: ["explain what Lehman's terms are in simple language", "describe Lehman's terms using easy-to-understand words"]
|
|
111
|
+
✅ CORRECT: explain Lehman's terms in simple language
|
|
112
|
+
|
|
113
|
+
- "show and display files" →
|
|
114
|
+
❌ WRONG: ["show the files", "display the files"]
|
|
115
|
+
✅ CORRECT: show the files
|
|
116
|
+
|
|
117
|
+
- "check and verify disk space" →
|
|
118
|
+
❌ WRONG: ["check the disk space", "verify the disk space"]
|
|
119
|
+
✅ CORRECT: check the disk space
|
|
120
|
+
|
|
121
|
+
- "list directory contents completely" →
|
|
122
|
+
❌ WRONG: ["list the directory contents", "show all items"]
|
|
123
|
+
✅ CORRECT: list all directory contents
|
|
124
|
+
|
|
125
|
+
- "install and set up dependencies" →
|
|
126
|
+
❌ WRONG: ["install dependencies", "set up dependencies"]
|
|
127
|
+
✅ CORRECT: install dependencies
|
|
128
|
+
|
|
129
|
+
### ✅ CORRECT: Single Task
|
|
130
|
+
|
|
131
|
+
Simple requests should remain as single tasks:
|
|
31
132
|
|
|
32
133
|
- "change dir to ~" → change directory to the home folder
|
|
33
134
|
- "install deps" → install dependencies
|
|
34
135
|
- "make new file called test.txt" → create a new file called test.txt
|
|
35
136
|
- "show me files here" → show the files in the current directory
|
|
137
|
+
- "explain quantum physics simply" → explain quantum physics in simple terms
|
|
138
|
+
- "describe the process in detail" → describe the process in detail
|
|
139
|
+
- "check disk space thoroughly" → check the disk space thoroughly
|
|
140
|
+
|
|
141
|
+
### ✅ CORRECT: Multiple Tasks
|
|
36
142
|
|
|
37
|
-
|
|
143
|
+
Only split when tasks are truly distinct operations:
|
|
38
144
|
|
|
39
145
|
- "install deps, run tests" → ["install dependencies", "run tests"]
|
|
40
146
|
- "create file; add content" → ["create a file", "add content"]
|
|
41
147
|
- "build project and deploy" → ["build the project", "deploy"]
|
|
148
|
+
|
|
149
|
+
### ✅ CORRECT: Complex Questions (Split into Sequences)
|
|
150
|
+
|
|
151
|
+
Split only when multiple distinct queries or operations are needed:
|
|
152
|
+
|
|
153
|
+
- "tell me weather in Wro, is it over 70 deg" → ["show the weather in Wrocław", "check if the temperature is above 70 degrees"]
|
|
154
|
+
- "pls what is 7th prime and how many are to 1000" → ["find the 7th prime number", "count how many prime numbers are below 1000"]
|
|
155
|
+
- "check disk space and warn if below 10%" → ["check the disk space", "show a warning if it is below 10%"]
|
|
156
|
+
- "find config file and show its contents" → ["find the config file", "show its contents"]
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { readFileSync, existsSync } from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname, join } from 'path';
|
|
6
|
-
import { render } from 'ink';
|
|
6
|
+
import { render, Text } from 'ink';
|
|
7
|
+
import { loadConfig, ConfigError, configExists, saveConfig, } from './services/config.js';
|
|
8
|
+
import { createAnthropicService } from './services/anthropic.js';
|
|
7
9
|
import { Please } from './ui/Please.js';
|
|
10
|
+
import { ConfigThenCommand } from './ui/ConfigThenCommand.js';
|
|
8
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
12
|
const __dirname = dirname(__filename);
|
|
10
13
|
// Get package info
|
|
@@ -21,4 +24,45 @@ const appInfo = {
|
|
|
21
24
|
description: packageJson.description,
|
|
22
25
|
isDev,
|
|
23
26
|
};
|
|
24
|
-
|
|
27
|
+
// Get command from command-line arguments
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const rawCommand = args.join(' ').trim();
|
|
30
|
+
async function runApp() {
|
|
31
|
+
// Check if config exists, if not run setup
|
|
32
|
+
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
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Try to load and validate config
|
|
48
|
+
try {
|
|
49
|
+
const config = loadConfig();
|
|
50
|
+
if (!rawCommand) {
|
|
51
|
+
// "pls" when config present: show welcome box
|
|
52
|
+
render(_jsx(Please, { app: appInfo }));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// "pls do stuff": fetch and show the plan
|
|
56
|
+
const claudeService = createAnthropicService(config.anthropic.apiKey, config.anthropic.model);
|
|
57
|
+
render(_jsx(Please, { app: appInfo, command: rawCommand, claudeService: claudeService }));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof ConfigError) {
|
|
62
|
+
render(_jsx(Text, { color: "red", children: error.message }));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
runApp();
|
|
@@ -4,11 +4,11 @@ import { dirname, join } from 'path';
|
|
|
4
4
|
import Anthropic from '@anthropic-ai/sdk';
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = dirname(__filename);
|
|
7
|
-
const
|
|
7
|
+
const PLAN_PROMPT = readFileSync(join(__dirname, '../config/PLAN.md'), 'utf-8');
|
|
8
8
|
export class AnthropicService {
|
|
9
9
|
client;
|
|
10
10
|
model;
|
|
11
|
-
constructor(apiKey, model = 'claude-
|
|
11
|
+
constructor(apiKey, model = 'claude-haiku-4-5-20251001') {
|
|
12
12
|
this.client = new Anthropic({ apiKey });
|
|
13
13
|
this.model = model;
|
|
14
14
|
}
|
|
@@ -16,7 +16,7 @@ export class AnthropicService {
|
|
|
16
16
|
const response = await this.client.messages.create({
|
|
17
17
|
model: this.model,
|
|
18
18
|
max_tokens: 200,
|
|
19
|
-
system:
|
|
19
|
+
system: PLAN_PROMPT,
|
|
20
20
|
messages: [
|
|
21
21
|
{
|
|
22
22
|
role: 'user',
|
|
@@ -49,6 +49,6 @@ export class AnthropicService {
|
|
|
49
49
|
return [text];
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
export function
|
|
53
|
-
return new AnthropicService(apiKey);
|
|
52
|
+
export function createAnthropicService(apiKey, model) {
|
|
53
|
+
return new AnthropicService(apiKey, model);
|
|
54
54
|
}
|
package/dist/services/config.js
CHANGED
|
@@ -1,57 +1,115 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
|
-
import { join } from 'path';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
4
8
|
export class ConfigError extends Error {
|
|
5
9
|
constructor(message) {
|
|
6
10
|
super(message);
|
|
7
11
|
this.name = 'ConfigError';
|
|
8
12
|
}
|
|
9
13
|
}
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const CONFIG_FILE = join(homedir(), '.plsrc');
|
|
15
|
+
function parseYamlConfig(content) {
|
|
16
|
+
try {
|
|
17
|
+
return YAML.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw new ConfigError(`\nFailed to parse YAML configuration file at ${CONFIG_FILE}\n` +
|
|
21
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
22
|
}
|
|
16
23
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
function validateConfig(parsed) {
|
|
25
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
26
|
+
throw new ConfigError(`\nInvalid configuration format in ${CONFIG_FILE}\n` +
|
|
27
|
+
'Expected a YAML object with configuration settings.');
|
|
28
|
+
}
|
|
29
|
+
const config = parsed;
|
|
30
|
+
// Validate anthropic section
|
|
31
|
+
if (!config.anthropic || typeof config.anthropic !== 'object') {
|
|
32
|
+
throw new ConfigError(`\nMissing or invalid 'anthropic' section in ${CONFIG_FILE}\n` +
|
|
33
|
+
'Please add:\n' +
|
|
34
|
+
'anthropic:\n' +
|
|
35
|
+
' api-key: sk-ant-...');
|
|
36
|
+
}
|
|
37
|
+
const anthropic = config.anthropic;
|
|
38
|
+
// Support both 'api-key' (kebab-case) and 'apiKey' (camelCase)
|
|
39
|
+
const apiKey = anthropic['api-key'] || anthropic.apiKey;
|
|
40
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
41
|
+
throw new ConfigError(`\nMissing or invalid 'anthropic.api-key' in ${CONFIG_FILE}\n` +
|
|
42
|
+
'Please add your Anthropic API key:\n' +
|
|
43
|
+
'anthropic:\n' +
|
|
44
|
+
' api-key: sk-ant-...');
|
|
45
|
+
}
|
|
46
|
+
const validatedConfig = {
|
|
47
|
+
anthropic: {
|
|
48
|
+
apiKey: apiKey,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
// Optional model
|
|
52
|
+
if (anthropic.model && typeof anthropic.model === 'string') {
|
|
53
|
+
validatedConfig.anthropic.model = anthropic.model;
|
|
54
|
+
}
|
|
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;
|
|
28
61
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (key) {
|
|
32
|
-
result[key] = value;
|
|
62
|
+
if (typeof ui.verbose === 'boolean') {
|
|
63
|
+
validatedConfig.ui.verbose = ui.verbose;
|
|
33
64
|
}
|
|
34
65
|
}
|
|
35
|
-
return
|
|
66
|
+
return validatedConfig;
|
|
36
67
|
}
|
|
37
68
|
export function loadConfig() {
|
|
38
|
-
ensureConfigDirectory();
|
|
39
69
|
if (!existsSync(CONFIG_FILE)) {
|
|
40
|
-
throw new ConfigError(
|
|
41
|
-
'Please create it with your
|
|
42
|
-
'Example
|
|
70
|
+
throw new ConfigError(`\nConfiguration file not found at ${CONFIG_FILE}\n\n` +
|
|
71
|
+
'Please create it with your Anthropic API key.\n' +
|
|
72
|
+
'Example:\n\n' +
|
|
73
|
+
'anthropic:\n' +
|
|
74
|
+
' api-key: sk-ant-...\n' +
|
|
75
|
+
' model: claude-haiku-4-5-20251001\n');
|
|
43
76
|
}
|
|
44
77
|
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
45
|
-
const parsed =
|
|
46
|
-
|
|
47
|
-
if (!claudeApiKey) {
|
|
48
|
-
throw new ConfigError('CLAUDE_API_KEY not found in configuration file.\n' +
|
|
49
|
-
`Please add it to ${CONFIG_FILE}`);
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
claudeApiKey,
|
|
53
|
-
};
|
|
78
|
+
const parsed = parseYamlConfig(content);
|
|
79
|
+
return validateConfig(parsed);
|
|
54
80
|
}
|
|
55
81
|
export function getConfigPath() {
|
|
56
82
|
return CONFIG_FILE;
|
|
57
83
|
}
|
|
84
|
+
export function configExists() {
|
|
85
|
+
return existsSync(CONFIG_FILE);
|
|
86
|
+
}
|
|
87
|
+
export function mergeConfig(existingContent, sectionName, newValues) {
|
|
88
|
+
const parsed = existingContent.trim()
|
|
89
|
+
? YAML.parse(existingContent) || {}
|
|
90
|
+
: {};
|
|
91
|
+
// Update or add section
|
|
92
|
+
const section = parsed[sectionName] || {};
|
|
93
|
+
for (const [key, value] of Object.entries(newValues)) {
|
|
94
|
+
section[key] = value;
|
|
95
|
+
}
|
|
96
|
+
parsed[sectionName] = section;
|
|
97
|
+
// Sort sections alphabetically
|
|
98
|
+
const sortedKeys = Object.keys(parsed).sort();
|
|
99
|
+
const sortedConfig = {};
|
|
100
|
+
for (const key of sortedKeys) {
|
|
101
|
+
sortedConfig[key] = parsed[key];
|
|
102
|
+
}
|
|
103
|
+
// Convert back to YAML
|
|
104
|
+
return YAML.stringify(sortedConfig);
|
|
105
|
+
}
|
|
106
|
+
export function saveConfig(apiKey, model) {
|
|
107
|
+
const existingContent = existsSync(CONFIG_FILE)
|
|
108
|
+
? readFileSync(CONFIG_FILE, 'utf-8')
|
|
109
|
+
: '';
|
|
110
|
+
const newContent = mergeConfig(existingContent, 'anthropic', {
|
|
111
|
+
'api-key': apiKey,
|
|
112
|
+
model: model,
|
|
113
|
+
});
|
|
114
|
+
writeFileSync(CONFIG_FILE, newContent, 'utf-8');
|
|
115
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mergeConfig } from './config.js';
|
|
3
|
+
describe('mergeConfig', () => {
|
|
4
|
+
it('creates new config when file is empty', () => {
|
|
5
|
+
const result = mergeConfig('', 'anthropic', {
|
|
6
|
+
'api-key': 'sk-ant-test',
|
|
7
|
+
model: 'claude-haiku-4-5-20251001',
|
|
8
|
+
});
|
|
9
|
+
expect(result).toContain('anthropic:');
|
|
10
|
+
expect(result).toContain(' api-key: sk-ant-test');
|
|
11
|
+
expect(result).toContain(' model: claude-haiku-4-5-20251001');
|
|
12
|
+
});
|
|
13
|
+
it('adds new section to existing config', () => {
|
|
14
|
+
const existing = `ui:
|
|
15
|
+
theme: dark
|
|
16
|
+
verbose: true`;
|
|
17
|
+
const result = mergeConfig(existing, 'anthropic', {
|
|
18
|
+
'api-key': 'sk-ant-test',
|
|
19
|
+
model: 'claude-haiku-4-5-20251001',
|
|
20
|
+
});
|
|
21
|
+
expect(result).toContain('ui:');
|
|
22
|
+
expect(result).toContain(' theme: dark');
|
|
23
|
+
expect(result).toContain('anthropic:');
|
|
24
|
+
expect(result).toContain(' api-key: sk-ant-test');
|
|
25
|
+
});
|
|
26
|
+
it('sorts sections alphabetically', () => {
|
|
27
|
+
const existing = `ui:
|
|
28
|
+
theme: dark`;
|
|
29
|
+
const result = mergeConfig(existing, 'anthropic', {
|
|
30
|
+
'api-key': 'sk-ant-test',
|
|
31
|
+
});
|
|
32
|
+
const anthropicIndex = result.indexOf('anthropic:');
|
|
33
|
+
const uiIndex = result.indexOf('ui:');
|
|
34
|
+
expect(anthropicIndex).toBeLessThan(uiIndex);
|
|
35
|
+
});
|
|
36
|
+
it('updates existing section without removing other keys', () => {
|
|
37
|
+
const existing = `anthropic:
|
|
38
|
+
api-key: sk-ant-old
|
|
39
|
+
custom-setting: value`;
|
|
40
|
+
const result = mergeConfig(existing, 'anthropic', {
|
|
41
|
+
'api-key': 'sk-ant-new',
|
|
42
|
+
model: 'claude-haiku-4-5-20251001',
|
|
43
|
+
});
|
|
44
|
+
expect(result).toContain('api-key: sk-ant-new');
|
|
45
|
+
expect(result).toContain('model: claude-haiku-4-5-20251001');
|
|
46
|
+
expect(result).toContain('custom-setting: value');
|
|
47
|
+
expect(result).not.toContain('sk-ant-old');
|
|
48
|
+
});
|
|
49
|
+
it('adds empty line before new section', () => {
|
|
50
|
+
const existing = `ui:
|
|
51
|
+
theme: dark`;
|
|
52
|
+
const result = mergeConfig(existing, 'anthropic', {
|
|
53
|
+
'api-key': 'sk-ant-test',
|
|
54
|
+
});
|
|
55
|
+
expect(result).toMatch(/anthropic:\n {2}api-key:/);
|
|
56
|
+
expect(result).toMatch(/ui:\n {2}theme:/);
|
|
57
|
+
});
|
|
58
|
+
it('handles multiple sections with sorting', () => {
|
|
59
|
+
const existing = `ui:
|
|
60
|
+
theme: dark
|
|
61
|
+
|
|
62
|
+
config:
|
|
63
|
+
llm: anthropic
|
|
64
|
+
name: Sensei`;
|
|
65
|
+
const result = mergeConfig(existing, 'anthropic', {
|
|
66
|
+
'api-key': 'sk-ant-test',
|
|
67
|
+
});
|
|
68
|
+
const sections = result.match(/^[a-z]+:/gm) || [];
|
|
69
|
+
expect(sections).toEqual(['anthropic:', 'config:', 'ui:']);
|
|
70
|
+
});
|
|
71
|
+
it('updates key in existing section', () => {
|
|
72
|
+
const existing = `anthropic:
|
|
73
|
+
api-key: sk-ant-old
|
|
74
|
+
model: old-model`;
|
|
75
|
+
const result = mergeConfig(existing, 'anthropic', {
|
|
76
|
+
model: 'new-model',
|
|
77
|
+
});
|
|
78
|
+
expect(result).toContain('api-key: sk-ant-old');
|
|
79
|
+
expect(result).toContain('model: new-model');
|
|
80
|
+
expect(result).not.toContain('old-model');
|
|
81
|
+
});
|
|
82
|
+
});
|
package/dist/ui/Command.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { Spinner } from './Spinner.js';
|
|
5
|
-
const MIN_PROCESSING_TIME =
|
|
5
|
+
const MIN_PROCESSING_TIME = 2000; // purelly for visual effect
|
|
6
6
|
export function Command({ rawCommand, claudeService }) {
|
|
7
7
|
const [processedTasks, setProcessedTasks] = useState([]);
|
|
8
8
|
const [error, setError] = useState(null);
|
|
@@ -36,5 +36,5 @@ export function Command({ rawCommand, claudeService }) {
|
|
|
36
36
|
mounted = false;
|
|
37
37
|
};
|
|
38
38
|
}, [rawCommand, claudeService]);
|
|
39
|
-
return (_jsxs(Box, { alignSelf: "flex-start",
|
|
39
|
+
return (_jsxs(Box, { alignSelf: "flex-start", marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: ["> pls ", rawCommand] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), processedTasks.length > 0 && (_jsx(Box, { flexDirection: "column", children: processedTasks.map((task, index) => (_jsxs(Box, { children: [_jsx(Text, { color: "whiteBright", children: ' - ' }), _jsx(Text, { color: "white", children: task })] }, index))) }))] }));
|
|
40
40
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
export function ConfigSetup({ onComplete }) {
|
|
6
|
+
const [step, setStep] = React.useState('api-key');
|
|
7
|
+
const [apiKey, setApiKey] = React.useState('');
|
|
8
|
+
const [model, setModel] = React.useState('claude-haiku-4-5-20251001');
|
|
9
|
+
const handleApiKeySubmit = (value) => {
|
|
10
|
+
setApiKey(value);
|
|
11
|
+
setStep('model');
|
|
12
|
+
};
|
|
13
|
+
const handleModelSubmit = (value) => {
|
|
14
|
+
const finalModel = value.trim() || 'claude-haiku-4-5-20251001';
|
|
15
|
+
setModel(finalModel);
|
|
16
|
+
setStep('done');
|
|
17
|
+
onComplete({ apiKey, model: finalModel });
|
|
18
|
+
};
|
|
19
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsx(Text, { children: "Configuration required." }), _jsx(Box, { children: _jsxs(Text, { color: "whiteBright", dimColor: true, children: ["==>", " Get your API key from: https://platform.claude.com/"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Anthropic API key:" }) }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "> " }), step === 'api-key' ? (_jsx(TextInput, { value: apiKey, onChange: setApiKey, onSubmit: handleApiKeySubmit, mask: "*" })) : (_jsx(Text, { dimColor: true, children: '*'.repeat(12) }))] }), (step === 'model' || step === 'done') && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsxs(Text, { children: ["Model ", _jsx(Text, { dimColor: true, children: "(default: claude-haiku-4-5-20251001)" }), ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "> " }), step === 'model' ? (_jsx(TextInput, { value: model, onChange: setModel, onSubmit: handleModelSubmit })) : (_jsx(Text, { dimColor: true, children: model }))] })] })), step === 'done' && (_jsx(Box, { marginY: 1, children: _jsx(Text, { color: "green", children: "\u2713 Configuration saved" }) }))] }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box } from 'ink';
|
|
4
|
+
import { createAnthropicService } from '../services/anthropic.js';
|
|
5
|
+
import { ConfigSetup } from './ConfigSetup.js';
|
|
6
|
+
import { Command } from './Command.js';
|
|
7
|
+
export function ConfigThenCommand({ command, onConfigSave, }) {
|
|
8
|
+
const [configComplete, setConfigComplete] = React.useState(false);
|
|
9
|
+
const [savedConfig, setSavedConfig] = React.useState(null);
|
|
10
|
+
const handleConfigComplete = (config) => {
|
|
11
|
+
onConfigSave(config.apiKey, config.model);
|
|
12
|
+
setSavedConfig(config);
|
|
13
|
+
setConfigComplete(true);
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: [_jsx(ConfigSetup, { onComplete: handleConfigComplete }), configComplete && savedConfig && (_jsx(Command, { rawCommand: command, claudeService: createAnthropicService(savedConfig.apiKey, savedConfig.model) }))] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
export function History({ items }) {
|
|
4
|
+
if (items.length === 0) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return (_jsx(Box, { flexDirection: "column", gap: 1, children: items.map((item, index) => (_jsx(Box, { children: item }, index))) }));
|
|
8
|
+
}
|
package/dist/ui/Please.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box } from 'ink';
|
|
4
|
+
import { Command } from './Command.js';
|
|
2
5
|
import { Welcome } from './Welcome.js';
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
import { ConfigSetup } from './ConfigSetup.js';
|
|
7
|
+
import { History } from './History.js';
|
|
8
|
+
export const Please = ({ app: info, command, claudeService, showConfigSetup, onConfigComplete, }) => {
|
|
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 })] }));
|
|
13
|
+
}
|
|
14
|
+
// 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 }))] }));
|
|
5
16
|
};
|
package/dist/ui/Welcome.js
CHANGED
|
@@ -9,5 +9,5 @@ export function Welcome({ info: app }) {
|
|
|
9
9
|
const words = app.name
|
|
10
10
|
.split('-')
|
|
11
11
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1));
|
|
12
|
-
return (_jsx(Box, { alignSelf: "flex-start",
|
|
12
|
+
return (_jsx(Box, { alignSelf: "flex-start", children: _jsxs(Box, { borderStyle: "round", borderColor: "green", paddingX: 3, paddingY: 1, flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, gap: 1, children: [words.map((word, index) => (_jsx(Text, { color: "greenBright", bold: true, children: word }, index))), _jsxs(Text, { color: "whiteBright", dimColor: true, children: ["v", app.version] }), app.isDev && _jsx(Text, { color: "yellowBright", children: "dev" })] }), descriptionLines.map((line, index) => (_jsx(Box, { children: _jsxs(Text, { color: "white", children: [line, "."] }) }, index))), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "brightWhite", bold: true, children: "Usage:" }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "whiteBright", dimColor: true, children: ">" }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "greenBright", bold: true, children: "pls" }), _jsx(Text, { color: "yellow", bold: true, children: "[describe your request]" })] })] })] })] }) }));
|
|
13
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "tsc && chmod +x dist/index.js",
|
|
15
|
-
"dev": "
|
|
14
|
+
"build": "tsc && chmod +x dist/index.js && mkdir -p dist/config && cp src/config/*.md dist/config/",
|
|
15
|
+
"dev": "npm run build && tsc --watch",
|
|
16
16
|
"prepare": "npm run build",
|
|
17
17
|
"test": "vitest run",
|
|
18
18
|
"test:watch": "vitest",
|
|
@@ -40,8 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/aswitalski/pls#readme",
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@anthropic-ai/sdk": "^0.68.0",
|
|
43
44
|
"ink": "^6.4.0",
|
|
44
|
-
"
|
|
45
|
+
"ink-text-input": "^6.0.0",
|
|
46
|
+
"react": "^19.2.0",
|
|
47
|
+
"yaml": "^2.8.1"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"@types/node": "^20.10.6",
|