@yivan-lab/pretty-please 1.3.0 → 1.4.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.
@@ -0,0 +1,117 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import os from 'os'
4
+ import { getConfig } from './config.js'
5
+ import type { ShellHistoryItem } from './shell-hook.js'
6
+
7
+ /**
8
+ * 直接读取系统 shell 历史文件(类似 thefuck)
9
+ * 用于没有安装 shell hook 的情况
10
+ *
11
+ * 限制:系统历史文件不记录退出码,所以 exit 字段都是 0
12
+ */
13
+ export function getSystemShellHistory(): ShellHistoryItem[] {
14
+ const shell = process.env.SHELL || ''
15
+ const home = os.homedir()
16
+
17
+ let historyFile: string
18
+ let parser: (line: string) => ShellHistoryItem | null
19
+
20
+ if (shell.includes('zsh')) {
21
+ // zsh 历史文件
22
+ historyFile = process.env.HISTFILE || path.join(home, '.zsh_history')
23
+ parser = parseZshHistoryLine
24
+ } else if (shell.includes('bash')) {
25
+ // bash 历史文件
26
+ historyFile = process.env.HISTFILE || path.join(home, '.bash_history')
27
+ parser = parseBashHistoryLine
28
+ } else {
29
+ // 不支持的 shell
30
+ return []
31
+ }
32
+
33
+ if (!fs.existsSync(historyFile)) {
34
+ return []
35
+ }
36
+
37
+ try {
38
+ const content = fs.readFileSync(historyFile, 'utf-8')
39
+ const lines = content.trim().split('\n')
40
+ const limit = getConfig().shellHistoryLimit || 10
41
+
42
+ // 只取最后 N 条
43
+ const recentLines = lines.slice(-limit)
44
+
45
+ return recentLines
46
+ .map(line => parser(line))
47
+ .filter((item): item is ShellHistoryItem => item !== null)
48
+ } catch {
49
+ return []
50
+ }
51
+ }
52
+
53
+ /**
54
+ * 解析 zsh 历史行
55
+ * 格式: ": 1234567890:0;ls -la"
56
+ * 或者: "ls -la" (简单格式)
57
+ */
58
+ function parseZshHistoryLine(line: string): ShellHistoryItem | null {
59
+ // 扩展格式: ": timestamp:duration;command"
60
+ const extendedMatch = line.match(/^:\s*(\d+):\d+;(.+)$/)
61
+ if (extendedMatch) {
62
+ const timestamp = parseInt(extendedMatch[1])
63
+ const cmd = extendedMatch[2].trim()
64
+ return {
65
+ cmd,
66
+ exit: 0, // 系统历史文件不记录退出码
67
+ time: new Date(timestamp * 1000).toISOString(),
68
+ }
69
+ }
70
+
71
+ // 简单格式
72
+ const cmd = line.trim()
73
+ if (cmd) {
74
+ return {
75
+ cmd,
76
+ exit: 0,
77
+ time: new Date().toISOString(),
78
+ }
79
+ }
80
+
81
+ return null
82
+ }
83
+
84
+ /**
85
+ * 解析 bash 历史行
86
+ * 格式: "ls -la"
87
+ * bash 历史文件默认不记录时间戳
88
+ */
89
+ function parseBashHistoryLine(line: string): ShellHistoryItem | null {
90
+ const cmd = line.trim()
91
+ if (cmd) {
92
+ return {
93
+ cmd,
94
+ exit: 0, // 系统历史文件不记录退出码
95
+ time: new Date().toISOString(),
96
+ }
97
+ }
98
+ return null
99
+ }
100
+
101
+ /**
102
+ * 从系统历史中获取最近一条命令
103
+ * 排除 pls 命令本身
104
+ */
105
+ export function getLastCommandFromSystem(): ShellHistoryItem | null {
106
+ const history = getSystemShellHistory()
107
+
108
+ // 从后往前找第一条非 pls 命令
109
+ for (let i = history.length - 1; i >= 0; i--) {
110
+ const item = history[i]
111
+ if (!item.cmd.startsWith('pls') && !item.cmd.startsWith('please')) {
112
+ return item
113
+ }
114
+ }
115
+
116
+ return null
117
+ }
@@ -6,6 +6,9 @@ import { getCurrentTheme } from '../ui/theme.js'
6
6
  * 用于不需要 Ink 的场景,避免清屏和性能问题
7
7
  */
8
8
 
9
+ // 命令框最小宽度(统一配置)
10
+ export const MIN_COMMAND_BOX_WIDTH = 20
11
+
9
12
  // 获取当前主题颜色
10
13
  function getColors() {
11
14
  const theme = getCurrentTheme()
@@ -115,7 +118,7 @@ export function drawCommandBox(command: string, title: string = '生成命令'):
115
118
  ...wrappedLines.map((l) => getDisplayWidth(l)),
116
119
  titleWidth
117
120
  )
118
- const boxWidth = Math.min(actualMaxWidth + 4, termWidth - 2)
121
+ const boxWidth = Math.max(MIN_COMMAND_BOX_WIDTH, Math.min(actualMaxWidth + 4, termWidth - 2))
119
122
 
120
123
  const topPadding = boxWidth - titleWidth - 5
121
124
  const topBorder = '┌─ ' + title + ' ' + '─'.repeat(Math.max(0, topPadding)) + '┐'