@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/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> `please 查看当前目录` — 就像在跟电脑说话一样自然
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<img src="https://github.com/user-attachments/assets/
|
|
10
|
+
<img src="https://github.com/user-attachments/assets/aeebcac4-ee52-4b9d-b2c9-77c2930e7454" alt="Pretty Please Demo" width="800">
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
## 这是啥?
|
|
@@ -54,7 +54,6 @@ Enumerating objects: 5, done.
|
|
|
54
54
|
- **命令打错了?** 直接 `pls` 自动修复,像 thefuck 一样方便,但更智能
|
|
55
55
|
- 记不住 `tar` 的一堆参数
|
|
56
56
|
- 想批量处理文件但懒得写脚本
|
|
57
|
-
- 需要在多台服务器上执行同样的操作
|
|
58
57
|
- 想问问某个命令怎么用
|
|
59
58
|
|
|
60
59
|
## 能干啥?
|
|
@@ -66,6 +65,7 @@ Enumerating objects: 5, done.
|
|
|
66
65
|
- **错误恢复重试** - 命令失败了 AI 会分析原因并调整策略
|
|
67
66
|
|
|
68
67
|
**高级功能:**
|
|
68
|
+
- **学习你的习惯** - 开启 Shell Hook 后,AI 会记住你常用的命令,下次优先用你习惯的工具
|
|
69
69
|
- **远程执行** - 通过 SSH 在服务器上跑命令,支持批量(`-r server1,server2,server3`)
|
|
70
70
|
- **对话模式** - `pls chat grep 怎么用`,随时问问题
|
|
71
71
|
- **命令别名** - 把常用操作存成快捷方式
|
|
@@ -74,23 +74,31 @@ Enumerating objects: 5, done.
|
|
|
74
74
|
|
|
75
75
|
## 安装
|
|
76
76
|
|
|
77
|
+
注意:目前 pls 在 windows 端可能会有不兼容导致的 bug,如果遇到可以发 issue 反馈,谢谢
|
|
78
|
+
|
|
77
79
|
**方式一:npm(推荐)**
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
82
|
npm i -g @yivan-lab/pretty-please
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
**方式二:一键脚本(无需 Node.js
|
|
85
|
+
**方式二:一键脚本(无需 Node.js,但是是 bun 打包的,体积比较大)**
|
|
84
86
|
|
|
85
87
|
```bash
|
|
86
88
|
# Linux / macOS
|
|
87
89
|
curl -fsSL https://raw.githubusercontent.com/IvanLark/pretty-please/main/install.sh | bash
|
|
88
90
|
|
|
91
|
+
# Linux / macOS(国内加速)
|
|
92
|
+
curl -fsSL https://gh-proxy.org/https://raw.githubusercontent.com/IvanLark/pretty-please/main/install.sh | bash
|
|
93
|
+
|
|
89
94
|
# Windows PowerShell
|
|
90
95
|
irm https://raw.githubusercontent.com/IvanLark/pretty-please/main/install.ps1 | iex
|
|
96
|
+
|
|
97
|
+
# Windows PowerShell(国内加速)
|
|
98
|
+
irm https://gh-proxy.org/https://raw.githubusercontent.com/IvanLark/pretty-please/main/install.ps1 | iex
|
|
91
99
|
```
|
|
92
100
|
|
|
93
|
-
支持平台:Linux (x64/arm64) / macOS (Intel/Apple Silicon) / Windows x64
|
|
101
|
+
支持平台:Linux (x64/arm64) / macOS (Intel/Apple Silicon) / Windows (x64/ARM64)
|
|
94
102
|
|
|
95
103
|
## 快速开始
|
|
96
104
|
|
|
@@ -255,6 +263,22 @@ pls hook uninstall # 卸载 hook
|
|
|
255
263
|
|
|
256
264
|
支持 zsh / bash / PowerShell。
|
|
257
265
|
|
|
266
|
+
**开了 Hook 后,pls 会学习你的命令习惯。** 比如你平时用 `eza` 而不是 `ls`,用 `bat` 而不是 `cat`,AI 生成命令时会优先用你习惯的工具。用得越多,AI 越懂你。
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
pls prefs # 看看 AI 学到了什么
|
|
270
|
+
pls prefs clear # 清空偏好统计
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 系统信息
|
|
274
|
+
|
|
275
|
+
查看当前系统信息(AI 生成命令时会参考这些):
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
pls sysinfo # 查看系统信息
|
|
279
|
+
pls sysinfo refresh # 刷新缓存
|
|
280
|
+
```
|
|
281
|
+
|
|
258
282
|
### 主题
|
|
259
283
|
|
|
260
284
|
7 个内置主题 + 自定义主题:
|
|
@@ -371,6 +395,10 @@ pls hook install # 安装
|
|
|
371
395
|
pls hook status # 状态
|
|
372
396
|
pls hook uninstall # 卸载
|
|
373
397
|
|
|
398
|
+
# 偏好 & 系统
|
|
399
|
+
pls prefs # 查看命令偏好
|
|
400
|
+
pls sysinfo # 查看系统信息
|
|
401
|
+
|
|
374
402
|
# 主题
|
|
375
403
|
pls theme # 当前主题
|
|
376
404
|
pls theme list # 所有主题
|
package/bin/pls.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from 'commander'
|
|
|
3
3
|
import { fileURLToPath } from 'url'
|
|
4
4
|
import { dirname, join } from 'path'
|
|
5
5
|
import path from 'path'
|
|
6
|
-
import { exec } from 'child_process'
|
|
6
|
+
import { exec, spawn } from 'child_process'
|
|
7
7
|
import fs from 'fs'
|
|
8
8
|
import os from 'os'
|
|
9
9
|
import chalk from 'chalk'
|
|
@@ -12,7 +12,7 @@ import chalk from 'chalk'
|
|
|
12
12
|
// import { render } from 'ink'
|
|
13
13
|
// import { MultiStepCommandGenerator } from '../src/components/MultiStepCommandGenerator.js'
|
|
14
14
|
// import { Chat } from '../src/components/Chat.js'
|
|
15
|
-
import { isConfigValid, setConfigValue, getConfig, maskApiKey } from '../src/config.js'
|
|
15
|
+
import { isConfigValid, setConfigValue, getConfig, maskApiKey, displayConfig } from '../src/config.js'
|
|
16
16
|
import { clearHistory, addHistory, getHistory, getHistoryFilePath } from '../src/history.js'
|
|
17
17
|
import { clearChatHistory, getChatRoundCount, getChatHistoryFilePath, displayChatHistory } from '../src/chat-history.js'
|
|
18
18
|
import { type ExecutedStep } from '../src/multi-step.js'
|
|
@@ -50,6 +50,12 @@ import {
|
|
|
50
50
|
generateBatchRemoteCommands,
|
|
51
51
|
executeBatchRemoteCommands,
|
|
52
52
|
} from '../src/remote.js'
|
|
53
|
+
import { getSystemInfo, formatSystemInfo, refreshSystemCache, displaySystemInfo } from '../src/sysinfo.js'
|
|
54
|
+
import {
|
|
55
|
+
displayCommandStats,
|
|
56
|
+
clearCommandStats,
|
|
57
|
+
getStatsFilePath,
|
|
58
|
+
} from '../src/user-preferences.js'
|
|
53
59
|
import {
|
|
54
60
|
addRemoteHistory,
|
|
55
61
|
displayRemoteHistory,
|
|
@@ -65,6 +71,11 @@ import {
|
|
|
65
71
|
uninstallRemoteShellHook,
|
|
66
72
|
getRemoteHookStatus,
|
|
67
73
|
} from '../src/shell-hook.js'
|
|
74
|
+
import {
|
|
75
|
+
buildShellExecConfig,
|
|
76
|
+
getDefaultShell,
|
|
77
|
+
isWindows,
|
|
78
|
+
} from '../src/utils/platform.js'
|
|
68
79
|
|
|
69
80
|
// 获取主题颜色的辅助函数
|
|
70
81
|
function getThemeColors() {
|
|
@@ -116,10 +127,92 @@ process.on('beforeExit', () => {
|
|
|
116
127
|
}
|
|
117
128
|
})
|
|
118
129
|
|
|
130
|
+
/**
|
|
131
|
+
* 需要 TTY 的工具白名单
|
|
132
|
+
* 这些工具在 pipe 模式下可能会卡住或无输出
|
|
133
|
+
*/
|
|
134
|
+
const TTY_REQUIRED_COMMANDS = new Set([
|
|
135
|
+
// ls 替代品(带图标/颜色)
|
|
136
|
+
'eza', 'exa', 'lsd',
|
|
137
|
+
// cat 替代品(带语法高亮)
|
|
138
|
+
'bat', 'batcat',
|
|
139
|
+
// diff 替代品
|
|
140
|
+
'delta', 'diff-so-fancy',
|
|
141
|
+
// 系统监控
|
|
142
|
+
'htop', 'btop', 'top', 'glances', 'gtop', 'bpytop',
|
|
143
|
+
// 编辑器
|
|
144
|
+
'vim', 'nvim', 'nano', 'emacs', 'micro', 'helix', 'hx',
|
|
145
|
+
// 分页器
|
|
146
|
+
'less', 'more', 'most',
|
|
147
|
+
// 模糊搜索
|
|
148
|
+
'fzf', 'skim', 'sk',
|
|
149
|
+
// 终端复用器
|
|
150
|
+
'tmux', 'screen', 'zellij',
|
|
151
|
+
// TUI 工具
|
|
152
|
+
'lazygit', 'lazydocker', 'lazysql', 'k9s', 'tig',
|
|
153
|
+
// 文件管理器
|
|
154
|
+
'nnn', 'ranger', 'lf', 'yazi', 'mc', 'vifm',
|
|
155
|
+
// 数据查看
|
|
156
|
+
'visidata', 'vd',
|
|
157
|
+
])
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 使用 inherit 模式执行命令(用于需要 TTY 的工具)
|
|
161
|
+
* 特点:命令能正常执行,但无法捕获输出
|
|
162
|
+
*/
|
|
163
|
+
function executeWithInherit(command: string): Promise<{ exitCode: number; output: string; stdout: string; stderr: string }> {
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
console.log('') // 空行
|
|
166
|
+
|
|
167
|
+
// 计算命令框宽度
|
|
168
|
+
const termWidth = process.stdout.columns || 80
|
|
169
|
+
const maxContentWidth = termWidth - 6
|
|
170
|
+
const lines = command.split('\n')
|
|
171
|
+
const wrappedLines: string[] = []
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
wrappedLines.push(...console2.wrapText(line, maxContentWidth))
|
|
174
|
+
}
|
|
175
|
+
const actualMaxWidth = Math.max(
|
|
176
|
+
...wrappedLines.map((l) => console2.getDisplayWidth(l)),
|
|
177
|
+
console2.getDisplayWidth('生成命令')
|
|
178
|
+
)
|
|
179
|
+
const boxWidth = Math.max(console2.MIN_COMMAND_BOX_WIDTH, Math.min(actualMaxWidth + 4, termWidth - 2))
|
|
180
|
+
console2.printSeparator('输出', boxWidth)
|
|
181
|
+
|
|
182
|
+
// 使用 platform 模块构建跨平台命令执行配置
|
|
183
|
+
const execConfig = buildShellExecConfig(command)
|
|
184
|
+
|
|
185
|
+
// 使用 spawn + inherit(输出直接到终端)
|
|
186
|
+
const child = spawn(execConfig.shell, execConfig.args, {
|
|
187
|
+
stdio: 'inherit',
|
|
188
|
+
env: process.env,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
child.on('close', (code) => {
|
|
192
|
+
console2.printSeparator('', boxWidth)
|
|
193
|
+
resolve({ exitCode: code || 0, output: '', stdout: '', stderr: '' })
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
child.on('error', (err) => {
|
|
197
|
+
console2.printSeparator('', boxWidth)
|
|
198
|
+
console2.error(err.message)
|
|
199
|
+
resolve({ exitCode: 1, output: err.message, stdout: '', stderr: err.message })
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
119
204
|
/**
|
|
120
205
|
* 执行命令(原生版本)
|
|
121
206
|
*/
|
|
122
207
|
function executeCommand(command: string): Promise<{ exitCode: number; output: string; stdout: string; stderr: string }> {
|
|
208
|
+
// 检测是否是需要 TTY 的工具
|
|
209
|
+
const firstCmd = command.trim().split(/[\s|&;]/)[0]
|
|
210
|
+
if (TTY_REQUIRED_COMMANDS.has(firstCmd)) {
|
|
211
|
+
// 使用 inherit 模式执行(无法捕获输出,但能正常运行)
|
|
212
|
+
return executeWithInherit(command)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 普通命令:使用 pipe 模式(捕获输出)
|
|
123
216
|
return new Promise((resolve) => {
|
|
124
217
|
let stdout = ''
|
|
125
218
|
let stderr = ''
|
|
@@ -142,8 +235,10 @@ function executeCommand(command: string): Promise<{ exitCode: number; output: st
|
|
|
142
235
|
const boxWidth = Math.max(console2.MIN_COMMAND_BOX_WIDTH, Math.min(actualMaxWidth + 4, termWidth - 2))
|
|
143
236
|
console2.printSeparator('输出', boxWidth)
|
|
144
237
|
|
|
145
|
-
// 使用
|
|
146
|
-
const
|
|
238
|
+
// 使用 platform 模块构建跨平台命令执行配置
|
|
239
|
+
const execConfig = buildShellExecConfig(command)
|
|
240
|
+
|
|
241
|
+
const child = exec(execConfig.command, { shell: execConfig.shell })
|
|
147
242
|
|
|
148
243
|
child.stdout?.on('data', (data) => {
|
|
149
244
|
stdout += data
|
|
@@ -191,37 +286,7 @@ configCmd
|
|
|
191
286
|
.alias('show')
|
|
192
287
|
.description('查看当前配置')
|
|
193
288
|
.action(() => {
|
|
194
|
-
|
|
195
|
-
const CONFIG_FILE = join(os.homedir(), '.please', 'config.json')
|
|
196
|
-
|
|
197
|
-
console.log('')
|
|
198
|
-
console2.title('当前配置:')
|
|
199
|
-
console2.muted('━'.repeat(50))
|
|
200
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('apiKey')}: ${maskApiKey(config.apiKey)}`)
|
|
201
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('baseUrl')}: ${config.baseUrl}`)
|
|
202
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('provider')}: ${config.provider}`)
|
|
203
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('model')}: ${config.model}`)
|
|
204
|
-
console.log(
|
|
205
|
-
` ${chalk.hex(getThemeColors().primary)('shellHook')}: ${
|
|
206
|
-
config.shellHook ? chalk.hex(getThemeColors().success)('已启用') : chalk.gray('未启用')
|
|
207
|
-
}`
|
|
208
|
-
)
|
|
209
|
-
console.log(
|
|
210
|
-
` ${chalk.hex(getThemeColors().primary)('editMode')}: ${
|
|
211
|
-
config.editMode === 'auto' ? chalk.hex(getThemeColors().primary)('auto (自动编辑)') : chalk.gray('manual (按E编辑)')
|
|
212
|
-
}`
|
|
213
|
-
)
|
|
214
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
|
|
215
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
|
|
216
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
|
|
217
|
-
console.log(
|
|
218
|
-
` ${chalk.hex(getThemeColors().primary)('theme')}: ${
|
|
219
|
-
config.theme === 'dark' ? chalk.hex(getThemeColors().primary)('dark (深色)') : chalk.hex(getThemeColors().primary)('light (浅色)')
|
|
220
|
-
}`
|
|
221
|
-
)
|
|
222
|
-
console2.muted('━'.repeat(50))
|
|
223
|
-
console2.muted(`配置文件: ${CONFIG_FILE}`)
|
|
224
|
-
console.log('')
|
|
289
|
+
displayConfig()
|
|
225
290
|
})
|
|
226
291
|
|
|
227
292
|
configCmd
|
|
@@ -816,6 +881,59 @@ aliasCmd.action(() => {
|
|
|
816
881
|
displayAliases()
|
|
817
882
|
})
|
|
818
883
|
|
|
884
|
+
// sysinfo 子命令
|
|
885
|
+
const sysinfoCmd = program.command('sysinfo').description('管理系统信息')
|
|
886
|
+
|
|
887
|
+
sysinfoCmd
|
|
888
|
+
.command('show')
|
|
889
|
+
.description('查看系统信息')
|
|
890
|
+
.action(async () => {
|
|
891
|
+
const info = await getSystemInfo()
|
|
892
|
+
displaySystemInfo(info)
|
|
893
|
+
})
|
|
894
|
+
|
|
895
|
+
sysinfoCmd
|
|
896
|
+
.command('refresh')
|
|
897
|
+
.description('刷新系统信息缓存')
|
|
898
|
+
.action(() => {
|
|
899
|
+
console.log('')
|
|
900
|
+
refreshSystemCache()
|
|
901
|
+
console.log('')
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
// 默认 sysinfo 命令(显示信息)
|
|
905
|
+
sysinfoCmd.action(async () => {
|
|
906
|
+
const info = await getSystemInfo()
|
|
907
|
+
displaySystemInfo(info)
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
// prefs 子命令
|
|
911
|
+
const prefsCmd = program.command('prefs').description('管理命令偏好统计')
|
|
912
|
+
|
|
913
|
+
prefsCmd
|
|
914
|
+
.command('show')
|
|
915
|
+
.description('查看命令偏好统计')
|
|
916
|
+
.action(() => {
|
|
917
|
+
displayCommandStats()
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
prefsCmd
|
|
921
|
+
.command('clear')
|
|
922
|
+
.description('清空偏好统计')
|
|
923
|
+
.action(() => {
|
|
924
|
+
const colors = getThemeColors()
|
|
925
|
+
clearCommandStats()
|
|
926
|
+
console.log('')
|
|
927
|
+
console.log(chalk.hex(colors.success)('✓ 已清空命令偏好统计'))
|
|
928
|
+
console.log(chalk.gray(` 统计文件: ${getStatsFilePath()}`))
|
|
929
|
+
console.log('')
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
// 默认 prefs 命令(显示统计)
|
|
933
|
+
prefsCmd.action(() => {
|
|
934
|
+
displayCommandStats()
|
|
935
|
+
})
|
|
936
|
+
|
|
819
937
|
// remote 子命令
|
|
820
938
|
const remoteCmd = program.command('remote').description('管理远程服务器')
|
|
821
939
|
|
package/dist/bin/pls.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { dirname
|
|
4
|
+
import { dirname } from 'path';
|
|
5
5
|
import path from 'path';
|
|
6
|
-
import { exec } from 'child_process';
|
|
6
|
+
import { exec, spawn } from 'child_process';
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import os from 'os';
|
|
9
9
|
import chalk from 'chalk';
|
|
@@ -12,7 +12,7 @@ import chalk from 'chalk';
|
|
|
12
12
|
// import { render } from 'ink'
|
|
13
13
|
// import { MultiStepCommandGenerator } from '../src/components/MultiStepCommandGenerator.js'
|
|
14
14
|
// import { Chat } from '../src/components/Chat.js'
|
|
15
|
-
import { isConfigValid, setConfigValue, getConfig,
|
|
15
|
+
import { isConfigValid, setConfigValue, getConfig, displayConfig } from '../src/config.js';
|
|
16
16
|
import { clearHistory, addHistory, getHistory, getHistoryFilePath } from '../src/history.js';
|
|
17
17
|
import { clearChatHistory, getChatRoundCount, getChatHistoryFilePath, displayChatHistory } from '../src/chat-history.js';
|
|
18
18
|
import { installShellHook, uninstallShellHook, getHookStatus, detectShell, getShellConfigPath, displayShellHistory, clearShellHistory, } from '../src/shell-hook.js';
|
|
@@ -20,8 +20,11 @@ import { checkForUpdates, showUpdateNotice, performUpgrade, } from '../src/upgra
|
|
|
20
20
|
import { getCurrentTheme } from '../src/ui/theme.js';
|
|
21
21
|
import { addAlias, removeAlias, displayAliases, resolveAlias, } from '../src/alias.js';
|
|
22
22
|
import { addRemote, removeRemote, displayRemotes, getRemote, testRemoteConnection, sshExec, collectRemoteSysInfo, setRemoteWorkDir, getRemoteWorkDir, generateBatchRemoteCommands, executeBatchRemoteCommands, } from '../src/remote.js';
|
|
23
|
+
import { getSystemInfo, refreshSystemCache, displaySystemInfo } from '../src/sysinfo.js';
|
|
24
|
+
import { displayCommandStats, clearCommandStats, getStatsFilePath, } from '../src/user-preferences.js';
|
|
23
25
|
import { addRemoteHistory, displayRemoteHistory, clearRemoteHistory, fetchRemoteShellHistory, displayRemoteShellHistory, clearRemoteShellHistory, } from '../src/remote-history.js';
|
|
24
26
|
import { detectRemoteShell, getRemoteShellConfigPath, installRemoteShellHook, uninstallRemoteShellHook, getRemoteHookStatus, } from '../src/shell-hook.js';
|
|
27
|
+
import { buildShellExecConfig, } from '../src/utils/platform.js';
|
|
25
28
|
// 获取主题颜色的辅助函数
|
|
26
29
|
function getThemeColors() {
|
|
27
30
|
const theme = getCurrentTheme();
|
|
@@ -65,10 +68,81 @@ process.on('beforeExit', () => {
|
|
|
65
68
|
showUpdateNotice(packageJson.version, updateCheckResult.latestVersion);
|
|
66
69
|
}
|
|
67
70
|
});
|
|
71
|
+
/**
|
|
72
|
+
* 需要 TTY 的工具白名单
|
|
73
|
+
* 这些工具在 pipe 模式下可能会卡住或无输出
|
|
74
|
+
*/
|
|
75
|
+
const TTY_REQUIRED_COMMANDS = new Set([
|
|
76
|
+
// ls 替代品(带图标/颜色)
|
|
77
|
+
'eza', 'exa', 'lsd',
|
|
78
|
+
// cat 替代品(带语法高亮)
|
|
79
|
+
'bat', 'batcat',
|
|
80
|
+
// diff 替代品
|
|
81
|
+
'delta', 'diff-so-fancy',
|
|
82
|
+
// 系统监控
|
|
83
|
+
'htop', 'btop', 'top', 'glances', 'gtop', 'bpytop',
|
|
84
|
+
// 编辑器
|
|
85
|
+
'vim', 'nvim', 'nano', 'emacs', 'micro', 'helix', 'hx',
|
|
86
|
+
// 分页器
|
|
87
|
+
'less', 'more', 'most',
|
|
88
|
+
// 模糊搜索
|
|
89
|
+
'fzf', 'skim', 'sk',
|
|
90
|
+
// 终端复用器
|
|
91
|
+
'tmux', 'screen', 'zellij',
|
|
92
|
+
// TUI 工具
|
|
93
|
+
'lazygit', 'lazydocker', 'lazysql', 'k9s', 'tig',
|
|
94
|
+
// 文件管理器
|
|
95
|
+
'nnn', 'ranger', 'lf', 'yazi', 'mc', 'vifm',
|
|
96
|
+
// 数据查看
|
|
97
|
+
'visidata', 'vd',
|
|
98
|
+
]);
|
|
99
|
+
/**
|
|
100
|
+
* 使用 inherit 模式执行命令(用于需要 TTY 的工具)
|
|
101
|
+
* 特点:命令能正常执行,但无法捕获输出
|
|
102
|
+
*/
|
|
103
|
+
function executeWithInherit(command) {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
console.log(''); // 空行
|
|
106
|
+
// 计算命令框宽度
|
|
107
|
+
const termWidth = process.stdout.columns || 80;
|
|
108
|
+
const maxContentWidth = termWidth - 6;
|
|
109
|
+
const lines = command.split('\n');
|
|
110
|
+
const wrappedLines = [];
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
wrappedLines.push(...console2.wrapText(line, maxContentWidth));
|
|
113
|
+
}
|
|
114
|
+
const actualMaxWidth = Math.max(...wrappedLines.map((l) => console2.getDisplayWidth(l)), console2.getDisplayWidth('生成命令'));
|
|
115
|
+
const boxWidth = Math.max(console2.MIN_COMMAND_BOX_WIDTH, Math.min(actualMaxWidth + 4, termWidth - 2));
|
|
116
|
+
console2.printSeparator('输出', boxWidth);
|
|
117
|
+
// 使用 platform 模块构建跨平台命令执行配置
|
|
118
|
+
const execConfig = buildShellExecConfig(command);
|
|
119
|
+
// 使用 spawn + inherit(输出直接到终端)
|
|
120
|
+
const child = spawn(execConfig.shell, execConfig.args, {
|
|
121
|
+
stdio: 'inherit',
|
|
122
|
+
env: process.env,
|
|
123
|
+
});
|
|
124
|
+
child.on('close', (code) => {
|
|
125
|
+
console2.printSeparator('', boxWidth);
|
|
126
|
+
resolve({ exitCode: code || 0, output: '', stdout: '', stderr: '' });
|
|
127
|
+
});
|
|
128
|
+
child.on('error', (err) => {
|
|
129
|
+
console2.printSeparator('', boxWidth);
|
|
130
|
+
console2.error(err.message);
|
|
131
|
+
resolve({ exitCode: 1, output: err.message, stdout: '', stderr: err.message });
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
68
135
|
/**
|
|
69
136
|
* 执行命令(原生版本)
|
|
70
137
|
*/
|
|
71
138
|
function executeCommand(command) {
|
|
139
|
+
// 检测是否是需要 TTY 的工具
|
|
140
|
+
const firstCmd = command.trim().split(/[\s|&;]/)[0];
|
|
141
|
+
if (TTY_REQUIRED_COMMANDS.has(firstCmd)) {
|
|
142
|
+
// 使用 inherit 模式执行(无法捕获输出,但能正常运行)
|
|
143
|
+
return executeWithInherit(command);
|
|
144
|
+
}
|
|
145
|
+
// 普通命令:使用 pipe 模式(捕获输出)
|
|
72
146
|
return new Promise((resolve) => {
|
|
73
147
|
let stdout = '';
|
|
74
148
|
let stderr = '';
|
|
@@ -85,8 +159,9 @@ function executeCommand(command) {
|
|
|
85
159
|
const actualMaxWidth = Math.max(...wrappedLines.map((l) => console2.getDisplayWidth(l)), console2.getDisplayWidth('生成命令'));
|
|
86
160
|
const boxWidth = Math.max(console2.MIN_COMMAND_BOX_WIDTH, Math.min(actualMaxWidth + 4, termWidth - 2));
|
|
87
161
|
console2.printSeparator('输出', boxWidth);
|
|
88
|
-
// 使用
|
|
89
|
-
const
|
|
162
|
+
// 使用 platform 模块构建跨平台命令执行配置
|
|
163
|
+
const execConfig = buildShellExecConfig(command);
|
|
164
|
+
const child = exec(execConfig.command, { shell: execConfig.shell });
|
|
90
165
|
child.stdout?.on('data', (data) => {
|
|
91
166
|
stdout += data;
|
|
92
167
|
hasOutput = true;
|
|
@@ -127,24 +202,7 @@ configCmd
|
|
|
127
202
|
.alias('show')
|
|
128
203
|
.description('查看当前配置')
|
|
129
204
|
.action(() => {
|
|
130
|
-
|
|
131
|
-
const CONFIG_FILE = join(os.homedir(), '.please', 'config.json');
|
|
132
|
-
console.log('');
|
|
133
|
-
console2.title('当前配置:');
|
|
134
|
-
console2.muted('━'.repeat(50));
|
|
135
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('apiKey')}: ${maskApiKey(config.apiKey)}`);
|
|
136
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('baseUrl')}: ${config.baseUrl}`);
|
|
137
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('provider')}: ${config.provider}`);
|
|
138
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('model')}: ${config.model}`);
|
|
139
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('shellHook')}: ${config.shellHook ? chalk.hex(getThemeColors().success)('已启用') : chalk.gray('未启用')}`);
|
|
140
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('editMode')}: ${config.editMode === 'auto' ? chalk.hex(getThemeColors().primary)('auto (自动编辑)') : chalk.gray('manual (按E编辑)')}`);
|
|
141
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`);
|
|
142
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`);
|
|
143
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`);
|
|
144
|
-
console.log(` ${chalk.hex(getThemeColors().primary)('theme')}: ${config.theme === 'dark' ? chalk.hex(getThemeColors().primary)('dark (深色)') : chalk.hex(getThemeColors().primary)('light (浅色)')}`);
|
|
145
|
-
console2.muted('━'.repeat(50));
|
|
146
|
-
console2.muted(`配置文件: ${CONFIG_FILE}`);
|
|
147
|
-
console.log('');
|
|
205
|
+
displayConfig();
|
|
148
206
|
});
|
|
149
207
|
configCmd
|
|
150
208
|
.command('set <key> <value>')
|
|
@@ -648,6 +706,51 @@ aliasCmd
|
|
|
648
706
|
aliasCmd.action(() => {
|
|
649
707
|
displayAliases();
|
|
650
708
|
});
|
|
709
|
+
// sysinfo 子命令
|
|
710
|
+
const sysinfoCmd = program.command('sysinfo').description('管理系统信息');
|
|
711
|
+
sysinfoCmd
|
|
712
|
+
.command('show')
|
|
713
|
+
.description('查看系统信息')
|
|
714
|
+
.action(async () => {
|
|
715
|
+
const info = await getSystemInfo();
|
|
716
|
+
displaySystemInfo(info);
|
|
717
|
+
});
|
|
718
|
+
sysinfoCmd
|
|
719
|
+
.command('refresh')
|
|
720
|
+
.description('刷新系统信息缓存')
|
|
721
|
+
.action(() => {
|
|
722
|
+
console.log('');
|
|
723
|
+
refreshSystemCache();
|
|
724
|
+
console.log('');
|
|
725
|
+
});
|
|
726
|
+
// 默认 sysinfo 命令(显示信息)
|
|
727
|
+
sysinfoCmd.action(async () => {
|
|
728
|
+
const info = await getSystemInfo();
|
|
729
|
+
displaySystemInfo(info);
|
|
730
|
+
});
|
|
731
|
+
// prefs 子命令
|
|
732
|
+
const prefsCmd = program.command('prefs').description('管理命令偏好统计');
|
|
733
|
+
prefsCmd
|
|
734
|
+
.command('show')
|
|
735
|
+
.description('查看命令偏好统计')
|
|
736
|
+
.action(() => {
|
|
737
|
+
displayCommandStats();
|
|
738
|
+
});
|
|
739
|
+
prefsCmd
|
|
740
|
+
.command('clear')
|
|
741
|
+
.description('清空偏好统计')
|
|
742
|
+
.action(() => {
|
|
743
|
+
const colors = getThemeColors();
|
|
744
|
+
clearCommandStats();
|
|
745
|
+
console.log('');
|
|
746
|
+
console.log(chalk.hex(colors.success)('✓ 已清空命令偏好统计'));
|
|
747
|
+
console.log(chalk.gray(` 统计文件: ${getStatsFilePath()}`));
|
|
748
|
+
console.log('');
|
|
749
|
+
});
|
|
750
|
+
// 默认 prefs 命令(显示统计)
|
|
751
|
+
prefsCmd.action(() => {
|
|
752
|
+
displayCommandStats();
|
|
753
|
+
});
|
|
651
754
|
// remote 子命令
|
|
652
755
|
const remoteCmd = program.command('remote').description('管理远程服务器');
|
|
653
756
|
remoteCmd
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yivan-lab/pretty-please",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "AI 驱动的命令行工具,将自然语言转换为可执行的 Shell 命令",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"dev": "tsx bin/pls.tsx",
|
|
12
12
|
"build": "tsc && node scripts/postbuild.js",
|
|
13
13
|
"start": "node dist/bin/pls.js",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"test:ui": "vitest --ui",
|
|
17
|
+
"test:coverage": "vitest run --coverage",
|
|
14
18
|
"link:dev": "mkdir -p ~/.local/bin && ln -sf \"$(pwd)/bin/pls.tsx\" ~/.local/bin/pls-dev && echo '✅ pls-dev 已链接到 ~/.local/bin/pls-dev'",
|
|
15
19
|
"unlink:dev": "rm -f ~/.local/bin/pls-dev && echo '✅ pls-dev 已移除'",
|
|
16
20
|
"prepublishOnly": "pnpm build"
|
|
@@ -55,6 +59,7 @@
|
|
|
55
59
|
"chalk": "^5.6.2",
|
|
56
60
|
"cli-highlight": "^2.1.11",
|
|
57
61
|
"commander": "^14.0.2",
|
|
62
|
+
"detect-package-manager": "^3.0.2",
|
|
58
63
|
"ink": "^6.5.1",
|
|
59
64
|
"ink-box": "^2.0.0",
|
|
60
65
|
"ink-markdown": "^1.0.4",
|
|
@@ -73,8 +78,11 @@
|
|
|
73
78
|
"@types/hast": "^3.0.4",
|
|
74
79
|
"@types/node": "^25.0.2",
|
|
75
80
|
"@types/react": "^19.2.7",
|
|
81
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
82
|
+
"@vitest/ui": "^4.0.16",
|
|
76
83
|
"react-devtools-core": "^7.0.1",
|
|
77
84
|
"tsx": "^4.21.0",
|
|
78
|
-
"typescript": "^5.9.3"
|
|
85
|
+
"typescript": "^5.9.3",
|
|
86
|
+
"vitest": "^4.0.16"
|
|
79
87
|
}
|
|
80
88
|
}
|