@yivan-lab/pretty-please 1.3.1 → 1.5.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.
Files changed (94) hide show
  1. package/README.md +250 -620
  2. package/bin/pls.tsx +178 -40
  3. package/dist/bin/pls.js +149 -27
  4. package/dist/package.json +10 -2
  5. package/dist/src/__integration__/command-generation.test.d.ts +5 -0
  6. package/dist/src/__integration__/command-generation.test.js +508 -0
  7. package/dist/src/__integration__/error-recovery.test.d.ts +5 -0
  8. package/dist/src/__integration__/error-recovery.test.js +511 -0
  9. package/dist/src/__integration__/shell-hook-workflow.test.d.ts +5 -0
  10. package/dist/src/__integration__/shell-hook-workflow.test.js +375 -0
  11. package/dist/src/__tests__/alias.test.d.ts +5 -0
  12. package/dist/src/__tests__/alias.test.js +421 -0
  13. package/dist/src/__tests__/chat-history.test.d.ts +5 -0
  14. package/dist/src/__tests__/chat-history.test.js +372 -0
  15. package/dist/src/__tests__/config.test.d.ts +5 -0
  16. package/dist/src/__tests__/config.test.js +822 -0
  17. package/dist/src/__tests__/history.test.d.ts +5 -0
  18. package/dist/src/__tests__/history.test.js +439 -0
  19. package/dist/src/__tests__/remote-history.test.d.ts +5 -0
  20. package/dist/src/__tests__/remote-history.test.js +641 -0
  21. package/dist/src/__tests__/remote.test.d.ts +5 -0
  22. package/dist/src/__tests__/remote.test.js +689 -0
  23. package/dist/src/__tests__/shell-hook-install.test.d.ts +5 -0
  24. package/dist/src/__tests__/shell-hook-install.test.js +413 -0
  25. package/dist/src/__tests__/shell-hook-remote.test.d.ts +5 -0
  26. package/dist/src/__tests__/shell-hook-remote.test.js +507 -0
  27. package/dist/src/__tests__/shell-hook.test.d.ts +5 -0
  28. package/dist/src/__tests__/shell-hook.test.js +440 -0
  29. package/dist/src/__tests__/sysinfo.test.d.ts +5 -0
  30. package/dist/src/__tests__/sysinfo.test.js +572 -0
  31. package/dist/src/__tests__/system-history.test.d.ts +5 -0
  32. package/dist/src/__tests__/system-history.test.js +457 -0
  33. package/dist/src/components/Chat.js +9 -28
  34. package/dist/src/config.d.ts +2 -0
  35. package/dist/src/config.js +30 -2
  36. package/dist/src/mastra-chat.js +10 -6
  37. package/dist/src/multi-step.js +10 -8
  38. package/dist/src/project-context.d.ts +22 -0
  39. package/dist/src/project-context.js +168 -0
  40. package/dist/src/prompts.d.ts +4 -4
  41. package/dist/src/prompts.js +23 -6
  42. package/dist/src/shell-hook.d.ts +32 -0
  43. package/dist/src/shell-hook.js +226 -33
  44. package/dist/src/sysinfo.d.ts +38 -9
  45. package/dist/src/sysinfo.js +245 -21
  46. package/dist/src/system-history.d.ts +18 -0
  47. package/dist/src/system-history.js +151 -0
  48. package/dist/src/ui/__tests__/theme.test.d.ts +5 -0
  49. package/dist/src/ui/__tests__/theme.test.js +688 -0
  50. package/dist/src/upgrade.js +3 -0
  51. package/dist/src/user-preferences.d.ts +44 -0
  52. package/dist/src/user-preferences.js +147 -0
  53. package/dist/src/utils/__tests__/platform-capabilities.test.d.ts +5 -0
  54. package/dist/src/utils/__tests__/platform-capabilities.test.js +214 -0
  55. package/dist/src/utils/__tests__/platform-exec.test.d.ts +5 -0
  56. package/dist/src/utils/__tests__/platform-exec.test.js +212 -0
  57. package/dist/src/utils/__tests__/platform-shell.test.d.ts +5 -0
  58. package/dist/src/utils/__tests__/platform-shell.test.js +300 -0
  59. package/dist/src/utils/__tests__/platform.test.d.ts +5 -0
  60. package/dist/src/utils/__tests__/platform.test.js +137 -0
  61. package/dist/src/utils/platform.d.ts +88 -0
  62. package/dist/src/utils/platform.js +331 -0
  63. package/package.json +10 -2
  64. package/src/__integration__/command-generation.test.ts +602 -0
  65. package/src/__integration__/error-recovery.test.ts +620 -0
  66. package/src/__integration__/shell-hook-workflow.test.ts +457 -0
  67. package/src/__tests__/alias.test.ts +545 -0
  68. package/src/__tests__/chat-history.test.ts +462 -0
  69. package/src/__tests__/config.test.ts +1043 -0
  70. package/src/__tests__/history.test.ts +538 -0
  71. package/src/__tests__/remote-history.test.ts +791 -0
  72. package/src/__tests__/remote.test.ts +866 -0
  73. package/src/__tests__/shell-hook-install.test.ts +510 -0
  74. package/src/__tests__/shell-hook-remote.test.ts +679 -0
  75. package/src/__tests__/shell-hook.test.ts +564 -0
  76. package/src/__tests__/sysinfo.test.ts +718 -0
  77. package/src/__tests__/system-history.test.ts +608 -0
  78. package/src/components/Chat.tsx +10 -37
  79. package/src/config.ts +29 -2
  80. package/src/mastra-chat.ts +12 -5
  81. package/src/multi-step.ts +11 -5
  82. package/src/project-context.ts +191 -0
  83. package/src/prompts.ts +26 -5
  84. package/src/shell-hook.ts +254 -32
  85. package/src/sysinfo.ts +326 -25
  86. package/src/system-history.ts +170 -0
  87. package/src/ui/__tests__/theme.test.ts +869 -0
  88. package/src/upgrade.ts +5 -0
  89. package/src/user-preferences.ts +178 -0
  90. package/src/utils/__tests__/platform-capabilities.test.ts +265 -0
  91. package/src/utils/__tests__/platform-exec.test.ts +278 -0
  92. package/src/utils/__tests__/platform-shell.test.ts +353 -0
  93. package/src/utils/__tests__/platform.test.ts +170 -0
  94. package/src/utils/platform.ts +431 -0
package/src/upgrade.ts CHANGED
@@ -370,6 +370,11 @@ pause
370
370
 
371
371
  console2.muted('━'.repeat(40))
372
372
  console2.success(`升级成功: ${currentVersion} → ${latestVersion}`)
373
+
374
+ // 升级成功后,重装 shell hook(如果已启用)
375
+ const { reinstallShellHook } = await import('./shell-hook.js')
376
+ await reinstallShellHook({ reason: '版本升级,更新 Shell Hook 脚本' })
377
+
373
378
  console.log('')
374
379
 
375
380
  return true
@@ -0,0 +1,178 @@
1
+ /**
2
+ * 用户命令偏好统计模块
3
+ *
4
+ * 功能:
5
+ * - 读取和分析 ~/.please/command_stats.txt
6
+ * - 获取用户最常用的命令
7
+ * - 格式化为 AI 可理解的字符串
8
+ * - 智能过滤非偏好命令(Shell 内置、系统通用命令等)
9
+ */
10
+
11
+ import fs from 'fs'
12
+ import path from 'path'
13
+ import { CONFIG_DIR, getConfig } from './config.js'
14
+
15
+ const STATS_FILE = path.join(CONFIG_DIR, 'command_stats.txt')
16
+
17
+ /**
18
+ * 命令黑名单:这些命令不算"用户偏好"
19
+ * - Shell 内置命令:cd、export、source 等(必须用的,不是偏好)
20
+ * - 系统基础命令:ls、cat、grep 等(太基础,不反映偏好)
21
+ * - 系统通用命令:clear、exit、history 等(通用命令,不是偏好)
22
+ * - 查询命令:man、which、type 等(查询用途,不是偏好)
23
+ * - 权限命令:sudo、doas 等(权限提升,不是偏好)
24
+ * - pls 自身:pls-dev、pls、please(自引用)
25
+ */
26
+ const COMMAND_BLACKLIST = new Set([
27
+ // Shell 内置命令
28
+ 'cd', 'pushd', 'popd', 'dirs',
29
+ 'export', 'set', 'unset', 'declare', 'local', 'readonly',
30
+ 'alias', 'unalias',
31
+ 'source', '.',
32
+ 'history', 'fc',
33
+ 'jobs', 'fg', 'bg', 'disown',
34
+ 'eval', 'exec', 'builtin', 'command',
35
+ 'true', 'false', ':', 'test', '[',
36
+
37
+ // 系统基础命令(太基础,不反映偏好)
38
+ 'ls', 'cat', 'grep', 'find', 'head', 'tail',
39
+ 'cp', 'mv', 'rm', 'mkdir', 'rmdir', 'touch',
40
+ 'chmod', 'chown', 'ln',
41
+ 'wc', 'sort', 'uniq', 'cut', 'tr', 'sed', 'awk',
42
+
43
+ // 系统通用命令(不算偏好)
44
+ 'clear', 'reset',
45
+ 'exit', 'logout',
46
+ 'pwd',
47
+ 'echo', 'printf',
48
+ 'sleep', 'wait',
49
+ 'kill', 'killall', 'pkill',
50
+
51
+ // 查询命令
52
+ 'man', 'which', 'type', 'whereis', 'whatis', 'apropos',
53
+ 'help', 'info',
54
+
55
+ // 权限命令
56
+ 'sudo', 'doas', 'su',
57
+
58
+ // pls 自身
59
+ 'pls', 'pls-dev', 'please',
60
+ ])
61
+
62
+ /**
63
+ * 命令统计接口
64
+ */
65
+ export interface CommandStat {
66
+ command: string
67
+ count: number
68
+ }
69
+
70
+ /**
71
+ * 确保统计文件存在
72
+ */
73
+ function ensureStatsFile(): void {
74
+ if (!fs.existsSync(CONFIG_DIR)) {
75
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
76
+ }
77
+ if (!fs.existsSync(STATS_FILE)) {
78
+ fs.writeFileSync(STATS_FILE, '', 'utf-8')
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 获取所有命令统计数据
84
+ */
85
+ export function getCommandStats(): Record<string, number> {
86
+ ensureStatsFile()
87
+
88
+ const content = fs.readFileSync(STATS_FILE, 'utf-8')
89
+ const stats: Record<string, number> = {}
90
+
91
+ for (const line of content.split('\n')) {
92
+ if (!line.trim()) continue
93
+ const [cmd, count] = line.split('=')
94
+ if (cmd && count) {
95
+ stats[cmd] = parseInt(count, 10)
96
+ }
97
+ }
98
+
99
+ return stats
100
+ }
101
+
102
+ /**
103
+ * 获取使用频率最高的命令(智能过滤版)
104
+ * @param limit 可选的数量限制,不传则使用配置中的 userPreferencesTopK
105
+ */
106
+ export function getTopCommands(limit?: number): CommandStat[] {
107
+ const config = getConfig()
108
+ const topK = limit !== undefined ? limit : config.userPreferencesTopK
109
+
110
+ const stats = getCommandStats()
111
+ return Object.entries(stats)
112
+ .filter(([command]) => !COMMAND_BLACKLIST.has(command)) // 过滤黑名单
113
+ .map(([command, count]) => ({ command, count }))
114
+ .sort((a, b) => b.count - a.count)
115
+ .slice(0, topK)
116
+ }
117
+
118
+ /**
119
+ * 格式化用户偏好为 AI 可理解的字符串
120
+ *
121
+ * 示例输出:
122
+ * "用户偏好: git(234), eza(156), vim(89), docker(67), pnpm(45)"
123
+ */
124
+ export function formatUserPreferences(): string {
125
+ const top = getTopCommands() // 使用配置中的 topK
126
+ if (top.length === 0) return ''
127
+
128
+ const lines = top.map(({ command, count }) => `${command}(${count})`)
129
+ return `用户偏好: ${lines.join(', ')}`
130
+ }
131
+
132
+ /**
133
+ * 清空统计数据
134
+ */
135
+ export function clearCommandStats(): void {
136
+ ensureStatsFile()
137
+ fs.writeFileSync(STATS_FILE, '', 'utf-8')
138
+ }
139
+
140
+ /**
141
+ * 获取统计文件路径(用于 CLI 展示)
142
+ */
143
+ export function getStatsFilePath(): string {
144
+ return STATS_FILE
145
+ }
146
+
147
+ /**
148
+ * 显示统计信息(用于 CLI)
149
+ */
150
+ export function displayCommandStats(): void {
151
+ const config = getConfig()
152
+ const stats = getCommandStats()
153
+ const totalCommands = Object.keys(stats).length
154
+ const totalExecutions = Object.values(stats).reduce((sum, count) => sum + count, 0)
155
+
156
+ if (totalCommands === 0) {
157
+ console.log('\n暂无命令统计数据')
158
+ console.log('提示: 安装并启用 Shell Hook 后会自动开始统计\n')
159
+ return
160
+ }
161
+
162
+ const displayLimit = config.userPreferencesTopK // 使用配置项
163
+ const top = getTopCommands(displayLimit)
164
+
165
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
166
+ console.log(`📊 命令使用统计`)
167
+ console.log(`总命令数: ${totalCommands}, 总执行次数: ${totalExecutions}`)
168
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
169
+ console.log(`\nTop ${displayLimit} 常用命令(已过滤非偏好命令):\n`)
170
+
171
+ top.forEach(({ command, count }, index) => {
172
+ const percentage = ((count / totalExecutions) * 100).toFixed(1)
173
+ const bar = '█'.repeat(Math.floor(count / top[0].count * 20))
174
+ console.log(`${String(index + 1).padStart(2)}. ${command.padEnd(15)} ${bar} ${count} (${percentage}%)`)
175
+ })
176
+
177
+ console.log(`\n统计文件: ${STATS_FILE}\n`)
178
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Shell 能力专项测试
3
+ * 测试各 Shell 的配置文件、历史文件、能力矩阵等
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
7
+ import { getShellCapabilities, type ShellType } from '../platform'
8
+ import os from 'os'
9
+ import path from 'path'
10
+
11
+ describe('Shell Capabilities - 配置文件路径', () => {
12
+ const originalPlatform = process.platform
13
+ const home = os.homedir()
14
+
15
+ afterEach(() => {
16
+ Object.defineProperty(process, 'platform', {
17
+ value: originalPlatform,
18
+ })
19
+ })
20
+
21
+ it('Zsh 应该返回 ~/.zshrc', () => {
22
+ const caps = getShellCapabilities('zsh')
23
+ expect(caps.configPath).toBe(path.join(home, '.zshrc'))
24
+ })
25
+
26
+ it('Bash 在 macOS 应该返回 ~/.bash_profile', () => {
27
+ Object.defineProperty(process, 'platform', { value: 'darwin', writable: true })
28
+ const caps = getShellCapabilities('bash')
29
+ expect(caps.configPath).toBe(path.join(home, '.bash_profile'))
30
+ })
31
+
32
+ it('Bash 在 Linux 应该返回 ~/.bashrc', () => {
33
+ Object.defineProperty(process, 'platform', { value: 'linux', writable: true })
34
+ const caps = getShellCapabilities('bash')
35
+ expect(caps.configPath).toBe(path.join(home, '.bashrc'))
36
+ })
37
+
38
+ it('Fish 应该返回 ~/.config/fish/config.fish', () => {
39
+ const caps = getShellCapabilities('fish')
40
+ expect(caps.configPath).toBe(path.join(home, '.config', 'fish', 'config.fish'))
41
+ })
42
+
43
+ it('PowerShell 5 应该返回正确的 profile 路径', () => {
44
+ const caps = getShellCapabilities('powershell5')
45
+ expect(caps.configPath).toBe(
46
+ path.join(home, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1')
47
+ )
48
+ })
49
+
50
+ it('PowerShell 7 应该返回正确的 profile 路径', () => {
51
+ const caps = getShellCapabilities('powershell7')
52
+ expect(caps.configPath).toBe(
53
+ path.join(home, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
54
+ )
55
+ })
56
+
57
+ it('CMD 应该返回 null (不支持配置文件)', () => {
58
+ const caps = getShellCapabilities('cmd')
59
+ expect(caps.configPath).toBeNull()
60
+ })
61
+
62
+ it('Unknown 应该返回 null', () => {
63
+ const caps = getShellCapabilities('unknown')
64
+ expect(caps.configPath).toBeNull()
65
+ })
66
+ })
67
+
68
+ describe('Shell Capabilities - 历史文件路径', () => {
69
+ const originalEnv = { ...process.env }
70
+ const home = os.homedir()
71
+
72
+ beforeEach(() => {
73
+ // 清空 HISTFILE 环境变量
74
+ delete process.env.HISTFILE
75
+ })
76
+
77
+ afterEach(() => {
78
+ process.env = { ...originalEnv }
79
+ })
80
+
81
+ it('Zsh 应该返回 ~/.zsh_history (默认)', () => {
82
+ const caps = getShellCapabilities('zsh')
83
+ expect(caps.historyPath).toBe(path.join(home, '.zsh_history'))
84
+ })
85
+
86
+ it('Zsh 应该使用 HISTFILE 环境变量覆盖', () => {
87
+ process.env.HISTFILE = '/custom/path/.zsh_history'
88
+ const caps = getShellCapabilities('zsh')
89
+ expect(caps.historyPath).toBe('/custom/path/.zsh_history')
90
+ })
91
+
92
+ it('Bash 应该返回 ~/.bash_history (默认)', () => {
93
+ const caps = getShellCapabilities('bash')
94
+ expect(caps.historyPath).toBe(path.join(home, '.bash_history'))
95
+ })
96
+
97
+ it('Bash 应该使用 HISTFILE 环境变量覆盖', () => {
98
+ process.env.HISTFILE = '/custom/path/.bash_history'
99
+ const caps = getShellCapabilities('bash')
100
+ expect(caps.historyPath).toBe('/custom/path/.bash_history')
101
+ })
102
+
103
+ it('Fish 应该返回 ~/.local/share/fish/fish_history', () => {
104
+ const caps = getShellCapabilities('fish')
105
+ expect(caps.historyPath).toBe(path.join(home, '.local', 'share', 'fish', 'fish_history'))
106
+ })
107
+
108
+ it('PowerShell 应该返回 PSReadLine 历史文件路径', () => {
109
+ const caps = getShellCapabilities('powershell5')
110
+ const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming')
111
+ expect(caps.historyPath).toBe(
112
+ path.join(appData, 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt')
113
+ )
114
+ })
115
+
116
+ it('PowerShell 5 和 7 应该使用相同的历史文件', () => {
117
+ const caps5 = getShellCapabilities('powershell5')
118
+ const caps7 = getShellCapabilities('powershell7')
119
+ expect(caps5.historyPath).toBe(caps7.historyPath)
120
+ })
121
+
122
+ it('CMD 应该返回 null (不持久化历史)', () => {
123
+ const caps = getShellCapabilities('cmd')
124
+ expect(caps.historyPath).toBeNull()
125
+ })
126
+
127
+ it('Unknown 应该返回 null', () => {
128
+ const caps = getShellCapabilities('unknown')
129
+ expect(caps.historyPath).toBeNull()
130
+ })
131
+ })
132
+
133
+ describe('Shell Capabilities - 能力矩阵', () => {
134
+ it('Zsh 应该支持 Hook 和历史', () => {
135
+ const caps = getShellCapabilities('zsh')
136
+ expect(caps.supportsHook).toBe(true)
137
+ expect(caps.supportsHistory).toBe(true)
138
+ })
139
+
140
+ it('Bash 应该支持 Hook 和历史', () => {
141
+ const caps = getShellCapabilities('bash')
142
+ expect(caps.supportsHook).toBe(true)
143
+ expect(caps.supportsHistory).toBe(true)
144
+ })
145
+
146
+ it('Fish 应该支持 Hook 和历史', () => {
147
+ const caps = getShellCapabilities('fish')
148
+ expect(caps.supportsHook).toBe(true)
149
+ expect(caps.supportsHistory).toBe(true)
150
+ })
151
+
152
+ it('PowerShell 5 应该支持 Hook 和历史', () => {
153
+ const caps = getShellCapabilities('powershell5')
154
+ expect(caps.supportsHook).toBe(true)
155
+ expect(caps.supportsHistory).toBe(true)
156
+ })
157
+
158
+ it('PowerShell 7 应该支持 Hook 和历史', () => {
159
+ const caps = getShellCapabilities('powershell7')
160
+ expect(caps.supportsHook).toBe(true)
161
+ expect(caps.supportsHistory).toBe(true)
162
+ })
163
+
164
+ it('CMD 不应该支持 Hook 和历史', () => {
165
+ const caps = getShellCapabilities('cmd')
166
+ expect(caps.supportsHook).toBe(false)
167
+ expect(caps.supportsHistory).toBe(false)
168
+ })
169
+
170
+ it('Unknown 不应该支持 Hook 和历史', () => {
171
+ const caps = getShellCapabilities('unknown')
172
+ expect(caps.supportsHook).toBe(false)
173
+ expect(caps.supportsHistory).toBe(false)
174
+ })
175
+ })
176
+
177
+ describe('Shell Capabilities - 可执行文件', () => {
178
+ const originalEnv = { ...process.env }
179
+
180
+ afterEach(() => {
181
+ process.env = { ...originalEnv }
182
+ })
183
+
184
+ it('Zsh 应该返回 $SHELL 或默认路径', () => {
185
+ delete process.env.SHELL
186
+ const caps = getShellCapabilities('zsh')
187
+ expect(caps.executable).toBe('/bin/zsh')
188
+ })
189
+
190
+ it('Zsh 应该使用 $SHELL 环境变量', () => {
191
+ process.env.SHELL = '/usr/local/bin/zsh'
192
+ const caps = getShellCapabilities('zsh')
193
+ expect(caps.executable).toBe('/usr/local/bin/zsh')
194
+ })
195
+
196
+ it('Bash 应该返回 $SHELL 或默认路径', () => {
197
+ delete process.env.SHELL
198
+ const caps = getShellCapabilities('bash')
199
+ expect(caps.executable).toBe('/bin/bash')
200
+ })
201
+
202
+ it('Fish 应该返回 $SHELL 或默认路径', () => {
203
+ delete process.env.SHELL
204
+ const caps = getShellCapabilities('fish')
205
+ expect(caps.executable).toBe('/usr/bin/fish')
206
+ })
207
+
208
+ it('PowerShell 5 应该返回 powershell.exe', () => {
209
+ const caps = getShellCapabilities('powershell5')
210
+ expect(caps.executable).toBe('powershell.exe')
211
+ })
212
+
213
+ it('PowerShell 7 应该返回 pwsh.exe', () => {
214
+ const caps = getShellCapabilities('powershell7')
215
+ expect(caps.executable).toBe('pwsh.exe')
216
+ })
217
+
218
+ it('CMD 应该返回 $COMSPEC 或 cmd.exe', () => {
219
+ delete process.env.COMSPEC
220
+ const caps = getShellCapabilities('cmd')
221
+ expect(caps.executable).toBe('cmd.exe')
222
+ })
223
+
224
+ it('CMD 应该使用 $COMSPEC 环境变量', () => {
225
+ process.env.COMSPEC = 'C:\\Windows\\System32\\cmd.exe'
226
+ const caps = getShellCapabilities('cmd')
227
+ expect(caps.executable).toBe('C:\\Windows\\System32\\cmd.exe')
228
+ })
229
+ })
230
+
231
+ describe('Shell Capabilities - 显示名称', () => {
232
+ it('应该返回正确的显示名称', () => {
233
+ expect(getShellCapabilities('zsh').displayName).toBe('Zsh')
234
+ expect(getShellCapabilities('bash').displayName).toBe('Bash')
235
+ expect(getShellCapabilities('fish').displayName).toBe('Fish')
236
+ expect(getShellCapabilities('cmd').displayName).toBe('CMD')
237
+ expect(getShellCapabilities('powershell5').displayName).toBe('PowerShell 5.x')
238
+ expect(getShellCapabilities('powershell7').displayName).toBe('PowerShell 7+')
239
+ expect(getShellCapabilities('unknown').displayName).toBe('Unknown')
240
+ })
241
+ })
242
+
243
+ describe('Shell Capabilities - 完整性检查', () => {
244
+ it('每个 Shell 类型都应该返回完整的能力信息', () => {
245
+ const shells: ShellType[] = ['zsh', 'bash', 'fish', 'cmd', 'powershell5', 'powershell7', 'unknown']
246
+
247
+ shells.forEach((shell) => {
248
+ const caps = getShellCapabilities(shell)
249
+
250
+ expect(caps).toHaveProperty('supportsHook')
251
+ expect(caps).toHaveProperty('supportsHistory')
252
+ expect(caps).toHaveProperty('configPath')
253
+ expect(caps).toHaveProperty('historyPath')
254
+ expect(caps).toHaveProperty('executable')
255
+ expect(caps).toHaveProperty('displayName')
256
+
257
+ expect(typeof caps.supportsHook).toBe('boolean')
258
+ expect(typeof caps.supportsHistory).toBe('boolean')
259
+ expect(typeof caps.executable).toBe('string')
260
+ expect(typeof caps.displayName).toBe('string')
261
+ expect(caps.executable).toBeTruthy()
262
+ expect(caps.displayName).toBeTruthy()
263
+ })
264
+ })
265
+ })