@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/src/prompts.ts CHANGED
@@ -3,34 +3,42 @@
3
3
  */
4
4
 
5
5
  /**
6
- * 构建命令生成模式的系统提示词
6
+ * ============================================================
7
+ * 命令生成模式的静态 System Prompt
8
+ * ============================================================
9
+ * 包含所有核心规则、输出格式定义、判断标准、错误处理策略和示例
10
+ * 不包含任何动态数据(系统信息、历史记录等)
7
11
  */
8
- export function buildCommandSystemPrompt(
9
- sysinfo: string,
10
- plsHistory: string,
11
- shellHistory: string,
12
- shellHookEnabled: boolean
13
- ): string {
14
- let prompt = `你是一个专业的 shell 脚本生成器。用户会提供他们的系统信息和一个命令需求。
15
- 你的任务是返回一个可执行的、原始的 shell 命令或脚本来完成他们的目标。
12
+ export const SHELL_COMMAND_SYSTEM_PROMPT = `你是一个专业的 shell 脚本生成器。
13
+ 你将接收到包含 XML 标签的上下文信息,然后根据用户需求生成可执行的 shell 命令。
14
+
15
+ ### 📋 输入数据格式说明
16
+ 你会收到以下 XML 标签包裹的上下文信息:
17
+ - <system_info>:用户的操作系统、Shell 类型、当前目录、包管理器等环境信息
18
+ - <command_history>:用户最近执行的命令历史(用于理解上下文引用,如"刚才的文件"、"上一个命令")
19
+ - <execution_log>:**多步骤任务的关键信息**,记录了之前步骤的命令、退出码和输出结果
20
+ - 如果存在此标签,说明这是一个多步骤任务
21
+ - 必须检查每个 <step> 中的 <exit_code>,0=成功,非0=失败
22
+ - 根据 <output> 的内容决定下一步操作
23
+ - <user_request>:用户的原始自然语言需求
16
24
 
17
- 重要规则:
25
+ ### ⚠️ 重要规则
18
26
  1. 返回 JSON 格式,command 字段必须是可直接执行的命令(无解释、无注释、无 markdown)
19
27
  2. 不要添加 shebang(如 #!/bin/bash)
20
28
  3. command 可以包含多条命令(用 && 连接),但整体算一个命令
21
- 4. 根据用户的系统信息选择合适的命令(如包管理器)
22
- 5. 如果用户引用了之前的操作(如"刚才的"、"上一个"),请参考历史记录
29
+ 4. 根据 <system_info> 中的信息选择合适的命令(如包管理器)
30
+ 5. 如果用户引用了之前的操作(如"刚才的"、"上一个"),请参考 <command_history>
23
31
  6. 绝对不要输出 pls 或 please 命令!
24
32
 
25
- 【输出格式 - 非常重要】
33
+ ### 📤 输出格式 - 非常重要
26
34
 
27
- 单步模式(一个命令完成):
35
+ **单步模式(一个命令完成):**
28
36
  如果任务只需要一个命令就能完成,只返回:
29
37
  {
30
38
  "command": "ls -la"
31
39
  }
32
40
 
33
- 多步模式(需要多个命令,后续依赖前面的结果):
41
+ **多步模式(需要多个命令,后续依赖前面的结果):**
34
42
  如果任务需要多个命令,且后续命令必须根据前面的执行结果来决定,则返回:
35
43
 
36
44
  【多步骤完整示例】
@@ -40,115 +48,265 @@ export function buildCommandSystemPrompt(
40
48
  {
41
49
  "command": "find . -name '*.log' -size +100M",
42
50
  "continue": true,
43
- "reasoning": "查找大日志", (精简即可)
44
- "nextStepHint": "压缩找到的文件" (精简即可)
51
+ "reasoning": "先查找符合条件的日志文件",
52
+ "nextStepHint": "根据查找结果压缩文件"
45
53
  }
46
54
 
47
- 执行后你会收到:
48
- 命令已执行
49
- 退出码: 0
50
- 输出:
55
+ 执行后你会收到(在 <execution_log> 中):
56
+ <execution_log>
57
+ <step index="1">
58
+ <command>find . -name '*.log' -size +100M</command>
59
+ <exit_code>0</exit_code>
60
+ <output>
51
61
  ./app.log
52
62
  ./system.log
63
+ </output>
64
+ </step>
65
+ </execution_log>
53
66
 
54
67
  然后你返回第二步:
55
68
  {
56
69
  "command": "tar -czf logs.tar.gz ./app.log ./system.log",
57
70
  "continue": false,
58
- "reasoning": "压缩日志文件"
71
+ "reasoning": "压缩找到的日志文件"
59
72
  }
60
73
 
61
- 关键判断标准:
62
- - 多步 = 后续命令依赖前面的输出(如先 find 看有哪些,再根据结果操作具体文件)
63
- - 单步 = 一个命令就能完成(即使命令里有 && 连接多条,也算一个命令)
74
+ ### 🎯 关键判断标准
75
+ - **多步** = 后续命令依赖前面的输出(如先 find 看有哪些,再根据结果操作具体文件)
76
+ - **单步** = 一个命令就能完成(即使命令里有 && 连接多条,也算一个命令)
64
77
 
65
- 常见场景举例:
66
- - "删除空文件夹" → 单步:find . -empty -delete (一个命令完成)
78
+ ### 📚 常见场景举例
79
+ - "删除空文件夹" → 单步:\`find . -empty -delete\` (一个命令完成)
67
80
  - "查找大文件并压缩" → 多步:先 find 看有哪些,再 tar 压缩具体文件
68
- - "安装 git" → 单步:brew install git
69
- - "备份并删除旧日志" → 多步:先 mkdir backup,再 mv 文件到 backup
70
- - "查看目录" → 单步:ls -la
81
+ - "安装 git" → 单步:\`brew install git\` 或 \`apt-get install git\`
82
+ - "备份并删除旧日志" → 多步:先 \`mkdir backup\`,再 \`mv\` 文件到 backup
83
+ - "查看目录" → 单步:\`ls -la\`
84
+ - "查看磁盘使用情况并排序" → 单步:\`df -h | sort -k5 -rh\` (一个管道命令)
85
+ - "查看进程并杀死某个进程" → 多步:先 \`ps aux | grep xxx\` 看 PID,再 \`kill\` 具体 PID
86
+
87
+ **严格要求**:单步模式只返回 {"command": "xxx"},绝对不要输出 continue/reasoning/nextStepHint!
71
88
 
72
- 严格要求:单步模式只返回 {"command": "xxx"},绝对不要输出 continue/reasoning/nextStepHint!
89
+ ### 🔧 错误处理策略
73
90
 
74
- 【错误处理】
75
- 如果你收到命令执行失败的信息(退出码非0),你应该:
76
- 1. 分析错误原因
77
- 2. 调整命令策略,返回修正后的命令
78
- 3. 设置 continue: true 重试,或设置 continue: false 放弃
91
+ **当 <execution_log> 中最后一步的 <exit_code> 不为 0 时:**
79
92
 
80
- 错误处理示例:
81
- 上一步失败,你收到:
82
- 命令已执行
83
- 退出码: 1
84
- 输出:
85
- mv: rename ./test.zip to ./c/test.zip: No such file or directory
93
+ 1. **分析错误原因**:仔细阅读 <output> 中的错误信息
94
+ 2. **调整命令策略**:生成修正后的命令
95
+ 3. **决定是否继续**:
96
+ - 设置 \`continue: true\` 重试修正后的命令
97
+ - 设置 \`continue: false\` 放弃任务
98
+
99
+ **错误处理示例 1:权限错误**
100
+ <execution_log>
101
+ <step index="1">
102
+ <command>mkdir /var/log/myapp</command>
103
+ <exit_code>1</exit_code>
104
+ <output>mkdir: cannot create directory '/var/log/myapp': Permission denied</output>
105
+ </step>
106
+ </execution_log>
86
107
 
87
108
  你分析后返回修正:
88
109
  {
89
- "command": "cp test.zip a/ && cp test.zip b/ && cp test.zip c/",
110
+ "command": "sudo mkdir /var/log/myapp",
111
+ "continue": true,
112
+ "reasoning": "权限不足,使用 sudo 重试"
113
+ }
114
+
115
+ **错误处理示例 2:文件不存在**
116
+ <execution_log>
117
+ <step index="1">
118
+ <command>mv test.zip backup/</command>
119
+ <exit_code>1</exit_code>
120
+ <output>mv: cannot stat 'test.zip': No such file or directory</output>
121
+ </step>
122
+ </execution_log>
123
+
124
+ 你分析后决定放弃:
125
+ {
126
+ "command": "",
90
127
  "continue": false,
91
- "reasoning": "改用 cp 复制而非 mv"
128
+ "reasoning": "源文件 test.zip 不存在,无法移动,任务无法继续"
92
129
  }
93
130
 
94
- 或者如果决定放弃(无法修正),返回:
131
+ **错误处理示例 3:命令不存在,尝试安装**
132
+ <execution_log>
133
+ <step index="1">
134
+ <command>docker ps</command>
135
+ <exit_code>127</exit_code>
136
+ <output>bash: docker: command not found</output>
137
+ </step>
138
+ </execution_log>
139
+
140
+ 你根据 <system_info> 中的包管理器返回:
141
+ {
142
+ "command": "brew install docker",
143
+ "continue": true,
144
+ "reasoning": "docker 未安装,根据系统包管理器安装"
145
+ }
146
+
147
+ **错误处理示例 4:网络错误**
148
+ <execution_log>
149
+ <step index="1">
150
+ <command>ping -c 3 example.com</command>
151
+ <exit_code>2</exit_code>
152
+ <output>ping: example.com: Name or service not known</output>
153
+ </step>
154
+ </execution_log>
155
+
156
+ 你分析后返回:
95
157
  {
96
158
  "command": "",
97
159
  "continue": false,
98
- "reasoning": "文件不存在且无法恢复,任务无法继续"
160
+ "reasoning": "网络不可达或 DNS 解析失败,无法继续"
99
161
  }
100
162
 
101
- 重要:当 continue: false 且决定放弃时,command 可以留空,重点是在 reasoning 中说明为什么放弃。
163
+ ### 🛑 何时应该放弃(continue: false
164
+ - 用户输入的路径不存在且无法推测
165
+ - 需要的工具未安装且无法自动安装(如非 root 用户无法 apt install)
166
+ - 权限问题且无法用 sudo 解决(如 SELinux 限制)
167
+ - 网络不可达或 DNS 解析失败
168
+ - 重试 2 次后仍然失败
169
+ - 任务本身不合理(如"删除根目录")
170
+
171
+ **放弃时的要求**:
172
+ - \`command\` 字段可以留空("")
173
+ - \`continue\` 必须为 false
174
+ - \`reasoning\` 必须详细说明为什么放弃,以及尝试了什么
102
175
 
103
- 关于 pls/please 工具:
176
+ ### 📌 关于 pls/please 工具
104
177
  用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
105
178
  当用户输入 "pls <描述>" 时,AI(也就是你)会生成对应的 shell 命令供用户确认执行。
106
- 历史记录中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
179
+ <command_history> 中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
180
+ `
107
181
 
108
- 用户的系统信息:${sysinfo}`
182
+ /**
183
+ * ============================================================
184
+ * 构建动态 User Prompt(XML 格式)
185
+ * ============================================================
186
+ * 将系统信息、历史记录、执行日志等动态数据组装成 XML 结构
187
+ */
188
+ export function buildUserContextPrompt(
189
+ userRequest: string,
190
+ sysInfoStr: string,
191
+ historyStr: string,
192
+ executedSteps: Array<{ command: string; exitCode: number; output: string }>
193
+ ): string {
194
+ const parts: string[] = []
195
+
196
+ // 1. 系统信息
197
+ parts.push(`<system_info>`)
198
+ parts.push(sysInfoStr)
199
+ parts.push(`</system_info>`)
200
+
201
+ // 2. 命令历史(如果有)
202
+ if (historyStr && historyStr.trim()) {
203
+ parts.push(`<command_history>`)
204
+ parts.push(historyStr)
205
+ parts.push(`</command_history>`)
206
+ }
207
+
208
+ // 3. 执行日志(多步骤的核心,紧凑 XML 结构)
209
+ if (executedSteps && executedSteps.length > 0) {
210
+ parts.push(`<execution_log>`)
211
+ executedSteps.forEach((step, i) => {
212
+ // 截断过长的输出(前 800 + 后 800,中间省略)
213
+ let safeOutput = step.output || ''
214
+ if (safeOutput.length > 2000) {
215
+ const head = safeOutput.slice(0, 800)
216
+ const tail = safeOutput.slice(-800)
217
+ safeOutput = head + '\n\n...(输出过长,已截断)...\n\n' + tail
218
+ }
109
219
 
110
- // 根据是否启用 shell hook 决定展示哪个历史
111
- if (shellHookEnabled && shellHistory) {
112
- prompt += `\n\n${shellHistory}`
113
- } else if (plsHistory) {
114
- prompt += `\n\n${plsHistory}`
220
+ parts.push(`<step index="${i + 1}">`)
221
+ parts.push(`<command>${step.command}</command>`)
222
+ parts.push(`<exit_code>${step.exitCode}</exit_code>`)
223
+ parts.push(`<output>`)
224
+ parts.push(safeOutput)
225
+ parts.push(`</output>`)
226
+ parts.push(`</step>`)
227
+ })
228
+ parts.push(`</execution_log>`)
229
+ parts.push(``)
230
+ parts.push(`⚠️ 注意:请检查 <execution_log> 中最后一步的 <exit_code>。如果非 0,请分析 <output> 并修复命令。`)
115
231
  }
116
232
 
117
- return prompt
233
+ // 4. 用户需求
234
+ parts.push(`<user_request>`)
235
+ parts.push(userRequest)
236
+ parts.push(`</user_request>`)
237
+
238
+ return parts.join('\n')
118
239
  }
119
240
 
120
241
  /**
121
- * 构建 Chat 对话模式的系统提示词
242
+ * ============================================================
243
+ * Chat 对话模式的静态 System Prompt
244
+ * ============================================================
245
+ * 包含所有核心规则和能力描述,不包含动态数据
122
246
  */
123
- export function buildChatSystemPrompt(
124
- sysinfo: string,
125
- plsHistory: string,
126
- shellHistory: string,
127
- shellHookEnabled: boolean
128
- ): string {
129
- let prompt = `你是一个命令行专家助手,帮助用户理解和使用命令行工具。
247
+ export const CHAT_SYSTEM_PROMPT = `你是一个命令行专家助手,帮助用户理解和使用命令行工具。
248
+
249
+ ### 📋 输入数据格式说明
250
+ 你会收到以下 XML 标签包裹的上下文信息:
251
+ - <system_info>:用户的操作系统、Shell 类型、当前目录等环境信息
252
+ - <command_history>:用户最近通过 pls 执行的命令(用于理解上下文引用)
253
+ - <shell_history>:用户最近在终端执行的所有命令(如果启用了 Shell Hook)
254
+ - <user_question>:用户的具体问题
130
255
 
131
- 【你的能力】
256
+ ### 🎯 你的能力
132
257
  - 解释命令的含义、参数、用法
133
258
  - 分析命令的执行效果和潜在风险
134
259
  - 回答命令行、Shell、系统管理相关问题
135
260
  - 根据用户需求推荐合适的命令并解释
136
261
 
137
- 【回答要求】
138
- - 简洁清晰,避免冗余
139
- - 危险操作要明确警告
140
- - 适当给出示例命令
141
- - 结合用户的系统环境给出针对性建议
262
+ ### 📝 回答要求
263
+ - 简洁清晰,避免冗余解释
264
+ - 危险操作要明确警告(如 rm -rf、chmod 777 等)
265
+ - 适当给出示例命令(使用代码块格式)
266
+ - 结合 <system_info> 中的系统环境给出针对性建议
267
+ - 如果用户引用了"刚才的命令"、"上一个操作",请参考历史记录
142
268
 
143
- 【用户系统信息】
144
- ${sysinfo}`
269
+ ### 📌 关于 pls/please 工具
270
+ 用户正在使用 pls(pretty-please)工具,这是一个将自然语言转换为 shell 命令的 AI 助手。
271
+ <command_history> 中标记为 [pls] 的条目表示用户通过 pls 工具执行的命令。
272
+ 你可以解释这些命令,帮助用户理解它们的作用。`
273
+
274
+ /**
275
+ * ============================================================
276
+ * 构建 Chat 动态 User Context(XML 格式)
277
+ * ============================================================
278
+ * 将系统信息、历史记录等动态数据组装成 XML 结构
279
+ * 这个上下文会作为最新一条 user 消息发送给 AI
280
+ */
281
+ export function buildChatUserContext(
282
+ userQuestion: string,
283
+ sysInfoStr: string,
284
+ plsHistory: string,
285
+ shellHistory: string,
286
+ shellHookEnabled: boolean
287
+ ): string {
288
+ const parts: string[] = []
145
289
 
146
- // 根据是否启用 shell hook 决定展示哪个历史
147
- if (shellHookEnabled && shellHistory) {
148
- prompt += `\n\n${shellHistory}`
149
- } else if (plsHistory) {
150
- prompt += `\n\n${plsHistory}`
290
+ // 1. 系统信息
291
+ parts.push('<system_info>')
292
+ parts.push(sysInfoStr)
293
+ parts.push('</system_info>')
294
+
295
+ // 2. 历史记录(根据 Shell Hook 状态选择)
296
+ if (shellHookEnabled && shellHistory && shellHistory.trim()) {
297
+ parts.push('<shell_history>')
298
+ parts.push(shellHistory)
299
+ parts.push('</shell_history>')
300
+ } else if (plsHistory && plsHistory.trim()) {
301
+ parts.push('<command_history>')
302
+ parts.push(plsHistory)
303
+ parts.push('</command_history>')
151
304
  }
152
305
 
153
- return prompt
306
+ // 3. 用户问题
307
+ parts.push('<user_question>')
308
+ parts.push(userQuestion)
309
+ parts.push('</user_question>')
310
+
311
+ return parts.join('\n')
154
312
  }
package/src/shell-hook.ts CHANGED
@@ -19,7 +19,6 @@ function getColors() {
19
19
  }
20
20
 
21
21
  const SHELL_HISTORY_FILE = path.join(CONFIG_DIR, 'shell_history.jsonl')
22
- const MAX_SHELL_HISTORY = 20
23
22
 
24
23
  // Hook 标记,用于识别我们添加的内容
25
24
  const HOOK_START_MARKER = '# >>> pretty-please shell hook >>>'
@@ -86,6 +85,9 @@ export function getShellConfigPath(shellType: ShellType): string | null {
86
85
  * 生成 zsh hook 脚本
87
86
  */
88
87
  function generateZshHook(): string {
88
+ const config = getConfig()
89
+ const limit = config.shellHistoryLimit || 10 // 从配置读取
90
+
89
91
  return `
90
92
  ${HOOK_START_MARKER}
91
93
  # 记录命令到 pretty-please 历史
@@ -102,8 +104,8 @@ __pls_precmd() {
102
104
  # 转义命令中的特殊字符
103
105
  local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
104
106
  echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
105
- # 保持文件不超过 ${MAX_SHELL_HISTORY}
106
- tail -n ${MAX_SHELL_HISTORY} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
107
+ # 保持文件不超过 ${limit} 行(从配置读取)
108
+ 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
109
  unset __PLS_LAST_CMD
108
110
  fi
109
111
  }
@@ -119,6 +121,9 @@ ${HOOK_END_MARKER}
119
121
  * 生成 bash hook 脚本
120
122
  */
121
123
  function generateBashHook(): string {
124
+ const config = getConfig()
125
+ const limit = config.shellHistoryLimit || 10 // 从配置读取
126
+
122
127
  return `
123
128
  ${HOOK_START_MARKER}
124
129
  # 记录命令到 pretty-please 历史
@@ -130,7 +135,7 @@ __pls_prompt_command() {
130
135
  local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
131
136
  local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
132
137
  echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
133
- tail -n ${MAX_SHELL_HISTORY} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
138
+ 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"
134
139
  fi
135
140
  }
136
141
 
@@ -145,6 +150,9 @@ ${HOOK_END_MARKER}
145
150
  * 生成 PowerShell hook 脚本
146
151
  */
147
152
  function generatePowerShellHook(): string {
153
+ const config = getConfig()
154
+ const limit = config.shellHistoryLimit || 10 // 从配置读取
155
+
148
156
  return `
149
157
  ${HOOK_START_MARKER}
150
158
  # 记录命令到 pretty-please 历史
@@ -160,8 +168,8 @@ function __Pls_RecordCommand {
160
168
  $escapedCmd = $lastCmd -replace '\\\\', '\\\\\\\\' -replace '"', '\\\\"'
161
169
  $json = "{\`"cmd\`":\`"$escapedCmd\`",\`"exit\`":$exitCode,\`"time\`":\`"$timestamp\`"}"
162
170
  Add-Content -Path "${CONFIG_DIR}/shell_history.jsonl" -Value $json
163
- # 保持文件不超过 ${MAX_SHELL_HISTORY}
164
- $content = Get-Content "${CONFIG_DIR}/shell_history.jsonl" -Tail ${MAX_SHELL_HISTORY}
171
+ # 保持文件不超过 ${limit} 行(从配置读取)
172
+ $content = Get-Content "${CONFIG_DIR}/shell_history.jsonl" -Tail ${limit}
165
173
  $content | Set-Content "${CONFIG_DIR}/shell_history.jsonl"
166
174
  }
167
175
  }
@@ -314,7 +322,7 @@ export function getShellHistory(): ShellHistoryItem[] {
314
322
  .split('\n')
315
323
  .filter((line) => line.trim())
316
324
 
317
- return lines
325
+ const allHistory = lines
318
326
  .map((line) => {
319
327
  try {
320
328
  return JSON.parse(line) as ShellHistoryItem
@@ -323,6 +331,10 @@ export function getShellHistory(): ShellHistoryItem[] {
323
331
  }
324
332
  })
325
333
  .filter((item): item is ShellHistoryItem => item !== null)
334
+
335
+ // 应用 shellHistoryLimit 限制:只返回最近的 N 条
336
+ const limit = config.shellHistoryLimit || 15
337
+ return allHistory.slice(-limit)
326
338
  } catch {
327
339
  return []
328
340
  }
@@ -505,6 +517,49 @@ export function displayShellHistory(): void {
505
517
  console.log('')
506
518
  }
507
519
 
520
+ /**
521
+ * 当 shellHistoryLimit 变化时,自动重装 Hook
522
+ * 返回是否成功重装
523
+ */
524
+ export async function reinstallHookForLimitChange(oldLimit: number, newLimit: number): Promise<boolean> {
525
+ const config = getConfig()
526
+
527
+ // 只有在 hook 已启用时才重装
528
+ if (!config.shellHook) {
529
+ return false
530
+ }
531
+
532
+ // 值没有变化,不需要重装
533
+ if (oldLimit === newLimit) {
534
+ return false
535
+ }
536
+
537
+ const colors = getColors()
538
+
539
+ console.log('')
540
+ console.log(chalk.hex(colors.primary)(`检测到 shellHistoryLimit 变化 (${oldLimit} → ${newLimit})`))
541
+ console.log(chalk.hex(colors.primary)('正在更新 Shell Hook...'))
542
+
543
+ uninstallShellHook()
544
+ await installShellHook()
545
+
546
+ console.log('')
547
+ console.log(chalk.hex(colors.warning)('⚠️ 请重启终端或运行以下命令使新配置生效:'))
548
+
549
+ const shellType = detectShell()
550
+ let configFile = '~/.zshrc'
551
+ if (shellType === 'bash') {
552
+ configFile = process.platform === 'darwin' ? '~/.bash_profile' : '~/.bashrc'
553
+ } else if (shellType === 'powershell') {
554
+ configFile = '~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1'
555
+ }
556
+
557
+ console.log(chalk.gray(` source ${configFile}`))
558
+ console.log('')
559
+
560
+ return true
561
+ }
562
+
508
563
  /**
509
564
  * 清空 shell 历史
510
565
  */
@@ -524,6 +579,9 @@ export function clearShellHistory(): void {
524
579
  * 生成远程 zsh hook 脚本
525
580
  */
526
581
  function generateRemoteZshHook(): string {
582
+ const config = getConfig()
583
+ const limit = config.shellHistoryLimit || 10 // 从配置读取
584
+
527
585
  return `
528
586
  ${HOOK_START_MARKER}
529
587
  # 记录命令到 pretty-please 历史
@@ -542,8 +600,8 @@ __pls_precmd() {
542
600
  # 转义命令中的特殊字符
543
601
  local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
544
602
  echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
545
- # 保持文件不超过 50
546
- tail -n 50 ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
603
+ # 保持文件不超过 ${limit} 行(从配置读取)
604
+ tail -n ${limit} ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
547
605
  unset __PLS_LAST_CMD
548
606
  fi
549
607
  }
@@ -559,6 +617,9 @@ ${HOOK_END_MARKER}
559
617
  * 生成远程 bash hook 脚本
560
618
  */
561
619
  function generateRemoteBashHook(): string {
620
+ const config = getConfig()
621
+ const limit = config.shellHistoryLimit || 10 // 从配置读取
622
+
562
623
  return `
563
624
  ${HOOK_START_MARKER}
564
625
  # 记录命令到 pretty-please 历史
@@ -572,7 +633,7 @@ __pls_prompt_command() {
572
633
  mkdir -p ~/.please
573
634
  local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
574
635
  echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> ~/.please/shell_history.jsonl
575
- tail -n 50 ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
636
+ tail -n ${limit} ~/.please/shell_history.jsonl > ~/.please/shell_history.jsonl.tmp && mv ~/.please/shell_history.jsonl.tmp ~/.please/shell_history.jsonl
576
637
  fi
577
638
  }
578
639