@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.
- package/README.md +250 -620
- package/bin/pls.tsx +178 -40
- package/dist/bin/pls.js +149 -27
- package/dist/package.json +10 -2
- package/dist/src/__integration__/command-generation.test.d.ts +5 -0
- package/dist/src/__integration__/command-generation.test.js +508 -0
- package/dist/src/__integration__/error-recovery.test.d.ts +5 -0
- package/dist/src/__integration__/error-recovery.test.js +511 -0
- package/dist/src/__integration__/shell-hook-workflow.test.d.ts +5 -0
- package/dist/src/__integration__/shell-hook-workflow.test.js +375 -0
- package/dist/src/__tests__/alias.test.d.ts +5 -0
- package/dist/src/__tests__/alias.test.js +421 -0
- package/dist/src/__tests__/chat-history.test.d.ts +5 -0
- package/dist/src/__tests__/chat-history.test.js +372 -0
- package/dist/src/__tests__/config.test.d.ts +5 -0
- package/dist/src/__tests__/config.test.js +822 -0
- package/dist/src/__tests__/history.test.d.ts +5 -0
- package/dist/src/__tests__/history.test.js +439 -0
- package/dist/src/__tests__/remote-history.test.d.ts +5 -0
- package/dist/src/__tests__/remote-history.test.js +641 -0
- package/dist/src/__tests__/remote.test.d.ts +5 -0
- package/dist/src/__tests__/remote.test.js +689 -0
- package/dist/src/__tests__/shell-hook-install.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-install.test.js +413 -0
- package/dist/src/__tests__/shell-hook-remote.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-remote.test.js +507 -0
- package/dist/src/__tests__/shell-hook.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook.test.js +440 -0
- package/dist/src/__tests__/sysinfo.test.d.ts +5 -0
- package/dist/src/__tests__/sysinfo.test.js +572 -0
- package/dist/src/__tests__/system-history.test.d.ts +5 -0
- package/dist/src/__tests__/system-history.test.js +457 -0
- package/dist/src/components/Chat.js +9 -28
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +30 -2
- package/dist/src/mastra-chat.js +10 -6
- package/dist/src/multi-step.js +10 -8
- package/dist/src/project-context.d.ts +22 -0
- package/dist/src/project-context.js +168 -0
- package/dist/src/prompts.d.ts +4 -4
- package/dist/src/prompts.js +23 -6
- package/dist/src/shell-hook.d.ts +32 -0
- package/dist/src/shell-hook.js +226 -33
- package/dist/src/sysinfo.d.ts +38 -9
- package/dist/src/sysinfo.js +245 -21
- package/dist/src/system-history.d.ts +18 -0
- package/dist/src/system-history.js +151 -0
- package/dist/src/ui/__tests__/theme.test.d.ts +5 -0
- package/dist/src/ui/__tests__/theme.test.js +688 -0
- package/dist/src/upgrade.js +3 -0
- package/dist/src/user-preferences.d.ts +44 -0
- package/dist/src/user-preferences.js +147 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.js +214 -0
- package/dist/src/utils/__tests__/platform-exec.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-exec.test.js +212 -0
- package/dist/src/utils/__tests__/platform-shell.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-shell.test.js +300 -0
- package/dist/src/utils/__tests__/platform.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform.test.js +137 -0
- package/dist/src/utils/platform.d.ts +88 -0
- package/dist/src/utils/platform.js +331 -0
- package/package.json +10 -2
- package/src/__integration__/command-generation.test.ts +602 -0
- package/src/__integration__/error-recovery.test.ts +620 -0
- package/src/__integration__/shell-hook-workflow.test.ts +457 -0
- package/src/__tests__/alias.test.ts +545 -0
- package/src/__tests__/chat-history.test.ts +462 -0
- package/src/__tests__/config.test.ts +1043 -0
- package/src/__tests__/history.test.ts +538 -0
- package/src/__tests__/remote-history.test.ts +791 -0
- package/src/__tests__/remote.test.ts +866 -0
- package/src/__tests__/shell-hook-install.test.ts +510 -0
- package/src/__tests__/shell-hook-remote.test.ts +679 -0
- package/src/__tests__/shell-hook.test.ts +564 -0
- package/src/__tests__/sysinfo.test.ts +718 -0
- package/src/__tests__/system-history.test.ts +608 -0
- package/src/components/Chat.tsx +10 -37
- package/src/config.ts +29 -2
- package/src/mastra-chat.ts +12 -5
- package/src/multi-step.ts +11 -5
- package/src/project-context.ts +191 -0
- package/src/prompts.ts +26 -5
- package/src/shell-hook.ts +254 -32
- package/src/sysinfo.ts +326 -25
- package/src/system-history.ts +170 -0
- package/src/ui/__tests__/theme.test.ts +869 -0
- package/src/upgrade.ts +5 -0
- package/src/user-preferences.ts +178 -0
- package/src/utils/__tests__/platform-capabilities.test.ts +265 -0
- package/src/utils/__tests__/platform-exec.test.ts +278 -0
- package/src/utils/__tests__/platform-shell.test.ts +353 -0
- package/src/utils/__tests__/platform.test.ts +170 -0
- package/src/utils/platform.ts +431 -0
package/src/sysinfo.ts
CHANGED
|
@@ -1,23 +1,116 @@
|
|
|
1
1
|
import os from 'os'
|
|
2
|
-
import
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import { detectProjectContext, formatProjectContext, type ProjectContext } from './project-context.js'
|
|
6
|
+
import { getConfig, CONFIG_DIR } from './config.js'
|
|
7
|
+
import { getCurrentTheme } from './ui/theme.js'
|
|
8
|
+
import {
|
|
9
|
+
detectShell,
|
|
10
|
+
getShellCapabilities,
|
|
11
|
+
commandExists,
|
|
12
|
+
batchCommandExists,
|
|
13
|
+
isWindows,
|
|
14
|
+
} from './utils/platform.js'
|
|
3
15
|
|
|
4
16
|
/**
|
|
5
|
-
*
|
|
17
|
+
* 要检测的命令列表(45 个)
|
|
6
18
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
const COMMANDS_TO_CHECK = {
|
|
20
|
+
// 现代 CLI 工具(ls/find/grep/cat 等的替代品)
|
|
21
|
+
modern: [
|
|
22
|
+
'eza', 'lsd', 'exa', // ls 替代
|
|
23
|
+
'fd', 'fdfind', // find 替代
|
|
24
|
+
'rg', 'ag', 'ack', // grep 替代
|
|
25
|
+
'bat', 'batcat', // cat 替代
|
|
26
|
+
'fzf', 'skim', // 模糊搜索
|
|
27
|
+
'jq', 'yq', 'fx', // JSON/YAML 处理
|
|
28
|
+
'delta', 'diff-so-fancy', // diff 替代
|
|
29
|
+
'zoxide', 'z', 'autojump', // cd 替代
|
|
30
|
+
'tldr', 'tealdeer', // man 替代
|
|
31
|
+
'dust', 'duf', 'ncdu', // du 替代
|
|
32
|
+
'procs', 'bottom', 'htop', // ps/top 替代
|
|
33
|
+
'sd', // sed 替代
|
|
34
|
+
'hyperfine', // benchmark
|
|
35
|
+
],
|
|
36
|
+
|
|
37
|
+
// 包管理器
|
|
38
|
+
node: ['pnpm', 'yarn', 'bun', 'npm'],
|
|
39
|
+
python: ['uv', 'rye', 'poetry', 'pipenv', 'pip'],
|
|
40
|
+
rust: ['cargo'],
|
|
41
|
+
go: ['go'],
|
|
42
|
+
ruby: ['gem', 'bundle'],
|
|
43
|
+
php: ['composer'],
|
|
44
|
+
|
|
45
|
+
// 容器/云工具
|
|
46
|
+
container: ['docker', 'podman', 'nerdctl'],
|
|
47
|
+
k8s: ['kubectl', 'k9s', 'helm'],
|
|
48
|
+
|
|
49
|
+
// 版本控制
|
|
50
|
+
vcs: ['git', 'gh', 'glab', 'hg'],
|
|
51
|
+
|
|
52
|
+
// 构建工具
|
|
53
|
+
build: ['make', 'cmake', 'ninja', 'just', 'task'],
|
|
54
|
+
|
|
55
|
+
// 其他常用工具
|
|
56
|
+
misc: ['curl', 'wget', 'aria2c', 'rsync', 'ssh', 'tmux', 'screen'],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 静态系统信息(缓存 7 天)
|
|
61
|
+
*/
|
|
62
|
+
export interface StaticSystemInfo {
|
|
63
|
+
os: string
|
|
9
64
|
arch: string
|
|
10
65
|
shell: string
|
|
11
|
-
packageManager: string
|
|
12
|
-
cwd: string
|
|
13
66
|
user: string
|
|
67
|
+
systemPackageManager: string
|
|
68
|
+
availableCommands: string[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 动态系统信息(每次实时获取)
|
|
73
|
+
*/
|
|
74
|
+
export interface DynamicSystemInfo {
|
|
75
|
+
cwd: string
|
|
76
|
+
project: ProjectContext | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 完整系统信息
|
|
81
|
+
*/
|
|
82
|
+
export interface SystemInfo extends StaticSystemInfo, DynamicSystemInfo {}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 缓存文件结构
|
|
86
|
+
*/
|
|
87
|
+
interface SystemCache {
|
|
88
|
+
version: number
|
|
89
|
+
cachedAt: string
|
|
90
|
+
expiresInDays: number
|
|
91
|
+
static: StaticSystemInfo
|
|
14
92
|
}
|
|
15
93
|
|
|
94
|
+
// 缓存文件路径
|
|
95
|
+
const CACHE_FILE = path.join(CONFIG_DIR, 'system_cache.json')
|
|
96
|
+
|
|
16
97
|
/**
|
|
17
|
-
*
|
|
98
|
+
* 检测系统包管理器
|
|
18
99
|
*/
|
|
19
100
|
function detectPackageManager(): string {
|
|
20
|
-
|
|
101
|
+
// Windows 包管理器
|
|
102
|
+
if (isWindows()) {
|
|
103
|
+
const windowsManagers = ['winget', 'scoop', 'choco']
|
|
104
|
+
for (const mgr of windowsManagers) {
|
|
105
|
+
if (commandExists(mgr)) {
|
|
106
|
+
return mgr
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return 'unknown'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Unix 包管理器
|
|
113
|
+
const unixManagers = [
|
|
21
114
|
{ name: 'brew', command: 'brew' },
|
|
22
115
|
{ name: 'apt', command: 'apt-get' },
|
|
23
116
|
{ name: 'dnf', command: 'dnf' },
|
|
@@ -27,12 +120,9 @@ function detectPackageManager(): string {
|
|
|
27
120
|
{ name: 'apk', command: 'apk' },
|
|
28
121
|
]
|
|
29
122
|
|
|
30
|
-
for (const mgr of
|
|
31
|
-
|
|
32
|
-
execSync(`which ${mgr.command}`, { stdio: 'ignore' })
|
|
123
|
+
for (const mgr of unixManagers) {
|
|
124
|
+
if (commandExists(mgr.command)) {
|
|
33
125
|
return mgr.name
|
|
34
|
-
} catch {
|
|
35
|
-
// 继续检测下一个
|
|
36
126
|
}
|
|
37
127
|
}
|
|
38
128
|
|
|
@@ -40,30 +130,241 @@ function detectPackageManager(): string {
|
|
|
40
130
|
}
|
|
41
131
|
|
|
42
132
|
/**
|
|
43
|
-
*
|
|
133
|
+
* 检测可用命令(跨平台版本)
|
|
134
|
+
* 使用 platform 模块的 batchCommandExists 函数
|
|
44
135
|
*/
|
|
45
|
-
function
|
|
46
|
-
|
|
136
|
+
function detectAvailableCommands(): string[] {
|
|
137
|
+
const allCommands = Object.values(COMMANDS_TO_CHECK).flat()
|
|
138
|
+
return batchCommandExists(allCommands)
|
|
47
139
|
}
|
|
48
140
|
|
|
49
141
|
/**
|
|
50
|
-
*
|
|
142
|
+
* 检测所有静态信息(纯同步,不需要 async)
|
|
51
143
|
*/
|
|
52
|
-
|
|
144
|
+
function detectStaticInfo(): StaticSystemInfo {
|
|
145
|
+
// 使用 platform 模块检测 Shell
|
|
146
|
+
const shell = detectShell()
|
|
147
|
+
const capabilities = getShellCapabilities(shell)
|
|
148
|
+
|
|
53
149
|
return {
|
|
54
150
|
os: os.platform(),
|
|
55
151
|
arch: os.arch(),
|
|
56
|
-
shell:
|
|
57
|
-
packageManager: detectPackageManager(),
|
|
58
|
-
cwd: process.cwd(),
|
|
152
|
+
shell: capabilities.displayName,
|
|
59
153
|
user: os.userInfo().username,
|
|
154
|
+
systemPackageManager: detectPackageManager(),
|
|
155
|
+
availableCommands: detectAvailableCommands(),
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 获取静态系统信息(带缓存)
|
|
161
|
+
*/
|
|
162
|
+
export function getStaticSystemInfo(): StaticSystemInfo {
|
|
163
|
+
const config = getConfig()
|
|
164
|
+
|
|
165
|
+
// 确保配置目录存在
|
|
166
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
167
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
171
|
+
try {
|
|
172
|
+
const cache: SystemCache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'))
|
|
173
|
+
|
|
174
|
+
// 缓存未过期
|
|
175
|
+
const expireDays = config.systemCacheExpireDays || 7
|
|
176
|
+
const age = Date.now() - new Date(cache.cachedAt).getTime()
|
|
177
|
+
if (age < expireDays * 24 * 60 * 60 * 1000) {
|
|
178
|
+
return cache.static
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// 缓存文件损坏,重新检测
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 首次或过期,重新检测
|
|
186
|
+
const info = detectStaticInfo()
|
|
187
|
+
|
|
188
|
+
// 保存缓存
|
|
189
|
+
const cache: SystemCache = {
|
|
190
|
+
version: 1,
|
|
191
|
+
cachedAt: new Date().toISOString(),
|
|
192
|
+
expiresInDays: config.systemCacheExpireDays || 7,
|
|
193
|
+
static: info,
|
|
194
|
+
}
|
|
195
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2))
|
|
196
|
+
|
|
197
|
+
return info
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 获取动态系统信息(每次实时)
|
|
202
|
+
*/
|
|
203
|
+
export async function getDynamicSystemInfo(): Promise<DynamicSystemInfo> {
|
|
204
|
+
return {
|
|
205
|
+
cwd: process.cwd(),
|
|
206
|
+
project: await detectProjectContext(process.cwd()),
|
|
60
207
|
}
|
|
61
208
|
}
|
|
62
209
|
|
|
63
210
|
/**
|
|
64
|
-
*
|
|
211
|
+
* 获取完整系统信息(主接口)
|
|
65
212
|
*/
|
|
66
|
-
export function
|
|
67
|
-
|
|
68
|
-
|
|
213
|
+
export async function getSystemInfo(): Promise<SystemInfo> {
|
|
214
|
+
return {
|
|
215
|
+
...getStaticSystemInfo(),
|
|
216
|
+
...(await getDynamicSystemInfo()),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 辅助函数:分类命令
|
|
222
|
+
*/
|
|
223
|
+
function categorizeCommands(commands: string[]): {
|
|
224
|
+
modern: string[]
|
|
225
|
+
packageManagers: string[]
|
|
226
|
+
containers: string[]
|
|
227
|
+
others: string[]
|
|
228
|
+
} {
|
|
229
|
+
const modern = ['eza', 'lsd', 'exa', 'fd', 'fdfind', 'rg', 'ag', 'ack', 'bat', 'batcat', 'fzf', 'jq', 'yq', 'delta']
|
|
230
|
+
const packageManagers = ['pnpm', 'yarn', 'bun', 'npm', 'uv', 'poetry', 'cargo', 'go']
|
|
231
|
+
const containers = ['docker', 'podman', 'kubectl', 'k9s', 'helm']
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
modern: commands.filter(cmd => modern.includes(cmd)),
|
|
235
|
+
packageManagers: commands.filter(cmd => packageManagers.includes(cmd)),
|
|
236
|
+
containers: commands.filter(cmd => containers.includes(cmd)),
|
|
237
|
+
others: commands.filter(cmd =>
|
|
238
|
+
!modern.includes(cmd) &&
|
|
239
|
+
!packageManagers.includes(cmd) &&
|
|
240
|
+
!containers.includes(cmd)
|
|
241
|
+
),
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 格式化系统信息为字符串(供 AI 使用)
|
|
247
|
+
*/
|
|
248
|
+
export function formatSystemInfo(info: SystemInfo): string {
|
|
249
|
+
const parts: string[] = []
|
|
250
|
+
|
|
251
|
+
// 基础信息
|
|
252
|
+
parts.push(`OS: ${info.os}, Arch: ${info.arch}, Shell: ${info.shell}, User: ${info.user}`)
|
|
253
|
+
parts.push(`Package Manager: ${info.systemPackageManager}, CWD: ${info.cwd}`)
|
|
254
|
+
|
|
255
|
+
// 可用工具(分类展示)
|
|
256
|
+
if (info.availableCommands.length > 0) {
|
|
257
|
+
const categorized = categorizeCommands(info.availableCommands)
|
|
258
|
+
const lines: string[] = []
|
|
259
|
+
|
|
260
|
+
if (categorized.modern.length > 0) {
|
|
261
|
+
lines.push(`现代工具: ${categorized.modern.join(', ')}`)
|
|
262
|
+
}
|
|
263
|
+
if (categorized.packageManagers.length > 0) {
|
|
264
|
+
lines.push(`包管理器: ${categorized.packageManagers.join(', ')}`)
|
|
265
|
+
}
|
|
266
|
+
if (categorized.containers.length > 0) {
|
|
267
|
+
lines.push(`容器工具: ${categorized.containers.join(', ')}`)
|
|
268
|
+
}
|
|
269
|
+
if (categorized.others.length > 0) {
|
|
270
|
+
lines.push(`其他: ${categorized.others.join(', ')}`)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (lines.length > 0) {
|
|
274
|
+
parts.push(`【用户终端可用工具】(非完整列表)`)
|
|
275
|
+
parts.push(...lines)
|
|
276
|
+
//parts.push(`注: 为确保兼容性和输出捕获,建议优先使用标准命令(ls/find/grep/cat/ps)`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 项目上下文
|
|
281
|
+
if (info.project && info.project.types.length > 0) {
|
|
282
|
+
parts.push(formatProjectContext(info.project))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return parts.join('\n')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 显示系统信息(CLI 美化版)
|
|
290
|
+
*/
|
|
291
|
+
export function displaySystemInfo(info: SystemInfo): void {
|
|
292
|
+
const theme = getCurrentTheme()
|
|
293
|
+
|
|
294
|
+
console.log(chalk.bold('\n系统信息:'))
|
|
295
|
+
console.log(chalk.hex(theme.text.muted)('━'.repeat(50)))
|
|
296
|
+
|
|
297
|
+
// 基础信息
|
|
298
|
+
console.log(` ${chalk.hex(theme.primary)('操作系统')}: ${info.os}`)
|
|
299
|
+
console.log(` ${chalk.hex(theme.primary)('架构')}: ${info.arch}`)
|
|
300
|
+
console.log(` ${chalk.hex(theme.primary)('Shell')}: ${info.shell}`)
|
|
301
|
+
console.log(` ${chalk.hex(theme.primary)('用户')}: ${info.user}`)
|
|
302
|
+
console.log(` ${chalk.hex(theme.primary)('系统包管理器')}: ${info.systemPackageManager}`)
|
|
303
|
+
console.log(` ${chalk.hex(theme.primary)('当前目录')}: ${chalk.hex(theme.text.secondary)(info.cwd)}`)
|
|
304
|
+
|
|
305
|
+
// 可用工具
|
|
306
|
+
if (info.availableCommands.length > 0) {
|
|
307
|
+
console.log(` ${chalk.hex(theme.primary)('可用命令数')}: ${chalk.hex(theme.success)(info.availableCommands.length)}`)
|
|
308
|
+
|
|
309
|
+
const categorized = categorizeCommands(info.availableCommands)
|
|
310
|
+
|
|
311
|
+
if (categorized.modern.length > 0) {
|
|
312
|
+
console.log(` ${chalk.hex(theme.text.secondary)('现代工具')}: ${categorized.modern.join(', ')}`)
|
|
313
|
+
}
|
|
314
|
+
if (categorized.packageManagers.length > 0) {
|
|
315
|
+
console.log(` ${chalk.hex(theme.text.secondary)('包管理器')}: ${categorized.packageManagers.join(', ')}`)
|
|
316
|
+
}
|
|
317
|
+
if (categorized.containers.length > 0) {
|
|
318
|
+
console.log(` ${chalk.hex(theme.text.secondary)('容器工具')}: ${categorized.containers.join(', ')}`)
|
|
319
|
+
}
|
|
320
|
+
if (categorized.others.length > 0) {
|
|
321
|
+
console.log(` ${chalk.hex(theme.text.secondary)('其他工具')}: ${categorized.others.join(', ')}`)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 项目信息
|
|
326
|
+
if (info.project && info.project.types.length > 0) {
|
|
327
|
+
console.log(chalk.hex(theme.text.muted)(' ──────────────────────────────────────────────────'))
|
|
328
|
+
console.log(` ${chalk.hex(theme.primary)('项目类型')}: ${info.project.types.join(', ')}`)
|
|
329
|
+
|
|
330
|
+
if (info.project.packageManager) {
|
|
331
|
+
console.log(` ${chalk.hex(theme.primary)('包管理器')}: ${info.project.packageManager}`)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (info.project.git) {
|
|
335
|
+
const statusColor = info.project.git.status === 'clean' ? theme.success : theme.warning
|
|
336
|
+
const statusText = info.project.git.status === 'clean' ? '干净' : info.project.git.status === 'dirty' ? '有改动' : '未知'
|
|
337
|
+
console.log(` ${chalk.hex(theme.primary)('Git 分支')}: ${info.project.git.branch} (${chalk.hex(statusColor)(statusText)})`)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (info.project.scripts && info.project.scripts.length > 0) {
|
|
341
|
+
console.log(` ${chalk.hex(theme.primary)('可用脚本')}: ${info.project.scripts.join(', ')}`)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log(chalk.hex(theme.text.muted)('━'.repeat(50)))
|
|
346
|
+
console.log(chalk.hex(theme.text.muted)(`缓存文件: ${CACHE_FILE}\n`))
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 强制刷新缓存
|
|
351
|
+
*/
|
|
352
|
+
export function refreshSystemCache(): void {
|
|
353
|
+
const config = getConfig()
|
|
354
|
+
const info = detectStaticInfo()
|
|
355
|
+
|
|
356
|
+
const cache: SystemCache = {
|
|
357
|
+
version: 1,
|
|
358
|
+
cachedAt: new Date().toISOString(),
|
|
359
|
+
expiresInDays: config.systemCacheExpireDays || 7,
|
|
360
|
+
static: info,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 确保配置目录存在
|
|
364
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
365
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2))
|
|
369
|
+
console.log('✓ 系统信息缓存已刷新')
|
|
69
370
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
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
|
+
import { detectShell, getShellCapabilities } from './utils/platform.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 直接读取系统 shell 历史文件(类似 thefuck)
|
|
10
|
+
* 用于没有安装 shell hook 的情况
|
|
11
|
+
*
|
|
12
|
+
* 限制:系统历史文件不记录退出码,所以 exit 字段都是 0
|
|
13
|
+
*
|
|
14
|
+
* 支持的 Shell:
|
|
15
|
+
* - Unix: zsh, bash, fish
|
|
16
|
+
* - Windows: PowerShell 5.x, PowerShell 7+ (通过 PSReadLine)
|
|
17
|
+
* - 不支持: CMD (无持久化历史)
|
|
18
|
+
*/
|
|
19
|
+
export function getSystemShellHistory(): ShellHistoryItem[] {
|
|
20
|
+
const shell = detectShell()
|
|
21
|
+
const capabilities = getShellCapabilities(shell)
|
|
22
|
+
|
|
23
|
+
// 检查是否支持历史读取
|
|
24
|
+
if (!capabilities.supportsHistory || !capabilities.historyPath) {
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const historyFile = capabilities.historyPath
|
|
29
|
+
let parser: (line: string) => ShellHistoryItem | null
|
|
30
|
+
|
|
31
|
+
switch (shell) {
|
|
32
|
+
case 'zsh':
|
|
33
|
+
parser = parseZshHistoryLine
|
|
34
|
+
break
|
|
35
|
+
case 'bash':
|
|
36
|
+
parser = parseBashHistoryLine
|
|
37
|
+
break
|
|
38
|
+
case 'fish':
|
|
39
|
+
parser = parseFishHistoryLine
|
|
40
|
+
break
|
|
41
|
+
case 'powershell5':
|
|
42
|
+
case 'powershell7':
|
|
43
|
+
parser = parsePowerShellHistoryLine
|
|
44
|
+
break
|
|
45
|
+
default:
|
|
46
|
+
return []
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(historyFile)) {
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(historyFile, 'utf-8')
|
|
55
|
+
const lines = content.trim().split('\n')
|
|
56
|
+
const limit = getConfig().shellHistoryLimit || 10
|
|
57
|
+
|
|
58
|
+
// 只取最后 N 条
|
|
59
|
+
const recentLines = lines.slice(-limit)
|
|
60
|
+
|
|
61
|
+
return recentLines
|
|
62
|
+
.map(line => parser(line))
|
|
63
|
+
.filter((item): item is ShellHistoryItem => item !== null)
|
|
64
|
+
} catch {
|
|
65
|
+
return []
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 解析 zsh 历史行
|
|
71
|
+
* 格式: ": 1234567890:0;ls -la"
|
|
72
|
+
* 或者: "ls -la" (简单格式)
|
|
73
|
+
*/
|
|
74
|
+
function parseZshHistoryLine(line: string): ShellHistoryItem | null {
|
|
75
|
+
// 扩展格式: ": timestamp:duration;command"
|
|
76
|
+
const extendedMatch = line.match(/^:\s*(\d+):\d+;(.+)$/)
|
|
77
|
+
if (extendedMatch) {
|
|
78
|
+
const timestamp = parseInt(extendedMatch[1])
|
|
79
|
+
const cmd = extendedMatch[2].trim()
|
|
80
|
+
return {
|
|
81
|
+
cmd,
|
|
82
|
+
exit: 0, // 系统历史文件不记录退出码
|
|
83
|
+
time: new Date(timestamp * 1000).toISOString(),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 简单格式
|
|
88
|
+
const cmd = line.trim()
|
|
89
|
+
if (cmd) {
|
|
90
|
+
return {
|
|
91
|
+
cmd,
|
|
92
|
+
exit: 0,
|
|
93
|
+
time: new Date().toISOString(),
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 解析 bash 历史行
|
|
102
|
+
* 格式: "ls -la"
|
|
103
|
+
* bash 历史文件默认不记录时间戳
|
|
104
|
+
*/
|
|
105
|
+
function parseBashHistoryLine(line: string): ShellHistoryItem | null {
|
|
106
|
+
const cmd = line.trim()
|
|
107
|
+
if (cmd) {
|
|
108
|
+
return {
|
|
109
|
+
cmd,
|
|
110
|
+
exit: 0, // 系统历史文件不记录退出码
|
|
111
|
+
time: new Date().toISOString(),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 解析 Fish 历史行
|
|
119
|
+
* Fish 历史文件使用 YAML-like 格式:
|
|
120
|
+
* - cmd: ls -la
|
|
121
|
+
* when: 1234567890
|
|
122
|
+
*/
|
|
123
|
+
function parseFishHistoryLine(line: string): ShellHistoryItem | null {
|
|
124
|
+
// Fish 历史格式比较特殊,这里简化处理
|
|
125
|
+
// 实际格式是多行的,这里只处理 cmd 行
|
|
126
|
+
const cmdMatch = line.match(/^- cmd:\s*(.+)$/)
|
|
127
|
+
if (cmdMatch) {
|
|
128
|
+
return {
|
|
129
|
+
cmd: cmdMatch[1].trim(),
|
|
130
|
+
exit: 0,
|
|
131
|
+
time: new Date().toISOString(),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 解析 PowerShell 历史行
|
|
139
|
+
* PSReadLine 历史文件格式: 每行一条命令,纯文本
|
|
140
|
+
* 路径: %APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
|
|
141
|
+
*/
|
|
142
|
+
function parsePowerShellHistoryLine(line: string): ShellHistoryItem | null {
|
|
143
|
+
const cmd = line.trim()
|
|
144
|
+
if (cmd) {
|
|
145
|
+
return {
|
|
146
|
+
cmd,
|
|
147
|
+
exit: 0, // 系统历史文件不记录退出码
|
|
148
|
+
time: new Date().toISOString(),
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 从系统历史中获取最近一条命令
|
|
156
|
+
* 排除 pls 命令本身
|
|
157
|
+
*/
|
|
158
|
+
export function getLastCommandFromSystem(): ShellHistoryItem | null {
|
|
159
|
+
const history = getSystemShellHistory()
|
|
160
|
+
|
|
161
|
+
// 从后往前找第一条非 pls 命令
|
|
162
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
163
|
+
const item = history[i]
|
|
164
|
+
if (!item.cmd.startsWith('pls') && !item.cmd.startsWith('please')) {
|
|
165
|
+
return item
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null
|
|
170
|
+
}
|