@yivan-lab/pretty-please 1.1.0 → 1.2.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 +283 -1
- package/bin/pls.tsx +1022 -104
- package/dist/bin/pls.js +894 -84
- package/dist/package.json +4 -4
- package/dist/src/alias.d.ts +41 -0
- package/dist/src/alias.js +240 -0
- package/dist/src/chat-history.js +10 -1
- package/dist/src/components/Chat.js +2 -1
- package/dist/src/components/CodeColorizer.js +26 -20
- package/dist/src/components/CommandBox.js +2 -1
- package/dist/src/components/ConfirmationPrompt.js +2 -1
- package/dist/src/components/Duration.js +2 -1
- package/dist/src/components/InlineRenderer.js +2 -1
- package/dist/src/components/MarkdownDisplay.js +2 -1
- package/dist/src/components/MultiStepCommandGenerator.d.ts +3 -1
- package/dist/src/components/MultiStepCommandGenerator.js +20 -10
- package/dist/src/components/TableRenderer.js +2 -1
- package/dist/src/config.d.ts +34 -3
- package/dist/src/config.js +71 -31
- package/dist/src/multi-step.d.ts +22 -6
- package/dist/src/multi-step.js +27 -4
- package/dist/src/remote-history.d.ts +63 -0
- package/dist/src/remote-history.js +315 -0
- package/dist/src/remote.d.ts +113 -0
- package/dist/src/remote.js +634 -0
- package/dist/src/shell-hook.d.ts +53 -0
- package/dist/src/shell-hook.js +242 -19
- package/dist/src/ui/theme.d.ts +27 -24
- package/dist/src/ui/theme.js +71 -21
- package/dist/src/upgrade.d.ts +41 -0
- package/dist/src/upgrade.js +348 -0
- package/dist/src/utils/console.js +22 -11
- package/package.json +4 -4
- package/src/alias.ts +301 -0
- package/src/chat-history.ts +11 -1
- package/src/components/Chat.tsx +2 -1
- package/src/components/CodeColorizer.tsx +27 -19
- package/src/components/CommandBox.tsx +2 -1
- package/src/components/ConfirmationPrompt.tsx +2 -1
- package/src/components/Duration.tsx +2 -1
- package/src/components/InlineRenderer.tsx +2 -1
- package/src/components/MarkdownDisplay.tsx +2 -1
- package/src/components/MultiStepCommandGenerator.tsx +25 -11
- package/src/components/TableRenderer.tsx +2 -1
- package/src/config.ts +117 -32
- package/src/multi-step.ts +43 -6
- package/src/remote-history.ts +390 -0
- package/src/remote.ts +800 -0
- package/src/shell-hook.ts +271 -19
- package/src/ui/theme.ts +101 -24
- package/src/upgrade.ts +397 -0
- package/src/utils/console.ts +22 -11
package/bin/pls.tsx
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { render } from 'ink'
|
|
4
2
|
import { Command } from 'commander'
|
|
5
3
|
import { fileURLToPath } from 'url'
|
|
6
4
|
import { dirname, join } from 'path'
|
|
@@ -8,8 +6,11 @@ import { exec } from 'child_process'
|
|
|
8
6
|
import fs from 'fs'
|
|
9
7
|
import os from 'os'
|
|
10
8
|
import chalk from 'chalk'
|
|
11
|
-
|
|
12
|
-
import
|
|
9
|
+
// React 和 Ink 懒加载(只在需要 UI 时加载)
|
|
10
|
+
// import React from 'react'
|
|
11
|
+
// import { render } from 'ink'
|
|
12
|
+
// import { MultiStepCommandGenerator } from '../src/components/MultiStepCommandGenerator.js'
|
|
13
|
+
// import { Chat } from '../src/components/Chat.js'
|
|
13
14
|
import { isConfigValid, setConfigValue, getConfig, maskApiKey } from '../src/config.js'
|
|
14
15
|
import { clearHistory, addHistory, getHistory, getHistoryFilePath } from '../src/history.js'
|
|
15
16
|
import { clearChatHistory, getChatRoundCount, getChatHistoryFilePath, displayChatHistory } from '../src/chat-history.js'
|
|
@@ -23,9 +24,63 @@ import {
|
|
|
23
24
|
displayShellHistory,
|
|
24
25
|
clearShellHistory,
|
|
25
26
|
} from '../src/shell-hook.js'
|
|
27
|
+
import {
|
|
28
|
+
checkForUpdates,
|
|
29
|
+
showUpdateNotice,
|
|
30
|
+
performUpgrade,
|
|
31
|
+
} from '../src/upgrade.js'
|
|
32
|
+
import { getCurrentTheme } from '../src/ui/theme.js'
|
|
33
|
+
import {
|
|
34
|
+
addAlias,
|
|
35
|
+
removeAlias,
|
|
36
|
+
displayAliases,
|
|
37
|
+
resolveAlias,
|
|
38
|
+
} from '../src/alias.js'
|
|
39
|
+
import {
|
|
40
|
+
addRemote,
|
|
41
|
+
removeRemote,
|
|
42
|
+
displayRemotes,
|
|
43
|
+
getRemote,
|
|
44
|
+
testRemoteConnection,
|
|
45
|
+
sshExec,
|
|
46
|
+
collectRemoteSysInfo,
|
|
47
|
+
setRemoteWorkDir,
|
|
48
|
+
getRemoteWorkDir,
|
|
49
|
+
generateBatchRemoteCommands,
|
|
50
|
+
executeBatchRemoteCommands,
|
|
51
|
+
} from '../src/remote.js'
|
|
52
|
+
import {
|
|
53
|
+
addRemoteHistory,
|
|
54
|
+
displayRemoteHistory,
|
|
55
|
+
clearRemoteHistory,
|
|
56
|
+
fetchRemoteShellHistory,
|
|
57
|
+
displayRemoteShellHistory,
|
|
58
|
+
clearRemoteShellHistory,
|
|
59
|
+
} from '../src/remote-history.js'
|
|
60
|
+
import {
|
|
61
|
+
detectRemoteShell,
|
|
62
|
+
getRemoteShellConfigPath,
|
|
63
|
+
installRemoteShellHook,
|
|
64
|
+
uninstallRemoteShellHook,
|
|
65
|
+
getRemoteHookStatus,
|
|
66
|
+
} from '../src/shell-hook.js'
|
|
67
|
+
|
|
68
|
+
// 获取主题颜色的辅助函数
|
|
69
|
+
function getThemeColors() {
|
|
70
|
+
const theme = getCurrentTheme()
|
|
71
|
+
return {
|
|
72
|
+
primary: theme.primary,
|
|
73
|
+
success: theme.success,
|
|
74
|
+
error: theme.error,
|
|
75
|
+
warning: theme.warning,
|
|
76
|
+
info: theme.info,
|
|
77
|
+
muted: theme.text.muted,
|
|
78
|
+
secondary: theme.text.secondary,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
26
81
|
import * as console2 from '../src/utils/console.js'
|
|
27
82
|
// 导入 package.json(Bun 会自动打包进二进制)
|
|
28
|
-
import packageJson from '../package.json'
|
|
83
|
+
import packageJson from '../package.json' with { type: 'json' }
|
|
29
84
|
|
|
30
85
|
// 保留这些用于其他可能的用途
|
|
31
86
|
const __filename = fileURLToPath(import.meta.url)
|
|
@@ -33,12 +88,40 @@ const __dirname = dirname(__filename)
|
|
|
33
88
|
|
|
34
89
|
const program = new Command()
|
|
35
90
|
|
|
91
|
+
|
|
92
|
+
// 启动时异步检查更新(不阻塞主流程)
|
|
93
|
+
let updateCheckResult: { hasUpdate: boolean; latestVersion: string | null } | null = null
|
|
94
|
+
const isUpgradeCommand = process.argv.includes('upgrade')
|
|
95
|
+
|
|
96
|
+
// 延迟更新检查到命令解析后(减少启动时间)
|
|
97
|
+
// 非 upgrade 命令时才检查更新
|
|
98
|
+
if (!isUpgradeCommand) {
|
|
99
|
+
// 延迟 100ms 开始检查,避免影响简单命令的响应速度
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
checkForUpdates(packageJson.version)
|
|
102
|
+
.then((result) => {
|
|
103
|
+
updateCheckResult = result
|
|
104
|
+
})
|
|
105
|
+
.catch(() => {
|
|
106
|
+
// 静默失败
|
|
107
|
+
})
|
|
108
|
+
}, 100)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 程序退出时显示更新提示
|
|
112
|
+
process.on('beforeExit', () => {
|
|
113
|
+
if (updateCheckResult?.hasUpdate && updateCheckResult.latestVersion && !isUpgradeCommand) {
|
|
114
|
+
showUpdateNotice(packageJson.version, updateCheckResult.latestVersion)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
36
118
|
/**
|
|
37
119
|
* 执行命令(原生版本)
|
|
38
120
|
*/
|
|
39
|
-
function executeCommand(command: string): Promise<{ exitCode: number; output: string }> {
|
|
121
|
+
function executeCommand(command: string): Promise<{ exitCode: number; output: string; stdout: string; stderr: string }> {
|
|
40
122
|
return new Promise((resolve) => {
|
|
41
|
-
let
|
|
123
|
+
let stdout = ''
|
|
124
|
+
let stderr = ''
|
|
42
125
|
let hasOutput = false
|
|
43
126
|
|
|
44
127
|
console.log('') // 空行
|
|
@@ -49,16 +132,17 @@ function executeCommand(command: string): Promise<{ exitCode: number; output: st
|
|
|
49
132
|
const boxWidth = Math.max(maxContentWidth + 4, console2.getDisplayWidth('生成命令') + 6, 20)
|
|
50
133
|
console2.printSeparator('输出', boxWidth)
|
|
51
134
|
|
|
52
|
-
|
|
135
|
+
// 使用 bash 并启用 pipefail,确保管道中任何命令失败都能正确返回非零退出码
|
|
136
|
+
const child = exec(`set -o pipefail; ${command}`, { shell: '/bin/bash' })
|
|
53
137
|
|
|
54
138
|
child.stdout?.on('data', (data) => {
|
|
55
|
-
|
|
139
|
+
stdout += data
|
|
56
140
|
hasOutput = true
|
|
57
141
|
process.stdout.write(data)
|
|
58
142
|
})
|
|
59
143
|
|
|
60
144
|
child.stderr?.on('data', (data) => {
|
|
61
|
-
|
|
145
|
+
stderr += data
|
|
62
146
|
hasOutput = true
|
|
63
147
|
process.stderr.write(data)
|
|
64
148
|
})
|
|
@@ -67,7 +151,7 @@ function executeCommand(command: string): Promise<{ exitCode: number; output: st
|
|
|
67
151
|
if (hasOutput) {
|
|
68
152
|
console2.printSeparator('', boxWidth)
|
|
69
153
|
}
|
|
70
|
-
resolve({ exitCode: code || 0, output })
|
|
154
|
+
resolve({ exitCode: code || 0, output: stdout + stderr, stdout, stderr })
|
|
71
155
|
})
|
|
72
156
|
|
|
73
157
|
child.on('error', (err) => {
|
|
@@ -76,7 +160,7 @@ function executeCommand(command: string): Promise<{ exitCode: number; output: st
|
|
|
76
160
|
}
|
|
77
161
|
console2.error(err.message)
|
|
78
162
|
console2.printSeparator('', boxWidth)
|
|
79
|
-
resolve({ exitCode: 1, output: err.message })
|
|
163
|
+
resolve({ exitCode: 1, output: err.message, stdout: '', stderr: err.message })
|
|
80
164
|
})
|
|
81
165
|
})
|
|
82
166
|
}
|
|
@@ -87,6 +171,7 @@ program
|
|
|
87
171
|
.description('AI 驱动的命令行工具,将自然语言转换为可执行的 Shell 命令')
|
|
88
172
|
.version(packageJson.version, '-v, --version', '显示版本号')
|
|
89
173
|
.helpOption('-h, --help', '显示帮助信息')
|
|
174
|
+
.allowUnknownOption(true) // 允许未知选项(用于别名参数传递)
|
|
90
175
|
|
|
91
176
|
// config 子命令
|
|
92
177
|
const configCmd = program.command('config').description('管理配置')
|
|
@@ -102,23 +187,28 @@ configCmd
|
|
|
102
187
|
console.log('')
|
|
103
188
|
console2.title('当前配置:')
|
|
104
189
|
console2.muted('━'.repeat(50))
|
|
105
|
-
console.log(` ${chalk.hex(
|
|
106
|
-
console.log(` ${chalk.hex(
|
|
107
|
-
console.log(` ${chalk.hex(
|
|
108
|
-
console.log(` ${chalk.hex(
|
|
190
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('apiKey')}: ${maskApiKey(config.apiKey)}`)
|
|
191
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('baseUrl')}: ${config.baseUrl}`)
|
|
192
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('provider')}: ${config.provider}`)
|
|
193
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('model')}: ${config.model}`)
|
|
194
|
+
console.log(
|
|
195
|
+
` ${chalk.hex(getThemeColors().primary)('shellHook')}: ${
|
|
196
|
+
config.shellHook ? chalk.hex(getThemeColors().success)('已启用') : chalk.gray('未启用')
|
|
197
|
+
}`
|
|
198
|
+
)
|
|
109
199
|
console.log(
|
|
110
|
-
` ${chalk.hex(
|
|
111
|
-
config.
|
|
200
|
+
` ${chalk.hex(getThemeColors().primary)('editMode')}: ${
|
|
201
|
+
config.editMode === 'auto' ? chalk.hex(getThemeColors().primary)('auto (自动编辑)') : chalk.gray('manual (按E编辑)')
|
|
112
202
|
}`
|
|
113
203
|
)
|
|
204
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
|
|
205
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
|
|
206
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
|
|
114
207
|
console.log(
|
|
115
|
-
` ${chalk.hex(
|
|
116
|
-
config.
|
|
208
|
+
` ${chalk.hex(getThemeColors().primary)('theme')}: ${
|
|
209
|
+
config.theme === 'dark' ? chalk.hex(getThemeColors().primary)('dark (深色)') : chalk.hex(getThemeColors().primary)('light (浅色)')
|
|
117
210
|
}`
|
|
118
211
|
)
|
|
119
|
-
console.log(` ${chalk.hex('#00D9FF')('chatHistoryLimit')}: ${config.chatHistoryLimit} 轮`)
|
|
120
|
-
console.log(` ${chalk.hex('#00D9FF')('commandHistoryLimit')}: ${config.commandHistoryLimit} 条`)
|
|
121
|
-
console.log(` ${chalk.hex('#00D9FF')('shellHistoryLimit')}: ${config.shellHistoryLimit} 条`)
|
|
122
212
|
console2.muted('━'.repeat(50))
|
|
123
213
|
console2.muted(`配置文件: ${CONFIG_FILE}`)
|
|
124
214
|
console.log('')
|
|
@@ -147,6 +237,75 @@ configCmd.action(async () => {
|
|
|
147
237
|
await runConfigWizard()
|
|
148
238
|
})
|
|
149
239
|
|
|
240
|
+
// theme 子命令
|
|
241
|
+
const themeCmd = program.command('theme').description('管理主题')
|
|
242
|
+
|
|
243
|
+
themeCmd
|
|
244
|
+
.command('list')
|
|
245
|
+
.description('查看所有可用主题')
|
|
246
|
+
.action(async () => {
|
|
247
|
+
const { themes } = await import('../src/ui/theme.js')
|
|
248
|
+
const config = getConfig()
|
|
249
|
+
const currentTheme = config.theme || 'dark'
|
|
250
|
+
|
|
251
|
+
console.log('')
|
|
252
|
+
console2.title('🎨 可用主题:')
|
|
253
|
+
console2.muted('━'.repeat(50))
|
|
254
|
+
|
|
255
|
+
Object.keys(themes).forEach((themeName) => {
|
|
256
|
+
const isCurrent = themeName === currentTheme
|
|
257
|
+
const prefix = isCurrent ? '●' : '○'
|
|
258
|
+
const label = themeName === 'dark' ? 'dark (深色)' : 'light (浅色)'
|
|
259
|
+
const color = themeName === 'dark' ? '#00D9FF' : '#0284C7'
|
|
260
|
+
|
|
261
|
+
if (isCurrent) {
|
|
262
|
+
console.log(` ${chalk.hex(color)(prefix)} ${chalk.hex(color).bold(label)} ${chalk.gray('(当前)')}`)
|
|
263
|
+
} else {
|
|
264
|
+
console.log(` ${chalk.gray(prefix)} ${label}`)
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
console2.muted('━'.repeat(50))
|
|
269
|
+
console.log('')
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
themeCmd
|
|
273
|
+
.argument('[name]', '主题名称 (dark, light)')
|
|
274
|
+
.description('切换主题')
|
|
275
|
+
.action((name?: string) => {
|
|
276
|
+
if (!name) {
|
|
277
|
+
// 显示当前主题
|
|
278
|
+
const config = getConfig()
|
|
279
|
+
const currentTheme = config.theme || 'dark'
|
|
280
|
+
const label = currentTheme === 'dark' ? 'dark (深色)' : 'light (浅色)'
|
|
281
|
+
const color = currentTheme === 'dark' ? '#00D9FF' : '#0284C7'
|
|
282
|
+
|
|
283
|
+
console.log('')
|
|
284
|
+
console.log(`当前主题: ${chalk.hex(color).bold(label)}`)
|
|
285
|
+
console.log('')
|
|
286
|
+
console2.muted('使用 pls theme list 查看所有主题')
|
|
287
|
+
console2.muted('使用 pls theme <name> 切换主题')
|
|
288
|
+
console.log('')
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 切换主题
|
|
293
|
+
try {
|
|
294
|
+
setConfigValue('theme', name)
|
|
295
|
+
const label = name === 'dark' ? 'dark (深色)' : 'light (浅色)'
|
|
296
|
+
const color = name === 'dark' ? '#00D9FF' : '#0284C7'
|
|
297
|
+
|
|
298
|
+
console.log('')
|
|
299
|
+
console2.success(`已切换到 ${chalk.hex(color).bold(label)} 主题`)
|
|
300
|
+
console.log('')
|
|
301
|
+
} catch (error: any) {
|
|
302
|
+
console.log('')
|
|
303
|
+
console2.error(error.message)
|
|
304
|
+
console.log('')
|
|
305
|
+
process.exit(1)
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
150
309
|
// history 子命令
|
|
151
310
|
const historyCmd = program.command('history').description('查看或管理命令历史')
|
|
152
311
|
|
|
@@ -170,16 +329,16 @@ historyCmd
|
|
|
170
329
|
history.forEach((item: any, index: number) => {
|
|
171
330
|
const status = item.executed
|
|
172
331
|
? item.exitCode === 0
|
|
173
|
-
? chalk.hex(
|
|
174
|
-
: chalk.hex(
|
|
332
|
+
? chalk.hex(getThemeColors().success)('✓')
|
|
333
|
+
: chalk.hex(getThemeColors().error)(`✗ 退出码:${item.exitCode}`)
|
|
175
334
|
: chalk.gray('(未执行)')
|
|
176
335
|
|
|
177
|
-
console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex(
|
|
336
|
+
console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex(getThemeColors().primary)(item.userPrompt)}`)
|
|
178
337
|
|
|
179
338
|
// 显示用户修改信息
|
|
180
339
|
if (item.userModified && item.aiGeneratedCommand) {
|
|
181
340
|
console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(item.aiGeneratedCommand)}`)
|
|
182
|
-
console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.
|
|
341
|
+
console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.hex(getThemeColors().warning)('(已修改)')}`)
|
|
183
342
|
} else {
|
|
184
343
|
console.log(` ${chalk.dim('→')} ${item.command} ${status}`)
|
|
185
344
|
}
|
|
@@ -251,16 +410,16 @@ historyCmd.action(() => {
|
|
|
251
410
|
history.forEach((item: any, index: number) => {
|
|
252
411
|
const status = item.executed
|
|
253
412
|
? item.exitCode === 0
|
|
254
|
-
? chalk.hex(
|
|
255
|
-
: chalk.hex(
|
|
413
|
+
? chalk.hex(getThemeColors().success)('✓')
|
|
414
|
+
: chalk.hex(getThemeColors().error)(`✗ 退出码:${item.exitCode}`)
|
|
256
415
|
: chalk.gray('(未执行)')
|
|
257
416
|
|
|
258
|
-
console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex(
|
|
417
|
+
console.log(`\n${chalk.gray(`${index + 1}.`)} ${chalk.hex(getThemeColors().primary)(item.userPrompt)}`)
|
|
259
418
|
|
|
260
419
|
// 显示用户修改信息
|
|
261
420
|
if (item.userModified && item.aiGeneratedCommand) {
|
|
262
421
|
console.log(` ${chalk.dim('AI 生成:')} ${chalk.gray(item.aiGeneratedCommand)}`)
|
|
263
|
-
console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.
|
|
422
|
+
console.log(` ${chalk.dim('用户修改为:')} ${item.command} ${status} ${chalk.hex(getThemeColors().warning)('(已修改)')}`)
|
|
264
423
|
} else {
|
|
265
424
|
console.log(` ${chalk.dim('→')} ${item.command} ${status}`)
|
|
266
425
|
}
|
|
@@ -329,19 +488,19 @@ hookCmd
|
|
|
329
488
|
console.log('')
|
|
330
489
|
console2.title('📊 Shell Hook 状态')
|
|
331
490
|
console2.muted('━'.repeat(40))
|
|
332
|
-
console.log(` ${chalk.hex(
|
|
333
|
-
console.log(` ${chalk.hex(
|
|
491
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('Shell 类型')}: ${status.shellType}`)
|
|
492
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('配置文件')}: ${status.configPath || '未知'}`)
|
|
334
493
|
console.log(
|
|
335
|
-
` ${chalk.hex(
|
|
336
|
-
status.installed ? chalk.hex(
|
|
494
|
+
` ${chalk.hex(getThemeColors().primary)('已安装')}: ${
|
|
495
|
+
status.installed ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
|
|
337
496
|
}`
|
|
338
497
|
)
|
|
339
498
|
console.log(
|
|
340
|
-
` ${chalk.hex(
|
|
341
|
-
status.enabled ? chalk.hex(
|
|
499
|
+
` ${chalk.hex(getThemeColors().primary)('已启用')}: ${
|
|
500
|
+
status.enabled ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
|
|
342
501
|
}`
|
|
343
502
|
)
|
|
344
|
-
console.log(` ${chalk.hex(
|
|
503
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('历史文件')}: ${status.historyFile}`)
|
|
345
504
|
console2.muted('━'.repeat(40))
|
|
346
505
|
|
|
347
506
|
if (!status.installed) {
|
|
@@ -358,19 +517,19 @@ hookCmd.action(() => {
|
|
|
358
517
|
console.log('')
|
|
359
518
|
console2.title('📊 Shell Hook 状态')
|
|
360
519
|
console2.muted('━'.repeat(40))
|
|
361
|
-
console.log(` ${chalk.hex(
|
|
362
|
-
console.log(` ${chalk.hex(
|
|
520
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('Shell 类型')}: ${status.shellType}`)
|
|
521
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('配置文件')}: ${status.configPath || '未知'}`)
|
|
363
522
|
console.log(
|
|
364
|
-
` ${chalk.hex(
|
|
365
|
-
status.installed ? chalk.hex(
|
|
523
|
+
` ${chalk.hex(getThemeColors().primary)('已安装')}: ${
|
|
524
|
+
status.installed ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
|
|
366
525
|
}`
|
|
367
526
|
)
|
|
368
527
|
console.log(
|
|
369
|
-
` ${chalk.hex(
|
|
370
|
-
status.enabled ? chalk.hex(
|
|
528
|
+
` ${chalk.hex(getThemeColors().primary)('已启用')}: ${
|
|
529
|
+
status.enabled ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
|
|
371
530
|
}`
|
|
372
531
|
)
|
|
373
|
-
console.log(` ${chalk.hex(
|
|
532
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('历史文件')}: ${status.historyFile}`)
|
|
374
533
|
console2.muted('━'.repeat(40))
|
|
375
534
|
|
|
376
535
|
if (!status.installed) {
|
|
@@ -380,6 +539,429 @@ hookCmd.action(() => {
|
|
|
380
539
|
console.log('')
|
|
381
540
|
})
|
|
382
541
|
|
|
542
|
+
// upgrade 子命令
|
|
543
|
+
program
|
|
544
|
+
.command('upgrade')
|
|
545
|
+
.description('升级到最新版本')
|
|
546
|
+
.action(async () => {
|
|
547
|
+
const success = await performUpgrade(packageJson.version)
|
|
548
|
+
process.exit(success ? 0 : 1)
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
// alias 子命令
|
|
552
|
+
const aliasCmd = program.command('alias').description('管理命令别名')
|
|
553
|
+
|
|
554
|
+
// 获取所有子命令名称(用于检测冲突)
|
|
555
|
+
function getReservedCommands(): string[] {
|
|
556
|
+
return program.commands.map((cmd) => cmd.name())
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
aliasCmd
|
|
560
|
+
.command('list')
|
|
561
|
+
.description('列出所有别名')
|
|
562
|
+
.action(() => {
|
|
563
|
+
displayAliases()
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
aliasCmd
|
|
567
|
+
.command('add <name> <prompt>')
|
|
568
|
+
.description('添加别名(prompt 支持 {{param}} 或 {{param:default}} 参数模板)')
|
|
569
|
+
.option('-d, --description <desc>', '别名描述')
|
|
570
|
+
.action((name, prompt, options) => {
|
|
571
|
+
try {
|
|
572
|
+
addAlias(name, prompt, options.description, getReservedCommands())
|
|
573
|
+
console.log('')
|
|
574
|
+
console2.success(`已添加别名: ${name}`)
|
|
575
|
+
console.log(` ${chalk.gray('→')} ${prompt}`)
|
|
576
|
+
console.log('')
|
|
577
|
+
} catch (error: any) {
|
|
578
|
+
console.log('')
|
|
579
|
+
console2.error(error.message)
|
|
580
|
+
console.log('')
|
|
581
|
+
process.exit(1)
|
|
582
|
+
}
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
aliasCmd
|
|
586
|
+
.command('remove <name>')
|
|
587
|
+
.description('删除别名')
|
|
588
|
+
.action((name) => {
|
|
589
|
+
const removed = removeAlias(name)
|
|
590
|
+
console.log('')
|
|
591
|
+
if (removed) {
|
|
592
|
+
console2.success(`已删除别名: ${name}`)
|
|
593
|
+
} else {
|
|
594
|
+
console2.error(`别名不存在: ${name}`)
|
|
595
|
+
}
|
|
596
|
+
console.log('')
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
// 默认 alias 命令(显示列表)
|
|
600
|
+
aliasCmd.action(() => {
|
|
601
|
+
displayAliases()
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
// remote 子命令
|
|
605
|
+
const remoteCmd = program.command('remote').description('管理远程服务器')
|
|
606
|
+
|
|
607
|
+
remoteCmd
|
|
608
|
+
.command('list')
|
|
609
|
+
.description('列出所有远程服务器')
|
|
610
|
+
.action(() => {
|
|
611
|
+
displayRemotes()
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
remoteCmd
|
|
615
|
+
.command('add <name> <host>')
|
|
616
|
+
.description('添加远程服务器(格式: user@host 或 user@host:port)')
|
|
617
|
+
.option('-k, --key <path>', 'SSH 私钥路径')
|
|
618
|
+
.option('-p, --password', '使用密码认证(每次执行时输入)')
|
|
619
|
+
.action((name, host, options) => {
|
|
620
|
+
try {
|
|
621
|
+
addRemote(name, host, { key: options.key, password: options.password })
|
|
622
|
+
console.log('')
|
|
623
|
+
console2.success(`已添加远程服务器: ${name}`)
|
|
624
|
+
console.log(` ${chalk.gray('→')} ${host}`)
|
|
625
|
+
if (options.key) {
|
|
626
|
+
console.log(` ${chalk.gray('密钥:')} ${options.key}`)
|
|
627
|
+
}
|
|
628
|
+
if (options.password) {
|
|
629
|
+
console.log(` ${chalk.gray('认证:')} 密码(每次执行时输入)`)
|
|
630
|
+
}
|
|
631
|
+
console.log('')
|
|
632
|
+
} catch (error: any) {
|
|
633
|
+
console.log('')
|
|
634
|
+
console2.error(error.message)
|
|
635
|
+
console.log('')
|
|
636
|
+
process.exit(1)
|
|
637
|
+
}
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
remoteCmd
|
|
641
|
+
.command('remove <name>')
|
|
642
|
+
.description('删除远程服务器')
|
|
643
|
+
.action((name) => {
|
|
644
|
+
const removed = removeRemote(name)
|
|
645
|
+
console.log('')
|
|
646
|
+
if (removed) {
|
|
647
|
+
console2.success(`已删除远程服务器: ${name}`)
|
|
648
|
+
} else {
|
|
649
|
+
console2.error(`远程服务器不存在: ${name}`)
|
|
650
|
+
}
|
|
651
|
+
console.log('')
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
remoteCmd
|
|
655
|
+
.command('test <name>')
|
|
656
|
+
.description('测试远程服务器连接')
|
|
657
|
+
.action(async (name) => {
|
|
658
|
+
const remote = getRemote(name)
|
|
659
|
+
if (!remote) {
|
|
660
|
+
console.log('')
|
|
661
|
+
console2.error(`远程服务器不存在: ${name}`)
|
|
662
|
+
console.log('')
|
|
663
|
+
process.exit(1)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
console.log('')
|
|
667
|
+
console2.info(`正在测试连接 ${name} (${remote.user}@${remote.host}:${remote.port})...`)
|
|
668
|
+
|
|
669
|
+
const result = await testRemoteConnection(name)
|
|
670
|
+
console.log(` ${result.message}`)
|
|
671
|
+
|
|
672
|
+
if (result.success) {
|
|
673
|
+
// 采集系统信息
|
|
674
|
+
console2.info('正在采集系统信息...')
|
|
675
|
+
try {
|
|
676
|
+
const sysInfo = await collectRemoteSysInfo(name, true)
|
|
677
|
+
console.log(` ${chalk.gray('系统:')} ${sysInfo.os} ${sysInfo.osVersion}`)
|
|
678
|
+
console.log(` ${chalk.gray('Shell:')} ${sysInfo.shell}`)
|
|
679
|
+
console.log(` ${chalk.gray('主机名:')} ${sysInfo.hostname}`)
|
|
680
|
+
} catch (error: any) {
|
|
681
|
+
console2.warning(`无法采集系统信息: ${error.message}`)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
console.log('')
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
// remote hook 子命令
|
|
688
|
+
const remoteHookCmd = remoteCmd.command('hook').description('管理远程服务器 Shell Hook')
|
|
689
|
+
|
|
690
|
+
remoteHookCmd
|
|
691
|
+
.command('install <name>')
|
|
692
|
+
.description('在远程服务器安装 Shell Hook')
|
|
693
|
+
.action(async (name) => {
|
|
694
|
+
const remote = getRemote(name)
|
|
695
|
+
if (!remote) {
|
|
696
|
+
console.log('')
|
|
697
|
+
console2.error(`远程服务器不存在: ${name}`)
|
|
698
|
+
console.log('')
|
|
699
|
+
process.exit(1)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
console.log('')
|
|
703
|
+
console2.title('🔧 远程 Shell Hook 安装')
|
|
704
|
+
console2.muted('━'.repeat(40))
|
|
705
|
+
console2.info(`目标服务器: ${name} (${remote.user}@${remote.host})`)
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
// 检测远程 shell 类型
|
|
709
|
+
const sshExecFn = async (cmd: string) => {
|
|
710
|
+
const result = await sshExec(name, cmd, { timeout: 30000 })
|
|
711
|
+
return { stdout: result.stdout, exitCode: result.exitCode }
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const shellType = await detectRemoteShell(sshExecFn)
|
|
715
|
+
const configPath = getRemoteShellConfigPath(shellType)
|
|
716
|
+
console2.muted(`检测到 Shell: ${shellType}`)
|
|
717
|
+
console2.muted(`配置文件: ${configPath}`)
|
|
718
|
+
console.log('')
|
|
719
|
+
|
|
720
|
+
const result = await installRemoteShellHook(sshExecFn, shellType)
|
|
721
|
+
console.log(` ${result.message}`)
|
|
722
|
+
|
|
723
|
+
if (result.success) {
|
|
724
|
+
console.log('')
|
|
725
|
+
console2.warning('⚠️ 请在远程服务器重启终端或执行:')
|
|
726
|
+
console2.info(` source ${configPath}`)
|
|
727
|
+
}
|
|
728
|
+
} catch (error: any) {
|
|
729
|
+
console2.error(`安装失败: ${error.message}`)
|
|
730
|
+
}
|
|
731
|
+
console.log('')
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
remoteHookCmd
|
|
735
|
+
.command('uninstall <name>')
|
|
736
|
+
.description('从远程服务器卸载 Shell Hook')
|
|
737
|
+
.action(async (name) => {
|
|
738
|
+
const remote = getRemote(name)
|
|
739
|
+
if (!remote) {
|
|
740
|
+
console.log('')
|
|
741
|
+
console2.error(`远程服务器不存在: ${name}`)
|
|
742
|
+
console.log('')
|
|
743
|
+
process.exit(1)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
console.log('')
|
|
747
|
+
console2.info(`正在从 ${name} 卸载 Shell Hook...`)
|
|
748
|
+
|
|
749
|
+
try {
|
|
750
|
+
const sshExecFn = async (cmd: string) => {
|
|
751
|
+
const result = await sshExec(name, cmd, { timeout: 30000 })
|
|
752
|
+
return { stdout: result.stdout, exitCode: result.exitCode }
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const shellType = await detectRemoteShell(sshExecFn)
|
|
756
|
+
const result = await uninstallRemoteShellHook(sshExecFn, shellType)
|
|
757
|
+
console.log(` ${result.message}`)
|
|
758
|
+
|
|
759
|
+
if (result.success) {
|
|
760
|
+
console.log('')
|
|
761
|
+
console2.warning('⚠️ 请在远程服务器重启终端使其生效')
|
|
762
|
+
}
|
|
763
|
+
} catch (error: any) {
|
|
764
|
+
console2.error(`卸载失败: ${error.message}`)
|
|
765
|
+
}
|
|
766
|
+
console.log('')
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
remoteHookCmd
|
|
770
|
+
.command('status <name>')
|
|
771
|
+
.description('查看远程服务器 Shell Hook 状态')
|
|
772
|
+
.action(async (name) => {
|
|
773
|
+
const remote = getRemote(name)
|
|
774
|
+
if (!remote) {
|
|
775
|
+
console.log('')
|
|
776
|
+
console2.error(`远程服务器不存在: ${name}`)
|
|
777
|
+
console.log('')
|
|
778
|
+
process.exit(1)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
console.log('')
|
|
782
|
+
console2.info(`正在检查 ${name} 的 Hook 状态...`)
|
|
783
|
+
|
|
784
|
+
try {
|
|
785
|
+
const sshExecFn = async (cmd: string) => {
|
|
786
|
+
const result = await sshExec(name, cmd, { timeout: 30000 })
|
|
787
|
+
return { stdout: result.stdout, exitCode: result.exitCode }
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const status = await getRemoteHookStatus(sshExecFn)
|
|
791
|
+
|
|
792
|
+
console.log('')
|
|
793
|
+
console2.title(`📊 远程 Shell Hook 状态 - ${name}`)
|
|
794
|
+
console2.muted('━'.repeat(40))
|
|
795
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('Shell 类型')}: ${status.shellType}`)
|
|
796
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('配置文件')}: ${status.configPath}`)
|
|
797
|
+
console.log(
|
|
798
|
+
` ${chalk.hex(getThemeColors().primary)('已安装')}: ${
|
|
799
|
+
status.installed ? chalk.hex(getThemeColors().success)('是') : chalk.gray('否')
|
|
800
|
+
}`
|
|
801
|
+
)
|
|
802
|
+
console2.muted('━'.repeat(40))
|
|
803
|
+
|
|
804
|
+
if (!status.installed) {
|
|
805
|
+
console.log('')
|
|
806
|
+
console2.muted(`提示: 运行 pls remote hook install ${name} 安装 Shell Hook`)
|
|
807
|
+
}
|
|
808
|
+
} catch (error: any) {
|
|
809
|
+
console2.error(`检查失败: ${error.message}`)
|
|
810
|
+
}
|
|
811
|
+
console.log('')
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
// remote history 子命令
|
|
815
|
+
const remoteHistoryCmd = remoteCmd.command('history').description('管理远程服务器历史记录')
|
|
816
|
+
|
|
817
|
+
remoteHistoryCmd
|
|
818
|
+
.command('show <name>')
|
|
819
|
+
.description('显示远程服务器命令历史')
|
|
820
|
+
.action((name) => {
|
|
821
|
+
displayRemoteHistory(name)
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
remoteHistoryCmd
|
|
825
|
+
.command('clear <name>')
|
|
826
|
+
.description('清空远程服务器命令历史')
|
|
827
|
+
.action((name) => {
|
|
828
|
+
clearRemoteHistory(name)
|
|
829
|
+
console.log('')
|
|
830
|
+
console2.success(`已清空服务器 "${name}" 的命令历史`)
|
|
831
|
+
console.log('')
|
|
832
|
+
})
|
|
833
|
+
|
|
834
|
+
remoteHistoryCmd
|
|
835
|
+
.command('shell <name>')
|
|
836
|
+
.description('显示远程服务器 Shell 历史')
|
|
837
|
+
.action(async (name) => {
|
|
838
|
+
await displayRemoteShellHistory(name)
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
remoteHistoryCmd
|
|
842
|
+
.command('shell-clear <name>')
|
|
843
|
+
.description('清空远程服务器 Shell 历史')
|
|
844
|
+
.action(async (name) => {
|
|
845
|
+
await clearRemoteShellHistory(name)
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
// remote default 子命令
|
|
849
|
+
remoteCmd
|
|
850
|
+
.command('default [name]')
|
|
851
|
+
.description('设置或查看默认远程服务器')
|
|
852
|
+
.option('-c, --clear', '清除默认服务器设置')
|
|
853
|
+
.action((name?: string, options?: { clear?: boolean }) => {
|
|
854
|
+
const config = getConfig()
|
|
855
|
+
|
|
856
|
+
// 清除默认
|
|
857
|
+
if (options?.clear) {
|
|
858
|
+
if (config.defaultRemote) {
|
|
859
|
+
setConfigValue('defaultRemote', '')
|
|
860
|
+
console.log('')
|
|
861
|
+
console2.success('已清除默认远程服务器')
|
|
862
|
+
console.log('')
|
|
863
|
+
} else {
|
|
864
|
+
console.log('')
|
|
865
|
+
console2.muted('当前没有设置默认远程服务器')
|
|
866
|
+
console.log('')
|
|
867
|
+
}
|
|
868
|
+
return
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// 查看默认
|
|
872
|
+
if (!name) {
|
|
873
|
+
console.log('')
|
|
874
|
+
if (config.defaultRemote) {
|
|
875
|
+
const remote = getRemote(config.defaultRemote)
|
|
876
|
+
if (remote) {
|
|
877
|
+
console.log(`默认远程服务器: ${chalk.hex(getThemeColors().primary)(config.defaultRemote)}`)
|
|
878
|
+
console.log(` ${chalk.gray('→')} ${remote.user}@${remote.host}:${remote.port}`)
|
|
879
|
+
} else {
|
|
880
|
+
console2.warning(`默认服务器 "${config.defaultRemote}" 不存在,建议清除设置`)
|
|
881
|
+
console2.muted('运行 pls remote default --clear 清除')
|
|
882
|
+
}
|
|
883
|
+
} else {
|
|
884
|
+
console2.muted('当前没有设置默认远程服务器')
|
|
885
|
+
console2.muted('使用 pls remote default <name> 设置默认服务器')
|
|
886
|
+
}
|
|
887
|
+
console.log('')
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// 设置默认
|
|
892
|
+
const remote = getRemote(name)
|
|
893
|
+
if (!remote) {
|
|
894
|
+
console.log('')
|
|
895
|
+
console2.error(`远程服务器不存在: ${name}`)
|
|
896
|
+
console2.muted('使用 pls remote list 查看所有服务器')
|
|
897
|
+
console.log('')
|
|
898
|
+
process.exit(1)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
setConfigValue('defaultRemote', name)
|
|
902
|
+
console.log('')
|
|
903
|
+
console2.success(`已设置默认远程服务器: ${name}`)
|
|
904
|
+
console.log(` ${chalk.gray('→')} ${remote.user}@${remote.host}:${remote.port}`)
|
|
905
|
+
console2.muted('现在可以使用 pls -r <prompt> 直接在该服务器执行')
|
|
906
|
+
console.log('')
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
// remote workdir 子命令
|
|
910
|
+
remoteCmd
|
|
911
|
+
.command('workdir <name> [path]')
|
|
912
|
+
.description('设置或查看远程服务器的工作目录')
|
|
913
|
+
.option('-c, --clear', '清除工作目录设置')
|
|
914
|
+
.action((name: string, workdirPath?: string, options?: { clear?: boolean }) => {
|
|
915
|
+
const remote = getRemote(name)
|
|
916
|
+
if (!remote) {
|
|
917
|
+
console.log('')
|
|
918
|
+
console2.error(`远程服务器不存在: ${name}`)
|
|
919
|
+
console.log('')
|
|
920
|
+
process.exit(1)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// 清除工作目录
|
|
924
|
+
if (options?.clear) {
|
|
925
|
+
if (remote.workDir) {
|
|
926
|
+
setRemoteWorkDir(name, '-')
|
|
927
|
+
console.log('')
|
|
928
|
+
console2.success(`已清除 ${name} 的工作目录设置`)
|
|
929
|
+
console.log('')
|
|
930
|
+
} else {
|
|
931
|
+
console.log('')
|
|
932
|
+
console2.muted(`${name} 没有设置工作目录`)
|
|
933
|
+
console.log('')
|
|
934
|
+
}
|
|
935
|
+
return
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// 查看工作目录
|
|
939
|
+
if (!workdirPath) {
|
|
940
|
+
console.log('')
|
|
941
|
+
if (remote.workDir) {
|
|
942
|
+
console.log(`${chalk.hex(getThemeColors().primary)(name)} 的工作目录:`)
|
|
943
|
+
console.log(` ${chalk.gray('→')} ${remote.workDir}`)
|
|
944
|
+
} else {
|
|
945
|
+
console2.muted(`${name} 没有设置工作目录`)
|
|
946
|
+
console2.muted(`使用 pls remote workdir ${name} <path> 设置工作目录`)
|
|
947
|
+
}
|
|
948
|
+
console.log('')
|
|
949
|
+
return
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// 设置工作目录
|
|
953
|
+
setRemoteWorkDir(name, workdirPath)
|
|
954
|
+
console.log('')
|
|
955
|
+
console2.success(`已设置 ${name} 的工作目录: ${workdirPath}`)
|
|
956
|
+
console2.muted('现在在该服务器执行的命令会自动切换到此目录')
|
|
957
|
+
console.log('')
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
// 默认 remote 命令(显示列表)
|
|
961
|
+
remoteCmd.action(() => {
|
|
962
|
+
displayRemotes()
|
|
963
|
+
})
|
|
964
|
+
|
|
383
965
|
// chat 子命令
|
|
384
966
|
const chatCmd = program.command('chat').description('AI 对话模式,问答、讲解命令')
|
|
385
967
|
|
|
@@ -408,8 +990,8 @@ chatCmd
|
|
|
408
990
|
console.log('')
|
|
409
991
|
console2.title('💬 AI 对话模式')
|
|
410
992
|
console2.muted('━'.repeat(40))
|
|
411
|
-
console.log(` ${chalk.hex(
|
|
412
|
-
console.log(` ${chalk.hex(
|
|
993
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('当前对话轮数')}: ${roundCount}`)
|
|
994
|
+
console.log(` ${chalk.hex(getThemeColors().primary)('历史文件')}: ${historyFile}`)
|
|
413
995
|
console2.muted('━'.repeat(40))
|
|
414
996
|
console.log('')
|
|
415
997
|
console2.muted('用法:')
|
|
@@ -428,28 +1010,42 @@ chatCmd
|
|
|
428
1010
|
process.exit(1)
|
|
429
1011
|
}
|
|
430
1012
|
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
1013
|
+
// 懒加载 Chat 组件(避免启动时加载 React/Ink)
|
|
1014
|
+
;(async () => {
|
|
1015
|
+
const React = await import('react')
|
|
1016
|
+
const { render } = await import('ink')
|
|
1017
|
+
const { Chat } = await import('../src/components/Chat.js')
|
|
1018
|
+
|
|
1019
|
+
render(
|
|
1020
|
+
React.createElement(Chat, {
|
|
1021
|
+
prompt,
|
|
1022
|
+
debug: options.debug,
|
|
1023
|
+
showRoundCount: true,
|
|
1024
|
+
onComplete: () => process.exit(0),
|
|
1025
|
+
})
|
|
1026
|
+
)
|
|
1027
|
+
})()
|
|
440
1028
|
})
|
|
441
1029
|
|
|
442
1030
|
// 默认命令(执行 prompt)
|
|
443
1031
|
program
|
|
444
1032
|
.argument('[prompt...]', '自然语言描述你想执行的操作')
|
|
445
1033
|
.option('-d, --debug', '显示调试信息(系统信息、完整 prompt 等)')
|
|
1034
|
+
.option('-r, --remote [name]', '在远程服务器上执行(不指定则使用默认服务器)')
|
|
446
1035
|
.action((promptArgs, options) => {
|
|
1036
|
+
// 智能处理 -r 参数:如果 -r 后面的值不是已注册的服务器名,把它当作 prompt 的一部分
|
|
1037
|
+
if (typeof options.remote === 'string' && !getRemote(options.remote)) {
|
|
1038
|
+
// "查看当前目录" 不是服务器名,放回 prompt
|
|
1039
|
+
promptArgs.unshift(options.remote)
|
|
1040
|
+
options.remote = true // 改为使用默认服务器
|
|
1041
|
+
}
|
|
1042
|
+
|
|
447
1043
|
if (promptArgs.length === 0) {
|
|
448
1044
|
program.help()
|
|
449
1045
|
return
|
|
450
1046
|
}
|
|
451
1047
|
|
|
452
|
-
|
|
1048
|
+
let prompt = promptArgs.join(' ')
|
|
453
1049
|
|
|
454
1050
|
if (!prompt.trim()) {
|
|
455
1051
|
console.log('')
|
|
@@ -459,6 +1055,23 @@ program
|
|
|
459
1055
|
process.exit(1)
|
|
460
1056
|
}
|
|
461
1057
|
|
|
1058
|
+
// 尝试解析别名(支持 pls disk 和 pls @disk 两种格式)
|
|
1059
|
+
try {
|
|
1060
|
+
const aliasResult = resolveAlias(prompt)
|
|
1061
|
+
if (aliasResult.resolved) {
|
|
1062
|
+
prompt = aliasResult.prompt
|
|
1063
|
+
if (options.debug) {
|
|
1064
|
+
console.log('')
|
|
1065
|
+
console2.muted(`别名解析: ${aliasResult.aliasName} → ${prompt}`)
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
} catch (error: any) {
|
|
1069
|
+
console.log('')
|
|
1070
|
+
console2.error(error.message)
|
|
1071
|
+
console.log('')
|
|
1072
|
+
process.exit(1)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
462
1075
|
// 检查配置
|
|
463
1076
|
if (!isConfigValid()) {
|
|
464
1077
|
console.log('')
|
|
@@ -468,8 +1081,190 @@ program
|
|
|
468
1081
|
process.exit(1)
|
|
469
1082
|
}
|
|
470
1083
|
|
|
471
|
-
//
|
|
1084
|
+
// 解析远程服务器名称
|
|
1085
|
+
// options.remote 可能是:
|
|
1086
|
+
// - undefined: 没有使用 -r
|
|
1087
|
+
// - true: 使用了 -r 但没有指定名称(使用默认)
|
|
1088
|
+
// - string: 使用了 -r 并指定了名称(支持逗号分隔的多个服务器)
|
|
1089
|
+
let remoteName: string | undefined
|
|
1090
|
+
let remoteNames: string[] | undefined // 批量执行时的服务器列表
|
|
1091
|
+
if (options.remote !== undefined) {
|
|
1092
|
+
if (options.remote === true) {
|
|
1093
|
+
// 使用默认服务器
|
|
1094
|
+
const config = getConfig()
|
|
1095
|
+
if (!config.defaultRemote) {
|
|
1096
|
+
console.log('')
|
|
1097
|
+
console2.error('未设置默认远程服务器')
|
|
1098
|
+
console2.muted('使用 pls remote default <name> 设置默认服务器')
|
|
1099
|
+
console2.muted('或使用 pls -r <name> <prompt> 指定服务器')
|
|
1100
|
+
console.log('')
|
|
1101
|
+
process.exit(1)
|
|
1102
|
+
}
|
|
1103
|
+
remoteName = config.defaultRemote
|
|
1104
|
+
} else {
|
|
1105
|
+
// 检查是否为批量执行(逗号分隔的服务器名)
|
|
1106
|
+
if (options.remote.includes(',')) {
|
|
1107
|
+
remoteNames = options.remote.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
1108
|
+
|
|
1109
|
+
// 验证所有服务器是否存在
|
|
1110
|
+
const invalidServers = remoteNames!.filter(name => !getRemote(name))
|
|
1111
|
+
if (invalidServers.length > 0) {
|
|
1112
|
+
console.log('')
|
|
1113
|
+
console2.error(`以下服务器不存在: ${invalidServers.join(', ')}`)
|
|
1114
|
+
console2.muted('使用 pls remote list 查看所有服务器')
|
|
1115
|
+
console2.muted('使用 pls remote add <name> <user@host> 添加服务器')
|
|
1116
|
+
console.log('')
|
|
1117
|
+
process.exit(1)
|
|
1118
|
+
}
|
|
1119
|
+
} else {
|
|
1120
|
+
remoteName = options.remote
|
|
1121
|
+
|
|
1122
|
+
// 检查服务器是否存在
|
|
1123
|
+
const remote = getRemote(remoteName!)
|
|
1124
|
+
if (!remote) {
|
|
1125
|
+
console.log('')
|
|
1126
|
+
console2.error(`远程服务器不存在: ${remoteName}`)
|
|
1127
|
+
console2.muted('使用 pls remote add <name> <user@host> 添加服务器')
|
|
1128
|
+
console.log('')
|
|
1129
|
+
process.exit(1)
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// 懒加载 MultiStepCommandGenerator 组件(避免启动时加载 React/Ink)
|
|
472
1136
|
;(async () => {
|
|
1137
|
+
// 批量远程执行模式
|
|
1138
|
+
if (remoteNames && remoteNames.length > 0) {
|
|
1139
|
+
console.log('')
|
|
1140
|
+
console2.info(`正在为 ${remoteNames.length} 台服务器生成命令...`)
|
|
1141
|
+
console.log('')
|
|
1142
|
+
|
|
1143
|
+
try {
|
|
1144
|
+
// 1. 并发生成命令
|
|
1145
|
+
const commands = await generateBatchRemoteCommands(remoteNames, prompt, { debug: options.debug })
|
|
1146
|
+
|
|
1147
|
+
// 2. 显示生成的命令
|
|
1148
|
+
console2.success('✓ 命令生成完成\n')
|
|
1149
|
+
const theme = getCurrentTheme()
|
|
1150
|
+
commands.forEach(({ server, command, sysInfo }) => {
|
|
1151
|
+
console.log(chalk.hex(theme.primary)(`${server}`) + chalk.gray(` (${sysInfo.os}):`))
|
|
1152
|
+
console.log(chalk.hex(theme.secondary)(` ${command}`))
|
|
1153
|
+
})
|
|
1154
|
+
console.log('')
|
|
1155
|
+
|
|
1156
|
+
// 3. 询问用户确认
|
|
1157
|
+
const readline = await import('readline')
|
|
1158
|
+
const rl = readline.createInterface({
|
|
1159
|
+
input: process.stdin,
|
|
1160
|
+
output: process.stdout,
|
|
1161
|
+
})
|
|
1162
|
+
|
|
1163
|
+
const confirmed = await new Promise<boolean>((resolve) => {
|
|
1164
|
+
console.log(chalk.gray(`将在 ${remoteNames!.length} 台服务器执行以上命令`))
|
|
1165
|
+
rl.question(chalk.gray('执行? [回车执行 / Ctrl+C 取消] '), (answer) => {
|
|
1166
|
+
rl.close()
|
|
1167
|
+
resolve(true)
|
|
1168
|
+
})
|
|
1169
|
+
})
|
|
1170
|
+
|
|
1171
|
+
if (!confirmed) {
|
|
1172
|
+
console.log('')
|
|
1173
|
+
console2.muted('已取消执行')
|
|
1174
|
+
console.log('')
|
|
1175
|
+
process.exit(0)
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// 4. 并发执行
|
|
1179
|
+
console.log('')
|
|
1180
|
+
console2.info('正在执行...')
|
|
1181
|
+
const results = await executeBatchRemoteCommands(commands)
|
|
1182
|
+
|
|
1183
|
+
// 5. 显示执行结果摘要
|
|
1184
|
+
console.log('')
|
|
1185
|
+
console2.info('执行完成:\n')
|
|
1186
|
+
results.forEach(({ server, exitCode }) => {
|
|
1187
|
+
const icon = exitCode === 0 ? '✓' : '✗'
|
|
1188
|
+
const color = exitCode === 0 ? theme.success : theme.error
|
|
1189
|
+
console.log(` ${chalk.hex(color)(icon)} ${server} ${chalk.gray(`(退出码: ${exitCode})`)}`)
|
|
1190
|
+
})
|
|
1191
|
+
|
|
1192
|
+
// 6. 显示每个服务器的详细输出
|
|
1193
|
+
console.log('')
|
|
1194
|
+
results.forEach(({ server, output }) => {
|
|
1195
|
+
console.log(chalk.hex(theme.primary)(`─── ${server} ───`))
|
|
1196
|
+
console.log(output || chalk.gray('(无输出)'))
|
|
1197
|
+
})
|
|
1198
|
+
|
|
1199
|
+
// 7. 记录到历史
|
|
1200
|
+
results.forEach(({ server, command, exitCode, output }) => {
|
|
1201
|
+
addRemoteHistory(server, {
|
|
1202
|
+
userPrompt: prompt,
|
|
1203
|
+
command,
|
|
1204
|
+
aiGeneratedCommand: command, // 批量执行无编辑功能
|
|
1205
|
+
userModified: false,
|
|
1206
|
+
executed: true,
|
|
1207
|
+
exitCode,
|
|
1208
|
+
output,
|
|
1209
|
+
})
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1212
|
+
// 8. 根据结果决定退出码
|
|
1213
|
+
const allSuccess = results.every(r => r.exitCode === 0)
|
|
1214
|
+
const allFailed = results.every(r => r.exitCode !== 0)
|
|
1215
|
+
if (allFailed) {
|
|
1216
|
+
process.exit(2) // 全部失败
|
|
1217
|
+
} else if (!allSuccess) {
|
|
1218
|
+
process.exit(1) // 部分失败
|
|
1219
|
+
}
|
|
1220
|
+
process.exit(0) // 全部成功
|
|
1221
|
+
} catch (error: any) {
|
|
1222
|
+
console.log('')
|
|
1223
|
+
console2.error(`批量执行失败: ${error.message}`)
|
|
1224
|
+
console.log('')
|
|
1225
|
+
process.exit(1)
|
|
1226
|
+
}
|
|
1227
|
+
return
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// 单服务器执行模式
|
|
1231
|
+
const React = await import('react')
|
|
1232
|
+
const { render } = await import('ink')
|
|
1233
|
+
const { MultiStepCommandGenerator } = await import('../src/components/MultiStepCommandGenerator.js')
|
|
1234
|
+
|
|
1235
|
+
// 如果是远程模式,先获取远程上下文
|
|
1236
|
+
let remoteContext: {
|
|
1237
|
+
name: string
|
|
1238
|
+
sysInfo: Awaited<ReturnType<typeof collectRemoteSysInfo>>
|
|
1239
|
+
shellHistory: Awaited<ReturnType<typeof fetchRemoteShellHistory>>
|
|
1240
|
+
} | null = null
|
|
1241
|
+
|
|
1242
|
+
if (remoteName) {
|
|
1243
|
+
console.log('')
|
|
1244
|
+
console2.info(`正在连接远程服务器 ${remoteName}...`)
|
|
1245
|
+
|
|
1246
|
+
try {
|
|
1247
|
+
// 采集系统信息(使用缓存)
|
|
1248
|
+
const sysInfo = await collectRemoteSysInfo(remoteName)
|
|
1249
|
+
if (options.debug) {
|
|
1250
|
+
console2.muted(`系统: ${sysInfo.os} ${sysInfo.osVersion} (${sysInfo.shell})`)
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// 获取远程 shell 历史
|
|
1254
|
+
const shellHistory = await fetchRemoteShellHistory(remoteName)
|
|
1255
|
+
if (options.debug && shellHistory.length > 0) {
|
|
1256
|
+
console2.muted(`Shell 历史: ${shellHistory.length} 条`)
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
remoteContext = { name: remoteName, sysInfo, shellHistory }
|
|
1260
|
+
console2.success(`已连接到 ${remoteName}`)
|
|
1261
|
+
} catch (error: any) {
|
|
1262
|
+
console2.error(`无法连接到 ${remoteName}: ${error.message}`)
|
|
1263
|
+
console.log('')
|
|
1264
|
+
process.exit(1)
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
473
1268
|
const executedSteps: ExecutedStep[] = []
|
|
474
1269
|
let currentStepNumber = 1
|
|
475
1270
|
let lastStepFailed = false // 跟踪上一步是否失败
|
|
@@ -479,16 +1274,22 @@ program
|
|
|
479
1274
|
|
|
480
1275
|
// 使用 Ink 渲染命令生成
|
|
481
1276
|
const { waitUntilExit, unmount } = render(
|
|
482
|
-
|
|
483
|
-
prompt
|
|
484
|
-
debug
|
|
485
|
-
previousSteps
|
|
486
|
-
currentStepNumber
|
|
487
|
-
|
|
1277
|
+
React.createElement(MultiStepCommandGenerator, {
|
|
1278
|
+
prompt,
|
|
1279
|
+
debug: options.debug,
|
|
1280
|
+
previousSteps: executedSteps,
|
|
1281
|
+
currentStepNumber,
|
|
1282
|
+
remoteContext: remoteContext ? {
|
|
1283
|
+
name: remoteContext.name,
|
|
1284
|
+
sysInfo: remoteContext.sysInfo,
|
|
1285
|
+
shellHistory: remoteContext.shellHistory,
|
|
1286
|
+
} : undefined,
|
|
1287
|
+
isRemote: !!remoteName, // 远程执行时不检测 builtin
|
|
1288
|
+
onStepComplete: (res: any) => {
|
|
488
1289
|
stepResult = res
|
|
489
1290
|
unmount()
|
|
490
|
-
}
|
|
491
|
-
|
|
1291
|
+
},
|
|
1292
|
+
})
|
|
492
1293
|
)
|
|
493
1294
|
|
|
494
1295
|
await waitUntilExit()
|
|
@@ -503,16 +1304,30 @@ program
|
|
|
503
1304
|
}
|
|
504
1305
|
|
|
505
1306
|
if (stepResult.hasBuiltin) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1307
|
+
// 远程模式记录到远程历史
|
|
1308
|
+
if (remoteName) {
|
|
1309
|
+
addRemoteHistory(remoteName, {
|
|
1310
|
+
userPrompt: currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${prompt}`,
|
|
1311
|
+
command: stepResult.command,
|
|
1312
|
+
aiGeneratedCommand: stepResult.aiGeneratedCommand,
|
|
1313
|
+
userModified: stepResult.userModified || false,
|
|
1314
|
+
executed: false,
|
|
1315
|
+
exitCode: null,
|
|
1316
|
+
output: '',
|
|
1317
|
+
reason: 'builtin',
|
|
1318
|
+
})
|
|
1319
|
+
} else {
|
|
1320
|
+
addHistory({
|
|
1321
|
+
userPrompt: currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${prompt}`,
|
|
1322
|
+
command: stepResult.command,
|
|
1323
|
+
aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
|
|
1324
|
+
userModified: stepResult.userModified || false,
|
|
1325
|
+
executed: false,
|
|
1326
|
+
exitCode: null,
|
|
1327
|
+
output: '',
|
|
1328
|
+
reason: 'builtin',
|
|
1329
|
+
})
|
|
1330
|
+
}
|
|
516
1331
|
process.exit(0)
|
|
517
1332
|
}
|
|
518
1333
|
|
|
@@ -543,11 +1358,34 @@ program
|
|
|
543
1358
|
process.exit(1)
|
|
544
1359
|
}
|
|
545
1360
|
|
|
546
|
-
//
|
|
1361
|
+
// 执行命令(本地或远程)
|
|
547
1362
|
const execStart = Date.now()
|
|
548
|
-
|
|
1363
|
+
let exitCode: number
|
|
1364
|
+
let output: string
|
|
1365
|
+
let stdout: string
|
|
1366
|
+
|
|
1367
|
+
if (remoteName) {
|
|
1368
|
+
// 远程执行
|
|
1369
|
+
const result = await executeRemoteCommand(remoteName, stepResult.command)
|
|
1370
|
+
exitCode = result.exitCode
|
|
1371
|
+
output = result.output
|
|
1372
|
+
stdout = result.stdout
|
|
1373
|
+
} else {
|
|
1374
|
+
// 本地执行
|
|
1375
|
+
const result = await executeCommand(stepResult.command)
|
|
1376
|
+
exitCode = result.exitCode
|
|
1377
|
+
output = result.output
|
|
1378
|
+
stdout = result.stdout
|
|
1379
|
+
}
|
|
549
1380
|
const execDuration = Date.now() - execStart
|
|
550
1381
|
|
|
1382
|
+
// 判断命令是否成功
|
|
1383
|
+
// 退出码 141 = 128 + 13 (SIGPIPE),是管道正常关闭时的信号
|
|
1384
|
+
// 例如:ps aux | head -3,head 读完 3 行就关闭管道,ps 收到 SIGPIPE
|
|
1385
|
+
// 但如果退出码是 141 且没有 stdout 输出,说明可能是真正的错误
|
|
1386
|
+
const isSigpipeWithOutput = exitCode === 141 && stdout.trim().length > 0
|
|
1387
|
+
const isSuccess = exitCode === 0 || isSigpipeWithOutput
|
|
1388
|
+
|
|
551
1389
|
// 保存到执行历史
|
|
552
1390
|
const executedStep: ExecutedStep = {
|
|
553
1391
|
command: stepResult.command,
|
|
@@ -559,21 +1397,34 @@ program
|
|
|
559
1397
|
}
|
|
560
1398
|
executedSteps.push(executedStep)
|
|
561
1399
|
|
|
562
|
-
// 记录到 pls
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1400
|
+
// 记录到 pls 历史(远程模式记录到远程历史)
|
|
1401
|
+
if (remoteName) {
|
|
1402
|
+
addRemoteHistory(remoteName, {
|
|
1403
|
+
userPrompt:
|
|
1404
|
+
currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${stepResult.reasoning || prompt}`,
|
|
1405
|
+
command: stepResult.command,
|
|
1406
|
+
aiGeneratedCommand: stepResult.aiGeneratedCommand,
|
|
1407
|
+
userModified: stepResult.userModified || false,
|
|
1408
|
+
executed: true,
|
|
1409
|
+
exitCode,
|
|
1410
|
+
output,
|
|
1411
|
+
})
|
|
1412
|
+
} else {
|
|
1413
|
+
addHistory({
|
|
1414
|
+
userPrompt:
|
|
1415
|
+
currentStepNumber === 1 ? prompt : `[步骤${currentStepNumber}] ${stepResult.reasoning || prompt}`,
|
|
1416
|
+
command: stepResult.command,
|
|
1417
|
+
aiGeneratedCommand: stepResult.aiGeneratedCommand, // AI 原始命令
|
|
1418
|
+
userModified: stepResult.userModified || false,
|
|
1419
|
+
executed: true,
|
|
1420
|
+
exitCode,
|
|
1421
|
+
output,
|
|
1422
|
+
})
|
|
1423
|
+
}
|
|
573
1424
|
|
|
574
1425
|
// 显示结果
|
|
575
1426
|
console.log('')
|
|
576
|
-
if (
|
|
1427
|
+
if (isSuccess) {
|
|
577
1428
|
if (currentStepNumber === 1 && stepResult.needsContinue !== true) {
|
|
578
1429
|
// 单步命令
|
|
579
1430
|
console2.success(`执行完成 ${console2.formatDuration(execDuration)}`)
|
|
@@ -627,25 +1478,92 @@ program
|
|
|
627
1478
|
})()
|
|
628
1479
|
})
|
|
629
1480
|
|
|
1481
|
+
/**
|
|
1482
|
+
* 执行远程命令
|
|
1483
|
+
* 如果设置了工作目录,自动添加 cd 前缀
|
|
1484
|
+
*/
|
|
1485
|
+
async function executeRemoteCommand(
|
|
1486
|
+
remoteName: string,
|
|
1487
|
+
command: string
|
|
1488
|
+
): Promise<{ exitCode: number; output: string; stdout: string; stderr: string }> {
|
|
1489
|
+
let stdout = ''
|
|
1490
|
+
let stderr = ''
|
|
1491
|
+
|
|
1492
|
+
// 如果有工作目录,自动添加 cd 前缀
|
|
1493
|
+
const workDir = getRemoteWorkDir(remoteName)
|
|
1494
|
+
const actualCommand = workDir ? `cd ${workDir} && ${command}` : command
|
|
1495
|
+
|
|
1496
|
+
console.log('') // 空行
|
|
1497
|
+
|
|
1498
|
+
// 计算命令框宽度,让分隔线长度一致
|
|
1499
|
+
const lines = command.split('\n')
|
|
1500
|
+
const maxContentWidth = Math.max(...lines.map(l => console2.getDisplayWidth(l)))
|
|
1501
|
+
const boxWidth = Math.max(maxContentWidth + 4, console2.getDisplayWidth('生成命令') + 6, 20)
|
|
1502
|
+
console2.printSeparator(`远程输出 (${remoteName})`, boxWidth)
|
|
1503
|
+
|
|
1504
|
+
try {
|
|
1505
|
+
const result = await sshExec(remoteName, actualCommand, {
|
|
1506
|
+
onStdout: (data) => {
|
|
1507
|
+
stdout += data
|
|
1508
|
+
process.stdout.write(data)
|
|
1509
|
+
},
|
|
1510
|
+
onStderr: (data) => {
|
|
1511
|
+
stderr += data
|
|
1512
|
+
process.stderr.write(data)
|
|
1513
|
+
},
|
|
1514
|
+
})
|
|
1515
|
+
|
|
1516
|
+
if (stdout || stderr) {
|
|
1517
|
+
console2.printSeparator('', boxWidth)
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
return {
|
|
1521
|
+
exitCode: result.exitCode,
|
|
1522
|
+
output: stdout + stderr,
|
|
1523
|
+
stdout,
|
|
1524
|
+
stderr,
|
|
1525
|
+
}
|
|
1526
|
+
} catch (error: any) {
|
|
1527
|
+
console2.printSeparator('', boxWidth)
|
|
1528
|
+
console2.error(error.message)
|
|
1529
|
+
return {
|
|
1530
|
+
exitCode: 1,
|
|
1531
|
+
output: error.message,
|
|
1532
|
+
stdout: '',
|
|
1533
|
+
stderr: error.message,
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
630
1538
|
// 自定义帮助信息
|
|
631
1539
|
program.addHelpText(
|
|
632
1540
|
'after',
|
|
633
1541
|
`
|
|
634
1542
|
${chalk.bold('示例:')}
|
|
635
|
-
${chalk.hex(
|
|
636
|
-
${chalk.hex(
|
|
637
|
-
${chalk.hex(
|
|
638
|
-
${chalk.hex(
|
|
639
|
-
${chalk.hex(
|
|
640
|
-
${chalk.hex(
|
|
641
|
-
${chalk.hex(
|
|
642
|
-
${chalk.hex(
|
|
643
|
-
${chalk.hex(
|
|
644
|
-
${chalk.hex(
|
|
645
|
-
${chalk.hex(
|
|
646
|
-
${chalk.hex(
|
|
647
|
-
${chalk.hex(
|
|
648
|
-
${chalk.hex(
|
|
1543
|
+
${chalk.hex(getThemeColors().primary)('pls 安装 git')} 让 AI 生成安装 git 的命令
|
|
1544
|
+
${chalk.hex(getThemeColors().primary)('pls 查找大于 100MB 的文件')} 查找大文件
|
|
1545
|
+
${chalk.hex(getThemeColors().primary)('pls 删除刚才创建的文件')} AI 会参考历史记录
|
|
1546
|
+
${chalk.hex(getThemeColors().primary)('pls --debug 压缩 logs 目录')} 显示调试信息
|
|
1547
|
+
${chalk.hex(getThemeColors().primary)('pls chat tar 命令怎么用')} AI 对话模式
|
|
1548
|
+
${chalk.hex(getThemeColors().primary)('pls chat clear')} 清空对话历史
|
|
1549
|
+
${chalk.hex(getThemeColors().primary)('pls history')} 查看 pls 命令历史
|
|
1550
|
+
${chalk.hex(getThemeColors().primary)('pls history clear')} 清空历史记录
|
|
1551
|
+
${chalk.hex(getThemeColors().primary)('pls alias')} 查看命令别名
|
|
1552
|
+
${chalk.hex(getThemeColors().primary)('pls alias add disk "查看磁盘"')} 添加别名
|
|
1553
|
+
${chalk.hex(getThemeColors().primary)('pls disk')} 使用别名(等同于 pls @disk)
|
|
1554
|
+
${chalk.hex(getThemeColors().primary)('pls hook')} 查看 shell hook 状态
|
|
1555
|
+
${chalk.hex(getThemeColors().primary)('pls hook install')} 安装 shell hook(增强功能)
|
|
1556
|
+
${chalk.hex(getThemeColors().primary)('pls hook uninstall')} 卸载 shell hook
|
|
1557
|
+
${chalk.hex(getThemeColors().primary)('pls upgrade')} 升级到最新版本
|
|
1558
|
+
${chalk.hex(getThemeColors().primary)('pls config')} 交互式配置
|
|
1559
|
+
${chalk.hex(getThemeColors().primary)('pls config list')} 查看当前配置
|
|
1560
|
+
|
|
1561
|
+
${chalk.bold('远程执行:')}
|
|
1562
|
+
${chalk.hex(getThemeColors().primary)('pls remote')} 查看远程服务器列表
|
|
1563
|
+
${chalk.hex(getThemeColors().primary)('pls remote add myserver root@1.2.3.4')} 添加服务器
|
|
1564
|
+
${chalk.hex(getThemeColors().primary)('pls remote test myserver')} 测试连接
|
|
1565
|
+
${chalk.hex(getThemeColors().primary)('pls -r myserver 查看磁盘')} 在远程服务器执行
|
|
1566
|
+
${chalk.hex(getThemeColors().primary)('pls remote hook install myserver')} 安装远程 Shell Hook
|
|
649
1567
|
`
|
|
650
1568
|
)
|
|
651
1569
|
|