@yivan-lab/pretty-please 1.4.0 → 1.5.1
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 +32 -4
- package/bin/pls.tsx +153 -35
- package/dist/bin/pls.js +126 -23
- 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 +6 -3
- package/dist/src/multi-step.js +6 -3
- 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 +13 -0
- package/dist/src/shell-hook.js +163 -33
- package/dist/src/sysinfo.d.ts +38 -9
- package/dist/src/sysinfo.js +245 -21
- package/dist/src/system-history.d.ts +5 -0
- package/dist/src/system-history.js +64 -18
- 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 +8 -3
- package/src/multi-step.ts +7 -2
- package/src/project-context.ts +191 -0
- package/src/prompts.ts +26 -5
- package/src/shell-hook.ts +179 -33
- package/src/sysinfo.ts +326 -25
- package/src/system-history.ts +67 -14
- 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/dist/src/shell-hook.js
CHANGED
|
@@ -5,6 +5,7 @@ import chalk from 'chalk';
|
|
|
5
5
|
import { CONFIG_DIR, getConfig, setConfigValue } from './config.js';
|
|
6
6
|
import { getHistory } from './history.js';
|
|
7
7
|
import { getCurrentTheme } from './ui/theme.js';
|
|
8
|
+
import { detectShell as platformDetectShell, } from './utils/platform.js';
|
|
8
9
|
// 获取主题颜色
|
|
9
10
|
function getColors() {
|
|
10
11
|
const theme = getCurrentTheme();
|
|
@@ -20,19 +21,36 @@ const SHELL_HISTORY_FILE = path.join(CONFIG_DIR, 'shell_history.jsonl');
|
|
|
20
21
|
// Hook 标记,用于识别我们添加的内容
|
|
21
22
|
const HOOK_START_MARKER = '# >>> pretty-please shell hook >>>';
|
|
22
23
|
const HOOK_END_MARKER = '# <<< pretty-please shell hook <<<';
|
|
24
|
+
/**
|
|
25
|
+
* 将 platform 模块的 ShellType 转换为本地 ShellType
|
|
26
|
+
*/
|
|
27
|
+
function toLocalShellType(platformShell) {
|
|
28
|
+
switch (platformShell) {
|
|
29
|
+
case 'zsh':
|
|
30
|
+
return 'zsh';
|
|
31
|
+
case 'bash':
|
|
32
|
+
return 'bash';
|
|
33
|
+
case 'powershell5':
|
|
34
|
+
case 'powershell7':
|
|
35
|
+
return 'powershell';
|
|
36
|
+
case 'cmd':
|
|
37
|
+
case 'fish':
|
|
38
|
+
case 'unknown':
|
|
39
|
+
default:
|
|
40
|
+
return 'unknown';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
23
43
|
/**
|
|
24
44
|
* 检测当前 shell 类型
|
|
45
|
+
* 使用 platform 模块进行跨平台检测
|
|
25
46
|
*/
|
|
26
47
|
export function detectShell() {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (process.platform === 'win32')
|
|
34
|
-
return 'powershell';
|
|
35
|
-
return 'unknown';
|
|
48
|
+
const platformShell = platformDetectShell();
|
|
49
|
+
// CMD 不支持 Hook,提示用户
|
|
50
|
+
if (platformShell === 'cmd') {
|
|
51
|
+
return 'unknown';
|
|
52
|
+
}
|
|
53
|
+
return toLocalShellType(platformShell);
|
|
36
54
|
}
|
|
37
55
|
/**
|
|
38
56
|
* 获取 shell 配置文件路径
|
|
@@ -55,6 +73,35 @@ export function getShellConfigPath(shellType) {
|
|
|
55
73
|
return null;
|
|
56
74
|
}
|
|
57
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* 生成命令统计的公共 shell 函数
|
|
78
|
+
* 用于 zsh 和 bash,避免代码重复
|
|
79
|
+
*/
|
|
80
|
+
function generateStatFunction() {
|
|
81
|
+
return `
|
|
82
|
+
# 统计命令频率(公共函数)
|
|
83
|
+
__pls_record_stat() {
|
|
84
|
+
local cmd_name="$1"
|
|
85
|
+
local stats_file="${CONFIG_DIR}/command_stats.txt"
|
|
86
|
+
|
|
87
|
+
# 确保文件存在
|
|
88
|
+
touch "$stats_file"
|
|
89
|
+
|
|
90
|
+
# 更新统计(纯 shell 实现,不依赖 jq)
|
|
91
|
+
if grep -q "^$cmd_name=" "$stats_file" 2>/dev/null; then
|
|
92
|
+
# 命令已存在,次数 +1
|
|
93
|
+
local count=$(grep "^$cmd_name=" "$stats_file" | cut -d= -f2)
|
|
94
|
+
count=$((count + 1))
|
|
95
|
+
# macOS 和 Linux 的 sed -i 不同,使用临时文件
|
|
96
|
+
sed "s/^$cmd_name=.*/$cmd_name=$count/" "$stats_file" > "$stats_file.tmp" 2>/dev/null
|
|
97
|
+
mv "$stats_file.tmp" "$stats_file" 2>/dev/null
|
|
98
|
+
else
|
|
99
|
+
# 新命令,追加
|
|
100
|
+
echo "$cmd_name=1" >> "$stats_file"
|
|
101
|
+
fi
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
58
105
|
/**
|
|
59
106
|
* 生成 zsh hook 脚本
|
|
60
107
|
*/
|
|
@@ -64,6 +111,7 @@ function generateZshHook() {
|
|
|
64
111
|
return `
|
|
65
112
|
${HOOK_START_MARKER}
|
|
66
113
|
# 记录命令到 pretty-please 历史
|
|
114
|
+
${generateStatFunction()}
|
|
67
115
|
__pls_preexec() {
|
|
68
116
|
__PLS_LAST_CMD="$1"
|
|
69
117
|
__PLS_CMD_START=$(date +%s)
|
|
@@ -79,6 +127,11 @@ __pls_precmd() {
|
|
|
79
127
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
|
|
80
128
|
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
81
129
|
tail -n ${limit} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
|
|
130
|
+
|
|
131
|
+
# 统计命令频率
|
|
132
|
+
local cmd_name=$(echo "$__PLS_LAST_CMD" | awk '{print $1}')
|
|
133
|
+
__pls_record_stat "$cmd_name"
|
|
134
|
+
|
|
82
135
|
unset __PLS_LAST_CMD
|
|
83
136
|
fi
|
|
84
137
|
}
|
|
@@ -98,6 +151,7 @@ function generateBashHook() {
|
|
|
98
151
|
return `
|
|
99
152
|
${HOOK_START_MARKER}
|
|
100
153
|
# 记录命令到 pretty-please 历史
|
|
154
|
+
${generateStatFunction()}
|
|
101
155
|
__pls_prompt_command() {
|
|
102
156
|
local exit_code=$?
|
|
103
157
|
local last_cmd=$(history 1 | sed 's/^ *[0-9]* *//')
|
|
@@ -107,6 +161,10 @@ __pls_prompt_command() {
|
|
|
107
161
|
local escaped_cmd=$(echo "$last_cmd" | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')
|
|
108
162
|
echo "{\\"cmd\\":\\"$escaped_cmd\\",\\"exit\\":$exit_code,\\"time\\":\\"$timestamp\\"}" >> "${CONFIG_DIR}/shell_history.jsonl"
|
|
109
163
|
tail -n ${limit} "${CONFIG_DIR}/shell_history.jsonl" > "${CONFIG_DIR}/shell_history.jsonl.tmp" && mv "${CONFIG_DIR}/shell_history.jsonl.tmp" "${CONFIG_DIR}/shell_history.jsonl"
|
|
164
|
+
|
|
165
|
+
# 统计命令频率
|
|
166
|
+
local cmd_name=$(echo "$last_cmd" | awk '{print $1}')
|
|
167
|
+
__pls_record_stat "$cmd_name"
|
|
110
168
|
fi
|
|
111
169
|
}
|
|
112
170
|
|
|
@@ -118,15 +176,26 @@ ${HOOK_END_MARKER}
|
|
|
118
176
|
}
|
|
119
177
|
/**
|
|
120
178
|
* 生成 PowerShell hook 脚本
|
|
179
|
+
* 使用 PowerShell 原生路径处理,避免跨平台路径问题
|
|
121
180
|
*/
|
|
122
181
|
function generatePowerShellHook() {
|
|
123
182
|
const config = getConfig();
|
|
124
183
|
const limit = config.shellHistoryLimit || 10; // 从配置读取
|
|
184
|
+
// 使用 PowerShell 原生路径变量,而不是嵌入 Node.js 路径
|
|
125
185
|
return `
|
|
126
186
|
${HOOK_START_MARKER}
|
|
127
187
|
# 记录命令到 pretty-please 历史
|
|
188
|
+
# 使用 PowerShell 原生路径
|
|
189
|
+
$Global:__PlsDir = Join-Path $env:USERPROFILE ".please"
|
|
190
|
+
$Global:__PlsHistoryFile = Join-Path $Global:__PlsDir "shell_history.jsonl"
|
|
191
|
+
$Global:__PlsStatsFile = Join-Path $Global:__PlsDir "command_stats.txt"
|
|
128
192
|
$Global:__PlsLastCmd = ""
|
|
129
193
|
|
|
194
|
+
# 确保目录存在
|
|
195
|
+
if (-not (Test-Path $Global:__PlsDir)) {
|
|
196
|
+
New-Item -Path $Global:__PlsDir -ItemType Directory -Force | Out-Null
|
|
197
|
+
}
|
|
198
|
+
|
|
130
199
|
function __Pls_RecordCommand {
|
|
131
200
|
$lastCmd = (Get-History -Count 1).CommandLine
|
|
132
201
|
if ($lastCmd -and $lastCmd -ne $Global:__PlsLastCmd) {
|
|
@@ -136,10 +205,38 @@ function __Pls_RecordCommand {
|
|
|
136
205
|
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
137
206
|
$escapedCmd = $lastCmd -replace '\\\\', '\\\\\\\\' -replace '"', '\\\\"'
|
|
138
207
|
$json = "{\`"cmd\`":\`"$escapedCmd\`",\`"exit\`":$exitCode,\`"time\`":\`"$timestamp\`"}"
|
|
139
|
-
Add-Content -Path
|
|
208
|
+
Add-Content -Path $Global:__PlsHistoryFile -Value $json
|
|
140
209
|
# 保持文件不超过 ${limit} 行(从配置读取)
|
|
141
|
-
$content = Get-Content
|
|
142
|
-
$content
|
|
210
|
+
$content = Get-Content $Global:__PlsHistoryFile -Tail ${limit} -ErrorAction SilentlyContinue
|
|
211
|
+
if ($content) {
|
|
212
|
+
$content | Set-Content $Global:__PlsHistoryFile
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# 统计命令频率
|
|
216
|
+
$cmdName = $lastCmd -split ' ' | Select-Object -First 1
|
|
217
|
+
|
|
218
|
+
# 确保统计文件存在
|
|
219
|
+
if (-not (Test-Path $Global:__PlsStatsFile)) {
|
|
220
|
+
New-Item -Path $Global:__PlsStatsFile -ItemType File -Force | Out-Null
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# 更新统计
|
|
224
|
+
$stats = Get-Content $Global:__PlsStatsFile -ErrorAction SilentlyContinue
|
|
225
|
+
$found = $false
|
|
226
|
+
$newStats = @()
|
|
227
|
+
foreach ($line in $stats) {
|
|
228
|
+
if ($line -match "^$cmdName=(\\d+)$") {
|
|
229
|
+
$count = [int]$matches[1] + 1
|
|
230
|
+
$newStats += "$cmdName=$count"
|
|
231
|
+
$found = $true
|
|
232
|
+
} else {
|
|
233
|
+
$newStats += $line
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (-not $found) {
|
|
237
|
+
$newStats += "$cmdName=1"
|
|
238
|
+
}
|
|
239
|
+
$newStats | Set-Content $Global:__PlsStatsFile
|
|
143
240
|
}
|
|
144
241
|
}
|
|
145
242
|
|
|
@@ -177,6 +274,18 @@ export async function installShellHook() {
|
|
|
177
274
|
const colors = getColors();
|
|
178
275
|
if (!configPath) {
|
|
179
276
|
console.log(chalk.hex(colors.error)(`❌ 不支持的 shell 类型: ${shellType}`));
|
|
277
|
+
// CMD 特殊提示
|
|
278
|
+
if (shellType === 'unknown') {
|
|
279
|
+
const platformShell = platformDetectShell();
|
|
280
|
+
if (platformShell === 'cmd') {
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(chalk.hex(colors.warning)('⚠️ CMD 不支持 Shell Hook 功能'));
|
|
283
|
+
console.log(chalk.hex(colors.secondary)('建议使用 PowerShell 获得完整体验:'));
|
|
284
|
+
console.log(chalk.hex(colors.secondary)(' 1. 按 Win 键搜索 "PowerShell"'));
|
|
285
|
+
console.log(chalk.hex(colors.secondary)(' 2. 在 PowerShell 中运行 pls hook install'));
|
|
286
|
+
console.log('');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
180
289
|
return false;
|
|
181
290
|
}
|
|
182
291
|
const hookScript = generateHookScript(shellType);
|
|
@@ -434,39 +543,60 @@ export function displayShellHistory() {
|
|
|
434
543
|
console.log('');
|
|
435
544
|
}
|
|
436
545
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
546
|
+
* 重新安装 Shell Hook(通用函数)
|
|
547
|
+
* 用于版本升级、配置变更等场景
|
|
548
|
+
*
|
|
549
|
+
* @param options.silent 是否静默模式(不输出日志)
|
|
550
|
+
* @param options.reason 重装原因(用于日志显示)
|
|
551
|
+
* @returns 是否成功重装
|
|
439
552
|
*/
|
|
440
|
-
export async function
|
|
553
|
+
export async function reinstallShellHook(options) {
|
|
441
554
|
const config = getConfig();
|
|
442
555
|
// 只有在 hook 已启用时才重装
|
|
443
556
|
if (!config.shellHook) {
|
|
444
557
|
return false;
|
|
445
558
|
}
|
|
446
|
-
// 值没有变化,不需要重装
|
|
447
|
-
if (oldLimit === newLimit) {
|
|
448
|
-
return false;
|
|
449
|
-
}
|
|
450
559
|
const colors = getColors();
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
560
|
+
const { silent = false, reason } = options || {};
|
|
561
|
+
if (!silent) {
|
|
562
|
+
console.log('');
|
|
563
|
+
if (reason) {
|
|
564
|
+
console.log(chalk.hex(colors.primary)(reason));
|
|
565
|
+
}
|
|
566
|
+
console.log(chalk.hex(colors.primary)('正在更新 Shell Hook...'));
|
|
567
|
+
}
|
|
568
|
+
// 卸载旧版本,安装新版本
|
|
454
569
|
uninstallShellHook();
|
|
455
570
|
await installShellHook();
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
571
|
+
if (!silent) {
|
|
572
|
+
console.log('');
|
|
573
|
+
console.log(chalk.hex(colors.warning)('⚠️ 请重启终端或运行以下命令使新配置生效:'));
|
|
574
|
+
const shellType = detectShell();
|
|
575
|
+
let configFile = '~/.zshrc';
|
|
576
|
+
if (shellType === 'bash') {
|
|
577
|
+
configFile = process.platform === 'darwin' ? '~/.bash_profile' : '~/.bashrc';
|
|
578
|
+
}
|
|
579
|
+
else if (shellType === 'powershell') {
|
|
580
|
+
configFile = '~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1';
|
|
581
|
+
}
|
|
582
|
+
console.log(chalk.gray(` source ${configFile}`));
|
|
583
|
+
console.log('');
|
|
465
584
|
}
|
|
466
|
-
console.log(chalk.gray(` source ${configFile}`));
|
|
467
|
-
console.log('');
|
|
468
585
|
return true;
|
|
469
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* 当 shellHistoryLimit 变化时,自动重装 Hook
|
|
589
|
+
* 返回是否成功重装
|
|
590
|
+
*/
|
|
591
|
+
export async function reinstallHookForLimitChange(oldLimit, newLimit) {
|
|
592
|
+
// 值没有变化,不需要重装
|
|
593
|
+
if (oldLimit === newLimit) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
return reinstallShellHook({
|
|
597
|
+
reason: `检测到 shellHistoryLimit 变化 (${oldLimit} → ${newLimit})`,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
470
600
|
/**
|
|
471
601
|
* 清空 shell 历史
|
|
472
602
|
*/
|
package/dist/src/sysinfo.d.ts
CHANGED
|
@@ -1,19 +1,48 @@
|
|
|
1
|
+
import { type ProjectContext } from './project-context.js';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* 静态系统信息(缓存 7 天)
|
|
3
4
|
*/
|
|
4
|
-
export interface
|
|
5
|
-
os:
|
|
5
|
+
export interface StaticSystemInfo {
|
|
6
|
+
os: string;
|
|
6
7
|
arch: string;
|
|
7
8
|
shell: string;
|
|
8
|
-
packageManager: string;
|
|
9
|
-
cwd: string;
|
|
10
9
|
user: string;
|
|
10
|
+
systemPackageManager: string;
|
|
11
|
+
availableCommands: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 动态系统信息(每次实时获取)
|
|
15
|
+
*/
|
|
16
|
+
export interface DynamicSystemInfo {
|
|
17
|
+
cwd: string;
|
|
18
|
+
project: ProjectContext | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 完整系统信息
|
|
22
|
+
*/
|
|
23
|
+
export interface SystemInfo extends StaticSystemInfo, DynamicSystemInfo {
|
|
11
24
|
}
|
|
12
25
|
/**
|
|
13
|
-
*
|
|
26
|
+
* 获取静态系统信息(带缓存)
|
|
27
|
+
*/
|
|
28
|
+
export declare function getStaticSystemInfo(): StaticSystemInfo;
|
|
29
|
+
/**
|
|
30
|
+
* 获取动态系统信息(每次实时)
|
|
31
|
+
*/
|
|
32
|
+
export declare function getDynamicSystemInfo(): Promise<DynamicSystemInfo>;
|
|
33
|
+
/**
|
|
34
|
+
* 获取完整系统信息(主接口)
|
|
35
|
+
*/
|
|
36
|
+
export declare function getSystemInfo(): Promise<SystemInfo>;
|
|
37
|
+
/**
|
|
38
|
+
* 格式化系统信息为字符串(供 AI 使用)
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatSystemInfo(info: SystemInfo): string;
|
|
41
|
+
/**
|
|
42
|
+
* 显示系统信息(CLI 美化版)
|
|
14
43
|
*/
|
|
15
|
-
export declare function
|
|
44
|
+
export declare function displaySystemInfo(info: SystemInfo): void;
|
|
16
45
|
/**
|
|
17
|
-
*
|
|
46
|
+
* 强制刷新缓存
|
|
18
47
|
*/
|
|
19
|
-
export declare function
|
|
48
|
+
export declare function refreshSystemCache(): void;
|
package/dist/src/sysinfo.js
CHANGED
|
@@ -1,10 +1,66 @@
|
|
|
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 } from './project-context.js';
|
|
6
|
+
import { getConfig, CONFIG_DIR } from './config.js';
|
|
7
|
+
import { getCurrentTheme } from './ui/theme.js';
|
|
8
|
+
import { detectShell, getShellCapabilities, commandExists, batchCommandExists, isWindows, } from './utils/platform.js';
|
|
3
9
|
/**
|
|
4
|
-
*
|
|
10
|
+
* 要检测的命令列表(45 个)
|
|
11
|
+
*/
|
|
12
|
+
const COMMANDS_TO_CHECK = {
|
|
13
|
+
// 现代 CLI 工具(ls/find/grep/cat 等的替代品)
|
|
14
|
+
modern: [
|
|
15
|
+
'eza', 'lsd', 'exa', // ls 替代
|
|
16
|
+
'fd', 'fdfind', // find 替代
|
|
17
|
+
'rg', 'ag', 'ack', // grep 替代
|
|
18
|
+
'bat', 'batcat', // cat 替代
|
|
19
|
+
'fzf', 'skim', // 模糊搜索
|
|
20
|
+
'jq', 'yq', 'fx', // JSON/YAML 处理
|
|
21
|
+
'delta', 'diff-so-fancy', // diff 替代
|
|
22
|
+
'zoxide', 'z', 'autojump', // cd 替代
|
|
23
|
+
'tldr', 'tealdeer', // man 替代
|
|
24
|
+
'dust', 'duf', 'ncdu', // du 替代
|
|
25
|
+
'procs', 'bottom', 'htop', // ps/top 替代
|
|
26
|
+
'sd', // sed 替代
|
|
27
|
+
'hyperfine', // benchmark
|
|
28
|
+
],
|
|
29
|
+
// 包管理器
|
|
30
|
+
node: ['pnpm', 'yarn', 'bun', 'npm'],
|
|
31
|
+
python: ['uv', 'rye', 'poetry', 'pipenv', 'pip'],
|
|
32
|
+
rust: ['cargo'],
|
|
33
|
+
go: ['go'],
|
|
34
|
+
ruby: ['gem', 'bundle'],
|
|
35
|
+
php: ['composer'],
|
|
36
|
+
// 容器/云工具
|
|
37
|
+
container: ['docker', 'podman', 'nerdctl'],
|
|
38
|
+
k8s: ['kubectl', 'k9s', 'helm'],
|
|
39
|
+
// 版本控制
|
|
40
|
+
vcs: ['git', 'gh', 'glab', 'hg'],
|
|
41
|
+
// 构建工具
|
|
42
|
+
build: ['make', 'cmake', 'ninja', 'just', 'task'],
|
|
43
|
+
// 其他常用工具
|
|
44
|
+
misc: ['curl', 'wget', 'aria2c', 'rsync', 'ssh', 'tmux', 'screen'],
|
|
45
|
+
};
|
|
46
|
+
// 缓存文件路径
|
|
47
|
+
const CACHE_FILE = path.join(CONFIG_DIR, 'system_cache.json');
|
|
48
|
+
/**
|
|
49
|
+
* 检测系统包管理器
|
|
5
50
|
*/
|
|
6
51
|
function detectPackageManager() {
|
|
7
|
-
|
|
52
|
+
// Windows 包管理器
|
|
53
|
+
if (isWindows()) {
|
|
54
|
+
const windowsManagers = ['winget', 'scoop', 'choco'];
|
|
55
|
+
for (const mgr of windowsManagers) {
|
|
56
|
+
if (commandExists(mgr)) {
|
|
57
|
+
return mgr;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return 'unknown';
|
|
61
|
+
}
|
|
62
|
+
// Unix 包管理器
|
|
63
|
+
const unixManagers = [
|
|
8
64
|
{ name: 'brew', command: 'brew' },
|
|
9
65
|
{ name: 'apt', command: 'apt-get' },
|
|
10
66
|
{ name: 'dnf', command: 'dnf' },
|
|
@@ -13,40 +69,208 @@ function detectPackageManager() {
|
|
|
13
69
|
{ name: 'zypper', command: 'zypper' },
|
|
14
70
|
{ name: 'apk', command: 'apk' },
|
|
15
71
|
];
|
|
16
|
-
for (const mgr of
|
|
17
|
-
|
|
18
|
-
execSync(`which ${mgr.command}`, { stdio: 'ignore' });
|
|
72
|
+
for (const mgr of unixManagers) {
|
|
73
|
+
if (commandExists(mgr.command)) {
|
|
19
74
|
return mgr.name;
|
|
20
75
|
}
|
|
21
|
-
catch {
|
|
22
|
-
// 继续检测下一个
|
|
23
|
-
}
|
|
24
76
|
}
|
|
25
77
|
return 'unknown';
|
|
26
78
|
}
|
|
27
79
|
/**
|
|
28
|
-
*
|
|
80
|
+
* 检测可用命令(跨平台版本)
|
|
81
|
+
* 使用 platform 模块的 batchCommandExists 函数
|
|
29
82
|
*/
|
|
30
|
-
function
|
|
31
|
-
|
|
83
|
+
function detectAvailableCommands() {
|
|
84
|
+
const allCommands = Object.values(COMMANDS_TO_CHECK).flat();
|
|
85
|
+
return batchCommandExists(allCommands);
|
|
32
86
|
}
|
|
33
87
|
/**
|
|
34
|
-
*
|
|
88
|
+
* 检测所有静态信息(纯同步,不需要 async)
|
|
35
89
|
*/
|
|
36
|
-
|
|
90
|
+
function detectStaticInfo() {
|
|
91
|
+
// 使用 platform 模块检测 Shell
|
|
92
|
+
const shell = detectShell();
|
|
93
|
+
const capabilities = getShellCapabilities(shell);
|
|
37
94
|
return {
|
|
38
95
|
os: os.platform(),
|
|
39
96
|
arch: os.arch(),
|
|
40
|
-
shell:
|
|
41
|
-
packageManager: detectPackageManager(),
|
|
42
|
-
cwd: process.cwd(),
|
|
97
|
+
shell: capabilities.displayName,
|
|
43
98
|
user: os.userInfo().username,
|
|
99
|
+
systemPackageManager: detectPackageManager(),
|
|
100
|
+
availableCommands: detectAvailableCommands(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 获取静态系统信息(带缓存)
|
|
105
|
+
*/
|
|
106
|
+
export function getStaticSystemInfo() {
|
|
107
|
+
const config = getConfig();
|
|
108
|
+
// 确保配置目录存在
|
|
109
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
110
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
113
|
+
try {
|
|
114
|
+
const cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
115
|
+
// 缓存未过期
|
|
116
|
+
const expireDays = config.systemCacheExpireDays || 7;
|
|
117
|
+
const age = Date.now() - new Date(cache.cachedAt).getTime();
|
|
118
|
+
if (age < expireDays * 24 * 60 * 60 * 1000) {
|
|
119
|
+
return cache.static;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// 缓存文件损坏,重新检测
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// 首次或过期,重新检测
|
|
127
|
+
const info = detectStaticInfo();
|
|
128
|
+
// 保存缓存
|
|
129
|
+
const cache = {
|
|
130
|
+
version: 1,
|
|
131
|
+
cachedAt: new Date().toISOString(),
|
|
132
|
+
expiresInDays: config.systemCacheExpireDays || 7,
|
|
133
|
+
static: info,
|
|
134
|
+
};
|
|
135
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
136
|
+
return info;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 获取动态系统信息(每次实时)
|
|
140
|
+
*/
|
|
141
|
+
export async function getDynamicSystemInfo() {
|
|
142
|
+
return {
|
|
143
|
+
cwd: process.cwd(),
|
|
144
|
+
project: await detectProjectContext(process.cwd()),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 获取完整系统信息(主接口)
|
|
149
|
+
*/
|
|
150
|
+
export async function getSystemInfo() {
|
|
151
|
+
return {
|
|
152
|
+
...getStaticSystemInfo(),
|
|
153
|
+
...(await getDynamicSystemInfo()),
|
|
44
154
|
};
|
|
45
155
|
}
|
|
46
156
|
/**
|
|
47
|
-
*
|
|
157
|
+
* 辅助函数:分类命令
|
|
48
158
|
*/
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
159
|
+
function categorizeCommands(commands) {
|
|
160
|
+
const modern = ['eza', 'lsd', 'exa', 'fd', 'fdfind', 'rg', 'ag', 'ack', 'bat', 'batcat', 'fzf', 'jq', 'yq', 'delta'];
|
|
161
|
+
const packageManagers = ['pnpm', 'yarn', 'bun', 'npm', 'uv', 'poetry', 'cargo', 'go'];
|
|
162
|
+
const containers = ['docker', 'podman', 'kubectl', 'k9s', 'helm'];
|
|
163
|
+
return {
|
|
164
|
+
modern: commands.filter(cmd => modern.includes(cmd)),
|
|
165
|
+
packageManagers: commands.filter(cmd => packageManagers.includes(cmd)),
|
|
166
|
+
containers: commands.filter(cmd => containers.includes(cmd)),
|
|
167
|
+
others: commands.filter(cmd => !modern.includes(cmd) &&
|
|
168
|
+
!packageManagers.includes(cmd) &&
|
|
169
|
+
!containers.includes(cmd)),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 格式化系统信息为字符串(供 AI 使用)
|
|
174
|
+
*/
|
|
175
|
+
export function formatSystemInfo(info) {
|
|
176
|
+
const parts = [];
|
|
177
|
+
// 基础信息
|
|
178
|
+
parts.push(`OS: ${info.os}, Arch: ${info.arch}, Shell: ${info.shell}, User: ${info.user}`);
|
|
179
|
+
parts.push(`Package Manager: ${info.systemPackageManager}, CWD: ${info.cwd}`);
|
|
180
|
+
// 可用工具(分类展示)
|
|
181
|
+
if (info.availableCommands.length > 0) {
|
|
182
|
+
const categorized = categorizeCommands(info.availableCommands);
|
|
183
|
+
const lines = [];
|
|
184
|
+
if (categorized.modern.length > 0) {
|
|
185
|
+
lines.push(`现代工具: ${categorized.modern.join(', ')}`);
|
|
186
|
+
}
|
|
187
|
+
if (categorized.packageManagers.length > 0) {
|
|
188
|
+
lines.push(`包管理器: ${categorized.packageManagers.join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
if (categorized.containers.length > 0) {
|
|
191
|
+
lines.push(`容器工具: ${categorized.containers.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
if (categorized.others.length > 0) {
|
|
194
|
+
lines.push(`其他: ${categorized.others.join(', ')}`);
|
|
195
|
+
}
|
|
196
|
+
if (lines.length > 0) {
|
|
197
|
+
parts.push(`【用户终端可用工具】(非完整列表)`);
|
|
198
|
+
parts.push(...lines);
|
|
199
|
+
//parts.push(`注: 为确保兼容性和输出捕获,建议优先使用标准命令(ls/find/grep/cat/ps)`)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// 项目上下文
|
|
203
|
+
if (info.project && info.project.types.length > 0) {
|
|
204
|
+
parts.push(formatProjectContext(info.project));
|
|
205
|
+
}
|
|
206
|
+
return parts.join('\n');
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 显示系统信息(CLI 美化版)
|
|
210
|
+
*/
|
|
211
|
+
export function displaySystemInfo(info) {
|
|
212
|
+
const theme = getCurrentTheme();
|
|
213
|
+
console.log(chalk.bold('\n系统信息:'));
|
|
214
|
+
console.log(chalk.hex(theme.text.muted)('━'.repeat(50)));
|
|
215
|
+
// 基础信息
|
|
216
|
+
console.log(` ${chalk.hex(theme.primary)('操作系统')}: ${info.os}`);
|
|
217
|
+
console.log(` ${chalk.hex(theme.primary)('架构')}: ${info.arch}`);
|
|
218
|
+
console.log(` ${chalk.hex(theme.primary)('Shell')}: ${info.shell}`);
|
|
219
|
+
console.log(` ${chalk.hex(theme.primary)('用户')}: ${info.user}`);
|
|
220
|
+
console.log(` ${chalk.hex(theme.primary)('系统包管理器')}: ${info.systemPackageManager}`);
|
|
221
|
+
console.log(` ${chalk.hex(theme.primary)('当前目录')}: ${chalk.hex(theme.text.secondary)(info.cwd)}`);
|
|
222
|
+
// 可用工具
|
|
223
|
+
if (info.availableCommands.length > 0) {
|
|
224
|
+
console.log(` ${chalk.hex(theme.primary)('可用命令数')}: ${chalk.hex(theme.success)(info.availableCommands.length)}`);
|
|
225
|
+
const categorized = categorizeCommands(info.availableCommands);
|
|
226
|
+
if (categorized.modern.length > 0) {
|
|
227
|
+
console.log(` ${chalk.hex(theme.text.secondary)('现代工具')}: ${categorized.modern.join(', ')}`);
|
|
228
|
+
}
|
|
229
|
+
if (categorized.packageManagers.length > 0) {
|
|
230
|
+
console.log(` ${chalk.hex(theme.text.secondary)('包管理器')}: ${categorized.packageManagers.join(', ')}`);
|
|
231
|
+
}
|
|
232
|
+
if (categorized.containers.length > 0) {
|
|
233
|
+
console.log(` ${chalk.hex(theme.text.secondary)('容器工具')}: ${categorized.containers.join(', ')}`);
|
|
234
|
+
}
|
|
235
|
+
if (categorized.others.length > 0) {
|
|
236
|
+
console.log(` ${chalk.hex(theme.text.secondary)('其他工具')}: ${categorized.others.join(', ')}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// 项目信息
|
|
240
|
+
if (info.project && info.project.types.length > 0) {
|
|
241
|
+
console.log(chalk.hex(theme.text.muted)(' ──────────────────────────────────────────────────'));
|
|
242
|
+
console.log(` ${chalk.hex(theme.primary)('项目类型')}: ${info.project.types.join(', ')}`);
|
|
243
|
+
if (info.project.packageManager) {
|
|
244
|
+
console.log(` ${chalk.hex(theme.primary)('包管理器')}: ${info.project.packageManager}`);
|
|
245
|
+
}
|
|
246
|
+
if (info.project.git) {
|
|
247
|
+
const statusColor = info.project.git.status === 'clean' ? theme.success : theme.warning;
|
|
248
|
+
const statusText = info.project.git.status === 'clean' ? '干净' : info.project.git.status === 'dirty' ? '有改动' : '未知';
|
|
249
|
+
console.log(` ${chalk.hex(theme.primary)('Git 分支')}: ${info.project.git.branch} (${chalk.hex(statusColor)(statusText)})`);
|
|
250
|
+
}
|
|
251
|
+
if (info.project.scripts && info.project.scripts.length > 0) {
|
|
252
|
+
console.log(` ${chalk.hex(theme.primary)('可用脚本')}: ${info.project.scripts.join(', ')}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
console.log(chalk.hex(theme.text.muted)('━'.repeat(50)));
|
|
256
|
+
console.log(chalk.hex(theme.text.muted)(`缓存文件: ${CACHE_FILE}\n`));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 强制刷新缓存
|
|
260
|
+
*/
|
|
261
|
+
export function refreshSystemCache() {
|
|
262
|
+
const config = getConfig();
|
|
263
|
+
const info = detectStaticInfo();
|
|
264
|
+
const cache = {
|
|
265
|
+
version: 1,
|
|
266
|
+
cachedAt: new Date().toISOString(),
|
|
267
|
+
expiresInDays: config.systemCacheExpireDays || 7,
|
|
268
|
+
static: info,
|
|
269
|
+
};
|
|
270
|
+
// 确保配置目录存在
|
|
271
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
272
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
273
|
+
}
|
|
274
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
275
|
+
console.log('✓ 系统信息缓存已刷新');
|
|
52
276
|
}
|
|
@@ -4,6 +4,11 @@ import type { ShellHistoryItem } from './shell-hook.js';
|
|
|
4
4
|
* 用于没有安装 shell hook 的情况
|
|
5
5
|
*
|
|
6
6
|
* 限制:系统历史文件不记录退出码,所以 exit 字段都是 0
|
|
7
|
+
*
|
|
8
|
+
* 支持的 Shell:
|
|
9
|
+
* - Unix: zsh, bash, fish
|
|
10
|
+
* - Windows: PowerShell 5.x, PowerShell 7+ (通过 PSReadLine)
|
|
11
|
+
* - 不支持: CMD (无持久化历史)
|
|
7
12
|
*/
|
|
8
13
|
export declare function getSystemShellHistory(): ShellHistoryItem[];
|
|
9
14
|
/**
|