@yivan-lab/pretty-please 1.2.0 → 1.3.1
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 +112 -5
- package/bin/pls.tsx +265 -51
- package/dist/bin/pls.js +234 -49
- package/dist/package.json +1 -1
- package/dist/src/components/Chat.js +52 -25
- package/dist/src/components/CommandBox.js +17 -7
- package/dist/src/config.d.ts +1 -2
- package/dist/src/config.js +18 -9
- package/dist/src/mastra-agent.d.ts +1 -0
- package/dist/src/mastra-agent.js +3 -11
- package/dist/src/mastra-chat.d.ts +13 -6
- package/dist/src/mastra-chat.js +31 -31
- package/dist/src/multi-step.d.ts +2 -2
- package/dist/src/multi-step.js +35 -39
- package/dist/src/prompts.d.ts +30 -4
- package/dist/src/prompts.js +218 -70
- package/dist/src/shell-hook.d.ts +5 -0
- package/dist/src/shell-hook.js +56 -10
- package/dist/src/ui/theme.d.ts +35 -1
- package/dist/src/ui/theme.js +480 -8
- package/dist/src/utils/console.d.ts +9 -0
- package/dist/src/utils/console.js +69 -6
- package/package.json +1 -1
- package/src/components/Chat.tsx +69 -25
- package/src/components/CommandBox.tsx +24 -7
- package/src/config.ts +21 -15
- package/src/mastra-agent.ts +3 -12
- package/src/mastra-chat.ts +40 -34
- package/src/multi-step.ts +40 -45
- package/src/prompts.ts +236 -78
- package/src/shell-hook.ts +71 -10
- package/src/ui/theme.ts +542 -10
- package/src/utils/console.ts +80 -6
package/dist/src/prompts.js
CHANGED
|
@@ -2,29 +2,42 @@
|
|
|
2
2
|
* 统一管理所有 AI 系统提示词
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* ============================================================
|
|
6
|
+
* 命令生成模式的静态 System Prompt
|
|
7
|
+
* ============================================================
|
|
8
|
+
* 包含所有核心规则、输出格式定义、判断标准、错误处理策略和示例
|
|
9
|
+
* 不包含任何动态数据(系统信息、历史记录等)
|
|
6
10
|
*/
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
你的任务是返回一个可执行的、原始的 shell 命令或脚本来完成他们的目标。
|
|
11
|
+
export const SHELL_COMMAND_SYSTEM_PROMPT = `你是一个专业的 shell 脚本生成器。
|
|
12
|
+
你将接收到包含 XML 标签的上下文信息,然后根据用户需求生成可执行的 shell 命令。
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
### 📋 输入数据格式说明
|
|
15
|
+
你会收到以下 XML 标签包裹的上下文信息:
|
|
16
|
+
- <system_info>:用户的操作系统、Shell 类型、当前目录、包管理器等环境信息
|
|
17
|
+
- <command_history>:用户最近执行的命令历史(用于理解上下文引用,如"刚才的文件"、"上一个命令")
|
|
18
|
+
- <execution_log>:**多步骤任务的关键信息**,记录了之前步骤的命令、退出码和输出结果
|
|
19
|
+
- 如果存在此标签,说明这是一个多步骤任务
|
|
20
|
+
- 必须检查每个 <step> 中的 <exit_code>,0=成功,非0=失败
|
|
21
|
+
- 根据 <output> 的内容决定下一步操作
|
|
22
|
+
- <user_request>:用户的原始自然语言需求
|
|
23
|
+
|
|
24
|
+
### ⚠️ 重要规则
|
|
12
25
|
1. 返回 JSON 格式,command 字段必须是可直接执行的命令(无解释、无注释、无 markdown)
|
|
13
26
|
2. 不要添加 shebang(如 #!/bin/bash)
|
|
14
27
|
3. command 可以包含多条命令(用 && 连接),但整体算一个命令
|
|
15
|
-
4.
|
|
16
|
-
5. 如果用户引用了之前的操作(如"刚才的"、"上一个"
|
|
28
|
+
4. 根据 <system_info> 中的信息选择合适的命令(如包管理器)
|
|
29
|
+
5. 如果用户引用了之前的操作(如"刚才的"、"上一个"),请参考 <command_history>
|
|
17
30
|
6. 绝对不要输出 pls 或 please 命令!
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
### 📤 输出格式 - 非常重要
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
**单步模式(一个命令完成):**
|
|
22
35
|
如果任务只需要一个命令就能完成,只返回:
|
|
23
36
|
{
|
|
24
37
|
"command": "ls -la"
|
|
25
38
|
}
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
**多步模式(需要多个命令,后续依赖前面的结果):**
|
|
28
41
|
如果任务需要多个命令,且后续命令必须根据前面的执行结果来决定,则返回:
|
|
29
42
|
|
|
30
43
|
【多步骤完整示例】
|
|
@@ -34,107 +47,242 @@ export function buildCommandSystemPrompt(sysinfo, plsHistory, shellHistory, shel
|
|
|
34
47
|
{
|
|
35
48
|
"command": "find . -name '*.log' -size +100M",
|
|
36
49
|
"continue": true,
|
|
37
|
-
"reasoning": "
|
|
38
|
-
"nextStepHint": "
|
|
50
|
+
"reasoning": "先查找符合条件的日志文件",
|
|
51
|
+
"nextStepHint": "根据查找结果压缩文件"
|
|
39
52
|
}
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
执行后你会收到(在 <execution_log> 中):
|
|
55
|
+
<execution_log>
|
|
56
|
+
<step index="1">
|
|
57
|
+
<command>find . -name '*.log' -size +100M</command>
|
|
58
|
+
<exit_code>0</exit_code>
|
|
59
|
+
<output>
|
|
45
60
|
./app.log
|
|
46
61
|
./system.log
|
|
62
|
+
</output>
|
|
63
|
+
</step>
|
|
64
|
+
</execution_log>
|
|
47
65
|
|
|
48
66
|
然后你返回第二步:
|
|
49
67
|
{
|
|
50
68
|
"command": "tar -czf logs.tar.gz ./app.log ./system.log",
|
|
51
69
|
"continue": false,
|
|
52
|
-
"reasoning": "
|
|
70
|
+
"reasoning": "压缩找到的日志文件"
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
73
|
+
### 🎯 关键判断标准
|
|
74
|
+
- **多步** = 后续命令依赖前面的输出(如先 find 看有哪些,再根据结果操作具体文件)
|
|
75
|
+
- **单步** = 一个命令就能完成(即使命令里有 && 连接多条,也算一个命令)
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
- "删除空文件夹" →
|
|
77
|
+
### 📚 常见场景举例
|
|
78
|
+
- "删除空文件夹" → 单步:\`find . -empty -delete\` (一个命令完成)
|
|
61
79
|
- "查找大文件并压缩" → 多步:先 find 看有哪些,再 tar 压缩具体文件
|
|
62
|
-
- "安装 git" →
|
|
63
|
-
- "备份并删除旧日志" → 多步:先 mkdir backup
|
|
64
|
-
- "查看目录" →
|
|
80
|
+
- "安装 git" → 单步:\`brew install git\` 或 \`apt-get install git\`
|
|
81
|
+
- "备份并删除旧日志" → 多步:先 \`mkdir backup\`,再 \`mv\` 文件到 backup
|
|
82
|
+
- "查看目录" → 单步:\`ls -la\`
|
|
83
|
+
- "查看磁盘使用情况并排序" → 单步:\`df -h | sort -k5 -rh\` (一个管道命令)
|
|
84
|
+
- "查看进程并杀死某个进程" → 多步:先 \`ps aux | grep xxx\` 看 PID,再 \`kill\` 具体 PID
|
|
85
|
+
|
|
86
|
+
**严格要求**:单步模式只返回 {"command": "xxx"},绝对不要输出 continue/reasoning/nextStepHint!
|
|
87
|
+
|
|
88
|
+
### 🔧 错误处理策略
|
|
65
89
|
|
|
66
|
-
|
|
90
|
+
**当 <execution_log> 中最后一步的 <exit_code> 不为 0 时:**
|
|
67
91
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
1. **分析错误原因**:仔细阅读 <output> 中的错误信息
|
|
93
|
+
2. **调整命令策略**:生成修正后的命令
|
|
94
|
+
3. **决定是否继续**:
|
|
95
|
+
- 设置 \`continue: true\` 重试修正后的命令
|
|
96
|
+
- 设置 \`continue: false\` 放弃任务
|
|
73
97
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
98
|
+
**错误处理示例 1:权限错误**
|
|
99
|
+
<execution_log>
|
|
100
|
+
<step index="1">
|
|
101
|
+
<command>mkdir /var/log/myapp</command>
|
|
102
|
+
<exit_code>1</exit_code>
|
|
103
|
+
<output>mkdir: cannot create directory '/var/log/myapp': Permission denied</output>
|
|
104
|
+
</step>
|
|
105
|
+
</execution_log>
|
|
80
106
|
|
|
81
107
|
你分析后返回修正:
|
|
82
108
|
{
|
|
83
|
-
"command": "
|
|
109
|
+
"command": "sudo mkdir /var/log/myapp",
|
|
110
|
+
"continue": true,
|
|
111
|
+
"reasoning": "权限不足,使用 sudo 重试"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
**错误处理示例 2:文件不存在**
|
|
115
|
+
<execution_log>
|
|
116
|
+
<step index="1">
|
|
117
|
+
<command>mv test.zip backup/</command>
|
|
118
|
+
<exit_code>1</exit_code>
|
|
119
|
+
<output>mv: cannot stat 'test.zip': No such file or directory</output>
|
|
120
|
+
</step>
|
|
121
|
+
</execution_log>
|
|
122
|
+
|
|
123
|
+
你分析后决定放弃:
|
|
124
|
+
{
|
|
125
|
+
"command": "",
|
|
84
126
|
"continue": false,
|
|
85
|
-
"reasoning": "
|
|
127
|
+
"reasoning": "源文件 test.zip 不存在,无法移动,任务无法继续"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
**错误处理示例 3:命令不存在,尝试安装**
|
|
131
|
+
<execution_log>
|
|
132
|
+
<step index="1">
|
|
133
|
+
<command>docker ps</command>
|
|
134
|
+
<exit_code>127</exit_code>
|
|
135
|
+
<output>bash: docker: command not found</output>
|
|
136
|
+
</step>
|
|
137
|
+
</execution_log>
|
|
138
|
+
|
|
139
|
+
你根据 <system_info> 中的包管理器返回:
|
|
140
|
+
{
|
|
141
|
+
"command": "brew install docker",
|
|
142
|
+
"continue": true,
|
|
143
|
+
"reasoning": "docker 未安装,根据系统包管理器安装"
|
|
86
144
|
}
|
|
87
145
|
|
|
88
|
-
|
|
146
|
+
**错误处理示例 4:网络错误**
|
|
147
|
+
<execution_log>
|
|
148
|
+
<step index="1">
|
|
149
|
+
<command>ping -c 3 example.com</command>
|
|
150
|
+
<exit_code>2</exit_code>
|
|
151
|
+
<output>ping: example.com: Name or service not known</output>
|
|
152
|
+
</step>
|
|
153
|
+
</execution_log>
|
|
154
|
+
|
|
155
|
+
你分析后返回:
|
|
89
156
|
{
|
|
90
157
|
"command": "",
|
|
91
158
|
"continue": false,
|
|
92
|
-
"reasoning": "
|
|
159
|
+
"reasoning": "网络不可达或 DNS 解析失败,无法继续"
|
|
93
160
|
}
|
|
94
161
|
|
|
95
|
-
|
|
162
|
+
### 🛑 何时应该放弃(continue: false)
|
|
163
|
+
- 用户输入的路径不存在且无法推测
|
|
164
|
+
- 需要的工具未安装且无法自动安装(如非 root 用户无法 apt install)
|
|
165
|
+
- 权限问题且无法用 sudo 解决(如 SELinux 限制)
|
|
166
|
+
- 网络不可达或 DNS 解析失败
|
|
167
|
+
- 重试 2 次后仍然失败
|
|
168
|
+
- 任务本身不合理(如"删除根目录")
|
|
96
169
|
|
|
97
|
-
|
|
170
|
+
**放弃时的要求**:
|
|
171
|
+
- \`command\` 字段可以留空("")
|
|
172
|
+
- \`continue\` 必须为 false
|
|
173
|
+
- \`reasoning\` 必须详细说明为什么放弃,以及尝试了什么
|
|
174
|
+
|
|
175
|
+
### 📌 关于 pls/please 工具
|
|
98
176
|
用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
|
|
99
177
|
当用户输入 "pls <描述>" 时,AI(也就是你)会生成对应的 shell 命令供用户确认执行。
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
178
|
+
<command_history> 中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
|
|
179
|
+
`;
|
|
180
|
+
/**
|
|
181
|
+
* ============================================================
|
|
182
|
+
* 构建动态 User Prompt(XML 格式)
|
|
183
|
+
* ============================================================
|
|
184
|
+
* 将系统信息、历史记录、执行日志等动态数据组装成 XML 结构
|
|
185
|
+
*/
|
|
186
|
+
export function buildUserContextPrompt(userRequest, sysInfoStr, historyStr, executedSteps) {
|
|
187
|
+
const parts = [];
|
|
188
|
+
// 1. 系统信息
|
|
189
|
+
parts.push(`<system_info>`);
|
|
190
|
+
parts.push(sysInfoStr);
|
|
191
|
+
parts.push(`</system_info>`);
|
|
192
|
+
// 2. 命令历史(如果有)
|
|
193
|
+
if (historyStr && historyStr.trim()) {
|
|
194
|
+
parts.push(`<command_history>`);
|
|
195
|
+
parts.push(historyStr);
|
|
196
|
+
parts.push(`</command_history>`);
|
|
106
197
|
}
|
|
107
|
-
|
|
108
|
-
|
|
198
|
+
// 3. 执行日志(多步骤的核心,紧凑 XML 结构)
|
|
199
|
+
if (executedSteps && executedSteps.length > 0) {
|
|
200
|
+
parts.push(`<execution_log>`);
|
|
201
|
+
executedSteps.forEach((step, i) => {
|
|
202
|
+
// 截断过长的输出(前 800 + 后 800,中间省略)
|
|
203
|
+
let safeOutput = step.output || '';
|
|
204
|
+
if (safeOutput.length > 2000) {
|
|
205
|
+
const head = safeOutput.slice(0, 800);
|
|
206
|
+
const tail = safeOutput.slice(-800);
|
|
207
|
+
safeOutput = head + '\n\n...(输出过长,已截断)...\n\n' + tail;
|
|
208
|
+
}
|
|
209
|
+
parts.push(`<step index="${i + 1}">`);
|
|
210
|
+
parts.push(`<command>${step.command}</command>`);
|
|
211
|
+
parts.push(`<exit_code>${step.exitCode}</exit_code>`);
|
|
212
|
+
parts.push(`<output>`);
|
|
213
|
+
parts.push(safeOutput);
|
|
214
|
+
parts.push(`</output>`);
|
|
215
|
+
parts.push(`</step>`);
|
|
216
|
+
});
|
|
217
|
+
parts.push(`</execution_log>`);
|
|
218
|
+
parts.push(``);
|
|
219
|
+
parts.push(`⚠️ 注意:请检查 <execution_log> 中最后一步的 <exit_code>。如果非 0,请分析 <output> 并修复命令。`);
|
|
109
220
|
}
|
|
110
|
-
|
|
221
|
+
// 4. 用户需求
|
|
222
|
+
parts.push(`<user_request>`);
|
|
223
|
+
parts.push(userRequest);
|
|
224
|
+
parts.push(`</user_request>`);
|
|
225
|
+
return parts.join('\n');
|
|
111
226
|
}
|
|
112
227
|
/**
|
|
113
|
-
*
|
|
228
|
+
* ============================================================
|
|
229
|
+
* Chat 对话模式的静态 System Prompt
|
|
230
|
+
* ============================================================
|
|
231
|
+
* 包含所有核心规则和能力描述,不包含动态数据
|
|
114
232
|
*/
|
|
115
|
-
export
|
|
116
|
-
|
|
233
|
+
export const CHAT_SYSTEM_PROMPT = `你是一个命令行专家助手,帮助用户理解和使用命令行工具。
|
|
234
|
+
|
|
235
|
+
### 📋 输入数据格式说明
|
|
236
|
+
你会收到以下 XML 标签包裹的上下文信息:
|
|
237
|
+
- <system_info>:用户的操作系统、Shell 类型、当前目录等环境信息
|
|
238
|
+
- <command_history>:用户最近通过 pls 执行的命令(用于理解上下文引用)
|
|
239
|
+
- <shell_history>:用户最近在终端执行的所有命令(如果启用了 Shell Hook)
|
|
240
|
+
- <user_question>:用户的具体问题
|
|
117
241
|
|
|
118
|
-
|
|
242
|
+
### 🎯 你的能力
|
|
119
243
|
- 解释命令的含义、参数、用法
|
|
120
244
|
- 分析命令的执行效果和潜在风险
|
|
121
245
|
- 回答命令行、Shell、系统管理相关问题
|
|
122
246
|
- 根据用户需求推荐合适的命令并解释
|
|
123
247
|
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
248
|
+
### 📝 回答要求
|
|
249
|
+
- 简洁清晰,避免冗余解释
|
|
250
|
+
- 危险操作要明确警告(如 rm -rf、chmod 777 等)
|
|
251
|
+
- 适当给出示例命令(使用代码块格式)
|
|
252
|
+
- 结合 <system_info> 中的系统环境给出针对性建议
|
|
253
|
+
- 如果用户引用了"刚才的命令"、"上一个操作",请参考历史记录
|
|
254
|
+
|
|
255
|
+
### 📌 关于 pls/please 工具
|
|
256
|
+
用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
|
|
257
|
+
<command_history> 中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
|
|
258
|
+
你可以解释这些命令,帮助用户理解它们的作用。`;
|
|
259
|
+
/**
|
|
260
|
+
* ============================================================
|
|
261
|
+
* 构建 Chat 动态 User Context(XML 格式)
|
|
262
|
+
* ============================================================
|
|
263
|
+
* 将系统信息、历史记录等动态数据组装成 XML 结构
|
|
264
|
+
* 这个上下文会作为最新一条 user 消息发送给 AI
|
|
265
|
+
*/
|
|
266
|
+
export function buildChatUserContext(userQuestion, sysInfoStr, plsHistory, shellHistory, shellHookEnabled) {
|
|
267
|
+
const parts = [];
|
|
268
|
+
// 1. 系统信息
|
|
269
|
+
parts.push('<system_info>');
|
|
270
|
+
parts.push(sysInfoStr);
|
|
271
|
+
parts.push('</system_info>');
|
|
272
|
+
// 2. 历史记录(根据 Shell Hook 状态选择)
|
|
273
|
+
if (shellHookEnabled && shellHistory && shellHistory.trim()) {
|
|
274
|
+
parts.push('<shell_history>');
|
|
275
|
+
parts.push(shellHistory);
|
|
276
|
+
parts.push('</shell_history>');
|
|
135
277
|
}
|
|
136
|
-
else if (plsHistory) {
|
|
137
|
-
|
|
278
|
+
else if (plsHistory && plsHistory.trim()) {
|
|
279
|
+
parts.push('<command_history>');
|
|
280
|
+
parts.push(plsHistory);
|
|
281
|
+
parts.push('</command_history>');
|
|
138
282
|
}
|
|
139
|
-
|
|
283
|
+
// 3. 用户问题
|
|
284
|
+
parts.push('<user_question>');
|
|
285
|
+
parts.push(userQuestion);
|
|
286
|
+
parts.push('</user_question>');
|
|
287
|
+
return parts.join('\n');
|
|
140
288
|
}
|
package/dist/src/shell-hook.d.ts
CHANGED
|
@@ -50,6 +50,11 @@ export declare function getHookStatus(): HookStatus;
|
|
|
50
50
|
* 显示 shell 历史
|
|
51
51
|
*/
|
|
52
52
|
export declare function displayShellHistory(): void;
|
|
53
|
+
/**
|
|
54
|
+
* 当 shellHistoryLimit 变化时,自动重装 Hook
|
|
55
|
+
* 返回是否成功重装
|
|
56
|
+
*/
|
|
57
|
+
export declare function reinstallHookForLimitChange(oldLimit: number, newLimit: number): Promise<boolean>;
|
|
53
58
|
/**
|
|
54
59
|
* 清空 shell 历史
|
|
55
60
|
*/
|
package/dist/src/shell-hook.js
CHANGED
|
@@ -17,7 +17,6 @@ function getColors() {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
const SHELL_HISTORY_FILE = path.join(CONFIG_DIR, 'shell_history.jsonl');
|
|
20
|
-
const MAX_SHELL_HISTORY = 20;
|
|
21
20
|
// Hook 标记,用于识别我们添加的内容
|
|
22
21
|
const HOOK_START_MARKER = '# >>> pretty-please shell hook >>>';
|
|
23
22
|
const HOOK_END_MARKER = '# <<< pretty-please shell hook <<<';
|
|
@@ -60,6 +59,8 @@ export function getShellConfigPath(shellType) {
|
|
|
60
59
|
* 生成 zsh hook 脚本
|
|
61
60
|
*/
|
|
62
61
|
function generateZshHook() {
|
|
62
|
+
const config = getConfig();
|
|
63
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
63
64
|
return `
|
|
64
65
|
${HOOK_START_MARKER}
|
|
65
66
|
# 记录命令到 pretty-please 历史
|
|
@@ -76,8 +77,8 @@ __pls_precmd() {
|
|
|
76
77
|
# 转义命令中的特殊字符
|
|
77
78
|
local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
78
79
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
|
|
79
|
-
# 保持文件不超过 ${
|
|
80
|
-
tail -n ${
|
|
80
|
+
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
81
|
+
tail -n ${limit} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
|
|
81
82
|
unset __PLS_LAST_CMD
|
|
82
83
|
fi
|
|
83
84
|
}
|
|
@@ -92,6 +93,8 @@ ${HOOK_END_MARKER}
|
|
|
92
93
|
* 生成 bash hook 脚本
|
|
93
94
|
*/
|
|
94
95
|
function generateBashHook() {
|
|
96
|
+
const config = getConfig();
|
|
97
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
95
98
|
return `
|
|
96
99
|
${HOOK_START_MARKER}
|
|
97
100
|
# 记录命令到 pretty-please 历史
|
|
@@ -103,7 +106,7 @@ __pls_prompt_command() {
|
|
|
103
106
|
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
104
107
|
local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
105
108
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
|
|
106
|
-
tail -n ${
|
|
109
|
+
tail -n ${limit} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
|
|
107
110
|
fi
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -117,6 +120,8 @@ ${HOOK_END_MARKER}
|
|
|
117
120
|
* 生成 PowerShell hook 脚本
|
|
118
121
|
*/
|
|
119
122
|
function generatePowerShellHook() {
|
|
123
|
+
const config = getConfig();
|
|
124
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
120
125
|
return `
|
|
121
126
|
${HOOK_START_MARKER}
|
|
122
127
|
# 记录命令到 pretty-please 历史
|
|
@@ -132,8 +137,8 @@ function __Pls_RecordCommand {
|
|
|
132
137
|
$escapedCmd = $lastCmd -replace '\\\\', '\\\\\\\\' -replace '"', '\\\\"'
|
|
133
138
|
$json = "{\`"cmd\`":\`"$escapedCmd\`",\`"exit\`":$exitCode,\`"time\`":\`"$timestamp\`"}"
|
|
134
139
|
Add-Content -Path "${CONFIG_DIR}/shell_history.jsonl" -Value $json
|
|
135
|
-
# 保持文件不超过 ${
|
|
136
|
-
$content = Get-Content "${CONFIG_DIR}/shell_history.jsonl" -Tail ${
|
|
140
|
+
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
141
|
+
$content = Get-Content "${CONFIG_DIR}/shell_history.jsonl" -Tail ${limit}
|
|
137
142
|
$content | Set-Content "${CONFIG_DIR}/shell_history.jsonl"
|
|
138
143
|
}
|
|
139
144
|
}
|
|
@@ -260,7 +265,7 @@ export function getShellHistory() {
|
|
|
260
265
|
.trim()
|
|
261
266
|
.split('\n')
|
|
262
267
|
.filter((line) => line.trim());
|
|
263
|
-
|
|
268
|
+
const allHistory = lines
|
|
264
269
|
.map((line) => {
|
|
265
270
|
try {
|
|
266
271
|
return JSON.parse(line);
|
|
@@ -270,6 +275,9 @@ export function getShellHistory() {
|
|
|
270
275
|
}
|
|
271
276
|
})
|
|
272
277
|
.filter((item) => item !== null);
|
|
278
|
+
// 应用 shellHistoryLimit 限制:只返回最近的 N 条
|
|
279
|
+
const limit = config.shellHistoryLimit || 15;
|
|
280
|
+
return allHistory.slice(-limit);
|
|
273
281
|
}
|
|
274
282
|
catch {
|
|
275
283
|
return [];
|
|
@@ -425,6 +433,40 @@ export function displayShellHistory() {
|
|
|
425
433
|
console.log(chalk.gray(`文件: ${SHELL_HISTORY_FILE}`));
|
|
426
434
|
console.log('');
|
|
427
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* 当 shellHistoryLimit 变化时,自动重装 Hook
|
|
438
|
+
* 返回是否成功重装
|
|
439
|
+
*/
|
|
440
|
+
export async function reinstallHookForLimitChange(oldLimit, newLimit) {
|
|
441
|
+
const config = getConfig();
|
|
442
|
+
// 只有在 hook 已启用时才重装
|
|
443
|
+
if (!config.shellHook) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
// 值没有变化,不需要重装
|
|
447
|
+
if (oldLimit === newLimit) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
const colors = getColors();
|
|
451
|
+
console.log('');
|
|
452
|
+
console.log(chalk.hex(colors.primary)(`检测到 shellHistoryLimit 变化 (${oldLimit} → ${newLimit})`));
|
|
453
|
+
console.log(chalk.hex(colors.primary)('正在更新 Shell Hook...'));
|
|
454
|
+
uninstallShellHook();
|
|
455
|
+
await installShellHook();
|
|
456
|
+
console.log('');
|
|
457
|
+
console.log(chalk.hex(colors.warning)('⚠️ 请重启终端或运行以下命令使新配置生效:'));
|
|
458
|
+
const shellType = detectShell();
|
|
459
|
+
let configFile = '~/.zshrc';
|
|
460
|
+
if (shellType === 'bash') {
|
|
461
|
+
configFile = process.platform === 'darwin' ? '~/.bash_profile' : '~/.bashrc';
|
|
462
|
+
}
|
|
463
|
+
else if (shellType === 'powershell') {
|
|
464
|
+
configFile = '~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1';
|
|
465
|
+
}
|
|
466
|
+
console.log(chalk.gray(` source ${configFile}`));
|
|
467
|
+
console.log('');
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
428
470
|
/**
|
|
429
471
|
* 清空 shell 历史
|
|
430
472
|
*/
|
|
@@ -442,6 +484,8 @@ export function clearShellHistory() {
|
|
|
442
484
|
* 生成远程 zsh hook 脚本
|
|
443
485
|
*/
|
|
444
486
|
function generateRemoteZshHook() {
|
|
487
|
+
const config = getConfig();
|
|
488
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
445
489
|
return `
|
|
446
490
|
${HOOK_START_MARKER}
|
|
447
491
|
# 记录命令到 pretty-please 历史
|
|
@@ -460,8 +504,8 @@ __pls_precmd() {
|
|
|
460
504
|
# 转义命令中的特殊字符
|
|
461
505
|
local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
462
506
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
|
|
463
|
-
# 保持文件不超过
|
|
464
|
-
tail -n
|
|
507
|
+
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
508
|
+
tail -n ${limit} ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
|
|
465
509
|
unset __PLS_LAST_CMD
|
|
466
510
|
fi
|
|
467
511
|
}
|
|
@@ -476,6 +520,8 @@ ${HOOK_END_MARKER}
|
|
|
476
520
|
* 生成远程 bash hook 脚本
|
|
477
521
|
*/
|
|
478
522
|
function generateRemoteBashHook() {
|
|
523
|
+
const config = getConfig();
|
|
524
|
+
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
479
525
|
return `
|
|
480
526
|
${HOOK_START_MARKER}
|
|
481
527
|
# 记录命令到 pretty-please 历史
|
|
@@ -489,7 +535,7 @@ __pls_prompt_command() {
|
|
|
489
535
|
mkdir -p ~/.please
|
|
490
536
|
local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
491
537
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
|
|
492
|
-
tail -n
|
|
538
|
+
tail -n ${limit} ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
|
|
493
539
|
fi
|
|
494
540
|
}
|
|
495
541
|
|
package/dist/src/ui/theme.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type ThemeName =
|
|
1
|
+
export type ThemeName = string;
|
|
2
|
+
export type BuiltinThemeName = 'dark' | 'light' | 'nord' | 'dracula' | 'retro' | 'contrast' | 'monokai';
|
|
2
3
|
export interface Theme {
|
|
3
4
|
primary: string;
|
|
4
5
|
secondary: string;
|
|
@@ -24,6 +25,39 @@ export interface Theme {
|
|
|
24
25
|
comment: string;
|
|
25
26
|
};
|
|
26
27
|
}
|
|
28
|
+
export interface ThemeMetadata {
|
|
29
|
+
name: ThemeName;
|
|
30
|
+
displayName: string;
|
|
31
|
+
description: string;
|
|
32
|
+
category: 'dark' | 'light';
|
|
33
|
+
previewColor: string;
|
|
34
|
+
author?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ThemeDefinition {
|
|
37
|
+
metadata: ThemeMetadata;
|
|
38
|
+
colors: Theme;
|
|
39
|
+
}
|
|
40
|
+
export declare const themeDefinitions: Record<ThemeName, ThemeDefinition>;
|
|
27
41
|
export declare const themes: Record<ThemeName, Theme>;
|
|
28
42
|
export declare function getCurrentTheme(): Theme;
|
|
43
|
+
export declare function getThemeMetadata(name: ThemeName): ThemeMetadata | undefined;
|
|
44
|
+
export declare function getAllThemeMetadata(): ThemeMetadata[];
|
|
45
|
+
export declare function getThemeDefinition(name: ThemeName): ThemeDefinition | undefined;
|
|
46
|
+
export declare function isValidTheme(name: string): name is ThemeName;
|
|
47
|
+
export declare function isBuiltinTheme(name: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 验证主题格式是否正确
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateTheme(theme: any): theme is ThemeDefinition;
|
|
52
|
+
/**
|
|
53
|
+
* 获取主题验证的详细错误信息
|
|
54
|
+
*/
|
|
55
|
+
export declare function validateThemeWithDetails(theme: any): {
|
|
56
|
+
valid: boolean;
|
|
57
|
+
errors: string[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* 创建主题模板
|
|
61
|
+
*/
|
|
62
|
+
export declare function createThemeTemplate(name: string, displayName: string, category: 'dark' | 'light'): ThemeDefinition;
|
|
29
63
|
export declare const theme: Theme;
|