@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/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
|
|
|
@@ -1240,7 +1358,7 @@ program
|
|
|
1240
1358
|
.argument('[prompt...]', '自然语言描述你想执行的操作')
|
|
1241
1359
|
.option('-d, --debug', '显示调试信息(系统信息、完整 prompt 等)')
|
|
1242
1360
|
.option('-r, --remote [name]', '在远程服务器上执行(不指定则使用默认服务器)')
|
|
1243
|
-
.action((promptArgs, options) => {
|
|
1361
|
+
.action(async (promptArgs, options) => {
|
|
1244
1362
|
// 智能处理 -r 参数:如果 -r 后面的值不是已注册的服务器名,把它当作 prompt 的一部分
|
|
1245
1363
|
if (typeof options.remote === 'string' && !getRemote(options.remote)) {
|
|
1246
1364
|
// "查看当前目录" 不是服务器名,放回 prompt
|
|
@@ -1248,12 +1366,32 @@ program
|
|
|
1248
1366
|
options.remote = true // 改为使用默认服务器
|
|
1249
1367
|
}
|
|
1250
1368
|
|
|
1369
|
+
let prompt = ''
|
|
1370
|
+
|
|
1251
1371
|
if (promptArgs.length === 0) {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1372
|
+
// 无参数时:尝试自动检测上一条失败的命令
|
|
1373
|
+
const { getLastNonPlsCommand } = await import('../src/shell-hook.js')
|
|
1374
|
+
const lastCmd = getLastNonPlsCommand()
|
|
1375
|
+
|
|
1376
|
+
if (lastCmd && lastCmd.exit !== 0) {
|
|
1377
|
+
// 找到了失败的命令,自动生成 prompt
|
|
1378
|
+
prompt = `上一条命令「${lastCmd.cmd}」执行失败,退出码:${lastCmd.exit}。请生成正确的命令。`
|
|
1379
|
+
|
|
1380
|
+
if (options.debug) {
|
|
1381
|
+
console.log('')
|
|
1382
|
+
console2.muted(`自动检测到失败命令: ${lastCmd.cmd} (退出码: ${lastCmd.exit})`)
|
|
1383
|
+
console2.muted(`生成 prompt: ${prompt}`)
|
|
1384
|
+
}
|
|
1255
1385
|
|
|
1256
|
-
|
|
1386
|
+
// 继续执行命令生成流程(不 return)
|
|
1387
|
+
} else {
|
|
1388
|
+
// 没有失败的命令,显示帮助
|
|
1389
|
+
program.help()
|
|
1390
|
+
return
|
|
1391
|
+
}
|
|
1392
|
+
} else {
|
|
1393
|
+
prompt = promptArgs.join(' ')
|
|
1394
|
+
}
|
|
1257
1395
|
|
|
1258
1396
|
if (!prompt.trim()) {
|
|
1259
1397
|
console.log('')
|
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
|
|
@@ -1030,18 +1133,37 @@ program
|
|
|
1030
1133
|
.argument('[prompt...]', '自然语言描述你想执行的操作')
|
|
1031
1134
|
.option('-d, --debug', '显示调试信息(系统信息、完整 prompt 等)')
|
|
1032
1135
|
.option('-r, --remote [name]', '在远程服务器上执行(不指定则使用默认服务器)')
|
|
1033
|
-
.action((promptArgs, options) => {
|
|
1136
|
+
.action(async (promptArgs, options) => {
|
|
1034
1137
|
// 智能处理 -r 参数:如果 -r 后面的值不是已注册的服务器名,把它当作 prompt 的一部分
|
|
1035
1138
|
if (typeof options.remote === 'string' && !getRemote(options.remote)) {
|
|
1036
1139
|
// "查看当前目录" 不是服务器名,放回 prompt
|
|
1037
1140
|
promptArgs.unshift(options.remote);
|
|
1038
1141
|
options.remote = true; // 改为使用默认服务器
|
|
1039
1142
|
}
|
|
1143
|
+
let prompt = '';
|
|
1040
1144
|
if (promptArgs.length === 0) {
|
|
1041
|
-
|
|
1042
|
-
|
|
1145
|
+
// 无参数时:尝试自动检测上一条失败的命令
|
|
1146
|
+
const { getLastNonPlsCommand } = await import('../src/shell-hook.js');
|
|
1147
|
+
const lastCmd = getLastNonPlsCommand();
|
|
1148
|
+
if (lastCmd && lastCmd.exit !== 0) {
|
|
1149
|
+
// 找到了失败的命令,自动生成 prompt
|
|
1150
|
+
prompt = `上一条命令「${lastCmd.cmd}」执行失败,退出码:${lastCmd.exit}。请生成正确的命令。`;
|
|
1151
|
+
if (options.debug) {
|
|
1152
|
+
console.log('');
|
|
1153
|
+
console2.muted(`自动检测到失败命令: ${lastCmd.cmd} (退出码: ${lastCmd.exit})`);
|
|
1154
|
+
console2.muted(`生成 prompt: ${prompt}`);
|
|
1155
|
+
}
|
|
1156
|
+
// 继续执行命令生成流程(不 return)
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
// 没有失败的命令,显示帮助
|
|
1160
|
+
program.help();
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
prompt = promptArgs.join(' ');
|
|
1043
1166
|
}
|
|
1044
|
-
let prompt = promptArgs.join(' ');
|
|
1045
1167
|
if (!prompt.trim()) {
|
|
1046
1168
|
console.log('');
|
|
1047
1169
|
console2.error('请提供你想执行的操作描述');
|
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.0",
|
|
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
|
}
|