@yivan-lab/pretty-please 1.0.0 → 1.2.0
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 +381 -28
- package/bin/pls.tsx +1138 -109
- package/dist/bin/pls.d.ts +1 -1
- package/dist/bin/pls.js +994 -91
- package/dist/package.json +80 -0
- package/dist/src/ai.d.ts +1 -41
- package/dist/src/ai.js +9 -190
- package/dist/src/alias.d.ts +41 -0
- package/dist/src/alias.js +240 -0
- package/dist/src/builtin-detector.d.ts +14 -8
- package/dist/src/builtin-detector.js +36 -16
- package/dist/src/chat-history.d.ts +16 -11
- package/dist/src/chat-history.js +35 -4
- package/dist/src/components/Chat.js +5 -4
- package/dist/src/components/CodeColorizer.js +26 -20
- package/dist/src/components/CommandBox.js +3 -17
- package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
- package/dist/src/components/ConfirmationPrompt.js +9 -4
- package/dist/src/components/Duration.js +2 -1
- package/dist/src/components/InlineRenderer.js +2 -1
- package/dist/src/components/MarkdownDisplay.js +2 -1
- package/dist/src/components/MultiStepCommandGenerator.d.ts +5 -1
- package/dist/src/components/MultiStepCommandGenerator.js +127 -14
- package/dist/src/components/TableRenderer.js +2 -1
- package/dist/src/config.d.ts +59 -9
- package/dist/src/config.js +147 -48
- package/dist/src/history.d.ts +19 -5
- package/dist/src/history.js +26 -11
- package/dist/src/mastra-agent.d.ts +0 -1
- package/dist/src/mastra-agent.js +3 -4
- package/dist/src/mastra-chat.d.ts +28 -0
- package/dist/src/mastra-chat.js +93 -0
- package/dist/src/multi-step.d.ts +23 -7
- package/dist/src/multi-step.js +29 -6
- package/dist/src/prompts.d.ts +11 -0
- package/dist/src/prompts.js +140 -0
- package/dist/src/remote-history.d.ts +63 -0
- package/dist/src/remote-history.js +315 -0
- package/dist/src/remote.d.ts +113 -0
- package/dist/src/remote.js +634 -0
- package/dist/src/shell-hook.d.ts +87 -12
- package/dist/src/shell-hook.js +315 -17
- package/dist/src/sysinfo.d.ts +9 -5
- package/dist/src/sysinfo.js +2 -2
- package/dist/src/ui/theme.d.ts +27 -24
- package/dist/src/ui/theme.js +71 -21
- package/dist/src/upgrade.d.ts +41 -0
- package/dist/src/upgrade.js +348 -0
- package/dist/src/utils/console.d.ts +11 -11
- package/dist/src/utils/console.js +26 -17
- package/package.json +11 -9
- package/src/alias.ts +301 -0
- package/src/builtin-detector.ts +126 -0
- package/src/chat-history.ts +140 -0
- package/src/components/Chat.tsx +6 -5
- package/src/components/CodeColorizer.tsx +27 -19
- package/src/components/CommandBox.tsx +3 -17
- package/src/components/ConfirmationPrompt.tsx +11 -3
- package/src/components/Duration.tsx +2 -1
- package/src/components/InlineRenderer.tsx +2 -1
- package/src/components/MarkdownDisplay.tsx +2 -1
- package/src/components/MultiStepCommandGenerator.tsx +167 -16
- package/src/components/TableRenderer.tsx +2 -1
- package/src/config.ts +394 -0
- package/src/history.ts +160 -0
- package/src/mastra-agent.ts +3 -4
- package/src/mastra-chat.ts +124 -0
- package/src/multi-step.ts +45 -8
- package/src/prompts.ts +154 -0
- package/src/remote-history.ts +390 -0
- package/src/remote.ts +800 -0
- package/src/shell-hook.ts +754 -0
- package/src/{sysinfo.js → sysinfo.ts} +28 -16
- package/src/ui/theme.ts +101 -24
- package/src/upgrade.ts +397 -0
- package/src/utils/{console.js → console.ts} +36 -27
- package/bin/pls.js +0 -681
- package/src/ai.js +0 -324
- package/src/builtin-detector.js +0 -98
- package/src/chat-history.js +0 -94
- package/src/components/ChatStatus.tsx +0 -53
- package/src/components/CommandGenerator.tsx +0 -184
- package/src/components/ConfigDisplay.tsx +0 -64
- package/src/components/ConfigWizard.tsx +0 -101
- package/src/components/HistoryDisplay.tsx +0 -69
- package/src/components/HookManager.tsx +0 -150
- package/src/config.js +0 -221
- package/src/history.js +0 -131
- package/src/shell-hook.js +0 -393
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yivan-lab/pretty-please",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "AI 驱动的命令行工具,将自然语言转换为可执行的 Shell 命令",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pls": "./dist/bin/pls.js",
|
|
8
|
+
"please": "./dist/bin/pls.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsx bin/pls.tsx",
|
|
12
|
+
"build": "tsc && node scripts/postbuild.js",
|
|
13
|
+
"start": "node dist/bin/pls.js",
|
|
14
|
+
"link:dev": "mkdir -p ~/.local/bin && ln -sf \"$(pwd)/bin/pls.tsx\" ~/.local/bin/pls-dev && echo '✅ pls-dev 已链接到 ~/.local/bin/pls-dev'",
|
|
15
|
+
"unlink:dev": "rm -f ~/.local/bin/pls-dev && echo '✅ pls-dev 已移除'",
|
|
16
|
+
"prepublishOnly": "pnpm build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"ai",
|
|
21
|
+
"shell",
|
|
22
|
+
"openai",
|
|
23
|
+
"command-line",
|
|
24
|
+
"natural-language",
|
|
25
|
+
"terminal",
|
|
26
|
+
"assistant",
|
|
27
|
+
"mastra",
|
|
28
|
+
"deepseek"
|
|
29
|
+
],
|
|
30
|
+
"author": "yivan-lab",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/IvanLark/pretty-please.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/IvanLark/pretty-please/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/IvanLark/pretty-please#readme",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"bin",
|
|
45
|
+
"dist/bin",
|
|
46
|
+
"dist/src",
|
|
47
|
+
"dist/package.json",
|
|
48
|
+
"src",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE",
|
|
51
|
+
"tsconfig.json"
|
|
52
|
+
],
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@mastra/core": "^0.24.8",
|
|
55
|
+
"chalk": "^5.6.2",
|
|
56
|
+
"cli-highlight": "^2.1.11",
|
|
57
|
+
"commander": "^14.0.2",
|
|
58
|
+
"ink": "^6.5.1",
|
|
59
|
+
"ink-box": "^2.0.0",
|
|
60
|
+
"ink-markdown": "^1.0.4",
|
|
61
|
+
"ink-select-input": "^6.2.0",
|
|
62
|
+
"ink-spinner": "^5.0.0",
|
|
63
|
+
"ink-text-input": "^6.0.0",
|
|
64
|
+
"lowlight": "^3.3.0",
|
|
65
|
+
"marked": "^17.0.1",
|
|
66
|
+
"react": "^19.2.3",
|
|
67
|
+
"shiki": "^3.20.0",
|
|
68
|
+
"string-width": "^8.1.0",
|
|
69
|
+
"wrap-ansi": "^9.0.2",
|
|
70
|
+
"zod": "^3.25.76"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/hast": "^3.0.4",
|
|
74
|
+
"@types/node": "^25.0.2",
|
|
75
|
+
"@types/react": "^19.2.7",
|
|
76
|
+
"react-devtools-core": "^7.0.1",
|
|
77
|
+
"tsx": "^4.21.0",
|
|
78
|
+
"typescript": "^5.9.3"
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/src/ai.d.ts
CHANGED
|
@@ -1,45 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @param {string} prompt 用户输入的自然语言描述
|
|
4
|
-
* @param {object} options 选项
|
|
5
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
6
|
-
*/
|
|
7
|
-
export function generateCommand(prompt: string, options?: {
|
|
8
|
-
debug: boolean;
|
|
9
|
-
}): Promise<string | {
|
|
10
|
-
command: string;
|
|
11
|
-
debug: {
|
|
12
|
-
sysinfo: string;
|
|
13
|
-
model: any;
|
|
14
|
-
systemPrompt: string;
|
|
15
|
-
userPrompt: string;
|
|
16
|
-
};
|
|
17
|
-
}>;
|
|
18
|
-
/**
|
|
19
|
-
* 调用 AI 进行对话(chat 模式,支持流式输出)
|
|
20
|
-
* @param {string} prompt 用户输入的问题
|
|
21
|
-
* @param {object} options 选项
|
|
22
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
23
|
-
* @param {function} options.onChunk 流式输出回调,接收每个文本片段
|
|
24
|
-
*/
|
|
25
|
-
export function chatWithAI(prompt: string, options?: {
|
|
26
|
-
debug: boolean;
|
|
27
|
-
onChunk: Function;
|
|
28
|
-
}): Promise<string | {
|
|
29
|
-
reply: string;
|
|
30
|
-
debug: {
|
|
31
|
-
sysinfo: string;
|
|
32
|
-
model: any;
|
|
33
|
-
systemPrompt: string;
|
|
34
|
-
chatHistory: {
|
|
35
|
-
role: string;
|
|
36
|
-
content: string;
|
|
37
|
-
}[];
|
|
38
|
-
userPrompt: string;
|
|
39
|
-
};
|
|
40
|
-
}>;
|
|
41
|
-
/**
|
|
42
|
-
* 生成系统提示词
|
|
2
|
+
* 生成命令生成模式的系统提示词
|
|
43
3
|
* @param {string} sysinfo - 系统信息
|
|
44
4
|
* @param {string} plsHistory - pls 命令历史
|
|
45
5
|
* @param {string} shellHistory - shell 终端历史
|
package/dist/src/ai.js
CHANGED
|
@@ -1,27 +1,11 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import { getConfig } from './config.js';
|
|
3
|
-
import { formatSystemInfo } from './sysinfo.js';
|
|
4
|
-
import { formatHistoryForAI } from './history.js';
|
|
5
|
-
import { formatShellHistoryForAI, getShellHistory } from './shell-hook.js';
|
|
6
|
-
import { getChatHistory, addChatMessage } from './chat-history.js';
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
function createClient() {
|
|
11
|
-
const config = getConfig();
|
|
12
|
-
return new OpenAI({
|
|
13
|
-
apiKey: config.apiKey,
|
|
14
|
-
baseURL: config.baseUrl
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* 生成系统提示词
|
|
2
|
+
* 生成命令生成模式的系统提示词
|
|
19
3
|
* @param {string} sysinfo - 系统信息
|
|
20
4
|
* @param {string} plsHistory - pls 命令历史
|
|
21
5
|
* @param {string} shellHistory - shell 终端历史
|
|
22
6
|
* @param {boolean} shellHookEnabled - 是否启用了 shell hook
|
|
23
7
|
*/
|
|
24
|
-
function buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled) {
|
|
8
|
+
export function buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled) {
|
|
25
9
|
let prompt = `你是一个专业的 shell 脚本生成器。用户会提供他们的系统信息和一个命令需求。
|
|
26
10
|
你的任务是返回一个可执行的、原始的 shell 命令或脚本来完成他们的目标。
|
|
27
11
|
|
|
@@ -51,8 +35,8 @@ function buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled)
|
|
|
51
35
|
{
|
|
52
36
|
"command": "find . -name '*.log' -size +100M",
|
|
53
37
|
"continue": true,
|
|
54
|
-
"reasoning": "查找大日志",
|
|
55
|
-
"nextStepHint": "压缩找到的文件"
|
|
38
|
+
"reasoning": "查找大日志", (精简即可)
|
|
39
|
+
"nextStepHint": "压缩找到的文件" (精简即可)
|
|
56
40
|
}
|
|
57
41
|
|
|
58
42
|
执行后你会收到:
|
|
@@ -102,13 +86,15 @@ mv: rename ./test.zip to ./c/test.zip: No such file or directory
|
|
|
102
86
|
"reasoning": "改用 cp 复制而非 mv"
|
|
103
87
|
}
|
|
104
88
|
|
|
105
|
-
|
|
89
|
+
或者如果决定放弃(无法修正),返回:
|
|
106
90
|
{
|
|
107
|
-
"command": "
|
|
91
|
+
"command": "",
|
|
108
92
|
"continue": false,
|
|
109
|
-
"reasoning": "
|
|
93
|
+
"reasoning": "文件不存在且无法恢复,任务无法继续"
|
|
110
94
|
}
|
|
111
95
|
|
|
96
|
+
重要:当 continue: false 且决定放弃时,command 可以留空,重点是在 reasoning 中说明为什么放弃。
|
|
97
|
+
|
|
112
98
|
关于 pls/please 工具:
|
|
113
99
|
用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
|
|
114
100
|
当用户输入 "pls <描述>" 时,AI(也就是你)会生成对应的 shell 命令供用户确认执行。
|
|
@@ -126,170 +112,3 @@ mv: rename ./test.zip to ./c/test.zip: No such file or directory
|
|
|
126
112
|
}
|
|
127
113
|
return prompt;
|
|
128
114
|
}
|
|
129
|
-
// 导出给其他模块使用
|
|
130
|
-
export { buildSystemPrompt };
|
|
131
|
-
/**
|
|
132
|
-
* 调用 AI 生成命令
|
|
133
|
-
* @param {string} prompt 用户输入的自然语言描述
|
|
134
|
-
* @param {object} options 选项
|
|
135
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
136
|
-
*/
|
|
137
|
-
export async function generateCommand(prompt, options = {}) {
|
|
138
|
-
const config = getConfig();
|
|
139
|
-
const client = createClient();
|
|
140
|
-
const sysinfo = formatSystemInfo();
|
|
141
|
-
const plsHistory = formatHistoryForAI();
|
|
142
|
-
const shellHistory = formatShellHistoryForAI();
|
|
143
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
|
|
144
|
-
const systemPrompt = buildSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled);
|
|
145
|
-
const response = await client.chat.completions.create({
|
|
146
|
-
model: config.model,
|
|
147
|
-
messages: [
|
|
148
|
-
{
|
|
149
|
-
role: 'system',
|
|
150
|
-
content: systemPrompt
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
role: 'user',
|
|
154
|
-
content: prompt
|
|
155
|
-
}
|
|
156
|
-
],
|
|
157
|
-
max_tokens: 1024,
|
|
158
|
-
temperature: 0.2
|
|
159
|
-
});
|
|
160
|
-
const command = response.choices[0]?.message?.content?.trim();
|
|
161
|
-
if (!command) {
|
|
162
|
-
throw new Error('AI 返回了空的响应');
|
|
163
|
-
}
|
|
164
|
-
if (options.debug) {
|
|
165
|
-
return {
|
|
166
|
-
command,
|
|
167
|
-
debug: {
|
|
168
|
-
sysinfo,
|
|
169
|
-
model: config.model,
|
|
170
|
-
systemPrompt,
|
|
171
|
-
userPrompt: prompt
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
return command;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* 生成 chat 模式的系统提示词
|
|
179
|
-
* @param {string} sysinfo - 系统信息
|
|
180
|
-
* @param {string} plsHistory - pls 命令历史
|
|
181
|
-
* @param {string} shellHistory - shell 终端历史
|
|
182
|
-
* @param {boolean} shellHookEnabled - 是否启用了 shell hook
|
|
183
|
-
*/
|
|
184
|
-
function buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled) {
|
|
185
|
-
let prompt = `你是一个命令行专家助手,帮助用户理解和使用命令行工具。
|
|
186
|
-
|
|
187
|
-
【你的能力】
|
|
188
|
-
- 解释命令的含义、参数、用法
|
|
189
|
-
- 分析命令的执行效果和潜在风险
|
|
190
|
-
- 回答命令行、Shell、系统管理相关问题
|
|
191
|
-
- 根据用户需求推荐合适的命令并解释
|
|
192
|
-
|
|
193
|
-
【回答要求】
|
|
194
|
-
- 简洁清晰,避免冗余
|
|
195
|
-
- 危险操作要明确警告
|
|
196
|
-
- 适当给出示例命令
|
|
197
|
-
- 结合用户的系统环境给出针对性建议
|
|
198
|
-
|
|
199
|
-
【用户系统信息】
|
|
200
|
-
${sysinfo}`;
|
|
201
|
-
// 根据是否启用 shell hook 决定展示哪个历史
|
|
202
|
-
if (shellHookEnabled && shellHistory) {
|
|
203
|
-
prompt += `\n\n${shellHistory}`;
|
|
204
|
-
}
|
|
205
|
-
else if (plsHistory) {
|
|
206
|
-
prompt += `\n\n${plsHistory}`;
|
|
207
|
-
}
|
|
208
|
-
return prompt;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* 调用 AI 进行对话(chat 模式,支持流式输出)
|
|
212
|
-
* @param {string} prompt 用户输入的问题
|
|
213
|
-
* @param {object} options 选项
|
|
214
|
-
* @param {boolean} options.debug 是否返回调试信息
|
|
215
|
-
* @param {function} options.onChunk 流式输出回调,接收每个文本片段
|
|
216
|
-
*/
|
|
217
|
-
export async function chatWithAI(prompt, options = {}) {
|
|
218
|
-
const config = getConfig();
|
|
219
|
-
const client = createClient();
|
|
220
|
-
const sysinfo = formatSystemInfo();
|
|
221
|
-
const plsHistory = formatHistoryForAI();
|
|
222
|
-
const shellHistory = formatShellHistoryForAI();
|
|
223
|
-
const shellHookEnabled = config.shellHook && getShellHistory().length > 0;
|
|
224
|
-
const systemPrompt = buildChatSystemPrompt(sysinfo, plsHistory, shellHistory, shellHookEnabled);
|
|
225
|
-
// 获取对话历史
|
|
226
|
-
const chatHistory = getChatHistory();
|
|
227
|
-
// 构建消息数组
|
|
228
|
-
const messages = [
|
|
229
|
-
{ role: 'system', content: systemPrompt },
|
|
230
|
-
...chatHistory,
|
|
231
|
-
{ role: 'user', content: prompt }
|
|
232
|
-
];
|
|
233
|
-
// 流式输出模式
|
|
234
|
-
if (options.onChunk) {
|
|
235
|
-
const stream = await client.chat.completions.create({
|
|
236
|
-
model: config.model,
|
|
237
|
-
messages,
|
|
238
|
-
max_tokens: 2048,
|
|
239
|
-
temperature: 0.7,
|
|
240
|
-
stream: true
|
|
241
|
-
});
|
|
242
|
-
let fullContent = '';
|
|
243
|
-
for await (const chunk of stream) {
|
|
244
|
-
const content = chunk.choices[0]?.delta?.content || '';
|
|
245
|
-
if (content) {
|
|
246
|
-
fullContent += content;
|
|
247
|
-
options.onChunk(content);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (!fullContent) {
|
|
251
|
-
throw new Error('AI 返回了空的响应');
|
|
252
|
-
}
|
|
253
|
-
// 保存对话历史
|
|
254
|
-
addChatMessage(prompt, fullContent);
|
|
255
|
-
if (options.debug) {
|
|
256
|
-
return {
|
|
257
|
-
reply: fullContent,
|
|
258
|
-
debug: {
|
|
259
|
-
sysinfo,
|
|
260
|
-
model: config.model,
|
|
261
|
-
systemPrompt,
|
|
262
|
-
chatHistory,
|
|
263
|
-
userPrompt: prompt
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
return fullContent;
|
|
268
|
-
}
|
|
269
|
-
// 非流式模式(保持兼容)
|
|
270
|
-
const response = await client.chat.completions.create({
|
|
271
|
-
model: config.model,
|
|
272
|
-
messages,
|
|
273
|
-
max_tokens: 2048,
|
|
274
|
-
temperature: 0.7
|
|
275
|
-
});
|
|
276
|
-
const reply = response.choices[0]?.message?.content?.trim();
|
|
277
|
-
if (!reply) {
|
|
278
|
-
throw new Error('AI 返回了空的响应');
|
|
279
|
-
}
|
|
280
|
-
// 保存对话历史
|
|
281
|
-
addChatMessage(prompt, reply);
|
|
282
|
-
if (options.debug) {
|
|
283
|
-
return {
|
|
284
|
-
reply,
|
|
285
|
-
debug: {
|
|
286
|
-
sysinfo,
|
|
287
|
-
model: config.model,
|
|
288
|
-
systemPrompt,
|
|
289
|
-
chatHistory,
|
|
290
|
-
userPrompt: prompt
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
return reply;
|
|
295
|
-
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type AliasConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* 别名解析结果
|
|
4
|
+
*/
|
|
5
|
+
export interface AliasResolveResult {
|
|
6
|
+
resolved: boolean;
|
|
7
|
+
prompt: string;
|
|
8
|
+
aliasName?: string;
|
|
9
|
+
originalInput?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 获取所有别名
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAliases(): Record<string, AliasConfig>;
|
|
15
|
+
/**
|
|
16
|
+
* 添加别名
|
|
17
|
+
* @param name 别名名称
|
|
18
|
+
* @param prompt 对应的 prompt
|
|
19
|
+
* @param description 可选描述
|
|
20
|
+
* @param reservedCommands 保留的子命令列表(动态传入)
|
|
21
|
+
*/
|
|
22
|
+
export declare function addAlias(name: string, prompt: string, description?: string, reservedCommands?: string[]): void;
|
|
23
|
+
/**
|
|
24
|
+
* 删除别名
|
|
25
|
+
*/
|
|
26
|
+
export declare function removeAlias(name: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 解析别名
|
|
29
|
+
* 支持 `pls disk` 和 `pls @disk` 两种格式
|
|
30
|
+
* @param input 用户输入(可能是别名或普通 prompt)
|
|
31
|
+
* @returns 解析结果
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveAlias(input: string): AliasResolveResult;
|
|
34
|
+
/**
|
|
35
|
+
* 显示所有别名
|
|
36
|
+
*/
|
|
37
|
+
export declare function displayAliases(): void;
|
|
38
|
+
/**
|
|
39
|
+
* 获取别名的参数信息(用于帮助显示)
|
|
40
|
+
*/
|
|
41
|
+
export declare function getAliasParams(aliasName: string): string[];
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getConfig, saveConfig } from './config.js';
|
|
3
|
+
import { getCurrentTheme } from './ui/theme.js';
|
|
4
|
+
// 获取主题颜色
|
|
5
|
+
function getColors() {
|
|
6
|
+
const theme = getCurrentTheme();
|
|
7
|
+
return {
|
|
8
|
+
primary: theme.primary,
|
|
9
|
+
secondary: theme.secondary,
|
|
10
|
+
success: theme.success,
|
|
11
|
+
error: theme.error,
|
|
12
|
+
warning: theme.warning,
|
|
13
|
+
muted: theme.text.muted,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 获取所有别名
|
|
18
|
+
*/
|
|
19
|
+
export function getAliases() {
|
|
20
|
+
const config = getConfig();
|
|
21
|
+
return config.aliases || {};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 添加别名
|
|
25
|
+
* @param name 别名名称
|
|
26
|
+
* @param prompt 对应的 prompt
|
|
27
|
+
* @param description 可选描述
|
|
28
|
+
* @param reservedCommands 保留的子命令列表(动态传入)
|
|
29
|
+
*/
|
|
30
|
+
export function addAlias(name, prompt, description, reservedCommands = []) {
|
|
31
|
+
// 验证别名名称
|
|
32
|
+
if (!name || !name.trim()) {
|
|
33
|
+
throw new Error('别名名称不能为空');
|
|
34
|
+
}
|
|
35
|
+
// 移除可能的 @ 前缀
|
|
36
|
+
const aliasName = name.startsWith('@') ? name.slice(1) : name;
|
|
37
|
+
// 验证别名名称格式(只允许字母、数字、下划线、连字符)
|
|
38
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(aliasName)) {
|
|
39
|
+
throw new Error('别名名称只能包含字母、数字、下划线和连字符');
|
|
40
|
+
}
|
|
41
|
+
// 检查是否与保留命令冲突
|
|
42
|
+
if (reservedCommands.includes(aliasName)) {
|
|
43
|
+
throw new Error(`"${aliasName}" 是保留的子命令,不能用作别名`);
|
|
44
|
+
}
|
|
45
|
+
// 验证 prompt
|
|
46
|
+
if (!prompt || !prompt.trim()) {
|
|
47
|
+
throw new Error('prompt 不能为空');
|
|
48
|
+
}
|
|
49
|
+
const config = getConfig();
|
|
50
|
+
if (!config.aliases) {
|
|
51
|
+
config.aliases = {};
|
|
52
|
+
}
|
|
53
|
+
config.aliases[aliasName] = {
|
|
54
|
+
prompt: prompt.trim(),
|
|
55
|
+
description: description?.trim(),
|
|
56
|
+
};
|
|
57
|
+
saveConfig(config);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 删除别名
|
|
61
|
+
*/
|
|
62
|
+
export function removeAlias(name) {
|
|
63
|
+
// 移除可能的 @ 前缀
|
|
64
|
+
const aliasName = name.startsWith('@') ? name.slice(1) : name;
|
|
65
|
+
const config = getConfig();
|
|
66
|
+
if (!config.aliases || !config.aliases[aliasName]) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
delete config.aliases[aliasName];
|
|
70
|
+
saveConfig(config);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 解析参数模板
|
|
75
|
+
* 支持格式:{{param}} 或 {{param:default}}
|
|
76
|
+
*/
|
|
77
|
+
function parseTemplateParams(prompt) {
|
|
78
|
+
const regex = /\{\{([^}:]+)(?::[^}]*)?\}\}/g;
|
|
79
|
+
const params = [];
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = regex.exec(prompt)) !== null) {
|
|
82
|
+
if (!params.includes(match[1])) {
|
|
83
|
+
params.push(match[1]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return params;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 替换模板参数
|
|
90
|
+
* @param prompt 原始 prompt(可能包含模板参数)
|
|
91
|
+
* @param args 用户提供的参数(key=value 或 --key=value 格式)
|
|
92
|
+
*/
|
|
93
|
+
function replaceTemplateParams(prompt, args) {
|
|
94
|
+
// 解析用户参数
|
|
95
|
+
const userParams = {};
|
|
96
|
+
for (const arg of args) {
|
|
97
|
+
// 支持 --key=value 或 key=value 格式
|
|
98
|
+
const cleanArg = arg.startsWith('--') ? arg.slice(2) : arg;
|
|
99
|
+
const eqIndex = cleanArg.indexOf('=');
|
|
100
|
+
if (eqIndex > 0) {
|
|
101
|
+
const key = cleanArg.slice(0, eqIndex);
|
|
102
|
+
const value = cleanArg.slice(eqIndex + 1);
|
|
103
|
+
userParams[key] = value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 替换模板参数
|
|
107
|
+
let result = prompt;
|
|
108
|
+
// 匹配 {{param}} 或 {{param:default}}
|
|
109
|
+
result = result.replace(/\{\{([^}:]+)(?::([^}]*))?\}\}/g, (match, param, defaultValue) => {
|
|
110
|
+
if (userParams[param] !== undefined) {
|
|
111
|
+
return userParams[param];
|
|
112
|
+
}
|
|
113
|
+
if (defaultValue !== undefined) {
|
|
114
|
+
return defaultValue;
|
|
115
|
+
}
|
|
116
|
+
// 没有提供值也没有默认值,保留原样(后面会报错或让用户补充)
|
|
117
|
+
return match;
|
|
118
|
+
});
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 检查是否还有未替换的模板参数
|
|
123
|
+
*/
|
|
124
|
+
function hasUnresolvedParams(prompt) {
|
|
125
|
+
const regex = /\{\{([^}:]+)\}\}/g;
|
|
126
|
+
const unresolved = [];
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = regex.exec(prompt)) !== null) {
|
|
129
|
+
unresolved.push(match[1]);
|
|
130
|
+
}
|
|
131
|
+
return unresolved;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 解析别名
|
|
135
|
+
* 支持 `pls disk` 和 `pls @disk` 两种格式
|
|
136
|
+
* @param input 用户输入(可能是别名或普通 prompt)
|
|
137
|
+
* @returns 解析结果
|
|
138
|
+
*/
|
|
139
|
+
export function resolveAlias(input) {
|
|
140
|
+
const parts = input.trim().split(/\s+/);
|
|
141
|
+
if (parts.length === 0) {
|
|
142
|
+
return { resolved: false, prompt: input };
|
|
143
|
+
}
|
|
144
|
+
let aliasName = parts[0];
|
|
145
|
+
const restArgs = parts.slice(1);
|
|
146
|
+
// 支持 @ 前缀
|
|
147
|
+
if (aliasName.startsWith('@')) {
|
|
148
|
+
aliasName = aliasName.slice(1);
|
|
149
|
+
}
|
|
150
|
+
const aliases = getAliases();
|
|
151
|
+
const aliasConfig = aliases[aliasName];
|
|
152
|
+
if (!aliasConfig) {
|
|
153
|
+
return { resolved: false, prompt: input };
|
|
154
|
+
}
|
|
155
|
+
// 检查是否有模板参数
|
|
156
|
+
const templateParams = parseTemplateParams(aliasConfig.prompt);
|
|
157
|
+
let resolvedPrompt;
|
|
158
|
+
if (templateParams.length > 0) {
|
|
159
|
+
// 有模板参数,进行替换
|
|
160
|
+
resolvedPrompt = replaceTemplateParams(aliasConfig.prompt, restArgs);
|
|
161
|
+
// 检查是否还有未替换的必填参数
|
|
162
|
+
const unresolved = hasUnresolvedParams(resolvedPrompt);
|
|
163
|
+
if (unresolved.length > 0) {
|
|
164
|
+
throw new Error(`别名 "${aliasName}" 缺少必填参数: ${unresolved.join(', ')}`);
|
|
165
|
+
}
|
|
166
|
+
// 过滤掉已用于参数替换的 args,剩余的追加到 prompt
|
|
167
|
+
const usedArgs = restArgs.filter((arg) => {
|
|
168
|
+
const cleanArg = arg.startsWith('--') ? arg.slice(2) : arg;
|
|
169
|
+
return cleanArg.includes('=');
|
|
170
|
+
});
|
|
171
|
+
const extraArgs = restArgs.filter((arg) => !usedArgs.includes(arg));
|
|
172
|
+
if (extraArgs.length > 0) {
|
|
173
|
+
resolvedPrompt = `${resolvedPrompt} ${extraArgs.join(' ')}`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// 没有模板参数,直接追加额外内容
|
|
178
|
+
if (restArgs.length > 0) {
|
|
179
|
+
resolvedPrompt = `${aliasConfig.prompt} ${restArgs.join(' ')}`;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
resolvedPrompt = aliasConfig.prompt;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
resolved: true,
|
|
187
|
+
prompt: resolvedPrompt,
|
|
188
|
+
aliasName,
|
|
189
|
+
originalInput: input,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 显示所有别名
|
|
194
|
+
*/
|
|
195
|
+
export function displayAliases() {
|
|
196
|
+
const aliases = getAliases();
|
|
197
|
+
const colors = getColors();
|
|
198
|
+
const aliasNames = Object.keys(aliases);
|
|
199
|
+
console.log('');
|
|
200
|
+
if (aliasNames.length === 0) {
|
|
201
|
+
console.log(chalk.gray(' 暂无别名'));
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(chalk.gray(' 使用 pls alias add <name> "<prompt>" 添加别名'));
|
|
204
|
+
console.log('');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
console.log(chalk.bold('命令别名:'));
|
|
208
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
209
|
+
for (const name of aliasNames) {
|
|
210
|
+
const alias = aliases[name];
|
|
211
|
+
const params = parseTemplateParams(alias.prompt);
|
|
212
|
+
// 别名名称
|
|
213
|
+
let line = ` ${chalk.hex(colors.primary)(name)}`;
|
|
214
|
+
// 如果有参数,显示参数
|
|
215
|
+
if (params.length > 0) {
|
|
216
|
+
line += chalk.gray(` <${params.join('> <')}>`);
|
|
217
|
+
}
|
|
218
|
+
console.log(line);
|
|
219
|
+
// prompt 内容
|
|
220
|
+
console.log(` ${chalk.gray('→')} ${alias.prompt}`);
|
|
221
|
+
// 描述
|
|
222
|
+
if (alias.description) {
|
|
223
|
+
console.log(` ${chalk.gray(alias.description)}`);
|
|
224
|
+
}
|
|
225
|
+
console.log('');
|
|
226
|
+
}
|
|
227
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
228
|
+
console.log(chalk.gray('使用: pls <alias> 或 pls @<alias>'));
|
|
229
|
+
console.log('');
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 获取别名的参数信息(用于帮助显示)
|
|
233
|
+
*/
|
|
234
|
+
export function getAliasParams(aliasName) {
|
|
235
|
+
const aliases = getAliases();
|
|
236
|
+
const alias = aliases[aliasName];
|
|
237
|
+
if (!alias)
|
|
238
|
+
return [];
|
|
239
|
+
return parseTemplateParams(alias.prompt);
|
|
240
|
+
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Shell builtin 命令检测器
|
|
3
|
+
*
|
|
4
|
+
* 用于检测命令中是否包含 shell 内置命令(builtin)
|
|
5
|
+
* 这些命令在子进程中执行可能无效或行为异常
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Builtin 检测结果
|
|
5
9
|
*/
|
|
6
|
-
export
|
|
10
|
+
export interface BuiltinResult {
|
|
7
11
|
hasBuiltin: boolean;
|
|
8
12
|
builtins: string[];
|
|
9
|
-
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 检测命令中是否包含 builtin
|
|
16
|
+
*/
|
|
17
|
+
export declare function detectBuiltin(command: string): BuiltinResult;
|
|
10
18
|
/**
|
|
11
19
|
* 格式化 builtin 列表为易读的字符串
|
|
12
|
-
* @param {string[]} builtins - builtin 命令数组
|
|
13
|
-
* @returns {string} 格式化后的字符串
|
|
14
20
|
*/
|
|
15
|
-
export function formatBuiltins(builtins: string[]): string;
|
|
21
|
+
export declare function formatBuiltins(builtins: string[]): string;
|