@yivan-lab/pretty-please 1.0.0 → 1.1.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 (64) hide show
  1. package/README.md +98 -27
  2. package/bin/pls.tsx +135 -24
  3. package/dist/bin/pls.d.ts +1 -1
  4. package/dist/bin/pls.js +117 -24
  5. package/dist/package.json +80 -0
  6. package/dist/src/ai.d.ts +1 -41
  7. package/dist/src/ai.js +9 -190
  8. package/dist/src/builtin-detector.d.ts +14 -8
  9. package/dist/src/builtin-detector.js +36 -16
  10. package/dist/src/chat-history.d.ts +16 -11
  11. package/dist/src/chat-history.js +26 -4
  12. package/dist/src/components/Chat.js +3 -3
  13. package/dist/src/components/CommandBox.js +1 -16
  14. package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
  15. package/dist/src/components/ConfirmationPrompt.js +7 -3
  16. package/dist/src/components/MultiStepCommandGenerator.d.ts +2 -0
  17. package/dist/src/components/MultiStepCommandGenerator.js +110 -7
  18. package/dist/src/config.d.ts +27 -8
  19. package/dist/src/config.js +92 -33
  20. package/dist/src/history.d.ts +19 -5
  21. package/dist/src/history.js +26 -11
  22. package/dist/src/mastra-agent.d.ts +0 -1
  23. package/dist/src/mastra-agent.js +3 -4
  24. package/dist/src/mastra-chat.d.ts +28 -0
  25. package/dist/src/mastra-chat.js +93 -0
  26. package/dist/src/multi-step.d.ts +2 -2
  27. package/dist/src/multi-step.js +2 -2
  28. package/dist/src/prompts.d.ts +11 -0
  29. package/dist/src/prompts.js +140 -0
  30. package/dist/src/shell-hook.d.ts +35 -13
  31. package/dist/src/shell-hook.js +82 -7
  32. package/dist/src/sysinfo.d.ts +9 -5
  33. package/dist/src/sysinfo.js +2 -2
  34. package/dist/src/utils/console.d.ts +11 -11
  35. package/dist/src/utils/console.js +4 -6
  36. package/package.json +8 -6
  37. package/src/builtin-detector.ts +126 -0
  38. package/src/chat-history.ts +130 -0
  39. package/src/components/Chat.tsx +4 -4
  40. package/src/components/CommandBox.tsx +1 -16
  41. package/src/components/ConfirmationPrompt.tsx +9 -2
  42. package/src/components/MultiStepCommandGenerator.tsx +144 -7
  43. package/src/config.ts +309 -0
  44. package/src/history.ts +160 -0
  45. package/src/mastra-agent.ts +3 -4
  46. package/src/mastra-chat.ts +124 -0
  47. package/src/multi-step.ts +2 -2
  48. package/src/prompts.ts +154 -0
  49. package/src/shell-hook.ts +502 -0
  50. package/src/{sysinfo.js → sysinfo.ts} +28 -16
  51. package/src/utils/{console.js → console.ts} +16 -18
  52. package/bin/pls.js +0 -681
  53. package/src/ai.js +0 -324
  54. package/src/builtin-detector.js +0 -98
  55. package/src/chat-history.js +0 -94
  56. package/src/components/ChatStatus.tsx +0 -53
  57. package/src/components/CommandGenerator.tsx +0 -184
  58. package/src/components/ConfigDisplay.tsx +0 -64
  59. package/src/components/ConfigWizard.tsx +0 -101
  60. package/src/components/HistoryDisplay.tsx +0 -69
  61. package/src/components/HookManager.tsx +0 -150
  62. package/src/config.js +0 -221
  63. package/src/history.js +0 -131
  64. package/src/shell-hook.js +0 -393
@@ -0,0 +1,502 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import os from 'os'
4
+ import chalk from 'chalk'
5
+ import { CONFIG_DIR, getConfig, setConfigValue } from './config.js'
6
+ import { getHistory } from './history.js'
7
+
8
+ const SHELL_HISTORY_FILE = path.join(CONFIG_DIR, 'shell_history.jsonl')
9
+ const MAX_SHELL_HISTORY = 20
10
+
11
+ // Hook 标记,用于识别我们添加的内容
12
+ const HOOK_START_MARKER = '# >>> pretty-please shell hook >>>'
13
+ const HOOK_END_MARKER = '# <<< pretty-please shell hook <<<'
14
+
15
+ // Shell 类型
16
+ type ShellType = 'zsh' | 'bash' | 'powershell' | 'unknown'
17
+
18
+ /**
19
+ * Shell 历史记录项
20
+ */
21
+ export interface ShellHistoryItem {
22
+ cmd: string
23
+ exit: number
24
+ time: string
25
+ }
26
+
27
+ /**
28
+ * Hook 状态
29
+ */
30
+ export interface HookStatus {
31
+ enabled: boolean
32
+ installed: boolean
33
+ shellType: ShellType
34
+ configPath: string | null
35
+ historyFile: string
36
+ }
37
+
38
+ /**
39
+ * 检测当前 shell 类型
40
+ */
41
+ export function detectShell(): ShellType {
42
+ const shell = process.env.SHELL || ''
43
+ if (shell.includes('zsh')) return 'zsh'
44
+ if (shell.includes('bash')) return 'bash'
45
+ // Windows PowerShell
46
+ if (process.platform === 'win32') return 'powershell'
47
+ return 'unknown'
48
+ }
49
+
50
+ /**
51
+ * 获取 shell 配置文件路径
52
+ */
53
+ export function getShellConfigPath(shellType: ShellType): string | null {
54
+ const home = os.homedir()
55
+ switch (shellType) {
56
+ case 'zsh':
57
+ return path.join(home, '.zshrc')
58
+ case 'bash':
59
+ // macOS 使用 .bash_profile,Linux 使用 .bashrc
60
+ if (process.platform === 'darwin') {
61
+ return path.join(home, '.bash_profile')
62
+ }
63
+ return path.join(home, '.bashrc')
64
+ case 'powershell':
65
+ // PowerShell profile 路径
66
+ return path.join(home, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
67
+ default:
68
+ return null
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 生成 zsh hook 脚本
74
+ */
75
+ function generateZshHook(): string {
76
+ return `
77
+ ${HOOK_START_MARKER}
78
+ # 记录命令到 pretty-please 历史
79
+ __pls_preexec() {
80
+ __PLS_LAST_CMD="$1"
81
+ __PLS_CMD_START=$(date +%s)
82
+ }
83
+
84
+ __pls_precmd() {
85
+ local exit_code=$?
86
+ if [[ -n "$__PLS_LAST_CMD" ]]; then
87
+ local end_time=$(date +%s)
88
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
89
+ # 转义命令中的特殊字符
90
+ local escaped_cmd=$(echo "$__PLS_LAST_CMD" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
91
+ echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
92
+ # 保持文件不超过 ${MAX_SHELL_HISTORY} 行
93
+ 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"
94
+ unset __PLS_LAST_CMD
95
+ fi
96
+ }
97
+
98
+ autoload -Uz add-zsh-hook
99
+ add-zsh-hook preexec __pls_preexec
100
+ add-zsh-hook precmd __pls_precmd
101
+ ${HOOK_END_MARKER}
102
+ `
103
+ }
104
+
105
+ /**
106
+ * 生成 bash hook 脚本
107
+ */
108
+ function generateBashHook(): string {
109
+ return `
110
+ ${HOOK_START_MARKER}
111
+ # 记录命令到 pretty-please 历史
112
+ __pls_prompt_command() {
113
+ local exit_code=$?
114
+ local last_cmd=$(history 1 | sed 's/^ *[0-9]* *//')
115
+ if [[ -n "$last_cmd" && "$last_cmd" != "$__PLS_LAST_CMD" ]]; then
116
+ __PLS_LAST_CMD="$last_cmd"
117
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
118
+ local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
119
+ echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
120
+ 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"
121
+ fi
122
+ }
123
+
124
+ if [[ ! "$PROMPT_COMMAND" =~ __pls_prompt_command ]]; then
125
+ PROMPT_COMMAND="__pls_prompt_command;\${PROMPT_COMMAND}"
126
+ fi
127
+ ${HOOK_END_MARKER}
128
+ `
129
+ }
130
+
131
+ /**
132
+ * 生成 PowerShell hook 脚本
133
+ */
134
+ function generatePowerShellHook(): string {
135
+ return `
136
+ ${HOOK_START_MARKER}
137
+ # 记录命令到 pretty-please 历史
138
+ $Global:__PlsLastCmd = ""
139
+
140
+ function __Pls_RecordCommand {
141
+ $lastCmd = (Get-History -Count 1).CommandLine
142
+ if ($lastCmd -and $lastCmd -ne $Global:__PlsLastCmd) {
143
+ $Global:__PlsLastCmd = $lastCmd
144
+ $exitCode = $LASTEXITCODE
145
+ if ($null -eq $exitCode) { $exitCode = 0 }
146
+ $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
147
+ $escapedCmd = $lastCmd -replace '\\\\', '\\\\\\\\' -replace '"', '\\\\"'
148
+ $json = "{\`"cmd\`":\`"$escapedCmd\`",\`"exit\`":$exitCode,\`"time\`":\`"$timestamp\`"}"
149
+ Add-Content -Path "${CONFIG_DIR}/shell_history.jsonl" -Value $json
150
+ # 保持文件不超过 ${MAX_SHELL_HISTORY} 行
151
+ $content = Get-Content "${CONFIG_DIR}/shell_history.jsonl" -Tail ${MAX_SHELL_HISTORY}
152
+ $content | Set-Content "${CONFIG_DIR}/shell_history.jsonl"
153
+ }
154
+ }
155
+
156
+ if (-not (Get-Variable -Name __PlsPromptBackup -ErrorAction SilentlyContinue)) {
157
+ $Global:__PlsPromptBackup = $function:prompt
158
+ function Global:prompt {
159
+ __Pls_RecordCommand
160
+ & $Global:__PlsPromptBackup
161
+ }
162
+ }
163
+ ${HOOK_END_MARKER}
164
+ `
165
+ }
166
+
167
+ /**
168
+ * 生成 hook 脚本
169
+ */
170
+ function generateHookScript(shellType: ShellType): string | null {
171
+ switch (shellType) {
172
+ case 'zsh':
173
+ return generateZshHook()
174
+ case 'bash':
175
+ return generateBashHook()
176
+ case 'powershell':
177
+ return generatePowerShellHook()
178
+ default:
179
+ return null
180
+ }
181
+ }
182
+
183
+ /**
184
+ * 安装 shell hook
185
+ */
186
+ export async function installShellHook(): Promise<boolean> {
187
+ const shellType = detectShell()
188
+ const configPath = getShellConfigPath(shellType)
189
+
190
+ if (!configPath) {
191
+ console.log(chalk.red(`❌ 不支持的 shell 类型: ${shellType}`))
192
+ return false
193
+ }
194
+
195
+ const hookScript = generateHookScript(shellType)
196
+ if (!hookScript) {
197
+ console.log(chalk.red(`❌ 无法为 ${shellType} 生成 hook 脚本`))
198
+ return false
199
+ }
200
+
201
+ // 检查是否已安装
202
+ if (fs.existsSync(configPath)) {
203
+ const content = fs.readFileSync(configPath, 'utf-8')
204
+ if (content.includes(HOOK_START_MARKER)) {
205
+ console.log(chalk.yellow('⚠️ Shell hook 已安装,跳过'))
206
+ setConfigValue('shellHook', true)
207
+ return true
208
+ }
209
+ }
210
+
211
+ // 确保配置目录存在
212
+ if (!fs.existsSync(CONFIG_DIR)) {
213
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
214
+ }
215
+
216
+ // 备份原配置文件
217
+ if (fs.existsSync(configPath)) {
218
+ const backupPath = configPath + '.pls-backup'
219
+ fs.copyFileSync(configPath, backupPath)
220
+ console.log(chalk.gray(`已备份原配置文件到: ${backupPath}`))
221
+ }
222
+
223
+ // 追加 hook 脚本
224
+ fs.appendFileSync(configPath, hookScript)
225
+
226
+ // 更新配置
227
+ setConfigValue('shellHook', true)
228
+
229
+ console.log(chalk.green(`✅ Shell hook 已安装到: ${configPath}`))
230
+ console.log(chalk.yellow('⚠️ 请重启终端或执行以下命令使其生效:'))
231
+ console.log(chalk.cyan(` source ${configPath}`))
232
+
233
+ return true
234
+ }
235
+
236
+ /**
237
+ * 卸载 shell hook
238
+ */
239
+ export function uninstallShellHook(): boolean {
240
+ const shellType = detectShell()
241
+ const configPath = getShellConfigPath(shellType)
242
+
243
+ if (!configPath || !fs.existsSync(configPath)) {
244
+ console.log(chalk.yellow('⚠️ 未找到 shell 配置文件'))
245
+ setConfigValue('shellHook', false)
246
+ return true
247
+ }
248
+
249
+ let content = fs.readFileSync(configPath, 'utf-8')
250
+
251
+ // 移除 hook 脚本
252
+ const startIndex = content.indexOf(HOOK_START_MARKER)
253
+ const endIndex = content.indexOf(HOOK_END_MARKER)
254
+
255
+ if (startIndex === -1 || endIndex === -1) {
256
+ console.log(chalk.yellow('⚠️ 未找到已安装的 hook'))
257
+ setConfigValue('shellHook', false)
258
+ return true
259
+ }
260
+
261
+ // 移除从标记开始到结束的所有内容(包括换行符)
262
+ const before = content.substring(0, startIndex)
263
+ const after = content.substring(endIndex + HOOK_END_MARKER.length)
264
+ content = before + after.replace(/^\n/, '')
265
+
266
+ fs.writeFileSync(configPath, content)
267
+ setConfigValue('shellHook', false)
268
+
269
+ // 清空 shell 历史文件
270
+ if (fs.existsSync(SHELL_HISTORY_FILE)) {
271
+ fs.unlinkSync(SHELL_HISTORY_FILE)
272
+ }
273
+
274
+ console.log(chalk.green('✅ Shell hook 已卸载'))
275
+ console.log(chalk.yellow('⚠️ 请重启终端使其生效'))
276
+
277
+ return true
278
+ }
279
+
280
+ /**
281
+ * 读取 shell 历史记录
282
+ */
283
+ export function getShellHistory(): ShellHistoryItem[] {
284
+ const config = getConfig()
285
+
286
+ // 如果未启用 shell hook,返回空数组
287
+ if (!config.shellHook) {
288
+ return []
289
+ }
290
+
291
+ if (!fs.existsSync(SHELL_HISTORY_FILE)) {
292
+ return []
293
+ }
294
+
295
+ try {
296
+ const content = fs.readFileSync(SHELL_HISTORY_FILE, 'utf-8')
297
+ const lines = content
298
+ .trim()
299
+ .split('\n')
300
+ .filter((line) => line.trim())
301
+
302
+ return lines
303
+ .map((line) => {
304
+ try {
305
+ return JSON.parse(line) as ShellHistoryItem
306
+ } catch {
307
+ return null
308
+ }
309
+ })
310
+ .filter((item): item is ShellHistoryItem => item !== null)
311
+ } catch {
312
+ return []
313
+ }
314
+ }
315
+
316
+ /**
317
+ * 从 pls history 中查找匹配的记录
318
+ */
319
+ function findPlsHistoryMatch(prompt: string): ReturnType<typeof getHistory>[number] | null {
320
+ const plsHistory = getHistory()
321
+
322
+ // 尝试精确匹配 userPrompt
323
+ for (const record of plsHistory) {
324
+ if (record.userPrompt === prompt) {
325
+ return record
326
+ }
327
+ }
328
+
329
+ // 尝试模糊匹配(处理引号等情况)
330
+ const normalizedPrompt = prompt.trim().replace(/^["']|["']$/g, '')
331
+ for (const record of plsHistory) {
332
+ if (record.userPrompt === normalizedPrompt) {
333
+ return record
334
+ }
335
+ }
336
+
337
+ return null
338
+ }
339
+
340
+ /**
341
+ * 格式化 shell 历史供 AI 使用
342
+ * 对于 pls 命令,会从 pls history 中查找对应的详细信息
343
+ */
344
+ export function formatShellHistoryForAI(): string {
345
+ const history = getShellHistory()
346
+
347
+ if (history.length === 0) {
348
+ return ''
349
+ }
350
+
351
+ // pls 的子命令列表(这些不是 AI prompt)
352
+ const plsSubcommands = ['config', 'history', 'hook', 'help', '--help', '-h', '--version', '-v']
353
+
354
+ const lines = history.map((item, index) => {
355
+ const status = item.exit === 0 ? '✓' : `✗ 退出码:${item.exit}`
356
+
357
+ // 检查是否是 pls 命令
358
+ const plsMatch = item.cmd.match(/^(pls|please)\s+(.+)$/)
359
+ if (plsMatch) {
360
+ let args = plsMatch[2]
361
+
362
+ // 去掉 --debug / -d 选项,获取真正的参数
363
+ args = args.replace(/^(--debug|-d)\s+/, '')
364
+
365
+ const firstArg = args.split(/\s+/)[0]
366
+
367
+ // 如果是子命令,当作普通命令处理
368
+ if (plsSubcommands.includes(firstArg)) {
369
+ return `${index + 1}. ${item.cmd} ${status}`
370
+ }
371
+
372
+ // 是 AI prompt,尝试从 pls history 查找详细信息
373
+ const prompt = args
374
+ const plsRecord = findPlsHistoryMatch(prompt)
375
+
376
+ if (plsRecord) {
377
+ // 找到对应的 pls 记录,展示详细信息
378
+ if (plsRecord.reason === 'builtin') {
379
+ return `${index + 1}. [pls] "${prompt}" → 生成命令: ${plsRecord.command} (包含 builtin,未执行)`
380
+ } else if (plsRecord.executed) {
381
+ const execStatus = plsRecord.exitCode === 0 ? '✓' : `✗ 退出码:${plsRecord.exitCode}`
382
+
383
+ // 检查用户是否修改了命令
384
+ if (plsRecord.userModified && plsRecord.aiGeneratedCommand) {
385
+ return `${index + 1}. [pls] "${prompt}" → AI 生成: ${plsRecord.aiGeneratedCommand} / 用户修改为: ${plsRecord.command} ${execStatus}`
386
+ } else {
387
+ return `${index + 1}. [pls] "${prompt}" → 实际执行: ${plsRecord.command} ${execStatus}`
388
+ }
389
+ } else {
390
+ return `${index + 1}. [pls] "${prompt}" → 生成命令: ${plsRecord.command} (用户取消执行)`
391
+ }
392
+ }
393
+ // 找不到记录,只显示原始命令
394
+ return `${index + 1}. [pls] "${prompt}" ${status}`
395
+ }
396
+
397
+ // 普通命令
398
+ return `${index + 1}. ${item.cmd} ${status}`
399
+ })
400
+
401
+ return `【用户终端最近执行的命令】\n${lines.join('\n')}`
402
+ }
403
+
404
+ /**
405
+ * 获取 hook 状态
406
+ */
407
+ export function getHookStatus(): HookStatus {
408
+ const config = getConfig()
409
+ const shellType = detectShell()
410
+ const configPath = getShellConfigPath(shellType)
411
+
412
+ let installed = false
413
+ if (configPath && fs.existsSync(configPath)) {
414
+ const content = fs.readFileSync(configPath, 'utf-8')
415
+ installed = content.includes(HOOK_START_MARKER)
416
+ }
417
+
418
+ return {
419
+ enabled: config.shellHook,
420
+ installed,
421
+ shellType,
422
+ configPath,
423
+ historyFile: SHELL_HISTORY_FILE,
424
+ }
425
+ }
426
+
427
+ /**
428
+ * 显示 shell 历史
429
+ */
430
+ export function displayShellHistory(): void {
431
+ const config = getConfig()
432
+ const history = getShellHistory()
433
+
434
+ if (!config.shellHook) {
435
+ console.log('')
436
+ console.log(chalk.yellow('⚠️ Shell Hook 未启用'))
437
+ console.log(chalk.gray('运行 ') + chalk.cyan('pls hook install') + chalk.gray(' 启用 Shell Hook'))
438
+ console.log('')
439
+ return
440
+ }
441
+
442
+ if (history.length === 0) {
443
+ console.log('')
444
+ console.log(chalk.gray('暂无 Shell 历史记录'))
445
+ console.log('')
446
+ return
447
+ }
448
+
449
+ console.log('')
450
+ console.log(chalk.bold(`终端历史(最近 ${history.length} 条):`))
451
+ console.log(chalk.gray('━'.repeat(50)))
452
+
453
+ history.forEach((item, index) => {
454
+ const num = index + 1
455
+ const status = item.exit === 0 ? chalk.green('✓') : chalk.red(`✗ (${item.exit})`)
456
+
457
+ // 检查是否是 pls 命令
458
+ const isPls = item.cmd.startsWith('pls ') || item.cmd.startsWith('please ')
459
+
460
+ if (isPls) {
461
+ // pls 命令:尝试从 history 查找详细信息
462
+ const args = item.cmd.replace(/^(pls|please)\s+/, '')
463
+ const plsRecord = findPlsHistoryMatch(args)
464
+
465
+ if (plsRecord && plsRecord.executed) {
466
+ // 检查用户是否修改了命令
467
+ if (plsRecord.userModified && plsRecord.aiGeneratedCommand) {
468
+ console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} "${args}"`)
469
+ console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(plsRecord.aiGeneratedCommand)}`)
470
+ console.log(
471
+ ` ${chalk.dim('用户修改为:')} ${plsRecord.command} ${status} ${chalk.yellow('(已修改)')}`
472
+ )
473
+ } else {
474
+ console.log(
475
+ ` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} "${args}" → ${plsRecord.command} ${status}`
476
+ )
477
+ }
478
+ } else {
479
+ console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${chalk.magenta('[pls]')} ${args} ${status}`)
480
+ }
481
+ } else {
482
+ console.log(` ${chalk.cyan(num.toString().padStart(2, ' '))}. ${item.cmd} ${status}`)
483
+ }
484
+ })
485
+
486
+ console.log(chalk.gray('━'.repeat(50)))
487
+ console.log(chalk.gray(`配置: 保留最近 ${config.shellHistoryLimit} 条`))
488
+ console.log(chalk.gray(`文件: ${SHELL_HISTORY_FILE}`))
489
+ console.log('')
490
+ }
491
+
492
+ /**
493
+ * 清空 shell 历史
494
+ */
495
+ export function clearShellHistory(): void {
496
+ if (fs.existsSync(SHELL_HISTORY_FILE)) {
497
+ fs.unlinkSync(SHELL_HISTORY_FILE)
498
+ }
499
+ console.log('')
500
+ console.log(chalk.green('✓ Shell 历史已清空'))
501
+ console.log('')
502
+ }
@@ -1,10 +1,22 @@
1
- import os from 'os';
2
- import { execSync } from 'child_process';
1
+ import os from 'os'
2
+ import { execSync } from 'child_process'
3
+
4
+ /**
5
+ * 系统信息
6
+ */
7
+ export interface SystemInfo {
8
+ os: NodeJS.Platform
9
+ arch: string
10
+ shell: string
11
+ packageManager: string
12
+ cwd: string
13
+ user: string
14
+ }
3
15
 
4
16
  /**
5
17
  * 检测包管理器
6
18
  */
7
- function detectPackageManager() {
19
+ function detectPackageManager(): string {
8
20
  const managers = [
9
21
  { name: 'brew', command: 'brew' },
10
22
  { name: 'apt', command: 'apt-get' },
@@ -12,46 +24,46 @@ function detectPackageManager() {
12
24
  { name: 'yum', command: 'yum' },
13
25
  { name: 'pacman', command: 'pacman' },
14
26
  { name: 'zypper', command: 'zypper' },
15
- { name: 'apk', command: 'apk' }
16
- ];
27
+ { name: 'apk', command: 'apk' },
28
+ ]
17
29
 
18
30
  for (const mgr of managers) {
19
31
  try {
20
- execSync(`which ${mgr.command}`, { stdio: 'ignore' });
21
- return mgr.name;
32
+ execSync(`which ${mgr.command}`, { stdio: 'ignore' })
33
+ return mgr.name
22
34
  } catch {
23
35
  // 继续检测下一个
24
36
  }
25
37
  }
26
38
 
27
- return 'unknown';
39
+ return 'unknown'
28
40
  }
29
41
 
30
42
  /**
31
43
  * 获取当前 Shell
32
44
  */
33
- function getCurrentShell() {
34
- return process.env.SHELL || 'unknown';
45
+ function getCurrentShell(): string {
46
+ return process.env.SHELL || 'unknown'
35
47
  }
36
48
 
37
49
  /**
38
50
  * 收集系统信息
39
51
  */
40
- export function collectSystemInfo() {
52
+ export function collectSystemInfo(): SystemInfo {
41
53
  return {
42
54
  os: os.platform(),
43
55
  arch: os.arch(),
44
56
  shell: getCurrentShell(),
45
57
  packageManager: detectPackageManager(),
46
58
  cwd: process.cwd(),
47
- user: os.userInfo().username
48
- };
59
+ user: os.userInfo().username,
60
+ }
49
61
  }
50
62
 
51
63
  /**
52
64
  * 将系统信息格式化为字符串(供 AI 使用)
53
65
  */
54
- export function formatSystemInfo() {
55
- const info = collectSystemInfo();
56
- return `OS: ${info.os}, Arch: ${info.arch}, Shell: ${info.shell}, PkgMgr: ${info.packageManager}, CWD: ${info.cwd}`;
66
+ export function formatSystemInfo(): string {
67
+ const info = collectSystemInfo()
68
+ return `OS: ${info.os}, Arch: ${info.arch}, Shell: ${info.shell}, PkgMgr: ${info.packageManager}, CWD: ${info.cwd}`
57
69
  }
@@ -15,12 +15,12 @@ const colors = {
15
15
  warning: '#F59E0B',
16
16
  info: '#3B82F6',
17
17
  muted: '#6B7280',
18
- }
18
+ } as const
19
19
 
20
20
  /**
21
21
  * 计算字符串的显示宽度(中文占2个宽度)
22
22
  */
23
- export function getDisplayWidth(str) {
23
+ export function getDisplayWidth(str: string): number {
24
24
  let width = 0
25
25
  for (const char of str) {
26
26
  if (char.match(/[\u4e00-\u9fff\u3400-\u4dbf\uff00-\uffef\u3000-\u303f]/)) {
@@ -35,10 +35,10 @@ export function getDisplayWidth(str) {
35
35
  /**
36
36
  * 绘制命令框(原生版本)
37
37
  */
38
- export function drawCommandBox(command, title = '生成命令') {
38
+ export function drawCommandBox(command: string, title: string = '生成命令'): void {
39
39
  const lines = command.split('\n')
40
40
  const titleWidth = getDisplayWidth(title)
41
- const maxContentWidth = Math.max(...lines.map(l => getDisplayWidth(l)))
41
+ const maxContentWidth = Math.max(...lines.map((l) => getDisplayWidth(l)))
42
42
  const boxWidth = Math.max(maxContentWidth + 4, titleWidth + 6, 20)
43
43
 
44
44
  const topPadding = boxWidth - titleWidth - 5
@@ -50,10 +50,7 @@ export function drawCommandBox(command, title = '生成命令') {
50
50
  const lineWidth = getDisplayWidth(line)
51
51
  const padding = ' '.repeat(boxWidth - lineWidth - 4)
52
52
  console.log(
53
- chalk.hex(colors.warning)('│ ') +
54
- chalk.hex(colors.primary)(line) +
55
- padding +
56
- chalk.hex(colors.warning)(' │')
53
+ chalk.hex(colors.warning)('│ ') + chalk.hex(colors.primary)(line) + padding + chalk.hex(colors.warning)(' │')
57
54
  )
58
55
  }
59
56
  console.log(chalk.hex(colors.warning)(bottomBorder))
@@ -62,7 +59,7 @@ export function drawCommandBox(command, title = '生成命令') {
62
59
  /**
63
60
  * 格式化耗时
64
61
  */
65
- export function formatDuration(ms) {
62
+ export function formatDuration(ms: number): string {
66
63
  if (ms < 1000) {
67
64
  return `${ms}ms`
68
65
  }
@@ -72,9 +69,10 @@ export function formatDuration(ms) {
72
69
  /**
73
70
  * 输出分隔线
74
71
  */
75
- export function printSeparator(text = '输出', length = 38) {
72
+ export function printSeparator(text: string = '输出', length: number = 38): void {
76
73
  const textPart = text ? ` ${text} ` : ''
77
- const lineLength = Math.max(0, length - textPart.length)
74
+ const textWidth = getDisplayWidth(textPart) // 使用显示宽度而不是字符数
75
+ const lineLength = Math.max(0, length - textWidth)
78
76
  const leftDashes = '─'.repeat(Math.floor(lineLength / 2))
79
77
  const rightDashes = '─'.repeat(Math.ceil(lineLength / 2))
80
78
  console.log(chalk.gray(`${leftDashes}${textPart}${rightDashes}`))
@@ -83,48 +81,48 @@ export function printSeparator(text = '输出', length = 38) {
83
81
  /**
84
82
  * 输出成功消息
85
83
  */
86
- export function success(message) {
84
+ export function success(message: string): void {
87
85
  console.log(chalk.hex(colors.success)('✓ ' + message))
88
86
  }
89
87
 
90
88
  /**
91
89
  * 输出错误消息
92
90
  */
93
- export function error(message) {
91
+ export function error(message: string): void {
94
92
  console.log(chalk.hex(colors.error)('✗ ' + message))
95
93
  }
96
94
 
97
95
  /**
98
96
  * 输出警告消息
99
97
  */
100
- export function warning(message) {
98
+ export function warning(message: string): void {
101
99
  console.log(chalk.hex(colors.warning)('⚠️ ' + message))
102
100
  }
103
101
 
104
102
  /**
105
103
  * 输出信息消息
106
104
  */
107
- export function info(message) {
105
+ export function info(message: string): void {
108
106
  console.log(chalk.hex(colors.info)(message))
109
107
  }
110
108
 
111
109
  /**
112
110
  * 输出灰色文本
113
111
  */
114
- export function muted(message) {
112
+ export function muted(message: string): void {
115
113
  console.log(chalk.hex(colors.muted)(message))
116
114
  }
117
115
 
118
116
  /**
119
117
  * 输出标题
120
118
  */
121
- export function title(message) {
119
+ export function title(message: string): void {
122
120
  console.log(chalk.bold(message))
123
121
  }
124
122
 
125
123
  /**
126
124
  * 输出主色文本
127
125
  */
128
- export function primary(message) {
126
+ export function primary(message: string): void {
129
127
  console.log(chalk.hex(colors.primary)(message))
130
128
  }