@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.
- package/README.md +226 -624
- package/bin/pls.tsx +27 -7
- package/dist/bin/pls.js +25 -6
- package/dist/package.json +1 -1
- package/dist/src/components/CommandBox.js +2 -2
- package/dist/src/mastra-chat.js +4 -3
- package/dist/src/multi-step.js +4 -5
- package/dist/src/shell-hook.d.ts +19 -0
- package/dist/src/shell-hook.js +63 -0
- package/dist/src/system-history.d.ts +13 -0
- package/dist/src/system-history.js +105 -0
- package/dist/src/utils/console.d.ts +5 -0
- package/dist/src/utils/console.js +3 -1
- package/package.json +1 -1
- package/src/components/CommandBox.tsx +2 -2
- package/src/mastra-chat.ts +4 -2
- package/src/multi-step.ts +4 -3
- package/src/shell-hook.ts +76 -0
- package/src/system-history.ts +117 -0
- package/src/utils/console.ts +4 -1
|
@@ -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
|
+
}
|
package/src/utils/console.ts
CHANGED
|
@@ -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)) + '┐'
|